/* * Project Info: http://jcae.sourceforge.net * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * (C) Copyright 2007, by EADS France */ package org.jcae.viewer3d.fd; import java.awt.Color; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.util.*; import java.util.logging.Logger; import javax.media.j3d.*; import javax.vecmath.Point3d; import org.jcae.viewer3d.fd.sd.*; import com.sun.j3d.utils.picking.PickIntersection; import com.sun.j3d.utils.picking.PickResult; import com.sun.j3d.utils.picking.PickTool; /** * @author nicolas * * To change the template for this generated type comment go to Window - * Preferences - Java - Code Generation - Code and Comments */ public class PL02BranchGroup extends BranchGroup { private final static Logger LOGGER=Logger.getLogger(PL02BranchGroup.class.getName()); protected FDProvider provider; //private HashSet currentSelection; protected ArrayList<Shape3D> allEdgeShapes; // one of the values above protected static final int PICK_FACES = 0; protected static final int PICK_EDGES = 1; protected static final int PICK_VERTICES = 2; protected int pickType; // Maintain a list of selected quads and edges protected ArrayList<SelectionQuad> selection = new ArrayList<SelectionQuad>(); protected ArrayList<SelectionEdge> edgeSelection = new ArrayList<SelectionEdge>(); protected Shape3D selectionShape, edgeSelectionShape; float[][] grid; private Map<Integer, Color> baseColor=new HashMap<Integer, Color>(); public PL02BranchGroup(FDProvider provider) { this.provider=provider; setUserData(this); setCapability(Node.ALLOW_BOUNDS_READ); setCapability(Group.ALLOW_CHILDREN_EXTEND); setCapability(BranchGroup.ALLOW_DETACH); setCapability(Node.ALLOW_PICKABLE_WRITE); update(); } public void update() { removeAllChildren(); setPickable(true); //currentSelection = new HashSet(); allEdgeShapes = new ArrayList<Shape3D>(); grid=new float[3][]; grid[0]=new float[provider.getXGridCount()]; grid[1]=new float[provider.getYGridCount()]; grid[2]=new float[provider.getZGridCount()]; for(int i=0; i<grid[0].length; i++) { grid[0][i]=(float)provider.getXGrid(i); } for(int i=0; i<grid[1].length; i++) { grid[1][i]=(float)provider.getYGrid(i); } for(int i=0; i<grid[2].length; i++) { grid[2][i]=(float)provider.getZGrid(i); } makeGroups(); makeWires(); } private Plate[] domainToPlates(FDDomain domain) { LOGGER.finest("<creating plates >"); int n=domain.getNumberOfXPlate()+domain.getNumberOfYPlate()+domain.getNumberOfZPlate(); LOGGER.finest("number of plates in domain is "+n); Plate[] plates=new Plate[n]; Iterator<int[]> it=domain.getXPlateIterator(); int i=0; while(it.hasNext()) { int[] indices=it.next(); Plate p=new PlateX(); p.position=indices[0]; p.min1=indices[1]; p.min2=indices[2]; p.max1=indices[3]; p.max2=indices[4]; plates[i]=p; i++; } if(i!=domain.getNumberOfXPlate()) throw new IllegalStateException(i+"!="+domain.getNumberOfXPlate()); it=domain.getYPlateIterator(); while(it.hasNext()) { int[] indices=it.next(); Plate p=new PlateY(); p.position=indices[0]; p.min1=indices[1]; p.min2=indices[2]; p.max1=indices[3]; p.max2=indices[4]; plates[i]=p; i++; } if(i!=domain.getNumberOfXPlate()+domain.getNumberOfYPlate()) throw new IllegalStateException(i+"!="+domain.getNumberOfXPlate()+domain.getNumberOfYPlate()); it=domain.getZPlateIterator(); while(it.hasNext()) { int[] indices=it.next(); Plate p=new PlateZ(); p.position=indices[0]; p.min1=indices[1]; p.min2=indices[2]; p.max1=indices[3]; p.max2=indices[4]; plates[i]=p; i++; } if(i!=n) throw new IllegalStateException(i+"!="+n); LOGGER.finest("</creating plates>"); return plates; } protected void makeGroups() { int totalQuads = 0; int totalInternalEdges = 0; int totalExternalEdges = 0; // loop over each group // loop over each group int[] groupID = provider.getDomainIDs(); for (int g = 0; g < groupID.length; ++g) { LOGGER.finest("generating java3d tree for group number "+groupID[g]); // Set of EdgeLine objects. Overlapping edges on the same line are // merged together HashMap<EdgeLine, EdgeLine> externalEdges = new HashMap<EdgeLine, EdgeLine>(); // Same trick for internal edges. HashMap<EdgeLine, EdgeLine> internalEdges = new HashMap<EdgeLine, EdgeLine>(); FDDomain fdDomain=(FDDomain) provider.getDomain(groupID[g]); baseColor.put(new Integer(g), fdDomain.getColor()); Plate[] plates = domainToPlates(fdDomain); if(plates.length==0) continue; // Create plates for this group FloatBuffer nioCoords = ByteBuffer.allocateDirect( plates.length * 4 * 3 * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer(); FloatBuffer nioColors = ByteBuffer.allocateDirect( plates.length * 4 * 3 * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer(); float[] baseColor = getColorForOrder(g, 0); //System.out.println(baseColor[0]+" "+baseColor[1]+" "+baseColor[2]); for (int np = 0; np < plates.length; ++np) { Plate p = plates[np]; // put coordinates nioCoords.put(p.getCoordinates(grid)); // put colors for the 4 vertices nioColors.put(baseColor); nioColors.put(baseColor); nioColors.put(baseColor); nioColors.put(baseColor); // Merge external edges addEdge(externalEdges, getLine(p, 2, p.min1), p.min2, p.max2); addEdge(externalEdges, getLine(p, 2, p.max1), p.min2, p.max2); addEdge(externalEdges, getLine(p, 1, p.min2), p.min1, p.max1); addEdge(externalEdges, getLine(p, 1, p.max2), p.min1, p.max1); // Merge internal edges for (int i = p.min1 + 1; i < p.max1; ++i) addEdge(internalEdges, getLine(p, 2, i), p.min2, p.max2); for (int j = p.min2 + 1; j < p.max2; ++j) addEdge(internalEdges, getLine(p, 1, j), p.min1, p.max1); } // use by reference array of colors => fast to change! QuadArray qa = new NioQuadArray(plates.length * 4, GeometryArray.COORDINATES | GeometryArray.COLOR_3); qa.setCoordRefBuffer(new J3DBuffer(nioCoords)); qa.setColorRefBuffer(new J3DBuffer(nioColors)); qa.setCapability(GeometryArray.ALLOW_COLOR_WRITE); qa.setCapabilityIsFrequent(GeometryArray.ALLOW_COLOR_WRITE); Appearance a = new Appearance(); PolygonAttributes pa = new PolygonAttributes( PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0); pa.setPolygonOffset(1); pa.setPolygonOffsetFactor(1); a.setPolygonAttributes(pa); Shape3D s3d = new Shape3D(qa, a); PickTool.setCapabilities(s3d, PickTool.INTERSECT_FULL); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_READ); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); s3d.setCapability(Node.ALLOW_PICKABLE_READ); s3d.setCapability(Node.ALLOW_PICKABLE_WRITE); s3d.setPickable(true); s3d.setUserData(new BehindShape(s3d, plates, g)); this.addChild(s3d); // Create edge shapes directly, don't make them appear in graph int nInternalEdges = 0; for (Iterator<EdgeLine> it = internalEdges.keySet().iterator(); it.hasNext();) { EdgeLine el = it.next(); nInternalEdges += el.getNumberOfEdges(); } if (nInternalEdges > 0) { DoubleBuffer nioInternalEdges = ByteBuffer.allocateDirect( nInternalEdges * 2 * 3 * 8).order(ByteOrder.nativeOrder()) .asDoubleBuffer(); // create edge coords for (Iterator<EdgeLine> it = internalEdges.keySet().iterator(); it .hasNext();) { EdgeLine el = it.next(); nioInternalEdges.put(el.getCoords(grid)); } LineArray la = new NioLineArray(nInternalEdges * 2, GeometryArray.COORDINATES | GeometryArray.COLOR_3); la.setCoordRefBuffer(new J3DBuffer(nioInternalEdges)); int colSize = nInternalEdges * 2 * 3; FloatBuffer nioInternalColors = ByteBuffer.allocateDirect( colSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); float[] colors = getColorForOrder(g, 2); for (int i = 0; i < colSize; i += 3) nioInternalColors.put(colors); la.setColorRefBuffer(new J3DBuffer(nioInternalColors)); la.setUserData(new int[]{g, 2}); a = new Appearance(); //pa = new PolygonAttributes(PolygonAttributes.POLYGON_LINE, // PolygonAttributes.CULL_NONE, 0); //pa.setPolygonOffset(4); //pa.setPolygonOffsetFactor(4); //a.setPolygonAttributes(pa); //LineAttributes lat = new LineAttributes(); //lat.setLineAntialiasingEnable(true); //a.setLineAttributes(lat); //RenderingAttributes ra = new RenderingAttributes(); //ra.setAlphaTestFunction(RenderingAttributes.GREATER); //ra.setAlphaTestValue(0.5f); //a.setRenderingAttributes(ra); s3d = new Shape3D(la, a); PickTool.setCapabilities(s3d, PickTool.INTERSECT_FULL); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_READ); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); s3d.setCapability(Node.ALLOW_PICKABLE_READ); s3d.setCapability(Node.ALLOW_PICKABLE_WRITE); s3d.setPickable(false); // by default, see actions s3d.setUserData(this); // this object will handle edges this.addChild(s3d); allEdgeShapes.add(s3d); } // Now, create external edge int nExternalEdges = 0; for (Iterator<EdgeLine> it = externalEdges.keySet().iterator(); it.hasNext();) { EdgeLine el = it.next(); nExternalEdges += el.getNumberOfEdges(); } if (nExternalEdges > 0) { DoubleBuffer nioExternalEdges = ByteBuffer.allocateDirect( nExternalEdges * 2 * 3 * 8).order(ByteOrder.nativeOrder()) .asDoubleBuffer(); // create edge coords for (Iterator<EdgeLine> it = externalEdges.keySet().iterator(); it .hasNext();) { EdgeLine el = it.next(); nioExternalEdges.put(el.getCoords(grid)); } LineArray la = new NioLineArray(nExternalEdges * 2, GeometryArray.COORDINATES | GeometryArray.COLOR_3); la.setCoordRefBuffer(new J3DBuffer(nioExternalEdges)); int colSize = nExternalEdges * 2 * 3; FloatBuffer nioExternalColors = ByteBuffer.allocateDirect( colSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); float[] colors = getColorForOrder(g, 4); for (int i = 0; i < colSize; i += 3) nioExternalColors.put(colors); la.setColorRefBuffer(new J3DBuffer(nioExternalColors)); la.setUserData(new int[]{g, 4}); a = new Appearance(); //pa = new PolygonAttributes(PolygonAttributes.POLYGON_LINE, // PolygonAttributes.CULL_NONE, 0); //pa.setPolygonOffset(3); //pa.setPolygonOffsetFactor(3); //a.setPolygonAttributes(pa); s3d = new Shape3D(la, a); PickTool.setCapabilities(s3d, PickTool.INTERSECT_FULL); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_READ); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); s3d.setCapability(Node.ALLOW_PICKABLE_READ); s3d.setCapability(Node.ALLOW_PICKABLE_WRITE); s3d.setPickable(false); // by default, see actions s3d.setUserData(this); // this object will handle edges this.addChild(s3d); allEdgeShapes.add(s3d); } totalQuads += plates.length; totalInternalEdges += nInternalEdges; totalExternalEdges += nExternalEdges; } System.out.println("Total quads: " + totalQuads); System.out.println("Total Internal Plate edges: " + totalInternalEdges); System.out.println("Total External Plate edges: " + totalExternalEdges); } /** * Adds a new edge to a line, merging with other edges on this line if * necessary * * @param edges : * An EdgeLine map * @param a * line for this map, may already exist or not * @param i * first grid index for the nex edge on this line * @param j * second grid index for the nex edge on this line */ protected void addEdge(HashMap<EdgeLine, EdgeLine> edges, EdgeLine line, int i, int j) { EdgeLine el = edges.get(line); if (el == null) edges.put(line, line); else line = el; line.add(i, j); } /** * Gets the EdgeLine object corresponding to a plate edge * * @param p : * the plate for which an edge is wanted * @param dirnum : * the direction 1 or 2 of the plate * @param the * index in the other direction of the plate (p.min? and p.max? * for the external edges) * @return an EdgeLine with these characteristics */ protected EdgeLine getLine(Plate p, int dirnum, int idx) { if (p instanceof PlateX) { // Parameters match by incredible chance! return new EdgeLine(p.position, idx, dirnum); } if (p instanceof PlateY) { // first direction of Y plate is X, second is Z => 0 and 2 if (dirnum == 1) return new EdgeLine(p.position, idx, 0); else return new EdgeLine(idx, p.position, 2); } if (p instanceof PlateZ) { // first direction of Z plate is X, second is Y => 0 and 1 return new EdgeLine(idx, p.position, dirnum - 1); } return null; } /** * Overloads the quad array and implements ref to double translation for * Picking */ protected static class NioQuadArray extends QuadArray { //double[] array; public NioQuadArray(int arg0, int arg1) { super(arg0, arg1 | GeometryArray.BY_REFERENCE | GeometryArray.USE_NIO_BUFFER); } @Override public double[] getCoordRefDouble() { float[] fs=getCoordRefFloat(); double[] toReturn=new double[fs.length]; for(int i=0; i<fs.length; i++) { toReturn[i]=fs[i]; } return toReturn; } /* * (non-Javadoc) * * @see javax.media.j3d.GeometryArray#getCoordRefDouble() */ @Override public float[] getCoordRefFloat() { // Get ref to Nio buffer, of type Float by construction in the // calling code // Since we used allocateDirect to have it in main memory (thus // fastening transfer // Video ram), the buffer is not backed by a JVM array // => array() doesn't work // => keep Float buffer and make a double one here for the occasion // => better to copy a buffer from RAM to JVM only when picking, // rather than copy a buffer from JVM to RAM each time the scene is // rendered, as would // be the case without NIO. // Also, when picking, tests have shown that NIO buffer copy has no // noticeable impact // on performance. So, it is actually better to copy the array each // time the picking is // done than to hog memory with a permanent copy. float[] array = new float[getVertexCount() * 3]; FloatBuffer db = (FloatBuffer) super.getCoordRefBuffer() .getBuffer(); db.rewind(); db.get(array); // optimized get return array; } @Override public void setCoordRefBuffer(J3DBuffer buffer) { super.setCoordRefBuffer(buffer); /* * // Use this to cache data => too little perf gained for the * memory used array = new double[getVertexCount()*3]; DoubleBuffer * db = (DoubleBuffer)buffer.getBuffer(); db.rewind(); * db.get(array); // optimized get */ } } /** * Overloads the quad array and implements ref to double translation for * Picking */ protected static class NioLineArray extends LineArray { public NioLineArray(int arg0, int arg1) { super(arg0, arg1 | GeometryArray.BY_REFERENCE | GeometryArray.USE_NIO_BUFFER); } @Override public double[] getCoordRefDouble() { float[] fs=getCoordRefFloat(); double[] toReturn=new double[fs.length]; for(int i=0; i<fs.length; i++) { toReturn[i]=fs[i]; } return toReturn; } /* * (non-Javadoc) * * @see javax.media.j3d.GeometryArray#getCoordRefDouble() */ @Override public float[] getCoordRefFloat() { // Get ref to Nio buffer, of type Float by construction in the // calling code // Since we used allocateDirect to have it in main memory (thus // fastening transfer // Video ram), the buffer is not backed by a JVM array // => array() doesn't work // => keep Float buffer and make a double one here for the occasion // => better to copy a buffer from RAM to JVM only when picking, // rather than copy a buffer from JVM to RAM each time the scene is // rendered, as would // be the case without NIO. // Also, when picking, tests have shown that NIO buffer copy has no // noticeable impact // on performance. So, it is actually better to copy the array each // time the picking is // done than to hog memory with a permanent copy. float[] array = new float[getVertexCount() * 3]; FloatBuffer db = (FloatBuffer) super.getCoordRefBuffer() .getBuffer(); db.rewind(); db.get(array); // optimized get return array; } } /** * returns a color for a group/order and for a state * * @param order * A color order number. This determines the base color (hue). * <br> * -2 => Wire <br> * else group index * @param what * The goal for this color. <br> * 0 = unselected face. <br> * 1 = selected face. <br> * 2 = unselected internal edge <br> * 3 = selected internal edge <br> * 4 = unselected external edge <br> * 5 = selected external edge <br> * 6 = selected quad <br> */ public float[] getColorForOrder(int order, int what) { // wires have no group if (order == -2) { switch (what) { case 0 : return new float[]{0.4f, 0.4f, 0.4f}; case 1 : return new float[]{1.0f, 1.0f, 1.0f}; case 2 : return new float[]{0.95f, 0.95f, 0.95f}; } } float s,h; float b = 1.0f; Color c=baseColor.get(new Integer(order)); float[] hsb=Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null); h=hsb[0]; s=hsb[1]; b=hsb[2]; switch (what) { case 0 : s = s*1.0f; break; // unactive face has saturated color case 1 : s = s*0.75f; break; // selected face is whiter case 2 : s = s*0.5f; break; // internal edges case 3 : s = s*0.0f; break; // selected internal edges case 4 : s = s*1.0f; b = b*0.8f; break; // external edges case 5 : s = 0.0f; break; // selected external edges case 6 : s = s*0.3f; break; // selected quad is whiter default : s = s*1.0f; break; } return Color.getHSBColor(h,s,b).getRGBColorComponents(null); } /* * public static float[] getColorForOrder(int order, int what, float alpha) { * float[] ret = new float[4]; float[] base = getColorForOrder(order,what); * ret[0] = base[0]; ret[1] = base[1]; ret[2] = base[2]; ret[3] = alpha; * return ret; } */ /** Class named like this because the quads will always show behind the edges */ public class BehindShape { protected FloatBuffer colors; protected int groupIdx; protected Shape3D shape; // temporary variables used during picking => allocate them only once float[][] vertices = new float[4][3]; double[] point = new double[3]; private Plate[] plates; public BehindShape(Shape3D s3d, Plate[] plates, int g) { this.shape=s3d; this.groupIdx = g; colors = (FloatBuffer) ((NioQuadArray) (shape.getGeometry())) .getColorRefBuffer().getBuffer(); this.plates=plates; } /* * (non-Javadoc) * * @see syn3d.nodes.xith3d.ShapeNodeXith3D#setAppearanceForHighlight(boolean) */ public void setAppearanceForHighlight(boolean on) { if (colors == null) return; // parent constructor // Great by ref color buffer = just modify the thing and it is OK! float[] color = getColorForOrder(groupIdx, on ? 1 : 0); colors.rewind(); int l3 = plates.length * 3; for (int i = 0; i < l3; i += 3) colors.put(color); } /* * (non-Javadoc) * * @see syn3d.base.ActiveNode#highlight(boolean, java.lang.Object) */ public void highlight(boolean on, Object parameter) { System.out.println("Total memory: "+Runtime.getRuntime().totalMemory()); System.out.println("Free memory: "+Runtime.getRuntime().freeMemory()); System.out.println(System.currentTimeMillis()+" starting highlight with "+parameter); if (parameter instanceof PickResult) { PickResult result = (PickResult) parameter; result.setFirstIntersectOnly(true); PickIntersection pi = result.getIntersection(0); // indices of the picked quad // Indices are set to vertex indices, as this is not an Index // Geometry object // => easy to find the plate index from this int[] idx = pi.getPrimitiveCoordinateIndices(); int plateNum = idx[0] / 4; Plate p = plates[plateNum]; Point3d point3d = pi.getPointCoordinates(); point3d.get(point); FloatBuffer coords = (FloatBuffer) ((NioQuadArray) (shape .getGeometry())).getCoordRefBuffer().getBuffer(); for (int i = 0; i < idx.length; ++i) { coords.position(idx[i] * 3); coords.get(vertices[i]); } int d1 = 0, d2 = 0; if (p instanceof PlateX) { d1 = 1; d2 = 2; } else if (p instanceof PlateY) { d1 = 0; d2 = 2; } else if (p instanceof PlateZ) { d1 = 0; d2 = 1; } int u = (int) Math.floor((point[d1] - vertices[0][d1]) * (p.max1 - p.min1) / (vertices[3][d1] - vertices[0][d1])); int v = (int) Math.floor((point[d2] - vertices[0][d2]) * (p.max2 - p.min2) / (vertices[1][d2] - vertices[0][d2])); int quadIdx = v * (p.max1 - p.min1) + u; u += p.min1; v += p.min2; System.out.println((on ? "" : "de") + "selected quad " + quadIdx + " in plate " + plateNum + " in group "); System.out .println("Grid positions for the quad (x,y,z) indices:"); int[] pos = p.getXYZGridIndices(u, v); System.out.println("vertex1 = (" + pos[0] + ", " + pos[1] + ", " + pos[2] + ")"); pos = p.getXYZGridIndices(u, v + 1); System.out.println("vertex2 = (" + pos[0] + ", " + pos[1] + ", " + pos[2] + ")"); pos = p.getXYZGridIndices(u + 1, v + 1); System.out.println("vertex3 = (" + pos[0] + ", " + pos[1] + ", " + pos[2] + ")"); pos = p.getXYZGridIndices(u + 1, v); System.out.println("vertex4 = (" + pos[0] + ", " + pos[1] + ", " + pos[2] + ")"); float[] color = getColorForOrder(groupIdx, on ? 1 : 0); for (int i = 0; i < idx.length; ++i) { colors.position(idx[i] * 3); colors.put(color); } toggleSelectedQuad(on, new SelectionQuad(p, u, v, groupIdx)); // Use event propagation, but don't call // setAppearanceForHighlight FloatBuffer tmp = colors; colors = null; colors = tmp; } System.out.println(System.currentTimeMillis()+" end of highlight"); } } /** * Simple algorithm to add or remove a quad from the selection Ideas: 1. Use * one shape per quad => easy, but can become very fast too big to fit in * memory. => pb: flicker when adding / removing because the scene is * detached 2. Use only one shape, modify geometry TODO: pre-create an empty * shape to avoid initial flicker * * @param on * @param p * @param u * @param v * @param groupIdx2 */ protected void toggleSelectedQuad(boolean on, SelectionQuad sq) { LOGGER.finest("on="+on+" selectionQuad="+sq); if (on) { // add the given quad to the list if (selection.contains(sq)) return; // already in selection.add(sq); } else { // remove the given quad from the list if (!selection.contains(sq)) return; // not present selection.remove(sq); if (selection.size() == 0) { QuadArray qa = (QuadArray) selectionShape.getGeometry(); qa.setValidVertexCount(0); return; } } // Use in-memory arrays instead of NIO because selection should not be // too big // => faster QuadArray qa = new QuadArray(selection.size() * 4, GeometryArray.COORDINATES | GeometryArray.COLOR_3 | GeometryArray.BY_REFERENCE); float[] coords = new float[selection.size() * 4 * 3]; float[] colors = new float[selection.size() * 4 * 3]; for (int i = 0; i < coords.length; i += 12) { SelectionQuad quad = selection.get(i / 12); quad.updateCoords(grid, coords, i); quad.updateColors(colors, i); } qa.setCoordRefFloat(coords); qa.setColorRefFloat(colors); qa.setCapability(GeometryArray.ALLOW_COUNT_WRITE); // update selection Shape with the new selection list if (selectionShape == null) { Appearance a = new Appearance(); PolygonAttributes pa = new PolygonAttributes( PolygonAttributes.POLYGON_FILL, PolygonAttributes.CULL_NONE, 0); pa.setPolygonOffset(0.5f); // between faces and edges pa.setPolygonOffsetFactor(0.5f); a.setPolygonAttributes(pa); selectionShape = new Shape3D(qa, a); selectionShape.setUserData(this); selectionShape.setPickable(false); selectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); selectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); BranchGroup bg=new BranchGroup(); bg.addChild(selectionShape); addChild(bg); } else selectionShape.setGeometry(qa); } protected class SelectionQuad { public Plate p; public int u, v; public int groupIdx; /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (!(obj instanceof SelectionQuad)) return false; SelectionQuad sq = (SelectionQuad) obj; return (sq.p.equals(p)) && (sq.u == u) && (sq.v == v) && (sq.groupIdx == groupIdx); } /** * @param colors * A color buffer * @param i * The index in this buffer where to put the 12 components of * this quad */ public void updateColors(float[] colors, int i) { float[] baseColor = getColorForOrder(groupIdx, 6); System.arraycopy(baseColor, 0, colors, i, 3); System.arraycopy(baseColor, 0, colors, i + 3, 3); System.arraycopy(baseColor, 0, colors, i + 6, 3); System.arraycopy(baseColor, 0, colors, i + 9, 3); } /** * @param coords * A coordinate buffer * @param i * The index in this buffer where to put the 12 components of * this quad */ public void updateCoords(float[][] grid, float[] coords, int i) { System.arraycopy(p.getCoordinates(grid, u, v), 0, coords, i, 3); System.arraycopy(p.getCoordinates(grid, u, v + 1), 0, coords, i + 3, 3); System.arraycopy(p.getCoordinates(grid, u + 1, v + 1), 0, coords, i + 6, 3); System.arraycopy(p.getCoordinates(grid, u + 1, v), 0, coords, i + 9, 3); } public SelectionQuad(Plate p, int u, int v, int groupIdx) { this.p = p; this.u = u; this.v = v; this.groupIdx = groupIdx; } } /** * Simple algorithm to add or remove an edge from the selection Ideas: 1. * Use one shape per quad => easy, but can become very fast too big to fit * in memory. => pb: flicker when adding / removing because the scene is * detached 2. Use only one shape, modify geometry TODO: pre-create an empty * shape to avoid initial flicker * * @param on * @param p * @param u * @param v * @param groupIdx2 */ protected void toggleSelectedEdge(boolean on, SelectionEdge se) { if (on) { // add the given edge to the list if (edgeSelection.contains(se)) return; // already in edgeSelection.add(se); } else { // remove the given edge from the list if (!edgeSelection.contains(se)) return; // not present edgeSelection.remove(se); if (edgeSelection.size() == 0) { LineArray la = (LineArray) edgeSelectionShape.getGeometry(); la.setValidVertexCount(0); return; } } // Use in-memory arrays instead of NIO because edgeSelection should not // be too big // => faster LineArray la = new LineArray(edgeSelection.size() * 2, GeometryArray.COORDINATES | GeometryArray.COLOR_3 | GeometryArray.BY_REFERENCE); double[] coords = new double[edgeSelection.size() * 2 * 3]; float[] colors = new float[edgeSelection.size() * 2 * 3]; for (int i = 0; i < coords.length; i += 6) { SelectionEdge edge = edgeSelection.get(i / 6); edge.updateCoords(grid, coords, i); edge.updateColors(colors, i); } la.setCoordRefDouble(coords); la.setColorRefFloat(colors); la.setCapability(GeometryArray.ALLOW_COUNT_WRITE); // update edgeSelection Shape with the new edgeSelection list if (edgeSelectionShape == null) { Appearance a = new Appearance(); //PolygonAttributes pa = new // PolygonAttributes(PolygonAttributes.POLYGON_LINE, // PolygonAttributes.CULL_NONE, 0); //pa.setPolygonOffset(1); // above edges //pa.setPolygonOffsetFactor(1); LineAttributes lat = new LineAttributes(); lat.setLineWidth(2.0f); lat.setLineAntialiasingEnable(true); a.setLineAttributes(lat); //a.setPolygonAttributes(pa); edgeSelectionShape = new Shape3D(la, a); edgeSelectionShape.setUserData(this); edgeSelectionShape.setPickable(false); edgeSelectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); edgeSelectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); BranchGroup bg=new BranchGroup(); bg.addChild(edgeSelectionShape); addChild(bg); } else edgeSelectionShape.setGeometry(la); } protected class SelectionEdge { int[] end1; int[] end2; int groupIdx; int edgeType; public SelectionEdge(int[] end1, int[] end2, int groupIdx, int edgeType) { this.end1 = end1; this.end2 = end2; this.groupIdx = groupIdx; this.edgeType = edgeType; } /** * @param colors * A color buffer * @param i * The index in this buffer where to put the 12 components of * this quad */ public void updateColors(float[] colors, int i) { float[] baseColor = getColorForOrder(groupIdx, edgeType + 1); System.arraycopy(baseColor, 0, colors, i, 3); System.arraycopy(baseColor, 0, colors, i + 3, 3); } /** * @param coords * A coordinate buffer * @param i * The index in this buffer where to put the 12 components of * this quad */ public void updateCoords(float[][] grid, double[] coords, int i) { coords[i + 0] = grid[0][end1[0]]; coords[i + 1] = grid[1][end1[1]]; coords[i + 2] = grid[2][end1[2]]; coords[i + 3] = grid[0][end2[0]]; coords[i + 4] = grid[1][end2[1]]; coords[i + 5] = grid[2][end2[2]]; } @Override public boolean equals(Object obj) { if (!(obj instanceof SelectionEdge)) return false; SelectionEdge se = (SelectionEdge) obj; return Arrays.equals(end1, se.end1) && Arrays.equals(end2, se.end2) && (groupIdx == se.groupIdx); } } /** Handles highlighting for edges */ public void highlight(boolean on, Object parameter) { // Should always be a pick result since those Shape3D do not appear in // the JTree if (parameter instanceof PickResult) { PickResult result = (PickResult) parameter; result.setFirstIntersectOnly(true); PickIntersection pi = result.getIntersection(0); // indices of the picked line // should always be line at this point // Indices are set to vertex indices, as this is not an Index // Geometry object int[] idx = pi.getPrimitiveCoordinateIndices(); Point3d point3d = pi.getPointCoordinates(); double[] point = new double[3]; point3d.get(point); FloatBuffer coords = (FloatBuffer) (pi.getGeometryArray()) .getCoordRefBuffer().getBuffer(); float[] pt1 = new float[3]; float[] pt2 = new float[3]; coords.position(idx[0] * 3); coords.get(pt1); coords.position(idx[1] * 3); coords.get(pt2); int[] gpt1 = getGridCoordinate(pt1); int[] gpt2 = getGridCoordinate(pt2); int dim = 0; // lines are parallel to one of the axis => only one coordinate // changes if (gpt1[0] != gpt2[0]) dim = 0; else if (gpt1[1] != gpt2[1]) dim = 1; else if (gpt1[2] != gpt2[2]) dim = 2; else System.err .println("Error: edge is not parallel to one of the axis"); // use gpt1 and gpt2 as a variables for the new point => destroy // previous content gpt1[dim] = (int) Math.floor(gpt1[dim] + (point[dim] - pt1[dim]) * (gpt2[dim] - gpt1[dim]) / (pt2[dim] - pt1[dim])); System.out.println("Edge end 0 vertex grid coordinates = (" + gpt1[0] + ", " + gpt1[1] + ", " + gpt1[2] + ")"); gpt2[dim] = gpt1[dim] + 1; System.out.println("Edge end 1 vertex grid coordinates = (" + gpt2[0] + ", " + gpt2[1] + ", " + gpt2[2] + ")"); System.out.println("pi.getGeometryArray()="+pi.getGeometryArray()); System.out.println("pi.getGeometryArray().getUserData()="+pi.getGeometryArray().getUserData()); // handle wire case Object userData=pi.getGeometryArray().getUserData(); if(userData!=null && userData instanceof int[]) { int[] info = (int[])userData; if (info[0] < 0) { float[] color = getColorForOrder(info[0], on ? 2 : 0); idx = pi.getPrimitiveColorIndices(); FloatBuffer colors = (FloatBuffer) (pi.getGeometryArray()) .getColorRefBuffer().getBuffer(); colors.position(idx[0] * 3); colors.put(color); colors.position(idx[1] * 3); colors.put(color); } toggleSelectedEdge(on, new SelectionEdge(gpt1, gpt2, info[0], info[1])); } } // event propagation System.out.println(System.currentTimeMillis()+" end of highlight"); } /** * @param pt * @return the grid coordinates for a given point */ protected int[] getGridCoordinate(float[] pt) { int[] ret = new int[3]; for (int xyz = 0; xyz < 3; ++xyz) { double min = Double.POSITIVE_INFINITY; int idx = -1; for (int i = 0; i < grid[xyz].length; ++i) { double dist = Math.abs(grid[xyz][i] - pt[xyz]); // N1 norm if (dist < min) { min = dist; idx = i; } } ret[xyz] = idx; } return ret; } /** * Each EdgeLine object reports all edges along a particular infinite line * This line is parallel to one of the axis, thus defined by: - Its * direction: X, Y, or Z - The constant values in the other 2 directions Ex: * X line defined by Y=5, Z=2 The values for the constants are indices in * the grid array, thus integers * * Alongside each line, a certain number of edges can be defined. All * overlapping edges are merged to form unique lines, thus reducing the * geometry => Optimum solution in the end : no edge is duplicated, minimum * geometrical description => easy to find back the grid indices in picking * operations * */ protected static class EdgeLine { /** The constants : (Y,Z) or (X,Z) or (X,Y) */ int c1, c2; /** The direction : 0,1,2 for X,Y,Z */ int direction; /** * The edges on this line. Each edge end is written in turn, in a sorted * order. */ LinkedList<Integer> edges; /** * @param c1 * The first constant of one couple : (Y,Z) or (X,Z) or (X,Y) * @param c2 * The second constant of one couple : (Y,Z) or (X,Z) or * (X,Y) * @param direction * 0,1,2 for X,Y,Z */ public EdgeLine(int c1, int c2, int direction) { this.c1 = c1; this.c2 = c2; this.direction = direction; edges = new LinkedList<Integer>(); } /** * @param grid * @return an array of vertex coordinates, one vertex per edge end, 3 * coords per vertex */ public double[] getCoords(float[][] grid) { double[] ret = new double[edges.size() * 3]; Iterator<Integer> it = edges.iterator(); switch (direction) { case 0 : for (int i = 0; i < ret.length; i += 3) { ret[i + 0] = grid[0][it.next().intValue()]; ret[i + 1] = grid[1][c1]; ret[i + 2] = grid[2][c2]; } break; case 1 : for (int i = 0; i < ret.length; i += 3) { ret[i + 0] = grid[0][c1]; ret[i + 1] = grid[1][it.next().intValue()]; ret[i + 2] = grid[2][c2]; } break; case 2 : for (int i = 0; i < ret.length; i += 3) { ret[i + 0] = grid[0][c1]; ret[i + 1] = grid[1][c2]; ret[i + 2] = grid[2][it.next().intValue()]; } break; } return ret; } /** * @return */ public int getNumberOfEdges() { if ((edges.size() & 1) != 0) { System.out .println("Error: Odd number of vertex in edge line : " + edges.size()); } return edges.size() / 2; } /** * Add an edge along this line. Overlapping edges are merged. * * @param e1 * The first edge end * @param e2 * The second edge end */ public void add(int e1, int e2) { // sort the ends int min = (e1 < e2) ? e1 : e2; e2 = (e1 > e2) ? e1 : e2; e1 = min; // state of the current segment end : first end or last end of the // current segment boolean first = true; boolean inMerge = false; for (int i = 0; i < edges.size(); ++i) { int end = edges.get(i).intValue(); // wait till newsegment match this interval in edges list order if (e1 > end) { first = !first; continue; } // now e1<=end. Handle case where end is the first segment end if (first) { // If not already merging, see if merging is necessary if (!inMerge) { // segments overlap => extend existing segment to match // the new one if (end <= e2) { edges.set(i, new Integer(e1)); inMerge = true; // now merging the new segment } else { // Segments do not overlap => add a new segment, // finished edges.add(i, new Integer(e2)); // insert e2 before // the current end edges.add(i, new Integer(e1)); // insert e1 before // e2 return; } // In merge already => it normal that e1<=end. Let's // check e2 } else { // new segment terminates before current segment // => add the new point, finished if (e2 < end) { edges.add(i, new Integer(e2)); return; // new segment terminates after or inside current // segment => merge this end } else if (e2 >= end) { // remove this end, as the segments merge to make // one bigger segment edges.remove(i--); // remove corresponding index too } } } // Handle second case : end is the last end of the segment. // Note: e1<=end, see above else { // new segment terminates inside current one // => end of the merge, keep this segment's end if (e2 <= end) return; // New segment terminates strictly after current segment // remove this end, as the segments merge to make one bigger // segment edges.remove(i--); // remove corresponding index too // If not already merging, this is necessary inMerge = true; // now merging the new segment } first = !first; } // Still here? => new segment terminates after any existing segment // If not merging, must add the first end of the new segment since // it was larger than all existing segment ends if (!inMerge) edges.add(new Integer(e1)); // Close the new segment edges.add(new Integer(e2)); } /** * Equals is a bit special : test for direction and constant equality, * but the edges need not be the same along this line. Only the infinite * line is guaranteed to be the same. * * @see java.lang.Object#equals(java.lang.Object) * @see hashCode() */ @Override public boolean equals(Object obj) { if (!(obj instanceof EdgeLine)) return false; EdgeLine el = (EdgeLine) obj; return (el.direction == direction) && (el.c1 == c1) && (el.c2 == c2); } /** * Hashcode the direction and constants. Thus, an hashMap of edges can * be used to simply add new adges to a given direction without worrying * if another EdgeLine exists (@see equals(java.lang.Object) too) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return ((direction & 3) << 30) | ((c1 & 0x7F) << 15) | (c2 & 0x7f); } } private Wire[] createWireList() { int[] ids=provider.getDomainIDs(); LOGGER.finest("computing wires for "+ids.length+" domain."); int numberOfWire=0; for(int i=0; i<ids.length; i++) { FDDomain domain=(FDDomain)provider.getDomain(ids[i]); numberOfWire+=domain.getNumberOfXWire(); numberOfWire+=domain.getNumberOfYWire(); numberOfWire+=domain.getNumberOfZWire(); } LOGGER.finest("found "+numberOfWire+" wires."); Wire[] wires=new Wire[numberOfWire]; int iw=0; for(int i=0; i<ids.length; i++) { FDDomain domain=(FDDomain)provider.getDomain(ids[i]); Iterator<int[]> it=domain.getXWireIterator(); while(it.hasNext()) { int[] indices=it.next(); Wire w=new WireX(); w.position1=indices[1]; w.position2=indices[2]; w.min=indices[0]; w.max=indices[3]; wires[iw]=w; iw++; } domain=(FDDomain)provider.getDomain(ids[i]); it=domain.getYWireIterator(); while(it.hasNext()) { int[] indices=it.next(); Wire w=new WireY(); w.position1=indices[0]; w.position2=indices[2]; w.min=indices[1]; w.max=indices[3]; wires[iw]=w; iw++; } domain=(FDDomain)provider.getDomain(ids[i]); it=domain.getZWireIterator(); while(it.hasNext()) { int[] indices=it.next(); Wire w=new WireZ(); w.position1=indices[0]; w.position2=indices[1]; w.min=indices[2]; w.max=indices[3]; wires[iw]=w; iw++; } } return wires; } protected void makeWires() { Wire[] wires=createWireList(); if(wires.length==0) return; FloatBuffer nioWires = ByteBuffer.allocateDirect( wires.length * 2 * 3 * 4).order(ByteOrder.nativeOrder()) .asFloatBuffer(); for(int i=0; i<wires.length; i++) { nioWires.put(wires[i].getCoordinates(grid)); } // Create edge shapes directly, don't make it appear in graph LineArray la = new NioLineArray(wires.length * 2, GeometryArray.COORDINATES | GeometryArray.COLOR_3); la.setCoordRefBuffer(new J3DBuffer(nioWires)); int colSize = wires.length * 2 * 3; FloatBuffer nioWireColors = ByteBuffer.allocateDirect(colSize * 4) .order(ByteOrder.nativeOrder()).asFloatBuffer(); float[] colors = getColorForOrder(-2, 0); for (int i = 0; i < colSize; i += 3) nioWireColors.put(colors); la.setColorRefBuffer(new J3DBuffer(nioWireColors)); la.setUserData(new int[]{-2, 0}); Appearance a = new Appearance(); //PolygonAttributes pa = new // PolygonAttributes(PolygonAttributes.POLYGON_LINE, // PolygonAttributes.CULL_NONE, 0); //pa.setPolygonOffset(2); //pa.setPolygonOffsetFactor(2); //a.setPolygonAttributes(pa); /* * LineAttributes lat = new LineAttributes(); lat.setLineWidth(2.0f); * a.setLineAttributes(lat); */ Shape3D s3d = new Shape3D(la, a); PickTool.setCapabilities(s3d, PickTool.INTERSECT_FULL); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_READ); s3d.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); s3d.setCapability(Node.ALLOW_PICKABLE_READ); s3d.setCapability(Node.ALLOW_PICKABLE_WRITE); s3d.setPickable(true); // by default, can be changed with actions s3d.setUserData(this); // this object will handle edges addChild(s3d); allEdgeShapes.add(s3d); } }