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 8584 2006-08-10 23:06:37Z duns $
*/
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;
}
}