HOME / Projects / FreeHEP / freehep / org / freehep / j3d / plot / LegoBuilder.java
package org.freehep.j3d.plot;

import javax.media.j3d.Node;
import com.sun.j3d.utils.geometry.*;
import javax.media.j3d.*;
import javax.vecmath.*;

/**
 * @author Joy Kyriakopulos (joyk@fnal.gov)
 * @version $Id: LegoBuilder.java,v 1.1 2001/05/19 00:11:54 tonyj Exp $
 */
public class LegoBuilder extends AbstractPlotBuilder
{
    private static final Color3b grey = new Color3b((byte) 50, (byte) 50, (byte) 50);
    private static final Rainbow rainbow = new Rainbow();
    
    private static final Vector3f xnormal = new Vector3f(1f,0,0);
    private static final Vector3f xabnormal = new Vector3f(-1f,0,0);
    private static final Vector3f ynormal = new Vector3f(0,1f,0);
    private static final Vector3f yabnormal = new Vector3f(0,-1f,0);
    private static final Vector3f znormal = new Vector3f(0,0,1f);                                                         
    private static final Vector3f zabnormal = new Vector3f(0,0,-1f);
    private static final int legoChild = 0;
    private static final int wireChild = 1;
    
    private TimeStamp timeStamp = TimeStamp.sharedInstance();
    
    private int numWireLines = 600;
    
    private int bcur;
    private QuadArray quad;
    private LineArray line;
    private Shape3D shape;
    private Shape3D lineShape;
    private Switch targetSwitch;
    private int popBins = 0;        // number of populated bins in lego
    

    /**
     * @param data Bin Contents
     */
    public Node buildContent(NormalizedBinned2DData data)
    {
        // build switch node to switch between lego & wire-frame
        targetSwitch = new Switch();
        targetSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);

        shape = createShape();
        shape.setCapability(shape.ALLOW_GEOMETRY_WRITE);
        shape.setGeometry(buildGeometry(data));
        lineShape = createLineShape();
        lineShape.setCapability(shape.ALLOW_GEOMETRY_WRITE);
        
        targetSwitch.addChild(shape);       // lego should be added first
        targetSwitch.addChild(lineShape);   // "wire-frame" added second
        
        if (popBins > numWireLines/2) {        // if too many bins populated
            lineShape.setGeometry(buildWireGeometry(data));  // build alternate view
            targetSwitch.setUserData(Boolean.TRUE);
        }
        else
            targetSwitch.setUserData(Boolean.FALSE);
        
        targetSwitch.setWhichChild(legoChild);    // lego displayed by default

