package org.vorthmann.zome.render.java3d; import java.awt.Color; import java.awt.Component; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.media.j3d.Appearance; import javax.media.j3d.ColoringAttributes; import javax.media.j3d.Geometry; import javax.media.j3d.GeometryArray; import javax.media.j3d.GraphicsConfigTemplate3D; import javax.media.j3d.IndexedLineStripArray; import javax.media.j3d.LineAttributes; import javax.media.j3d.PolygonAttributes; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import org.vorthmann.j3d.J3dComponentFactory; import org.vorthmann.ui.Controller; 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.universe.SimpleUniverse; import com.vzome.core.algebra.AlgebraicMatrix; import com.vzome.core.algebra.AlgebraicVector; import com.vzome.core.math.Polyhedron; import com.vzome.core.math.Polyhedron.Face; import com.vzome.core.math.RealVector; import com.vzome.core.math.symmetry.Embedding; import com.vzome.core.render.Colors; import com.vzome.core.render.RenderedManifestation; import com.vzome.core.render.RenderingChanges; import com.vzome.core.viewing.Lights; import com.vzome.desktop.controller.RenderingViewer; import java.awt.GraphicsDevice; import java.util.logging.Level; import java.util.logging.Logger; public class Java3dFactory implements RenderingViewer.Factory, J3dComponentFactory { private static class GraphicsConfigurationFactory { private static final Logger logger = Logger.getLogger(new Throwable().getStackTrace()[0].getClassName()); private static GraphicsConfiguration GC; private static GraphicsConfiguration getGraphicsConfiguration() { if(GC == null) { long start = System.currentTimeMillis(); GraphicsConfigTemplate3D gct = new GraphicsConfigTemplate3D(); gct .setSceneAntialiasing( GraphicsConfigTemplate3D .REQUIRED ); gct .setDepthSize( 32 ); GraphicsDevice defaultScreenDevice = GraphicsEnvironment .getLocalGraphicsEnvironment() .getDefaultScreenDevice(); // Each time that getBestConfiguration() is called on my Windows PC, // a new JVM icon is shown on the taskbar for a total of 5 of them. // They are all replaced with yet another one as soon as the main window is displayed. // The multiple JVM icon symptom doesn't show up on Scott's Mac. // // Using this static class not only improves the startup time; // it also eliminates all but one of the extraneous taskbar icons at startup. // TODO: It would be nice if we could figure out how to avoid those taskbar icons all together. GraphicsConfiguration gc = defaultScreenDevice.getBestConfiguration( gct ); if ( gc == null ) { gct .setDepthSize( 24 ); gc = defaultScreenDevice.getBestConfiguration( gct ); } if ( gc == null ) { gc = SimpleUniverse .getPreferredConfiguration(); } GC = gc; logger .log(Level.INFO, "GraphicsConfiguration initialization in milliseconds: {0}", ( System.currentTimeMillis() - start )); } return GC; } } protected final Appearances mAppearances; protected final Appearance outlines; protected boolean mHasEmissiveColor; protected final Map<Polyhedron, Map<AlgebraicMatrix, Geometry> > solidGeometries = new HashMap<>(); protected final Map<Polyhedron, Map<AlgebraicMatrix, Geometry> > outlineGeometries = new HashMap<>(); public Java3dFactory( Colors colors, Boolean useEmissiveColor ) { mHasEmissiveColor = useEmissiveColor .booleanValue(); mAppearances = new Appearances( colors, mHasEmissiveColor ); outlines = new Appearance(); PolygonAttributes wirePa = new PolygonAttributes( PolygonAttributes.POLYGON_LINE, PolygonAttributes.CULL_BACK, -10f ); outlines .setPolygonAttributes( wirePa ); LineAttributes lineAtts = new LineAttributes( 1, LineAttributes .PATTERN_SOLID, true ); outlines .setLineAttributes( lineAtts ); outlines .setColoringAttributes( new ColoringAttributes( new Color3f( Color.BLACK ), ColoringAttributes .SHADE_FLAT ) ); } @Override public RenderingViewer createRenderingViewer( RenderingChanges scene, Component canvas ) { if ( canvas == null ) // this viewer is for offscreen rendering { canvas = new CapturingCanvas3D( GraphicsConfigurationFactory.getGraphicsConfiguration(), true ); } return new Java3dRenderingViewer( (Java3dSceneGraph) scene, (CapturingCanvas3D) canvas ); } @Override public RenderingChanges createRenderingChanges( Lights lights, boolean isSticky, Controller controller ) { return new Java3dSceneGraph( this, lights, isSticky, controller ); } Colors getColors() { return mAppearances .getColors(); } Appearance getAppearance( com.vzome.core.render.Color color, boolean glowing, boolean transparent ) { return mAppearances .getAppearance( color, glowing, transparent ); } Appearance getOutlineAppearance() { return this .outlines; } // The resulting geometry does not support the polygon offset // required to avoid "stitching" when the line geometry is rendered at the same Z-depth // as the solid geometry. Geometry makeOutlineGeometry( Map<AlgebraicMatrix, Geometry> map, Polyhedron poly, AlgebraicMatrix matrix, Embedding embedding ) { Geometry geom = map .get( matrix ); if ( geom == null ) { List<AlgebraicVector> polyVertices = poly .getVertexList(); Set<Face> faces = poly .getFaceSet(); int[] counts = new int [ faces .size() ]; int numIndices = 0; int i = 0; for (Face face : faces) { int arity = face .size(); if(face.get(0) != face.get(arity-1)) { // make room for one more vertex at the end to close the outline arity++; } counts[i++] = arity; numIndices += arity; } IndexedLineStripArray strips = new IndexedLineStripArray( polyVertices .size(), GeometryArray.COORDINATES, numIndices, counts ); i = 0; for (AlgebraicVector gv : polyVertices) { if ( matrix != null ) gv = matrix .timesColumn( gv ); RealVector v = embedding .embedInR3( gv ); strips .setCoordinate( i++, new Point3d( v.x, v.y, v.z ) ); } i = 0; for (Face face : faces) { int arity = face .size(); for ( int j = 0; j < arity; j++ ){ Integer index = face .get( j ); strips .setCoordinateIndex(i++, index); } if(face.get(0) != face.get(arity-1)) { // repeat the first vertex at the end to complete the outline. Integer index = face .get( 0 ); strips .setCoordinateIndex(i++, index); } } geom = strips; map .put( matrix, geom ); } return geom; } Geometry makeOutlineGeometry( RenderedManifestation rm ) { Polyhedron poly = rm .getShape(); Map<AlgebraicMatrix, Geometry> map = outlineGeometries .get( poly ); if ( map == null ){ map = new HashMap<>(); outlineGeometries .put( poly, map ); } return makeOutlineGeometry( map, poly, rm .getOrientation(), rm .getEmbedding() ); } Geometry makeSolidGeometry( RenderedManifestation rm ) { Polyhedron poly = rm .getShape(); Map<AlgebraicMatrix, Geometry> map = solidGeometries .get( poly ); if ( map == null ){ map = new HashMap<>(); solidGeometries .put( poly, map ); } return makeGeometry( map, poly, rm .getOrientation(), rm .reverseOrder(), true, rm .getEmbedding() ); } Geometry makeGeometry( Map<AlgebraicMatrix, Geometry> map, Polyhedron poly, AlgebraicMatrix matrix, boolean reverseFaces, boolean makeNormals, Embedding embedding ) { Geometry geom = map .get( matrix ); if ( geom == null ) { List<AlgebraicVector> vertices = poly .getVertexList(); Point3d[] coords = new Point3d [ vertices .size() ]; int i = 0; for (AlgebraicVector gv : vertices) { Point3d pt = new Point3d(); if ( matrix != null ) gv = matrix .timesColumn( gv ); RealVector v = embedding .embedInR3( gv ); pt.x = v.x; pt.y = v.y; pt.z = v.z; coords[i++] = pt; } Set<Face> faces = poly .getFaceSet(); int[] stripCounts = new int [ faces .size() ]; int[] contourCounts = new int [ faces .size() ]; int numIndices = 0; i = 0; for (Face face : faces) { int arity = face .size(); contourCounts[i] = 1; stripCounts[i++] = arity; numIndices += arity; } int[] indices = new int [ numIndices ]; i = 0; for (Face face : faces) { int arity = face .size(); for ( int j = 0; j < arity; j++ ){ Integer index = face .get( reverseFaces? arity-j-1 : j ); indices[i++] = index; } } GeometryInfo gi = new GeometryInfo( GeometryInfo .POLYGON_ARRAY ); gi .setCoordinates( coords ); gi .setCoordinateIndices( indices ); gi .setStripCounts( stripCounts ); gi .setContourCounts( contourCounts ); // gi .convertToIndexedTriangles(); if ( makeNormals ) { NormalGenerator ng = new NormalGenerator(); // zero crease angle means always make creases, no matter how close the normals are ng .setCreaseAngle( (float) Math .toRadians( 0 ) ); ng .generateNormals( gi ); // stripify Stripifier st = new Stripifier(); st.stripify( gi ); } geom = gi .getGeometryArray(); if ( makeNormals ) geom .setCapability( Geometry.ALLOW_INTERSECT ); map .put( matrix, geom ); } return geom; } boolean hasEmissiveColor() { return mHasEmissiveColor; } @Override public Component createJ3dComponent( String name ) { return new CapturingCanvas3D( GraphicsConfigurationFactory.getGraphicsConfiguration() ); } }