001/** 002 * J3DTriangleList -- A Java3D node that represents a TriangleList in a J3D scene graph. 003 * 004 * Copyright (C) 2015-2023, Joseph A. Huwaldt 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or modify it under the terms 008 * of the GNU Lesser General Public License as published by the Free Software Foundation; 009 * either version 2.1 of the License, or (at your option) any later version. 010 * 011 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 012 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 013 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public License along with 016 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 017 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 018 */ 019package geomss.j3d; 020 021import geomss.app.GeomSSCanvas3D; 022import geomss.geom.GeomTriangle; 023import geomss.geom.TriangleList; 024import static java.util.Objects.requireNonNull; 025import org.jogamp.java3d.*; 026import org.jogamp.vecmath.*; 027import javolution.context.StackContext; 028 029/** 030 * A Java 3D node that represents a TriangleList in a Java 3D scene graph. 031 * 032 * <p> Modified by: Joseph A. Huwaldt </p> 033 * 034 * @author Joseph A. Huwaldt, Date: April 13, 2009 035 * @version June 4, 2023 036 */ 037public class J3DTriangleList extends J3DGeomGroup<TriangleList> { 038 039 // The switch for main or mirrored geometry. 040 private Switch _symmSG; 041 042 // Store the Shape3D so it could potentially be used again in the future. 043 private TriangleListShape3D _oldSolidShape; 044 private TriangleListShape3D _oldWireframeShape; 045 private Shape3D _oldPointShape; 046 047 /** 048 * Construct a J3DTriangleList using the specified TriangleList as a reference. 049 * 050 * @param canvas The canvas that the geometry is being rendered into. 051 * @param geometry The GeomSS geometry to be turned into a Java3D node. 052 */ 053 public J3DTriangleList(GeomSSCanvas3D canvas, TriangleList geometry) { 054 super(requireNonNull(canvas), requireNonNull(geometry)); 055 } 056 057 /** 058 * Set the display of a mirrored copy of this geometry. This is called from 059 * "setDisplayed()" to turn on and off the display of mirrored geometry without 060 * changing the "mirrored state" of the object. This call does not affect the output 061 * of "isMirrored()". 062 * 063 * @param mirrored Flag indicating if the mirrored geometry should be displayed or 064 * not. 065 */ 066 @Override 067 protected void internalSetMirrored(boolean mirrored) { 068 if (mirrored) 069 _symmSG.setWhichChild(Switch.CHILD_ALL); 070 else 071 _symmSG.setWhichChild(0); 072 } 073 074 /** 075 * Create a new Java 3D <code>Group</code> that contains the geometry contained in 076 * this object. This method is called from <code>createSceneGraph</code>. Sub-classes 077 * must over-ride this method to provide geometry specific implementations. 078 * 079 * @return New Group that contains the geometry in this object. 080 * @see #createSceneGraph 081 */ 082 @Override 083 @SuppressWarnings("null") 084 protected Group createGeometry() { 085 J3DRenderingPrefs drawPrefs = getRenderingPrefs(); 086 TriangleList<? extends GeomTriangle> triLst = this.getGeomElement(); 087 TriangleListShape3D solid = null; 088 TriangleListShape3D wireFrame = null; 089 Shape3D pointShape = null; 090 091 // Try to recycle a previous rendering of this geometry element if at all possible. 092 J3DTriangleList oldGroup = (J3DTriangleList)getOldJ3DGeomGroup(); 093 if (oldGroup != null) { 094 J3DRenderingPrefs oldPrefs = oldGroup.getRenderingPrefs(); 095 if (oldPrefs.getDrawTolerance().equals(drawPrefs.getDrawTolerance())) { 096 //System.out.println("Re-using solidShape & wireframeShape"); 097 // Draw tolerance is unchanged, so re-use the existing Shape3D. 098 solid = (TriangleListShape3D)oldGroup._oldSolidShape.cloneNode(true); 099 wireFrame = (TriangleListShape3D)oldGroup._oldWireframeShape.cloneNode(true); 100 pointShape = (Shape3D)oldGroup._oldPointShape.cloneNode(true); 101 102 // Has the point color changed? 103 if (!oldPrefs.getPointColor().equals(drawPrefs.getPointColor())) { 104 //System.out.println("Changing point color"); 105 Color4f pointColor = drawPrefs.getPointColorJ3D(); 106 PointArray pointA = (PointArray)pointShape.getGeometry(); 107 int size = pointA.getVertexCount(); 108 for (int i = 0; i < size; ++i) { 109 pointA.setColor(i, pointColor); 110 } 111 } 112 113 } 114 } 115 116 // If we couldn't recycle the solid & wireFrame shapes, then create new ones. 117 if (solid == null) { 118 StackContext.enter(); 119 try { 120 solid = new TriangleListShape3D(triLst, triLst); 121 } finally { 122 StackContext.exit(); 123 } 124 125 // Wireframe is the same geometry, just differnet display attributes. 126 wireFrame = (TriangleListShape3D)solid.cloneNode(true); 127 128 // Create a new PointArray geometry for the point shape. 129 GeometryArray geom = (GeometryArray)solid.getGeometry(); 130 int numPnts = geom.getValidVertexCount(); 131 PointArray pointA = new PointArray(numPnts, PointArray.COORDINATES | PointArray.COLOR_4); 132 Color4f pointColor = drawPrefs.getPointColorJ3D(); 133 Point3f aPnt = new Point3f(); 134 for (int i = 0; i < numPnts; ++i) { 135 geom.getCoordinate(i, aPnt); 136 pointA.setCoordinate(i, aPnt); 137 pointA.setColor(i, pointColor); 138 } 139 pointShape = new GeomShape3D(triLst, pointA); 140 141 } 142 this._oldSolidShape = (TriangleListShape3D)solid.cloneNode(true); // Save off for potential re-use in the future. 143 this._oldWireframeShape = (TriangleListShape3D)wireFrame.cloneNode(true); 144 this._oldPointShape = (Shape3D)pointShape.cloneNode(true); 145 146 // Define wireframe attributes. 147 PolygonAttributes wireFrameAttrib = new PolygonAttributes(); 148 wireFrameAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE); 149 wireFrameAttrib.setCullFace(PolygonAttributes.CULL_NONE); 150 wireFrameAttrib.setPolygonOffset(-2); // Make wireframe appear "on top" of solid version. 151 152 // Define wireframe material. 153 Material wireMaterial = new Material(); 154 Color4f color = drawPrefs.getLineColorJ3D(); 155 wireMaterial.setDiffuseColor(color.x, color.y, color.z); 156 wireMaterial.setLightingEnable(true); 157 158 // Create a default solid version. 159 Appearance srfAppearance = drawPrefs.getSurfaceAppearance(); 160 PolygonAttributes polyAttrib = new PolygonAttributes(); 161 polyAttrib.setCullFace(PolygonAttributes.CULL_NONE); 162 polyAttrib.setBackFaceNormalFlip(true); // Flip surface normals for back faces. 163 srfAppearance.setPolygonAttributes(polyAttrib); 164 LineAttributes lineAttrib = new LineAttributes(); 165 lineAttrib.setLineWidth(drawPrefs.getLineWidth()); 166 srfAppearance.setLineAttributes(lineAttrib); 167 solid.setAppearance(srfAppearance); 168 169 // Create a duplicate wireframe version. 170 Appearance wfApp = (Appearance)srfAppearance.cloneNodeComponent(false); 171 wfApp.setPolygonAttributes(wireFrameAttrib); 172 wfApp.setMaterial(wireMaterial); 173 wireFrame.setAppearance(wfApp); 174 175 // Create a sold + wireframe version. 176 BranchGroup solidWireFrame = new BranchGroup(); 177 solidWireFrame.addChild(solid.cloneTree()); 178 solidWireFrame.addChild(wireFrame.cloneTree()); 179 180 // Create a switch group for the main geometry (unmirrored) 181 // that contains each variation on the display properties of an array. 182 Switch renderTypeSG = new Switch(0); 183 renderTypeSG.setCapability(Switch.ALLOW_SWITCH_READ); 184 renderTypeSG.setCapability(Switch.ALLOW_SWITCH_WRITE); 185 186 // Create a 3D shape from the point array 187 Appearance appearance = new Appearance(); 188 pointShape.setAppearance(appearance); 189 appearance.setPointAttributes(new PointAttributes(drawPrefs.getPointSize(), true)); 190 191 // Add the geometry to the main geometry switch. 192 renderTypeSG.addChild(solidWireFrame); 193 renderTypeSG.addChild(solid); 194 renderTypeSG.addChild(wireFrame); 195 renderTypeSG.addChild(pointShape); 196 197 // Add the basic unmirrored geometry to the switch. 198 _symmSG = new Switch(); 199 _symmSG.setCapability(Switch.ALLOW_SWITCH_READ); 200 _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE); 201 _symmSG.addChild(renderTypeSG); 202 203 // Clone the basic geometry to make the mirrored geometry. 204 Switch mirrored = new Switch(0); 205 mirrored.setCapability(Switch.ALLOW_SWITCH_READ); 206 mirrored.setCapability(Switch.ALLOW_SWITCH_WRITE); 207 208 // Clone the basic geometry and reverse normals to make the mirrored geometry. 209 TriangleListShape3D solidM = reverseNormals((TriangleListShape3D)solid.cloneTree(true)); 210 BranchGroup solidWireFrameM = new BranchGroup(); 211 solidWireFrameM.addChild(solidM.cloneTree()); 212 solidWireFrameM.addChild(wireFrame.cloneTree()); 213 214 // Add the geometry to the mirrored geometry switch. 215 mirrored.addChild(solidWireFrameM); 216 mirrored.addChild(solidM); 217 mirrored.addChild(wireFrame.cloneTree()); 218 mirrored.addChild(pointShape.cloneTree()); 219 220 // Create a mirror across the XZ plane of symmetry transform. 221 Transform3D symmT = new Transform3D(); 222 symmT.setScale(new Vector3d(1, -1, 1)); 223 TransformGroup symmTG = new TransformGroup(symmT); 224 225 // Add the mirrored geometry to the symmetry transform group. 226 symmTG.addChild(mirrored); 227 228 // Add the mirrored geometry to the switch. 229 _symmSG.addChild(symmTG); 230 231 // By default, display only the main geometry (not the mirrored). 232 _symmSG.setWhichChild(0); 233 234 return _symmSG; 235 } 236 237 /** 238 * Reverse the surface normals in the specified TriangleListShape3D object. 239 */ 240 private static TriangleListShape3D reverseNormals(TriangleListShape3D shape) { 241 242 // Reverse the surface normals in the geometry for the mirrored shape. 243 GeometryArray array = (GeometryArray)shape.getGeometry(); 244 int numVerts = array.getVertexCount(); 245 Vector3f normal = new Vector3f(); 246 for (int i = 0; i < numVerts; ++i) { 247 array.getNormal(i, normal); 248 normal.negate(); 249 array.setNormal(i, normal); 250 } 251 252 return shape; 253 } 254 255 /** 256 * Set the render type used for this group. 257 * 258 * @param type Value indicating the way that PointArray objects should be rendered. 259 */ 260 @Override 261 public void setRenderType(RenderType type) { 262 super.setRenderType(requireNonNull(type)); 263 264 // Get the main geometry's render switch. 265 Switch mainRenderSG = (Switch)_symmSG.getChild(0); 266 267 // Get the mirrored geometry's render switch. 268 Switch mirrRenderSG = (Switch)((Group)_symmSG.getChild(1)).getChild(0); 269 270 // Set switches based on the render type code. 271 switch (type) { 272 case SOLID_PLUS_WIREFRAME: 273 mainRenderSG.setWhichChild(0); 274 mirrRenderSG.setWhichChild(0); 275 break; 276 277 case SOLID: 278 mainRenderSG.setWhichChild(1); 279 mirrRenderSG.setWhichChild(1); 280 break; 281 282 case WIREFRAME: 283 case STRINGS: 284 mainRenderSG.setWhichChild(2); 285 mirrRenderSG.setWhichChild(2); 286 break; 287 288 case POINTS: 289 mainRenderSG.setWhichChild(3); 290 mirrRenderSG.setWhichChild(3); 291 break; 292 293 default: 294 break; 295 296 } 297 298 // Hide symmetric array geometry when POINTS or STRINGS are shown. 299 internalSetMirrored(isMirrored()); 300 } 301 302 /** 303 * Creates a new instance of the node. This routine is called by 304 * <code>cloneTree</code> to duplicate the current node. 305 * 306 * @param forceDuplicate when set to <code>true</code>, causes the 307 * <code>duplicateOnCloneTree</code> flag to be ignored. When 308 * <code>false</code>, the value of each node's 309 * <code>duplicateOnCloneTree</code> variable determines whether 310 * NodeComponent data is duplicated or copied. 311 * @return A new instance of this Java3D node. 312 */ 313 @Override 314 public Node cloneNode(boolean forceDuplicate) { 315 J3DTriangleList node = new J3DTriangleList(this.getCanvas3D(), this.getGeomElement()); 316 node.duplicateNode(this, forceDuplicate); 317 return node; 318 } 319}