/* * Created on Jun 30, 2003 */ package org.vorthmann.zome.render.java3d; import java.awt.Font; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import javax.media.j3d.AmbientLight; import javax.media.j3d.Appearance; import javax.media.j3d.Background; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.DirectionalLight; import javax.media.j3d.Geometry; import javax.media.j3d.Group; import javax.media.j3d.Light; import javax.media.j3d.LineArray; import javax.media.j3d.LinearFog; import javax.media.j3d.Locale; import javax.media.j3d.Node; import javax.media.j3d.OrientedShape3D; import javax.media.j3d.PointArray; import javax.media.j3d.Shape3D; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.media.j3d.VirtualUniverse; import javax.vecmath.Color3f; import javax.vecmath.Point3d; import javax.vecmath.Point3f; import javax.vecmath.Vector3d; import javax.vecmath.Vector3f; import org.vorthmann.ui.Controller; import com.sun.j3d.utils.geometry.Text2D; import com.vzome.core.algebra.AlgebraicField; import com.vzome.core.algebra.AlgebraicVector; import com.vzome.core.algebra.PentagonField; import com.vzome.core.math.Polyhedron; import com.vzome.core.math.RealVector; import com.vzome.core.math.symmetry.Axis; import com.vzome.core.math.symmetry.Direction; import com.vzome.core.math.symmetry.Embedding; import com.vzome.core.math.symmetry.IcosahedralSymmetry; import com.vzome.core.math.symmetry.Symmetry; import com.vzome.core.render.Color; import com.vzome.core.render.Colors; import com.vzome.core.render.RenderedManifestation; import com.vzome.core.render.RenderingChanges; import com.vzome.core.viewing.Lights; /** * @author vorth */ public class Java3dSceneGraph implements RenderingChanges, PropertyChangeListener { private static final Logger logger = Logger.getLogger( "org.vorthmann.zome.render.java3d" ); private final Locale mLocale; private final BranchGroup mScene, mRoot; private final TransformGroup mLights; private final LinearFog mFog; private final Light ambientForGlow, ambientForOutlines, directionalForOutlines; private int mGlowCount = 0; private final Background mBackground; private final Java3dFactory mFactory; //private final SharedGroup mBallGroup = null; private final BoundingSphere mEverywhere = new BoundingSphere( new Point3d( 0.0, 0.0, 0.0 ), Double.MAX_VALUE ); private final boolean isSticky; private boolean drawOutlines; private FrameLabels frameLabels = null; private IcosahedralLabels icosahedralLabels = null; public BranchGroup getRoot() { return mRoot; // or mScene? } void addView( BranchGroup view ) { mLocale.addBranchGraph( view ); } TransformGroup getLightsGroup() { return mLights; } public Java3dSceneGraph( Java3dFactory factory, Lights lights, boolean isSticky, Controller controller ) { mFactory = factory; this .isSticky = isSticky; lights .addPropertyListener( new PropertyChangeListener(){ @Override public void propertyChange( PropertyChangeEvent chg ) { if ( "backgroundColor" .equals( chg .getPropertyName() ) ) { int rgb = Integer .parseInt( (String) chg .getNewValue(), 16 ); Color newColor = new Color( rgb ); backgroundColorChanged( newColor ); } }} ); mFactory.getColors().addListener( new Colors.Changes() { @Override public void colorChanged( String name, Color newColor ) { if ( name.equals( Colors.BACKGROUND ) ) backgroundColorChanged( newColor ); } @Override public void colorAdded( String name, Color color ) { } } ); mRoot = new BranchGroup(); mRoot.setCapability( Group.ALLOW_CHILDREN_EXTEND ); mRoot.setCapability( Group.ALLOW_CHILDREN_READ ); mRoot.setCapability( Group.ALLOW_CHILDREN_WRITE ); mRoot.setCapability( Node.ENABLE_PICK_REPORTING ); // background and lighting should be in a separate model object // without lights, the model will be invisible... I tried it mLights = new TransformGroup(); mLights.setCapability( TransformGroup.ALLOW_TRANSFORM_READ ); mLights.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE ); mLights.setCapability( Group.ALLOW_CHILDREN_EXTEND ); mLights.setCapability( Group.ALLOW_CHILDREN_READ ); mLights.setCapability( Group.ALLOW_CHILDREN_WRITE ); mLights.setCapability( Node.ENABLE_PICK_REPORTING ); mRoot.addChild( mLights ); float[] rgb = new float[3]; Color3f color = new Color3f( lights.getAmbientColor().getRGBColorComponents( rgb ) ); ambientForGlow = new AmbientLight( color ); ambientForGlow .setInfluencingBounds( mEverywhere ); ambientForGlow .setEnable( true ); ambientForGlow .setCapability( Light.ALLOW_STATE_WRITE ); mLights .addChild( ambientForGlow ); ambientForOutlines = new AmbientLight( color ); ambientForOutlines .setInfluencingBounds( mEverywhere ); ambientForOutlines .setCapability( Light.ALLOW_STATE_WRITE ); mLights .addChild( ambientForOutlines ); if(lights.size() <= 0) { throw new IllegalArgumentException("Expected lights.size() to be greater than 0."); } Light light = null; for ( int i = 0; i < lights.size(); i++ ) { Vector3f direction = new Vector3f(); color = new Color3f( lights.getDirectionalLight( i, direction ).getRGBColorComponents( rgb ) ); light = new DirectionalLight( color, direction ); light .setInfluencingBounds( mEverywhere ); light .setEnable( true ); mLights .addChild( light ); } // TODO: model this better in core Lights... no overloading // use the last light in the array for the directional light directionalForOutlines = light; directionalForOutlines.setCapability(Light.ALLOW_STATE_WRITE); // --------------------------------------------------- mScene = new BranchGroup(); mScene.setCapability( Group.ALLOW_CHILDREN_EXTEND ); mScene.setCapability( Group.ALLOW_CHILDREN_READ ); mScene.setCapability( Group.ALLOW_CHILDREN_WRITE ); mScene.setCapability( Node.ENABLE_PICK_REPORTING ); mRoot.addChild( mScene ); Color bg = lights .getBackgroundColor(); bg.getRGBColorComponents( rgb ); mBackground = new Background( new Color3f( rgb ) ); mBackground.setCapability( Background.ALLOW_COLOR_WRITE ); mBackground.setApplicationBounds( mEverywhere ); mRoot.addChild( mBackground ); mFog = new LinearFog( rgb[0], rgb[1], rgb[2], 0d, 40d ); mFog.setInfluencingBounds( mEverywhere ); mFog.setCapability( LinearFog.ALLOW_COLOR_WRITE ); mFog.setCapability( LinearFog.ALLOW_DISTANCE_WRITE ); mRoot.addChild( mFog ); // if ( 1 == 0 ) { // // create axes // Color3f red = new Color3f( 1.0f, 0.0f, 0.0f ); // Color3f green = new Color3f( 0.0f, 1.0f, 0.0f ); // Color3f blue = new Color3f( 0.0f, 0.0f, 1.0f ); // Color3f black = new Color3f( 0.0f, 0.0f, 0.0f ); // // // create line for X axis // LineArray axisXLines = new LineArray( 2, LineArray.COORDINATES | LineArray.COLOR_3 ); // mRoot.addChild( new Shape3D( axisXLines ) ); // // axisXLines.setCoordinate( 0, new Point3f( - 10.0f, 0.0f, 0.0f ) ); // axisXLines.setCoordinate( 1, new Point3f( 10.0f, 0.0f, 0.0f ) ); // // axisXLines.setColor( 1, red ); // axisXLines.setColor( 0, black ); // // // create line for Y axis // LineArray axisYLines = new LineArray( 2, LineArray.COORDINATES | LineArray.COLOR_3 ); // mRoot.addChild( new Shape3D( axisYLines ) ); // // axisYLines.setCoordinate( 0, new Point3f( 0f, - 10.0f, 0f ) ); // axisYLines.setCoordinate( 1, new Point3f( 0.0f, 10.0f, 0.0f ) ); // // axisYLines.setColor( 1, green ); // axisYLines.setColor( 0, black ); // // // create line for Z axis // Point3f z1 = new Point3f( 0.0f, 0.0f, - 10.0f ); // Point3f z2 = new Point3f( 0.0f, 0.0f, 10.0f ); // // LineArray axisZLines = new LineArray( 2, LineArray.COORDINATES | LineArray.COLOR_3 ); // mRoot.addChild( new Shape3D( axisZLines ) ); // // axisZLines.setCoordinate( 0, new Point3f( 0f, 0f, - 10.0f ) ); // axisZLines.setCoordinate( 1, new Point3f( 0f, 0f, 10.0f ) ); // // axisZLines.setColor( 1, blue ); // axisZLines.setColor( 0, black ); // } if ( controller .propertyIsTrue( "drawOutlines" ) ) propertyChange("drawOutlines", true ); if ( controller .propertyIsTrue( "showFrameLabels" ) ) propertyChange("showFrameLabels", true ); if ( controller .propertyIsTrue( "showIcosahedralLabels" ) ) propertyChange("showIcosahedralLabels", true ); VirtualUniverse vu = new VirtualUniverse(); mLocale = new Locale( vu ); mLocale.addBranchGraph( mRoot ); } private class FrameLabels extends BranchGroup { public FrameLabels() { // <editor-fold defaultstate="collapsed"> setCapability(BranchGroup.ALLOW_DETACH); LineArray frameEdges = new LineArray(24, LineArray.COORDINATES | LineArray.COLOR_3); int nextEdge = 0; addChild(new Shape3D(frameEdges)); String[] labels = {"-", "0", "+"}; Color3f frameColor = new Color3f(0.9f, 1.0f, 0.0f); float scale = 30f; for (int x = -1; x < 2; x++) { for (int y = -1; y < 2; y++) { for (int z = -1; z < 2; z++) { if (x != 0 || y != 0 || z != 0) { int parity = (x + y + z) % 2; String label = labels[x + 1] + labels[y + 1] + labels[z + 1]; Text2D text2D = new Text2D(label, frameColor, "Helvetica", 72, Font.PLAIN); text2D.setRectangleScaleFactor(0.02f); text2D.getGeometry().setCapability(Geometry.ALLOW_INTERSECT); OrientedShape3D os3D = new OrientedShape3D(); os3D.setGeometry(text2D.getGeometry()); os3D.setAppearance(text2D.getAppearance()); os3D.setAlignmentMode(OrientedShape3D.ROTATE_ABOUT_POINT); Transform3D move = new Transform3D(); move.setTranslation(new Vector3d(scale * x, scale * y, scale * z)); TransformGroup tg = new TransformGroup(move); tg.addChild(os3D); addChild(tg); if (parity == 0) { // odd parity means this is an edge center, so let's render the edge of the cube int zeroCoord = (x == 0) ? 0 : ((y == 0) ? 1 : 2); float[] start = {scale * x, scale * y, scale * z}; float[] end = {scale * x, scale * y, scale * z}; start[zeroCoord] = scale; end[zeroCoord] = -scale; frameEdges.setCoordinate(nextEdge, new Point3f(start)); frameEdges.setColor(nextEdge++, frameColor); frameEdges.setCoordinate(nextEdge, new Point3f(end)); frameEdges.setColor(nextEdge++, frameColor); } } } } } // </editor-fold> } } private class IcosahedralLabels extends BranchGroup { public IcosahedralLabels() { // <editor-fold defaultstate="collapsed"> setCapability(BranchGroup.ALLOW_DETACH); Color3f minusColor = new Color3f(1.0f, 0.0f, 0.0f); Color3f plusColor = new Color3f(0.0f, 0.0f, 1.0f); AlgebraicField field = new PentagonField(); IcosahedralSymmetry symm = new IcosahedralSymmetry(field, "temp"); // we use black for index numbers, but turqouise for location of the text Direction indexOrbit = symm.getDirection("black"); Direction locationOrbit = symm.getDirection("turquoise"); for (Axis axis : indexOrbit) { boolean negative = axis.getSense() == Symmetry.MINUS; Color3f numColor = negative ? minusColor : plusColor; AlgebraicVector v = locationOrbit.getAxis(axis.normal().toRealVector()).normal(); RealVector location = v.toRealVector().scale( 11f ); String label = (negative ? "-" : "") + axis.getOrientation(); Text2D text2D = new Text2D(label, numColor, "Helvetica", 120, negative ? Font.PLAIN : Font.BOLD); text2D.setRectangleScaleFactor(0.02f); text2D.getGeometry().setCapability(Geometry.ALLOW_INTERSECT); OrientedShape3D os3D = new OrientedShape3D(); os3D.setGeometry(text2D.getGeometry()); os3D.setAppearance(text2D.getAppearance()); os3D.setAlignmentMode(OrientedShape3D.ROTATE_ABOUT_POINT); Transform3D move = new Transform3D(); move.setTranslation(new Vector3d(location.x, location.y, location.z)); TransformGroup tg = new TransformGroup(move); tg.addChild(os3D); addChild(tg); } // </editor-fold> } } @Override public void manifestationAdded( RenderedManifestation rm ) { // int[] /* AlgebraicVector */location = rm.getManifestation().getLocation(); // if ( location == null ) // location = rm.getShape().getField().origin( 3 ); RealVector loc = rm .getLocation(); if ( loc == null ) loc = new RealVector( 0d, 0d, 0d ); Appearance appearance = mFactory .getAppearance( rm.getColor(), rm.getGlow() > 0f, rm.getTransparency() > 0f ); Geometry geom = mFactory .makeSolidGeometry( rm ); if ( logger .isLoggable( Level.FINEST ) && rm .getManifestation() == null ) { Direction orbit = rm .getShape() .getOrbit(); String shape = ( orbit == null )? "BALL" : orbit .getName() + " strut"; logger .finest( shape + " at " + loc ); } // if we rendering wireframe, we're using absolute coordinates if ( ( geom instanceof PointArray ) || ( geom instanceof LineArray ) ) // location = rm.getShape().getField().origin( 3 ); loc = new RealVector( 0d, 0d, 0d ); Shape3D solidPolyhedron = new Shape3D( geom ); solidPolyhedron .setCapability( Shape3D.ALLOW_APPEARANCE_WRITE ); solidPolyhedron .setAppearance( appearance ); solidPolyhedron .setUserData( rm ); // omit this if trying to pre-optimize with makeGeometryAt Transform3D move = new Transform3D(); move.setTranslation( new Vector3d( loc.x, loc.y, loc.z ) ); TransformGroup tg = new TransformGroup( move ); tg.setCapability( Group.ALLOW_CHILDREN_EXTEND ); tg.setCapability( Group.ALLOW_CHILDREN_READ ); tg.setCapability( Group.ALLOW_CHILDREN_WRITE ); tg.setCapability( BranchGroup.ALLOW_DETACH ); tg.setCapability( Shape3D.ENABLE_PICK_REPORTING ); tg.setPickable( true ); tg.addChild( solidPolyhedron ); if ( drawOutlines ) { geom = mFactory .makeOutlineGeometry( rm ); Shape3D outlinePolyhedron = new Shape3D( geom ); outlinePolyhedron .setAppearance( mFactory .getOutlineAppearance() ); tg .addChild( outlinePolyhedron ); } // Create a Text2D leaf node, add it to the scene graph. // Text2D text2D = new Text2D( " label", new Color3f( 0.9f, 1.0f, // 0.0f), // "Helvetica", 36, Font.ITALIC ); // text2D .setRectangleScaleFactor( 0.02f ); // text2D .getGeometry() .setCapability( Geometry .ALLOW_INTERSECT ); // OrientedShape3D os3D = new OrientedShape3D(); // os3D .setGeometry( text2D.getGeometry() ); // os3D .setAppearance( text2D.getAppearance() ); // os3D .setAlignmentMode( OrientedShape3D.ROTATE_ABOUT_POINT ); // tg .addChild( os3D ); BranchGroup group = new BranchGroup(); group.setCapability( Group.ALLOW_CHILDREN_EXTEND ); group.setCapability( Group.ALLOW_CHILDREN_READ ); group.setCapability( Group.ALLOW_CHILDREN_WRITE ); group.setCapability( Node.ENABLE_PICK_REPORTING ); group.setCapability( BranchGroup.ALLOW_DETACH ); group.addChild( tg ); mScene.addChild( group ); if ( this .isSticky ) rm.setGraphicsObject( group ); } @Override public void reset() { mScene.removeAllChildren(); } @Override public void manifestationSwitched( RenderedManifestation from, RenderedManifestation to ) { BranchGroup target = (BranchGroup) from .getGraphicsObject(); if ( target == null ) { return; } TransformGroup tg = (TransformGroup) target .getChild( 0 ); Shape3D poly = (Shape3D) tg .getChild( 0 ); poly .setUserData( to ); if ( this .isSticky ) { to .setGraphicsObject( target ); from .setGraphicsObject( null ); } } @Override public void manifestationRemoved( RenderedManifestation rm ) { BranchGroup target = (BranchGroup) rm.getGraphicsObject(); if ( target == null ) { return; } if ( logger .isLoggable( Level.FINEST ) && rm .getManifestation() == null ) { Direction orbit = rm .getShape() .getOrbit(); String shape = ( orbit == null )? "BALL" : orbit .getName() + " strut"; logger .finest( shape + " at " + rm .getLocation() ); } for ( int i = 0; i < mScene.numChildren(); i++ ) { Object bg = mScene.getChild( i ); // Group tg = (Group) bg .getChild( 0 ); if ( target == bg /* tg .getChild( 0 ) */) { mScene.removeChild( i ); if ( this .isSticky ) rm .setGraphicsObject( null ); return; } } throw new RuntimeException( "polyhedron not in scene!" ); } @Override public void glowChanged( RenderedManifestation rm ) { boolean glowOn = rm.getGlow() > 0f; if ( mFactory.hasEmissiveColor() ) if ( glowOn ) { ++ mGlowCount; ambientForGlow.setEnable( false ); } else if ( -- mGlowCount == 0 ) { ambientForGlow.setEnable( true ); } colorChanged( rm ); } @Override public void colorChanged( RenderedManifestation rm ) { Group group = (Group) rm.getGraphicsObject(); if ( group == null ) return; group = (Group) group.getChild( 0 ); Node child = group.getChild( 0 ); if ( ! ( child instanceof Shape3D ) ) return; Shape3D polyhedron = (Shape3D) group.getChild( 0 ); if ( polyhedron != null ) { Appearance newAppearance = mFactory.getAppearance( rm.getColor(), rm.getGlow() > 0f, rm.getTransparency() > 0f ); polyhedron.setAppearance( newAppearance ); } } @Override public void locationChanged( RenderedManifestation manifestation ) { } @Override public void orientationChanged( RenderedManifestation manifestation ) { } @Override public void shapeChanged( RenderedManifestation rm ) { Group group = (Group) rm.getGraphicsObject(); if ( group == null ) return; group = (Group) group.getChild( 0 ); Node child = group.getChild( 0 ); // // if ( child instanceof Link ) { // if ( mWhichShape != 0 ) // return; // already switched all the balls // Switch ballSwitch = (Switch) mBallGroup .getChild( 0 ); // // Geometry newShape = mFactory .makeGeometry( rm ); // newShape .setCapability( Geometry .ALLOW_INTERSECT ); // Shape3D polyhedron = new Shape3D( newShape ); // Appearance appearance = mFactory .getAppearance( rm .getColorName(), // rm .getGlow() > 0f, rm .getTransparency() > 0f ); // polyhedron .setAppearance( appearance ); // group = new BranchGroup(); // group .addChild( polyhedron ); // ballSwitch .addChild( group ); // mWhichShape = 1; // ballSwitch .setWhichChild( mWhichShape ); // return; // } // Shape3D polyhedron = (Shape3D) child; Polyhedron oldShape = rm .getShape(); if ( oldShape == null ) { logger .severe( "no shape for: " + rm .getManifestation() .toString() ); } else if ( polyhedron != null ) { Geometry newShape = mFactory.makeSolidGeometry( rm ); newShape.setCapability( Geometry.ALLOW_INTERSECT ); polyhedron.setGeometry( newShape ); } } /* * (non-Javadoc) * * @see * org.vorthmann.zome.render.Colors.Changes#colorChanged(java.lang.Object, * org.vorthmann.zome.render.Color) */ public void backgroundColorChanged( Color newColor ) { float[] rgb = new float[3]; Color3f color = new Color3f( newColor.getRGBColorComponents( rgb ) ); mBackground.setColor( color ); mFog.setColor( color ); } public LinearFog getFog() { return mFog; } private void refreshPolygonOutlines() { Collection<BranchGroup> bgs = new ArrayList<>(); for ( int i = 0; i < mScene .numChildren(); i++ ) { BranchGroup bg = (BranchGroup) mScene .getChild( i ); bgs .add( bg ); } for (BranchGroup branchGroup : bgs) { TransformGroup tg = (TransformGroup) branchGroup .getChild( 0 ); Shape3D solidPoly = (Shape3D) tg .getChild( 0 ); RenderedManifestation rm = (RenderedManifestation) solidPoly .getUserData(); if ( rm != null ) { this .manifestationRemoved( rm ); this .manifestationAdded( rm ); } } } @Override public void propertyChange( PropertyChangeEvent evt ) { propertyChange(evt.getPropertyName(), evt.getNewValue(), evt.getOldValue()); } // properties that don't care about oldValue can call this overloaded method protected void propertyChange(String propertyName, Object newValue) { propertyChange(propertyName, newValue, null); } protected void propertyChange(String propertyName, Object newValue, Object oldValue) { switch (propertyName) { case "drawOutlines": drawOutlines = (Boolean) newValue; ambientForOutlines.setEnable(drawOutlines); directionalForOutlines.setEnable(drawOutlines); refreshPolygonOutlines(); break; case "showFrameLabels": if ((Boolean) newValue) { showFrameLabels(); } else { hideFrameLabels(); } break; case "showIcosahedralLabels": if ((Boolean) newValue) { showIcosahedralLabels(); } else { hideIcosahedralLabels(); } break; default: break; } } private void showFrameLabels() { if (frameLabels == null) { // lazy creation upon first use frameLabels = new FrameLabels(); } mRoot.addChild(frameLabels); } private void hideFrameLabels() { mRoot.removeChild(frameLabels); } private void showIcosahedralLabels() { if (icosahedralLabels == null) { // lazy creation upon first use icosahedralLabels = new IcosahedralLabels(); } mRoot.addChild(icosahedralLabels); } private void hideIcosahedralLabels() { mRoot.removeChild(icosahedralLabels); } }