// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/j3d/com/bbn/openmap/tools/j3d/OMGraphicUtil.java,v $ // $RCSfile: OMGraphicUtil.java,v $ // $Revision: 1.7 $ // $Date: 2009/02/25 22:34:04 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.tools.j3d; import java.awt.Color; import java.awt.Point; import java.awt.Shape; import java.awt.geom.FlatteningPathIterator; import java.awt.geom.PathIterator; import java.util.HashSet; import java.util.Iterator; import javax.media.j3d.Appearance; import javax.media.j3d.ColoringAttributes; import javax.media.j3d.LineArray; import javax.media.j3d.LineStripArray; import javax.media.j3d.Material; import javax.media.j3d.PolygonAttributes; import javax.media.j3d.Shape3D; import javax.media.j3d.TriangleStripArray; import javax.vecmath.Color3f; import javax.vecmath.Color4b; import javax.vecmath.Point3d; import com.bbn.openmap.omGraphics.OMColor; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMGrid; import com.bbn.openmap.omGraphics.grid.GridData; import com.bbn.openmap.omGraphics.grid.OMGridGenerator; import com.bbn.openmap.omGraphics.grid.SimpleColorGenerator; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.Debug; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; import com.sun.j3d.utils.geometry.Stripifier; import com.sun.j3d.utils.geometry.Triangulator; /** * This class handles translating OMGraphics into a Java 3D Scene. * * @author dietrick */ public class OMGraphicUtil { public final static int DEFAULT_NPOINTS_BUFFER_SIZE = 100; public final static Iterator NULL_ITERATOR = new HashSet().iterator(); /** * Method takes an OMGraphic, creates one or more Shape3D objects * out of it, and returns an Iterator containing them. * * @param graphic the OMGraphic. * @return Iterator containing Shape3D objects. */ public static Iterator createShape3D(OMGraphic graphic) { return createShape3D(graphic, 0); } /** * Method takes an OMGraphic, creates one or more Shape3D objects * out of it, and returns an Iterator containing them. * * @param graphic the OMGraphic. * @param baselineHeight the baselined height for all the graphics * on the list. * @return Iterator containing Shape3D objects. */ public static Iterator createShape3D(OMGraphic graphic, double baselineHeight) { Debug.message("3detail", "OMGraphicUtil.createShape3D()"); boolean DEBUG_SHAPE = Debug.debugging("3dshape"); if (graphic == null) { return NULL_ITERATOR; } if (graphic instanceof OMGraphicList) { HashSet set = new HashSet(); for (OMGraphic subgraphic : (OMGraphicList) graphic) { Debug.message("3detail", "OMGraphicUtil.createShape3D(): recursivly adding list..."); Iterator iterator = createShape3D(subgraphic, baselineHeight); while (iterator.hasNext()) { set.add(iterator.next()); } } return set.iterator(); } else { if (DEBUG_SHAPE) { Debug.output("OMGraphicUtil.createShape3D(): adding shape..."); } Shape shape = graphic.getShape(); if (shape != null) { // Handle the shapes, depending on if they should // be filled or not... // First, determine wether this is a line or // polygon thingy, and set the color accordingly. // Text should be handled differently - might need // to render text, and the background block. if (graphic.shouldRenderFill()) { // Do polygons. return createShape3D(shape, baselineHeight, graphic.getFillColor(), true); } else if (graphic.shouldRenderEdge()) { // Might as well make sure it's not totally // clear before creating lines. return createShape3D(shape, baselineHeight, graphic.getDisplayColor(), false); } else if (DEBUG_SHAPE) { Debug.output("OMGraphicUtil.createShape3D(): can't render graphic"); } } else if (DEBUG_SHAPE) { Debug.output("OMGraphicUtil.createShape3D(): shape from graphic is null"); } } return NULL_ITERATOR; } /** * Create an Iterator containing a set of Shape3D objects, created * from OMGrid. Currently only works for OMGrids containing * GridData.Int data. * * @param grid the OMGrid to create a 3D terrain object from. * @param baselineHeight the baselined height for all the values * in the grid, if the OMGridGenerator wants to use it. * @param projection the map projection * @return an iterator containing all Shape3D objects */ public static Iterator createShape3D(OMGrid grid, double baselineHeight, Projection projection) { TriangleStripArray gridStrip; if (grid.getRenderType() != OMGraphic.RENDERTYPE_LATLON) { Debug.error("OMGraphicUtil.createShape3D: can't handle non-LATLON grids yet"); return NULL_ITERATOR; } boolean DEBUG = false; if (Debug.debugging("3dgrid")) { DEBUG = true; } // if (grid.getGenerator() == null && grid.getFillColor() == // OMColor.clear) { // return createWireFrame(grid, baselineHeight, projection); // } Color fColor = grid.getFillColor(); Color lColor = grid.getLineColor(); boolean polyline = (fColor == OMColor.clear); if (DEBUG) { Debug.output("Polyline = " + polyline); } Color3f fillcolor = new Color3f(fColor); Color3f linecolor = new Color3f(lColor); int numRows = grid.getRows(); int numCols = grid.getColumns(); // create triangle strip for twist int stripCount = numRows - 1; int numberVerticesPerStrip = numCols * 2; int[] stripCounts = new int[stripCount]; for (int i = 0; i < stripCount; i++) { stripCounts[i] = numberVerticesPerStrip; } LatLonPoint anchorLL = new LatLonPoint.Double(grid.getLatitude(), grid.getLongitude()); // Point anchorP = projection.forward(anchorLL); double vRes = grid.getVerticalResolution(); double hRes = grid.getHorizontalResolution(); gridStrip = new TriangleStripArray(stripCount * numberVerticesPerStrip, TriangleStripArray.COORDINATES | TriangleStripArray.COLOR_3 | TriangleStripArray.NORMALS, stripCounts); // OK, what you want to do is calculate the index of the // vertices, add the correct multiplication of offsets in // degree space, and then inverse project that point to get // the actual coordinates. Point p = new Point(); int pointer = 0; GridData gridData = grid.getData(); if (!(gridData instanceof GridData.Int)) { // Need to fix this to work with all GridData types! Debug.error("OMGrid.interpValueAt only works for integer data."); } int[][] data = ((GridData.Int) gridData).getData(); boolean major = gridData.getMajor(); // int[][] data = grid.getData(); // boolean major = grid.getMajor(); int dataPoint; Color3f color; SimpleColorGenerator generator = null; OMGridGenerator tempGen = grid.getGenerator(); if (tempGen instanceof SimpleColorGenerator) { generator = (SimpleColorGenerator) tempGen; } for (int j = 0; j < numRows - 1; j++) { if (DEBUG) { Debug.output("Creating strip " + j); } // I think the '-' should be '+'... (changed, DFD) double lat1 = anchorLL.getY() + (j * vRes); double lat2 = anchorLL.getY() + (( j + 1.0) * vRes); for (int k = 0; k < numCols; k++) { if (DEBUG) { Debug.output(" working row " + k); } double lon = anchorLL.getX() + (k * hRes); projection.forward(lat1, lon, p); if (major) { dataPoint = data[k][j]; } else { dataPoint = data[j][k]; } gridStrip.setCoordinate(pointer, new Point3d((float) p.getX(), (float) dataPoint, (float) p.getY())); if (DEBUG) { Debug.output(" 1st coord " + p.getX() + ", " + dataPoint + ", " + p.getY()); } projection.forward(lat2, lon, p); if (major) { dataPoint = data[k][j + 1]; } else { dataPoint = data[j + 1][k]; } gridStrip.setCoordinate(pointer + 1, new Point3d((float) p.getX(), (float) dataPoint, (float) p.getY())); if (DEBUG) { Debug.output(" 2nd coord " + p.getX() + ", " + dataPoint + ", " + p.getY()); } // Need the TriangleStripArray.COLOR_3 Attribute set // above if (generator == null) { if (polyline) { color = linecolor; } else { color = fillcolor; } } else { color = new Color3f(new Color(generator.calibratePointValue(dataPoint))); } gridStrip.setColor(pointer++, color); gridStrip.setColor(pointer++, color); // else // pointer += 2; } } Shape3D shape = new Shape3D(gridStrip); Appearance appear = new Appearance(); PolygonAttributes polyAttrib = new PolygonAttributes(); if (polyline) { polyAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE); } polyAttrib.setCullFace(PolygonAttributes.CULL_NONE); appear.setPolygonAttributes(polyAttrib); shape.setAppearance(appear); HashSet set = new HashSet(); set.add(shape); return set.iterator(); } /** * Create an Iterator containing a set of Shape3D objects, created * from a java.awt.Shape. * * @param shape java.awt.Shape object. * @param baselineHeight the baselined height for all the values * in the grid, if the OMGridGenerator wants to use it. * @param color the color to make the object. * @param filled whether or not to fill the object with color. * @return Iterator containing Shape3D objects created from shape * object. */ public static Iterator createShape3D(Shape shape, double baselineHeight, Color color, boolean filled) { int bufferSize = DEFAULT_NPOINTS_BUFFER_SIZE; double[] data = expandArrayD(bufferSize, null); int dataIndex = 0; // How many spaces are left in the buffer. int refreshCounter = bufferSize; int[] stripCount = new int[1]; stripCount[0] = 0; // null is AffineTransform... PathIterator pi2 = shape.getPathIterator(null); // flatness might need to be calculated, based // on scale or something. Depends on how many // points there should be for an accurate // shape rendition. float flatness = .25f; FlatteningPathIterator pi = new FlatteningPathIterator(pi2, flatness); double[] coords = new double[6]; double pntx = 0; double pnty = 0; double pntz = baselineHeight; HashSet set = new HashSet(); Shape3D shape3D = null; Debug.message("3detail", "OMGraphicUtil.createShape3D(): figuring out coordinates"); // Creating the data[] while (!pi.isDone()) { int type = pi.currentSegment(coords); switch (type) { case PathIterator.SEG_MOVETO: if (dataIndex != 0) { shape3D = createShape3D(data, dataIndex, stripCount, color, filled); if (shape3D != null) { set.add(shape3D); } data = expandArrayD(bufferSize, null); dataIndex = 0; } case PathIterator.SEG_LINETO: // SEG_MOVETO is the first point of // the shape, SEG_LINETO are the // middle and end points. SEG_CLOSE // confirms the close, but we don't // need it. pntx = coords[0]; pnty = coords[1]; if (Debug.debugging("3detail")) { Debug.output("Shape coordinates: " + pntx + ", " + pnty); } // Get Z here, if you want to set the height of the // coordinate... // pntz = // See if there is space in the buffer. if (dataIndex >= data.length) { data = expandArrayD(bufferSize, data); refreshCounter = bufferSize; } data[dataIndex++] = pntx; data[dataIndex++] = pntz; data[dataIndex++] = pnty; // data[dataIndex++] = pntx; // data[dataIndex++] = pnty; // data[dataIndex++] = pntz; stripCount[0]++; refreshCounter -= 3; break; default: // Do nothing, because it's a repeat // of the last SEG_LINETO point. Debug.message("3detail", "Shape coordinates: " + coords[0] + ", " + coords[1] + " rounding out SEG_CLOSE"); } pi.next(); } if (dataIndex != 0) { shape3D = createShape3D(data, dataIndex, stripCount, color, filled); if (shape3D != null) { set.add(shape3D); } } return set.iterator(); } /** * Create a Shape3D from raw components. May return null. Assumes * a stripCount array of size one. * * @param data Description of the Parameter * @param realDataIndex Description of the Parameter * @param stripCount Description of the Parameter * @param color Description of the Parameter * @param filled Description of the Parameter * @return Description of the Return Value */ public static Shape3D createShape3D(double[] data, int realDataIndex, int[] stripCount, Color color, boolean filled) { try { double[] newData = new double[realDataIndex]; System.arraycopy(data, 0, newData, 0, realDataIndex); if (filled) { return createFilled(newData, stripCount, color); } else { return createEdges(newData, color); } } catch (java.lang.IllegalArgumentException iae) { Debug.error("OMGraphicUtil.createShape3D(): IllegalArgumentException caught: \n" + iae.toString()); StringBuffer sb = new StringBuffer(); for (int i = 0; i < stripCount.length; i++) { sb.append("{" + stripCount[i] + "}"); } Debug.output("Something funny happened on " + (filled ? "filled" : "edge") + " data[" + data.length + "], reflecting " + data.length / 3 + " nodes, with stripCount[" + stripCount.length + "] " + sb.toString()); } return null; } public static Shape3D createFilled(double[] data, int[] stripCount, Color color) throws IllegalArgumentException { // j + 1 is the number of shapes. // Might have to track the number of coordinates per shape. // Use a Triangulator to take geometry data and create // polygons out of it. Debug.message("3detail", "OMGraphicUtil: adding polygon, data length " + data.length + ", reflecting " + data.length / 3 + " nodes, with a strip count of " + stripCount.length); GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); gi.setCoordinates(data); gi.setStripCounts(stripCount); Triangulator tr = new Triangulator(); // Triangulator tr = new Triangulator(1); Debug.message("3detail", "OMGraphicUtil: begin triangulation"); tr.triangulate(gi); Debug.message("3detail", "OMGraphicUtil: end triangulation"); gi.recomputeIndices(); NormalGenerator ng = new NormalGenerator(); ng.generateNormals(gi); gi.recomputeIndices(); Stripifier st = new Stripifier(); st.stripify(gi); gi.recomputeIndices(); Shape3D shape3D = new Shape3D(); shape3D.setAppearance(createMaterialAppearance(color)); shape3D.setGeometry(gi.getGeometryArray()); return shape3D; } public static Shape3D createEdges(double[] data, Color color) throws IllegalArgumentException { int numPoints = data.length / 3; // Create a line for the polyline. Debug.message("3detail", "OMGraphicUtil: adding polyline of " + numPoints + " points."); LineStripArray la = new LineStripArray(numPoints, LineArray.COORDINATES | LineArray.COLOR_4, new int[] { numPoints }); la.setCoordinates(0, data); Color4b[] colors = createColorArray(numPoints, color); la.setColors(0, colors); return new Shape3D(la); } public static Appearance createMaterialAppearance(Color color) { Appearance materialAppear = new Appearance(); PolygonAttributes polyAttrib = new PolygonAttributes(); polyAttrib.setCullFace(PolygonAttributes.CULL_NONE); materialAppear.setPolygonAttributes(polyAttrib); Material material = new Material(); // Might want to look into using a Color4b at some point material.setAmbientColor(new Color3f(color)); materialAppear.setMaterial(material); return materialAppear; } public static Appearance createWireFrameAppearance(Color color) { Appearance materialAppear = new Appearance(); PolygonAttributes polyAttrib = new PolygonAttributes(); polyAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE); materialAppear.setPolygonAttributes(polyAttrib); ColoringAttributes redColoring = new ColoringAttributes(); redColoring.setColor(1.0f, 0.0f, 0.0f); materialAppear.setColoringAttributes(redColoring); return materialAppear; } /** * Create an array of Color4b objects for an OMGraphic * representation. The colors in an OMGraphic are ARGB, which is * why this creates a Color4b object. Since all the parts of an * OMGraphic are colored the same, create the array of colors to * be all the same color retrieved from the OMGraphic. * * @param size Description of the Parameter * @param color Description of the Parameter * @return Description of the Return Value */ public static Color4b[] createColorArray(int size, Color color) { Color4b[] colors = new Color4b[size]; for (int i = 0; i < size; i++) { colors[i] = new Color4b(color); } return colors; } /** * Create an array to hold double data for 3d polygons and lines. * * @param bufferSize the number of * * <pre> * points * </pre> * * to buffer. Equals three doubles per point. * @param currentArray if not null, will create an array the size * of the current array plus the size needed to hold the * desired number of points. * @return a double[]. */ public static double[] expandArrayD(int bufferSize, double[] currentArray) { if (currentArray == null) { return new double[bufferSize * 3]; } int length = currentArray.length; double[] ret = new double[length + bufferSize * 3]; System.arraycopy(currentArray, 0, ret, 0, length); return ret; } /** * Create an array to hold float data for 3d polygons and lines. * * @param bufferSize the number of * * <pre> * points * </pre> * * to buffer. Equals three floats per point. * @param currentArray if not null, will create an array the size * of the current array plus the size needed to hold the * desired number of points. * @return a float[]. */ public static float[] expandArrayF(int bufferSize, float[] currentArray) { if (currentArray == null) { return new float[bufferSize * 3]; } int length = currentArray.length; float[] ret = new float[length + bufferSize * 3]; System.arraycopy(currentArray, 0, ret, 0, length); return ret; } }