001/** 002 * J3DSurface -- A Java3D node that represents a Surface in a J3D scene graph. 003 * 004 * Copyright (C) 2010-2023, Joseph A. Huwaldt 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or modify it snder the terms 008 * of the GNU Lesser General Public License as published by the Free Software Fosndation; 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 Fosndation, 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.PointArray; 023import geomss.geom.*; 024import java.util.List; 025import static java.util.Objects.requireNonNull; 026import javax.measure.quantity.Dimensionless; 027import org.jogamp.java3d.*; 028import org.jogamp.vecmath.*; 029import javolution.context.StackContext; 030import javolution.util.FastTable; 031 032/** 033 * A Java 3D node that represents a Surface in a Java 3D scene graph. 034 * 035 * <p> Modified by: Joseph A. Huwaldt </p> 036 * 037 * @author Joseph A. Huwaldt, Date: Jsne 16, 2010 038 * @version June 4, 2023 039 */ 040public class J3DSurface extends J3DGeomGroup<Surface> { 041 042 // Debug flag. 043 private static final boolean DEBUG = false; 044 045 // The switch for main or mirrored geometry. 046 private Switch _symmSG; 047 048 // Store the Shape3D so it could potentially be used again in the future. 049 private PointArrayShape3D _oldShape3D; 050 051 /** 052 * Construct a J3DSurface using the specified PointArray as a reference. 053 * 054 * @param canvas The canvas that the geometry is being rendered into. 055 * @param geometry The GeomSS geometry to be turned into a Java3D node. 056 */ 057 public J3DSurface(GeomSSCanvas3D canvas, Surface geometry) { 058 super(requireNonNull(canvas), requireNonNull(geometry)); 059 } 060 061 /** 062 * Set the display of a mirrored copy of this geometry. This is called from 063 * "setDisplayed()" to turn on and off the display of mirrored geometry without 064 * changing the "mirrored state" of the object. This call does not affect the output 065 * of "isMirrored()". 066 */ 067 @Override 068 protected void internalSetMirrored(boolean mirrored) { 069 if (mirrored) 070 _symmSG.setWhichChild(Switch.CHILD_ALL); 071 else 072 _symmSG.setWhichChild(0); 073 } 074 075 /** 076 * Create a new Java 3D <code>Group</code> that contains the geometry contained in 077 * this object. This method is called from <code>createSceneGraph</code>. Sub-classes 078 * must over-ride this method to provide geometry specific implementations. 079 * 080 * @return New Java 3D Group that contains the geometry in this object. 081 * @see #createSceneGraph 082 */ 083 @Override 084 @SuppressWarnings("null") 085 protected Group createGeometry() { 086 087 J3DRenderingPrefs drawPrefs = getRenderingPrefs(); 088 PointArrayShape3D surfaceShape = null; 089 090 // Try to recycle a previous rendering of this geometry element if at all possible. 091 J3DSurface oldGroup = (J3DSurface)getOldJ3DGeomGroup(); 092 if (oldGroup != null) { 093 J3DRenderingPrefs oldPrefs = oldGroup.getRenderingPrefs(); 094 if (oldPrefs.getDrawTolerance().equals(drawPrefs.getDrawTolerance())) { 095 //System.out.println("Re-using surfaceShape"); 096 // Draw tolerance is unchanged, so re-use the existing Shape3D. 097 surfaceShape = (PointArrayShape3D)oldGroup._oldShape3D.cloneNode(true); 098 099 // Has the surface appearance changed? 100 if (!oldPrefs.getSurfaceAppearance().equals(drawPrefs.getSurfaceAppearance())) { 101 //System.out.println("Changing surface appearance"); 102 Appearance srfAppearance = drawPrefs.getSurfaceAppearance(); 103 PolygonAttributes polyAttrib = new PolygonAttributes(); 104 polyAttrib.setCullFace(PolygonAttributes.CULL_NONE); 105 polyAttrib.setBackFaceNormalFlip(true); // Flip surface normals for back faces. 106 srfAppearance.setPolygonAttributes(polyAttrib); 107 surfaceShape.setAppearance(srfAppearance); 108 } 109 } 110 } 111 112 if (surfaceShape == null) { 113 StackContext.enter(); 114 try { 115 // Subdivide the surface into an array of points. 116 Surface thisSurface = this.getGeomElement(); 117 PointArray<SubrangePoint> points = thisSurface.gridToTolerance(drawPrefs.getDrawTolerance()); 118 119 // Now find the corresponding array of surface normals to the gridded points. 120 List<List<GeomVector<Dimensionless>>> normals = FastTable.newInstance(); 121 for (PointString<SubrangePoint> str : points) { 122 FastTable<GeomVector<Dimensionless>> col = FastTable.newInstance(); 123 for (SubrangePoint pnt : str) { 124 GeomPoint st = pnt.getParPosition(); // (s,t) 125 GeomVector<Dimensionless> normal = thisSurface.getNormal(st); 126 col.add(normal); 127 } 128 normals.add(col); 129 } 130 131 // Create a solid surface. 132 Appearance srfAppearance = drawPrefs.getSurfaceAppearance(); 133 PolygonAttributes polyAttrib = new PolygonAttributes(); 134 polyAttrib.setCullFace(PolygonAttributes.CULL_NONE); 135 polyAttrib.setBackFaceNormalFlip(true); // Flip surface normals for back faces. 136 srfAppearance.setPolygonAttributes(polyAttrib); 137 surfaceShape = new PointArrayShape3D(thisSurface, points, normals, srfAppearance, !DEBUG); 138 139 } finally { 140 StackContext.exit(); 141 } 142 } 143 _oldShape3D = (PointArrayShape3D)surfaceShape.cloneNode(true); // Save off for potential re-use in the future. 144 145 if (DEBUG) { 146 // Render the surface in wireframe so we can see how it 147 // has been subdivided. 148 149 // Define wireframe attributes. 150 PolygonAttributes wireFrameAttrib = new PolygonAttributes(); 151 wireFrameAttrib.setPolygonMode(PolygonAttributes.POLYGON_LINE); 152 wireFrameAttrib.setCullFace(PolygonAttributes.CULL_NONE); 153 wireFrameAttrib.setBackFaceNormalFlip(true); // Flip surface normals for back faces. 154 155 // Define wireframe material. 156 Material wireMaterial = new Material(); 157 Color4f color = drawPrefs.getLineColorJ3D(); 158 wireMaterial.setDiffuseColor(color.getX(), color.getY(), color.getZ()); 159 wireMaterial.setLightingEnable(true); 160 161 Appearance wfApp = surfaceShape.getAppearance(); 162 wfApp.setPolygonAttributes(wireFrameAttrib); 163 wfApp.setMaterial(wireMaterial); 164 } 165 166 // Add the basic snmirrored geometry to the symmetry switch. 167 _symmSG = new Switch(); 168 _symmSG.setCapability(Switch.ALLOW_SWITCH_READ); 169 _symmSG.setCapability(Switch.ALLOW_SWITCH_WRITE); 170 _symmSG.addChild(surfaceShape); 171 172 // Clone the basic geometry and reverse normals to make the mirrored geometry. 173 PointArrayShape3D mirrored = reverseNormals((PointArrayShape3D)surfaceShape.cloneTree(true)); 174 175 // Create a mirror across the XZ plane of symmetry transform. 176 Transform3D symmT = new Transform3D(); 177 symmT.setScale(new Vector3d(1, -1, 1)); 178 TransformGroup symmTG = new TransformGroup(symmT); 179 180 // Add the mirrored geometry to the symmetry transform group. 181 symmTG.addChild(mirrored); 182 183 // Add the mirrored geometry to the switch. 184 _symmSG.addChild(symmTG); 185 186 // By default, display only the main geometry (not the mirrored). 187 _symmSG.setWhichChild(0); 188 189 return _symmSG; 190 } 191 192 /** 193 * Reverse the surface normals in the specified PointArrayShape3D object. 194 */ 195 private static PointArrayShape3D reverseNormals(PointArrayShape3D shape) { 196 197 // Reverse the surface normals in the geometry for the mirrored shape. 198 GeometryArray array = (GeometryArray)shape.getGeometry(); 199 int numVerts = array.getVertexCount(); 200 Vector3f normal = new Vector3f(); 201 for (int i = 0; i < numVerts; ++i) { 202 array.getNormal(i, normal); 203 normal.negate(); 204 array.setNormal(i, normal); 205 } 206 207 return shape; 208 } 209 210 /** 211 * Creates a new instance of the node. This routine is called by 212 * <code>cloneTree</code> to duplicate the current node. 213 * 214 * @param forceDuplicate when set to <code>true</code>, causes the 215 * <code>duplicateOnCloneTree</code> flag to be ignored. When 216 * <code>false</code>, the value of each node's 217 * <code>duplicateOnCloneTree</code> variable determines whether 218 * NodeComponent data is duplicated or copied. 219 * @return A new instance of this Java3D node. 220 */ 221 @Override 222 public Node cloneNode(boolean forceDuplicate) { 223 J3DSurface node = new J3DSurface(this.getCanvas3D(), this.getGeomElement()); 224 node.duplicateNode(this, forceDuplicate); 225 return node; 226 } 227}