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.3 2001/06/21 07:16:23 joyk 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 fullPlotChild = 0;
    private static final int sparsePlotChild = 1;

    private TimeStamp timeStamp = TimeStamp.sharedInstance();

        private boolean drawBlocks = true;  // default: draw Lego blocks
        // Default:  draw wire frame while animating if # populated bins > numWireLines/2
        private boolean linesWhileAnim = true; 
    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
        private boolean shapeIsLego = false;


    /**
     * @param data Bin Contents
     */
    public Node buildContent(NormalizedBinned2DData data)
    {
        // Build switch node to switch between full plot and sparse plot
        targetSwitch = new Switch();
        targetSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);

        // Create shape and geometry for full plot
        if (drawBlocks) {
                    shape = createShape();
                    shapeIsLego = true;
                    shape.setCapability(shape.ALLOW_GEOMETRY_WRITE);
                    shape.setCapability(shape.ALLOW_APPEARANCE_WRITE);
                    shape.setGeometry(buildGeometry(data));     // draw lego as full plot
                }
                else {
                    shape = createLineShape();
                    shapeIsLego = false;
                    shape.setCapability(shape.ALLOW_GEOMETRY_WRITE);
                    popBins = calcPopBins(data);   // buildWireGeometry won't calculate # populated bins
                    int[] binInc = {1,1};
                    shape.setGeometry(buildWireGeometry(data, binInc));  // draw full "wire frame" plot
                }
                
                lineShape = createLineShape();
        lineShape.setCapability(shape.ALLOW_GEOMETRY_WRITE);

        targetSwitch.addChild(shape);       // full plot should be added first
        targetSwitch.addChild(lineShape);   // sparse "wire-frame" added second

                //If necessary, create geometry for a sparsified plot
        if (linesWhileAnim && shapeIsLego || 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(fullPlotChild);    // full plot displayed by default
                
                System.out.println("buildContent: linesWhileAnim = "+linesWhileAnim+", popBins = "+popBins+", numWireLines = "+numWireLines+", shapeIsLego = "+shapeIsLego);

        return targetSwitch;
    }
    public void updatePlot(NormalizedBinned2DData data)
    {
                // Create geometry for full plot
        if (drawBlocks) {
                    if (!shapeIsLego) {
                        shape.setAppearance(createMaterialAppearance());
                        shapeIsLego = true;
                    }
                    shape.setGeometry(buildGeometry(data));
                }
                else {
                    popBins = calcPopBins(data);        // buildWireGeometry won't calculate # populated bins
                    int[] binInc = {1,1};
                    if (shapeIsLego) {
                        shape.setAppearance(createWireFrameAppearance());
                        shapeIsLego = false;
                    }
                    shape.setGeometry(buildWireGeometry(data,binInc));
                }
                
                //If necessary, create geometry for a sparsified plot
                System.out.println("in updatePlot: linesWhileAnim = "+linesWhileAnim+", popBins = "+popBins+", numWireLines = "+numWireLines+", shapeIsLego = "+shapeIsLego);
                if (linesWhileAnim && (shapeIsLego || 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;
    }

        public boolean getDrawBlocks()
    {
        return drawBlocks;
    }

    public void setDrawBlocks(boolean b)
    {
        drawBlocks = b;
    }
        
        public boolean getLinesWhileAnim()
    {
        return linesWhileAnim;
    }

    public void setLinesWhileAnim(boolean b)
    {
        linesWhileAnim = b;
    }

    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)
    {
        // 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(data.xBins(), data.yBins());
            return buildWireGeometry(data, wireBinInc);
        }
        
    private Geometry buildWireGeometry(NormalizedBinned2DData data, int[] wireBinInc)
    {
        timeStamp.print("Starting LegoBuilder.buildWireGeometry()");

        int nXbins = data.xBins();
        int nYbins = data.yBins();

        // 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;
        }
                System.out.println("in calcXYfactors: " + binInc[0] + " " + binInc[1]);
        return binInc;
    }
    
    // calcPopBins - method to calculate the number of bins that are != zero in the
    //               normalized data.  (This routine is called when the lego will not be built.
    //               Otherwise the lego sets popBins while it's building its geometry.)
    private int calcPopBins(NormalizedBinned2DData data)
    {
    int nXbins = data.xBins();
        int nYbins = data.yBins();
        int numPopBins = 0;
    float x = -1.f/nXbins - .5f;
    for (int k=-1; k < nXbins; k++, x += 1.f/nXbins)
    {
        float y = -1.f/nYbins - .5f;
        for (int l=-1; l < nYbins; l++, y += 1.f/nYbins)
        {
        float z = data.zAt(k,l);
        if (z != 0)
            ++numPopBins;            // keep track of how many bins != 0
            }
        }
        return numPopBins;
    }
}