        return targetSwitch;
    }
    public void updatePlot(NormalizedBinned2DData data)
    {
        shape.setGeometry(buildGeometry(data));
        if (popBins > numWireLines/2) {            // if too many bins populated
            lineShape.setGeometry(buildWireGeometry(data)); // build alternate view
            targetSwitch.setUserData(Boolean.TRUE);        // flags whether there is line content
        }
        else
            targetSwitch.setUserData(Boolean.FALSE);    // no line content to display
    }
    
    public int getNumWireLines()
    {
        return numWireLines;
    }
    
    public void setNumWireLines(int val)
    {
        numWireLines = val;
    }

    private Geometry buildGeometry(NormalizedBinned2DData data)
    {
        timeStamp.print("Starting LegoBuilder.buildContent()");
        
        int nXbins = data.xBins();
        int nYbins = data.yBins();
    
        // Create the coordinate and color arrays
        
        bcur = 0;
        int maxpoints = (nXbins+1) * (nYbins+1) * 4 * 3 * 4; // An over estimate
        quad = new QuadArray(maxpoints,QuadArray.COORDINATES+QuadArray.NORMALS+QuadArray.COLOR_3);
        
        // Create the floor
        
        drawXYrect(-.5f,-.5f,0,.5f,.5f,0,grey,znormal);
        
        float xBinWidth = 1.f/nXbins;
        float yBinWidth = 1.f/nYbins;
        float x = -xBinWidth - .5f;
        float xBinWAdj = 1.f/nXbins/100.f;
        float yBinWAdj = 1.f/nYbins/100.f;
        
        popBins = 0;
            
        // Note, we start a bin -1 on the X and Y axis. The getDataAt method always
        // returns 0 for elements outside of the legal bin range, so this, coupled with 
        // the fact that we never draw the tops of bins which have z=0, takes care of all
        // the edge effects.
        
        for (int k=-1; k < nXbins; k++, x += xBinWidth)
        {
            float y = -yBinWidth - .5f;
            for(int l=-1; l < nYbins; l++, y += yBinWidth)
            {
                float z = data.zAt(k,l);
                Color3b curColor = data.colorAt(k,l);
                
                // Construct colored, horizontal/top side of lego on the data point
                //                                          (X-Y plane at constant z)
                
                if (z != 0)    // skip drawing top if no height
                {           
                    ++popBins;            // keep track of how many bins > 0
                    drawXYrect(x+xBinWidth-xBinWAdj, y+yBinWidth-yBinWAdj, z,
                              x,                    y,                    z,
                              curColor, znormal);
                   // color bottom of bin (visible when top is scaled off & clipped)
                   drawXYrect(x+xBinWidth-xBinWAdj, y+yBinWidth-yBinWAdj, .001f,
                              x,                    y,                    .001f,
                              curColor, znormal);
                }
                
                // Construct sides between this and next Y bin                
                float nextZ = data.zAt(k,l+1);
                Color3b nextColor =  data.colorAt(k,l+1);
                if (z != 0)    {    // side of current bin - skip drawing if no height
                    drawXZrect(x,                    y+yBinWidth-yBinWAdj/2.f, z,
                               x+xBinWidth-xBinWAdj, y+yBinWidth-yBinWAdj/2.f, 0,
                               curColor, ynormal);
                    drawXZrect(x,                    y+yBinWidth-yBinWAdj, z,
                               x+xBinWidth-xBinWAdj, y+yBinWidth-yBinWAdj, 0,
                               curColor, yabnormal);  // inside - seen only if top clipped
                }
                if (nextZ != 0)    { // side of next bin - skip drawing if no height
                    drawXZrect(x,           y+yBinWidth, 0,
                               x+xBinWidth, y+yBinWidth, nextZ,
                               nextColor, yabnormal);
                    drawXZrect(x,           y+yBinWidth+yBinWAdj, 0,
                               x+xBinWidth, y+yBinWidth+yBinWAdj, nextZ,
                               nextColor, ynormal);  // inside - seen only if top clipped
                }
                
                // Construct sides between this and next X bin    
                nextZ = data.zAt(k+1,l);
                nextColor = data.colorAt(k+1,l);
                if (z != 0)     {    // side of current bin - skip drawing if no height
                       drawYZrect(x+xBinWidth-xBinWAdj/2.f, y,                    z,
                               x+xBinWidth-xBinWAdj/2.f, y+yBinWidth-yBinWAdj, 0,
                               curColor, xnormal);                    
                       drawYZrect(x+xBinWidth-xBinWAdj, y,                    z,
                               x+xBinWidth-xBinWAdj, y+yBinWidth-yBinWAdj, 0,
                               curColor, xabnormal);  // inside - seen only if top clipped                
                }
                if (nextZ != 0)     { // side of next bin - skip drawing if no height
                       drawYZrect(x+xBinWidth, y,           0,
                               x+xBinWidth, y+yBinWidth, nextZ,
                               nextColor, xabnormal);
                       drawYZrect(x+xBinWidth+xBinWAdj, y,           0,
                               x+xBinWidth+xBinWAdj, y+yBinWidth, nextZ,
                               nextColor, xnormal);   // inside - seen only if top clipped
                }
            }
        }
        timeStamp.print("finished, now finalizing, point count = "+bcur);
        return quad;
    }
    
    private Geometry buildWireGeometry(NormalizedBinned2DData data)
    {
        timeStamp.print("Starting LegoBuilder.buildWireGeometry()");
        
        int nXbins = data.xBins();
        int nYbins = data.yBins();
        
        // Compute x,y factors: will be > 1 if data is too large and
        // plot needs to be sparsified (i.e. made more sparse)
        int[] wireBinInc = calcXYfactors(nXbins, nYbins);  // change class for fact
    
        // Create the coordinate and color arrays
        
        bcur = 0;
        int maxpoints = ((nXbins/wireBinInc[0] + 1) * (nYbins/wireBinInc[1] + 1)) * 3 * 2 + 8; // An over estimate
        line = new LineArray(maxpoints,LineArray.COORDINATES+LineArray.NORMALS+LineArray.COLOR_3);
        
        float xBinWidth = (float)wireBinInc[0]/(float)(nXbins);
        float yBinWidth = (float)wireBinInc[1]/(float)(nYbins);;
        float x = - .5f;
            
        // Go through data, drawing a vertical line instead of a bin
        
        for (int k=0; k < nXbins; k+=wireBinInc[0], x += xBinWidth)
        {
            float y = - .5f;
            for (int l=0; l < nYbins; l+=wireBinInc[1], y += yBinWidth)
            {
                float z = data.zAt(k,l);
                Color3b curColor = data.colorAt(k,l);
                if (z != 0) {   // skip drawing if no height
                    drawVLine(x+xBinWidth/2.f, y+yBinWidth/2.f, 0.f, z, curColor, xnormal);
                }
            }
        }
        timeStamp.print("finished, now finalizing, point count = "+bcur);
        return line;
    }
        
    Shape3D createLineShape()    
    {
        Shape3D lineShape = new Shape3D();
        timeStamp.print("line geometry set");        
        lineShape.setAppearance(createWireFrameAppearance());        
        
        return lineShape;
    }
    
     Appearance createWireFrameAppearance()
     {
        Appearance materialAppear = new Appearance();
        LineAttributes lineAttrib = new LineAttributes();
        materialAppear.setLineAttributes(lineAttrib);
        ColoringAttributes redColoring = new ColoringAttributes();
        redColoring.setColor(1.0f, 0.0f, 0.0f);
        materialAppear.setColoringAttributes(redColoring);

        return materialAppear;
    }

    
    Shape3D createShape()    
    {
        Shape3D surface = new Shape3D();
        timeStamp.print("geometry set");        
        surface.setAppearance(createMaterialAppearance());        
        
        return surface;
    }

    private Appearance createMaterialAppearance() 
    {
        Appearance materialAppear = new Appearance();
        PolygonAttributes polyAttrib = new PolygonAttributes();
        polyAttrib.setCullFace(PolygonAttributes.CULL_NONE);
        materialAppear.setPolygonAttributes(polyAttrib);

        Material material = new Material();
        // set diffuse color to red (this color will only be used 
        //     if lighting disabled - per-vertex color overrides)
        material.setDiffuseColor(new Color3f(1.0f, 0.0f, 0.0f));
        materialAppear.setMaterial(material);

        return materialAppear;
    }
    
    // Construct colored line
    private void drawLine(float x1, float y1, float z1, 
                          float x2, float y2, float z2, 
                          Color3b lineColor, Vector3f normal)
    {
        line.setCoordinate(bcur,new Point3f(x1,y1,z1));
        line.setColor(bcur,lineColor);
        line.setNormal(bcur,normal);
        bcur++;

        line.setCoordinate(bcur,new Point3f(x2,y2,z2));
        line.setColor(bcur,lineColor);
        line.setNormal(bcur,normal);
        bcur++;

    }    

    // Construct colored, vertical line at constant X,Y
    private void drawVLine(float x,  float y, float z1, float z2, 
                            Color3b lineColor, Vector3f normal)
    {
        line.setCoordinate(bcur,new Point3f(x,y,z1));
        line.setColor(bcur,lineColor);
        line.setNormal(bcur,normal);
        bcur++;

        line.setCoordinate(bcur,new Point3f(x,y,z2));
        line.setColor(bcur,lineColor);
        line.setNormal(bcur,normal);
        bcur++;

    }    

    // Construct colored, horizontal rectangle in the X-Y plane at constant Z
    private void drawXYrect(float x1,  float y1, float z1, 
                            float x2,  float y2, float z2, 
                            Color3b rectColor, Vector3f normal)
    {
        quad.setCoordinate(bcur,new Point3f(x1,y1,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x1,y2,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x2,y2,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x2,y1,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;
    }
    // Construct colored, vertical rectangle in the X-Z plane at constant Y
    private void drawXZrect(float x1,  float y1, float z1, 
                            float x2,  float y2, float z2, 
                            Color3b rectColor, Vector3f normal)
    {
        quad.setCoordinate(bcur,new Point3f(x1,y1,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x2,y1,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x2,y1,z2));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x1,y1,z2));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;
    }    
    
    // Construct colored, vertical rectangle in the Y-Z plane at constant X
    private void drawYZrect(float x1,  float y1, float z1, 
                            float x2,  float y2, float z2, 
                            Color3b rectColor, Vector3f normal)
    {
        quad.setCoordinate(bcur,new Point3f(x1,y1,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x1,y2,z1));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x1,y2,z2));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;

        quad.setCoordinate(bcur,new Point3f(x1,y1,z2));
        quad.setColor(bcur,rectColor);
        quad.setNormal(bcur,normal);
        bcur++;
    }
    
    // Heuristic to calculate sparsification factors (i.e. to make plot 
    // more sparse) for both X and Y in cases where there's too much 
    // lego data to render in a short time during rotations, panning, 
    // zooming, etc.
    private int[] calcXYfactors(int nXbins, int nYbins)
    { 
        int[] binInc = {1,1};
        if (nXbins * nYbins > numWireLines) {
            double xyBinRatio = (double) nXbins / (double)nYbins;
            binInc[0] = (int) (nXbins / (Math.sqrt((double)numWireLines) / xyBinRatio));
            binInc[1] = (int) (nYbins / (Math.sqrt((double)numWireLines) * xyBinRatio));
            if (binInc[0] < 1) binInc[0] = 1;
            if (binInc[1] < 1) binInc[1] = 1;
        } 
        return binInc;
    }
}