From 7fad83338964ecd32303d6e2737b19963492ecfe Mon Sep 17 00:00:00 2001 From: Kevin Rushforth Date: Wed, 9 Jun 2004 04:13:21 +0000 Subject: Initial creation of j3d-core-utils sources in CVS repository git-svn-id: https://svn.java.net/svn/j3d-core-utils~svn/trunk@5 9497e636-51bd-65ba-982d-a4982e1767a5 --- src/classes/ToolsVersion | 9 + .../share/com/sun/j3d/ExceptionStrings.properties | 68 + .../com/sun/j3d/audioengines/AudioEngine.java | 208 ++ .../com/sun/j3d/audioengines/AudioEngine3D.java | 497 +++ .../com/sun/j3d/audioengines/AudioEngine3DL2.java | 308 ++ .../sun/j3d/audioengines/AudioEngineThread.java | 275 ++ .../com/sun/j3d/audioengines/AuralParameters.java | 197 + .../share/com/sun/j3d/audioengines/Sample.java | 479 +++ .../audioengines/javasound/JSAuralParameters.java | 79 + .../sun/j3d/audioengines/javasound/JSChannel.java | 435 +++ .../com/sun/j3d/audioengines/javasound/JSClip.java | 349 ++ .../javasound/JSDirectionalSample.java | 738 ++++ .../com/sun/j3d/audioengines/javasound/JSMidi.java | 67 + .../audioengines/javasound/JSPositionalSample.java | 1339 +++++++ .../sun/j3d/audioengines/javasound/JSSample.java | 362 ++ .../sun/j3d/audioengines/javasound/JSStream.java | 67 + .../sun/j3d/audioengines/javasound/JSThread.java | 855 +++++ .../j3d/audioengines/javasound/JavaSoundMixer.java | 959 +++++ .../share/com/sun/j3d/internal/BufferWrapper.java | 186 + .../com/sun/j3d/internal/ByteBufferWrapper.java | 197 + .../com/sun/j3d/internal/ByteOrderWrapper.java | 101 + .../share/com/sun/j3d/internal/Distance.java | 1096 ++++++ .../com/sun/j3d/internal/DoubleBufferWrapper.java | 153 + .../share/com/sun/j3d/internal/FastVector.java | 143 + .../com/sun/j3d/internal/FloatBufferWrapper.java | 152 + .../share/com/sun/j3d/internal/J3dUtilsI18N.java | 63 + .../com/sun/j3d/internal/UtilFreelistManager.java | 98 + .../com/sun/j3d/internal/UtilMemoryFreelist.java | 292 ++ .../sun/j3d/loaders/IncorrectFormatException.java | 62 + src/classes/share/com/sun/j3d/loaders/Loader.java | 176 + .../share/com/sun/j3d/loaders/LoaderBase.java | 147 + .../com/sun/j3d/loaders/ParsingErrorException.java | 62 + src/classes/share/com/sun/j3d/loaders/Scene.java | 148 + .../share/com/sun/j3d/loaders/SceneBase.java | 311 ++ .../com/sun/j3d/loaders/lw3d/DebugOutput.java | 85 + .../com/sun/j3d/loaders/lw3d/EnvelopeHandler.java | 133 + .../j3d/loaders/lw3d/FloatValueInterpolator.java | 171 + .../com/sun/j3d/loaders/lw3d/ImageScaler.java | 148 + .../com/sun/j3d/loaders/lw3d/J3dLwoParser.java | 601 +++ .../com/sun/j3d/loaders/lw3d/LWOBFileReader.java | 266 ++ .../lw3d/LightIntensityPathInterpolator.java | 101 + .../share/com/sun/j3d/loaders/lw3d/Lw3dLoader.java | 706 ++++ .../com/sun/j3d/loaders/lw3d/LwLightObject.java | 100 + .../share/com/sun/j3d/loaders/lw3d/LwoParser.java | 424 +++ .../share/com/sun/j3d/loaders/lw3d/LwoSurface.java | 316 ++ .../share/com/sun/j3d/loaders/lw3d/LwoTexture.java | 284 ++ .../com/sun/j3d/loaders/lw3d/LwsBackground.java | 163 + .../share/com/sun/j3d/loaders/lw3d/LwsCamera.java | 179 + .../com/sun/j3d/loaders/lw3d/LwsEnvelope.java | 160 + .../com/sun/j3d/loaders/lw3d/LwsEnvelopeFrame.java | 105 + .../loaders/lw3d/LwsEnvelopeLightIntensity.java | 153 + .../share/com/sun/j3d/loaders/lw3d/LwsFog.java | 143 + .../share/com/sun/j3d/loaders/lw3d/LwsFrame.java | 341 ++ .../share/com/sun/j3d/loaders/lw3d/LwsLight.java | 269 ++ .../share/com/sun/j3d/loaders/lw3d/LwsMotion.java | 688 ++++ .../share/com/sun/j3d/loaders/lw3d/LwsObject.java | 570 +++ .../com/sun/j3d/loaders/lw3d/LwsPrimitive.java | 68 + .../com/sun/j3d/loaders/lw3d/ParserObject.java | 91 + .../com/sun/j3d/loaders/lw3d/SequenceLine.java | 269 ++ .../com/sun/j3d/loaders/lw3d/SequenceReader.java | 172 + .../com/sun/j3d/loaders/lw3d/ShapeHolder.java | 267 ++ .../j3d/loaders/lw3d/SwitchPathInterpolator.java | 129 + .../com/sun/j3d/loaders/lw3d/TargaReader.java | 199 + .../com/sun/j3d/loaders/lw3d/TextfileParser.java | 245 ++ .../j3d/loaders/objectfile/DefaultMaterials.java | 952 +++++ .../com/sun/j3d/loaders/objectfile/ObjectFile.java | 1334 +++++++ .../loaders/objectfile/ObjectFileMaterials.java | 425 +++ .../j3d/loaders/objectfile/ObjectFileParser.java | 179 + .../com/sun/j3d/loaders/objectfile/RgbFile.java | 202 + .../share/com/sun/j3d/utils/applet/JMainFrame.java | 347 ++ .../share/com/sun/j3d/utils/applet/MainFrame.java | 397 ++ .../sun/j3d/utils/audio/DistanceAttenuation.java | 134 + .../behaviors/interpolators/CubicSplineCurve.java | 175 + .../interpolators/CubicSplineSegment.java | 543 +++ .../interpolators/KBCubicSplineCurve.java | 174 + .../interpolators/KBCubicSplineSegment.java | 630 ++++ .../utils/behaviors/interpolators/KBKeyFrame.java | 150 + .../KBRotPosScaleSplinePathInterpolator.java | 313 ++ .../interpolators/KBSplinePathInterpolator.java | 301 ++ .../RotPosScaleTCBSplinePathInterpolator.java | 247 ++ .../utils/behaviors/interpolators/TCBKeyFrame.java | 144 + .../interpolators/TCBSplinePathInterpolator.java | 278 ++ .../j3d/utils/behaviors/keyboard/KeyNavigator.java | 499 +++ .../behaviors/keyboard/KeyNavigatorBehavior.java | 216 ++ .../j3d/utils/behaviors/mouse/MouseBehavior.java | 329 ++ .../behaviors/mouse/MouseBehaviorCallback.java | 73 + .../sun/j3d/utils/behaviors/mouse/MouseRotate.java | 315 ++ .../j3d/utils/behaviors/mouse/MouseTranslate.java | 290 ++ .../sun/j3d/utils/behaviors/mouse/MouseZoom.java | 271 ++ .../sun/j3d/utils/behaviors/picking/Intersect.java | 1297 +++++++ .../utils/behaviors/picking/PickMouseBehavior.java | 152 + .../j3d/utils/behaviors/picking/PickObject.java | 841 +++++ .../behaviors/picking/PickRotateBehavior.java | 194 + .../behaviors/picking/PickTranslateBehavior.java | 178 + .../utils/behaviors/picking/PickZoomBehavior.java | 180 + .../utils/behaviors/picking/PickingCallback.java | 73 + .../behaviors/sensor/Mouse6DPointerBehavior.java | 195 + .../j3d/utils/behaviors/sensor/SensorBeamEcho.java | 231 ++ .../behaviors/sensor/SensorButtonListener.java | 94 + .../j3d/utils/behaviors/sensor/SensorEvent.java | 336 ++ .../utils/behaviors/sensor/SensorEventAgent.java | 715 ++++ .../utils/behaviors/sensor/SensorGnomonEcho.java | 225 ++ .../utils/behaviors/sensor/SensorInputAdaptor.java | 71 + .../utils/behaviors/sensor/SensorReadListener.java | 72 + .../sun/j3d/utils/behaviors/vp/OrbitBehavior.java | 1047 ++++++ .../behaviors/vp/ViewPlatformAWTBehavior.java | 400 ++ .../utils/behaviors/vp/ViewPlatformBehavior.java | 137 + .../j3d/utils/behaviors/vp/WandViewBehavior.java | 3843 ++++++++++++++++++++ .../sun/j3d/utils/compression/CommandStream.java | 265 ++ .../utils/compression/CompressedGeometryFile.java | 1007 +++++ .../j3d/utils/compression/CompressionStream.java | 2294 ++++++++++++ .../utils/compression/CompressionStreamColor.java | 270 ++ .../compression/CompressionStreamElement.java | 359 ++ .../utils/compression/CompressionStreamNormal.java | 673 ++++ .../utils/compression/CompressionStreamVertex.java | 318 ++ .../j3d/utils/compression/GeometryCompressor.java | 203 ++ .../com/sun/j3d/utils/compression/HuffmanNode.java | 223 ++ .../sun/j3d/utils/compression/HuffmanTable.java | 477 +++ .../com/sun/j3d/utils/compression/MeshBuffer.java | 238 ++ .../share/com/sun/j3d/utils/geometry/BBox.java | 128 + .../share/com/sun/j3d/utils/geometry/Basic.java | 168 + .../com/sun/j3d/utils/geometry/BottleNeck.java | 167 + .../share/com/sun/j3d/utils/geometry/Box.java | 469 +++ .../share/com/sun/j3d/utils/geometry/Bridge.java | 398 ++ .../share/com/sun/j3d/utils/geometry/Clean.java | 193 + .../com/sun/j3d/utils/geometry/ColorCube.java | 175 + .../share/com/sun/j3d/utils/geometry/Cone.java | 428 +++ .../share/com/sun/j3d/utils/geometry/Cylinder.java | 421 +++ .../com/sun/j3d/utils/geometry/Degenerate.java | 170 + .../com/sun/j3d/utils/geometry/Desperate.java | 431 +++ .../share/com/sun/j3d/utils/geometry/Distance.java | 73 + .../share/com/sun/j3d/utils/geometry/EarClip.java | 348 ++ .../share/com/sun/j3d/utils/geometry/Edge.java | 89 + .../com/sun/j3d/utils/geometry/EdgeTable.java | 116 + .../com/sun/j3d/utils/geometry/GeomBuffer.java | 557 +++ .../com/sun/j3d/utils/geometry/GeometryInfo.java | 2826 ++++++++++++++ .../j3d/utils/geometry/GeometryInfoGenerator.java | 857 +++++ .../share/com/sun/j3d/utils/geometry/Heap.java | 212 ++ .../share/com/sun/j3d/utils/geometry/HeapNode.java | 75 + .../share/com/sun/j3d/utils/geometry/Left.java | 74 + .../share/com/sun/j3d/utils/geometry/ListNode.java | 87 + .../share/com/sun/j3d/utils/geometry/NoHash.java | 302 ++ .../sun/j3d/utils/geometry/NormalGenerator.java | 907 +++++ .../share/com/sun/j3d/utils/geometry/Numerics.java | 644 ++++ .../com/sun/j3d/utils/geometry/Orientation.java | 173 + .../share/com/sun/j3d/utils/geometry/PntNode.java | 70 + .../com/sun/j3d/utils/geometry/Primitive.java | 262 ++ .../share/com/sun/j3d/utils/geometry/Project.java | 254 ++ .../share/com/sun/j3d/utils/geometry/Quadrics.java | 722 ++++ .../share/com/sun/j3d/utils/geometry/Simple.java | 225 ++ .../share/com/sun/j3d/utils/geometry/Sphere.java | 502 +++ .../com/sun/j3d/utils/geometry/Stripifier.java | 2535 +++++++++++++ .../sun/j3d/utils/geometry/StripifierStats.java | 345 ++ .../share/com/sun/j3d/utils/geometry/Text2D.java | 382 ++ .../share/com/sun/j3d/utils/geometry/Triangle.java | 69 + .../com/sun/j3d/utils/geometry/Triangulator.java | 1049 ++++++ .../com/sun/j3d/utils/image/TextureLoader.java | 779 ++++ .../com/sun/j3d/utils/picking/PickCanvas.java | 248 ++ .../sun/j3d/utils/picking/PickIntersection.java | 1570 ++++++++ .../com/sun/j3d/utils/picking/PickResult.java | 3344 +++++++++++++++++ .../share/com/sun/j3d/utils/picking/PickTool.java | 1027 ++++++ .../utils/picking/behaviors/PickMouseBehavior.java | 182 + .../picking/behaviors/PickRotateBehavior.java | 172 + .../picking/behaviors/PickTranslateBehavior.java | 157 + .../utils/picking/behaviors/PickZoomBehavior.java | 155 + .../utils/picking/behaviors/PickingCallback.java | 76 + .../utils/scenegraph/io/NamedObjectException.java | 68 + .../scenegraph/io/ObjectNotLoadedException.java | 68 + .../utils/scenegraph/io/SceneGraphFileReader.java | 224 ++ .../utils/scenegraph/io/SceneGraphFileWriter.java | 157 + .../sun/j3d/utils/scenegraph/io/SceneGraphIO.java | 112 + .../io/SceneGraphObjectReferenceControl.java | 69 + .../scenegraph/io/SceneGraphStreamReader.java | 117 + .../scenegraph/io/SceneGraphStreamWriter.java | 127 + .../utils/scenegraph/io/UnresolvedBehavior.java | 64 + .../io/UnsupportedUniverseException.java | 71 + .../scenegraph/io/doc-files/extensibility.html | 189 + .../com/sun/j3d/utils/scenegraph/io/package.html | 105 + .../utils/scenegraph/io/retained/Controller.java | 943 +++++ .../scenegraph/io/retained/J3fInputStream.java | 136 + .../scenegraph/io/retained/J3fOutputStream.java | 132 + .../io/retained/PositionInputStream.java | 98 + .../io/retained/PositionOutputStream.java | 91 + .../io/retained/RandomAccessFileControl.java | 500 +++ .../io/retained/SGIORuntimeException.java | 74 + .../scenegraph/io/retained/StreamControl.java | 205 ++ .../utils/scenegraph/io/retained/SymbolTable.java | 851 +++++ .../scenegraph/io/retained/SymbolTableData.java | 134 + .../KBRotPosScaleSplinePathInterpolatorState.java | 131 + .../RotPosScaleTCBSplinePathInterpolatorState.java | 128 + .../utils/behaviors/mouse/MouseBehaviorState.java | 85 + .../state/com/sun/j3d/utils/geometry/BoxState.java | 155 + .../com/sun/j3d/utils/geometry/ColorCubeState.java | 98 + .../com/sun/j3d/utils/geometry/ConeState.java | 144 + .../com/sun/j3d/utils/geometry/CylinderState.java | 143 + .../com/sun/j3d/utils/geometry/PrimitiveState.java | 86 + .../com/sun/j3d/utils/geometry/SphereState.java | 120 + .../com/sun/j3d/utils/geometry/Text2DState.java | 137 + .../sun/j3d/utils/image/ImageComponent2DURL.java | 122 + .../utils/image/ImageComponent2DURLIOListener.java | 71 + .../j3d/utils/image/ImageComponent2DURLState.java | 131 + .../j3d/utils/universe/PlatformGeometryState.java | 66 + .../j3d/utils/universe/SimpleUniverseState.java | 331 ++ .../sun/j3d/utils/universe/ViewerAvatarState.java | 67 + .../io/state/javax/media/j3d/AlphaState.java | 100 + .../javax/media/j3d/AlternateAppearanceState.java | 126 + .../state/javax/media/j3d/AmbientLightState.java | 61 + .../io/state/javax/media/j3d/AppearanceState.java | 191 + .../javax/media/j3d/AuralAttributesState.java | 125 + .../javax/media/j3d/BackgroundSoundState.java | 74 + .../io/state/javax/media/j3d/BackgroundState.java | 125 + .../io/state/javax/media/j3d/BehaviorState.java | 106 + .../io/state/javax/media/j3d/BillboardState.java | 105 + .../state/javax/media/j3d/BoundingLeafState.java | 87 + .../io/state/javax/media/j3d/BranchGroupState.java | 63 + .../io/state/javax/media/j3d/ClipState.java | 86 + .../javax/media/j3d/ColorInterpolatorState.java | 114 + .../javax/media/j3d/ColoringAttributesState.java | 84 + .../javax/media/j3d/CompressedGeometryState.java | 151 + .../io/state/javax/media/j3d/ConeSoundState.java | 122 + .../io/state/javax/media/j3d/DecalGroupState.java | 64 + .../javax/media/j3d/DepthComponentFloatState.java | 111 + .../javax/media/j3d/DepthComponentIntState.java | 111 + .../javax/media/j3d/DepthComponentNativeState.java | 94 + .../javax/media/j3d/DirectionalLightState.java | 77 + .../io/state/javax/media/j3d/DistanceLODState.java | 108 + .../state/javax/media/j3d/ExponentialFogState.java | 78 + .../io/state/javax/media/j3d/FogState.java | 108 + .../io/state/javax/media/j3d/Font3DState.java | 163 + .../state/javax/media/j3d/GeometryArrayState.java | 925 +++++ .../io/state/javax/media/j3d/GeometryState.java | 59 + .../javax/media/j3d/GeometryStripArrayState.java | 81 + .../io/state/javax/media/j3d/GroupState.java | 142 + .../javax/media/j3d/ImageComponent2DState.java | 114 + .../javax/media/j3d/ImageComponent3DState.java | 139 + .../state/javax/media/j3d/ImageComponentState.java | 385 ++ .../javax/media/j3d/IndexedGeometryArrayState.java | 176 + .../media/j3d/IndexedGeometryStripArrayState.java | 81 + .../javax/media/j3d/IndexedLineArrayState.java | 92 + .../media/j3d/IndexedLineStripArrayState.java | 86 + .../javax/media/j3d/IndexedPointArrayState.java | 93 + .../javax/media/j3d/IndexedQuadArrayState.java | 92 + .../javax/media/j3d/IndexedTriangleArrayState.java | 92 + .../media/j3d/IndexedTriangleFanArrayState.java | 95 + .../media/j3d/IndexedTriangleStripArrayState.java | 95 + .../state/javax/media/j3d/InterpolatorState.java | 88 + .../io/state/javax/media/j3d/LODState.java | 95 + .../io/state/javax/media/j3d/LeafState.java | 58 + .../io/state/javax/media/j3d/LightState.java | 113 + .../io/state/javax/media/j3d/LineArrayState.java | 76 + .../state/javax/media/j3d/LineAttributesState.java | 87 + .../state/javax/media/j3d/LineStripArrayState.java | 78 + .../io/state/javax/media/j3d/LinearFogState.java | 80 + .../io/state/javax/media/j3d/LinkState.java | 103 + .../io/state/javax/media/j3d/MaterialState.java | 96 + .../state/javax/media/j3d/MediaContainerState.java | 80 + .../io/state/javax/media/j3d/ModelClipState.java | 133 + .../io/state/javax/media/j3d/MorphState.java | 134 + .../state/javax/media/j3d/NodeComponentState.java | 85 + .../io/state/javax/media/j3d/NodeState.java | 88 + .../javax/media/j3d/NullSceneGraphObjectState.java | 105 + .../state/javax/media/j3d/OrderedGroupState.java | 85 + .../javax/media/j3d/OrientedShape3DState.java | 96 + .../javax/media/j3d/PathInterpolatorState.java | 81 + .../javax/media/j3d/PointAttributesState.java | 81 + .../io/state/javax/media/j3d/PointLightState.java | 79 + .../io/state/javax/media/j3d/PointSoundState.java | 101 + .../javax/media/j3d/PolygonAttributesState.java | 87 + .../javax/media/j3d/PositionInterpolatorState.java | 88 + .../media/j3d/PositionPathInterpolatorState.java | 104 + .../io/state/javax/media/j3d/QuadArrayState.java | 86 + .../io/state/javax/media/j3d/RasterState.java | 139 + .../javax/media/j3d/RenderingAttributesState.java | 93 + .../media/j3d/RotPosPathInterpolatorState.java | 118 + .../j3d/RotPosScalePathInterpolatorState.java | 124 + .../javax/media/j3d/RotationInterpolatorState.java | 90 + .../media/j3d/RotationPathInterpolatorState.java | 108 + .../javax/media/j3d/ScaleInterpolatorState.java | 89 + .../javax/media/j3d/SceneGraphObjectState.java | 506 +++ .../io/state/javax/media/j3d/Shape3DState.java | 121 + .../io/state/javax/media/j3d/SharedGroupState.java | 63 + .../io/state/javax/media/j3d/SoundState.java | 127 + .../io/state/javax/media/j3d/SoundscapeState.java | 103 + .../io/state/javax/media/j3d/SpotLightState.java | 81 + .../io/state/javax/media/j3d/SwitchState.java | 82 + .../media/j3d/SwitchValueInterpolatorState.java | 104 + .../javax/media/j3d/TexCoordGenerationState.java | 97 + .../io/state/javax/media/j3d/Text3DState.java | 115 + .../io/state/javax/media/j3d/Texture2DState.java | 140 + .../io/state/javax/media/j3d/Texture3DState.java | 103 + .../javax/media/j3d/TextureAttributesState.java | 139 + .../state/javax/media/j3d/TextureCubeMapState.java | 117 + .../io/state/javax/media/j3d/TextureState.java | 231 ++ .../javax/media/j3d/TextureUnitStateState.java | 111 + .../state/javax/media/j3d/TransformGroupState.java | 91 + .../media/j3d/TransformInterpolatorState.java | 88 + .../media/j3d/TransparencyAttributesState.java | 86 + .../media/j3d/TransparencyInterpolatorState.java | 112 + .../state/javax/media/j3d/TriangleArrayState.java | 92 + .../javax/media/j3d/TriangleFanArrayState.java | 94 + .../javax/media/j3d/TriangleStripArrayState.java | 95 + .../state/javax/media/j3d/ViewPlatformState.java | 83 + .../share/com/sun/j3d/utils/timer/J3DTimer.java | 102 + .../share/com/sun/j3d/utils/timer/package.html | 57 + .../com/sun/j3d/utils/universe/ConfigCommand.java | 417 +++ .../sun/j3d/utils/universe/ConfigContainer.java | 1479 ++++++++ .../com/sun/j3d/utils/universe/ConfigDevice.java | 66 + .../com/sun/j3d/utils/universe/ConfigObject.java | 371 ++ .../sun/j3d/utils/universe/ConfigPhysicalBody.java | 178 + .../utils/universe/ConfigPhysicalEnvironment.java | 179 + .../com/sun/j3d/utils/universe/ConfigScreen.java | 307 ++ .../com/sun/j3d/utils/universe/ConfigSensor.java | 211 ++ .../sun/j3d/utils/universe/ConfigSexpression.java | 569 +++ .../com/sun/j3d/utils/universe/ConfigView.java | 469 +++ .../sun/j3d/utils/universe/ConfigViewPlatform.java | 255 ++ .../utils/universe/ConfigViewPlatformBehavior.java | 139 + .../sun/j3d/utils/universe/ConfiguredUniverse.java | 768 ++++ .../com/sun/j3d/utils/universe/LocaleFactory.java | 82 + .../j3d/utils/universe/MultiTransformGroup.java | 140 + .../sun/j3d/utils/universe/PlatformGeometry.java | 66 + .../com/sun/j3d/utils/universe/SimpleUniverse.java | 412 +++ .../share/com/sun/j3d/utils/universe/ViewInfo.java | 3398 +++++++++++++++++ .../share/com/sun/j3d/utils/universe/Viewer.java | 1012 ++++++ .../com/sun/j3d/utils/universe/ViewerAvatar.java | 66 + .../sun/j3d/utils/universe/ViewingPlatform.java | 519 +++ .../utils/universe/doc-files/config-examples.html | 85 + .../utils/universe/doc-files/config-syntax.html | 1973 ++++++++++ .../utils/universe/doc-files/j3d1x1-behavior.html | 109 + .../utils/universe/doc-files/j3d1x1-stereo.html | 90 + .../j3d/utils/universe/doc-files/j3d1x1-vr.html | 173 + .../utils/universe/doc-files/j3d1x1-window.html | 70 + .../sun/j3d/utils/universe/doc-files/j3d1x1.html | 69 + .../j3d/utils/universe/doc-files/j3d1x2-flat.html | 152 + .../j3d/utils/universe/doc-files/j3d1x2-rot30.html | 111 + .../utils/universe/doc-files/j3d1x3-cave-vr.html | 243 ++ .../j3d/utils/universe/doc-files/j3d1x3-cave.html | 156 + .../j3d/utils/universe/doc-files/j3d1x3-rot45.html | 122 + .../j3d/utils/universe/doc-files/j3d2x2-flat.html | 147 + src/native/share/J3DTimer.c | 153 + 339 files changed, 101338 insertions(+) create mode 100644 src/classes/ToolsVersion create mode 100644 src/classes/share/com/sun/j3d/ExceptionStrings.properties create mode 100644 src/classes/share/com/sun/j3d/audioengines/AudioEngine.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/AuralParameters.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/Sample.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSAuralParameters.java create mode 100644 src/classes/share/com/sun/j3d/audioengines/javasound/JSChannel.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSMidi.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java create mode 100755 src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java create mode 100755 src/classes/share/com/sun/j3d/internal/BufferWrapper.java create mode 100755 src/classes/share/com/sun/j3d/internal/ByteBufferWrapper.java create mode 100755 src/classes/share/com/sun/j3d/internal/ByteOrderWrapper.java create mode 100644 src/classes/share/com/sun/j3d/internal/Distance.java create mode 100755 src/classes/share/com/sun/j3d/internal/DoubleBufferWrapper.java create mode 100644 src/classes/share/com/sun/j3d/internal/FastVector.java create mode 100755 src/classes/share/com/sun/j3d/internal/FloatBufferWrapper.java create mode 100644 src/classes/share/com/sun/j3d/internal/J3dUtilsI18N.java create mode 100644 src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java create mode 100644 src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java create mode 100644 src/classes/share/com/sun/j3d/loaders/IncorrectFormatException.java create mode 100644 src/classes/share/com/sun/j3d/loaders/Loader.java create mode 100644 src/classes/share/com/sun/j3d/loaders/LoaderBase.java create mode 100644 src/classes/share/com/sun/j3d/loaders/ParsingErrorException.java create mode 100644 src/classes/share/com/sun/j3d/loaders/Scene.java create mode 100644 src/classes/share/com/sun/j3d/loaders/SceneBase.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/DebugOutput.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/EnvelopeHandler.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/FloatValueInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/ImageScaler.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/J3dLwoParser.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LWOBFileReader.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LightIntensityPathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/Lw3dLoader.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwLightObject.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwoParser.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwoSurface.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwoTexture.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsBackground.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsCamera.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelope.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeFrame.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeLightIntensity.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsFog.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsFrame.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsLight.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsMotion.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsObject.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/LwsPrimitive.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/ParserObject.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/SequenceLine.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/SequenceReader.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/ShapeHolder.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/SwitchPathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/TargaReader.java create mode 100644 src/classes/share/com/sun/j3d/loaders/lw3d/TextfileParser.java create mode 100644 src/classes/share/com/sun/j3d/loaders/objectfile/DefaultMaterials.java create mode 100644 src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFile.java create mode 100644 src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileMaterials.java create mode 100644 src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileParser.java create mode 100644 src/classes/share/com/sun/j3d/loaders/objectfile/RgbFile.java create mode 100644 src/classes/share/com/sun/j3d/utils/applet/JMainFrame.java create mode 100644 src/classes/share/com/sun/j3d/utils/applet/MainFrame.java create mode 100644 src/classes/share/com/sun/j3d/utils/audio/DistanceAttenuation.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/CubicSplineCurve.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/CubicSplineSegment.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineCurve.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineSegment.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBKeyFrame.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBRotPosScaleSplinePathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBSplinePathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/RotPosScaleTCBSplinePathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBKeyFrame.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBSplinePathInterpolator.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigator.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigatorBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehaviorCallback.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseRotate.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseTranslate.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseZoom.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/Intersect.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickMouseBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickObject.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickRotateBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickTranslateBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickZoomBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/picking/PickingCallback.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/Mouse6DPointerBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorBeamEcho.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorButtonListener.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEvent.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEventAgent.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorGnomonEcho.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorInputAdaptor.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorReadListener.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/vp/OrbitBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformAWTBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/behaviors/vp/WandViewBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CommandStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java create mode 100644 src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/BBox.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Basic.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/BottleNeck.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Box.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Bridge.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Clean.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/ColorCube.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Cone.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Cylinder.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Degenerate.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Desperate.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Distance.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/EarClip.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Edge.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/EdgeTable.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/GeomBuffer.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/GeometryInfo.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/GeometryInfoGenerator.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Heap.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/HeapNode.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Left.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/ListNode.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/NoHash.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/NormalGenerator.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Numerics.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Orientation.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/PntNode.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Primitive.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Project.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Quadrics.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Simple.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Sphere.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Stripifier.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/StripifierStats.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Text2D.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Triangle.java create mode 100644 src/classes/share/com/sun/j3d/utils/geometry/Triangulator.java create mode 100644 src/classes/share/com/sun/j3d/utils/image/TextureLoader.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/PickCanvas.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/PickIntersection.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/PickResult.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/PickTool.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/behaviors/PickMouseBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/behaviors/PickRotateBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/picking/behaviors/PickingCallback.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/NamedObjectException.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/ObjectNotLoadedException.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileReader.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileWriter.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphIO.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphObjectReferenceControl.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamReader.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamWriter.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/UnresolvedBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/UnsupportedUniverseException.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/doc-files/extensibility.html create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/package.html create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/Controller.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/J3fInputStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/J3fOutputStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/PositionInputStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/PositionOutputStream.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/RandomAccessFileControl.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/SGIORuntimeException.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/StreamControl.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/SymbolTable.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/SymbolTableData.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/behaviors/interpolators/KBRotPosScaleSplinePathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/behaviors/interpolators/RotPosScaleTCBSplinePathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/behaviors/mouse/MouseBehaviorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/BoxState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/ColorCubeState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/ConeState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/CylinderState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/PrimitiveState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/SphereState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/geometry/Text2DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/image/ImageComponent2DURL.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/image/ImageComponent2DURLIOListener.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/image/ImageComponent2DURLState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/universe/PlatformGeometryState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/universe/SimpleUniverseState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/com/sun/j3d/utils/universe/ViewerAvatarState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AlphaState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AlternateAppearanceState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AmbientLightState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AppearanceState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AuralAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BackgroundSoundState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BackgroundState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BehaviorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BillboardState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BoundingLeafState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/BranchGroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ClipState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ColorInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ColoringAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/CompressedGeometryState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ConeSoundState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DecalGroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DepthComponentFloatState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DepthComponentIntState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DepthComponentNativeState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DirectionalLightState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/DistanceLODState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ExponentialFogState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/FogState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/Font3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/GeometryArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/GeometryState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/GeometryStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/GroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ImageComponent2DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ImageComponent3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ImageComponentState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedGeometryArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedGeometryStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedLineArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedLineStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedPointArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedQuadArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedTriangleArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedTriangleFanArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/IndexedTriangleStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/InterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LODState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LeafState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LightState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LineArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LineAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LineStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LinearFogState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/LinkState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/MaterialState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/MediaContainerState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ModelClipState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/MorphState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NodeComponentState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NodeState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NullSceneGraphObjectState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/OrderedGroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/OrientedShape3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PointAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PointLightState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PointSoundState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PolygonAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PositionInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/PositionPathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/QuadArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RasterState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RenderingAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RotPosPathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RotPosScalePathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RotationInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/RotationPathInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ScaleInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SceneGraphObjectState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/Shape3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SharedGroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SoundState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SoundscapeState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SpotLightState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SwitchState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/SwitchValueInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TexCoordGenerationState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/Text3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/Texture2DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/Texture3DState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TextureAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TextureCubeMapState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TextureState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TextureUnitStateState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TransformGroupState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TransformInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TransparencyAttributesState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TransparencyInterpolatorState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TriangleArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TriangleFanArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/TriangleStripArrayState.java create mode 100644 src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/ViewPlatformState.java create mode 100644 src/classes/share/com/sun/j3d/utils/timer/J3DTimer.java create mode 100644 src/classes/share/com/sun/j3d/utils/timer/package.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigCommand.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigContainer.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigDevice.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigObject.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalBody.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalEnvironment.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigScreen.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigSensor.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigSexpression.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigView.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatform.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatformBehavior.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ConfiguredUniverse.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/LocaleFactory.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/MultiTransformGroup.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/PlatformGeometry.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/SimpleUniverse.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/Viewer.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ViewerAvatar.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/ViewingPlatform.java create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/config-examples.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/config-syntax.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-behavior.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-stereo.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-vr.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-window.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-flat.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-rot30.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave-vr.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-rot45.html create mode 100644 src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d2x2-flat.html create mode 100644 src/native/share/J3DTimer.c (limited to 'src') diff --git a/src/classes/ToolsVersion b/src/classes/ToolsVersion new file mode 100644 index 0000000..474fa2c --- /dev/null +++ b/src/classes/ToolsVersion @@ -0,0 +1,9 @@ +Manifest-Version: 1.0 +Specification-Title: +Specification-Version: 1.3 +Specification-Vendor: Sun Microsystems, Inc. +Implementation-Title: Java 3D Utilities Runtime Environment +Implementation-Version: 1.3.2 +Implementation-Vendor: Sun Microsystems, Inc. +Extension-Name: javax.media.j3d +Implementation-Vendor-Id: com.sun diff --git a/src/classes/share/com/sun/j3d/ExceptionStrings.properties b/src/classes/share/com/sun/j3d/ExceptionStrings.properties new file mode 100644 index 0000000..bf031b7 --- /dev/null +++ b/src/classes/share/com/sun/j3d/ExceptionStrings.properties @@ -0,0 +1,68 @@ +Intersect0=Intersect.rayAndQuad : This quad has less than 4 points! +Intersect1=Intersect.rayAndTriangle : This triangle has less than 3 points! +Intersect3=Intersect.segmentAndQuad : This quad has less than 4 points! +Intersect5=Intersect.segmentAndTriangle:This triange has less than 3 points! +Intersect6=Intersect.segmentAndTriangle : This triange has less than 3 points! +Intersect7=Intersect.pointAndquad : This quad has less than 4 points! +Intersect9=Intersect.pointAndTriangle: This triange has less than 3 points! +Intersect10=Intersect.pointAndTriangle : This triange has less than 3 points! +Intersect11=Intersect.rayAndLine : This line has less than 2 points! +Intersect13=Intersect.segmentAndLine : This line has less than 2 points! +TCBKeyFrame0=TCBKeyFrame: tension value should be between -1 and 1 +TCBKeyFrame1=TCBKeyFrame: bias value should be between -1 and 1 +TCBKeyFrame2=TCBKeyFrame: continuity value should be between -1 and 1 +TCBSplinePathInterpolator0=TCBSplinePathInterpolator: need at least 2 Key Frames for the interpolator +TCBSplinePathInterpolator1=TCBSplinePathInterpolator: first key frame should have knot value of 0.0 +TCBSplinePathInterpolator2=TCBSplinePathInterpolator: last key frame should have knot value of 1.0 +TCBSplinePathInterpolator3=TCBSplinePathInterpolator: Key Frame knot values not in sequence +CubicSplineCurve0=CubicSplineCurve needs at least 4 key frames +KBKeyFrame0=KBKeyFrame: tension value should be between -1 and 1 +KBKeyFrame1=KBKeyFrame: bias value should be between -1 and 1 +KBKeyFrame2=KBKeyFrame: continuity value should be between -1 and 1 +KBSplinePathInterpolator0=KBSplinePathInterpolator: need at least 2 Key Frames for the interpolator +KBSplinePathInterpolator1=KBSplinePathInterpolator: first key frame should have knot value of 0.0 +KBSplinePathInterpolator2=KBSplinePathInterpolator: last key frame should have knot value of 1.0 +KBSplinePathInterpolator3=KBSplinePathInterpolator: Key Frame knot values not in sequence +KBCubicSplineCurve0=KBCubicSplineCurve needs at least 4 key frames +GeometryInfo0=Illegal primitive. +GeometryInfo1=Illegal use of deprecated setTextureCoordinateIndices(int[]) +GeometryInfo2=Length of float array not a multiple of dimensionality of texcoords +GeometryInfo3=Coordinate data required. +GeometryInfo4=Color Index list set with no color list set. +GeometryInfo5=Index lists must all be the same length +GeometryInfo6=StripCounts inconsistent with primitive +GeometryInfo7=stripCounts sum inconsistent with number of vertices. +GeometryInfo8=Sum of contourCounts must equal length of stripCounts array. +GeometryInfo9=Data must be one of Point3f, Color3f, Color4f, Vector3f, TexCoord2f, TexCoord3f or TexCoord4f. +GeometryInfo10=Missing Texture Coordinate data list +GeometryInfo11=Normal Index list set with no normal list set. +GeometryInfo12=For triangles, number of vertices must be multiple of 3. +GeometryInfo13=For quads, number of vertices must be multiple of 4. +GeometryInfo14=contourCounts only useful when primitive is POLYGON_ARRAY. +GeometryInfo15=2D texture coordinates not specified. +GeometryInfo16=3D texture coordinates not specified. +GeometryInfo17=4D texture coordinates not specified. +GeometryInfo18=Invalid texture coordinate set index. +GeometryInfo19=Missing Index List. +GeometryInfo20=Non-coordinate index list set in USE_COORD_INDEX_ONLY mode +GeometryInfo21=setTextureCoordinateParams not called +GeometryInfoGenerator0=Unsupported geometry type +Triangulator0=GeometryInfo must have primitive type POLYGON_ARRAY. +ViewingPlatform0=Multiple Viewer support not implemented +ViewingPlatform1=TransformGroup does not exist +DistanceAttenuation0=Distance attenuation array null +VrmlParser0=VRML binary parser disabled +LwsMotion0=Number of motion channels != 9! +LwoSurface0=VSPC problem +LwoParser0=File not of FORM-length-LWOB format +LwsEnvelope0=Number of envelope channels != 1! +SwitchPathInterpolator0=SwitchPathInterpolator: length of knots and numChildren must be equal +FloatValueInterpolator0=FloatValueInterpolator: first knot is not 0.0 +FloatValueInterpolator1=FloatValueInterpolator: last knot is not 1.0 +FloatValueInterpolator2=FloatValueInterpolator: knot values out of order +FloatValueInterpolator3=FloatValueInterpolator: number of knots and values must be equal +JavaSoundMixer0=JavaSoundMixer.prepareSound - bad URL +Behavior0=Cannot call addListener on a Behavior that was not created as a listener. +Stripifier0=Cannot getStripifierStats on a Stripifier object that was not created with the COLLECT_STATS flag. +OrbitBehavior0=Specified function must be one of ROTATE, TRANSLATE or ZOOM. +OrbitBehavior1=Minimum Orbit radius must be > 0.0. diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java new file mode 100644 index 0000000..8d4d36f --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine.java @@ -0,0 +1,208 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +import javax.media.j3d.*; + +/** + * The AudioEngine Class defines an audio output device that generates + * sound 'image' from scene graph. + * An AudioEngine object encapsulates the AudioDevice's basic information. + * + *

+ * NOTE: AudioEngine developers should not subclass this class directly. + * Subclass AudioEngine3DL2 instead. + */ +public class AudioEngine implements AudioDevice { + + /* + * This device's UNIX file descriptor + */ + int fileDescriptor; + + /* + * Type of audio output device J3D sound is played over: + * HEADPHONE, MONO_SPEAKER, STEREO_SPEAKERS + */ + int audioPlaybackType = HEADPHONES; + + /* + * Distance from center ear (midpoint between ears) to physical speaker. + * Default reflects distance for headphones. + * For two speakers it is assumed that the speakers are the same + * distance from the listener and that + */ + float distanceToSpeaker = 0.0f; + + /* + * Angle between the vector from center ear parallel to head coordiate + * Z axis and the vector from the center ear to the speaker. + * For two speakers it is assumed that the speakers are placed at the + * same angular offset from the listener. + */ + float angleOffsetToSpeaker = 0.0f; + + /* + * Channels currently available + */ + int channelsAvailable = 8; + + /* + * Total number of Channels ever available + */ + int totalChannels = 8; + + /** + * Construct a new AudioEngine with the specified P.E. + * @param physicalEnvironment the physical environment object where we + * want access to this device. + */ + public AudioEngine(PhysicalEnvironment physicalEnvironment ) { + physicalEnvironment.setAudioDevice(this); + } + + /** + * Code to initialize the device + * @return flag: true is initialized sucessfully, false if error + */ + public boolean initialize() { + // Expected to be over-ridden by extending class + return true; + } + + /** + * Code to close the device + * @return flag: true is closed sucessfully, false if error + */ + public boolean close() { + // Expected to be over-ridden by extending class + return true; + } + + /* + * Audio Playback Methods + */ + /** + * Set Type of Audio Playback physical transducer(s) sound is output to. + * Valid types are HEADPHONE, MONO_SPEAKER, STEREO_SPEAKERS + * @param type of audio output device + */ + public void setAudioPlaybackType(int type) { + audioPlaybackType = type; + } + + /** + * Get Type of Audio Playback Output Device + * returns audio playback type to which sound is currently output + */ + public int getAudioPlaybackType() { + return audioPlaybackType; + } + + /** + * Set Distance from the Center Ear to a Speaker + * @param distance from the center ear and to the speaker + */ + public void setCenterEarToSpeaker(float distance) { + distanceToSpeaker = distance; + } + + /** + * Get Distance from Ear to Speaker + * returns value set as distance from listener's ear to speaker + */ + public float getCenterEarToSpeaker() { + return distanceToSpeaker; + } + + /** + * Set Angle Offset To Speaker + * @param angle in radian between head coordinate Z axis and vector to speaker */ + public void setAngleOffsetToSpeaker(float angle) { + angleOffsetToSpeaker = angle; + } + + /** + * Get Angle Offset To Speaker + * returns value set as angle between vector to speaker and Z head axis + */ + public float getAngleOffsetToSpeaker() { + return angleOffsetToSpeaker; + } + + /** + * Query total number of channels available for sound rendering + * for this audio device. + * returns number of maximum sound channels you can run with this + * library/device-driver. + */ + public int getTotalChannels() { + // this method should be overridden by a device specific implementation + return (totalChannels); + } + + /** + * Query number of channels currently available for use by the + * returns number of sound channels currently available (number + * not being used by active sounds. + */ + public int getChannelsAvailable() { + return (channelsAvailable); + } + + /** + * Query number of channels that would be used to render a particular + * sound node. + * @param sound refenence to sound node that query to be performed on + * returns number of sound channels used by a specific Sound node + * @deprecated This method is now part of the Sound class + */ + public int getChannelsUsedForSound(Sound sound) { + if (sound != null) + return sound.getNumberOfChannelsUsed(); + else + return -1; + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java new file mode 100644 index 0000000..2e495d5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3D.java @@ -0,0 +1,497 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.ArrayList; + + +/** + * The AudioEngine3D Class defines an audio output device that generates + * sound 'image' from high-level sound parameters passed to it during + * scene graph. + * + *

+ * The methods in this class are meant to be optionally overridden by an + * extended class. This extended class would provice device specific code. + * + *

+ * Error checking on all parameters passed to these methods is already + * explicitly being done by the Java 3D core code that calls these methods. + * + *

+ * NOTE: AudioEngine developers should not subclass this class directly. + * Subclass AudioEngine3DL2 instead. + */ + +public class AudioEngine3D extends AudioEngine implements AudioDevice3D +{ + /* + * Identifiers of sample associated with sound source + * This array grows as the AudioDevice3D implementation requires it larger. + */ + protected ArrayList samples = new ArrayList(64); + + /** + * Current View sound is being rendered + */ + protected View currentView = (View)null; + + /* + * current Aural attribute Parameters + */ + protected AuralParameters attribs = new AuralParameters(); + + /** + * Construct a new AudioEngine with the specified PhysicalEnvironment. + * @param physicalEnvironment the physical environment object where we + * want access to this device. + */ + public AudioEngine3D(PhysicalEnvironment physicalEnvironment ) { + super(physicalEnvironment); + } + + /* + * + * Methods that affect AudioEngine3D fields that are NOT associated + * with a specific sound sample + * + */ + + /** + * Save a reference to the current View object. + * @param reference to current view object + */ + public void setView(View reference) { + currentView = reference; + return; + } + /** + * Get reference to the current View object. + * @return reference to current view object + */ + public View getView() { + return (currentView); + } + + /* + * + * Methods explicitly affect sound rendering and that require + * audio device specific methods that override this class. + * + */ + + /** + * Prepare Sound in device. + * Makes sound assessible to device - in this case attempts to load sound + * Stores sound type and data. + * @param soundType denotes type of sound: Background, Point or Cone + * @param soundData descrition of sound source data + * @return index into sample vector of Sample object for sound + */ + public int prepareSound(int soundType, MediaContainer soundData) { + // This method must be overridden by device specific implementation + return Sample.NULL_SAMPLE; + } + + /** + * Clear Sound. + * Removes/clears associated sound data with this sound source node + * @param index device specific reference number to device driver sample + */ + public void clearSound(int index) { + // This method must be overridden by device specific implementation + return; + } + + /** + * Set the transform for local to virtual world coordinate space + * @param index device specific reference number to device driver sample + * @param trans is a reference to virtual world composite transform + */ + public void setVworldXfrm(int index, Transform3D trans) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.vworldXfrm.set(trans); + return; + } + /** + * Start sample playing on audio device + * @param index device specific reference number to device driver sample + * @return status: < 0 denotes an error + */ + public int startSample(int index) { + // This method must be overridden by device specific implementation + return -1; // error if not overridden + } + + /** + * Stop sample playing on audio device + * @param index device specific reference number to device driver sample + * @return status: < 0 denotes an error + */ + public int stopSample(int index) { + // This method must be overridden by device specific implementation + return -1; // error if not overridden + } + + /** + * Update sample. + * Implies that some parameters affecting rendering have been modified. + * @param index device specific reference number to device driver sample + */ + // TODO: The update method exists on a TEMPORARY basis. + public void updateSample(int index) { + // This method must be overridden by device specific implementation + return; + } + + /** + * Mute sample. + * @param index device specific reference number to device driver sample + */ + public void muteSample(int index) { + // This method must be overridden by device specific implementation + return; + } + + /** + * Unmute sample. + * @param index device specific reference number to device driver sample + */ + public void unmuteSample(int index) { + // This method must be overridden by device specific implementation + return; + } + + /** + * Pause sample. + * @param index device specific reference number to device driver sample + */ + public void pauseSample(int index) { + // This method must be overridden by device specific implementation + return; + } + + /** + * Unpause sample. + * @param index device specific reference number to device driver sample + */ + public void unpauseSample(int index) { + // This method must be overridden by device specific implementation + return; + } + + /* + * + * Methods that affect fields associated with the sound sample + * and that may cause implicit rendering. + * + */ + /** + * Set gain scale factor applied to sample. + * @param index device specific reference number to device driver sample + * @param scaleFactor floating point multiplier applied to sample amplitude + */ + public void setSampleGain(int index, float scaleFactor) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setGain(scaleFactor); + return; + } + + /** + * Set number of times sample is looped. + * @param index device specific reference number to device driver sample + * @param count number of times sample is repeated + */ + public void setLoop(int index, int count) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setLoopCount(count); + return; + } + + /** + * Set location of sample. + * @param index device specific reference number to device driver sample + * @param position point location in virtual world coordinate of sample + */ + public void setPosition(int index, Point3d position) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setPosition(position); + return; + } + + /* Set elliptical distance attenuation arrays applied to sample amplitude. + * @param index device specific reference number to device driver sample + * @param frontDistance defines an array of distance along the position axis + * thru which ellipses pass + * @param frontAttenuationScaleFactor gain scale factors + * @param backDistance defines an array of distance along the negative axis + * thru which ellipses pass + * @param backAttenuationScaleFactor gain scale factors + */ + public void setDistanceGain(int index, + double[] frontDistance, float[] frontAttenuationScaleFactor, + double[] backDistance, float[] backAttenuationScaleFactor) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setDistanceGain(frontDistance, frontAttenuationScaleFactor, + backDistance, backAttenuationScaleFactor); + return; + } + + /** + * Set direction vector of sample. + * @param index device specific reference number to device driver sample + * @param direction vector in virtual world coordinate. + */ + public void setDirection(int index, Vector3d direction) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setDirection(direction); + return; + } + + /** + * Set angular attenuation arrays affecting angular amplitude attenuation + * and angular distance filtering. + * @param index device specific reference number to device driver sample + * @param filterType denotes type of filtering (on no filtering) applied + * to sample. + * @param angle array containing angular distances from sound axis + * @param attenuationScaleFactor array containing gain scale factor + * @param filterCutoff array containing filter cutoff frequencies. + * The filter values for each tuples can be set to Sound.NO_FILTER. + */ + public void setAngularAttenuation(int index, int filterType, + double[] angle, float[] attenuationScaleFactor, float[] filterCutoff) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setAngularAttenuation(filterType, angle, + attenuationScaleFactor, filterCutoff); + return; + } + + /** + * Set rolloff value for current aural attribute applied to all samples. + * @param rolloff scale factor applied to standard speed of sound. + */ + public void setRolloff(float rolloff) { + attribs.rolloff = rolloff; + return; + } + + /** + * Set reverberation surface reflection coefficient value for current aural + * attribute applied to all samples. + * @param coefficient applied to amplitude of reverbation added at each + * iteration of reverb processing. + */ + public void setReflectionCoefficient(float coefficient) { + attribs.reflectionCoefficient = coefficient; + return; + } + + /** + * Set reverberation delay time for current aural attribute applied to + * all samples. + * @param reverbDelay amount of time in millisecond between each + * iteration of reverb processing. + */ + public void setReverbDelay(float reverbDelay) { + attribs.reverbDelay = reverbDelay; + return; + } + + /** + * Set reverberation order for current aural attribute applied to all + * samples. + * @param reverbOrder number of times reverb process loop is iterated. + */ + public void setReverbOrder(int reverbOrder) { + attribs.reverbOrder = reverbOrder; + return; + } + + /** + * Set distance filter for current aural attribute applied to all samples. + * @param filterType denotes type of filtering (on no filtering) applied + * to all sample based on distance between listener and sound. + * @param dist is an attenuation array of distance and low-pass filter values. + */ + public void setDistanceFilter(int filterType, + double[] dist, float[] filterCutoff) { + attribs.setDistanceFilter(filterType, dist, filterCutoff); + return; + } + + /** + * Set frequency scale factor for current aural attribute applied to all + * samples. + * @param scaleFactor frequency scale factor applied to samples normal + * playback rate. + */ + public void setFrequencyScaleFactor(float scaleFactor) { + attribs.frequencyScaleFactor = scaleFactor; + return; + } + /** + * Set velocity scale factor for current aural attribute applied to all + * samples when Doppler is calculated. + * @param scaleFactor scale factor applied to postional samples' + * listener-to-soundSource velocity. + * playback rate. + */ + public void setVelocityScaleFactor(float scaleFactor) { + attribs.velocityScaleFactor = scaleFactor; + return; + } + + /** + * Get number of channels used by a particular sample on the audio device. + * @param index device specific reference number to device driver sample + * @return number of channels currently being used by this sample. + */ + public int getNumberOfChannelsUsed(int index) { + // This method must be overridden by device specific implementation + Sample sample = (Sample)getSample(index); + if (sample != null) + return (sample.getNumberOfChannelsUsed()); + else + return 0; + } + + /** + * Get number of channels that would be used by a particular sample on + * the audio device given the mute flag passed in as a parameter. + * @param index device specific reference number to device driver sample + * @param muteFlag denotes the mute state to assume while executing this + * query. This mute value does not have to match the current mute state + * of the sample. + * @return number of channels that would be used by this sample if it + * were playing. + */ + public int getNumberOfChannelsUsed(int index, boolean muteFlag) { + // This method must be overridden by device specific implementation + Sample sample = (Sample)getSample(index); + if (sample != null) + return (sample.getNumberOfChannelsUsed()); + else + return 0; + } + + /** + * Get length of time a sample would play if allowed to play to completion. + * @param index device specific reference number to device driver sample + * @return length of sample in milliseconds + */ + public long getSampleDuration(int index) { + Sample sample = (Sample)getSample(index); + if (sample != null) + return (sample.getDuration()); + else + return 0L; + } + + /** + * Get time this sample begun playing on the audio device. + * @param index device specific reference number to device driver sample + * @return system clock time sample started + */ + public long getStartTime(int index) { + Sample sample = (Sample)getSample(index); + if (sample != null) + return (sample.getStartTime()); + else + return 0L; + } + + /** + * Get reference to the array list of samples + * @return reference to samples list + * @deprecated unsafe to get reference to samples list with this method. + * It's better to directly reference samples list within a synchronized + * block which also contains calls to .getSample(index). + */ + protected ArrayList getSampleList() { + return (samples); + } + + public int getSampleListSize() { + return (samples.size()); + } + + /** + * Get specific sample from indexed sample list + * Checks for valid index before attempting to get sample from list. + * @param index device specific reference number to device driver sample + * @return reference to sample; returns null if index out of range. + * + * @since Java 3D 1.2.1 + */ + public Sample getSample(int index) { + synchronized(samples) { + if ((index >= 0) && (index < samples.size())) { + Sample sample = (Sample)samples.get(index); + return (sample); + } + else + return null; + } + } + + /* + * Get reference to current aural attribute parameters associated with + * this audio device. + * @return reference to current aural attribute parameters + */ + public AuralParameters getAuralParameters() { + return (attribs); + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java new file mode 100644 index 0000000..c8865c2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngine3DL2.java @@ -0,0 +1,308 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.ArrayList; + + +/** + * The AudioEngine3DL2 Class defines an audio output device that generates + * sound 'image' from high-level sound parameters passed to it during + * scene graph. + * + *

+ * The methods in this class are meant to be optionally overridden by an + * extended class. This extended class would provice device specific code. + * + *

+ * Error checking on all parameters passed to these methods is already + * explicitly being done by the Java 3D core code that calls these methods. + * + *

+ * These methods should NOT be called by any application if the audio engine + * is associated with a Physical Environment used by Java3D Core. + * + * @since Java 3D 1.3 + */ +public class AudioEngine3DL2 extends AudioEngine3D implements AudioDevice3DL2 { + /** + * Construct a new AudioEngine3DL2 with the specified PhysicalEnvironment. + * @param physicalEnvironment the physical environment object where we + * want access to this device. + */ + public AudioEngine3DL2(PhysicalEnvironment physicalEnvironment ) { + super(physicalEnvironment); + } + + /* + * + * Methods that affect AudioEngine3DLD fields that are NOT associated + * with a specific sound sample + * + */ + + /** + * Pauses audio device engine without closing the device and associated + * threads. + * Causes all cached sounds to be paused and all streaming sounds to be + * stopped. + */ + public void pause() { + // This method must be overridden by device specific implementation + return; + } + /** + * Resumes audio device engine (if previously paused) without + * reinitializing the device. + * Causes all paused cached sounds to be resumed and all streaming + * sounds restarted. + */ + public void resume() { + // This method must be overridden by device specific implementation + return; + } + + /** + * Set overall gain control of all sounds playing on the audio device. + * @param scaleFactor scale factor applied to calculated amplitudes for + * all sounds playing on this device + */ + public void setGain(float scaleFactor) { + // This method must be overridden by device specific implementation + return; + } + + + /* + * + * Methods explicitly affect a particular sound rendering and that + * require audio device specific methods that override this class. + * + */ + + /** + * Set scale factor applied to sample playback rate for a particular sound + * associated with the audio device. + * Changing the device sample rate affects both the pitch and speed. + * This scale factor is applied to ALL sound types. + * Changes (scales) the playback rate of a sound independent of + * Doppler rate changes. + * @param index device specific reference to device driver sample + * @param scaleFactor non-negative factor applied to calculated + * amplitudes for all sounds playing on this device + * @see Sound#setRateScaleFactor + */ + public void setRateScaleFactor(int index, float scaleFactor) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setRateScaleFactor(scaleFactor); + return; + } + + + /* + * + * Methods explicitly affect aural attributes of the listening space + * used to calculated reverberation during sound rendering. + * These require audio device specific methods that override this class. + * + */ + + /** + * Set late reflection (referred to as 'reverb') attenuation. + * This scale factor is applied to iterative, indistinguishable + * late reflections that constitute the tail of reverberated sound in + * the aural environment. + * This parameter, along with the early reflection coefficient, defines + * the reflective/absorptive characteristic of the surfaces in the + * current listening region. + * @param coefficient late reflection attenuation factor + * @see AuralAttributes#setReverbCoefficient + */ + public void setReverbCoefficient(float coefficient) { + attribs.reverbCoefficient = coefficient; + return; + } + + + /** + * Sets the early reflection delay time. + * In this form, the parameter specifies the delay time between each order + * of reflection (while reverberation is being rendered) explicitly given + * in milliseconds. + * @param reflectionDelay time between each order of early reflection + * @see AuralAttributes#setReflectionDelay + */ + public void setReflectionDelay(float reflectionDelay) { + attribs.reflectionDelay = reflectionDelay; + return; + } + + /** + * Set reverb decay time. + * Defines the reverberation decay curve. + * @param time decay time in milliseconds + * @see AuralAttributes#setDecayTime + */ + public void setDecayTime(float time) { + attribs.decayTime = time; + return; + } + + /** + * Set reverb decay filter. + * This provides for frequencies above the given cutoff frequency to be + * attenuated during reverb decay at a different rate than frequencies + * below this value. Thus, defining a different reverb decay curve for + * frequencies above the cutoff value. + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + * @see AuralAttributes#setDecayFilter + */ + public void setDecayFilter(float frequencyCutoff) { + attribs.decayFrequencyCutoff = frequencyCutoff; + return; + } + + /** + * Set reverb diffusion. + * This defines the echo dispersement (also referred to as 'echo density'). + * The value of this reverb parameter is expressed as a percent of the + * audio device's minimum-to-maximum values. + * @param diffusion percentage expressed within the range of 0.0 and 1.0 + * @see AuralAttributes#setDiffusion + */ + public void setDiffusion(float diffusion) { + attribs.diffusion = diffusion; + return; + } + + /** + * Set reverb density. + * This defines the modal density (also referred to as 'spectral + * coloration'). + * The value of this parameter is expressed as a percent of the audio + * device's minimum-to-maximum values for this reverb parameter. + * @param density reverb density expressed as a percentage, + * within the range of 0.0 and 1.0 + * @see AuralAttributes#setDensity + */ + public void setDensity(float density) { + attribs.density = density; + return; + } + + + /** + * Set the obstruction gain control. This method allows for attenuating + * sound waves traveling between the sound source and the listener + * obstructed by objects. Direct sound signals/waves for obstructed sound + * source are attenuated but not indirect (reflected) waves. + * There is no corresponding Core AuralAttributes method at this time. + * @param index device specific reference to device driver sample + * @param scaleFactor non-negative factor applied to direct sound gain + */ + public void setObstructionGain(int index, float scaleFactor) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setObstructionGain(scaleFactor); + return; + } + + /** + * Set the obstruction filter control. + * This provides for frequencies above the given cutoff frequency + * to be attenuated, during while the gain of an obstruction signal + * is being calculated, at a different rate than frequencies + * below this value. + * There is no corresponding Core AuralAttributes method at this time. + * @param index device specific reference to device driver sample + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + */ + + public void setObstructionFilter(int index, float frequencyCutoff) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setObstructionFilter(frequencyCutoff); + return; + } + + /** + * Set the occlusion gain control. This method allows for attenuating + * sound waves traveling between the sound source and the listener + * occluded by objects. Both direct and indirect sound signals/waves + * for occluded sound sources are attenuated. + * There is no corresponding Core AuralAttributes method at this time. + * @param index device specific reference to device driver sample + * @param scaleFactor non-negative factor applied to direct sound gain + */ + public void setOcclusionGain(int index, float scaleFactor) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setObstructionGain(scaleFactor); + return; + } + + /** + * Set the occlusion filter control. + * This provides for frequencies above the given cutoff frequency + * to be attenuated, during while the gain of an occluded signal + * is being calculated, at a different rate than frequencies below + * this value. + * There is no corresponding Core AuralAttributes method at this time. + * @param index device specific reference to device driver sample + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + */ + public void setOcclusionFilter(int index, float frequencyCutoff) { + Sample sample = (Sample)getSample(index); + if (sample != null) + sample.setObstructionFilter(frequencyCutoff); + return; + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java b/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java new file mode 100644 index 0000000..bf67e0a --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/AudioEngineThread.java @@ -0,0 +1,275 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +/* + * Audio Engine Thread + */ + +import javax.media.j3d.*; + +/** + * The Thread Class extended for Audio Device engines that must process + * calls dynamically, in 'real-time" to asynchronously change engine + * parameters. + * + *

+ * NOTE: this class is probably not needed for those Audio Device implementations + * that handle all dynamic parameters in the low-level audio library. + */ +public class AudioEngineThread extends Thread { + + // Debug print flag + static final protected boolean debugFlag = false; + + + protected void debugPrint(String message) { + if (debugFlag) + System.out.println(message); + } + + /** + * The classification types. + */ + protected static final int WORK_THREAD = 0x01; + protected static final int UPDATE_THREAD = 0x02; + + /** + * This runMonitor action puts the thread into an initial wait state + */ + protected static final int WAIT = 0; + + /** + * This runMonitor action notifies MasterControl that this thread + * has completed and wait. + */ + protected static final int NOTIFY_AND_WAIT = 1; + + /** + * This runMonitor action tells the thread to run N number of + * iterations. + */ + protected static final int RUN = 2; + + /** + * This runMonitor action tells the thread to stop running + */ + protected static final int STOP = 3; + + /** + * This indicates that this thread has been activated by MC + */ + protected boolean active = false; + + /** + * This indicates that this thread is alive and running + */ + protected boolean running = true; + + + /** + * This indicates that this thread is ready + */ + protected boolean started = false; + + /** + * The time values passed into this thread + */ + protected long referenceTime; + + /** + * Use to assign threadOpts WAIT_ALL_THREADS + */ + protected long lastWaitTimestamp = 0; + + /** + * The type of this thread. It is one of the above constants. + */ + protected int type; + + /** + * The classification of this thread. It is one of the above constants. + */ + protected int classification = WORK_THREAD; + + /** + * The arguments passed in for this thread + */ + protected Object[] args = null; + + /** + * Flag to indicate that user initiate a thread stop + */ + protected boolean userStop = false; + + /** + * Flag to indicate that this thread is waiting to be notify + */ + protected boolean waiting = false; + + /** + * Some variables used to name threads correctly + */ + protected static int numInstances = 0; + protected int instanceNum = -1; + + /** + * This constructor simply assigns the given id. + */ + public AudioEngineThread(ThreadGroup t, String threadName) { + super(t, threadName); + if (debugFlag) + debugPrint("AudioEngineThread.constructor("+threadName +")"); + } + + synchronized int newInstanceNum() { + return (++numInstances); + } + + int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + /** + * This method is defined by all slave threads to implement + * one iteration of work. + */ + synchronized public void doWork() { + if (debugFlag) + debugPrint("AudioEngineThread.doWork()"); + } + + /** + * This initializes this thread. Once this method returns, the thread is + * ready to do work. + */ + public void initialize() { + if (debugFlag) + debugPrint("AudioEngineThread.initialize()"); + this.start(); + while (!started) { + try { + Thread.currentThread().sleep(1, 0); + } catch (InterruptedException e) { + } + } + } + + /** + * This causes the threads run method to exit. + */ + public void finish() { + while (!waiting) { + try { + Thread.sleep(10); + } catch (InterruptedException e) {} + } + runMonitor(STOP, 0,null); + } + + /* + * This thread controls the syncing of all the canvases attached to + * this view. + */ + public void run() { + if (debugFlag) + debugPrint("AudioEngineThread.run"); + runMonitor(WAIT, 0, null); + while (running) { + doWork(); + runMonitor(WAIT, 0, null); + } + // resource clean up + shutdown(); + } + + synchronized public void runMonitor(int action, long referenceTime, Object[] args){ + switch (action) { + case WAIT: + if (debugFlag) + debugPrint("AudioEngineThread.runMonitor(WAIT)"); + try { + started = true; + waiting = true; + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + waiting = false; + break; + case RUN: + if (debugFlag) + debugPrint("AudioEngineThread.runMonitor(RUN)"); + this.referenceTime = referenceTime; + this.args = args; + notify(); + break; + case STOP: + if (debugFlag) + debugPrint("AudioEngineThread.runMonitor(STOP)"); + running = false; + notify(); + break; + } + } + + public void shutdown() { + } + + // default resource clean up method + public void cleanup() { + active = false; + running = true; + started = true; + lastWaitTimestamp = 0; + classification = WORK_THREAD; + args = null; + userStop = false; + referenceTime = 0; + + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java b/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java new file mode 100644 index 0000000..6c29164 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/AuralParameters.java @@ -0,0 +1,197 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * The AuralParameters Class defines a set of fields that define the + * Aural listening environment. Many of the parameters correspond to + * AuralAttribute fields. + * + *

+ * Error checking on all parameters passed to these methods is already + * explicitly being done by the Java 3D core code that calls these methods. + */ + +public class AuralParameters +{ + // Speed of Sound in meters/milliseconds + public static final float SPEED_OF_SOUND = 0.344f; + public static final int NO_FILTERING = -1; + + public float rolloff = 1.0f; + public float reflectionCoefficient = 0.0f; + public float reverbDelay = 40.0f; + public int reverbOrder = 0; + public float frequencyScaleFactor = 1.0f; + public float velocityScaleFactor = 0.0f; + int filterType = NO_FILTERING; + double[] filterDistance = null; + float[] filterCutoff = null; + + /* + * @since Java 3D 1.3 + */ + public float reverbCoefficient = 1.0f; + public float reflectionDelay = 20.0f; + public float decayTime = 1000.0f; + public float decayFrequencyCutoff = 5000.0f; + public float diffusion = 1.0f; // 100% + public float density = 1.0f; // 100% + + /** + * Construct a new AuralParameters object + */ + public AuralParameters() { + frequencyScaleFactor = 1.0f; + velocityScaleFactor = 0.0f; + rolloff = 1.0f; + reflectionCoefficient = 0.0f; + reflectionDelay = 20.0f; + reverbCoefficient = 1.0f; + reverbDelay = 40.0f; + reverbOrder = 0; + filterType = NO_FILTERING; + filterDistance = new double[2]; // start out with array of two + filterCutoff = new float[2]; // start out with array of two + decayTime = 1000.0f; + decayFrequencyCutoff = 5000.0f; + diffusion = 1.0f; // 100% + density = 1.0f; // 100% + } + + public void setDistanceFilter(int filterType, double[] distance, + float[] filterCutoff) { + boolean error = false; + boolean allocate = false; + int attenuationLength = 0; + if (distance == null || filterCutoff == null) { + error = true; + } + else { + attenuationLength = distance.length; + if (attenuationLength == 0 || filterType == NO_FILTERING) { + error = true; + } + } + if (error) { + this.filterType = NO_FILTERING; + this.filterDistance = null; + this.filterCutoff = null; + if (debugFlag) + debugPrint("setDistanceFilter NO_FILTERING"); + return; + } + this.filterType = filterType; + if (debugFlag) + debugPrint("setDistanceFilter type = " + filterType); + if ((filterDistance == null) || (filterCutoff == null)) { + allocate = true; + } + else if (attenuationLength > filterDistance.length) { + allocate = true; + } + if (allocate) { + if (debugFlag) + debugPrint("setDistanceFilter length = " + attenuationLength); + this.filterDistance = new double[attenuationLength]; + this.filterCutoff = new float[attenuationLength]; + } + System.arraycopy(distance, 0, this.filterDistance, 0, + attenuationLength); + System.arraycopy(filterCutoff, 0, this.filterCutoff, 0, + attenuationLength); + + if (debugFlag) { + debugPrint("setDistanceFilter arrays = "); + for (int i=0; i filterDistance.length) + attenuationLength = filterDistance.length; + System.arraycopy(this.filterDistance, 0, distance, 0, + attenuationLength); + System.arraycopy(this.filterCutoff, 0, filterCutoff, 0, + attenuationLength); + return; + } + + // Debug print flags + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + /** + * Debug print method for Sound nodes + */ + protected void debugPrint(String message) { + if (debugFlag) + System.out.println(message); + } + +} diff --git a/src/classes/share/com/sun/j3d/audioengines/Sample.java b/src/classes/share/com/sun/j3d/audioengines/Sample.java new file mode 100644 index 0000000..620c28e --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/Sample.java @@ -0,0 +1,479 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * The Sample class defines the data and methods associated with a sound + * sample played through the AudioDevice. + * This contains all the data fields for non-spatialized and spatialized + * (positional and directional) sound samples. + */ +public class Sample { + + // Debug print flags and methods + static final protected boolean debugFlag = false; + static final protected boolean internalErrors = false; + + protected void debugPrint(String message) { + if (debugFlag) + System.out.println(message); + } + + protected void debugPrintln(String message) { + if (debugFlag) + System.out.println(message); + } + + /** + * Null Sound identifier denotes sound is not created or initialized + */ + public static final int NULL_SAMPLE = -1; + + /** + * sound data associated with sound source + */ + protected MediaContainer soundData = null; + + /** + * sound data associated with sound source + */ + protected int soundType = -1; + + /** + * Overall Scale Factor applied to sound gain. + */ + protected float gain = 1.0f; // Valid values are >= 0.0. + + /** + * Overall Scale Factor applied to sound. + * @since Java 3D 1.3 + */ + protected float rateScaleFactor = 1.0f; // Valid values are >= 0.0. + + /** + * Number of times sound is looped/repeated during play + */ + protected int loopCount = 0; // Range from 0 to POSITIVE_INFINITY(-1) + + + /* + * Duration of sample + * This should match the Sound node constant of same name + */ + public static final int DURATION_UNKNOWN = -1; + protected long duration = DURATION_UNKNOWN; + + protected int numberOfChannels = 0; + protected boolean mute = false; // denotes if sample is muted + // (playing with zero gain) + + /* + * + * Fields associated with positional sound samples + * + */ + /* + * Local to Vworld transform + */ + protected Transform3D vworldXfrm = new Transform3D(); + protected boolean vwXfrmFlag = false; + + /* + * Origin of Sound source in Listener's space. + */ + protected Point3f position = new Point3f(0.0f, 0.0f, 0.0f); + + /* + * Pairs of distances and gain scale factors that define piecewise linear + * gain attenuation between each pair. + */ + protected double[] attenuationDistance = null; + protected float[] attenuationGain = null;; + + /** + * dirty flags denoting what has changed since last rendering + */ + protected int dirtyFlags = 0xFFFF; + + /* + * + * Direction sample fields + * + */ + /** + * The Cone Sound's direction vector. This is the cone axis. + */ + protected Vector3f direction = new Vector3f(0.0f, 0.0f, 1.0f); + + /** + * Pairs of distances and gain scale factors that define piecewise linear + * gain BACK attenuation between each pair. + * These are used for defining elliptical attenuation regions. + */ + protected double[] backAttenuationDistance = null; + protected float[] backAttenuationGain = null; + + /** + * Directional Sound's gain can be attenuated based on the listener's + * location off-angle from the source source direction. + * This can be set by three parameters: + * angular distance in radians + * gain scale factor + * filtering (currently the only filtering supported is lowpass) + */ + protected double[] angularDistance = {0.0, (Math.PI * 0.5)}; + protected float[] angularGain = {1.0f, 0.0f}; + + /** + * Distance Filter + * Each sound source is attenuated by a filter based on it's distance + * from the listener. + * For now the only supported filterType will be LOW_PASS frequency + * cutoff. + * At some time full FIR filtering will be supported. + */ + public static final int NO_FILTERING = -1; + public static final int LOW_PASS = 1; + + protected int angularFilterType = NO_FILTERING; + protected float[] angularFilterCutoff = {Sound.NO_FILTER, Sound.NO_FILTER}; + + /* + * Obstruction and Occlusion parameters + * For now the only type of filtering supported is a low-pass filter + * defined by a frequency cutoff value. + * @since Java 3D 1.3 + */ + protected float obstructionGain = 1.0f; // scale factor + protected int obstructionFilterType = NO_FILTERING; + protected float obstructionFilterCutoff = Sound.NO_FILTER; + protected float occlusionGain = 1.0f; // scale factor + protected int occlusionFilterType = NO_FILTERING; + protected float occlusionFilterCutoff = Sound.NO_FILTER; + + /* + * Construct a new audio device Sample object + */ + public Sample() { + if (debugFlag) + debugPrintln("Sample constructor"); + } + + public long getDuration() { + return 0; + } + + public long getStartTime() { + return 0; + } + + public int getNumberOfChannelsUsed() { + return 0; + } + + public void setDirtyFlags(int flags) { + dirtyFlags = flags; + } + + public int getDirtyFlags() { + return dirtyFlags; + } + + public void setSoundType(int type) { + soundType = type; + } + + public int getSoundType() { + return soundType; + } + + public void setSoundData(MediaContainer ref) { + soundData = ref; + } + + public MediaContainer getSoundData() { + return soundData; + } + + public void setMuteFlag(boolean flag) { + mute = flag; + } + + public boolean getMuteFlag() { + return mute; + } + + public void setVWrldXfrmFlag(boolean flag) { + // this flag is ONLY true if the VirtualWorld Transform is ever set + vwXfrmFlag = flag; + } + + public boolean getVWrldXfrmFlag() { + return vwXfrmFlag; + } + + public void setGain(float scaleFactor) { + gain = scaleFactor; + } + + public float getGain() { + return gain; + } + + public void setLoopCount(int count) { + loopCount = count; + } + + public int getLoopCount() { + return loopCount; + } + + + public void setPosition(Point3d position) { + this.position.set(position); + return; + } + + // TODO: no get method for Position + + + public void setDistanceGain( + double[] frontDistance, float[] frontAttenuationScaleFactor, + double[] backDistance, float[] backAttenuationScaleFactor) { + if (frontDistance != null) { + int size = frontDistance.length; + attenuationDistance = new double[size]; + attenuationGain = new float[size]; + for (int i=0; i=0 if everythings OK +**************/ + return null; // TODO: implement this + + } // reinitAudioInputStream + + + DataLine initDataLine(AudioInputStream ais) { + if (debugFlag) { + debugPrintln("JSChannel: initDataLine(" + ais + ")"); + debugPrintln(" must be overridden"); + } + return null; + } + + long getDuration() { + // TODO: how should this really be done?? + if (debugFlag) + debugPrintln("JSChannel:getDuration"); + + if (ais == null || audioFormat == null ) { + if (debugFlag) + debugPrintln("JSChannel: Internal Error getDuration"); + return (long)Sample.DURATION_UNKNOWN; + } + // Otherwise we'll assume that we can calculate this duration + + // get "duration" of audio stream (wave file) + // TODO: For True STREAMing audio the size is unknown... + long numFrames = ais.getFrameLength(); + if (debugFlag) + debugPrintln(" frame length = " + numFrames); + if (numFrames <= 0) + return (long)Sample.DURATION_UNKNOWN; + + float rateInFrames = audioFormat.getFrameRate(); + rateInHz = audioFormat.getSampleRate(); + if (debugFlag) + debugPrintln(" rate in Frames = " + rateInFrames); + if (numFrames <= 0) + return (long)Sample.DURATION_UNKNOWN; + long duration = (long)((float)numFrames/rateInFrames); + if (debugFlag) + debugPrintln(" duration(based on ais) = " + duration); + return duration; + } + + /** + * Start TWO Samples + */ + boolean startSamples(int loopCount, float leftGain, float rightGain, + int leftDelay, int rightDelay) { + if (debugFlag) + debugPrint("JSChannel: startSamples must be overridden"); + return false; + } // end of start Samples + + /* + * Starts a Sample + */ + boolean startSample(int loopCount, float gain, int delay) { + if (debugFlag) + debugPrint("JSChannel: startSample must be overridden"); + return false; + } // end of start (single) Sample + + int stopSample() { +// This will tell thread to stop reading and writing + // reload with old URL + // reloadSample + if (debugFlag) + debugPrint("JSChannel: stopSample must be overridden"); + startTime = 0; + return 0; + } + + int stopSamples() { +// This will tell thread to stop reading and writing + // TODO: For muting, stop sound but don't clear startTime... + // QUESTION: what does it mean for replaying that .stop "frees memory" + if (debugFlag) + debugPrint("JSChannel: stopSample must be overridden"); +// reloadSample + + startTime = 0; + return 0; + } + + void setSampleGain(float gain) { +// TODO: Must be done in thread + if (debugFlag) + debugPrint("JSChannel: setSampleGain must be overridden"); + } + + void setSampleDelay(int delay) { + if (debugFlag) + debugPrint("JSChannel: setSampleDelay must be overridden"); + /* + * null method + */ + // dynamic changes to sample delay while playing is not implemented + } + + void setSampleReverb(int type, boolean on) { + if (debugFlag) + debugPrint("JSChannel: setSampleReverb must be overridden"); + } + + void setSampleRate() { + if (debugFlag) + debugPrint("JSChannel: setSampleRate must be overridden"); + } + void scaleSampleRate(float scaleFactor) { + /** + * Change rate for Doppler affect or pitch shifting. + * Engine maximum sample rate is 48kHz so clamp to that + * max value. + */ + if (debugFlag) + debugPrintln("JSChannel: scaleSampleRate"); + if (ais == null) { + if (debugFlag) { + debugPrint("JSChannel: Internal Error scaleSampleRate: "); + debugPrintln("ais is null"); + } + return; + } + + AudioFormat audioFormat = ais.getFormat(); + float rate = audioFormat.getSampleRate(); + + double newRate = rate * scaleFactor; + if (newRate > 48000.0) // clamp to 48K max + newRate = 48000.0; +/**** +// NOTE: This doesn't work... +/// audioStream.setSampleRate(newRate); + +// need to set FloatControl.Type(SAMPLE_RATE) to new value somehow... + + if (debugFlag) { + debugPrintln("JSChannel: scaleSampleRate: new rate = " + + rate * scaleFactor); + debugPrintln(" >>>>>>>>>>>>>>> using scaleFactor = " + + scaleFactor); + } +****/ + } + + int pauseSamples() { + /** + * Pause playing samples + */ +// TODO: Notify thread + return 0; + } + + int pauseSample() { + /** + * Pause playing a sample + */ +// TODO: Notify thread + return 0; + } + + int unpauseSamples() { + /** + * Resume playing samples + */ +// TODO: Notify thread + return 0; + } + + int unpauseSample() { + /** + * Resume playing a sample + */ +// TODO: Notify thread + return 0; + } + + void setSampleFiltering(boolean filterFlag, float cutoffFreq) { + /** + * Set or clear low-pass filtering + */ +/**** +// QUESTION: how will this be done if data is written out one channel/sample at + a time?? +****/ + // QUESTION: should filtering of Midi data be performed? +// ais.setFiltering(filterFlag, cutoffFreq); + } + +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java new file mode 100755 index 0000000..a1558fa --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSClip.java @@ -0,0 +1,349 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. + */ + +package com.sun.j3d.audioengines.javasound; + +import java.applet.*; +import java.util.*; +import java.lang.String; +import java.net.*; +import java.io.*; +import java.io.InputStream; +import javax.sound.sampled.*; + +/** + * The JSClip Class defines an audio output methods that call JavaSound + * Hae mixer methods. + */ + +class JSClip extends JSChannel { + + Clip line; + +// TODO: separate left and right channel required until I write into +// stereo buffer! + Clip otherChannel = null; + +// TODO: Reverb channel that is centered and not delayed is maintained separately +// until a way to set stereo reverb send (panned and attenuated to give +// the same affect) is implemented + Clip reverbChannel = null; + + + /** + * Create data line for outputting audio input stream. + * for a stream that is a sourceDataline + * @return true is successful in initiallizing DataLine + */ + DataLine initDataLine(AudioInputStream ais) { + if (debugFlag) + debugPrintln("JSClip: initDataLine(" + ais + ")"); + + try { + if (debugFlag) + debugPrintln("JSClip: loadSample - try getting new line "); + /* + * From the AudioInputStream fetch information about the format + * of the audio data - including sampling frequency, number of + * channels, size of samples,... + */ + audioFormat = ais.getFormat(); + + /* + * we can't yet open the device for ALAW/ULAW playback, + * convert ALAW/ULAW to PCM + */ + if ((audioFormat.getEncoding() == AudioFormat.Encoding.ULAW) || + (audioFormat.getEncoding() == AudioFormat.Encoding.ALAW)) { + + AudioFormat tmp = + new AudioFormat( + AudioFormat.Encoding.PCM_SIGNED, + audioFormat.getSampleRate(), + audioFormat.getSampleSizeInBits() * 2, + audioFormat.getChannels(), + audioFormat.getFrameSize() * 2, + audioFormat.getFrameRate(), + true); + ais = AudioSystem.getAudioInputStream(tmp, ais); + audioFormat = tmp; + } + + /* + * ask JavaSound for outline with a format suitable for our + * AudioInputStream. In order to ask for a line, a Info object + * with the desired properties must be constructed. + * Clip is used for outputing buffered data. + * We have to pass the line the AudioFormat object so it knows + * format will be. + * + * TODO: we could give JavaSound a hint about how big the + * internal buffer for the line should be, rather than use the + * default. + */ + DataLine.Info info = new DataLine.Info(Clip.class, + audioFormat); + line = (Clip)AudioSystem.getLine(info); +/***** +// TODO: JSClip can't be a listener (do we need to do this in the thread?) + if (debugFlag) + debugPrintln("JSClip: addLineListener for clip"); + line.addLineListener(this); +******/ + + if (debugFlag) + debugPrintln("JSClip: open sound Clip"); + + // Make line ready to receive data. + line.open(ais); + + // Line can now receive data but still needs to be + // activated (opened) so it will pass data on to the + // audio device. This is done at "startSample" time. + } + catch (Exception e) { + if (debugFlag) { + debugPrint("JSClip: Internal Error loadSample "); + debugPrintln("get stream failed"); + } + e.printStackTrace(); + // TODO: clean up vector elements that were set up for + // failed sample + return null; + } + return (DataLine)line; + } // initDataLine + + /** + * Start TWO Samples + * + * used when two samples are associated with a single Point or Cone + * sound. This method handles starting both samples, rather than + * forcing the caller to make two calls to startSample, so that the + * actual Java Sound start methods called are as immediate (without + * delay between as possible. + */ + boolean startSamples(int loopCount, float leftGain, float rightGain, + int leftDelay, int rightDelay) { + // loop count is ignored for Stream and MIDI + // TODO: loop count isn't implemented for MIDI yet + + // left and rightDelay parameters are in terms of Samples + if (debugFlag) { + debugPrint("JSClip: startSamples "); + debugPrintln("start stream for Left called with "); + debugPrintln(" gain = " + leftGain + + " delay = " + leftDelay); + debugPrintln("start stream for Right called with "); + debugPrintln(" gain = " + rightGain + + " delay = " + rightDelay); + } + + // This is called assuming that the Stream is allocated for a + // Positional sample, but if it is not then fall back to + // starting the single sample associated with this Stream + if (otherChannel == null || reverbChannel == null) + startSample(loopCount, leftGain, leftDelay); + + /* + * ais for Left and Right streams should be same so just get ais + * left stream + */ + if (ais == null) { + if (debugFlag) { + debugPrint("JSClip: Internal Error startSamples: "); + debugPrintln("either left or right ais is null"); + } + return false; + } + Clip leftLine; + Clip rightLine; + leftLine = line; + rightLine = otherChannel; +// left line only for background sounds... +// TODO: +/*********** +for now just care about the left + if (leftLine == null || rightLine == null) { + if (debugFlag) { + debugPrint("JSClip: startSamples Internal Error: "); + debugPrintln("either left or right line null"); + } + return false; + } +************/ + + // we know that were processing TWO channels + double ZERO_EPS = 0.0039; // approx 1/256 - twice MIDI precision + double leftVolume = (double)leftGain; + double rightVolume = (double)rightGain; + +// TODO: if not reading/writing done for Clips then I can't do +// stereo trick (reading mono file and write to stereo buffer) + // Save time sound started, only in left + startTime = System.currentTimeMillis(); + if (debugFlag) + debugPrintln("*****start Stream with new start time " + + startTime); + try { + // QUESTION: Offset clip is done how??? +/******* +// TODO: +offset delayed sound +set volume +set pan?? +set reverb + boolean reverbLeft = false; // off; reverb has it own channel + boolean reverbRight = reverbLeft; + + if (leftDelay < rightDelay) { +XXXX audioLeftStream.start(leftVolume, panLeft, reverbLeft); +XXXX audioRightStream.start(rightVolume, panRight, reverbRight); + } + else { +XXXX audioRightStream.start(rightVolume, panRight, reverbRight); +XXXX audioLeftStream.start(leftVolume, panLeft, reverbLeft); + } +******/ + line.setLoopPoints(0, -1); // Loop the entire sound sample + line.loop(loopCount); // plays clip loopCount + 1 times + line.start(); // start the sound + } + catch (Exception e) { + if (debugFlag) { + debugPrint("JSClip: startSamples "); + debugPrintln("audioInputStream.read failed"); + } + e.printStackTrace(); + startTime = 0; + return false; + } + + if (debugFlag) + debugPrintln("JSClip: startSamples returns"); + return true; + } // end of startSamples + + + /* + * This method is called specifically for BackgroundSounds. + * There is exactly ONE sample (mono or stereo) associated with + * this type of sound. Consequently, delay is not applicable. + * Since the sound has no auralAttributes applied to it reverb + * is not applied to the sample. + */ + boolean startSample(int loopCount, float gain, int delay) { + /* + if (debugFlag) { + debugPrint("JSClip: startSample "); + debugPrintln("start stream called with "); + debugPrintln(" gain = " + gain + ", delay is zero"); + } + + // Since only one sample is processed in startSample, just call + // this more general method passing duplicate information + // We don't really want to do this in the long term. + return startSamples(loopCount, gain, gain, 0, 0); + */ + + // TODO: The following is temporary until we have fully + // functional startSample and startSamples methods + if (debugFlag) + debugPrintln("JSClip.startSample(): starting sound Clip"); + line.setFramePosition(0); // Start playing from the beginning + line.setLoopPoints(0, -1); // Loop the entire sound sample + line.loop(loopCount); + line.start(); + return true; + } // end of start (single) Sample + + int stopSample() { + // This will tell thread to stop reading and writing + // reload with old URL - reloadSample()??? + + if (debugFlag) + debugPrintln("JSClip.stopSample(): stopping sound Clip"); + line.stop(); + + startTime = 0; + return 0; + } + + int stopSamples() { + // This will tell thread to stop reading and writing + // TODO: For muting, stop sound but don't clear startTime... + // QUESTION: what does it mean for replaying that .stop "frees memory" + + // reloadSample + // QUESTION: set stop state WHERE??!! + + if (debugFlag) + debugPrintln("JSClip.stopSample(): stopping sound Clip"); + line.stop(); + + startTime = 0; + return 0; + } + + /* + * called by LineListener class + */ + public void update(LineEvent event) { + if (event.getType().equals(LineEvent.Type.STOP)) { + line.close(); // really a stop?? + } + else if (event.getType().equals(LineEvent.Type.CLOSE)) { + // this forces a system exit in example code + // TODO: what should be done to close line + if (debugFlag) + debugPrint("JSClip.update(CLOSE) entered "); + } + } + +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java new file mode 100755 index 0000000..ce8a97f --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSDirectionalSample.java @@ -0,0 +1,738 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * DirectionalSample object + * + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. + */ + +package com.sun.j3d.audioengines.javasound; + +import javax.media.j3d.*; +import com.sun.j3d.audioengines.*; +import javax.vecmath.*; + +/** + * The PostionalSample Class defines the data and methods associated with a + * PointSound sample played through the AudioDevice. + */ + +class JSDirectionalSample extends JSPositionalSample +{ + // The transformed direction of this sound + Vector3f xformDirection = new Vector3f(0.0f, 0.0f, 1.0f); + + public JSDirectionalSample() { + super(); + if (debugFlag) + debugPrintln("JSDirectionalSample constructor"); + } + + void setXformedDirection() { + if (debugFlag) + debugPrint("*** setXformedDirection"); + if (!getVWrldXfrmFlag()) { + if (debugFlag) + debugPrint(" Transform NOT set yet, so dir => xformDir"); + xformDirection.set(direction); + } + else { + if (debugFlag) + debugPrint(" Transform dir => xformDir"); + vworldXfrm.transform(direction, xformDirection); + } + if (debugFlag) + debugPrint(" xform(sound)Direction <= "+xformDirection.x+ + ", " + xformDirection.y + ", " + xformDirection.z); + } + + + /* *********************************** + * + * Intersect ray to head with Ellipse + * + * ***********************************/ + /* + * An ellipse is defined using: + * (1) the ConeSound's direction vector as the major axis of the ellipse; + * (2) the max parameter (a front distance attenuation value) along the + * cone's position axis; and + * (3) the min parameter (a back distance attenuation value) along the + * cone's negative axis + * This method calculates the distance from the sound source to the + * Intersection of the Ellipse with the ray from the sound source to the + * listener's head. + * This method returns the resulting distance. + * If an error occurs, -1.0 is returned. + * + * A calculation are done in 'Cone' space: + * The origin is defined as being the sound source position. + * The ConeSound source axis is the X-axis of this Cone's space. + * Since this ConeSound source defines a prolate spheroid (obtained + * by revolving an ellipsoid about the major axis) we can define the + * Y-axis of this Cone space as being in the same plane as the X-axis + * and the vector from the origin to the head. + * All calculations in Cone space can be generalized in this two- + * dimensional space without loss of precision. + * Location of the head, H, in Cone space can then be defined as: + * H'(x,y) = (cos @, sin @) * | H | + * where @ is the angle between the X-axis and the ray to H. + * Using the equation of the line thru the origin and H', and the + * equation of ellipse defined with min and max, find the + * intersection by solving for x and then y. + * + * (I) The equation of the line thru the origin and H', and the + * | H'(y) - S(y) | + * y - S(y) = | ----------- | * [x - S(x)] + * | H'(x) - S(x) | + * and since S(x,y) is the origin of ConeSpace: + * | H'(y) | + * y = | ----- | x + * | H'(x) | + * + * (II) The equation of ellipse: + * x**2 y**2 + * ---- + ---- = 1 + * a**2 b**2 + * given a is length from origin to ellipse along major, X-axis, and + * b is length from origin to ellipse along minor, Y-axis; + * where a**2 = [(max+min)/2]**2 , since 2a = min+max; + * where b**2 = min*max , since the triangle abc is made is defined by the + * the points: S(x,y), origin, and (0,b), + * thus b**2 = a**2 - S(x,y) = a**2 - ((a-min)**2) = 2a*min - min**2 + * b**2 = ((min+max)*min) - min**2 = min*max. + * so the equation of the ellipse becomes: + * x**2 y**2 + * ---------------- + ------- = 1 + * [(max+min)/2]**2 min*max + * + * Substuting for y from Eq.(I) into Eq.(II) gives + * x**2 [(H'(y)/H'(x))*x]**2 + * ---------------- + -------------------- = 1 + * [(max+min)/2]**2 min*max + * + * issolating x**2 gives + * | 1 [H'(y)/H'(x)]**2 | + * x**2 | ---------------- + ---------------- | = 1 + * | [(max+min)/2]**2 min*max | + * + * + * | 4 [(sin @ * |H|)/(cos @ * |H|)]**2 | + * x**2 | -------------- + -------------------------------- | = 1 + * | [(max+min)]**2 min*max | + * + * | | + * | 1 | + * | | + * x**2 = | --------------------------------------- | + * | | 4 [sin @/cos @]**2 | | + * | | -------------- + ---------------- | | + * | | [(max+min)]**2 min*max | | + * + * substitute tan @ for [sin @/cos @], and take the square root and you have + * the equation for x as calculated below. + * + * Then solve for y by plugging x into Eq.(I). + * + * Return the distance from the origin in Cone space to this intersection + * point: square_root(x**2 + y**2). + * + */ + double intersectEllipse(double max, double min ) { + + if (debugFlag) + debugPrint(" intersectEllipse entered with min/max = " + min + "/" + max); + /* + * First find angle '@' between the X-axis ('A') and the ray to Head ('H'). + * In local coordinates, use Dot Product of those two vectors to get cos @: + * A(u)*H(u) + A(v)*H(v) + A(w)*H(v) + * cos @ = -------------------------------- + * |A|*|H| + * then since domain of @ is { 0 <= @ <= PI }, arccos can be used to get @. + */ + Vector3f xAxis = this.direction; // axis is sound direction vector + // Get the already calculated vector from sound source position to head + Vector3f sourceToHead = this.sourceToCenterEar; + // error check vectors not empty + if (xAxis == null || sourceToHead == null) { + if (debugFlag) + debugPrint( " one or both of the vectors are null" ); + return (-1.0f); // denotes an error occurred + } + + // Dot Product + double dotProduct = (double)( (sourceToHead.dot(xAxis)) / + (sourceToHead.length() * xAxis.length())); + if (debugFlag) + debugPrint( " dot product = " + dotProduct ); + // since theta angle is in the range between 0 and PI, arccos can be used + double theta = (float)(Math.acos(dotProduct)); + if (debugFlag) + debugPrint( " theta = " + theta ); + + /* + * Solve for X using Eq.s (I) and (II) from above. + */ + double minPlusMax = (double)(min + max); + double tangent = Math.tan(theta); + double xSquared = 1.0 / + ( ( 4.0 / (minPlusMax * minPlusMax) ) + + ( (tangent * tangent) / (min * max) ) ); + double x = Math.sqrt(xSquared); + if (debugFlag) + debugPrint( " X = " + x ); + /* + * Solve for y, given the result for x: + * | H'(y) | | sin @ | + * y = | ----- | x = | ----- | x + * | H'(x) | | cos @ | + */ + double y = tangent * x; + if (debugFlag) + debugPrint( " Y = " + y ); + double ySquared = y * y; + + /* + * Now return distance from origin to intersection point (x,y) + */ + float distance = (float)(Math.sqrt(xSquared + ySquared)); + if (debugFlag) + debugPrint( " distance to intersection = " + distance ); + return (distance); + } + + /* ***************** + * + * Find Factor + * + * *****************/ + /* + * Interpolates the correct attenuation scale factor given a 'distance' + * value. This version used both front and back attenuation distance + * and scale factor arrays (if non-null) in its calculation of the + * the distance attenuation. + * If the back attenuation arrays are null then this executes the + * PointSoundRetained version of this method. + * This method finds the intesection of the ray from the sound source + * to the center-ear, with the ellipses defined by the two sets (front + * and back) of distance attenuation arrays. + * This method looks at pairs of intersection distance values to find + * which pair the input distance argument is between: + * [intersectionDistance[index] and intersectionDistance[index+1] + * The index is used to get factorArray[index] and factorArray[index+1]. + * Then the ratio of the 'distance' between this pair of intersection + * values is used to scale the two found factorArray values proportionally. + */ + float findFactor(double distanceToHead, + double[] maxDistanceArray, float[] maxFactorArray, + double[] minDistanceArray, float[] minFactorArray) { + int index, lowIndex, highIndex, indexMid; + double returnValue; + + if (debugFlag) { + debugPrint("JSDirectionalSample.findFactor entered:"); + debugPrint(" distance to head = " + distanceToHead); + } + + if (minDistanceArray == null || minFactorArray == null) { + /* + * Execute the PointSoundRetained version of this method. + * Assume it will check for other error conditions. + */ + return ( this.findFactor(distanceToHead, + maxDistanceArray, maxFactorArray) ); + } + + /* + * Error checking + */ + if (maxDistanceArray == null || maxFactorArray == null) { + if (debugFlag) + debugPrint(" findFactor: arrays null"); + return -1.0f; + } + // Assuming length > 1 already tested in set attenuation arrays methods + int arrayLength = maxDistanceArray.length; + if (arrayLength < 2) { + if (debugFlag) + debugPrint(" findFactor: arrays length < 2"); + return -1.0f; + } + int largestIndex = arrayLength - 1; + /* + * Calculate distanceGain scale factor + */ + /* + * distanceToHead is larger than greatest distance in maxDistanceArray + * so head is beyond the outer-most ellipse. + */ + if (distanceToHead >= maxDistanceArray[largestIndex]) { + if (debugFlag) + debugPrint(" findFactor: distance > " + + maxDistanceArray[largestIndex]); + if (debugFlag) + debugPrint(" maxDistanceArray length = " + + maxDistanceArray.length); + if (debugFlag) + debugPrint(" findFactor returns ****** " + + maxFactorArray[largestIndex] + " ******"); + return maxFactorArray[largestIndex]; + } + + /* + * distanceToHead is smaller than least distance in minDistanceArray + * so head is inside the inner-most ellipse. + */ + if (distanceToHead <= minDistanceArray[0]) { + if (debugFlag) + debugPrint(" findFactor: distance < " + + maxDistanceArray[0]); + if (debugFlag) + debugPrint(" findFactor returns ****** " + + minFactorArray[0] + " ******"); + return minFactorArray[0]; + } + + /* + * distanceToHead is between points within attenuation arrays. + * Use binary halfing of distance attenuation arrays. + */ + { + double[] distanceArray = new double[arrayLength]; + float[] factorArray = new float[arrayLength]; + boolean[] intersectionCalculated = new boolean[arrayLength]; + // initialize intersection calculated array flags to false + for (int i=0; i= 0.0) + intersectionCalculated[lowIndex] = true; + else { + /* + * Error in ellipse intersection calculation. Use + * average of max/min difference for intersection value. + */ + distanceArray[lowIndex] = (minDistanceArray[lowIndex] + + maxDistanceArray[lowIndex])*0.5; + if (internalErrors) + debugPrint( + "Internal Error in intersectEllipse; use " + + distanceArray[lowIndex] + + " for intersection value " ); + // Rather than aborting, just use average and go on... + intersectionCalculated[lowIndex] = true; + } + } // end of if intersection w/ lowIndex not already calculated + + if (!intersectionCalculated[highIndex]) { + distanceArray[highIndex] = this.intersectEllipse( + maxDistanceArray[highIndex],minDistanceArray[highIndex]); + // If return intersection distance is < 0 an error occurred. + if (distanceArray[highIndex] >= 0.0f) + intersectionCalculated[highIndex] = true; + else { + /* + * Error in ellipse intersection calculation. Use + * average of max/min difference for intersection value. + */ + distanceArray[highIndex] = (minDistanceArray[highIndex]+ + maxDistanceArray[highIndex])*0.5f; + if (internalErrors) + debugPrint( + "Internal Error in intersectEllipse; use " + + distanceArray[highIndex] + + " for intersection value " ); + // Rather than aborting, just use average and go on... + intersectionCalculated[highIndex] = true; + } + } // end of if intersection w/ highIndex not already calculated + + /* + * Test for intersection points being the same as head position + * distanceArray[lowIndex] and distanceArray[highIndex], if so + * return factor value directly from array + */ + if (distanceArray[lowIndex] >= distanceToHead) { + if ((lowIndex != 0) && + (distanceToHead < distanceArray[lowIndex])) { + if (internalErrors) + debugPrint( + "Internal Error: binary halving in " + + "findFactor failed; distance < low " + + "index value"); + } + if (debugFlag) { + debugPrint(" distanceArray[lowIndex] >= " + + "distanceToHead" ); + debugPrint( " factorIndex = " + lowIndex); + } + intersectionOnEllipse = true; + factorIndex = lowIndex; + break; + } + else if (distanceArray[highIndex] <= distanceToHead) { + if ((highIndex != largestIndex) && + (distanceToHead > distanceArray[highIndex])) { + if (internalErrors) + debugPrint( + "Internal Error: binary halving in " + + "findFactor failed; distance > high " + + "index value"); + } + if (debugFlag) { + debugPrint(" distanceArray[highIndex] >= " + + "distanceToHead" ); + debugPrint( " factorIndex = " + highIndex); + } + intersectionOnEllipse = true; + factorIndex = highIndex; + break; + } + + if (distanceToHead > distanceArray[lowIndex] && + distanceToHead < distanceArray[highIndex] ) { + indexMid = lowIndex + ((highIndex - lowIndex) / 2); + if (distanceToHead <= distanceArray[indexMid]) + // value of distance in lower "half" of list + highIndex = indexMid; + else // value if distance in upper "half" of list + lowIndex = indexMid; + } + } /* of while */ + + /* + * First check to see if distanceToHead is beyond min or max + * ellipses, or on an ellipse. + * If so, factor is calculated using the distance Ratio + * (distanceToHead - min) / (max-min) + * where max = maxDistanceArray[factorIndex], and + * min = minDistanceArray[factorIndex] + */ + if (intersectionOnEllipse && factorIndex >= 0) { + if (debugFlag) { + debugPrint( " ratio calculated using factorIndex " + + factorIndex); + debugPrint( " d.A. max pair for factorIndex " + + maxDistanceArray[factorIndex] + ", " + + maxFactorArray[factorIndex]); + debugPrint( " d.A. min pair for lowIndex " + + minDistanceArray[factorIndex] + ", " + + minFactorArray[factorIndex]); + } + returnValue = ( + ( (distanceArray[factorIndex] - + minDistanceArray[factorIndex]) / + (maxDistanceArray[factorIndex] - + minDistanceArray[factorIndex]) ) * + (maxFactorArray[factorIndex] - + minFactorArray[factorIndex]) ) + + minFactorArray[factorIndex] ; + if (debugFlag) + debugPrint(" findFactor returns ****** " + + returnValue + " ******"); + return (float)returnValue; + } + + /* Otherwise, for distanceToHead between distance intersection + * values, we need to calculate two factors - one for the + * ellipse defined by lowIndex min/max factor arrays, and + * the other by highIndex min/max factor arrays. Then the + * distance Ratio (defined above) is applied, using these + * two factor values, to get the final return value. + */ + double highFactorValue = 1.0; + double lowFactorValue = 0.0; + highFactorValue = + ( ((distanceArray[highIndex] - minDistanceArray[highIndex]) / + (maxDistanceArray[highIndex]-minDistanceArray[highIndex])) * + (maxFactorArray[highIndex] - minFactorArray[highIndex]) ) + + minFactorArray[highIndex] ; + if (debugFlag) { + debugPrint( " highFactorValue calculated w/ highIndex " + + highIndex); + debugPrint( " d.A. max pair for highIndex " + + maxDistanceArray[highIndex] + ", " + + maxFactorArray[highIndex]); + debugPrint( " d.A. min pair for lowIndex " + + minDistanceArray[highIndex] + ", " + + minFactorArray[highIndex]); + debugPrint( " highFactorValue " + highFactorValue); + } + lowFactorValue = + ( ((distanceArray[lowIndex] - minDistanceArray[lowIndex]) / + (maxDistanceArray[lowIndex] - minDistanceArray[lowIndex])) * + (maxFactorArray[lowIndex] - minFactorArray[lowIndex]) ) + + minFactorArray[lowIndex] ; + if (debugFlag) { + debugPrint( " lowFactorValue calculated w/ lowIndex " + + lowIndex); + debugPrint( " d.A. max pair for lowIndex " + + maxDistanceArray[lowIndex] + ", " + + maxFactorArray[lowIndex]); + debugPrint( " d.A. min pair for lowIndex " + + minDistanceArray[lowIndex] + ", " + + minFactorArray[lowIndex]); + debugPrint( " lowFactorValue " + lowFactorValue); + } + /* + * calculate gain scale factor based on the ratio distance + * between ellipses the distanceToHead lies between. + */ + /* + * ratio: distance from listener to sound source + * between lowIndex and highIndex times + * attenuation value between lowIndex and highIndex + * gives linearly interpolationed attenuation value + */ + if (debugFlag) { + debugPrint( " ratio calculated using distanceArray" + + lowIndex + ", highIndex " + highIndex); + debugPrint( " calculated pair for lowIndex " + + distanceArray[lowIndex]+", "+ lowFactorValue); + debugPrint( " calculated pair for highIndex " + + distanceArray[highIndex]+", "+ highFactorValue ); + } + + returnValue = + ( ( (distanceToHead - distanceArray[lowIndex]) / + (distanceArray[highIndex] - distanceArray[lowIndex]) ) * + (highFactorValue - lowFactorValue) ) + + factorArray[lowIndex] ; + if (debugFlag) + debugPrint(" findFactor returns ******" + + returnValue + " ******"); + return (float)returnValue; + } + + } + + /** + * CalculateDistanceAttenuation + * + * Simply calls ConeSound specific 'findFactor()' with + * both front and back attenuation linear distance and gain scale factor + * arrays. + */ + float calculateDistanceAttenuation(float distance) { + float factor = findFactor(distance, this.attenuationDistance, + this.attenuationGain, this.backAttenuationDistance, + this.backAttenuationGain); + if (factor < 0.0f) + return 1.0f; + else + return factor; + } + /** + * CalculateAngularGain + * + * Simply calls generic (for PointSound) 'findFactor()' with + * a single set of angular attenuation distance and gain scalefactor arrays. + */ + float calculateAngularGain() { + float angle = findAngularOffset(); + float factor = findFactor(angle, this.angularDistance, this.angularGain); + if (factor < 0.0f) + return 1.0f; + else + return factor; + } + + /* ***************** + * + * Find Angular Offset + * + * *****************/ + /* + * Calculates the angle from the sound's direction axis and the ray from + * the sound origin to the listener'center ear. + * For Cone Sounds this value is the arc cosine of dot-product between + * the sound direction vector and the vector (sound position,centerEar) + * all in Virtual World coordinates space. + * Center ear position is in Virtual World coordinates. + * Assumes that calculation done in VWorld Space... + * Assumes that xformPosition is already calculated... + */ + float findAngularOffset() { + Vector3f unitToEar = new Vector3f(); + Vector3f unitDirection = new Vector3f(); + Point3f xformPosition = positions[currentIndex]; + Point3f xformCenterEar = centerEars[currentIndex]; + float dotProduct; + float angle; + /* + * TODO: (Question) is assumption that xformed values available O.K. + * TODO: (Performance) save this angular offset and only recalculate + * if centerEar or sound position have changed. + */ + unitToEar.x = xformCenterEar.x - xformPosition.x; + unitToEar.y = xformCenterEar.y - xformPosition.y; + unitToEar.z = xformCenterEar.z - xformPosition.z; + unitToEar.normalize(); + unitDirection.normalize(this.direction); + dotProduct = unitToEar.dot(unitDirection); + angle = (float)(Math.acos((double)dotProduct)); + if (debugFlag) + debugPrint(" angle from cone direction = " + angle); + return(angle); + } + + /************ + * + * Calculate Filter + * + * *****************/ + /* + * Calculates the low-pass cutoff frequency filter value applied to the + * a sound based on both: + * Distance Filter (from Aural Attributes) based on distance + * between the sound and the listeners position + * Angular Filter (for Directional Sounds) based on the angle + * between a sound's projected direction and the + * vector between the sounds position and center ear. + * The lowest of these two filter is used. + * This filter value is stored into the sample's filterFreq field. + */ + void calculateFilter(float distance, AuralParameters attribs) { + // setting filter cutoff freq to 44.1kHz which, in this + // implementation, is the same as not performing filtering + float distanceFilter = 44100.0f; + float angularFilter = 44100.0f; + int arrayLength = attribs.getDistanceFilterLength(); + int filterType = attribs.getDistanceFilterType(); + + boolean distanceFilterFound = false; + boolean angularFilterFound = false; + if ((filterType == AuralParameters.NO_FILTERING) && arrayLength > 0) { + double[] distanceArray = new double[arrayLength]; + float[] cutoffArray = new float[arrayLength]; + attribs.getDistanceFilter(distanceArray, cutoffArray); + + if (debugFlag) { + debugPrint("distanceArray cutoffArray"); + for (int i=0; i + * NOTE: This class is not yet implemented. + */ + +class JSMidi extends JSChannel { + private static boolean warningReported = false; + + JSMidi() { + // Report a "not implemented" warning message + if (!warningReported) { + System.err.println("***"); + System.err.println("*** WARNING: JavaSoundMixer: MIDI sound not implemented"); + System.err.println("***"); + warningReported = true; + } + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java new file mode 100755 index 0000000..580c060 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSPositionalSample.java @@ -0,0 +1,1339 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Java Sound PositionalSample object + * + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. + */ + +package com.sun.j3d.audioengines.javasound; + +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.audioengines.*; + +/** + * The PostionalSample Class defines the data and methods associated with a + * PointSound sample played thru the AudioDevice. + */ + +class JSPositionalSample extends JSSample +{ + + // maintain fields for stereo channel rendering + float leftGain = 1.0f; // scale factor + float rightGain = 1.0f; // scale factor + int leftDelay = 0; // left InterauralTimeDifference in millisec + int rightDelay = 0; // right ITD in millisec + // fields for reverb channel + + // debug flag for the verbose Doppler calculation methods + static final + protected boolean dopplerFlag = true; + + /** + * For positional and directional sounds, TWO Hae streams or clips + * are allocated, one each for the left and right channels, played at + * a different (delayed) time and with a different gain value. + */ + int secondIndex = NULL_SAMPLE; + /** + * A third sample for control of reverb of the stream/clip is openned + * and maintained for all directional/positional sounds. + * For now, even if no aural attributes (using reverb) are active, + * a reverb channel is always started with the other two. A sound could + * be started without reverb and then reverb added later, but since there + * is no way to offset properly into all sounds (considering non-cached + * and nconsistent rate-changes during playing) this third sound is + * always allocated and started. + */ + int reverbIndex = NULL_SAMPLE; + + /** + * Save ear positions transformed into VirtualWorld coords from Head coords + * These default positions are used when the real values cannot queried + */ + Point3f xformLeftEar = new Point3f(-0.09f, -0.03f, 0.095f); + Point3f xformRightEar = new Point3f(0.09f, -0.03f, 0.095f); + // Z axis in head space - looking into the screen + Vector3f xformHeadZAxis = new Vector3f(0.0f, 0.0f, -1.0f); // Va + + /** + * Save vectors from source source position to transformed ear parameters + */ + Vector3f sourceToCenterEar = new Vector3f(); // Vh + Vector3f sourceToRightEar = new Vector3f(); // Vf or Vc + Vector3f sourceToLeftEar = new Vector3f(); // Vf or Vc + + boolean averageDistances = false; + long deltaTime = 0; + double sourcePositionChange = -1.0; + double headPositionChange = -1.0; + + /* + * Maintain the last locations of sound and head as well as time the + * sound was last processed. + * Process delta distance and time as part of Doppler calculations. + */ + static int MAX_DISTANCES = 4; + int numDistances = 0; +// TODO: time is based on changes to position!!! only +// TODO: must shap shot when either Position OR ear changes!!! +// TODO: must grab all changes to VIEW parameters (could change ear)!!! +// not just when pointer to View changes!! + long[] times = new long[MAX_DISTANCES]; + Point3f[] positions = new Point3f[MAX_DISTANCES]; // xformed sound source positions + Point3f[] centerEars = new Point3f[MAX_DISTANCES]; // xformed center ear positions + /* + * a set of indices (first, last, and current) are maintained to point + * into the above arrays + */ + int firstIndex = 0; + int lastIndex = 0; + int currentIndex = 0; + + /* + * Allow changes in Doppler rate only small incremental values otherwise + * you hear skips in the pitch of a sound during playback. + * When playback is faster, allow delta changes: + * (diff in Factor for octave (1.0))/(12 1/2-steps))*(1/4) of half-step + * When playback is slower, allow delta changes: + * (diff in Factor for octave (0.5))/(12 1/2-steps))*(1/4) of half-step + */ + double lastRequestedDopplerRateRatio = -1.0f; + double lastActualDopplerRateRatio = -1.0f; + static double maxRatio = 256.0f; // 8 times higher/lower + /* + * denotes movement of sound away or towards listener + */ + static int TOWARDS = 1; + static int NO_CHANGE = 0; + static int AWAY = -1; + + /* + * Process request for Filtering fields + */ + boolean filterFlag = false; + float filterFreq = -1.0f; + + /* + * Construct a new audio device Sample object + */ + public JSPositionalSample() { + super(); + if (debugFlag) + debugPrint("JSPositionalSample constructor"); + // initiallize circular buffer for averaging distance values + for (int i=0; i maxIndex) { + // increment each counter and loop around + averageDistances = true; + currentIndex++; + lastIndex++; + firstIndex++; + currentIndex %= MAX_DISTANCES; + lastIndex %= MAX_DISTANCES; + firstIndex %= MAX_DISTANCES; + } + } + + // Not only do we transform position but delta time is calculated and + // old transformed position is saved + // Average the last MAX_DISTANCES delta time and change in position using + // an array for both and circlularly storing the time and distance values + // into this array. + // Current transformed position and time in stored into maxIndex of their + // respective arrays. + void setXformedPosition() { + Point3f newPosition = new Point3f(); + if (debugFlag) + debugPrint("*** setXformedPosition"); + // xform Position + if (getVWrldXfrmFlag()) { + if (debugFlag) + debugPrint(" Transform set so transform pos"); + vworldXfrm.transform(position, newPosition); + } + else { + if (debugFlag) + debugPrint(" Transform NOT set so pos => xformPos"); + newPosition.set(position); + } + // store position and increment indices ONLY if theres an actual change + if (newPosition.x == positions[currentIndex].x && + newPosition.y == positions[currentIndex].y && + newPosition.z == positions[currentIndex].z ) { + if (debugFlag) + debugPrint(" No change in pos, so don't reset"); + return; + } + + incrementIndices(); + // store new transformed position + times[currentIndex] = System.currentTimeMillis(); + positions[currentIndex].set(newPosition); + if (debugFlag) + debugPrint(" xform(sound)Position -" + + " positions[" + currentIndex + "] = (" + + positions[currentIndex].x + ", " + + positions[currentIndex].y + ", " + + positions[currentIndex].z + ")"); + + // since this is a change to the sound position and not the + // head save the last head position into the current element + if (numDistances > 1) + centerEars[currentIndex].set(centerEars[lastIndex]); + + } + + /** + * Set Doppler effect Rate + * + * Calculate the rate of change in for the head and sound + * between the two time stamps (last two times position or + * VirtualWorld transform was updated). + * First determine if the head and sound source are moving + * towards each other (distance between them is decreasing), + * moving away from each other (distance between them is + * increasing), or no change (distance is the same, not moving + * or moving the same speed/direction). + * The following equation is used for determining the change in frequency - + * If there has been a change in the distance between the head and sound: + * + * f' = f * frequencyScaleFactor * velocityRatio + * + * For no change in the distance bewteen head and sound, velocityRatio is 1: + * + * f' = f + * + * For head and sound moving towards each other, velocityRatio (> 1.0) is: + * + * | speedOfSound*rollOff + velocityOfHead*velocityScaleFactor | + * | ------------------------------------------------------------- | + * | speedOfSound*rollOff - velocityOfSource*velocityScaleFactor | + * + * For head and sound moving away from each other, velocityRatio (< 1.0) is: + * + * | speedOfSound*rollOff - velocityOfHead*velocityScaleFactor | + * | ------------------------------------------------------------- | + * | speedOfSound*rollOff + velocityOfSource*velocityScaleFactor | + * + * where frequencyScaleFactor, rollOff, velocityScaleFactor all come from + * the active AuralAttributes parameters. + * The following special cases must be test for AuralAttribute parameters: + * rolloff + * Value MUST be > zero for any sound to be heard! + * If value is zero, all sounds affected by AuralAttribute region are silent. + * velocityScaleFactor + * Value MUST be > zero for any sound to be heard! + * If value is zero, all sounds affected by AuralAttribute region are paused. + * frequencyScaleFactor + * Value of zero disables Doppler calculations: + * Sfreq' = Sfreq * frequencyScaleFactor + * + * This rate is passed to device drive as a change to playback sample + * rate, in this case the frequency need not be known. + * + * Return value of zero denotes no change + * Return value of -1 denotes ERROR + */ + float calculateDoppler(AuralParameters attribs) { + double sampleRateRatio = 1.0; + double headVelocity = 0.0; // in milliseconds + double soundVelocity = 0.0; // in milliseconds + double distanceSourceToHead = 0.0; // in meters + double lastDistanceSourceToHead = 0.0; // in meters + float speedOfSound = attribs.SPEED_OF_SOUND; + double numerator = 1.0; + double denominator = 1.0; + int direction = NO_CHANGE; // sound movement away or towards listener + + Point3f lastXformPosition; + Point3f lastXformCenterEar; + Point3f xformPosition; + Point3f xformCenterEar; + float averagedSoundDistances = 0.0f; + float averagedEarsDistances = 0.0f; + + /* + * Average the differences between the last MAX_DISTANCE + * sound positions and head positions + */ + if (!averageDistances) { + // TODO: Use some EPSilion to do 'equals' test against + if (dopplerFlag) + debugPrint("JSPositionalSample.calculateDoppler - " + + "not enough distance data collected, " + + "dopplerRatio set to zero"); + // can't calculate change in direction + return 0.0f; // sample rate ratio is zero + } + + lastXformPosition = positions[lastIndex]; + lastXformCenterEar = centerEars[lastIndex]; + xformPosition = positions[currentIndex]; + xformCenterEar = centerEars[currentIndex]; + distanceSourceToHead = xformPosition.distance(xformCenterEar); + lastDistanceSourceToHead = lastXformPosition.distance(lastXformCenterEar); + if (dopplerFlag) { + debugPrint("JSPositionalSample.calculateDoppler - distances: " + + "current,last = " + distanceSourceToHead + ", " + + lastDistanceSourceToHead ); + debugPrint(" " + + "current position = " + + xformPosition.x + ", " + xformPosition.y + + ", " + xformPosition.z); + debugPrint(" " + + "current ear = " + + xformCenterEar.x + ", " + xformCenterEar.y + + ", " + xformCenterEar.z); + debugPrint(" " + + "last position = " + + lastXformPosition.x + ", " + lastXformPosition.y + + ", " + lastXformPosition.z); + debugPrint(" " + + "last ear = " + + lastXformCenterEar.x + ", " + lastXformCenterEar.y + + ", " + lastXformCenterEar.z); + } + if (distanceSourceToHead == lastDistanceSourceToHead) { + // TODO: Use some EPSilion to do 'equals' test against + if (dopplerFlag) + debugPrint("JSPositionalSample.calculateDoppler - " + + "distance diff = 0, dopplerRatio set to zero"); + // can't calculate change in direction + return 0.0f; // sample rate ratio is zero + } + + deltaTime = times[currentIndex] - times[firstIndex]; + for (int i=0; i<(MAX_DISTANCES-1); i++) { + averagedSoundDistances += positions[i+1].distance(positions[i]); + averagedEarsDistances += centerEars[i+1].distance(centerEars[i]); + } + averagedSoundDistances /= (MAX_DISTANCES-1); + averagedEarsDistances /= (MAX_DISTANCES-1); + soundVelocity = averagedSoundDistances/deltaTime; + headVelocity = averagedEarsDistances/deltaTime; + if (dopplerFlag) { + debugPrint(" " + + "delta time = " + deltaTime ); + debugPrint(" " + + "soundPosition delta = " + + xformPosition.distance(lastXformPosition)); + debugPrint(" " + + "soundVelocity = " + soundVelocity); + debugPrint(" " + + "headPosition delta = " + + xformCenterEar.distance(lastXformCenterEar)); + debugPrint(" " + + "headVelocity = " + headVelocity); + } + if (attribs != null) { + + float rolloff = attribs.rolloff; + float velocityScaleFactor = attribs.velocityScaleFactor; + if (rolloff != 1.0f) { + speedOfSound *= rolloff; + if (dopplerFlag) + debugPrint(" " + + "attrib rollof = " + rolloff); + } + if (velocityScaleFactor != 1.0f) { + soundVelocity *= velocityScaleFactor; + headVelocity *= velocityScaleFactor; + if (dopplerFlag) { + debugPrint(" " + + "attrib velocity scale factor = " + + velocityScaleFactor ); + debugPrint(" " + + "new soundVelocity = " + soundVelocity); + debugPrint(" " + + "new headVelocity = " + headVelocity); + } + } + } + if (distanceSourceToHead < lastDistanceSourceToHead) { + // sound and head moving towards each other + if (dopplerFlag) + debugPrint(" " + + "moving towards..."); + direction = TOWARDS; + numerator = speedOfSound + headVelocity; + denominator = speedOfSound - soundVelocity; + } + else { + // sound and head moving away from each other + // note: no change in distance case covered above + if (dopplerFlag) + debugPrint(" " + + "moving away..."); + direction = AWAY; + numerator = speedOfSound - headVelocity; + denominator = speedOfSound + soundVelocity; + } + if (numerator <= 0.0) { + if (dopplerFlag) + debugPrint("JSPositionalSample.calculateDoppler: " + + "BOOM!! - velocity of head > speed of sound"); + return -1.0f; + } + else if (denominator <= 0.0) { + if (dopplerFlag) + debugPrint("JSPositionalSample.calculateDoppler: " + + "BOOM!! - velocity of sound source negative"); + return -1.0f; + } + else { + if (dopplerFlag) + debugPrint("JSPositionalSample.calculateDoppler: " + + "numerator = " + numerator + + ", denominator = " + denominator ); + sampleRateRatio = numerator / denominator; + } + +/******** + IF direction WERE important to calling method... + * Return value greater than 0 denotes direction of sound source is + * towards the listener + * Return value less than 0 denotes direction of sound source is + * away from the listener + if (direction == AWAY) + return -((float)sampleRateRatio); + else + return (float)sampleRateRatio; +*********/ + return (float)sampleRateRatio; + } + + void updateEar(int dirtyFlags, View view) { + if (debugFlag) + debugPrint("*** updateEar fields"); + // xform Ear + Point3f xformCenterEar = new Point3f(); + if (!calculateNewEar(dirtyFlags, view, xformCenterEar)) { + if (debugFlag) + debugPrint("calculateNewEar returned false"); + return; + } + // store ear and increment indices ONLY if there is an actual change + if (xformCenterEar.x == centerEars[currentIndex].x && + xformCenterEar.y == centerEars[currentIndex].y && + xformCenterEar.z == centerEars[currentIndex].z ) { + if (debugFlag) + debugPrint(" No change in ear, so don't reset"); + return; + } + // store xform Ear + incrementIndices(); + times[currentIndex] = System.currentTimeMillis(); + centerEars[currentIndex].set(xformCenterEar); + // since this is a change to the head position and not the sound + // position save the last sound position into the current element + if (numDistances > 1) + positions[currentIndex].set(positions[lastIndex]); + } + + boolean calculateNewEar(int dirtyFlags, View view, Point3f xformCenterEar) { + /* + * Transform ear position (from Head) into Virtual World Coord space + */ + Point3d earPosition = new Point3d(); // temporary double Point + + // TODO: check dirty flags coming in + // For now, recalculate ear positions by forcing earsXformed false + boolean earsXformed = false; + if (!earsXformed) { + if (view != null) { + PhysicalBody body = view.getPhysicalBody(); + if (body != null) { + + // Get Head Coord. to Virtual World transform + // TODO: re-enable this when userHeadToVworld is + // implemented correctly!!! + Transform3D headToVwrld = new Transform3D(); + view.getUserHeadToVworld(headToVwrld); + if (debugFlag) { + debugPrint("user head to Vwrld colum-major:"); + double[] matrix = new double[16]; + headToVwrld.get(matrix); + debugPrint("JSPosSample " + matrix[0]+", " + + matrix[1]+", "+matrix[2]+", "+matrix[3]); + debugPrint("JSPosSample " + matrix[4]+", " + + matrix[5]+", "+matrix[6]+", "+matrix[7]); + debugPrint("JSPosSample " + matrix[8]+", " + + matrix[9]+", "+matrix[10]+", "+matrix[11]); + debugPrint("JSPosSample " + matrix[12]+", " + + matrix[13]+", "+matrix[14]+", "+matrix[15]); + } + + // Get left and right ear positions in Head Coord.s + // Transforms left and right ears to Virtual World coord.s + body.getLeftEarPosition(earPosition); + xformLeftEar.x = (float)earPosition.x; + xformLeftEar.y = (float)earPosition.y; + xformLeftEar.z = (float)earPosition.z; + body.getRightEarPosition(earPosition); + xformRightEar.x = (float)earPosition.x; + xformRightEar.y = (float)earPosition.y; + xformRightEar.z = (float)earPosition.z; + headToVwrld.transform(xformRightEar); + headToVwrld.transform(xformLeftEar); + // Transform head viewing (Z) axis to Virtual World coord.s + xformHeadZAxis.set(0.0f, 0.0f, -1.0f); // Va + headToVwrld.transform(xformHeadZAxis); + + // calculate the new (current) mid-point between the ears + // find the mid point between left and right ear positions + xformCenterEar.x = xformLeftEar.x + + ((xformRightEar.x - xformLeftEar.x)*0.5f); + xformCenterEar.y = xformLeftEar.y + + ((xformRightEar.y - xformLeftEar.y)*0.5f); + xformCenterEar.z = xformLeftEar.z + + ((xformRightEar.z - xformLeftEar.z)*0.5f); + // TODO: when head changes earDirty should be set! + // earDirty = false; + if (debugFlag) { + debugPrint(" earXformed CALCULATED"); + debugPrint(" xformCenterEar = " + + xformCenterEar.x + " " + + xformCenterEar.y + " " + + xformCenterEar.z ); + } + earsXformed = true; + } // end of body NOT null + } // end of view NOT null + } // end of earsDirty + else { + // TODO: use existing transformed ear positions + } + + if (!earsXformed) { + // uses the default head position of (0.0, -0.03, 0.095) + if (debugFlag) + debugPrint(" earXformed NOT calculated"); + } + return earsXformed; + } + + /** + * Render this sample + * + * Calculate the audiodevice parameters necessary to spatially play this + * sound. + */ + public void render(int dirtyFlags, View view, AuralParameters attribs) { + if (debugFlag) + debugPrint("JSPositionalSample.render"); + updateEar(dirtyFlags, view); + + /* + * Time to check velocities and change the playback rate if necessary... + * + * Rolloff value MUST be > zero for any sound to be heard! + * If rolloff is zero, all sounds affected by AuralAttribute region + * are silent. + * FrequencyScaleFactor value MUST be > zero for any sound to be heard! + * since Sfreq' = Sfreq * frequencyScaleFactor. + * If FrequencyScaleFactor is zero, all sounds affected by + * AuralAttribute region are paused. + * VelocityScaleFactor value of zero disables Doppler calculations. + * + * Scale 'Doppler' rate (or lack of Doppler) by frequencyScaleFactor. + */ + float dopplerRatio = 1.0f; + if (attribs != null) { + float rolloff = attribs.rolloff; + float frequencyScaleFactor = attribs.frequencyScaleFactor; + float velocityScaleFactor = attribs.velocityScaleFactor; + if (debugFlag || dopplerFlag) + debugPrint("JSPositionalSample: attribs NOT null"); + if (rolloff <= 0.0f) { + if (debugFlag) + debugPrint(" rolloff = " + rolloff + " <= 0.0" ); + // TODO: Make sound silent + // return ??? + } + else if (frequencyScaleFactor <= 0.0f) { + if (debugFlag) + debugPrint(" freqScaleFactor = " + frequencyScaleFactor + + " <= 0.0" ); + // TODO: Pause sound silent + // return ??? + } + else if (velocityScaleFactor > 0.0f) { + if (debugFlag || dopplerFlag) + debugPrint(" velocityScaleFactor = " + + velocityScaleFactor); +/******* + if (deltaTime > 0) { +*******/ + // Doppler can be calculated after the second time + // updateXformParams() is executed + dopplerRatio = calculateDoppler(attribs); + + if (dopplerRatio == 0.0f) { + // dopplerRatio zeroo denotes no changed + // TODO: But what if frequencyScaleFactor has changed + if (debugFlag) { + debugPrint("JSPositionalSample: render: " + + "dopplerRatio returned zero; no change"); + } + } + else if (dopplerRatio == -1.0f) { + // error returned by calculateDoppler + if (debugFlag) { + debugPrint("JSPositionalSample: render: " + + "dopplerRatio returned = " + + dopplerRatio + "< 0"); + } + // TODO: Make sound silent + // return ??? + } + else if (dopplerRatio > 0.0f) { + // rate could be changed + rateRatio = dopplerRatio * frequencyScaleFactor * + getRateScaleFactor(); + if (debugFlag) { + debugPrint(" scaled by frequencyScaleFactor = " + + frequencyScaleFactor ); + } + } +/****** + } + else { + if (debugFlag) + debugPrint("deltaTime <= 0 - skip Doppler calc"); + } +******/ + } + else { // auralAttributes not null but velocityFactor <= 0 + // Doppler is disabled + rateRatio = frequencyScaleFactor * getRateScaleFactor(); + } + } + /* + * since aural attributes undefined, default values are used, + * thus no Doppler calculated + */ + else { + if (debugFlag || dopplerFlag) + debugPrint("JSPositionalSample: attribs null"); + rateRatio = 1.0f; + } + + this.panSample(attribs); + } + + /* ***************** + * + * Calculate Angular Gain + * + * *****************/ + /* + * Calculates the Gain scale factor applied to the overall gain for + * a sound based on angle between a sound's projected direction and the + * vector between the sounds position and center ear. + * + * For Point Sounds this value is always 1.0f. + */ + float calculateAngularGain() { + return(1.0f); + } + + /* ***************** + * + * Calculate Filter + * + * *****************/ + /* + * Calculates the low-pass cutoff frequency filter value applied to the + * a sound based on both: + * Distance Filter (from Aural Attributes) based on distance + * between the sound and the listeners position + * Angular Filter (for Directional Sounds) based on the angle + * between a sound's projected direction and the + * vector between the sounds position and center ear. + * The lowest of these two filter is used. + * This filter value is stored into the sample's filterFreq field. + */ + void calculateFilter(float distance, AuralParameters attribs) { + // setting filter cutoff freq to 44.1kHz which, in this + // implementation, is the same as not performing filtering + float distanceFilter = 44100.0f; + float angularFilter = 44100.0f; + int arrayLength = attribs.getDistanceFilterLength(); + int filterType = attribs.getDistanceFilterType(); + boolean distanceFilterFound = false; + boolean angularFilterFound = false; + if ((filterType != AuralParameters.NO_FILTERING) && arrayLength > 0) { + double[] distanceArray = new double[arrayLength]; + float[] cutoffArray = new float[arrayLength]; + attribs.getDistanceFilter(distanceArray, cutoffArray); + + if (debugFlag) { + debugPrint("distanceArray cutoffArray"); + for (int i=0; i= distanceArray[largestIndex]) { + if (debugFlag) { + debugPrint(" findFactor: distance > " + + distanceArray[largestIndex]); + debugPrint(" distanceArray length = "+ arrayLength); + } + return factorArray[largestIndex]; + } + else if (distance <= distanceArray[0]) { + if (debugFlag) + debugPrint(" findFactor: distance < " + + distanceArray[0]); + return factorArray[0]; + } + /* + * Distance between points within attenuation array. + * Use binary halfing of distance array + */ + else { + lowIndex = 0; + highIndex = largestIndex; + if (debugFlag) + debugPrint(" while loop to find index: "); + while (lowIndex < (highIndex-1)) { + if (debugFlag) { + debugPrint(" lowIndex " + lowIndex + + ", highIndex " + highIndex); + debugPrint(" d.A. pair for lowIndex " + + distanceArray[lowIndex] + ", " + factorArray[lowIndex] ); + debugPrint(" d.A. pair for highIndex " + + distanceArray[highIndex] + ", " + factorArray[highIndex] ); + } + /* + * we can assume distance is between distance atttenuation vals + * distanceArray[lowIndex] and distanceArray[highIndex] + * calculate gain scale factor based on distance + */ + if (distanceArray[lowIndex] >= distance) { + if (distance < distanceArray[lowIndex]) { + if (internalErrors) + debugPrint("Internal Error: binary halving in " + + " findFactor failed; distance < index value"); + } + if (debugFlag) { + debugPrint( " index == distanceGain " + + lowIndex); + debugPrint(" findFactor returns [LOW=" + + lowIndex + "] " + factorArray[lowIndex]); + } + // take value of scale factor directly from factorArray + return factorArray[lowIndex]; + } + else if (distanceArray[highIndex] <= distance) { + if (distance > distanceArray[highIndex]) { + if (internalErrors) + debugPrint("Internal Error: binary halving in " + + " findFactor failed; distance > index value"); + } + if (debugFlag) { + debugPrint( " index == distanceGain " + + highIndex); + debugPrint(" findFactor returns [HIGH=" + + highIndex + "] " + factorArray[highIndex]); + } + // take value of scale factor directly from factorArray + return factorArray[highIndex]; + } + if (distance > distanceArray[lowIndex] && + distance < distanceArray[highIndex] ) { + indexMid = lowIndex + ((highIndex - lowIndex) / 2); + if (distance <= distanceArray[indexMid]) + // value of distance in lower "half" of list + highIndex = indexMid; + else // value if distance in upper "half" of list + lowIndex = indexMid; + } + } /* of while */ + + /* + * ratio: distance from listener to sound source + * between lowIndex and highIndex times + * attenuation value between lowIndex and highIndex + * gives linearly interpolationed attenuation value + */ + if (debugFlag) { + debugPrint( " ratio calculated using lowIndex " + + lowIndex + ", highIndex " + highIndex); + debugPrint( " d.A. pair for lowIndex " + + distanceArray[lowIndex]+", "+factorArray[lowIndex] ); + debugPrint( " d.A. pair for highIndex " + + distanceArray[highIndex]+", "+factorArray[highIndex] ); + } + + float outputFactor = + ((float)(((distance - distanceArray[lowIndex])/ + (distanceArray[highIndex] - distanceArray[lowIndex]) ) ) * + (factorArray[highIndex] - factorArray[lowIndex]) ) + + factorArray[lowIndex] ; + if (debugFlag) + debugPrint(" findFactor returns " + outputFactor); + return outputFactor; + } + } + + /** + * CalculateDistanceAttenuation + * + * Simply calls generic (for PointSound) 'findFactor()' with + * a single set of attenuation distance and gain scale factor arrays. + */ + float calculateDistanceAttenuation(float distance) { + float factor = 1.0f; + factor = findFactor((double)distance, this.attenuationDistance, + this.attenuationGain); + if (factor >= 0.0) + return (factor); + else + return (1.0f); + } + + /* ****************** + * + * Pan Sample + * + * ******************/ + /* + * Sets pan and delay for a single sample associated with this Sound. + * Front and Back quadrants are treated the same. + */ + void panSample(AuralParameters attribs) { + int quadrant = 1; + float intensityHigh = 1.0f; + float intensityLow = 0.125f; + float intensityDifference = intensityHigh - intensityLow; + + //TODO: time around "average" default head + // int delayHigh = 32; // 32.15 samples = .731 ms + // int delayLow = 0; + + float intensityOffset; // 0.0 -> 1.0 then 1.0 -> 0.0 for full rotation + float halfX; + int id; + int err; + + float nearZero = 0.000001f; + float nearOne = 0.999999f; + float nearNegativeOne = -nearOne; + float halfPi = (float)Math.PI * 0.5f; + /* + * Parameters used for IID and ITD equations. + * Name of parameters (as used in Guide, E.3) are denoted in comments. + */ + float distanceSourceToCenterEar = 0.0f; // Dh + float lastDistanceSourceToCenterEar = 0.0f; + float distanceSourceToRightEar = 0.0f; // Ef or Ec + float distanceSourceToLeftEar = 0.0f; // Ef or Ec + float distanceBetweenEars = 0.18f; // De + float radiusOfHead = 0.0f; // De/2 + float radiusOverDistanceToSource = 0.0f; // De/2 * 1/Dh + + float alpha = 0.0f; // 'alpha' + float sinAlpha = 0.0f; // sin(alpha); + float gamma = 0.0f; // 'gamma' + + // Speed of Sound (unaffected by rolloff) in millisec/meters + float speedOfSound = attribs.SPEED_OF_SOUND; + float invSpeedOfSound = 1.0f / attribs.SPEED_OF_SOUND; + + float sampleRate = 44.1f; // 44 samples/millisec + + boolean rightEarClosest = false; + boolean soundFromBehind = false; + + float distanceGain = 1.0f; + float allGains = this.gain; // product of gain scale factors + + Point3f workingPosition = new Point3f(); + Point3f workingCenterEar = new Point3f(); + + // Asuumes that head and ear positions can be retrieved from universe + + Vector3f mixScale = new Vector3f(); // for mix*Samples code + + // Use transformed position of this sound + workingPosition.set(positions[currentIndex]); + workingCenterEar.set(centerEars[currentIndex]); + if (debugFlag) { + debugPrint("panSample:workingPosition from" + + " positions["+currentIndex+"] -> " + + workingPosition.x + ", " + workingPosition.y + ", " + + workingPosition.z + " for pointSound " + this); + debugPrint("panSample:workingCenterEar " + + workingCenterEar.x + " " + workingCenterEar.y + " " + + workingCenterEar.z); + debugPrint("panSample:xformLeftEar " + + xformLeftEar.x + " " + xformLeftEar.y + " " + + xformLeftEar.z); + debugPrint("panSample:xformRightEar " + + xformRightEar.x + " " + xformRightEar.y + " " + + xformRightEar.z); + } + + // Create the vectors from the sound source to head positions + sourceToCenterEar.x = workingCenterEar.x - workingPosition.x; + sourceToCenterEar.y = workingCenterEar.y - workingPosition.y; + sourceToCenterEar.z = workingCenterEar.z - workingPosition.z; + sourceToRightEar.x = xformRightEar.x - workingPosition.x; + sourceToRightEar.y = xformRightEar.y - workingPosition.y; + sourceToRightEar.z = xformRightEar.z - workingPosition.z; + sourceToLeftEar.x = xformLeftEar.x - workingPosition.x; + sourceToLeftEar.y = xformLeftEar.y - workingPosition.y; + sourceToLeftEar.z = xformLeftEar.z - workingPosition.z; + + /* + * get distances from SoundSource to + * (i) head origin + * (ii) right ear + * (iii) left ear + */ + distanceSourceToCenterEar = workingPosition.distance(workingCenterEar); + distanceSourceToRightEar = workingPosition.distance(xformRightEar); + distanceSourceToLeftEar = workingPosition.distance(xformLeftEar); + distanceBetweenEars = xformRightEar.distance(xformLeftEar); + if (debugFlag) + debugPrint(" distance from left,right ears to source: = (" + + distanceSourceToLeftEar + ", " + distanceSourceToRightEar + ")"); + + radiusOfHead = distanceBetweenEars * 0.5f; + if (debugFlag) + debugPrint(" radius of head = " + radiusOfHead ); + radiusOverDistanceToSource = // De/2 * 1/Dh + radiusOfHead/distanceSourceToCenterEar; + if (debugFlag) + debugPrint(" radius over distance = " + radiusOverDistanceToSource ); + if (debugFlag) { + debugPrint("panSample:source to center ear " + + sourceToCenterEar.x + " " + sourceToCenterEar.y + " " + + sourceToCenterEar.z ); + debugPrint("panSample:xform'd Head ZAxis " + + xformHeadZAxis.x + " " + xformHeadZAxis.y + " " + + xformHeadZAxis.z ); + debugPrint("panSample:length of sourceToCenterEar " + + sourceToCenterEar.length()); + debugPrint("panSample:length of xformHeadZAxis " + + xformHeadZAxis.length()); + } + + // Dot Product + double dotProduct = (double)( + (sourceToCenterEar.dot(xformHeadZAxis))/ + (sourceToCenterEar.length() * xformHeadZAxis.length())); + if (debugFlag) + debugPrint( " dot product = " + dotProduct ); + alpha = (float)(Math.acos(dotProduct)); + if (debugFlag) + debugPrint( " alpha = " + alpha ); + + if (alpha > halfPi) { + if (debugFlag) + debugPrint(" sound from behind"); + soundFromBehind = true; + alpha = (float)Math.PI - alpha; + if (debugFlag) + debugPrint( " PI minus alpha =>" + alpha ); + } + else { + soundFromBehind = false; + if (debugFlag) + debugPrint(" sound from in front"); + } + + gamma = (float)(Math.acos(radiusOverDistanceToSource)); + if (debugFlag) + debugPrint( " gamma " + gamma ); + + rightEarClosest = + (distanceSourceToRightEar>distanceSourceToLeftEar) ? false : true ; + /* + * Determine the quadrant sound is in + */ + if (rightEarClosest) { + if (debugFlag) + debugPrint( " right ear closest"); + if (soundFromBehind) + quadrant = 4; + else + quadrant = 1; + } + else { + if (debugFlag) + debugPrint( " left ear closest"); + if (soundFromBehind) + quadrant = 3; + else + quadrant = 2; + } + sinAlpha = (float)(Math.sin((double)alpha)); + if (sinAlpha < 0.0) sinAlpha = -sinAlpha; + if (debugFlag) + debugPrint( " sin(alpha) " + sinAlpha ); + + /* + * The path from sound source to the farthest ear is always indirect + * (it wraps around part of the head). + * Calculate distance wrapped around the head for farthest ear + */ + float DISTANCE = (float)Math.sqrt((double) + distanceSourceToCenterEar * distanceSourceToCenterEar + + radiusOfHead * radiusOfHead); + if (debugFlag) + debugPrint( " partial distance from edge of head to source = " + + distanceSourceToCenterEar); + if (rightEarClosest) { + distanceSourceToLeftEar = + DISTANCE + radiusOfHead * (halfPi+alpha-gamma); + if (debugFlag) + debugPrint(" new distance from left ear to source = " + + distanceSourceToLeftEar); + } + else { + distanceSourceToRightEar = + DISTANCE + radiusOfHead * (halfPi+alpha-gamma); + if (debugFlag) + debugPrint(" new distance from right ear to source = " + + distanceSourceToRightEar); + } + /* + * The path from the source source to the closest ear could either + * be direct or indirect (wraps around part of the head). + * if sinAlpha >= radiusOverDistance path of sound to closest ear + * is direct, otherwise it is indirect + */ + if (sinAlpha < radiusOverDistanceToSource) { + if (debugFlag) + debugPrint(" closest path is also indirect "); + // Path of sound to closest ear is indirect + + if (rightEarClosest) { + distanceSourceToRightEar = + DISTANCE + radiusOfHead * (halfPi-alpha-gamma); + if (debugFlag) + debugPrint(" new distance from right ear to source = " + + distanceSourceToRightEar); + } + else { + distanceSourceToLeftEar = + DISTANCE + radiusOfHead * (halfPi-alpha-gamma); + if (debugFlag) + debugPrint(" new distance from left ear to source = " + + distanceSourceToLeftEar); + } + } + else { + if (debugFlag) + debugPrint(" closest path is direct "); + if (rightEarClosest) { + if (debugFlag) + debugPrint(" direct distance from right ear to source = " + + distanceSourceToRightEar); + } + else { + if (debugFlag) + debugPrint(" direct distance from left ear to source = " + + distanceSourceToLeftEar); + } + } + + /** + * Short-cut taken. Rather than using actual delays from source + * (where the overall distances would be taken into account in + * determining delay) the difference in the left and right delay + * are applied. + * This approach will be preceptibly wrong for sound sources that + * are very far away from the listener so both ears would have + * large delay. + */ + sampleRate = channel.rateInHz * (0.001f); // rate in milliseconds + if (rightEarClosest) { + rightDelay = 0; + leftDelay = (int)((distanceSourceToLeftEar - distanceSourceToRightEar) * + invSpeedOfSound * sampleRate); + } + else { + leftDelay = 0; + rightDelay = (int)((distanceSourceToRightEar - distanceSourceToLeftEar) * + invSpeedOfSound * sampleRate); + } + + if (debugFlag) { + debugPrint(" using inverted SoS = " + invSpeedOfSound); + debugPrint(" and sample rate = " + sampleRate); + debugPrint(" left and right delay = (" + + leftDelay + ", " + rightDelay + ")"); + } + + // What should the gain be for the different ears??? + // TODO: now using a hack that sets gain based on a unit circle!!! + workingPosition.sub(workingCenterEar); // offset sound pos. by head origin + // normalize; put Sound on unit sphere around head origin + workingPosition.scale(1.0f/distanceSourceToCenterEar); + if (debugFlag) + debugPrint(" workingPosition after unitization " + + workingPosition.x+" "+workingPosition.y+" "+workingPosition.z ); + + /* + * Get the correct distance gain scale factor from attenuation arrays. + * This requires that sourceToCenterEar vector has been calculated. + */ + // TODO: now using distance from center ear to source + // Using distances from each ear to source would be more accurate + distanceGain = calculateDistanceAttenuation(distanceSourceToCenterEar); + + allGains *= distanceGain; + + /* + * Add angular gain (for Cone sound) + */ + if (debugFlag) + debugPrint(" all Gains (without angular gain) " + allGains); + // assume that transfromed Position is already calculated + allGains *= this.calculateAngularGain(); + if (debugFlag) + debugPrint(" (incl. angular gain) " + allGains); + + halfX = workingPosition.x/2.0f; + if (halfX >= 0) + intensityOffset = (intensityDifference * (0.5f - halfX)); + else + intensityOffset = (intensityDifference * (0.5f + halfX)); + + /* + * For now have delay constant for front back sound for now + */ + if (debugFlag) + debugPrint("panSample: quadrant " + quadrant); + switch (quadrant) { + case 1: + // Sound from front, right of center of head + case 4: + // Sound from back, right of center of head + rightGain = allGains * (intensityHigh - intensityOffset); + leftGain = allGains * (intensityLow + intensityOffset); + break; + + case 2: + // Sound from front, left of center of head + case 3: + // Sound from back, right of center of head + leftGain = allGains * (intensityHigh - intensityOffset); + rightGain = allGains * (intensityLow + intensityOffset); + break; + } /* switch */ + if (debugFlag) + debugPrint("panSample: left/rightGain " + leftGain + + ", " + rightGain); + + // Combines distance and angular filter to set this sample's current + // frequency cutoff value + calculateFilter(distanceSourceToCenterEar, attribs); + + } /* panSample() */ + +// NOTE: setGain in audioengines.Sample is used to set/get user suppled factor +// this class uses this single gain value to calculate the left and +// right gain values +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java new file mode 100755 index 0000000..cc19fda --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSSample.java @@ -0,0 +1,362 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Java Sound Sample object + * + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. + */ + +package com.sun.j3d.audioengines.javasound; + +import java.net.URL; +import java.io.InputStream; +import javax.media.j3d.*; +import javax.sound.sampled.*; +import com.sun.j3d.audioengines.*; + +/** + * The Sample Class extended for Java Sound Mixer specific audio device. + */ + +class JSSample extends com.sun.j3d.audioengines.Sample +{ + /* + * NOTE: for this device type there is exactly one sample associated + * with each sound. + */ + + /** + * Sound Data Types + * + * Samples can be processed as streaming or buffered data. + * Fully spatializing sound sources may require data to be buffered. + * + * Sound data specified as Streaming is not copied by the AudioDevice + * driver implementation. It is up the application to ensure that + * this data is continuously accessible during sound rendering. + * Futhermore, full sound spatialization may not be possible, for + * all AudioDevice implementations on unbuffered sound data. + */ + static final int STREAMING_AUDIO_DATA = 1; + /** + * Sound data specified as Buffered is copied by the AudioDevice + * driver implementation. + */ + static final int BUFFERED_AUDIO_DATA = 2; + /** + * MIDI data + * TODO: differentiate between STREAMING and BUFFERED MIDI data + * right now all MIDI data is buffered + */ + static final int STREAMING_MIDI_DATA = 3; + static final int BUFFERED_MIDI_DATA = 3; + static final int UNSUPPORTED_DATA_TYPE = -1; + + static final int NULL_SAMPLE = -1; + + /** + * sound data types: BUFFERED (cached) or STREAMING (non-cached) + */ + int dataType = BUFFERED_AUDIO_DATA; + + JSChannel channel = null; + + /** + * Offset pointer within currently playing sample data + */ + long dataOffset = 0; + + /* + * Maintain continuously playing silent sound sources. + */ + long timeDeactivated = 0; + long positionDeactivated = 0; + + long sampleLength = 0; + long loopStartOffset = 0; // for most this will be 0 + long loopLength = 0; // for most this is end sample - sampleLength + long attackLength = 0; // portion of sample before loop section + long releaseLength = 0; // portion of sample after loop section + + float rateRatio = 1.0f; + float currentRateRatio = -1.0f; // last actual rate ratio send to device + float targetRateRatio = -1.0f; + boolean rampRateFlag = false; + + public JSSample() { + super(); + if (debugFlag) + debugPrintln("JSSample constructor"); + } + + // the only public methods are those declared in the audioengines + // package as public + + /* + * This excutes code necessary to set current fields to their current + * correct values before JavaSoundMixer either start or updates the + * sample thru calls to JSThread. + */ + public void render(int dirtyFlags, View view, AuralParameters attribs) { + if (debugFlag) + debugPrint("JSSample.render "); + // if this is starting set gain, delay (for Pos), freq rate ... + // TODO: NOT SURE - leaving this in for now + float freqScaleFactor = attribs.frequencyScaleFactor; + if (attribs != null) { + if (freqScaleFactor <= 0.0f) { + // TODO: Pause Sample + } + else + rateRatio = currentRateRatio * freqScaleFactor; + } + else + rateRatio = currentRateRatio; + } + + /** + * Clears/re-initialize fields associated with sample data for + * this sound, + * and frees any device specific data associated with this sample. + */ + public void clear() { + super.clear(); + if (debugFlag) + debugPrintln("JSSample.clear() entered"); + // TODO: unload sound data at device +// null out samples element that points to this? +// would this cause samples list size to shrink? +// if sample elements are never freed then does this mean +// a have a memory leak? + dataType = UNSUPPORTED_DATA_TYPE; + dataOffset = 0; + timeDeactivated = 0; + positionDeactivated = 0; + sampleLength = 0; + loopStartOffset = 0; + loopLength = 0; + attackLength = 0; + releaseLength = 0; + rateRatio = 1.0f; + channel = null; + if (debugFlag) + debugPrintln("JSSample.clear() exited"); + } + + // @return error true if error occurred + boolean load(MediaContainer soundData) { + /** + * Get the AudioInputStream first. + * MediaContiner passed to method assumed to be a clone of the + * application node with the query capability bits set on. + */ + String path = soundData.getURLString(); + URL url = soundData.getURLObject(); + InputStream inputStream = soundData.getInputStream(); + boolean cacheFlag = soundData.getCacheEnable(); + AudioInputStream ais = null; + DataLine dataLine = null; + + // TODO: How do we determine if the file is a MIDI file??? + // for now set dataType to BUFFERED_ or STREAMING_AUDIO_DATA + // used to test for ais instanceof AudioMidiInputStream || + // ais instanceof AudioRmfInputStream ) + // then set dataType = JSSample.BUFFERED_MIDI_DATA; + // QUESTION: can non-cached MIDI files ever be supported ? + /**************** + // TODO: when we have a way to determine data type use code below + if (dataType==UNSUPPORTED_DATA_TYPE OR error_occurred) + clearSound(index); + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound get dataType failed"); + return true; + } + *****************/ + // ...for now just check cacheFlag + if (cacheFlag) + dataType = BUFFERED_AUDIO_DATA; + else + dataType = STREAMING_AUDIO_DATA; + + if ((url == null) && (inputStream == null) && (path == null)) { + if (debugFlag) + debugPrint("JavaSoundMixer.loadSound null data - return error"); + return true; + } + + // get ais + if (path != null) { + // generate url from string, and pass url to driver + if (debugFlag) { + debugPrint("JavaSoundMixer.loadSound with path = " + path); + } + try { + url = new URL(path); + } + catch (Exception e) { + // do not throw an exception while rendering + return true; + } + } + + // get DataLine channel based on data type + if (dataType == BUFFERED_AUDIO_DATA) { + if (debugFlag) + debugPrintln("JSSample.load dataType = BUFFERED "); + channel = new JSClip(); + if (debugFlag) + debugPrintln(" calls JSClip.initAudioInputStream"); + if (url != null) + ais = channel.initAudioInputStream(url, cacheFlag); + else if (inputStream != null) + ais = channel.initAudioInputStream(inputStream, cacheFlag); + if (ais == null) { + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound " + + "initAudioInputStream() failed"); + return true; + } + if (debugFlag) + debugPrintln(" calls JSClip.initDataLine"); + dataLine = channel.initDataLine(ais); + } + else if (dataType == STREAMING_AUDIO_DATA) { + if (debugFlag) + debugPrintln("JSSample.load dataType = STREAMING "); + channel = new JSStream(); + if (debugFlag) + debugPrintln(" calls JSStream.initAudioInputStream"); + if (url != null) + ais = channel.initAudioInputStream(url, cacheFlag); + else if (inputStream != null) + ais = channel.initAudioInputStream(inputStream, cacheFlag); + if (ais == null) { + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound " + + "initAudioInputStream() failed"); + return true; + } + if (debugFlag) + debugPrintln(" calls JSStream.initDataLine"); + dataLine = channel.initDataLine(ais); + } + else { + if (debugFlag) + debugPrintln("JSSample.load doesn't support MIDI yet"); + } + if (dataLine == null) { + if (debugFlag) + debugPrint("JSSample.load initDataLine failed "); + channel = null; + return true; + } + duration = channel.getDuration(); + if (debugFlag) + debugPrint("JSSample.load channel duration = " + duration); + /* + * Since no error occurred while loading, save all the characteristics + * for the sound in the sample. + */ + setDirtyFlags(0xFFFF); + setSoundType(soundType); + setSoundData(soundData); + + if (debugFlag) + debugPrintln("JSSample.load returned without error"); + return false; + } + + void reset() { + if (debugFlag) + debugPrint("JSSample.reset() exit"); + rateRatio = 1.0f; + } + +// TODO: NEED methods for any field accessed by both JSThread and +// JavaSoundMixer so that we can make these MT safe?? + /* + * Process request for Filtering fields + */ + boolean getFilterFlag() { + return false; + } + float getFilterFreq() { + return -1.0f; + } + + void setCurrentRateRatio(float ratio) { + currentRateRatio = ratio; + } + + float getCurrentRateRatio() { + return currentRateRatio; + } + + void setTargetRateRatio(float ratio) { + targetRateRatio = ratio; + } + + float getTargetRateRatio() { + return targetRateRatio; + } + + void setRampRateFlag(boolean flag) { + rampRateFlag = flag; + } + + boolean getRampRateFlag() { + return rampRateFlag; + } + + void setDataType(int type) { + dataType = type; + } + + int getDataType() { + return dataType; + } + +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java new file mode 100755 index 0000000..340e1af --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSStream.java @@ -0,0 +1,67 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines.javasound; + +/** + * The JSStream class defines audio output methods that call the JavaSound + * API methods for streams. + * + *

+ * NOTE: This class is not yet implemented. + */ + +class JSStream extends JSChannel { + private static boolean warningReported = false; + + JSStream() { + // Report a "not implemented" warning message + if (!warningReported) { + System.err.println("***"); + System.err.println("*** WARNING: JavaSoundMixer: Streaming (uncached) audio not implemented"); + System.err.println("***"); + warningReported = true; + } + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java new file mode 100755 index 0000000..ed91910 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JSThread.java @@ -0,0 +1,855 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.audioengines.javasound; + +/* + * JavaSound engine Thread + * + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. When this is done, we may or may not need this class. + */ + +import javax.media.j3d.*; +import com.sun.j3d.audioengines.*; + +/** + * The Thread Class extended for JavaSound Mixer specific audio device + * calls that dynamically, in 'real-time" change engine parameters + * such as volume/gain and sample-rate/frequency(pitch). + */ + +class JSThread extends com.sun.j3d.audioengines.AudioEngineThread { + + /** + * The thread data for this thread + */ + int totalChannels = 0; + /** + * flags denoting if dynamic gain or rate interpolation is to be performed + */ + boolean rampGain = false; + + // global thread flat rampRate set true only when setTargetRate called + // for any sample. but it is cleared only by doWork when no sample + // has a need for the rate to be ramped any further. + boolean rampRate = false; + +/*** TODO: + * + * scalefactors applied to current sample rate to determine delta changes + * in rate (in Hz) + * + float currentGain = 1.0f; + float targetGain = 1.0f; +***********/ + + // reference to engine that created this thread + AudioEngine3D audioEngine = null; + + /** + * This constructor simply assigns the given id. + */ + JSThread(ThreadGroup t, AudioEngine3DL2 engine) { + super(t, "J3D-JavaSoundThread"); + audioEngine = engine; + // TODO: really get total JavaSound channels + totalChannels = 32; + if (debugFlag) + debugPrint("JSThread.constructor("+t+")"); + } + + + + /** + * This method performs one iteration of pending work to do + * + * Wildly "garbled" sounds was caused by unequal changes in delta + * time verses delta distances (resulting in jumps in rate factors + * calculated for Doppler. This work thread is meant to smoothly + * increment/decrement changes in rate (and other future parameters) + * until the target value is reached. + */ + synchronized public void doWork() { + if (debugFlag) + debugPrint("JSThread.doWork()"); +/******* + while (rampRate || rampGain) { +*********/ +/****** DESIGN +// Loop while sound is playing, reget attributes and gains/reverb,... params +// update lowlevel params then read modify then copy to line(s) + +can keep my own loop count for streams??? not really + +*******/ + // QUESTION: will size ever get smaller after get performed??? + int numSamples = audioEngine.getSampleListSize(); + JSSample sample = null; + int numRateRamps = 0; + for (int index = 0; index < numSamples; index++) { + // loop thru samples looking for ones needing rate incremented + sample = (JSSample)audioEngine.getSample(index); + if (sample == null) + continue; + if (sample.getRampRateFlag()) { + if (debugFlag) + debugPrint(" rampRate true"); + boolean endOfRampReached = adjustRate(sample); + sample.setRampRateFlag(!endOfRampReached); + if (!endOfRampReached) + numRateRamps++; + } + // TODO: support changes in gain this way as well + } + if (numRateRamps > 0) { + rampRate = true; +runMonitor(RUN, 0, null); + } + else + rampRate = false; +/********* + try { + Thread.sleep(4); + } catch (InterruptedException e){} +*********/ +/******** + } // while +*********/ + // otherwise do nothing + } + + int getTotalChannels() { + return (totalChannels); + } + + /** + * Gradually change rate scale factor + * + * If the rate change is too great suddenly, it sounds like a + * jump, so we need to change gradually over time. + * Since an octive delta change up is 2.0 but down is 0.5, forced + * "max" rate of change is different for both. + * @return true if target rate value was reached + */ + boolean adjustRate(JSSample sample) { + // QUESTION: what should max delta rate changes be + // Using 1/32 of a half-step (1/12 of an octive)??? + double maxRateChangeDown = 0.00130213; + double maxRateChangeUp = 0.00260417; + + double lastActualRateRatio = sample.getCurrentRateRatio(); + double requestedRateRatio = sample.getTargetRateRatio(); + boolean endOfRamp = false; // flag denotes if target rate reached + if ( lastActualRateRatio > 0 ) { + double sampleRateRatio = requestedRateRatio; // in case diff = 0 + double diff = 0.0; + if (debugFlag) { + debugPrint("JSThread.adjustRate: between " + + lastActualRateRatio + " & " + requestedRateRatio); + } + diff = requestedRateRatio - lastActualRateRatio; + if (diff > 0.0) { // direction of movement is towards listener + // inch up towards the requested target rateRatio + if (diff >= maxRateChangeUp) { + sampleRateRatio = lastActualRateRatio + maxRateChangeUp; + if (debugFlag) { + debugPrint(" adjustRate: " + + "diff >= maxRateChangeUp so "); + debugPrint(" adjustRate: " + + " sampleRateRatio incremented up by max"); + } + endOfRamp = false; // target value not reached + } + /* + * otherwise delta change is within tolerance + * so use requested RateRatio as calculated w/out change + */ + else { + sampleRateRatio = requestedRateRatio; + if (debugFlag) { + debugPrint(" adjustRate: " + + " requestedRateRatio reached"); + } + endOfRamp = true; // reached + } + } + else if (diff < 0.0) { // movement is away from listener + // inch down towards the requested target rateRatio + if ((-diff) >= maxRateChangeDown) { + sampleRateRatio = lastActualRateRatio - maxRateChangeDown; + if (debugFlag) { + debugPrint(" adjustRate: " + + "-(diff) >= maxRateChangeUp so "); + debugPrint(" adjustRate: " + + " sampleRateRatio incremented down by max "); + } + endOfRamp = false; // target value not reached + } + /* + * otherwise negitive delta change is within tolerance so + * use sampleRateRatio as calculated w/out change + */ + else { + sampleRateRatio = requestedRateRatio; + if (debugFlag) { + debugPrint(" adjustRate: " + + " requestedRateRatio reached"); + } + endOfRamp = true; // reached + } + } + else // there is no difference between last set and requested rates + return true; + + this.setSampleRate(sample, (float)sampleRateRatio); + } + else { + // this is the first time thru with a rate change + if (debugFlag) { + debugPrint(" adjustRate: " + + "last requested rateRatio not set yet " + + "so sampleRateRatio left unchanged"); + } + this.setSampleRate(sample, (float)requestedRateRatio); + endOfRamp = false; // target value not reached + } + return endOfRamp; + } // adjustRate + + void setSampleRate(JSSample sample, JSAuralParameters attribs) { +// TODO: + } + + // gain set at start sample time as well + void setSampleGain(JSSample sample, JSAuralParameters attribs) { +/******* + // take fields as already set in sample and updates gain + // called after sample.render performed + if (debugFlag) + debugPrint("JSThread.setSampleGain()"); +leftGain, rightGain + if (debugFlag) { + debugPrint(" " + + "StereoGain during update " + leftGain + + ", " + rightGain); + debugPrint(" " + + "StereoDelay during update " + leftDelay + + ", " + rightDelay); + } + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + boolean muted = sample.getMuteFlag(); + + if (debugFlag) + debugPrint("setStereoGain for sample "+sample+" " + leftGain + + ", " + rightGain); + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA || + dataType == JSAuralParameters.BUFFERED_AUDIO_DATA ) { + thread.setSampleGain(sample, leftGain); + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + thread.setSampleGain( + ((JSPositionalSample)sample).getSecondIndex(), rightGain); thread.setSampleGain( + ((JSPositionalSample)sample).getReverbIndex(), reverbGain); + } + } + // TODO: JavaSound does not support MIDI song panning yet + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + // Stereo samples not used for Midi Song playback + thread.setSampleGain(sample, (leftGain+rightGain) ); + ****** + // -1.0 far left, 0.0 center, 1.0 far right + position = (leftGain - rightGain) / (leftGain + rightGain); + JSMidi.setSamplePan(sample, position); + + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + JSMidi.setSampleGain( + ((JSPositionalSample)sample).getSecondIndex(), rightGain); JSMidi.setSampleGain( + ((JSPositionalSample)sample).getReverbIndex(), reverbGain); + } + ****** + } + else { + if (debugFlag) + debugPrint( "JSThread: Internal Error setSampleGain dataType " + + dataType + " invalid"); + return; + } + ***** + // force specific gain + // go ahead and set gain immediately + this.setSampleGain(sample, scaleFactor); + rampGain = false; // disable ramping of gain +******/ + } + + void setSampleDelay(JSSample sample, JSAuralParameters attribs) { +/****** + // take fields as already set in sample and updates delay + // called after sample.render performed + // adjust by attrib rolloff + float delayTime = attribs.reverbDelay * attribs.rolloff; + + leftDelay = (int)(sample.leftDelay * attribs.rolloff); + rightDelay = (int)(sample.rightDelay * attribs.rolloff); +leftDelay, rightDelay + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + if (debugFlag) + debugPrint("setStereoDelay for sample "+sample+" " + leftDelay + + ", " + rightDelay); + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + JSStream.setSampleDelay( + sample, leftDelay); + JSStream.setSampleDelay( + ((JSPositionalSample)sample).getSecondIndex(), rightDelay); + } + else + JSStream.setSampleDelay(sample, 0); + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + JSClip.setSampleDelay( + sample, leftDelay); + JSClip.setSampleDelay( + ((JSPositionalSample)sample).getSecondIndex(), rightDelay); + } + else + JSClip.setSampleDelay(sample, 0); + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + ******** + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + JSMidi.setSampleDelay( + sample, leftDelay); + JSMidi.setSampleDelay( + ((JSPositionalSample)sample).getSecondIndex(), rightDelay); + } + else + ******** + JSMidi.setSampleDelay(sample, 0); + } + else { + if (debugFlag) + debugPrint( "JSThread: Internal Error setSampleDelay dataType " + + dataType + " invalid"); + return; + } +******/ + } + + void setTargetGain(JSSample sample, float scaleFactor) { +/********** +// TODO: implement this + // current gain is used as starting scalefactor for ramp +// TEMPORARY: for now just set gain + this.setSampleGain(sample, scaleFactor); + rampGain = false; + rampGain = true; + targetGain = scaleFactor; + runMonitor(RUN, 0, null); +**********/ + } + + void setRate(JSSample sample, float rateScaleFactor) { + // force specific rate + // go ahead and set rate immediately + // take fields as already set in sample and updates rate + // called after sample.render performed + this.setSampleRate(sample, rateScaleFactor); + // disables rate from being gradually increased or decreased + // don't set global thread flat rampRate false just because + // one sample's rate is set to a specific value. + sample.setRampRateFlag(false); + } + + void setTargetRate(JSSample sample, float rateScaleFactor) { + // make gradual change in rate factors up or down to target rate + sample.setRampRateFlag(true); + sample.setTargetRateRatio(rateScaleFactor); + rampRate = true; + runMonitor(RUN, 0, null); + } + +// TODO: should have methods for delay and pan as well + + void setSampleGain(JSSample sample, float gain) { +/*********** +// QUESTION: What needs to be synchronized??? + if (debugFlag) + debugPrint("JSThread.setSampleGain for sample "+sample+" " + gain ); + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + boolean muted = sample.getMuteFlag(); +// TODO: + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) +{ + com.sun.j3d.audio.J3DHaeStream.setSampleGain(index, gain); + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + com.sun.j3d.audio.J3DHaeClip.setSampleGain(index, gain); + } + else { + // dataType==JSAuralParameters.STREAMING_MIDI_DATA + // dataType==JSAuralParameters.BUFFERED_MIDI_DATA + com.sun.j3d.audio.J3DHaeMidi.setSampleGain(index, gain); + } +***************/ + } + + void setSampleRate(JSSample sample, float scaleFactor) { +/********* +// QUESTION: What needs to be synchronized??? + // TODO: use sample.rateRatio?? + if (debugFlag) + debugPrint("JSThread.setSampleRate sample " + + sample + ", scale factor = " + scaleFactor); + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + +// TODO: + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + com.sun.j3d.audio.J3DHaeStream.scaleSampleRate(index, scaleFactor); + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + com.sun.j3d.audio.J3DHaeStream.scaleSampleRate( + ((JSPositionalSample)sample).getSecondIndex(), + scaleFactor); + com.sun.j3d.audio.J3DHaeStream.scaleSampleRate( + ((JSPositionalSample)sample).getReverbIndex(), + scaleFactor); + } + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + com.sun.j3d.audio.J3DHaeClip.scaleSampleRate(index, scaleFactor); + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + com.sun.j3d.audio.J3DHaeClip.scaleSampleRate( + ((JSPositionalSample)sample).getSecondIndex(), + scaleFactor); + com.sun.j3d.audio.J3DHaeClip.scaleSampleRate( + ((JSPositionalSample)sample).getReverbIndex(), + scaleFactor); + } + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + com.sun.j3d.audio.J3DHaeMidi.scaleSampleRate(index, scaleFactor); + // TODO: MIDI only supported for Background sounds + } +***********/ + sample.setCurrentRateRatio(scaleFactor); + } + + boolean startSample(JSSample sample) { +/********** +// QUESTION: should this have a return values - error - or not?? + + int returnValue = 0; + AuralParameters attribs = audioEngine.getAuralParameters(); + int soundType = sample.getSoundType(); + boolean muted = sample.getMuteFlag(); + int dataType = sample.getDataType(); + int loopCount = sample.getLoopCount(); + float leftGain = sample.leftGain; + float rightGain = sample.rightGain; + int leftDelay = (int)(sample.leftDelay * attribs.rolloff); + int rightDelay = (int)(sample.rightDelay * attribs.rolloff); + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + if (soundType == AudioDevice3D.BACKGROUND_SOUND) { + returnValue = JSStream.startSample(sample, + loopCount, leftGain); + if (debugFlag) + debugPrint("JSThread " + + "start stream backgroundSound with gain " + leftGain); + } + else { // soundType is POINT_SOUND or CONE_SOUND + // start up main left and right channels for spatial rendered sound + returnValue = JSStream.startSamples(sample, + ((JSPositionalSample)sample).getSecondIndex(), + loopCount, leftGain, rightGain, leftDelay, rightDelay); + // + // start up reverb channel w/out delay even if reverb not on now // + float reverbGain = 0.0f; + if (!muted && auralParams.reverbFlag) { + reverbGain = sample.getGain() * + attribs.reflectionCoefficient; + } + int reverbRtrnVal = JSStream.startSample( + ((JSPositionalSample)sample).getReverbIndex(), loopCount, reverbGain); + if (debugFlag) + debugPrint("JSThread " + + "start stream positionalSound with gain "+ leftGain + + ", " + rightGain); + } + } + + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + if (soundType == AudioDevice3D.BACKGROUND_SOUND) { + returnValue = JSClip.startSample(sample, + loopCount, leftGain ); + if (debugFlag) + debugPrint("JSThread " + + "start buffer backgroundSound with gain " + leftGain); + } + else { // soundType is POINT_SOUND or CONE_SOUND + // start up main left and right channels for spatial rendered sound + returnValue = JSClip.startSamples(sample, + ((JSPositionalSample)sample).getSecondIndex(), + loopCount, leftGain, rightGain, leftDelay, rightDelay); + // + // start up reverb channel w/out delay even if reverb not on now // + float reverbGain = 0.0f; + if (!muted && auralParams.reverbFlag) { + reverbGain = sample.getGain() * + attribs.reflectionCoefficient; + } + int reverbRtrnVal = JSClip.startSample( + ((JSPositionalSample)sample).getReverbIndex(), + loopCount, reverbGain); + + if (debugFlag) + debugPrint("JSThread " + + "start stream positionalSound with gain " + leftGain + + ", " + rightGain); + } + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + if (soundType == AudioDevice3D.BACKGROUND_SOUND) { + returnValue = JSMidi.startSample(sample, + loopCount, leftGain); + if (debugFlag) + debugPrint("JSThread " + + "start Midi backgroundSound with gain " + leftGain); + } + else { // soundType is POINT_SOUND or CONE_SOUND + // start up main left and right channels for spatial rendered sound + returnValue = JSMidi.startSamples(sample, + ((JSPositionalSample)sample).getSecondIndex(), + loopCount, leftGain, rightGain, leftDelay, rightDelay); + ******* + // TODO: positional MIDI sounds not supported. + // The above startSamples really just start on sample + // Don't bother with reverb channel for now. + + // + // start up reverb channel w/out delay even if reverb not on now // + float reverbGain = 0.0f; + if (!muted && auralParams.reverbFlag) { + reverbGain = sample.getGain() * + attribs.reflectionCoefficient; + } + int reverbRtrnVal = JSMidi.startSample( + ((JSPositionalSample)sample).getReverbIndex(), loopCount, reverbGain); + ******* + if (debugFlag) + debugPrint("JSThread " + + "start Midi positionalSound with gain "+ leftGain + + ", " + rightGain); + } + } + + else { + if (debugFlag) + debugPrint( + "JSThread: Internal Error startSample dataType " + + dataType + " invalid"); + return false; + } + // TODO: have to look at return values and conditionally return 'success' +**********/ + return true; + } + + boolean stopSample(JSSample sample) { +/*********** +// QUESTION: should this have a return values - error - or not?? + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + + int returnValue = 0; + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSStream.stopSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSStream.stopSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSStream.stopSample(sample); + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSClip.stopSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSClip.stopSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSClip.stopSample(sample); + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + + ***** + // TODO: positional sounds NOT supported yet + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSMidi.stopSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSMidi.stopSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + ***** + returnValue = JSMidi.stopSample(sample); + } + else { + if (debugFlag) + debugPrint( "JSThread: Internal Error stopSample dataType " + + dataType + " invalid"); + return -1; + } + +************/ + return true; + } + + + void pauseSample(JSSample sample) { +/********** + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + int returnValue = 0; + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSStream.pauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSStream.pauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSStream.pauseSample(sample); + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSClip.pauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSClip.pauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSClip.pauseSample(sample); + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + ******* + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSMidi.pauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSMidi.pauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + ***** + returnValue = JSMidi.pauseSample(sample); + } + else { + if (debugFlag) + debugPrint( + "JSThread: Internal Error pauseSample dataType " + + dataType + " invalid"); + } + if (returnValue < 0) { + if (debugFlag) + debugPrint( "JSThread: Internal Error pauseSample " + + "for sample " + sample + " failed"); + } +// QUESTION: return value or not??? + return; +*************/ + } + + void unpauseSample(JSSample sample) { +/************** + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + int returnValue = 0; + if (dataType == JSAuralParameters.STREAMING_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSStream.unpauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSStream.unpauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSStream.unpauseSample(sample); + } + else if (dataType == JSAuralParameters.BUFFERED_AUDIO_DATA) { + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSClip.unpauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSClip.unpauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + returnValue = JSClip.unpauseSample(sample); + } + else if (dataType == JSAuralParameters.STREAMING_MIDI_DATA || + + dataType == JSAuralParameters.BUFFERED_MIDI_DATA) { + ********* + // TODO: positional Midi sounds + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + returnValue = JSMidi.unpauseSamples(sample, + ((JSPositionalSample)sample).getSecondIndex()); + returnValue = JSMidi.unpauseSample( + ((JSPositionalSample)sample).getReverbIndex()); + } + else + ********* + returnValue = JSMidi.unpauseSample(sample); + } + else { + if (debugFlag) + debugPrint( + "JSThread: Internal Error unpauseSample dataType " + dataType + " invalid"); + } + if (returnValue < 0) { + if (debugFlag) + debugPrint( "JSThread: Internal Error unpauseSample " + + "for sample " + sample + " failed"); + + } +// QUESTION: return value or not??? + return; +*************/ + } + +// TODO: + void muteSample(JSSample sample) { + // is this already muted? if so don't do anytning + + // This determines if mute is done as a zero gain or + // as a stop, advance restart... + } + +// TODO: + void unmuteSample(JSSample sample) { + if (debugFlag) + debugPrint( "JSThread.unmuteSample not implemented"); + } + + int startStreams() { +// QUESTION: return value or not??? + return 0; + } + int startStream() { +// QUESTION: return value or not??? + return 0; + } + int startClips() { +// QUESTION: return value or not??? + return 0; + } + int startClip() { +// QUESTION: return value or not??? + return 0; + } + + /** + * This initializes this thread. Once this method returns, the thread is + * ready to do work. + */ + public void initialize() { + super.initialize(); + // this.setPriority(Thread.MAX_PRIORITY); + // TODO: init values of fields??? + if (debugFlag) + debugPrint("JSThread.initialize()"); + // TODO: doesn't do anything yet + } + + /** + * Code to close the device + * @return flag: true is closed sucessfully, false if error + */ + boolean close() { + // TODO: for now do nothing + return false; + } + + public void shutdown() { + } + + + + + // default resource clean up method + public void cleanup() { + super.cleanup(); + if (debugFlag) + debugPrint("JSThread.cleanup()"); + } +} diff --git a/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java b/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java new file mode 100755 index 0000000..2d5bc73 --- /dev/null +++ b/src/classes/share/com/sun/j3d/audioengines/javasound/JavaSoundMixer.java @@ -0,0 +1,959 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Audio device driver using Java Sound Mixer Engine. + * + * IMPLEMENTATION NOTE: The JavaSoundMixer is incomplete and really needs + * to be rewritten. + */ + +package com.sun.j3d.audioengines.javasound; + +import java.net.URL; +import java.io.InputStream; +import javax.vecmath.*; +import javax.media.j3d.*; +import com.sun.j3d.audioengines.*; +import java.util.ArrayList; +import java.lang.Thread; + +/** + * The JavaSoundMixer Class defines an audio output device that accesses + * JavaSound functionality stream data. + */ +public class JavaSoundMixer extends AudioEngine3DL2 { + + // Debug print flags and methods + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) + System.out.println(message); + } + + void debugPrintln(String message) { + if (debugFlag) + System.out.println(message); + } + + // Determines method to call for added or setting sound into ArrayList + static final int ADD_TO_LIST = 1; + static final int SET_INTO_LIST = 2; + + // current Aural Parameters = Aural Attributes from core + JavaSound + // specific fields, including reverberation parameters. + JSAuralParameters auralParams = null; + + // thread for dynamically changing audio parameters such as volume + // and sample rate. + JSThread thread = null; + + /* + * new fields in extended class + */ + protected float deviceGain = 1.0f; + + protected static final int NOT_PAUSED = 0; + protected static final int PAUSE_PENDING = 1; + protected static final int PAUSED = 2; + protected static final int RESUME_PENDING = 3; + protected int pause = NOT_PAUSED; + + /* + * Construct a new JavaSoundMixer with the specified P.E. + * @param physicalEnvironment the physical environment object where we + * want access to this device. + */ + public JavaSoundMixer(PhysicalEnvironment physicalEnvironment ) { + super(physicalEnvironment); + thread = new JSThread(Thread.currentThread().getThreadGroup(), this); + } + + /** + * Query total number of channels available for sound rendering + * for this audio device. + * Overridden method from AudioEngine. + * @return number of maximum voices play simultaneously on JavaSound Mixer. + */ + public int getTotalChannels() { + if (thread != null) + return thread.getTotalChannels(); + else + return 32; + } + + /** + * Code to initialize the device + * New interface to mixer/engine specific methods + * @return flag: true is initialized sucessfully, false if error + */ + public boolean initialize() { + if (thread == null) { + return false; + } + // init JavaSound dynamic thread + thread.initialize(); + auralParams = new JSAuralParameters(); + if (debugFlag) + debugPrintln("JavaSoundMixer: JSStream.initialize returned true"); + return true; + } + + /** + * Code to close the device. + * New interface to mixer/engine specific methods + * @return flag: true is closed sucessfully, false if error + */ + public boolean close() { + if (thread == null) + return false; + if (thread.close()) { + if (debugFlag) + debugPrintln("JavaSoundMixer: JSStream.close returned true"); + return true; + } + else { + if (debugFlag) + debugPrintln("JavaSoundMixer: JSStream.close returned false"); + return false; + } + } + + + /** + * Code to load sound data into a channel of device mixer. + * Load sound as one or mores sample into the Java Sound Mixer: + * a) as either a STREAM or CLIP based on whether cached is enabled + * b) positional and directional sounds use three samples per + * sound + * Overriden method from AudioEngine3D. + * + * Sound type determines if this is a Background, Point or Cone + * sound source and thus the JSXxxxSample object type + * Call JSXxxxxSample.loadSample() + * If no error + * Get the next free index in the samples list. + * Store a reference to JSXxxxSample object in samples list. + * @return index to the sample in samples list. + */ + public int prepareSound(int soundType, MediaContainer soundData) { + int index = JSSample.NULL_SAMPLE; + int methodType = ADD_TO_LIST; + if (soundData == null) + return JSSample.NULL_SAMPLE; + synchronized(samples) { + // for now force to just add to end of samples list + int samplesSize = samples.size(); + index = samplesSize; + samples.ensureCapacity(index+1); + boolean error = false; + + if (soundType == AudioDevice3D.CONE_SOUND) { + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound type=CONE"); + JSDirectionalSample dirSample = new JSDirectionalSample(); + error = dirSample.load(soundData); + if (error) + return JSSample.NULL_SAMPLE; + if (methodType == SET_INTO_LIST) + samples.set(index, dirSample); + else + samples.add(index, dirSample); + /* + * Since no error occurred while loading, save all the + * characterstics for the sound in the sample. + */ + dirSample.setDirtyFlags(0xFFFF); + dirSample.setSoundType(soundType); + dirSample.setSoundData(soundData); + + } + else if (soundType == AudioDevice3D.POINT_SOUND) { + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound type=POINT"); + JSPositionalSample posSample = new JSPositionalSample(); + error = posSample.load(soundData); + if (error) + return JSSample.NULL_SAMPLE; + if (methodType == SET_INTO_LIST) + samples.set(index, posSample); + else + samples.add(index, posSample); + posSample.setDirtyFlags(0xFFFF); + posSample.setSoundType(soundType); + posSample.setSoundData(soundData); + } + else { // soundType == AudioDevice3D.BACKGROUND_SOUND + if (debugFlag) + debugPrintln("JavaSoundMixer.prepareSound type=BACKGROUND"); + JSSample sample = null; + sample = new JSSample(); + error = sample.load(soundData); + if (error) + return JSSample.NULL_SAMPLE; + if (methodType == SET_INTO_LIST) + samples.set(index, sample); + else + samples.add(index, sample); + sample.setDirtyFlags(0xFFFF); + sample.setSoundType(soundType); + sample.setSoundData(soundData); + } + } + + if (debugFlag) { + debugPrint(" prepareSound type = "+soundType); + debugPrintln("JavaSoundMixer.prepareSound returned "+index); + } + return index; + } + + /** + * Clears the fields associated with sample data for this sound. + * Overriden method from AudioEngine3D. + */ + public void clearSound(int index) { + // TODO: call JSXXXX clear method + JSSample sample = null; + if ( (sample = (JSSample)getSample(index)) == null) + return; + sample.clear(); + synchronized(samples) { + samples.set(index, null); + } + } + + /** + * Save a reference to the local to virtual world coordinate space + * Overriden method from AudioEngine3D. + */ + public void setVworldXfrm(int index, Transform3D trans) { + if (debugFlag) + debugPrintln("JavaSoundMixer: setVworldXfrm for index " + index); + super.setVworldXfrm(index, trans); + if (debugFlag) { + double[] matrix = new double[16]; + trans.get(matrix); + debugPrintln("JavaSoundMixer column-major transform "); + debugPrintln("JavaSoundMixer " + matrix[0]+", "+matrix[1]+ + ", "+matrix[2]+", "+matrix[3]); + debugPrintln("JavaSoundMixer " + matrix[4]+", "+matrix[5]+ + ", "+matrix[6]+", "+matrix[7]); + debugPrintln("JavaSoundMixer " + matrix[8]+", "+matrix[9]+ + ", "+matrix[10]+", "+matrix[11]); + debugPrintln("JavaSoundMixer " + matrix[12]+", "+matrix[13]+ + ", "+matrix[14]+", "+matrix[15]); + } + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + int soundType = sample.getSoundType(); + + if (soundType == AudioDevice3D.CONE_SOUND) { + JSDirectionalSample dirSample = null; + if ((dirSample = (JSDirectionalSample)getSample(index)) == null) + return; + dirSample.setXformedDirection(); + dirSample.setXformedPosition(); + // flag that VirtualWorld transform set + dirSample.setVWrldXfrmFlag(true); + } + else if (soundType == AudioDevice3D.POINT_SOUND) { + JSPositionalSample posSample = null; + if ((posSample = (JSPositionalSample)getSample(index)) == null) + return; + posSample.setXformedPosition(); + // flag that VirtualWorld transform set + posSample.setVWrldXfrmFlag(true); + } + return; + } + /* + * Overriden method from AudioEngine3D. + */ + public void setPosition(int index, Point3d position) { + if (debugFlag) + debugPrintln("JavaSoundMixer: setPosition for index " + index); + super.setPosition(index, position); + JSPositionalSample posSample = null; + if ((posSample = (JSPositionalSample)getSample(index)) == null) + return; + int soundType = posSample.getSoundType(); + if ( (soundType == AudioDevice3D.POINT_SOUND) || + (soundType == AudioDevice3D.CONE_SOUND) ) { + posSample.setXformedPosition(); + } + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void setDirection(int index, Vector3d direction) { + if (debugFlag) + debugPrintln("JavaSoundMixer: setDirection for index " + index); + super.setDirection(index, direction); + JSDirectionalSample dirSample = null; + if ((dirSample = (JSDirectionalSample)getSample(index)) == null) + return; + int soundType = dirSample.getSoundType(); + if (soundType == AudioDevice3D.CONE_SOUND) { + dirSample.setXformedDirection(); + } + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void setReflectionCoefficient(float coefficient) { + super.setReflectionCoefficient(coefficient); + auralParams.reverbDirty |= JSAuralParameters.REFLECTION_COEFF_CHANGED; + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void setReverbDelay(float reverbDelay) { + super.setReverbDelay(reverbDelay); + auralParams.reverbDirty |= JSAuralParameters.REVERB_DELAY_CHANGED; + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void setReverbOrder(int reverbOrder) { + super.setReverbOrder(reverbOrder); + auralParams.reverbDirty |= JSAuralParameters.REVERB_ORDER_CHANGED; + return; + } + + /* + * QUESTION: if this is used, for now, exclusively, to start a Background + * or any single sampled Sounds, why are there if-else cases to handle + * Point and Cone sounds?? + * + * For now background sounds are not reverberated + * + * Overriden method from AudioEngine3D. + */ + public int startSample(int index) { + // TODO: Rewrite this function + + if (debugFlag) + debugPrintln("JavaSoundMixer: STARTSample for index " + index); + + JSSample sample = null; + if ( ( (sample = (JSSample)getSample(index)) == null) || + thread == null ) + return JSSample.NULL_SAMPLE; + + int soundType = sample.getSoundType(); + boolean muted = sample.getMuteFlag(); + if (muted) { + if (debugFlag) + debugPrintln(" MUTEd start"); + thread.muteSample(sample); + if (soundType != AudioDevice3D.BACKGROUND_SOUND) + setFilter(index, false, Sound.NO_FILTER); + } + else { + sample.render(sample.getDirtyFlags(), getView(), auralParams); + this.scaleSampleRate(index, sample.rateRatio); + // filtering + if (soundType != AudioDevice3D.BACKGROUND_SOUND) + setFilter(index, sample.getFilterFlag(), sample.getFilterFreq()); + } + + boolean startSuccessful; + startSuccessful = thread.startSample(sample); + + sample.channel.startSample(sample.getLoopCount(), sample.getGain(), 0); + + if (!startSuccessful) { + if (internalErrors) + debugPrintln( + "JavaSoundMixer: Internal Error startSample for index " + + index + " failed"); + return JSSample.NULL_SAMPLE; + } + else { + if (debugFlag) + debugPrintln(" startSample worked, " + + "returning " + startSuccessful); + // NOTE: Set AuralParameters AFTER sound started + // Setting AuralParameters before you start sound doesn't work + if (!muted) { + if (auralParams.reverbDirty > 0) { + if (debugFlag) { + debugPrintln("startSample: reverb settings are:"); + debugPrintln(" coeff = "+ + auralParams.reflectionCoefficient + + ", delay = " + auralParams.reverbDelay + + ", order = " + auralParams.reverbOrder); + } + float delayTime = auralParams.reverbDelay * auralParams.rolloff; + calcReverb(sample); + } + // NOTE: it apprears that reverb has to be reset in + // JavaSound engine when sound re-started?? + // force reset of reverb parameters when sound is started + setReverb(sample); + } + return index; + } + } + + /* + * Overriden method from AudioEngine3D. + */ + public int stopSample(int index) { + // TODO: Rewrite this function + + if (debugFlag) + debugPrintln("JavaSoundMixer: STOPSample for index " + index); + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return -1; + + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + + boolean stopSuccessful = true; + stopSuccessful = thread.stopSample(sample); + + sample.channel.stopSample(); + + if (!stopSuccessful) { + if (internalErrors) + debugPrintln( "JavaSoundMixer: Internal Error stopSample(s) for index " + + index + " failed"); + return -1; + } + else { + // set fields in sample to reset for future start + sample.reset(); + if (debugFlag) + debugPrintln("JavaSoundMixer: stopSample for index " + + index + " worked, returning " + stopSuccessful); + return 0; + } + } + + /* + * Overriden method from AudioEngine3D. + */ + public void pauseSample(int index) { + if (debugFlag) + debugPrintln("JavaSoundMixer: PAUSESample for index " + index); + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + // check thread != null + thread.pauseSample(sample); + } + + /* + * Overriden method from AudioEngine3D. + */ + public void unpauseSample(int index) { + if (debugFlag) + debugPrintln("JavaSoundMixer: UNPAUSESample for index " + index); + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + thread.unpauseSample(sample); + } + + /* + * Force thread to update sample. + * Overriden method from AudioEngine3D. + */ + + public void updateSample(int index) { + if (debugFlag) + debugPrintln("JavaSoundMixer: UPDATESample for index " + index); + JSSample sample = null; + if ( ( (sample = (JSSample)getSample(index)) == null) || + thread == null ) + return; + + int soundType = sample.getSoundType(); + boolean muted = sample.getMuteFlag(); + + if (muted) { + if (soundType != AudioDevice3D.BACKGROUND_SOUND) + setFilter(index, false, Sound.NO_FILTER); + thread.muteSample(sample); + if (debugFlag) + debugPrintln(" Mute during update"); + } + else { + // If reverb parameters changed resend to audio device + if (auralParams.reverbDirty > 0) { + if (debugFlag) { + debugPrintln("updateSample: reverb settings are:"); + debugPrintln(" coeff = " + auralParams.reflectionCoefficient+ + ", delay = " + auralParams.reverbDelay + + ", order = " + auralParams.reverbOrder); + } + float delayTime = auralParams.reverbDelay * auralParams.rolloff; + calcReverb(sample); + } + // TODO: Only re-set reverb if values different + // For now force reset to ensure that reverb is currently correct + setReverb(sample); // ensure reverb is current/correct + + // TODO: For now sum left & rightGains for reverb gain + float reverbGain = 0.0f; + if (!muted && auralParams.reverbFlag) { + reverbGain = sample.getGain() * auralParams.reflectionCoefficient; + } + + sample.render(sample.getDirtyFlags(), getView(), auralParams); + + // filtering + if (soundType != AudioDevice3D.BACKGROUND_SOUND) + setFilter(index, sample.getFilterFlag(), sample.getFilterFreq()); + thread.setSampleGain(sample, auralParams); + thread.setSampleRate(sample, auralParams); + thread.setSampleDelay(sample, auralParams); + } + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void muteSample(int index) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + + if (debugFlag) + debugPrintln("JavaSoundMixer: muteSample"); + sample.setMuteFlag(true); + thread.muteSample(sample); + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public void unmuteSample(int index) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + + if (debugFlag) + debugPrintln("JavaSoundMixer: unmuteSample"); + sample.setMuteFlag(false); + + // since while mute the reverb type and state was not updated... + // Reverb has to be recalculated when sound is unmuted . + auralParams.reverbDirty = 0xFFFF; // force an update of reverb params + sample.setDirtyFlags(0xFFFF); // heavy weight forcing of gain/delay update + + // TODO: force an update of ALL parameters that could have changed + // while muting disabled... + + thread.unmuteSample(sample); + return; + } + + /* + * Overriden method from AudioEngine3D. + */ + public long getSampleDuration(int index) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return Sample.DURATION_UNKNOWN; + long duration; + + if (sample != null) + duration = sample.getDuration(); + else + duration = Sample.DURATION_UNKNOWN; + if (debugFlag) + debugPrintln(" return duration " + duration); + return duration; + } + + /* + * Overriden method from AudioEngine3D. + */ + public int getNumberOfChannelsUsed(int index) { + /* + * Calls same method with different signature containing the + * sample's mute flag passed as the 2nd parameter. + */ + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return 0; + else + return getNumberOfChannelsUsed(index, sample.getMuteFlag()); + } + + /** + * Overriden method from AudioEngine3D. + */ + public int getNumberOfChannelsUsed(int index, boolean muted) { + /* + * The JavaSoundMixer implementation uses THREE channels to render + * the stereo image of each Point and Cone Sounds: + * Two for rendering the right and left portions of the rendered + * spatialized sound image - panned hard right or left respectively. + * This implementation uses one channel to render Background sounds + * whether the sample is mono or stereo. + * + * TODO: When muted is implemented, that flag should be check + * so that zero is returned. + */ + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return 0; + + int soundType = sample.getSoundType(); + int dataType = sample.getDataType(); + + // TODO: for now positional Midi sound used only 1 sample + if (dataType == JSSample.STREAMING_MIDI_DATA || + dataType == JSSample.BUFFERED_MIDI_DATA) + return 1; + + if (soundType == BACKGROUND_SOUND) + return 1; + else // for Point and Cone sounds + return 3; + } + + /* + * Overriden method from AudioEngine3D. + */ + public long getStartTime(int index) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return 0L; + if (sample.channel == null) + return 0L; + return (long)sample.channel.startTime; + } + + /* + * Methods called during rendering + */ + void scaleSampleRate(int index, float scaleFactor) { + if (debugFlag) + debugPrintln("JavaSoundMixer: scaleSampleRate index " + + index + ", scale factor = " + scaleFactor); + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null || + thread == null) + return; + int dataType = sample.getDataType(); + if (debugFlag) + debugPrintln(" scaleSampleRate.dataType = " + dataType + + "using sample " + sample + " from samples[" + + index +"]"); + int soundType = sample.getSoundType(); + + if (dataType == JSSample.STREAMING_AUDIO_DATA || + dataType == JSSample.BUFFERED_AUDIO_DATA) { + thread.setSampleRate(sample, scaleFactor); + /********** + // TODO: + if (soundType != AudioDevice3D.BACKGROUND_SOUND) { + thread.setSampleRate( ((JSPositionalSample)sample).getSecondIndex(), + scaleFactor); + thread.setSampleRate(((JSPositionalSample)sample).getReverbIndex(), + scaleFactor); + } + **********/ + } + else if (dataType == JSSample.STREAMING_MIDI_DATA || + dataType == JSSample.BUFFERED_MIDI_DATA) { + thread.setSampleRate(sample, scaleFactor); + /********** + if (soundType != AudioDevice3D.BACKGROUND_SOUND) { + thread.setSampleRate(((JSPositionalSample)sample).getSecondIndex(), + scaleFactor); + thread.setSampleRate(((JSPositionalSample)sample).getReverbIndex(), + scaleFactor); + } + **********/ + } + else { + if (internalErrors) + debugPrintln( + "JavaSoundMixer: Internal Error scaleSampleRate dataType " + + dataType + " invalid"); + } + } + + /* + * Methods called during rendering + */ + void calcReverb(JSSample sample) { + /* + * Java Sound reverb parameters are a subset of Java 3D parameters + */ + int dataType = sample.getDataType(); + int soundType = sample.getSoundType(); + float decay = auralParams.decayTime; + float delay = auralParams.reverbDelay * auralParams.rolloff; + float reflection = auralParams.reflectionCoefficient; + int order = auralParams.reverbOrder; + /* + * Remember Coeff change is choosen over Order change if BOTH made + * otherwise the last one changed take precidence. + */ + if (auralParams.reflectionCoefficient == 0.0f || + auralParams.reverbCoefficient == 0.0f) + auralParams.reverbFlag = false; + else { + auralParams.reverbFlag = true; + if (order > 0) { + // clamp reverb decay time to order*delay + float clampedTime = order * delay; + if ( clampedTime < decay) + decay = clampedTime; + } + if (delay < 100.0f) { + // "small" reverberant space + if (decay <= 1500.0f) + auralParams.reverbType = 2; + else + auralParams.reverbType = 4; + } + else if (delay < 500.0f) { + // "medium" reverberant space + if (decay <= 1500.0f) + auralParams.reverbType = 3; + else + auralParams.reverbType = 6; + } + else { // delay >= 500.0f + // "large" reverberant space + if (decay <= 1500.0f) + auralParams.reverbType = 6; + else + auralParams.reverbType = 5; + } + } + + if (debugFlag) + debugPrintln("JavaSoundMixer: setReverb for " + + sample + ", type = " + auralParams.reverbType + ", flag = " + auralParams.reverbFlag); + + auralParams.reverbDirty = 0; // clear the attribute reverb dirty flags + } + + /* + * Interal method for setting reverb parameters called during rendering. + * This not called by SoundScheduler. + */ + void setReverb(JSSample sample) { + /* + * Only third sample of multisample sounds has reverb parameters set. + * For now, only positional and directional sounds are reverberated. + */ + int soundType = sample.getSoundType(); + int dataType = sample.getDataType(); + + // QUESTION: Should reverb be applied to background sounds? + if ( (soundType == AudioDevice3D.CONE_SOUND) || + (soundType == AudioDevice3D.POINT_SOUND) ) { + if (debugFlag) + debugPrintln("setReverb called with type, on = " + + auralParams.reverbType + ", " + auralParams.reverbFlag); + if (sample == null) + return; + JSPositionalSample posSample = (JSPositionalSample)sample; + if (posSample.channel == null) + return; + + /********** + // NOTE: no support for reverb channel yet... + int reverbIndex = posSample.getReverbIndex(); + **********/ + if (dataType == JSSample.STREAMING_AUDIO_DATA) { + JSStream stream = (JSStream)posSample.channel; + stream.setSampleReverb(auralParams.reverbType, auralParams.reverbFlag); + } + else if (dataType == JSSample.BUFFERED_AUDIO_DATA) { + JSClip clip = (JSClip)posSample.channel; + clip.setSampleReverb(auralParams.reverbType, auralParams.reverbFlag); + } + /********** + // TODO: + else if (dataType == JSSample.STREAMING_MIDI_DATA || + dataType == JSSample.BUFFERED_MIDI_DATA) { + JSMidi.setSampleReverb(reverbIndex, + auralParams.reverbType, auralParams.reverbFlag); + } + **********/ + else { + if (internalErrors) + debugPrintln( "JavaSoundMixer: Internal Error setReverb " + + "dataType " + dataType + " invalid"); + } + } + } + + // TEMPORARY: Override of method due to bug in Java Sound + public void setLoop(int index, int count) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + int dataType = sample.getDataType(); + + // WORKAROUND: + // Bug in Java Sound engine hangs when INFINITE_LOOP count + // for Audio Wave data. Leave count unchanged for Midi data. + if (dataType==JSSample.STREAMING_AUDIO_DATA || + dataType==JSSample.BUFFERED_AUDIO_DATA) { + if (count == Sound.INFINITE_LOOPS) { + // LoopCount of 'loop Infinitely' forced to largest positive int + count = 0x7FFFFFF; + } + } + super.setLoop(index, count); + return; + } + + // Perform device specific filtering + // Assumes that this is called for positional and directional sounds + // not background sounds, so there are at lease two samples assigned + // per sound. + // TODO: remove assumption from method + void setFilter(int index, boolean filterFlag, float filterFreq) { + JSPositionalSample posSample = null; + if ((posSample = (JSPositionalSample)getSample(index)) == null) + return; + if (posSample.channel == null) + return; + int dataType = posSample.getDataType(); + + // Filtering can NOT be performed on MIDI Songs + if (dataType == JSSample.STREAMING_MIDI_DATA || + dataType == JSSample.BUFFERED_MIDI_DATA) { + return; + } + + /**** + // TODO: multiple clips per channel + int secondIndex = posSample.getSecondIndex(); + *****/ + if (dataType == JSSample.BUFFERED_AUDIO_DATA) { + JSClip clip = (JSClip)posSample.channel; + clip.setSampleFiltering(filterFlag,filterFreq); + /***** + JSClip.setSampleFiltering(econdIndex, filterFlag, filterFreq); + ******/ + } + else { // dataType == JSSample.STREAMING_AUDIO_DATA + JSStream stream = (JSStream)posSample.channel; + stream.setSampleFiltering(filterFlag,filterFreq); + /***** + JSStream.setSampleFiltering(secondIndex, ilterFlag, filterFreq); + ******/ + } + // QUESTION: should reverb channel be filtered??? + + if (debugFlag) { + debugPrintln("JavaSoundMixer:setFilter " + + "of non-backgroundSound by (" + + filterFlag + ", " + filterFreq + ")"); + } + } + // + // Set overall gain for device + // @since Java 3D 1.3 + // + public void setGain(float scaleFactor) { + float oldDeviceGain = deviceGain; + float gainFactor = scaleFactor/oldDeviceGain; + // TODO: for each sample, change gain by gainFactor + deviceGain = scaleFactor; // set given scalefactor as new device gain + return; + } + + /* + * Set sample specific sample rate scale factor gain + * @since Java 3D 1.3 + */ + public void setRateScaleFactor(int index, float rateScaleFactor) { + JSSample sample = null; + if ((sample = (JSSample)getSample(index)) == null) + return; + sample.setRateScaleFactor(rateScaleFactor); + this.scaleSampleRate(index, rateScaleFactor); + } + + /** + * Pauses audio device engine without closing the device and associated + * threads. + * Causes all cached sounds to be paused and all streaming sounds to be + * stopped. + */ + public void pause() { + pause = PAUSE_PENDING; + // TODO: pause all sounds + return; + } + /** + * Resumes audio device engine (if previously paused) without reinitializing * the device. + * Causes all paused cached sounds to be resumed and all streaming sounds + * restarted. + */ + public void resume() { + pause = RESUME_PENDING; + // TODO: unpause all sounds + return; + } +} diff --git a/src/classes/share/com/sun/j3d/internal/BufferWrapper.java b/src/classes/share/com/sun/j3d/internal/BufferWrapper.java new file mode 100755 index 0000000..40370bc --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/BufferWrapper.java @@ -0,0 +1,186 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import javax.media.j3d.J3DBuffer; +import java.nio.Buffer; + +/** + * NIO Buffers are new in Java 1.4 but we need to run on 1.3 + * as well, so this class was created to hide the NIO classes + * from non-1.4 Java 3D users. + * + *

+ * NOTE: We no longer need to support JDK 1.3 as of the Java 3D 1.3.2 + * community source release on java.net. We should be able to get rid + * of this class. + */ + +public abstract class BufferWrapper { + + /** + * Value returned from getBufferType(), this indicates + * that the BufferWrapper contains a null buffer. + */ + public static final int TYPE_NULL = 0; + + /** + * Value returned from getBufferType(), this indicates + * that the BufferWrapper does not hold data of type + * byte, float, or double. + */ + public static final int TYPE_UNKNOWN = 1; + + /** + * Value returned from getBufferType(), this indicates + * that the BufferWrapper contains a java.nio.ByteBuffer. + */ + public static final int TYPE_BYTE = 2; + + /** + * Value returned from getBufferType(), this indicates + * that the BufferWrapper contains a java.nio.FloatBuffer. + */ + public static final int TYPE_FLOAT = 3; + + /** + * Value returned from getBufferType(), this indicates + * that the BufferWrapper contains a java.nio.DoubleBuffer. + */ + public static final int TYPE_DOUBLE = 4; + + /** + * Never used - this class is abstract. + */ + public BufferWrapper() { + } + + /** + * Must be implemented by sublasses. + */ + abstract Buffer getBuffer(); + + /** + * @return Buffer as object of type Object. + */ + public Object getBufferAsObject() { + return getBuffer(); + } + + // Wrapper for all relevant Buffer methods. + + /** + * @return This buffer's capacity (set at initialization in + * allocateDirect() ). + * @see ByteBufferWrapper#allocateDirect + */ + public int capacity() { + return getBuffer().capacity(); + } + + /** + * @return This buffer's limit. + */ + public int limit() { + return getBuffer().limit(); + } + + /** + * @return This buffer's position. + */ + public int position() { + return getBuffer().position(); + } + + /** + * Sets this buffer's position. + * @return This buffer. + */ + public BufferWrapper position(int newPosition){ + getBuffer().position(newPosition); + return this; + } + + /** + * Resets this buffer's position to the previously marked + * position. + * @return This buffer. + */ + public BufferWrapper rewind() { + getBuffer().rewind(); + return this; + } + + /** + * @return An integer indicating the type of data held in + * this buffer. + * @see #TYPE_NULL + * @see #TYPE_BYTE + * @see #TYPE_FLOAT + * @see #TYPE_DOUBLE + * @see #TYPE_UNKNOWN + */ + public static int getBufferType(J3DBuffer b) { + int bufferType; + Buffer buffer = b.getBuffer(); + + if (buffer == null) { + bufferType = TYPE_NULL; + } + else if (buffer instanceof java.nio.ByteBuffer) { + bufferType = TYPE_BYTE; + } + else if (buffer instanceof java.nio.FloatBuffer) { + bufferType = TYPE_FLOAT; + } + else if (buffer instanceof java.nio.DoubleBuffer) { + bufferType = TYPE_DOUBLE; + } + else { + bufferType = TYPE_UNKNOWN; + } + return bufferType; + } +} diff --git a/src/classes/share/com/sun/j3d/internal/ByteBufferWrapper.java b/src/classes/share/com/sun/j3d/internal/ByteBufferWrapper.java new file mode 100755 index 0000000..61f2c58 --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/ByteBufferWrapper.java @@ -0,0 +1,197 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import javax.media.j3d.J3DBuffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * NIO Buffers are new in Java 1.4 but we need to run on 1.3 + * as well, so this class was created to hide the NIO classes + * from non-1.4 Java 3D users. + * + *

+ * NOTE: We no longer need to support JDK 1.3 as of the Java 3D 1.3.2 + * community source release on java.net. We should be able to get rid + * of this class. + */ + +public class ByteBufferWrapper extends BufferWrapper { + + private ByteBuffer buffer = null; + + /** + * Constructor initializes buffer with a + * java.nio.ByteBuffer object. + */ + public ByteBufferWrapper(ByteBuffer buffer) { + this.buffer = buffer; + } + + /** + * Constructor initializes buffer with a + * javax.media.j3d.J3DBuffer object. + */ + public ByteBufferWrapper(J3DBuffer b) { + buffer = (ByteBuffer)(b.getBuffer()); + } + + /** + * Allocate a direct ByteBuffer with the given capacity. + * @return New ByteBufferWrapper containing the + * new buffer. + */ + public static ByteBufferWrapper allocateDirect(int capacity) { + ByteBuffer bb = ByteBuffer.allocateDirect(capacity); + return new ByteBufferWrapper(bb); + } + + /** + * Returns the java.nio.Buffer contained within this + * ByteBufferWrapper. + */ + public java.nio.Buffer getBuffer() { + return this.buffer; + } + + // Wrapper for all relevant ByteBuffer methods. + + /** + * @return A boolean indicating whether the java.nio.Buffer + * object contained within this ByteBuffer is direct or + * indirect. + */ + public boolean isDirect() { + return buffer.isDirect(); + } + + /** + * Reads the byte at this buffer's current position, + * and then increments the position. + */ + public byte get() { + return buffer.get(); + } + + /** + * Reads the byte at the given offset into the buffer. + */ + public byte get(int index) { + return buffer.get(index); + } + + /** + * Bulk get method. Transfers dst.length + * bytes from + * the buffer to the destination array and increments the + * buffer's position by dst.length. + */ + public ByteBufferWrapper get(byte[] dst) { + buffer.get(dst); + return this; + } + + /** + * Bulk get method. Transfers length bytes + * from the buffer starting at position offset into + * the destination array. + */ + public ByteBufferWrapper get(byte[] dst, int offset, int length) { + buffer.get(dst, offset, length); + return this; + } + + /** + * Returns the byte order of this buffer. + */ + public ByteOrderWrapper order() { + if ( buffer.order()==ByteOrder.BIG_ENDIAN ) return ByteOrderWrapper.BIG_ENDIAN; + else return ByteOrderWrapper.LITTLE_ENDIAN; + } + + /** + * Modifies this buffer's byte order. + */ + public ByteBufferWrapper order(ByteOrderWrapper bo) + { + if ( bo == ByteOrderWrapper.BIG_ENDIAN ) buffer.order( ByteOrder.BIG_ENDIAN ); + else buffer.order( ByteOrder.LITTLE_ENDIAN ); + return this; + } + + /** + * Creates a view of this ByteBufferWrapper as a + * FloatBufferWrapper. Uses the correct + */ + public FloatBufferWrapper asFloatBuffer() { + return new FloatBufferWrapper( buffer.asFloatBuffer() ); + } + + /** + * Creates a view of this ByteBufferWrapper as a + * DoubleBufferWrapper. + */ + public DoubleBufferWrapper asDoubleBuffer() { + return new DoubleBufferWrapper( buffer.asDoubleBuffer() ); + } + + /** + * Bulk put method. Transfers src.length + * bytes into the buffer at the current position. + */ + public ByteBufferWrapper put(byte[] src) { + buffer.put(src); + return this; + } + + /** + * Creates and returns a J3DBuffer object containing the + * buffer in this ByteBufferWrapper object. + */ + public J3DBuffer getJ3DBuffer() { + return new J3DBuffer( buffer ); + } +} diff --git a/src/classes/share/com/sun/j3d/internal/ByteOrderWrapper.java b/src/classes/share/com/sun/j3d/internal/ByteOrderWrapper.java new file mode 100755 index 0000000..bfaa373 --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/ByteOrderWrapper.java @@ -0,0 +1,101 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import javax.media.j3d.J3DBuffer; +import java.nio.Buffer; +import java.nio.ByteOrder; + +/** + * NIO Buffers are new in Java 1.4 but we need to run on 1.3 + * as well, so this class was created to hide the NIO classes + * from non-1.4 Java 3D users. + * + *

+ * Typesafe enum for byte orders. + * + *

+ * NOTE: We no longer need to support JDK 1.3 as of the Java 3D 1.3.2 + * community source release on java.net. We should be able to get rid + * of this class. + */ + +public final class ByteOrderWrapper { + + private final String enum_name; + + /** + * Private constructor is only called from static initializers + * in this class. + */ + private ByteOrderWrapper(String name) { + enum_name = name; + } + + /** + * Static initializer creates object of this type. + */ + public static final ByteOrderWrapper BIG_ENDIAN = + new ByteOrderWrapper("BIG_ENDIAN"); + + /** + * Static initializer creates object of this type. + */ + public static final ByteOrderWrapper LITTLE_ENDIAN = + new ByteOrderWrapper("LITTLE_ENDIAN"); + + public String toString() { + return enum_name; + } + + /** + * Returns the native byte order of the host system. + */ + public static ByteOrderWrapper nativeOrder() { + if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { + return ByteOrderWrapper.BIG_ENDIAN; + } else return ByteOrderWrapper.LITTLE_ENDIAN; + } +} diff --git a/src/classes/share/com/sun/j3d/internal/Distance.java b/src/classes/share/com/sun/j3d/internal/Distance.java new file mode 100644 index 0000000..f49659f --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/Distance.java @@ -0,0 +1,1096 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// -------------------------------------------------- +// +// Distance routines, ported from: +// +// Magic Software, Inc. +// http://www.magic-software.com +// http://www.wild-magic.com +// Copyright (c) 2004. All Rights Reserved +// +// The Wild Magic Library (WML) source code is supplied under the terms of +// the license agreement http://www.magic-software.com/License/WildMagic.pdf +// and may not be copied or disclosed except in accordance with the terms of +// that agreement. +// +// -------------------------------------------------- + +package com.sun.j3d.internal; + +import javax.vecmath.*; + +/** + * Utility class used to calculate distance. Contains static methods + * used by picking method to determine intersections. + */ + +public class Distance { + /* Threshold factor to determine if two lines are parallel */ + static final double FUZZ = 1E-5; + + /* Utility method, for easy switch between distance and squared distance */ + private static final double DIST (double in) { + // return Math.sqrt (Math.abs (in)); + return Math.abs (in); + } + + /** + * Minimum ray to segment distance. + * + * @param rayorig Origin of the ray + * @param raydir Direction of the ray + * @param segstart Segment start point + * @param segend Segment end point + * @return the square of the minimum distance from the ray to the segment + */ + static public double rayToSegment (Point3d rayorig, + Vector3d raydir, + Point3d segstart, + Point3d segend) { + return rayToSegment (rayorig, raydir, segstart, segend, null, null, null); + } + + /** + * Minimum ray to segment distance. Returns the square of the distance. + * + * @param rayorig Origin of the ray + * + * @param raydir Direction of the ray + * + * @param segstart Segment start point + * + * @param segend Segment end point + * + * @param rayint If non-null, will be filled with the coordinates of + * the point corresponding to the minimum distance on the ray. + * + * @param segint If non-null, will be filled with the coordinates of + * the point corresponding to the minimum distance on the segment. + * + * @param param An array of two doubles, will be filled with the + * parametric factors used to find the point of shortest distance on + * each primitive (ray = O +sD, with O=origin and + * D=direction). param[0] will contain the parameter for the ray, + * and param[1] the parameter for the segment. + * + * @return the square of the minimum distance from the ray to the + * segment + */ + static public double rayToSegment (Point3d rayorig, + Vector3d raydir, + Point3d segstart, + Point3d segend, + Point3d rayint, + Point3d segint, + double[] param) { + double s, t; + + Vector3d diff = new Vector3d(); + diff.sub (rayorig,segstart); + Vector3d segdir = new Vector3d(); + segdir.sub (segend, segstart); + /* + System.out.println (rayorig + "\n" + raydir + "\n" + segstart + "\n" + + segdir); + */ + double A = raydir.dot (raydir);//Dot(ray.m,ray.m); + double B = -raydir.dot (segdir);//-Dot(ray.m,seg.m); + double C = segdir.dot (segdir);//Dot(seg.m,seg.m); + double D = raydir.dot (diff);//Dot(ray.m,diff); + double E; // -Dot(seg.m,diff), defer until needed + double F = diff.dot (diff);//Dot(diff,diff); + double det = Math.abs(A*C-B*B); // A*C-B*B = |Cross(M0,M1)|^2 >= 0 + + double tmp; + + if (det >= FUZZ) { + // ray and segment are not parallel + E = -segdir.dot (diff);//-Dot(seg.m,diff); + s = B*E-C*D; + t = B*D-A*E; + + if (s >= 0) { + if (t >= 0) { + if (t <= det) { // region 0 + // minimum at interior points of ray and segment + double invDet = 1.0f/det; + s *= invDet; + t *= invDet; + if (rayint!=null) rayint.scaleAdd (s, raydir, rayorig); + if (segint!=null) segint.scaleAdd (t, segdir, segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(s*(A*s+B*t+2*D)+t*(B*s+C*t+2*E)+F); + } + else { // region 1 + + t = 1; + if (D >= 0) { + s = 0; + if (rayint!=null) rayint.set (rayorig); + if (segint!=null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + s = -D/A; + if (rayint!=null) rayint.scaleAdd (s, raydir, rayorig); + if (segint!=null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST((D+2*B)*s+C+2*E+F); + } + } + } + else { // region 5 + t = 0; + if (D >= 0) { + s = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + s = -D/A; + if (rayint != null) rayint.scaleAdd (s, raydir, rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + } + else { + if (t <= 0) { // region 4 + if (D < 0) { + s = -D/A; + t = 0; + if (rayint != null) rayint.scaleAdd (s, raydir, rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + else { + s = 0; + if (E >= 0) { + t = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.scaleAdd (t, segdir, segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + } + else if (t <= det) { // region 3 + s = 0; + if (E >= 0) { + t = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.scaleAdd (t, segdir, segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + else { // region 2 + tmp = B+D; + if (tmp < 0) { + s = -tmp/A; + t = 1; + if (rayint != null) rayint.scaleAdd (s, raydir, rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*s+C+2*E+F); + } + else { + s = 0; + if (E >= 0) { + t = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.scaleAdd (t, segdir, segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + } + } + } + else { + // ray and segment are parallel + if (B > 0) { + // opposite direction vectors + t = 0; + if (D >= 0) { + s = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + s = -D/A; + if (rayint != null) rayint.scaleAdd (s, raydir, rayorig); + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + else { + // same direction vectors + E = segdir.dot (diff);//-Dot(seg.m,diff); + t = 1; + tmp = B+D; + if (tmp >= 0) { + s = 0; + if (rayint != null) rayint.set (rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + s = -tmp/A; + if (rayint != null) rayint.scaleAdd (s, raydir, rayorig); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*s+C+2*E+F); + } + } + } + } + + /** + * Minimum ray to ray distance. Returns the square of the distance. + * + * @param ray0orig Origin of ray 0 + * @param ray0dir Direction of ray 0 + * @param ray1orig Origin of ray 1 + * @param ray1dir Direction of ray 1 + * @return the square of the minimum distance from the ray to the segment + */ + static public double rayToRay (Point3d ray0orig, + Vector3d ray0dir, + Point3d ray1orig, + Vector3d ray1dir) { + return rayToRay (ray0orig, ray0dir, ray1orig, ray1dir, null, null, null); + } + + /** + * Minimum ray to ray distance. Returns the square of the distance. + * + * @param ray0orig Origin of ray 0 + * + * @param ray0dir Direction of ray 0 + * + * @param ray1orig Origin of ray 1 + * + * @param ray1dir Direction of ray 1 + * + * @param ray0int If non-null, will be filled with the coordinates + * of the point corresponding to the minimum distance on ray 0. + * + * @param ray1int If non-null, will be filled with the coordinates + * of the point corresponding to the minimum distance on ray 1. + * + * @param param An array of two doubles, will be filled with the + * parametric factors used to find the point of shortest distance on + * each primitive (ray = O +sD, with O=origin and + * D=direction). param[0] will contain the parameter for ray0, and + * param[1] the parameter for ray1. + * + * @return the square of the minimum distance from the ray to the segment + */ + static public double rayToRay (Point3d ray0orig, + Vector3d ray0dir, + Point3d ray1orig, + Vector3d ray1dir, + Point3d ray0int, + Point3d ray1int, + double[] param) { + + double s, t; + + Vector3d diff = new Vector3d(); + diff.sub (ray0orig, ray1orig); + + double A = ray0dir.dot (ray0dir); //Dot(ray0.m,ray0.m); + double B = -ray0dir.dot (ray1dir); //-Dot(ray0.m,ray1.m); + double C = ray1dir.dot (ray1dir); //Dot(ray1.m,ray1.m); + double D = ray0dir.dot (diff); //Dot(ray0.m,diff); + double E; // -Dot(ray1.m,diff), defer until needed + double F = diff.dot (diff); //Dot(diff,diff); + double det = Math.abs(A*C-B*B); // A*C-B*B = |Cross(M0,M1)|^2 >= 0 + /* + System.out.println (ray0orig + "\n" + ray0dir + "\n" + + ray1orig + "\n" + ray1dir); + System.out.println (A + " " + B + " " + C + " " + D + " " + F + " " + det); + */ + if (det >= FUZZ) { + // rays are not parallel + E = -ray1dir.dot (diff); //-Dot(ray1.m,diff); + s = B*E-C*D; + t = B*D-A*E; + + if (s >= 0) { + if (t >= 0) { // region 0 (interior) + // minimum at two interior points of rays + double invDet = 1.0f/det; + s *= invDet; + t *= invDet; + if (ray0int != null) ray0int.scaleAdd (s, ray0dir, ray0orig); + if (ray1int != null) ray1int.scaleAdd (t, ray1dir, ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(s*(A*s+B*t+2*D)+t*(B*s+C*t+2*E)+F); + } + else { // region 3 (side) + t = 0; + if (D >= 0) { + s = 0; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + s = -D/A; + if (ray0int != null) ray0int.scaleAdd (s, ray0dir, ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + } + else { + if (t >= 0) { // region 1 (side) + s = 0; + if (E >= 0) { + t = 0; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + t = -E/C; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.scaleAdd (t, ray1dir, ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + else { // region 2 (corner) + if (D < 0) { + s = -D/A; + t = 0; + if (ray0int != null) ray0int.scaleAdd (s, ray0dir, ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + else { + s = 0; + if (E >= 0) { + t = 0; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + t = -E/C; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.scaleAdd (t, ray1dir, ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + } + } + } + else { + // rays are parallel + if (B > 0) { + // opposite direction vectors + t = 0; + if (D >= 0) { + s = 0; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + s = -D/A; + if (ray0int != null) ray0int.scaleAdd (s, ray0dir, ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + else { + // same direction vectors + if (D >= 0) { + E = ray1dir.dot (diff); //-Dot(ray1.m,diff); + s = 0; + t = -E/C; + if (ray0int != null) ray0int.set (ray0orig); + if (ray1int != null) ray1int.scaleAdd (t, ray1dir, ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + else { + s = -D/A; + t = 0; + if (ray0int != null) ray0int.scaleAdd (s, ray0dir, ray0orig); + if (ray1int != null) ray1int.set (ray1orig); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + } + } + + /** + * Minimum pt to ray distance. Returns the square of the distance. + * @param pt The point + * @param rayorig Origin of the ray + * @param raydir Direction of the ray + * @return the square of the minimum distance between the point and the ray + */ + static public double pointToRay (Point3d pt, + Point3d rayorig, + Vector3d raydir) { + return pointToRay (pt, rayorig, raydir, null, null); + } + + /** + * Minimum pt to ray distance. Returns the square of the distance. + * + * @param pt The point + * + * @param rayorig Origin of the ray + * + * @param raydir Direction of the ray + * + * @param rayint If non-null, will be filled with the coordinates of + * the point corresponding to the minimum distance on the ray. + * + * @param param An array of one double, will be filled with the + * parametric factors used to find the point of shortest distance on + * the ray (ray = O +sD, with O=origin and D=direction). param[0] + * will contain the parameter for the ray. + * + * @return the square of the minimum distance between the point and the ray + */ + static public double pointToRay (Point3d pt, + Point3d rayorig, + Vector3d raydir, + Point3d rayint, + double[] param) { + + double t; + + Vector3d diff = new Vector3d(); + diff.sub (pt, rayorig); + t = raydir.dot (diff); //Dot(ray.m,diff); + + if (t <= 0.0) { + t = 0.0; // behind start of ray + if (rayint != null) rayint.set (rayorig); + if (param != null) { param[0] = t; } + } else { + t /= raydir.dot (raydir); //Dot(ray.m,ray.m); + diff.scaleAdd (-t, raydir, diff); // diff = diff - t*ray.m; + if (rayint != null) rayint.scaleAdd (t, raydir, rayorig); + if (param != null) { param[0] = t; } + } + return diff.dot(diff); + } + + /** + * Minimum pt to segment distance. Returns the square of the distance. + */ + static public double pointToSegment (Point3d pt, + Point3d segstart, + Point3d segend) { + return pointToSegment (pt, segstart, segend, null, null); + } + + /** + * Minimum pt to segment distance. Returns the square of the distance. + */ + static public double pointToSegment (Point3d pt, + Point3d segstart, + Point3d segend, + Point3d segint, + double[] param) { + + double t; + Vector3d segdir = new Vector3d (); + segdir.sub (segend, segstart); + Vector3d diff = new Vector3d(); + diff.sub (pt,segstart); + t = segdir.dot (diff); //Dot(seg.m,diff); + + if (t <= 0.0) { + t = 0.0f; + if (segint != null) segint.set (segstart); + if (param != null) { param[0] = t; } + } + else { + double mDotm = segdir.dot (segdir); //Dot(seg.m,seg.m); + if (t >= mDotm) { + t = 1.0f; + diff.sub (segdir); + if (segint != null) segint.set (segend); + if (param != null) { param[0] = t; } + } + else { + t /= mDotm; + diff.scaleAdd (-t, segdir, diff); //diff = diff - t*seg.m; + if (segint != null) segint.scaleAdd (t, segdir, segstart); + if (param != null) { param[0] = t; } + } + } + return diff.dot(diff); //DIST(diff); + } + + + /** + * Minimum segment to segment distance. Returns the square of the distance. + * @param seg0start the start of segment 0 + * @param seg0end the end of segment 0 + * @param seg1start the start of segment 1 + * @param seg1end the end of segment 1 + * @return the square of the minimum distance from segment to segment + */ + static public double segmentToSegment (Point3d seg0start, + Point3d seg0end, + Point3d seg1start, + Point3d seg1end) { + return segmentToSegment (seg0start, seg0end, seg1start, seg1end, + null, null, null); + } + + /** + * Minimum segment to segment distance. Returns the square of the distance. + * + * @param seg0start the start of segment 0 + * + * @param seg0end the end of segment 0 + * + * @param seg1start the start of segment 1 + * + * @param seg1end the end of segment 1 + * + * @param seg0int If non-null, will be filled with the coordinates + * of the point corresponding to the minimum distance on segment 0. + * + * @param seg1int If non-null, will be filled with the coordinates + * of the point corresponding to the minimum distance on segment 1. + * + * @param param An array of two doubles, will be filled with the + * parametric factors used to find the point of shortest distance on + * each primitive (segment = O +sD, with O=origin and + * D=direction). param[0] will contain the parameter for segment 0, + * and param[1] the parameter for segment 1. + * + * @return the square of the minimum distance from segment to segment + */ + static public double segmentToSegment (Point3d seg0start, + Point3d seg0end, + Point3d seg1start, + Point3d seg1end, + Point3d seg0int, + Point3d seg1int, + double[] param) { + double s,t; + + Vector3d diff = new Vector3d(); + diff.sub (seg0start,seg1start); + + Vector3d seg0dir = new Vector3d(); + seg0dir.sub (seg0end, seg0start); + Vector3d seg1dir = new Vector3d(); + seg1dir.sub (seg1end, seg1start); + + double A = seg0dir.dot (seg0dir); //Dot(seg0dir,seg0dir); + double B = -seg0dir.dot (seg1dir); //-Dot(seg0dir,seg1dir); + double C = seg1dir.dot (seg1dir); //Dot(seg1dir,seg1dir); + double D = seg0dir.dot (diff); //Dot(seg0dir,diff); + double E; // -Dot(seg1dir,diff), defer until needed + double F = diff.dot (diff); //Dot(diff,diff); + double det = Math.abs(A*C-B*B); // A*C-B*B = |Cross(M0,M1)|^2 >= 0 + + double tmp; + + if (det >= FUZZ) { + // line segments are not parallel + E = -seg1dir.dot (diff); //-Dot(seg1dir,diff); + s = B*E-C*D; + t = B*D-A*E; + + if (s >= 0) { + if (s <= det) { + if (t >= 0) { + if (t <= det) { // region 0 (interior) + // minimum at two interior points of 3D lines + double invDet = 1.0f/det; + s *= invDet; + t *= invDet; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(s*(A*s+B*t+2*D)+t*(B*s+C*t+2*E)+F); + } + else { // region 3 (side) + t = 1; + tmp = B+D; + if (tmp >= 0) { + s = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else if (-tmp >= A) { + s = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(E+tmp)); + } + else { + s = -tmp/A; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*s+C+2*E+F); + } + } + } + else { // region 7 (side) + t = 0; + if (D >= 0) { + s = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-D >= A) { + s = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else { + s = -D/A; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + } + else { + if (t >= 0) { + if (t <= det) { // region 1 (side) + s = 1; + tmp = B+E; + if (tmp >= 0) { + t = 0; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else if (-tmp >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(D+tmp)); + } + else { + t = -tmp/C; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*t+A+2*D+F); + } + } + else { // region 2 (corner) + tmp = B+D; + if (-tmp <= A) { + t = 1; + if (tmp >= 0) { + s = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + s = -tmp/A; + if (seg0int!=null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int!=null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*s+C+2*E+F); + } + } + else { + s = 1; + tmp = B+E; + if (tmp >= 0) { + t = 0; + if (seg0int!=null) seg0int.set (seg0end); + if (seg1int!=null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else if (-tmp >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(D+tmp)); + } + else { + t = -tmp/C; + if (seg0int!=null) seg0int.set (seg0end); + if (seg1int!=null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*t+A+2*D+F); + } + } + } + } + else { // region 8 (corner) + if (-D < A) { + t = 0; + if (D >= 0) { + s = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else { + s = -D/A; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + else { + s = 1; + tmp = B+E; + if (tmp >= 0) { + t = 0; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else if (-tmp >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(D+tmp)); + } + else { + t = -tmp/C; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*t+A+2*D+F); + } + } + } + } + } + else { + if (t >= 0) { + if (t <= det) { // region 5 (side) + s = 0; + if (E >= 0) { + t = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + else { // region 4 (corner) + tmp = B+D; + if (tmp < 0) { + t = 1; + if (-tmp >= A) { + s = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(E+tmp)); + } + else { + s = -tmp/A; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(tmp*s+C+2*E+F); + } + } + else { + s = 0; + if (E >= 0) { + t = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + } + } + else { // region 6 (corner) + if (D < 0) { + t = 0; + if (-D >= A) { + s = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else { + s = -D/A; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + } + else { + s = 0; + if (E >= 0) { + t = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-E >= C) { + t = 1; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -E/C; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(E*t+F); + } + } + } + } + } + else { + // line segments are parallel + if (B > 0) { + // direction vectors form an obtuse angle + if (D >= 0) { + s = 0; + t = 0; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F); + } + else if (-D <= A) { + s = -D/A; + t = 0; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + else { + E = -seg1dir.dot (diff); //-Dot(seg1dir,diff); + s = 1; + tmp = A+D; + if (-tmp >= B) { + t = 1; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+C+F+2*(B+D+E)); + } + else { + t = -tmp/B; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F+t*(C*t+2*(B+E))); + } + } + } + else { + // direction vectors form an acute angle + if (-D >= A) { + s = 1; + t = 0; + if (seg0int != null) seg0int.set (seg0end); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(A+2*D+F); + } + else if (D <= 0) { + s = -D/A; + t = 0; + if (seg0int != null) seg0int.scaleAdd (s, seg0dir, seg0start); + if (seg1int != null) seg1int.set (seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(D*s+F); + } + else { + E = -seg1dir.dot (diff); //-Dot(seg1dir,diff); + s = 0; + if (D >= -B) { + t = 1; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.set (seg1end); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(C+2*E+F); + } + else { + t = -D/B; + if (seg0int != null) seg0int.set (seg0start); + if (seg1int != null) seg1int.scaleAdd (t, seg1dir, seg1start); + if (param != null) { param[0] = s; param[1] = t; } + return DIST(F+t*(2*E+C*t)); + } + } + } + } + } +} + + + + diff --git a/src/classes/share/com/sun/j3d/internal/DoubleBufferWrapper.java b/src/classes/share/com/sun/j3d/internal/DoubleBufferWrapper.java new file mode 100755 index 0000000..c6f011d --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/DoubleBufferWrapper.java @@ -0,0 +1,153 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import javax.media.j3d.J3DBuffer; +import java.nio.DoubleBuffer; + +/** + * NIO Buffers are new in Java 1.4 but we need to run on 1.3 + * as well, so this class was created to hide the NIO classes + * from non-1.4 Java 3D users. + * + *

+ * NOTE: We no longer need to support JDK 1.3 as of the Java 3D 1.3.2 + * community source release on java.net. We should be able to get rid + * of this class. + */ + +public class DoubleBufferWrapper extends BufferWrapper { + + private DoubleBuffer buffer = null; + + /** + * Constructor initializes buffer with a + * java.nio.DoubleBuffer object. + */ + public DoubleBufferWrapper(DoubleBuffer buffer) { + this.buffer = buffer; + } + + /** + * Constructor initializes buffer with a + * javax.media.j3d.J3DBuffer object. + */ + public DoubleBufferWrapper(J3DBuffer b) { + buffer = (DoubleBuffer)(b.getBuffer()); + } + + /** + * Returns the java.nio.Buffer contained within this + * DoubleBufferWrapper. + */ + public java.nio.Buffer getBuffer() { + return this.buffer; + } + + // Wrapper for all relevant DoubleBuffer methods. + + /** + * @return A boolean indicating whether the java.nio.Buffer + * object contained within this DoubleBuffer is direct or + * indirect. + */ + public boolean isDirect() { + return buffer.isDirect(); + } + + /** + * Reads the double at this buffer's current position, + * and then increments the position. + */ + public double get() { + return buffer.get(); + } + + /** + * Reads the double at the given offset into the buffer. + */ + public double get(int index) { + return buffer.get(index); + } + + /** + * Bulk get method. Transfers dst.length + * doubles from + * the buffer to the destination array and increments the + * buffer's position by dst.length. + */ + public DoubleBufferWrapper get(double[] dst) { + buffer.get(dst); + return this; + } + + /** + * Bulk get method. Transfers length doubles + * from the buffer starting at position offset into + * the destination array. + */ + public DoubleBufferWrapper get(double[] dst, int offset, int length){ + buffer.get(dst, offset, length); + return this; + } + + /** + * Bulk put method. Transfers src.length + * doubles into the buffer at the current position. + */ + public DoubleBufferWrapper put(double[] src) { + buffer.put(src); + return this; + } + + /** + * Creates and returns a J3DBuffer object containing the + * buffer in this DoubleBufferWrapper object. + */ + public J3DBuffer getJ3DBuffer() { + return new J3DBuffer( buffer ); + } + +} diff --git a/src/classes/share/com/sun/j3d/internal/FastVector.java b/src/classes/share/com/sun/j3d/internal/FastVector.java new file mode 100644 index 0000000..b3ed4c2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/FastVector.java @@ -0,0 +1,143 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +/** + * The FastVector object is a growable array of ints. It's much faster + * than the Java Vector class because it isn't synchronized. This class + * was created because it is needed in several places by graphics + * utilities. + */ +public class FastVector { + + private int data[]; + private int capacity; + private int increment; + private int size; + + /** + * Add an element to the end of the array. + */ + public void addElement(int element) + { + if (size >= capacity) { + capacity += (increment == 0) ? capacity : increment; + int newData[] = new int[capacity]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + data[size++] = element; + } // End of addElement + + + + /** + * Get number of ints currently stored in the array; + */ + public int getSize() + { + return size; + } // End of getSize + + + + /** + * Get access to array data + */ + public int[] getData() + { + return data; + } // End of getData + + + + /** + * Constructor. + * @param initialCapacity Number of ints the object can hold + * without reallocating the array. + * @param capacityIncrement Once the array has grown beyond + * its capacity, how much larger the reallocated array should be. + */ + public FastVector(int initialCapacity, int capacityIncrement) + { + data = new int[initialCapacity]; + capacity = initialCapacity; + increment = capacityIncrement; + size = 0; + } // End of FastVector(int, int) + + + + /** + * Constructor. + * When the array runs out of space, its size is doubled. + * @param initialCapacity Number of ints the object can hold + * without reallocating the array. + */ + public FastVector(int initialCapacity) + { + data = new int[initialCapacity]; + capacity = initialCapacity; + increment = 0; + size = 0; + } // End of FastVector(int) + + + + /** + * Constructor. + * The array is constructed with initial capacity of one integer. + * When the array runs out of space, its size is doubled. + */ + public FastVector() + { + data = new int[1]; + capacity = 1; + increment = 0; + size = 0; + } // End of FastVector() +} // End of class FastVector + +// End of file FastVector.java diff --git a/src/classes/share/com/sun/j3d/internal/FloatBufferWrapper.java b/src/classes/share/com/sun/j3d/internal/FloatBufferWrapper.java new file mode 100755 index 0000000..8868901 --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/FloatBufferWrapper.java @@ -0,0 +1,152 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import javax.media.j3d.J3DBuffer; +import java.nio.FloatBuffer; + +/** + * NIO Buffers are new in Java 1.4 but we need to run on 1.3 + * as well, so this class was created to hide the NIO classes + * from non-1.4 Java 3D users. + * + *

+ * NOTE: We no longer need to support JDK 1.3 as of the Java 3D 1.3.2 + * community source release on java.net. We should be able to get rid + * of this class. + */ + +public class FloatBufferWrapper extends BufferWrapper { + + private FloatBuffer buffer = null; + + /** + * Constructor initializes buffer with a + * java.nio.FloatBuffer object. + */ + public FloatBufferWrapper(FloatBuffer buffer) { + this.buffer = buffer; + } + + /** + * Constructor initializes buffer with a + * javax.media.j3d.J3DBuffer object. + */ + public FloatBufferWrapper(javax.media.j3d.J3DBuffer b) { + this.buffer = (FloatBuffer)(b.getBuffer()); + } + + /** + * Returns the java.nio.Buffer contained within this + * FloatBufferWrapper. + */ + public java.nio.Buffer getBuffer() { + return this.buffer; + } + + // Wrapper for all relevant FloatBuffer methods. + + /** + * @return A boolean indicating whether the java.nio.Buffer + * object contained within this FloatBuffer is direct or + * indirect. + */ + public boolean isDirect() { + return buffer.isDirect(); + } + + /** + * Reads the float at this buffer's current position, + * and then increments the position. + */ + public float get() { + return buffer.get(); + } + + /** + * Reads the float at the given offset into the buffer. + */ + public float get(int index) { + return buffer.get(index); + } + + /** + * Bulk get method. Transfers dst.length + * floats from + * the buffer to the destination array and increments the + * buffer's position by dst.length. + */ + public FloatBufferWrapper get(float[] dst) { + buffer.get(dst); + return this; + } + + /** + * Bulk get method. Transfers length floats + * from the buffer starting at position offset into + * the destination array. + */ + public FloatBufferWrapper get(float[] dst, int offset, int length){ + buffer.get(dst, offset, length); + return this; + } + + /** + * Bulk put method. Transfers src.length + * floats into the buffer at the current position. + */ + public FloatBufferWrapper put(float[] src) { + buffer.put(src); + return this; + } + + /** + * Creates and returns a J3DBuffer object containing the + * buffer in this FloatBufferWrapper object. + */ + public J3DBuffer getJ3DBuffer() { + return new J3DBuffer( buffer ); + } +} diff --git a/src/classes/share/com/sun/j3d/internal/J3dUtilsI18N.java b/src/classes/share/com/sun/j3d/internal/J3dUtilsI18N.java new file mode 100644 index 0000000..a61c43a --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/J3dUtilsI18N.java @@ -0,0 +1,63 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import java.io.*; +import java.util.*; + + +public class J3dUtilsI18N { + static public String getString(String key) { + String s; + try { + s = (String) ResourceBundle.getBundle("com.sun.j3d.ExceptionStrings").getString(key); + } + catch (MissingResourceException e) { + System.err.println("J3dUtilsI18N: Error looking up: " + key); + s = key; + } + return s; + } +} diff --git a/src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java b/src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java new file mode 100644 index 0000000..457968a --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/UtilFreelistManager.java @@ -0,0 +1,98 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + + +public class UtilFreelistManager { + + private static final boolean DEBUG = false; + + // constants that represent the freelists managed by the Manager + public static final int VECTOR3D = 0; + public static final int POINT3D = 1; + public static final int PICKRESULT = 2; + public static final int MAXINT = 2; + + // what list we are going to shrink next + private static int currlist = 0; + + // the freelists managed by the manager + public static UtilMemoryFreelist vector3dFreelist = new UtilMemoryFreelist("javax.vecmath.Vector3d"); + public static UtilMemoryFreelist point3dFreelist = new UtilMemoryFreelist("javax.vecmath.Point3d"); + public static UtilMemoryFreelist pickResultFreelist = new UtilMemoryFreelist("com.sun.j3d.utils.picking.PickResult"); + + +// static MemoryFreeList[] freelist = new MemoryFreeList[MAXINT+1]; + +// static void createFreeLists() { +// freelist[VECTOR3D] = new MemoryFreeList("javax.vecmath.Vector3d"); +// freelist[POINT3D] = new MemoryFreeList("javax.vecmath.Point3d"); +// } + + +// // see if the current list can be shrunk +// static void manageLists() { +// // System.out.println("manageLists"); +// if (freelist[currlist] != null) { +// freelist[currlist].shrink(); +// } + +// currlist++; +// if (currlist > MAXINT) currlist = 0; +// } + +// // return the freelist specified by the list param +// static MemoryFreeList getFreeList(int list) { +// if (list < 0 || list > MAXINT) { +// if (DEBUG) System.out.println("illegal list"); +// return null; +// } +// else { +// return freelist[list]; +// } +// } + + +} diff --git a/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java b/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java new file mode 100644 index 0000000..3f559b7 --- /dev/null +++ b/src/classes/share/com/sun/j3d/internal/UtilMemoryFreelist.java @@ -0,0 +1,292 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.internal; + +import java.util.*; + +// this class must be synchronized because different threads may try to access +// the freelists +public class UtilMemoryFreelist { // extends AbstractList { + + // never go smaller than the initial capacity + ArrayList elementData = null; + int size = 0; + int currBlockSize = 10; + Object[] currBlock = null; + int currBlockIndex = 0; + int spaceUsed = 0; + int numBlocks = 0; + int capacity = 0; + int minBlockSize = 0; + boolean justShrunk = false; + int initcap = 10; + + // the minimum size since the last shrink + int minSize = 0; + + Class c = null; + + public UtilMemoryFreelist(String className) { + this(className, 10); + } + + public UtilMemoryFreelist(String className, int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException ("Illegal Capacity: " + + initialCapacity); + } + + try { + c = Class.forName(className); + } + catch (Exception e) { +// System.out.println(e); + } + + initcap = initialCapacity; + currBlockSize = initialCapacity; + minBlockSize = currBlockSize; + elementData = new ArrayList(); + // add the first block of memory to the arraylist + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks++; + capacity += currBlockSize; + } + + public UtilMemoryFreelist(String className, Collection collection) { + try { + c = Class.forName(className); + } + catch (Exception e) { +// System.out.println(e); + } + size = collection.size(); + initcap = size; + currBlockSize = size; + minBlockSize = currBlockSize; + elementData = new ArrayList(); + currBlock = new Object[currBlockSize]; + collection.toArray(currBlock); + elementData.add(currBlock); + numBlocks++; + capacity += currBlockSize; + spaceUsed = size; + } + + public synchronized int size() { + return size; + } + + + public synchronized boolean add(Object o) { + if (justShrunk) { + // empty some space out in the current block instead of + // adding this message + if ((currBlockSize/2) < spaceUsed) { + size -= (spaceUsed - (currBlockSize/2)); + spaceUsed = (currBlockSize/2); + Arrays.fill(currBlock, spaceUsed, currBlockSize-1, null); + } + justShrunk = false; + return false; + } + else { + ensureCapacity(size+1); + + // check to see if the whole block is used and if so, reset the + // current block +// System.out.println("spaceUsed = " + spaceUsed + " currBlockSize = " + +// currBlockSize + " currBlockIndex = " + +// currBlockIndex + " currBlock = " + currBlock); + if ((currBlockIndex == -1) || (spaceUsed >= currBlockSize)) { + currBlockIndex++; + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + spaceUsed = 0; + } + int index = spaceUsed++; + currBlock[index] = o; + size++; + + return true; + } + } + + private synchronized Object removeLastElement() { +// System.out.println("removeLastElement: size = " + size); + int index = --spaceUsed; +// System.out.println("index = " + index); + Object elm = currBlock[index]; + currBlock[index] = null; + size--; + + // see if this block is empty now, and if it is set the previous + // block to the current block + if (spaceUsed == 0) { + currBlockIndex--; + if (currBlockIndex < 0) { + currBlock = null; + currBlockSize = 0; + } + else { + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + } + spaceUsed = currBlockSize; + } + + return elm; + } + + + public synchronized void shrink() { +// System.out.println("shrink size = " + size + " minSize = " + +// minSize); + if ((minSize > minBlockSize) && (numBlocks > 1)) { + justShrunk = true; + +// System.out.println("removing a block"); +// Runtime r = Runtime.getRuntime(); +// r.gc(); +// System.out.println("numBlocks = " + numBlocks + " size = " + size); +// System.out.println("free memory before shrink: " + r.freeMemory()); + + // remove the last block + Object[] block = (Object[])elementData.remove(numBlocks-1); + numBlocks--; + capacity -= block.length; + + // we only need to do this if the block removed was the current + // block. otherwise we just removed a null block. + if (numBlocks == currBlockIndex) { + size -= spaceUsed; + // set the current block to the last one + currBlockIndex = numBlocks-1; + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + + spaceUsed = currBlockSize; + + } + +// r.gc(); +// System.out.println("free memory after shrink: " + r.freeMemory()); +// System.out.println("numBlocks = " + numBlocks + " size = " + size); + } + else { + justShrunk = false; + } + minSize = size; + } + + public synchronized void ensureCapacity(int minCapacity) { +// System.out.println("ensureCapacity: size = " + size + " capacity: " + +// elementData.length); +// System.out.println("minCapacity = " + minCapacity + " capacity = " +// + capacity); + + if (minCapacity > capacity) { +// System.out.println("adding a block: numBlocks = " + numBlocks); + int lastBlockSize = + ((Object[])elementData.get(numBlocks-1)).length; + int prevBlockSize = 0; + if (numBlocks > 1) { + prevBlockSize = + ((Object[])elementData.get(numBlocks-2)).length; + } + currBlockSize = lastBlockSize + prevBlockSize; + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks++; + currBlockIndex++; + capacity += currBlockSize; + // there is nothing used in this block yet + spaceUsed = 0; + } + } + + synchronized void rangeCheck(int index) { + if (index >= size || index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + + ", Size: " + size); + } + } + + public synchronized void clear() { +// System.out.println("clear"); + elementData.clear(); + + // put an empty block in + currBlockSize = initcap; + minBlockSize = currBlockSize; + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks = 1; + capacity = currBlockSize; + spaceUsed = 0; + size = 0; + currBlockIndex = 0; + justShrunk = false; + } + + public synchronized Object getObject() { + if (size > 0) { + return removeLastElement(); + } + else { + try { + return c.newInstance(); + } + catch (Exception e) { +// System.out.println("caught exception"); +// System.out.print(e); + return null; + } + } + } + +} + diff --git a/src/classes/share/com/sun/j3d/loaders/IncorrectFormatException.java b/src/classes/share/com/sun/j3d/loaders/IncorrectFormatException.java new file mode 100644 index 0000000..3ab53d8 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/IncorrectFormatException.java @@ -0,0 +1,62 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + + +/** + * Exception used to indicate that a file of the incorrect + * type was passed to a loader. + */ +public class IncorrectFormatException extends RuntimeException { + + public IncorrectFormatException() { + super(); + } + + public IncorrectFormatException(String s) { + super(s); + } +} + diff --git a/src/classes/share/com/sun/j3d/loaders/Loader.java b/src/classes/share/com/sun/j3d/loaders/Loader.java new file mode 100644 index 0000000..aeab387 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/Loader.java @@ -0,0 +1,176 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + +import java.net.URL; +import java.io.Reader; +import java.io.FileNotFoundException; + +/** + * The Loader interface is used to specify the location + * and elements of a file format to load. + * The interface is used to give loaders of various + * file formats a common public interface. Ideally + * the Scene interface will be implemented to give + * the user a consistent interface to extract the + * data. + * + * @see com.sun.j3d.loaders.Scene + */ +public interface Loader { + + // These are the values to be used in constructing the + // load flags for the loader. Users should OR the selected + // values together to construct an aggregate flag integer + // (see the setFlags() method). Users wishing to load all + // data in a file should use the LOAD_ALL specifier. + + /** This flag enables the loading of light objects into the scene.*/ + public static final int LOAD_LIGHT_NODES = 1; + + /** This flag enables the loading of fog objects into the scene.*/ + public static final int LOAD_FOG_NODES = 2; + + /** This flag enables the loading of background objects into the scene.*/ + public static final int LOAD_BACKGROUND_NODES = 4; + + /** This flag enables the loading of behaviors into the scene.*/ + public static final int LOAD_BEHAVIOR_NODES = 8; + + /** This flag enables the loading of view (camera) objects into + * the scene.*/ + public static final int LOAD_VIEW_GROUPS = 16; + + /** This flag enables the loading of sound objects into the scene.*/ + public static final int LOAD_SOUND_NODES = 32; + + /** This flag enables the loading of all objects into the scene.*/ + public static final int LOAD_ALL = 0xffffffff; + + + // Loading methods + + /** + * This method loads the named file and returns the Scene + * containing the scene. Any data files referenced by this + * file should be located in the same place as the named file; + * otherwise users should specify an alternate base path with + * the setBasePath(String) method. + */ + public Scene load(String fileName) throws FileNotFoundException, + IncorrectFormatException, ParsingErrorException; + + /** + * This method loads the named file and returns the Scene + * containing the scene. Any data files referenced by the Reader + * should be located in the same place as the named file; otherwise, + * users should specify an alternate base path with the setBaseUrl(URL) + * method. + */ + public Scene load(URL url) throws FileNotFoundException, + IncorrectFormatException, ParsingErrorException; + + /** + * This method loads the Reader and returns the Scene + * containing the scene. Any data files referenced by the Reader should + * be located in the user's current working directory. + */ + public Scene load(Reader reader) + throws FileNotFoundException, IncorrectFormatException, + ParsingErrorException; + + + // Variable get/set methods + + /** + * This method sets the base URL name for data files associated with + * the file passed into the load(URL) method. + * The basePath should be null by default, which is an indicator + * to the loader that it should look for any associated files starting + * from the same directory as the file passed into the load(URL) method. + */ + public void setBaseUrl(URL url); + + /** + * This method sets the base path name for data files associated with + * the file passed into the load(String) method. + * The basePath should be null by default, which is an indicator + * to the loader that it should look for any associated files starting + * from the same directory as the file passed into the load(String) + * method. + */ + public void setBasePath(String pathName); + + /** + * Returns the current base URL setting. By default this is null, + * implying the loader should look for associated files starting + * from the same directory as the file passed into the load(URL) method. + */ + public URL getBaseUrl(); + + /** + * Returns the current base path setting. By default this is null, + * implying the loader should look for associated files starting + * from the same directory as the file passed into the load(String) + * method. + */ + public String getBasePath(); + + /** + * This method sets the load flags for the file. The flags should + * equal 0 by default (which tells the loader to only load geometry). + * To enable the loading of any particular scene elements, pass + * in a logical OR of the LOAD values specified above. + */ + public void setFlags(int flags); + + /** + * Returns the current loading flags setting. + */ + public int getFlags(); +} + + + diff --git a/src/classes/share/com/sun/j3d/loaders/LoaderBase.java b/src/classes/share/com/sun/j3d/loaders/LoaderBase.java new file mode 100644 index 0000000..ba2d92e --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/LoaderBase.java @@ -0,0 +1,147 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + +import java.net.URL; +import java.io.Reader; + +/** + * This class implements the Loader interface. To use + * a file loader would extend this class. + */ +public abstract class LoaderBase implements Loader { + + /** Stores the types of objects that the user wishes to load.*/ + protected int loadFlags = 0; + + /** Stores the baseUrl for data files associated with the URL + * passed into load(URL).*/ + protected URL baseUrl = null; + + /** Stores the basePath for data files associated with the file + * passed into load(String).*/ + protected String basePath = null; + + // Constructors + + /** + * Constructs a Loader with default values for all variables. + */ + public LoaderBase() { + } + + /** + * Constructs a Loader with the specified flags word. + */ + public LoaderBase(int flags) { + loadFlags = flags; + } + + + // Variable get/set methods + + /** + * This method sets the base URL name for data files associated with + * the file. The baseUrl should be null by default, which is an indicator + * to the loader that it should look for any associated files starting + * from the same place as the URL passed into the load(URL) method. + * Note: Users of setBaseUrl() would then use load(URL) + * as opposed to load(String). + */ + public void setBaseUrl(URL url) { + baseUrl = url; + } + + /** + * This method sets the base path name for data files associated with + * the file. The basePath should be null by default, which is an indicator + * to the loader that it should look for any associated files starting + * from the same directory as the file passed into the load(String) + * method. + * Note: Users of setBasePath() would then use load(String) + * as opposed to load(URL). + */ + public void setBasePath(String pathName) { + basePath = pathName; + } + + /** + * Returns the current base URL setting. + */ + public URL getBaseUrl() { + return baseUrl; + } + + /** + * Returns the current base path setting. + */ + public String getBasePath() { + return basePath; + } + + /** + * This method sets the load flags for the file. The flags should + * equal 0 by default (which tells the loader to only load geometry). + */ + public void setFlags(int flags) { + loadFlags = flags; + } + + /** + * Returns the current loading flags setting. + */ + public int getFlags() { + return loadFlags; + } + +} + + + + + + + + diff --git a/src/classes/share/com/sun/j3d/loaders/ParsingErrorException.java b/src/classes/share/com/sun/j3d/loaders/ParsingErrorException.java new file mode 100644 index 0000000..7d94002 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/ParsingErrorException.java @@ -0,0 +1,62 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + + +/** + * Exception used to indicate that the loader encountered + * a problem parsing the specified file. + */ +public class ParsingErrorException extends RuntimeException { + + public ParsingErrorException() { + super(); + } + + public ParsingErrorException(String s) { + super(s); + } +} + diff --git a/src/classes/share/com/sun/j3d/loaders/Scene.java b/src/classes/share/com/sun/j3d/loaders/Scene.java new file mode 100644 index 0000000..34f3399 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/Scene.java @@ -0,0 +1,148 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + +import java.util.Hashtable; + +import javax.media.j3d.Behavior; +import javax.media.j3d.BranchGroup; +import javax.media.j3d.TransformGroup; +import javax.media.j3d.Light; +import javax.media.j3d.Background; +import javax.media.j3d.Fog; +import javax.media.j3d.Sound; + + +/** + * The Scene interface is a set of methods used to extract + * Java 3D scene graph information from a file loader utility. + * The interface is used to give loaders of various + * file formats a common public interface. + */ +public interface Scene { + + + /** + * This method returns the BranchGroup containing the overall + * scene loaded by the loader. All enabled items will be loaded + * into this scene except for Behaviors (the Behavior group must be + * retrieved separately so that users can choose whether and when + * to activate behaviors). + */ + public BranchGroup getSceneGroup(); + + /** + * This method returns an array of all View Groups defined in the file. + * Each View Group is a TransformGroup that is already placed within + * the scene that is returned in the getSceneGroup() call. This + * TransformGroup holds the position/orientation of the view + * as defined by the file. A user might request these references to + * the groups in order to look at the data stored there or + * to place ViewPlatforms within these groups and allow the + * View to activate these ViewPlatforms so that the user would + * see the scene from the viewpoints defined in the file. + */ + public TransformGroup[] getViewGroups(); + + /** + * This method returns an array of floats with the horizontal field + * of view. The entries in the array will correspond to those in the + * array returned by the method getViewGroups. The entries from these + * two arrays together provide all the information needed to recreate + * the viewing parameters associated with a scene graph. + */ + public float[] getHorizontalFOVs(); + + /** + * This method returns an array of all Lights defined in the file. + * If no lights are defined, null is returned. + */ + public Light[] getLightNodes(); + + /** + * This method returns a Hashtable which contains a list of all named + * objects in the file and their associated scene graph objects. The + * naming scheme for file objects is file-type dependent, but may include + * such names as the DEF names of Vrml or filenames of objects (as + * in Lightwave 3D). If no named objects are defined, null is returned. + */ + public Hashtable getNamedObjects(); + + /** + * This method returns an array of all Background nodes defined in the + * file. IF no Background nodes are defined, null is returned. + */ + public Background[] getBackgroundNodes(); + + /** + * This method returns an array of all Fog nodes defined in the + * file. If no fog nodes are defined, null is returned. + */ + public Fog[] getFogNodes(); + + /** + * This method returns an array of all the behavior nodes + * in the scene. If no Behavior nodes are defined, null is returned. + */ + public Behavior[] getBehaviorNodes(); + + /** + * This method returns an array of all of the Sound nodes defined + * in the file. If no Sound nodes are defined, null is returned. + */ + public Sound[] getSoundNodes(); + + /** + * This method returns the text description of the file. If no + * such description exists, this method should return null. + */ + public String getDescription(); + +} + + + + + diff --git a/src/classes/share/com/sun/j3d/loaders/SceneBase.java b/src/classes/share/com/sun/j3d/loaders/SceneBase.java new file mode 100644 index 0000000..05f6413 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/SceneBase.java @@ -0,0 +1,311 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders; + +import java.lang.Float; + +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; + +import javax.media.j3d.Behavior; +import javax.media.j3d.BranchGroup; +import javax.media.j3d.TransformGroup; +import javax.media.j3d.Light; +import javax.media.j3d.Background; +import javax.media.j3d.Fog; +import javax.media.j3d.Sound; + + +/** + * This class implements the Scene interface and extends it to incorporate + * utilities that could be used by loaders. There should be little need + * for future loaders to subclass this, or to implement Scene directly, + * as the functionality of a SceneBase is fairly straightforward. This + * class is responsible for both the storage and retrieval of data from + * the Scene. The storage methods (used only by Loader writers) are all + * of the add* routines. The retrieval methods (used primarily by Loader + * users) are all of the get* routines. + */ +public class SceneBase implements Scene { + + BranchGroup sceneGroup = null; + BranchGroup behaviorGroup = null; + Hashtable namedObjects = new Hashtable(); + String description = null; + + Vector viewVector = new Vector(); + Vector hfovVector = new Vector(); + Vector behaviorVector = new Vector(); + Vector lightVector = new Vector(); + Vector fogVector = new Vector(); + Vector backgroundVector = new Vector(); + Vector soundVector = new Vector(); + + // Add methods + + /** + * Sets the sceneGroup to be the group that is passed in. + */ + public void setSceneGroup(BranchGroup scene) { + sceneGroup = scene; + } + + /** + * Adds the given group to the list of view groups. + */ + public void addViewGroup(TransformGroup tg) { + viewVector.addElement(tg); + } + + /** + * Adds the given field of view value to the list of field of view values. + */ + public void addHorizontalFOV(float hfov) { + hfovVector.addElement(new Float(hfov)); + } + + /** + * Adds the given behavior to a list of behaviors + */ + public void addBehaviorNode(Behavior b) { + behaviorVector.addElement(b); + } + + /** + * Adds the given Light node to the list of lights. + */ + public void addLightNode(Light light) { + lightVector.addElement(light); + } + + /** + * Adds the given Background node to the list of backgrounds. + */ + public void addBackgroundNode(Background background) { + backgroundVector.addElement(background); + } + + /** + * Adds the given Sound node to the list of sounds. + */ + public void addSoundNode(Sound sound) { + soundVector.addElement(sound); + } + + /** + * Adds the given Fog node to the list of fog nodes. + */ + public void addFogNode(Fog fog) { + fogVector.addElement(fog); + } + + /** + * Sets the text description of the scene to the passed in String. + */ + public void addDescription(String descriptionString) { + description = descriptionString; + } + + /** + * Adds the given String/Object pair to the table of named objects. + */ + public void addNamedObject(String name, Object object) { + if (namedObjects.get(name) == null) + namedObjects.put(name, object); + else { + // key already exists - append a unique integer to end of name + int nameIndex = 1; + boolean done = false; + while (!done) { + // Iterate starting from "[1]" until we find a unique key + String tempName = name + "[" + nameIndex + "]"; + if (namedObjects.get(tempName) == null) { + namedObjects.put(tempName, object); + done = true; + } + nameIndex++; + } + } + } + + /** + * This method returns the BranchGroup containing the overall + * scene loaded by the loader. + */ + public BranchGroup getSceneGroup() { + return sceneGroup; + } + + + /** + * This method returns an array of all View Groups defined in the file. + * A View Group is defined as a TransformGroup which contains a + * ViewPlatform. The TransformGroup holds the position/orientation + * information for the given ViewPlatform and the ViewPlatform + * holds an view-specific information, such as Field of View. + */ + public TransformGroup[] getViewGroups() { + if (viewVector.isEmpty()) + return null; + TransformGroup[] viewGroups = new TransformGroup[viewVector.size()]; + viewVector.copyInto(viewGroups); + return viewGroups; + } + + /** + * This method returns an array of floats that contains the horizontal + * field of view values for each corresponding entry in the array of + * view groups returned by the method getViewGroups. + */ + public float[] getHorizontalFOVs() { + if (hfovVector.isEmpty()) + return null; + + int arraySize = hfovVector.size(); + float[] hfovs = new float[arraySize]; + Float[] tmpFovs = new Float[hfovVector.size()]; + hfovVector.copyInto(tmpFovs); + + // copy to array of floats and delete Floats + for (int i=0; i 0) { + System.out.print(theOutput); + } + } + + void println(int outputType, String theOutput) { + if ((outputType & validOutput) > 0) + System.out.println(theOutput); + } + +} + + + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/EnvelopeHandler.java b/src/classes/share/com/sun/j3d/loaders/lw3d/EnvelopeHandler.java new file mode 100644 index 0000000..faad047 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/EnvelopeHandler.java @@ -0,0 +1,133 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.io.StreamTokenizer; +import java.io.IOException; +import java.lang.reflect.Constructor; +import com.sun.j3d.loaders.ParsingErrorException; +import java.lang.ClassNotFoundException; +import java.lang.InstantiationException; +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; + + +/** + * This class is used in implementing Envelope objects (of which there + * is currently only one - LightIntensity). + * The class is called whenever the parser has encountered + * a token which could have an envelope description. If the + * token simply has a numeric value, this value is stored. + * If, instead, there is an envelope, then the class creates + * the envelope class and parses that information. + */ + +class EnvelopeHandler extends TextfileParser { + + float theValue = 0; + boolean hasValue = false; + boolean hasEnvelope = true; + LwsEnvelope theEnvelope = null; + + /** + * Constructor: This constructor is used if there is no existing + * implementation for this type of envelope. The real constructor + * is called with the generic LwsEnvelope class name, which will + * allow s to parse and ignore the envelope data + */ + EnvelopeHandler(StreamTokenizer st, + int totalFrames, float totalTime) { + this(st, totalFrames, totalTime, + "com.sun.j3d.utils.loaders.lw3d.LwsEnvelope"); + } + + /** + * Constructor: This constructor is called with the name of a class + * that can handle the encountered envelope. This is done so that this + * EnvelopeHandler class can generically call the given class to handle + * the envelope, whether it results in parsing/ignoring the data or + * in actually using the data + */ + EnvelopeHandler(StreamTokenizer st, + int totalFrames, + float totalTime, + String envClassName) throws ParsingErrorException { + try { + theValue = (float)getNumber(st); + hasValue = true; + } + catch (NumberFormatException e) { + if (st.ttype == '(') { + st.pushBack(); + // This code creates a new instance for the given class name + try { + Class envClass = Class.forName(envClassName); + Constructor constructors[] = envClass.getConstructors(); + Constructor con = constructors[0]; + Object args[] = new Object[3]; + args[0] = (Object)st; + args[1] = (Object)(new Integer(totalFrames)); + args[2] = (Object)(new Float(totalTime)); + try { + theEnvelope = (LwsEnvelope)con.newInstance(args); + hasEnvelope = true; + } + catch (InstantiationException e3) { + // Ignore + } + catch (IllegalAccessException e3) { + // Ignore + } + catch (InvocationTargetException e3) { + // Ignore + } + } + catch (ClassNotFoundException e2) { + // Ignore + } + } + } + } +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/FloatValueInterpolator.java b/src/classes/share/com/sun/j3d/loaders/lw3d/FloatValueInterpolator.java new file mode 100644 index 0000000..c270c0f --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/FloatValueInterpolator.java @@ -0,0 +1,171 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import javax.vecmath.*; +import java.util.BitSet; +import java.util.Enumeration; + +import javax.media.j3d.Alpha; +import javax.media.j3d.Node; +import javax.media.j3d.NodeReferenceTable; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.Interpolator; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class acts as an interpolator between values specified in a + * floating point array, based on knot values (keyframes) specified in a + * knots array. + */ +abstract class FloatValueInterpolator extends Interpolator { + + private float knots[]; + private int knotsLength; + protected int currentKnotIndex; + protected float currentInterpolationRatio; + protected float values[]; + protected float currentValue; + + /** + * Constructs a new FloatValueInterpolator object. + * @param alpha the alpha object for this interpolator + * @param knots an array of knot values that specify a spline + */ + FloatValueInterpolator(Alpha alpha, float k[], float v[]) { + + super(alpha); + + // Check that first knot = 0.0f + knotsLength = k.length; + if (k[0] < -0.0001 || k[0] > 0.0001) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("FloatValueInterpolator0")); + } + + // Check that last knot = 1.0f + if ((k[knotsLength-1] - 1.0f) < -0.0001 || + (k[knotsLength-1] - 1.0f) > 0.0001) { + + throw new IllegalArgumentException(J3dUtilsI18N.getString("FloatValueInterpolator1")); + } + + // Check to see that knots are in ascending order and copy them + this.knots = new float[knotsLength]; + for (int i = 0; i < knotsLength; i++) { + if ((i > 0) && (k[i] < k[i-1])) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("FloatValueInterpolator2")); + } + this.knots[i] = k[i]; + } + + // check to see that we have the same number of values as knots + if (knotsLength != v.length) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("FloatValueInterpolator3")); + } + + // copy the values + this.values = new float[knotsLength]; + for(int i = 0; i < knotsLength; i++) { + this.values[i] = v[i]; + } + + } + + /** + * This method sets the value at the specified index for + * this interpolator. + * @param index the index to be changed + * @param position the new value at index + */ + void setValue(int index, float value) { + this.values[index] = value; + } + + /** + * This method retrieves the value at the specified index. + * @param index the index of the value requested + * @return the interpolator's value at the index + */ + float getValue(int index) { + return this.values[index]; + } + + /** + * This method computes the bounding knot indices and interpolation value + * "currentValue" given the current value of alpha, the knots[] array and + * the array of values. + * If the index is 0 and there will be no interpolation, both the + * index variable and the interpolation variable are set to 0. + * Otherwise, currentKnotIndex is set to the lower index of the + * two bounding knot points and the currentInterpolationRatio + * variable is set to the ratio of the alpha value between these + * two bounding knot points. + */ + protected void computePathInterpolation() { + float alphaValue = (this.getAlpha()).value(); + + for (int i = 0; i < knotsLength; i++) { + if ((i == 0 && alphaValue <= knots[i]) || + (i > 0 && alphaValue >= knots[i-1] && alphaValue <= knots[i])) { + + if (i==0) { + currentInterpolationRatio = 0f; + currentKnotIndex = 0; + currentValue = values[0]; + } + else { + currentInterpolationRatio = + (alphaValue - knots[i-1])/(knots[i] - knots[i-1]); + currentKnotIndex = i - 1; + currentValue = values[i-1] + + currentInterpolationRatio * (values[i] - values[i-1]); + } + break; + } + } + } + +} + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/ImageScaler.java b/src/classes/share/com/sun/j3d/loaders/lw3d/ImageScaler.java new file mode 100644 index 0000000..d457f9c --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/ImageScaler.java @@ -0,0 +1,148 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; + +/** + * This class resizes an image to be the nearest power of 2 wide and high. + * This facility now exists inside of the TextureLoader class, so + * ImageScaler should be eliminated at some point. + */ + +class ImageScaler { + + int origW, origH; + Image origImage; + + ImageScaler(Image image, int w, int h) { + origImage = image; + origW = w; + origH = h; + } + + ImageScaler(BufferedImage image) { + origImage = image; + origW = image.getWidth(); + origH = image.getHeight(); + } + + /** + * Utility method to return closes poser of 2 to the given integer + */ + int getClosestPowerOf2(int value) { + + if (value < 1) + return value; + + int powerValue = 1; + for (int i = 1; i < 20; ++i) { + powerValue *= 2; + if (value < powerValue) { + // Found max bound of power, determine which is closest + int minBound = powerValue/2; + if ((powerValue - value) > + (value - minBound)) + return minBound; + else + return powerValue; + } + } + // shouldn't reach here... + return 1; + } + + /** + * Returns an Image that has been scaled from the original image to + * the closest power of 2 + */ + Image getScaledImage() { + int newWidth = getClosestPowerOf2(origW); + int newHeight = getClosestPowerOf2(origH); + + // If the image is already a power of 2 wide/tall, no need to scale + if (newWidth == origW && + newHeight == origH) + return origImage; + + Image scaledImage = null; + + if (origImage instanceof BufferedImage) { + // If BufferedImage, then we have some work to do + BufferedImage origImageB = (BufferedImage)origImage; + scaledImage = + new BufferedImage(newWidth, + newHeight, + origImageB.getType()); + BufferedImage scaledImageB = (BufferedImage)scaledImage; + float widthScale = (float)origW/(float)newWidth; + float heightScale = (float)origH/(float)newHeight; + int origPixels[] = ((DataBufferInt)origImageB.getRaster().getDataBuffer()).getData(); + int newPixels[] = ((DataBufferInt)scaledImageB.getRaster().getDataBuffer()).getData(); + for (int row = 0; row < newHeight; ++row) { + for (int column = 0; column < newWidth; ++column) { + int oldRow = Math.min(origH-1, + (int)((float)(row)* + heightScale + .5f)); + int oldColumn = + Math.min(origW-1, + (int)((float)column*widthScale + .5f)); + newPixels[row*newWidth + column] = + origPixels[oldRow*origW + oldColumn]; + } + } + } + else { + // If regular Image, then the work is done for us + scaledImage = origImage.getScaledInstance(newWidth, + newHeight, + Image.SCALE_DEFAULT); + } + return scaledImage; + } +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/J3dLwoParser.java b/src/classes/share/com/sun/j3d/loaders/lw3d/J3dLwoParser.java new file mode 100644 index 0000000..1db2dbb --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/J3dLwoParser.java @@ -0,0 +1,601 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.Component; +import java.awt.Image; +import java.util.Enumeration; +import java.util.Vector; +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.image.TextureLoader; +import com.sun.j3d.loaders.IncorrectFormatException; +import java.io.FileNotFoundException; + +import javax.media.j3d.*; +import javax.vecmath.*; + +import java.net.*; + + +/** + * This class is responsible for turning Lightwave geometry data into + * Java3D geometry. It is a subclass of LwoObject and calls that + * superclass when first instantiated to parse the binary file and turn it + * into intermediate data structures. J3dLwoParser then goes through + * those data structures and turns them into Java3D objects. For each + * ShapeHolder object created by the parent class, this class retrieves + * the geometry data and associated surface data, creates the + * appropriate Geometry object (usually IndexedTriangleFanArray, + * unless the object is points or lines), including calculating normals for + * the polygons and sets up the Appearance according to the surface + * parameters (including calculating texture coordinates if necessary). + */ + +class J3dLwoParser extends LwoParser { + + float normalCoordsArray[]; + int normalIndicesArray[]; + Shape3D objectShape; + Color3f color, diffuseColor, specularColor, emissiveColor; + float shininess; + Vector objectShapeList = new Vector(); + + /** + * Constructor: Calls LwoObject to parse file and create data structures + */ + J3dLwoParser(String fileName, + int debugVals) throws FileNotFoundException { + super(fileName, debugVals); + } + + J3dLwoParser(URL url, int debugVals) + throws FileNotFoundException { + super(url, debugVals); + } + + void getSurf(int length) throws FileNotFoundException { + super.getSurf(length); + } + + + /** + * Turns LwoObject's data structures (created from the binary geometry + * file) into Java3d objects + */ + void createJava3dGeometry() throws IncorrectFormatException { + + GeometryArray object; + LwoTexture texture; + + for (Enumeration e = shapeList.elements(); + e.hasMoreElements() ;) { + int vertexFormat = javax.media.j3d.GeometryArray.COORDINATES; + ShapeHolder shape = (ShapeHolder)e.nextElement(); + debugOutputLn(LINE_TRACE, "about to create Arrays for Shape"); + debugOutputLn(VALUES, "shape = " + shape); + shape.createArrays(true); + int vertexCount = shape.coordsArray.length/3; + int indexCount = 0; + if (shape.facetIndices != null) + indexCount = shape.facetIndices.length; + debugOutputLn(VALUES, "numSurf = " + shape.numSurf); + // Find the right surface. Note: surfaces are indexed by + // name. So take this surf number, look up the name of that + // surface in surfaceNameList, then take that name and + // find the matching LwoSurface + String surfName = + (String)surfNameList.elementAt(shape.numSurf - 1); + LwoSurface surf = null; + for (int surfNum = 0; + surfNum < surfaceList.size(); + ++surfNum) { + LwoSurface tempSurf = + (LwoSurface)surfaceList.elementAt(surfNum); + String tempSurfName = tempSurf.surfName; + if (surfName.equals(tempSurfName)) { + surf = tempSurf; + break; + } + } + if (surf == null) { + throw new IncorrectFormatException( + "bad surf for surfnum/name = " + shape.numSurf + ", " + + surfName); + } + debugOutputLn(VALUES, "surf = " + surf); + + // Get the LwoTexture object (if any) for the surface + texture = surf.getTexture(); + + Appearance appearance = new Appearance(); + if (shape.facetSizes[0] == 1) { + // This case happens if the objects are points + // Note that points are colored, not lit + object = new + javax.media.j3d.PointArray(vertexCount, vertexFormat); + object.setCoordinates(0, shape.coordsArray); + ColoringAttributes colorAtt = + new ColoringAttributes(surf.getColor(), + ColoringAttributes.FASTEST); + PointAttributes pointStyle = new PointAttributes(); + pointStyle.setPointSize(1); + + appearance.setColoringAttributes(colorAtt); + appearance.setPointAttributes(pointStyle); + } + else if (shape.facetSizes[0] == 2) { + // This case happens if the objects are lines + // Note that lines are colored, not lit + debugOutputLn(LINE_TRACE, "Creating IndexedLineArray"); + object = new javax.media.j3d.LineArray(vertexCount, + vertexFormat); + object.setCoordinates(0, shape.coordsArray); + ColoringAttributes colorAtt = + new ColoringAttributes(surf.getColor(), + ColoringAttributes.FASTEST); + appearance.setColoringAttributes(colorAtt); + } + else { + // This is the case for any polygonal objects + debugOutputLn(LINE_TRACE, "Creating IndexedTriFanArray"); + // create triFanArray + vertexFormat |= javax.media.j3d.GeometryArray.NORMALS; + + debugOutputLn(LINE_TRACE, "about to process vertices/indices, facetIndices = " + + shape.facetIndices); + if (shape.facetIndices != null) { + float[] textureCoords = null; + int[] textureIndices = null; + + debugOutputLn(LINE_TRACE, "setting vertexCount, normind = " + shape.normalIndices); + // If these are null we're going direct (non-indexed) + debugOutputLn(LINE_TRACE, "vtxcount, format, indcount = " + + vertexCount + ", " + vertexFormat + + ", " + indexCount); + if (texture != null) { + // There's a texture here - need to create the appropriate arrays + // and calculate texture coordinates for the object + vertexFormat |= GeometryArray.TEXTURE_COORDINATE_2; + textureCoords = new float[vertexCount * 2]; + textureIndices = new int[shape.facetIndices.length]; + calculateTextureCoords(texture, shape.coordsArray, + shape.facetIndices, + textureCoords, textureIndices); + debugOutputLn(LINE_TRACE, "textureCoords:"); + debugOutputLn(LINE_TRACE, "texture Coords, Indices.length = " + textureCoords.length + ", " + textureIndices.length); + } + debugOutputLn(LINE_TRACE, "about to create GeometryInfo"); + + // Use the GeometryInfo utility to calculate smooth normals + GeometryInfo gi = + new GeometryInfo(GeometryInfo.TRIANGLE_FAN_ARRAY); + gi.setCoordinates(shape.coordsArray); + gi.setCoordinateIndices(shape.facetIndices); + gi.setStripCounts(shape.facetSizes); + if (texture != null) { + gi.setTextureCoordinateParams(1, 2); + gi.setTextureCoordinates(0, textureCoords); + gi.setTextureCoordinateIndices(0, textureIndices); + } + gi.recomputeIndices(); + NormalGenerator ng = + new NormalGenerator(surf.getCreaseAngle()); + ng.generateNormals(gi); + Stripifier st = new Stripifier(); + st.stripify(gi); + object = gi.getGeometryArray(true, true, false); + debugOutputLn(LINE_TRACE, "done."); + } + else { + // This case is called if LwoObject did not create facet + // indices. This code is not currently used because facet + // indices are always created for polygonal objects + debugOutputLn(LINE_TRACE, + "about to create trifanarray with " + + "vertexCount, facetSizes.len = " + + vertexCount + ", " + + shape.facetSizes.length); + object = new + javax.media.j3d.TriangleFanArray(vertexCount, + vertexFormat, + shape.facetSizes); + object.setCoordinates(0, shape.coordsArray); + object.setNormals(0, shape.normalCoords); + debugOutputLn(VALUES, "passed in normalCoords, length = " + + shape.normalCoords.length); + } + debugOutputLn(LINE_TRACE, "created fan array"); + + // Setup Appearance given the surface parameters + Material material = new Material(surf.getColor(), + surf.getEmissiveColor(), + surf.getDiffuseColor(), + surf.getSpecularColor(), + surf.getShininess()); + material.setLightingEnable(true); + appearance.setMaterial(material); + if (surf.getTransparency() != 0f) { + TransparencyAttributes ta = new TransparencyAttributes(); + ta.setTransparency(surf.getTransparency()); + ta.setTransparencyMode(ta.BLENDED); + appearance.setTransparencyAttributes(ta); + } + if (texture != null) { + debugOutputLn(LINE_TRACE, "texture != null, enable texturing"); + Texture tex = texture.getTexture(); + tex.setEnable(true); + appearance.setTexture(tex); + TextureAttributes ta = new TextureAttributes(); + if (texture.getType().equals("DTEX")) + ta.setTextureMode(TextureAttributes.MODULATE); + else if (texture.getType().equals("CTEX")) + ta.setTextureMode(TextureAttributes.DECAL); + appearance.setTextureAttributes(ta); + } + else { + debugOutputLn(LINE_TRACE, "texture == null, no texture to use"); + } + } + debugOutputLn(LINE_TRACE, "done creating object"); + + // This does gc + shape.nullify(); + + objectShape = new Shape3D(object); + + // Combine the appearance and geometry + objectShape.setAppearance(appearance); + objectShapeList.addElement(objectShape); + } + } + + /** + * Calculate texture coordinates for the geometry given the texture + * map properties specified in the LwoTexture object + */ + void calculateTextureCoords(LwoTexture texture, + float verts[], int indices[], + float[] textureCoords, int[] textureIndices) { + + /* + the actual math in these coord calculations comes directly from + Newtek - they posted sample code to help compute tex coords based + on the type of mapping: + + Here are some simplified code fragments showing how LightWave + computes UV coordinates from X, Y, and Z. If the resulting + UV coordinates are not in the range from 0 to 1, the + appropriate integer should be added to them to bring them into + that range (the fract function should have accomplished + this by subtracting the floor of each number from itself). + Then they can be multiplied by the width and height (in pixels) + of an image map to determine which pixel to look up. The + texture size, center, and tiling parameters are taken right + off the texture control panel. + + + x -= xTextureCenter; + y -= yTextureCenter; + z -= zTextureCenter; + if (textureType == TT_PLANAR) { + s = (textureAxis == TA_X) ? z / zTextureSize + .5 : + x / xTextureSize + .5; + t = (textureAxis == TA_Y) ? -z / zTextureSize + .5 : + -y / yTextureSize + .5; + u = fract(s); + v = fract(t); + } + else if (type == TT_CYLINDRICAL) { + if (textureAxis == TA_X) { + xyztoh(z,x,-y,&lon); + t = -x / xTextureSize + .5; + } + else if (textureAxis == TA_Y) { + xyztoh(-x,y,z,&lon); + t = -y / yTextureSize + .5; + } + else { + xyztoh(-x,z,-y,&lon); + t = -z / zTextureSize + .5; + } + lon = 1.0 - lon / TWOPI; + if (widthTiling != 1.0) + lon = fract(lon) * widthTiling; + u = fract(lon); + v = fract(t); + } + else if (type == TT_SPHERICAL) { + if (textureAxis == TA_X) + xyztohp(z,x,-y,&lon,&lat); + else if (textureAxis == TA_Y) + xyztohp(-x,y,z,&lon,&lat); + else + xyztohp(-x,z,-y,&lon,&lat); + lon = 1.0 - lon / TWOPI; + lat = .5 - lat / PI; + if (widthTiling != 1.0) + lon = fract(lon) * widthTiling; + if (heightTiling != 1.0) + lat = fract(lat) * heightTiling; + u = fract(lon); + v = fract(lat); + } + + support functions: + + void xyztoh(float x,float y,float z,float *h) + { + if (x == 0.0 && z == 0.0) + *h = 0.0; + else { + if (z == 0.0) + *h = (x < 0.0) ? HALFPI : -HALFPI; + else if (z < 0.0) + *h = -atan(x / z) + PI; + else + *h = -atan(x / z); + } + } + + void xyztohp(float x,float y,float z,float *h,float *p) + { + if (x == 0.0 && z == 0.0) { + *h = 0.0; + if (y != 0.0) + *p = (y < 0.0) ? -HALFPI : HALFPI; + else + *p = 0.0; + } + else { + if (z == 0.0) + *h = (x < 0.0) ? HALFPI : -HALFPI; + else if (z < 0.0) + *h = -atan(x / z) + PI; + else + *h = -atan(x / z); + x = sqrt(x * x + z * z); + if (x == 0.0) + *p = (y < 0.0) ? -HALFPI : HALFPI; + else + *p = atan(y / x); + } + } + */ + + debugOutputLn(TRACE, "calculateTextureCoords()"); + // Compute texture coord stuff + float sx = 0, sz = 0, ty = 0, tz = 0; + double s, t; + int textureAxis = texture.getTextureAxis(); + Vector3f textureSize = texture.getTextureSize(); + Vector3f textureCenter = texture.getTextureCenter(); + + String mappingType = texture.getMappingType(); + if (mappingType.startsWith("Cylindrical")) + calculateCylindricalTextureCoords(textureAxis, textureSize, + textureCenter, textureCoords, + textureIndices, + verts, indices); + else if (mappingType.startsWith("Spherical")) + calculateSphericalTextureCoords(textureAxis, + textureCenter, textureCoords, + textureIndices, + verts, indices); + else if (mappingType.startsWith("Planar")) + calculatePlanarTextureCoords(textureAxis, textureSize, + textureCenter, textureCoords, + textureIndices, + verts, indices); + + } + + /** See the comments in calculateTextureCoordinates*/ + double xyztoh(float x,float y,float z) { + if (x == 0.0 && z == 0.0) + return 0.0; + else { + if (z == 0.0) + return (x < 0.0) ? Math.PI/2.0 : -Math.PI/2.0; + else if (z < 0.0) + return -Math.atan(x / z) + Math.PI; + else + return -Math.atan(x / z); + } + } + + /** See the comments in calculateTextureCoordinates*/ + double xyztop(float x,float y,float z) { + double p; + + if (x == 0.0 && z == 0.0) { + if (y != 0.0) + p = (y < 0.0) ? -Math.PI/2 : Math.PI/2; + else + p = 0.0; + } + else { + x = (float)Math.sqrt(x * x + z * z); + if (x == 0.0) + p = (y < 0.0) ? -Math.PI/2 : Math.PI/2; + else + p = Math.atan(y / x); + } + return p; + } + + + /** See the comments in calculateTextureCoordinates*/ + void calculateSphericalTextureCoords(int textureAxis, + Vector3f textureCenter, + float textureCoords[], + int textureIndices[], + float verts[], int indices[]) { + debugOutputLn(TRACE, "calculateSphericalTextureCoords"); + double s, t; + + + for (int i = 0; i < indices.length; ++i) { + float x = verts[3*indices[i]] - textureCenter.x; + float y = verts[3*indices[i]+1] - textureCenter.y; + float z = -(verts[3*indices[i]+2] + textureCenter.z); + if (textureAxis == 1){ // X Axis + s = xyztoh(z, x, -y); + t = xyztop(z,x,-y); + } + else if (textureAxis == 2) { // Y Axis + s = xyztoh(-x,y,z); + t = xyztop(-x,y,z); + } + else { // Z Axis + s = xyztoh(-x,z,-y); + t = xyztop(-x,z,-y); + } + s = 1.0 - s / (2*Math.PI); + t = -(.5 - t / Math.PI); + textureCoords[indices[i]*2] = (float)s; + textureCoords[indices[i]*2 + 1] = (float)t; + textureIndices[i] = indices[i]; + } + } + + /** See the comments in calculateTextureCoordinates*/ + void calculateCylindricalTextureCoords(int textureAxis, + Vector3f textureSize, + Vector3f textureCenter, + float textureCoords[], + int textureIndices[], + float verts[], int indices[]) { + debugOutputLn(TRACE, "calculateCylindricalTextureCoords"); + debugOutputLn(VALUES, "axis, size, center, tc, ti, v, i = " + + textureAxis + ", " + + textureSize + ", " + + textureCenter + ", " + + textureCoords + ", " + + textureIndices + ", " + + verts + ", " + + indices); + double s, t; + + debugOutputLn(VALUES, "Cyl Texture Coords:"); + for (int i = 0; i < indices.length; ++i) { + float x = verts[3*indices[i]] - textureCenter.x; + float y = verts[3*indices[i]+1] - textureCenter.y; + float z = -(verts[3*indices[i]+2] + textureCenter.z); + // Negate z value because we invert geom z's to swap handedness + if (textureAxis == 1) { // X axis + s = xyztoh(z,x,-y); + t = x / textureSize.x + .5; + } + else if (textureAxis == 2) { // Y Axis + s = xyztoh(-x,y,z); + t = y / textureSize.y + .5; + } + else { + s = xyztoh(-x,z,-y); + t = z / textureSize.z + .5; + } + s = 1.0 - s / (2*Math.PI); + textureCoords[indices[i]*2] = (float)s; + textureCoords[indices[i]*2 + 1] = (float)t; + textureIndices[i] = indices[i]; + debugOutputLn(VALUES, "x, y, z = " + + x + ", " + y + ", " + z + " " + + "s, t = " + s + ", " + t); + } + } + + /** See the comments in calculateTextureCoordinates*/ + void calculatePlanarTextureCoords(int textureAxis, Vector3f textureSize, + Vector3f textureCenter, + float textureCoords[], + int textureIndices[], + float verts[], int indices[]) { + debugOutputLn(TRACE, "calculatePlanarTextureCoords"); + debugOutputLn(VALUES, "size, center, axis = " + + textureSize + textureCenter + ", " + textureAxis); + float sx = 0, sz = 0, ty = 0, tz = 0; + double s, t; + + if (textureAxis == 1) { // X Axis + sz = -1.0f / textureSize.z; // Negate because we negate z in geom + ty = 1.0f / textureSize.y; + } + else if (textureAxis == 2) { // Y Axis + sx = 1.0f / textureSize.x; + tz = -1.0f / textureSize.z; // Negate because we negate z in geom + } + else { // Z Axis + sx = 1.0f / textureSize.x; + ty = 1.0f / textureSize.y; + } + + debugOutputLn(VALUES, "Planar Texture Coords:"); + for (int i = 0; i < indices.length; ++i) { + float x = verts[3*indices[i]] - textureCenter.x; + float y = verts[3*indices[i]+1] - textureCenter.y; + float z = verts[3*indices[i]+2] + textureCenter.z; + s = x*sx + z*sz + .5; + t = y*ty + z*tz + .5; + textureCoords[indices[i]*2] = (float)s; + textureCoords[indices[i]*2 + 1] = (float)t; + textureIndices[i] = indices[i]; + debugOutputLn(VALUES, "x, y, z = " + + x + ", " + y + ", " + z + " " + + "s, t = " + s + ", " + t); + } + } + + + Shape3D getJava3dShape() { + return objectShape; + } + + Vector getJava3dShapeList() { + return objectShapeList; + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LWOBFileReader.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LWOBFileReader.java new file mode 100644 index 0000000..d83bfe4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LWOBFileReader.java @@ -0,0 +1,266 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import java.io.IOException; +import com.sun.j3d.loaders.ParsingErrorException; + + +class LWOBFileReader extends BufferedInputStream { + + + + // Debugging constants + final static int TRACE = DebugOutput.TRACE; + final static int VALUES = DebugOutput.VALUES; + final static int MISC = DebugOutput.MISC; + final static int LINE_TRACE = DebugOutput.LINE_TRACE; + final static int NONE = DebugOutput.NONE; + final static int EXCEPTION = DebugOutput.EXCEPTION; + + protected DebugOutput debugPrinter; + + protected String theFilename; + + protected int marker; + + + + protected void debugOutputLn(int outputType, String theOutput) { + if (theOutput.equals("")) + debugPrinter.println(outputType, theOutput); + else + debugPrinter.println(outputType, + getClass().getName() + "::" + theOutput); + } // End of debugOutputLn + + + + // Return a string consisting of the next 4 bytes in the file + public String getToken() throws ParsingErrorException { + byte tokenBuffer[] = new byte[4]; + try { + int readResult = read(tokenBuffer, 0, 4); + if (readResult == -1) { + debugOutputLn(LINE_TRACE, "no token - returning null"); + return null; + } + return new String(tokenBuffer); + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "getToken: " + e); + throw new ParsingErrorException(e.getMessage()); + } + } + + + + /** + * Skip ahead amount bytes in the file + */ + public void skipLength(int amount) throws ParsingErrorException { + try { + skip((long)amount); + marker += amount; + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "skipLength: " + e); + throw new ParsingErrorException(e.getMessage()); + } + } + + + + /** + * Read four bytes from the file and return their integer value + */ + public int getInt() throws ParsingErrorException { + try { + int x = 0; + for (int i = 0 ; i < 4 ; i++) { + int readResult = read(); + if (readResult == -1) + throw new ParsingErrorException("Unexpected EOF"); + x = (x << 8) | readResult; + } + return x; + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "getInt: " + e); + throw new ParsingErrorException(e.getMessage()); + } + } + + + + /** + * Read four bytes from the file and return their float value + */ + public float getFloat() throws ParsingErrorException { + return Float.intBitsToFloat(getInt()); + } // End of getFloat + + + + /** + * Returns the name of the file associated with this stream + */ + public String getFilename() { + return theFilename; + } // End of getFilename + + + + /** + * Returns a string read from the file. The string is assumed to + * end with '0'. + */ + public String getString() throws ParsingErrorException { + byte buf[] = new byte[512]; + try { + byte b; + int len = 0; + do { + b = (byte)read(); + buf[len++] = b; + } while (b != 0); + // Have to read an even number of bytes + if (len % 2 != 0) read(); + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "getString: " + e); + throw new ParsingErrorException(e.getMessage()); + } + return new String(buf); + } // End of getString + + + + /** + * Reads an array of xyz values. + */ + public void getVerts(float ar[], int num) throws ParsingErrorException { + for (int i = 0 ; i < num ; i++) { + ar[i * 3 + 0] = getFloat(); + ar[i * 3 + 1] = getFloat(); + ar[i * 3 + 2] = -getFloat(); + } + } // End of getVerts + + + + /** + * Reads two bytes from the file and returns their integer value. + */ + public int getShortInt() throws ParsingErrorException { + int i = 0; + try { + i = read(); + i = (i << 8) | read(); + // Sign extension + if ((i & 0x8000) != 0) i |= 0xffff0000; + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "getShortInt: " + e); + throw new ParsingErrorException(e.getMessage()); + } + return i; + } // End of getShortInt + + + + /** + * Returns the current position in the file + */ + public int getMarker() { + // protected field inherited from BufferedInputStream + return marker; + } // End of getMarker + + + + public int read() throws IOException { + marker++; + return super.read(); + } // End of read() + + + + public int read(byte[] buffer, int offset, int count) throws IOException { + int ret = super.read(buffer, offset, count); + if (ret != -1) marker += ret; + return ret; + } // End of read(byte[], int, int) + + + + /** + * Constructor. + */ + public LWOBFileReader(String filename) throws FileNotFoundException { + super(new FileInputStream(filename)); + + // Add constants on this line to get more debug output + debugPrinter = new DebugOutput(127); + + marker = 0; + } // End of constructor + + public LWOBFileReader(java.net.URL url) throws java.io.IOException { + super(url.openStream()); + + // add constants on this line to get more debug output + debugPrinter = new DebugOutput(127); + + marker = 0; + } + +} // End of file LWOBFileReader diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LightIntensityPathInterpolator.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LightIntensityPathInterpolator.java new file mode 100644 index 0000000..61ba2fb --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LightIntensityPathInterpolator.java @@ -0,0 +1,101 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.util.*; + +import javax.media.j3d.Alpha; +import javax.media.j3d.Light; + +/** + * This Interpolator object modifies the intensity of a Light object + * according to the keyframes in a light intensity envelope + */ +class LightIntensityPathInterpolator extends FloatValueInterpolator { + + LwLightObject theLight; + + LightIntensityPathInterpolator(Alpha alpha, + float knots[], + float values[], + Object target) { + + super(alpha, knots, values); + theLight = (LwLightObject)target; + } + + /** + * This method is invoked by the behavior scheduler every frame. It maps + * the alpha value that corresponds to the current time into the + * appropriate light intensity for that time as obtained by interpolating + * between the light intensity values for each knot point that were passed + * to this class. + * @param criteria enumeration of criteria that have triggered this wakeup + */ + + public void processStimulus(Enumeration criteria) { + // Handle stimulus + + if (this.getAlpha() != null) { + + // Let FloatValueInterpolator calculate the correct + // interpolated value + computePathInterpolation(); + + // Set light intensity to the value calculated by + // FloatValueInterpolator + if (theLight != null) + theLight.setIntensity(currentValue); + + if ((this.getAlpha()).finished()) + return; + } + + wakeupOn(defaultWakeupCriterion); + + } + +} + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/Lw3dLoader.java b/src/classes/share/com/sun/j3d/loaders/lw3d/Lw3dLoader.java new file mode 100644 index 0000000..c783518 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/Lw3dLoader.java @@ -0,0 +1,706 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + +import com.sun.j3d.loaders.*; +import java.awt.Component; +import java.io.*; +import java.util.Vector; +import java.util.Enumeration; +import java.net.URL; +import java.net.MalformedURLException; + +import javax.media.j3d.*; +import javax.vecmath.Color3f; +import javax.vecmath.Point3d; + + +/** + * This class implements the Loader API and allows users to load + * Lightwave 3D scene files. In order to load properly, the object + * files referred to in the scene files and the image files referred + * to by the object files must all be specified with path and filenames + * that are valid with respect to the directory in which the application + * is being executed. + */ + +public class Lw3dLoader extends TextfileParser implements Loader { + + Vector objectList; + Vector lightList; + BranchGroup sceneGroupNode; + Color3f ambientColor; + LwsCamera camera = null; + LwsFog fog = null; + LwsBackground background = null; + int loadFlags = 0; + int loadBehaviors = 0; + Vector sceneBehaviors; + SceneBase scene = null; + String basePath = null; + String internalBasePath = null; + URL baseUrl = null; + String internalBaseUrl = null; // store url base as String + static final int FILE_TYPE_NONE = 0; + static final int FILE_TYPE_URL = 1; + static final int FILE_TYPE_FILENAME = 2; + static final int FILE_TYPE_READER = 4; + int fileType = FILE_TYPE_NONE; + + /** + * Default constructor. Sets up default values for some variables. + */ + public Lw3dLoader() { + + ambientColor = new Color3f(0f, 0f, 0f); + objectList = new Vector(); + lightList = new Vector(); + debugPrinter.setValidOutput(0x0); + + } + + /** + * This constructor takes a flags word that specifies which types of + * scenefile items should be loaded into the scene. The possible + * values are specified in the com.sun.j3d.loaders.Loader class. + */ + public Lw3dLoader(int flags) { + + this(); + loadFlags = flags; + loadBehaviors = (loadFlags & Loader.LOAD_BEHAVIOR_NODES); + + } + + /** + * This method loads the named file and returns the Scene + * containing the scene. Any data files referenced by the Reader + * should be located in the same place as the named file; otherwise, + * users should specify an alternate base path with the setBaseUrl(URL) + * method. + */ + public Scene load(URL url) throws FileNotFoundException, + IncorrectFormatException, ParsingErrorException { + + fileType = FILE_TYPE_URL; + setInternalBaseUrl(url); + InputStreamReader reader; + try { + reader = new InputStreamReader( + new BufferedInputStream(url.openStream())); + } + catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); + } + Scene returnScene = load(reader); + fileType = FILE_TYPE_NONE; + return returnScene; + } + + /** + * This method loads the named file and returns the Scene + * containing the scene. Any data files referenced by this + * file should be located in the same place as the named file; + * otherwise users should specify an alternate base path with + * the setBasePath(String) method. + */ + public Scene load(String fileName) throws FileNotFoundException, + IncorrectFormatException, ParsingErrorException { + + fileType = FILE_TYPE_FILENAME; + setInternalBasePath(fileName); + Reader reader = new BufferedReader(new FileReader(fileName)); + Scene returnScene = load(reader); + fileType = FILE_TYPE_NONE; + return returnScene; + } + + /** + * This method loads the Reader and returns the Scene + * containing the scene. Any data files referenced by the Reader should + * be located in the user's current working directory. + */ + public Scene load(Reader reader) throws FileNotFoundException, + IncorrectFormatException, ParsingErrorException { + + if (fileType == FILE_TYPE_NONE) + fileType = FILE_TYPE_READER; + StreamTokenizer tokenizer = new StreamTokenizer(reader); + setupTokenizer(tokenizer); + + getAndCheckString(tokenizer, "LWSC"); + getNumber(tokenizer); + getAndCheckString(tokenizer, "FirstFrame"); + int firstFrame = (int)getNumber(tokenizer); + getAndCheckString(tokenizer, "LastFrame"); + int finalFrame = (int)getNumber(tokenizer); + skipUntilString(tokenizer, "FramesPerSecond"); + double fps = getNumber(tokenizer); + float totalTime = (float)(finalFrame - firstFrame)/(float)fps; + boolean done = false; + while (!done) { + int token; + try { + token = tokenizer.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + switch (tokenizer.ttype) { + case StreamTokenizer.TT_EOF: + done = true; + break; + case StreamTokenizer.TT_WORD: + debugOutputLn(VALUES, " String = " + tokenizer.sval); + if (tokenizer.sval.equals("AddNullObject")) { + LwsObject obj = + new LwsObject(tokenizer, false, + firstFrame, + finalFrame, totalTime, + this, + debugPrinter.getValidOutput()); + obj.createJava3dObject(null, loadBehaviors); + objectList.addElement(obj); + } + else if (tokenizer.sval.equals("LoadObject")) { + String filename = getString(tokenizer); + tokenizer.pushBack(); // push filename token back + debugOutputLn(TIME, "loading " + filename + " at " + + System.currentTimeMillis()); + LwsObject obj = new LwsObject(tokenizer, true, + firstFrame, + finalFrame, totalTime, + this, + debugPrinter.getValidOutput()); + debugOutputLn(TIME, "done loading at " + + System.currentTimeMillis()); + LwsObject cloneObject = null; + for (Enumeration e = objectList.elements() ; + e.hasMoreElements() ;) { + LwsObject tmpObj = (LwsObject)e.nextElement(); + if (tmpObj.fileName != null && + tmpObj.fileName.equals(filename)) { + cloneObject = tmpObj; + break; + } + } + obj.createJava3dObject(cloneObject, loadBehaviors); + objectList.addElement(obj); + } + else if (tokenizer.sval.equals("AmbientColor")) { + ambientColor.x = (float)getNumber(tokenizer)/255f; + ambientColor.y = (float)getNumber(tokenizer)/255f; + ambientColor.z = (float)getNumber(tokenizer)/255f; + } + else if (tokenizer.sval.equals("AmbIntensity")) { + // TODO: must be able to handle envelopes here + float intensity = (float)getNumber(tokenizer); + ambientColor.x *= intensity; + ambientColor.y *= intensity; + ambientColor.z *= intensity; + } + else if (tokenizer.sval.equals("AddLight")) { + LwsLight light = + new LwsLight(tokenizer, + finalFrame, totalTime, + debugPrinter.getValidOutput()); + light.createJava3dObject(loadBehaviors); + lightList.addElement(light); + } + else if (tokenizer.sval.equals("ShowCamera")) { + camera = new LwsCamera(tokenizer, firstFrame, + finalFrame, totalTime, + debugPrinter.getValidOutput()); + camera.createJava3dObject(loadBehaviors); + } + else if (tokenizer.sval.equals("FogType")) { + int fogType = (int)getNumber(tokenizer); + if (fogType != 0) { + fog = new LwsFog(tokenizer, + debugPrinter.getValidOutput()); + fog.createJava3dObject(); + } + } + else if (tokenizer.sval.equals("SolidBackdrop")) { + background = + new LwsBackground(tokenizer, + debugPrinter.getValidOutput()); + background.createJava3dObject(); + } + break; + default: + debugOutputLn(VALUES, " Unknown ttype, token = " + + tokenizer.ttype + ", " + token); + break; + } + } + + // Set up scene groups and parent objects appropriately + sceneGroupNode = new BranchGroup(); + sceneBehaviors = new Vector(); + parentObjects(); + constructScene(); + + return scene; + + } + + + /** + * This method creates the Scene (actually SceneBase) data structure + * and adds all appropriate items to it. This is the data structure + * that the user will get back from the load() call and inquire to + * get data from the scene. + */ + void constructScene() { + + // Construct Scene data structure + scene = new SceneBase(); + + if ((loadFlags & Loader.LOAD_LIGHT_NODES) != 0) { + addLights(); + addAmbient(); + } + + if ((loadFlags & Loader.LOAD_FOG_NODES) != 0) + addFog(); + + if ((loadFlags & Loader.LOAD_BACKGROUND_NODES) != 0) + addBackground(); + + if ((loadFlags & Loader.LOAD_VIEW_GROUPS) != 0) + addCamera(); + + if (loadBehaviors != 0) + addBehaviors(); + + scene.setSceneGroup(sceneGroupNode); + + // now add named objects to the scenes name table + for (Enumeration e = objectList.elements(); e.hasMoreElements() ;) { + + LwsObject obj = (LwsObject)e.nextElement(); + if (obj.fileName != null) + scene.addNamedObject(obj.fileName,(Object)obj.getObjectNode()); + else if (obj.objName != null) + scene.addNamedObject(obj.objName,(Object)obj.getObjectNode()); + + } + } + + + /** + * Creates a url for internal use. This method is not currently + * used (url's are ignored for this loader; only filenames work) + */ + void setInternalBaseUrl(URL url) { +// System.out.println("setInternalBaseUrl url = " + url); + java.util.StringTokenizer stok = + new java.util.StringTokenizer(url.toString(), +// java.io.File.separator); + "\\/"); + int tocount = stok.countTokens()-1; + StringBuffer sb = new StringBuffer(80); + for(int ji = 0; ji < tocount ; ji++) { + String a = stok.nextToken(); + if((ji == 0) && //(!a.equals("file:"))) { + (!a.regionMatches(true, 0, "file:", 0, 5))) { + sb.append(a); + // urls use / on windows also +// sb.append(java.io.File.separator); +// sb.append(java.io.File.separator); + sb.append('/'); + sb.append('/'); + } else { + sb.append(a); + // urls use / on windows also +// sb.append( java.io.File.separator ); + sb.append('/'); + } + } + internalBaseUrl = sb.toString(); +// System.out.println("internalBaseUrl = " + internalBaseUrl); + } + + /** + * Standardizes the filename for use in the loader + */ + void setInternalBasePath(String fileName) { + java.util.StringTokenizer stok = + new java.util.StringTokenizer(fileName, + java.io.File.separator); + int tocount = stok.countTokens()-1; + StringBuffer sb = new StringBuffer(80); + if (fileName!= null && + fileName.startsWith(java.io.File.separator)) + sb.append(java.io.File.separator); + for(int ji = 0; ji < tocount ; ji++) { + String a = stok.nextToken(); + sb.append(a); + sb.append( java.io.File.separator ); + } + internalBasePath = sb.toString(); + } + + String getInternalBasePath() { + return internalBasePath; + } + + String getInternalBaseUrl() { + return internalBaseUrl; + } + + int getFileType() { + return fileType; + } + + /** + * This method parents all objects in the scene appropriately. If + * the scen file specifies a Parent node for the object, then the + * object is parented to that node. If not, then the object is + * parented to the scene's root. + */ + void parentObjects() { + debugOutputLn(TRACE, "parentObjects()"); + for (Enumeration e = objectList.elements(); e.hasMoreElements(); ) { + + LwsObject obj = (LwsObject)e.nextElement(); + if (obj.getParent() != -1) { + + LwsObject parent = (LwsObject) + objectList.elementAt(obj.getParent() - 1); + parent.addChild(obj); + debugOutputLn(VALUES, "added child successfully"); + + } else { + + if (obj.getObjectNode() != null) + sceneGroupNode.addChild(obj.getObjectNode()); + + } + + // Collect all behaviors + if (loadBehaviors != 0) { + if (!(obj.getObjectBehaviors()).isEmpty()) { + sceneBehaviors.addAll(obj.getObjectBehaviors()); + + } + } + } + + debugOutputLn(LINE_TRACE, "Done with parentObjects()"); + + } + + + /** + * This method sets the base URL name for data files + * associated with the file passed into the load(URL) method. + * The basePath should be null by default, which is an + * indicator to the loader that it should look for any + * associated files starting from the same directory as the + * file passed into the load(URL) method. + */ + public void setBaseUrl(URL url) { + baseUrl = url; + } + + /** + * This method sets the base path to be used when searching for all + * data files within a Lightwave scene. + */ + public void setBasePath(String pathName) { + // This routine standardizes path names so that all pathnames + // will have standard file separators, they'll end in a separator + // character, and if the user passes in null or "" (meaning to + // set the current directory as the base path), this will become + // "./" (or ".\") + basePath = pathName; + if (basePath == null || basePath == "") + basePath = "." + java.io.File.separator; + basePath = basePath.replace('/', java.io.File.separatorChar); + basePath = basePath.replace('\\', java.io.File.separatorChar); + if (!basePath.endsWith(java.io.File.separator)) + basePath = basePath + java.io.File.separator; + } + + /** + * Returns the current base URL setting. + */ + public URL getBaseUrl() { + return baseUrl; + } + + /** + * Returns the current base path setting. + */ + public String getBasePath() { + return basePath; + } + + /** + * This method sets the load flags for the file. The flags should + * equal 0 by default (which tells the loader to only load geometry). + */ + public void setFlags(int flags) { + loadFlags = flags; + } + + /** + * Returns the current loading flags setting. + */ + public int getFlags() { + return loadFlags; + } + + + + /** + * getObject() iterates through the objectList checking the given + * name against the fileName and objectName of each object in turn. + * For the filename, it carves off the pathname and just checks the + * final name (e.g., "missile.lwo"). + * If name has []'s at the end, it will use the number inside those + * brackets to pick which object out of an ordered set it will + * send back (objectList is created in the order that objects + * exist in the file, so this order should correspond to the order + * specified by the user). If no []'s exist, just pass back the + * first one encountered that matches. + */ + public TransformGroup getObject(String name) { + debugOutputLn(TRACE, "getObject()"); + int indexNumber = -1; + int currentObjectCount = 0; + String subobjectName = name; + if (name.indexOf("[") != -1) { + // caller wants specifically numbered subjbect; get that number + int bracketsIndex = name.indexOf("["); + subobjectName = name.substring(0, bracketsIndex); + String bracketsString = name.substring(bracketsIndex); + int bracketEndIndex = bracketsString.indexOf("]"); + String indexString = bracketsString.substring(1, bracketEndIndex); + indexNumber = (new Integer(indexString)).intValue(); + } + for (Enumeration e = objectList.elements() ; + e.hasMoreElements() ;) { + LwsObject tempObj = (LwsObject)e.nextElement(); + debugOutputLn(VALUES, "tempObj, file, objname = " + + tempObj + tempObj.fileName + + tempObj.objName); + if ((tempObj.fileName != null && + tempObj.fileName.indexOf(subobjectName) != -1) || + (tempObj.objName != null && + tempObj.objName.indexOf(subobjectName) != -1)) { + if (indexNumber < 0 || + indexNumber == currentObjectCount) + return tempObj.getObjectNode(); + else + currentObjectCount++; + } + } + debugOutputLn(VALUES, " no luck - wanted " + + name + " returning null"); + return null; + } + + + /** + * This method sets up the StreamTokenizer for the scene file. Note + * that we're not parsing numbers as numbers because the tokenizer + * does not interpret scientific notation correctly. + */ + void setupTokenizer(StreamTokenizer tokenizer) { + tokenizer.resetSyntax(); + tokenizer.wordChars('a', 'z'); + tokenizer.wordChars('A', 'Z'); + tokenizer.wordChars(128 + 32, 255); + tokenizer.whitespaceChars(0, ' '); + tokenizer.commentChar('/'); + tokenizer.quoteChar('"'); + tokenizer.quoteChar('\''); + tokenizer.wordChars('0', '9'); + tokenizer.wordChars('.', '.'); + tokenizer.wordChars('-', '-'); + tokenizer.wordChars('/', '/'); + tokenizer.wordChars('\\', '\\'); + tokenizer.wordChars('_', '_'); + tokenizer.wordChars('&', '&'); + tokenizer.ordinaryChar('('); + tokenizer.ordinaryChar(')'); + tokenizer.whitespaceChars('\r', '\r'); + + // add ':' as wordchar so urls will work + tokenizer.wordChars(':', ':'); + // add '~' as wordchar for urls + tokenizer.wordChars('~', '~'); + } + + /** + * Adds Ambient lighting effects to the scene + */ + void addAmbient() { + AmbientLight aLgt = new AmbientLight(ambientColor); + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 100000.0); + aLgt.setInfluencingBounds(bounds); + sceneGroupNode.addChild(aLgt); + // scope ambient light to the lw3d scene + aLgt.addScope(sceneGroupNode); + scene.addLightNode(aLgt); + } + + /** + * Add any defined lights to the java3d scene + */ + void addLights() { + // Add lights to the scene + for (Enumeration e1 = lightList.elements(); e1.hasMoreElements(); ) { + + debugOutputLn(LINE_TRACE, "adding light to scene group"); + LwsLight light = (LwsLight)e1.nextElement(); + + if (light.getObjectNode() != null) { + // scope light to the lw3d scene + light.getLight().addScope(sceneGroupNode); + + if (light.getParent() != -1) { + LwsObject parent = (LwsObject) + objectList.elementAt(light.getParent() - 1); + parent.addChild(light); + } + else { // No parent - add to scene group + sceneGroupNode.addChild(light.getObjectNode()); + + } + + // collect behaviors if LOAD_BEHAVIOR_NODES is set + if (loadBehaviors != 0) { + if (!(light.getObjectBehaviors()).isEmpty()) + sceneBehaviors.addAll(light.getObjectBehaviors()); + + } + + scene.addLightNode(light.getLight()); + } + else + debugOutputLn(LINE_TRACE, "light object null?"); + } + } + + /** + * Adds the Camera's transform group to the scene, either by parenting + * it to the appropriate object or by adding it to the scene root. + * To use this camera data, users can request the camera/view data + * for the scene and can then insert a ViewPlatform in the transform group. + */ + void addCamera() { + // Add camera effects to scene. + if (camera != null) { + if (camera.getParent() != -1) { + debugOutputLn(VALUES, "camera parent = " + + camera.getParent()); + LwsObject parent = (LwsObject) + objectList.elementAt(camera.getParent() - 1); + parent.addChild(camera); + debugOutputLn(VALUES, "added child successfully"); + } + else { + sceneGroupNode.addChild(camera.getObjectNode()); + + } + + // collect behaviors if LOAD_BEHAVIOR_NODES is set + if (loadBehaviors != 0) { + if (!(camera.getObjectBehaviors()).isEmpty()) + sceneBehaviors.addAll(camera.getObjectBehaviors()); + } + + scene.addViewGroup(camera.getObjectNode()); + } + } + + /** + * Add appropriate fog effects to the scene + */ + void addFog() { + if (fog != null) { + Fog fogNode = fog.getObjectNode(); + if (fogNode != null) { + sceneGroupNode.addChild(fogNode); + scene.addFogNode(fogNode); + } + } + } + + /** + * Add the behaviors to the scene + */ + void addBehaviors() { + if (!sceneBehaviors.isEmpty()) { + Enumeration e = sceneBehaviors.elements(); + while (e.hasMoreElements()) { + scene.addBehaviorNode((Behavior)e.nextElement()); + } + } + } + + /** + * Add appropriate background effects to the scene. Note that the java3d + * background may not have all of the information of the lw3d background, + * as the loader does not currently process items such as gradients between + * the horizon and sky colors + */ + void addBackground() { + if (background != null) { + Background bgNode = background.getObjectNode(); + if (bgNode != null) { + sceneGroupNode.addChild(bgNode); + scene.addBackgroundNode(bgNode); + } + } + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwLightObject.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwLightObject.java new file mode 100644 index 0000000..ce61df2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwLightObject.java @@ -0,0 +1,100 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import javax.vecmath.Color3f; +import javax.media.j3d.Light; + + +/** + * This class is used to set the Light Intensity value according to the + * keyframe value of the envelope for that light. The class is used in + * conjunction with LightIntensityPathInterpolator, which uses this + * class as the target of its interpolations. + */ + +class LwLightObject { + + float intensity; + Color3f color; + Light theLight; + + LwLightObject(Light theLight, float intensity, Color3f color) { + this.intensity = intensity; + this.color = color; + this.theLight = theLight; + } + + void setIntensity(float intensity) { + Color3f newLightColor = new Color3f(color.x * intensity, + color.y * intensity, + color.z * intensity); + if (theLight != null) + theLight.setColor(newLightColor); + this.intensity = intensity; + } + + void setColor(Color3f color) { + Color3f newLightColor = new Color3f(color.x * intensity, + color.y * intensity, + color.z * intensity); + if (theLight != null) + theLight.setColor(newLightColor); + this.color = color; + } + + void setLight(Light l) { + theLight = l; + Color3f newLightColor = new Color3f(color.x * intensity, + color.y * intensity, + color.z * intensity); + if (theLight != null) + theLight.setColor(newLightColor); + } +} + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwoParser.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoParser.java new file mode 100644 index 0000000..40f2df0 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoParser.java @@ -0,0 +1,424 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Vector; +import java.util.Date; +import java.util.Enumeration; +import com.sun.j3d.loaders.lw3d.LWOBFileReader; +import com.sun.j3d.internal.J3dUtilsI18N; +import java.net.*; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.IncorrectFormatException; + + +/** + * This class is responsible for parsing a binary Object file and storing + * the data. This class is not called directly, but is the parent class of + * J3dLwoObject. The subclass calls this class to parse the file, then it + * turns the resulting data into Java3D objects. LwoObject instantiates + * an LWOBFileReader object to parse the data. Then the class creates a + * list of ShapeHolder objects to hold all of the vertex/facet data and + * surface references and creates a list of LwoSurface objects to hold + * the data for each surface.
+ * Rather than describe in detail how the file is parsed for each method, + * I advise the user of this code to understand the lw3d file format + * specs, which are pretty clear. + */ + +class LwoParser extends ParserObject { + + LWOBFileReader theReader; + int currLength; + float coordsArray[]; + float normalCoordsArray[]; + int facetIndicesArray[]; + int facetSizesArray[]; + int normalIndicesArray[]; + int red = 255, green = 255, blue = 255; + float diffuse = 0.0f, specular = 0.0f, transparency = 0.0f, luminosity = 0.0f; + int gloss = 128; + Vector surfNameList = null; + Vector surfaceList = new Vector(200); + Vector shapeList = new Vector(200); + + /** + * Constructor: Creates file reader and calls parseFile() to actually + * read the file and grab the data + */ + LwoParser(String fileName, int debugVals) + throws FileNotFoundException { + + super(debugVals); + debugOutputLn(TRACE, "parser()"); + long start = System.currentTimeMillis(); + theReader = new LWOBFileReader(fileName); + debugOutputLn(TIME, " file opened in " + + (System.currentTimeMillis() - start)); + parseFile(); + } + + LwoParser(URL url, int debugVals) + throws FileNotFoundException { + super(debugVals); + debugOutputLn(TRACE, "parser()"); + try { + long start = System.currentTimeMillis(); + theReader = new LWOBFileReader(url); + debugOutputLn(TIME, " file opened in " + + (System.currentTimeMillis() - start)); + } + catch (IOException ex) { + throw new FileNotFoundException(url.toString()); + } + parseFile(); + } + + + /** + * Detail polygons are currently not implemented by this loader. Their + * structure in geometry files is a bit complex, so there's this separate + * method for simply parsing through and ignoring the data for detail + * polygons + */ + int skipDetailPolygons(int numPolys) throws ParsingErrorException { + debugOutputLn(TRACE, "skipDetailPolygons(), numPolys = " + numPolys); + int lengthRead = 0; + int vert; + + try { + for (int polyNum = 0; polyNum < numPolys; ++polyNum) { + debugOutputLn(VALUES, "polyNum = " + polyNum); + int numVerts = theReader.getShortInt(); + theReader.skip(numVerts * 2 + 2); // skip indices plus surf + lengthRead += (numVerts * 2) + 4; // increment counter + } + } + catch (IOException e) { + debugOutputLn(EXCEPTION, "Exception in reading detail polys: " + e); + throw new ParsingErrorException(e.getMessage()); + } + return lengthRead; + } + + /** + * Returns already-existing ShapeHolder if one exists with the same + * surface and the same geometry type (point, line, or poly) + */ + ShapeHolder getAppropriateShape(int numSurf, int numVerts) { + for (Enumeration e = shapeList.elements(); + e.hasMoreElements() ;) { + ShapeHolder shape = (ShapeHolder)e.nextElement(); + if (shape.numSurf == numSurf) + if (shape.numVerts == numVerts || + (shape.numVerts > 3 && + numVerts > 3)) + return shape; + } + return null; + } + + + /** + * Parse the file for all the data for a POLS object (polygon + * description) + */ + void getPols(int length) { + debugOutputLn(TRACE, "getPols(len), len = " + length); + int vert; + int lengthRead = 0; + int prevNumVerts = -1; + int prevNumSurf = 0; + Vector facetSizesList; + int facetIndicesArray[]; + facetSizesList = + new Vector(length/6); // worst case size (every poly one vert) + // Note that our array sizes are hardcoded because we don't + // know until we're done how large they will be + facetIndicesArray = new int[length/2]; + ShapeHolder shape = new ShapeHolder(debugPrinter.getValidOutput()); + debugOutputLn(VALUES, "new shape = " + shape); + shape.coordsArray = coordsArray; + shape.facetSizesList = facetSizesList; + //shape.facetIndicesList = facetIndicesList; + shape.facetIndicesArray = facetIndicesArray; + shapeList.addElement(shape); + + //long startTime = (new Date()).getTime(); + boolean firstTime = true; + while (lengthRead < length) { + int numVerts = theReader.getShortInt(); + lengthRead += 2; + int intArray[] = new int[numVerts]; + for (int i = 0; i < numVerts; ++i) { + intArray[i] = theReader.getShortInt(); + lengthRead += 2; + } + + int numSurf = theReader.getShortInt(); + lengthRead += 2; + long startTimeBuff = 0, startTimeList = 0; + if (!firstTime && + (numSurf != prevNumSurf || + ((numVerts != prevNumVerts) && + ((prevNumVerts < 3) || + (numVerts < 3))))) { + // If above true, then start new shape + shape = getAppropriateShape(numSurf, numVerts); + if (shape == null) { + //debugOutputLn(LINE_TRACE, "Starting new shape"); + facetSizesList = new Vector(length/6); + facetIndicesArray = new int[length/2]; + shape = new ShapeHolder(debugPrinter.getValidOutput()); + shape.coordsArray = coordsArray; + shape.facetSizesList = facetSizesList; + //shape.facetIndicesList = facetIndicesList; + shape.facetIndicesArray = facetIndicesArray; + shape.numSurf = numSurf; + shape.numVerts = numVerts; + shapeList.addElement(shape); + } + else { + facetSizesList = shape.facetSizesList; + facetIndicesArray = shape.facetIndicesArray; + } + } + else { + shape.numSurf = numSurf; + shape.numVerts = numVerts; + } + prevNumVerts = numVerts; + prevNumSurf = numSurf; + facetSizesList.addElement(new Integer(numVerts)); + + int currPtr = 0; + System.arraycopy(intArray, 0, + facetIndicesArray, shape.currentNumIndices, + numVerts); + shape.currentNumIndices += numVerts; + if (numSurf < 0) { // neg number means detail poly + int numPolys = theReader.getShortInt(); + lengthRead += skipDetailPolygons(numPolys); + shape.numSurf = ~shape.numSurf & 0xffff; + if (shape.numSurf == 0) + shape.numSurf = 1; // Can't have surface = 0 + } + firstTime = false; + } + } + + /** + * Parses file to get the names of all surfaces. Each polygon will + * be associated with a particular surface number, which is the index + * number of these names + */ + void getSrfs(int length) { + String surfName = new String(); + surfNameList = new Vector(length/2); // worst case size (each name 2 chars long) + int lengthRead = 0; + int stopMarker = theReader.getMarker() + length; + + int surfIndex = 0; + while (theReader.getMarker() < stopMarker) { + debugOutputLn(VALUES, "marker, stop = " + + theReader.getMarker() + ", " + stopMarker); + debugOutputLn(LINE_TRACE, "About to call getString"); + surfName = theReader.getString(); + debugOutputLn(VALUES, "Surfname = " + surfName); + surfNameList.addElement(surfName); + } + } + + /** + * Parses file to get all vertices + */ + void getPnts(int length) throws ParsingErrorException { + int numVerts = length / 12; + float x, y, z; + + coordsArray = new float[numVerts*3]; + theReader.getVerts(coordsArray, numVerts); + } + + /** + * Creates new LwoSurface object that parses file and gets all + * surface parameters for a particular surface + */ + void getSurf(int length) throws FileNotFoundException { + debugOutputLn(TRACE, "getSurf()"); + + // Create LwoSurface object to read and hold each surface, then + // store that surface in a vector of all surfaces. + + LwoSurface surf = new LwoSurface(theReader, length, + debugPrinter.getValidOutput()); + surfaceList.addElement(surf); + } + + + /** + * parses entire file. + * return -1 on error or 0 on completion + */ + int parseFile() throws FileNotFoundException, IncorrectFormatException { + debugOutputLn(TRACE, "parseFile()"); + int length = 0; + int lengthRead = 0; + int fileLength = 100000; + + long loopStartTime = System.currentTimeMillis(); + // Every parsing unit begins with a four character string + String tokenString = theReader.getToken(); + + while (!(tokenString == null) && + lengthRead < fileLength) { + long startTime = System.currentTimeMillis(); + // Based on value of tokenString, go to correct parsing method + length = theReader.getInt(); + + lengthRead += 4; + //debugOutputLn(VALUES, "length, lengthRead, fileLength = " + + // length + ", " + lengthRead + ", " + fileLength); + //debugOutputLn(VALUES, "LWOB marker is at: " + theReader.getMarker()); + + if (tokenString.equals("FORM")) { + //debugOutputLn(LINE_TRACE, "got a form"); + fileLength = length + 4; + length = 0; + tokenString = theReader.getToken(); + lengthRead += 4; + if (!tokenString.equals("LWOB")) + throw new IncorrectFormatException( + "File not of FORM-length-LWOB format"); + } + else if (tokenString.equals("PNTS")) { + //debugOutputLn(LINE_TRACE, "PNTS"); + getPnts(length); + debugOutputLn(TIME, "done with " + tokenString + " in " + + (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("POLS")) { + //debugOutputLn(LINE_TRACE, "POLS"); + getPols(length); + debugOutputLn(TIME, "done with " + tokenString + " in " + + (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("SRFS")) { + //debugOutputLn(LINE_TRACE, "SRFS"); + getSrfs(length); + debugOutputLn(TIME, "done with " + tokenString + " in " + + (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("CRVS")) { + //debugOutputLn(LINE_TRACE, "CRVS"); + theReader.skipLength(length); + //debugOutputLn(TIME, "done with " + tokenString + " in " + + // (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("PCHS")) { + //debugOutputLn(LINE_TRACE, "PCHS"); + theReader.skipLength(length); + //debugOutputLn(TIME, "done with " + tokenString + " in " + + // (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("SURF")) { + //debugOutputLn(LINE_TRACE, "SURF"); + getSurf(length); + //debugOutputLn(VALUES, "Done with SURF, marker = " + theReader.getMarker()); + debugOutputLn(TIME, "done with " + tokenString + " in " + + (System.currentTimeMillis() - startTime)); + } + else if (tokenString.equals("LWOB")) { + //debugOutputLn(LINE_TRACE, "LWOB"); + } + else { + //debugOutputLn(LINE_TRACE, "Unknown object = " + tokenString); + theReader.skipLength(length); + //debugOutputLn(TIME, "done with " + tokenString + " in " + + // (System.currentTimeMillis() - startTime)); + } + lengthRead += length; + if (lengthRead < fileLength) { + //debugOutputLn(VALUES, "end of parseFile, length, lengthRead = " + + // length + ", " + lengthRead); + tokenString = theReader.getToken(); + lengthRead += 4; + //debugOutputLn(VALUES, "just got tokenString = " + tokenString); + } + } + debugOutputLn(TIME, "done with parseFile in " + + (System.currentTimeMillis() - loopStartTime)); + return 0; + } + + /** + * This method is used only for testing + */ + static void main(String[] args) { + String fileName; + if (args.length == 0) + fileName = "cube.obj"; + else + fileName = args[0]; + + try { + LwoParser theParser = new LwoParser(fileName, 0); + } + catch (FileNotFoundException e) { + System.err.println(e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } +} + + + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwoSurface.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoSurface.java new file mode 100644 index 0000000..813b5ad --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoSurface.java @@ -0,0 +1,316 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.Image; +import java.io.IOException; +import java.util.Vector; +import java.util.Enumeration; +import javax.vecmath.Color3f; +import javax.vecmath.Vector3f; +import com.sun.j3d.loaders.lw3d.LWOBFileReader; +import com.sun.j3d.internal.J3dUtilsI18N; +import java.io.FileNotFoundException; +import com.sun.j3d.loaders.IncorrectFormatException; +import com.sun.j3d.loaders.ParsingErrorException; + + +/** + * This class is responsible for retrieving the surface parameters for a + * particular surface from a binary Object file and turning that data + * into Java3D data. These surface parameters include + * diffuse/specular/emissive properties, color, shininess, transparency, + * and textures. For textures, this class instantiates a LwoTexture object + * to parse that data and turn it into Java3D texture data. + */ + +class LwoSurface extends ParserObject { + + LWOBFileReader theReader; + int red = 255, green = 255, blue = 255; + float diffuse = 0.0f, specular = 0.0f, transparency = 0.0f, luminosity = 0.0f; + float creaseAngle = 0.0f; + int gloss = 128; + Color3f color, diffuseColor, specularColor, emissiveColor; + float shininess; + Image theImage = null; + Vector3f textureCenter = null, textureSize = null; + int textureAxis; + String surfName; + Vector textureList = new Vector(); + + /** + * Constructor that parses surface data from the binary file + * and creates the necessary Java3d objects + */ + LwoSurface(LWOBFileReader reader, int length, int debugVals) + throws FileNotFoundException { + + super(debugVals); + debugOutputLn(TRACE, "LwoSurface()"); + theReader = reader; + getSurf(length); + setJ3dColors(); + } + + /** + * Creates Java3d color objects from the lw3d surface data + */ + void setJ3dColors() { + color = new Color3f((float)red/(float)255, + (float)green/(float)255, + (float)blue/(float)255); + diffuseColor = new Color3f(diffuse*color.x, + diffuse*color.y, + diffuse*color.z); + specularColor = new Color3f(specular*color.x, + specular*color.y, + specular*color.z); + emissiveColor = new Color3f(luminosity*color.x, + luminosity*color.y, + luminosity*color.z); + shininess = (float)(128.0 * ((float)gloss/1024.0)); + } + + Color3f getColor() { + return color; + } + + Color3f getDiffuseColor() { + return diffuseColor; + } + + Color3f getSpecularColor() { + return specularColor; + } + + Color3f getEmissiveColor() { + return emissiveColor; + } + + float getShininess() { + return shininess; + } + + float getCreaseAngle() { + return creaseAngle; + } + + /** + * Returns the LwoTexture for the surface, if any is defined. Note that + * lw3d allows users to define multiple textures for any surface, which + * is not possible through Java3d. Therefore, we just grab the first + * texture in any list of textures for a surface + */ + LwoTexture getTexture() { + debugOutputLn(TRACE, "getTexture()"); + try { + if (textureList.isEmpty()) { + return null; + } + else { + return (LwoTexture)textureList.elementAt(0); + } + } + catch (ArrayIndexOutOfBoundsException e) { + debugOutputLn(EXCEPTION, + "getTexture(), exception returning first element: " + e); + return null; + } + } + + String getSurfName() { + return surfName; + } + + float getTransparency() { + return transparency; + } + + /** + * Parses the binary file and gets all data for this surface + */ + void getSurf(int length) + throws FileNotFoundException, IncorrectFormatException, + ParsingErrorException { + + debugOutputLn(TRACE, "getSurf()"); + + // These "got*" booleans are to help use read the best version of + // the data - the float values for these parameters should take + // precedence over the other format + boolean gotLuminosityFloat = false; + boolean gotTransparencyFloat = false; + boolean gotDiffuseFloat = false; + boolean gotSpecularFloat = false; + int surfStopMarker = theReader.getMarker() + length; + surfName = theReader.getString(); + String tokenString = theReader.getToken(); + while (!(tokenString == null) && + theReader.getMarker() < surfStopMarker) { + debugOutputLn(VALUES, " tokenString = " + tokenString); + debugOutputLn(VALUES, " marker, stop = " + + theReader.getMarker() + ", " + surfStopMarker); + String textureToken = null; + int fieldLength = theReader.getShortInt(); + debugOutputLn(VALUES, " fl = " + fieldLength); + + if (tokenString.equals("COLR")) { + debugOutputLn(LINE_TRACE, " COLR"); + try { + red = theReader.read(); + green = theReader.read(); + blue = theReader.read(); + theReader.read(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + if (fieldLength != 4) + throw new IncorrectFormatException( + J3dUtilsI18N.getString("LwoSurface0")); + } + else if (tokenString.equals("FLAG")) { + debugOutputLn(LINE_TRACE, " FLAG"); + theReader.skipLength(fieldLength); + } + else if (tokenString.equals("VLUM")) { + debugOutputLn(LINE_TRACE, " VLUM"); + luminosity = theReader.getFloat(); + gotLuminosityFloat = true; + } + else if (tokenString.equals("LUMI")) { + debugOutputLn(LINE_TRACE, " LUMI"); + if (gotLuminosityFloat) + theReader.skipLength(fieldLength); + else + luminosity = (float)(theReader.getShortInt())/255; + } + else if (tokenString.equals("VDIF")) { + debugOutputLn(LINE_TRACE, " VDIF"); + if (fieldLength != 4) + throw new IncorrectFormatException("VDIF problem"); + diffuse = theReader.getFloat(); + gotDiffuseFloat = true; + debugOutputLn(VALUES, "diff = " + diffuse); + } + else if (tokenString.equals("DIFF")) { + debugOutputLn(LINE_TRACE, " DIFF"); + if (gotDiffuseFloat) + theReader.skipLength(fieldLength); + else + diffuse = (float)theReader.getShortInt()/255; + } + else if (tokenString.equals("VTRN")) { + debugOutputLn(LINE_TRACE, " VTRN"); + transparency = theReader.getFloat(); + gotTransparencyFloat = true; + } + else if (tokenString.equals("TRAN")) { + debugOutputLn(LINE_TRACE, " TRAN"); + if (gotTransparencyFloat) + theReader.skipLength(fieldLength); + else + transparency = (float)theReader.getShortInt()/255; + } + else if (tokenString.equals("VSPC")) { + debugOutputLn(LINE_TRACE, " VSPC"); + specular = theReader.getFloat(); + gotSpecularFloat = true; + debugOutputLn(VALUES, "spec = " + specular); + } + else if (tokenString.equals("SPEC")) { + debugOutputLn(LINE_TRACE, " SPEC"); + if (gotSpecularFloat) + theReader.skipLength(fieldLength); + else { + if (fieldLength == 4) // Bug in some LW versions + specular = (float)theReader.getInt()/255; + else + specular = (float)theReader.getShortInt()/255; + } + } + else if (tokenString.equals("GLOS")) { + debugOutputLn(LINE_TRACE, " GLOS"); + if (fieldLength == 4) + gloss = theReader.getInt(); + else + gloss = theReader.getShortInt(); + } + else if (tokenString.equals("SMAN")) { + debugOutputLn(LINE_TRACE, " SMAN"); + creaseAngle = theReader.getFloat(); + } + else if (tokenString.endsWith("TEX")) { + // Textures are complex - hand off this bit to the + // LwoTexture class + LwoTexture texture = + new LwoTexture(theReader, + surfStopMarker - theReader.getMarker(), + tokenString, + debugPrinter.getValidOutput()); + textureToken = texture.getNextToken(); + if (texture.isHandled()) + textureList.addElement(texture); + debugOutputLn(WARNING, "val = " + tokenString); + } + else { + debugOutputLn(WARNING, + "unrecognized token: " + tokenString); + theReader.skipLength(fieldLength); + } + if (theReader.getMarker() < surfStopMarker) { + if (textureToken == null) + tokenString = theReader.getToken(); + else + tokenString = textureToken; + } + } + } + +} + + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwoTexture.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoTexture.java new file mode 100644 index 0000000..57bbf7b --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwoTexture.java @@ -0,0 +1,284 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.Component; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.FileReader; +import java.io.File; +import java.io.IOException; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Hashtable; +import javax.vecmath.Color3f; +import javax.vecmath.Vector3f; +import com.sun.j3d.utils.image.TextureLoader; +import javax.media.j3d.Texture; +import javax.media.j3d.Texture2D; +import javax.media.j3d.ImageComponent; +import javax.media.j3d.ImageComponent2D; +import com.sun.j3d.loaders.lw3d.LWOBFileReader; +import java.io.FileNotFoundException; +import com.sun.j3d.loaders.ParsingErrorException; + +/** + * This class is responsible for parsing the binary data in an Object file + * that describes a texture for a particular surface and turning that data + * into Java3D texture data. If the texture is coming from a file (which + * is the only type of texture handled by the loader currently; other + * types of texture definitions are ignored), then this class instantiates + * a TargaReader object to read the data in that file. Once all of the + * data has been read, the class creates a Java3D Texture object by first + * scaling the image using the ImageScaler class (since all textures must + * have width/height = power of 2; Note: this functionality is now built + * into the TextureLoader class, so it could be removed from this loader) + * and then creating a Texture with that image. + */ + +class LwoTexture extends ParserObject { + + LWOBFileReader theReader; + int red = 255, green = 255, blue = 255; + Color3f color, diffuseColor, specularColor, emissiveColor; + Image theImage = null; + String imageFile = null; + Vector3f textureSize = new Vector3f(1f, 1f, 1f);; + Vector3f textureCenter = new Vector3f(0f, 0f, 0f); + int textureAxis; + int flags = 0; + String type; + String mappingType; + String nextToken = null; + static Hashtable imageTable = new Hashtable(); + static Hashtable textureTable = new Hashtable(); + + /** + * Constructor: calls readTexture() to parse the file and retrieve + * texture parameters + */ + LwoTexture(LWOBFileReader reader, int length, String typename, + int debugVals) throws FileNotFoundException { + super(debugVals); + debugOutputLn(TRACE, "Constructor"); + theReader = reader; + type = typename; + readTexture(length); + } + + String getNextToken() { + return nextToken; + } + + /** + * The loader currently only handles CTEX and DTEX texture types + * (These either represent the surface color like a decal (CTEX) + * or modify the diffuse color (DTEX) + */ + boolean isHandled() { + if ((type.equals("CTEX") || + type.equals("DTEX")) && + theImage != null) + return true; + debugOutputLn(LINE_TRACE, "failed isHandled(), type, theImage = " + + type + ", " + theImage); + return false; + } + + /** + * Return the actual Texture object associated with the current image. + * If we've already created a texture for this image, return that; + * otherwise create a new Texture + */ + Texture getTexture() { + debugOutputLn(TRACE, "getTexture()"); + if (theImage == null) + return null; + Texture2D t2d = (Texture2D)textureTable.get(theImage); + if (t2d == null) { + ImageScaler scaler = new ImageScaler((BufferedImage)theImage); + BufferedImage scaledImage = (BufferedImage)scaler.getScaledImage(); + TextureLoader tl = new TextureLoader(scaledImage); + t2d = (Texture2D)tl.getTexture(); + textureTable.put(theImage, t2d); + } + + return t2d; + } + + String getType() { + return type; + } + + Color3f getColor() { + return color; + } + + Image getImage() { + return theImage; + } + + Vector3f getTextureSize() { + return textureSize; + } + + int getTextureAxis() { + return textureAxis; + } + + Vector3f getTextureCenter() { + return textureCenter; + } + + String getMappingType() { + return mappingType; + } + + /** + * Parse the binary file to retrieve all texture parameters for this + * surface. If/when we encounter a TIMG parameter, which contains the + * filename of an image, then create a new TargaReader object to + * read that image file + */ + void readTexture(int length) + throws FileNotFoundException, ParsingErrorException { + + debugOutputLn(TRACE, "readTexture()"); + + int surfStopMarker = theReader.getMarker() + length; + mappingType = theReader.getString(); + debugOutputLn(VALUES, "mappingType = " + mappingType); + String tokenString = theReader.getToken(); + while (!(tokenString == null) && theReader.getMarker() < surfStopMarker) { + + debugOutputLn(VALUES, " tokenString = " + tokenString); + debugOutputLn(VALUES, " marker, stop = " + theReader.getMarker() + ", " + surfStopMarker); + + if (tokenString.endsWith("TEX") || + (!tokenString.startsWith("T") || tokenString.equals("TRAN"))) { + nextToken = tokenString; + return; + } + + int fieldLength = theReader.getShortInt(); + debugOutputLn(VALUES, " fl = " + fieldLength); + + if (tokenString.equals("TFLG")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + flags = theReader.getShortInt(); + textureAxis = flags & 0x07; + debugOutputLn(WARNING, "val = " + flags); + } + else if (tokenString.equals("TCLR")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + try { + red = theReader.read(); + green = theReader.read(); + blue = theReader.read(); + theReader.read(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + debugOutputLn(WARNING, "val = " + red + ", " + green + + ", " + blue); + } + else if (tokenString.equals("TIMG")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + imageFile = theReader.getString(); + debugOutputLn(VALUES, "imageFile = " + imageFile); + if (imageFile.indexOf("none") == -1) { + if ((theImage = + (Image)imageTable.get(imageFile)) == null) { + try { + TargaReader tr = + new TargaReader(imageFile, + debugPrinter.getValidOutput()); + theImage = tr.getImage(); + imageTable.put(imageFile, theImage); + } + catch (FileNotFoundException e) { + // Ignore texture if can't find it + debugOutputLn(WARNING, "Image File skipped: " + + imageFile); + } + } + } + debugOutputLn(WARNING, "val = __" + imageFile + "__"); + } + else if (tokenString.equals("TWRP")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + int widthWrap = theReader.getShortInt(); + int heightWrap = theReader.getShortInt(); + debugOutputLn(WARNING, "val = " + widthWrap + ", " + + heightWrap); + } + else if (tokenString.equals("TCTR")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + textureCenter.x = theReader.getFloat(); + textureCenter.y = theReader.getFloat(); + textureCenter.z = theReader.getFloat(); + debugOutputLn(WARNING, "val = " + textureCenter); + } + else if (tokenString.equals("TSIZ")) { + debugOutputLn(WARNING, "Not yet handling: " + tokenString); + textureSize.x = theReader.getFloat(); + textureSize.y = theReader.getFloat(); + textureSize.z = theReader.getFloat(); + debugOutputLn(WARNING, "val = " + textureSize); + } + else { + debugOutputLn(WARNING, + "unrecognized token: " + tokenString); + theReader.skipLength(fieldLength); + } + if (theReader.getMarker() < surfStopMarker) { + tokenString = theReader.getToken(); + } + } + } +} + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsBackground.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsBackground.java new file mode 100644 index 0000000..559857c --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsBackground.java @@ -0,0 +1,163 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.Enumeration; +import com.sun.j3d.loaders.ParsingErrorException; + + +/** + * This class creates a Background object (solid color only, no geometry) + * according to some of the data stored in a Scene file. Note: Lightwave + * defines much more complex backgrounds that the loader currently + * handles. It should be possible to se Background Geometry to handle + * most of these cases, if there's time and will to work on the problem. + */ + +class LwsBackground extends TextfileParser { + + // data from the file + int solidBackdrop; + Color3f color, zenithColor, skyColor, groundColor, nadirColor; + Background backgroundObject = null; + + + /** + * Constructor: parses stream and retrieves all Background-related data + */ + LwsBackground(StreamTokenizer st, int debugVals) + throws ParsingErrorException { + + debugPrinter.setValidOutput(debugVals); + debugOutput(TRACE, "LwsBackground()"); + color = new Color3f(0f, 0f, 0f); + zenithColor = new Color3f(0f, 0f, 0f); + skyColor = new Color3f(0f, 0f, 0f); + groundColor = new Color3f(0f, 0f, 0f); + nadirColor = new Color3f(0f, 0f, 0f); + + solidBackdrop = (int)getNumber(st); + while (!isCurrentToken(st, "FogType")) { + debugOutputLn(LINE_TRACE, "currentToken = " + st.sval); + + if (isCurrentToken(st, "BackdropColor")) { + color.x = (float)getNumber(st)/255f; + color.y = (float)getNumber(st)/255f; + color.z = (float)getNumber(st)/255f; + } + else if (isCurrentToken(st, "NadirColor")) { + nadirColor.x = (float)getNumber(st)/255f; + nadirColor.y = (float)getNumber(st)/255f; + nadirColor.z = (float)getNumber(st)/255f; + } + else if (isCurrentToken(st, "SkyColor")) { + skyColor.x = (float)getNumber(st)/255f; + skyColor.y = (float)getNumber(st)/255f; + skyColor.z = (float)getNumber(st)/255f; + } + else if (isCurrentToken(st, "GroundColor")) { + groundColor.x = (float)getNumber(st)/255f; + groundColor.y = (float)getNumber(st)/255f; + groundColor.z = (float)getNumber(st)/255f; + } + else if (isCurrentToken(st, "NadirColor")) { + nadirColor.x = (float)getNumber(st)/255f; + nadirColor.y = (float)getNumber(st)/255f; + nadirColor.z = (float)getNumber(st)/255f; + } + try { + st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + st.pushBack(); // push token back on stack + } + + /** + * Creates Java3d objects from the background data. Note that there + * are plenty of lw3d background attributes that the loader currently + * ignores. Some of these may best be handled by creating background + * geometry rather than a solid background color + */ + void createJava3dObject() { + // TODO: there are various attributes of + // backdrops that we're not currently handling. In + // particular, if the file calls for a gradient background + // (solidBackdrop == 0), we ignore the request and just + // create a solid background from the sky color instead. + // We should be able to do something with the + // various colors specified, perhaps by creating + // background geometry with the appropriate vertex + // colors? + + if (solidBackdrop != 0) { + backgroundObject = new Background(color); + debugOutput(VALUES, "Background color = " + color); + } + else { + backgroundObject = new Background(skyColor); + debugOutput(VALUES, "Background color = " + skyColor); + } + BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100000.0); + backgroundObject.setApplicationBounds(bounds); + } + + Background getObjectNode() { + return backgroundObject; + } + + void printVals() { + debugOutputLn(VALUES, " BACKGROUND vals: "); + } + + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsCamera.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsCamera.java new file mode 100644 index 0000000..afc2a53 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsCamera.java @@ -0,0 +1,179 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import java.util.Vector; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.Enumeration; +import com.sun.j3d.loaders.ParsingErrorException; + +/** + * This class parses the data in a Scene file related to the camera and + * creates Java3D TransformGroup that holds the data for positioning + * and orienting the view according to the camera specifications. + */ + +class LwsCamera extends TextfileParser implements LwsPrimitive { + + // data from the file + String fileName; + String objName; + LwsMotion motion; + int parent; + TransformGroup objectTransform; + Vector objectBehavior; + + /** + * Constructor: parses camera info and creates LwsMotion object for + * keyframe data + */ + LwsCamera(StreamTokenizer st, int firstFrame, + int totalFrames, float totalTime, + int debugVals) throws ParsingErrorException { + debugPrinter.setValidOutput(debugVals); + parent = -1; + getNumber(st); // Skip ShowCamera parameters + getNumber(st); + getAndCheckString(st, "CameraMotion"); + motion = new LwsMotion(st, firstFrame, totalFrames, totalTime, + debugPrinter.getValidOutput()); + + // TODO: buggy way to stop processing the camera. Should actually + // process required/optional fields in order and stop when there's + // no more. + + while (!isCurrentToken(st, "DepthOfField")) { + debugOutputLn(LINE_TRACE, "currentToken = " + st.sval); + + if (isCurrentToken(st, "ParentObject")) { + parent = (int)getNumber(st); + } + try { + st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + getNumber(st); // skip shadow type parameter + } + + /** + * Returns parent of the camera object + */ + int getParent() { + return parent; + } + + /** + * Creates Java3D items from the camera data. These objects consist + * of: a TransformGroup to hold the view platform, and the behaviors + * (if any) that act upon the view's TransformGroup. + */ + void createJava3dObject(int loadBehaviors) + { + Matrix4d mat = new Matrix4d(); + mat.setIdentity(); + // Set the node's transform matrix according to the first frame + // of the object's motion + LwsFrame firstFrame = motion.getFirstFrame(); + firstFrame.setMatrix(mat); + debugOutputLn(VALUES, " Camera Matrix = \n" + mat); + Transform3D t1 = new Transform3D(); + Matrix4d m = new Matrix4d(); + double scale = .1; + m.setColumn(0, scale, 0, 0, 0); // setScale not yet implemented + m.setColumn(1, 0, scale, 0, 0); + m.setColumn(2, 0, 0, scale, 0); + m.setColumn(3, 0, 0, 0, 1); + Transform3D scaleTrans = new Transform3D(m); + TransformGroup scaleGroup = new TransformGroup(scaleTrans); + scaleGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + scaleGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); + // mat.mul(m); + t1.set(mat); + objectTransform = new TransformGroup(t1); + objectTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + objectBehavior = new Vector();; + if (loadBehaviors != 0) { + motion.createJava3dBehaviors(objectTransform); + Behavior b = motion.getBehaviors(); + if (b != null) + objectBehavior.addElement(b); + } + } + + /** + * Returns TransformGroup of camera + */ + public TransformGroup getObjectNode() + { + return objectTransform; + } + + /** + * Returns animation behaviors for camera + */ + public Vector getObjectBehaviors() + { + debugOutputLn(TRACE, "getObjectBehaviors()"); + return objectBehavior; + } + + /** + * This is a debuggin utility, not currently activated. It prints + * out the camera values + */ + void printVals() + { + System.out.println(" objName = " + objName); + motion.printVals(); + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelope.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelope.java new file mode 100644 index 0000000..e2b994e --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelope.java @@ -0,0 +1,160 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.IncorrectFormatException; + +/** + * This class is a superclass for any implementation of envelopes; the + * only subclass currently is LwsEnvelopeLightIntensity. LwsEnvelope + * parses the data in a Scene file and extracts the envelope data. + */ + +class LwsEnvelope extends TextfileParser { + + // data from the file + String name; + LwsEnvelopeFrame frames[]; + int numFrames; + int numChannels; + boolean loop; + float totalTime; + int totalFrames; + Behavior behaviors; + + /** + * Constructor: calls getEnvelope() to parse the stream for the + * envelope data + */ + LwsEnvelope(StreamTokenizer st, int frames, float time) { + numFrames = 0; + totalTime = time; + totalFrames = frames; + name = getName(st); + getEnvelope(st); + } + + /** + * Parses the stream to retrieve all envelope data. Creates + * LwsEnvelopeFrame objects for each keyframe of the envelope + * (these frames differ slightly from LwsFrame objects because + * envelopes contain slightly different data) + */ + void getEnvelope(StreamTokenizer st) + throws IncorrectFormatException, ParsingErrorException + { + debugOutputLn(TRACE, "getEnvelope()"); + numChannels = (int)getNumber(st); + if (numChannels != 1) { + throw new IncorrectFormatException( + J3dUtilsI18N.getString("LwsEnvelope0")); + } + debugOutputLn(LINE_TRACE, "got channels"); + + numFrames = (int)getNumber(st); + frames = new LwsEnvelopeFrame[numFrames]; + debugOutputLn(VALUES, "got frames" + numFrames); + + for (int i = 0; i < numFrames; ++i) { + frames[i] = new LwsEnvelopeFrame(st); + } + debugOutput(LINE_TRACE, "got all frames"); + + try { + st.nextToken(); + while (!isCurrentToken(st, "EndBehavior")) { + // There is an undocumented "FrameOffset" in some files + st.nextToken(); + } + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + int repeatVal = (int)getNumber(st); + if (repeatVal == 1) + loop = false; + else + loop = true; + } + + /** + * This superclass does nothing here - if the loader understands + * how to deal with a particular type of envelope, it will use + * a subclass of LwsEnvelope that will override this method + */ + void createJava3dBehaviors(TransformGroup target) { + behaviors = null; + } + + Behavior getBehaviors() { + return behaviors; + } + + + LwsEnvelopeFrame getFirstFrame() { + if (numFrames > 0) + return frames[0]; + else + return null; + } + + + void printVals() { + debugOutputLn(VALUES, " name = " + name); + debugOutputLn(VALUES, " numChannels = " + numChannels); + debugOutputLn(VALUES, " numFrames = " + numFrames); + debugOutputLn(VALUES, " loop = " + loop); + for (int i = 0; i < numFrames; ++i) { + debugOutputLn(VALUES, " FRAME " + i); + frames[i].printVals(); + } + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeFrame.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeFrame.java new file mode 100644 index 0000000..587175f --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeFrame.java @@ -0,0 +1,105 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import javax.vecmath.Matrix4d; +import javax.vecmath.Vector3d; +import javax.vecmath.Point3f; + +/** + * This class represents one keyframe in an envelope sequence. + */ + +class LwsEnvelopeFrame extends TextfileParser { + + // data from the file + double value; + double frameNumber; + int linearValue; + double tension, continuity, bias; + + + /** + * Constructor: parses stream and stores data for one keyframe of + * an envelope sequence + */ + LwsEnvelopeFrame(StreamTokenizer st) { + value = getNumber(st); + debugOutputLn(VALUES, "value = " + value); + frameNumber = (int)getNumber(st); + linearValue = (int)getNumber(st); + debugOutputLn(VALUES, "framenum, linear " + frameNumber + " , " + linearValue); + tension = getNumber(st); + continuity = getNumber(st); + bias = getNumber(st); + debugOutputLn(VALUES, "tension, cont, bias = " + tension + ", " + continuity + ", " + bias); + //System.out.println(" FRAME VALS"); + //printVals(); + } + + + double getValue() { + return value; + } + + + double getFrameNum() { + return frameNumber; + } + + + void printVals() { + debugOutputLn(VALUES, " value = " + value); + debugOutputLn(VALUES, " frameNum = " + frameNumber); + debugOutputLn(VALUES, " lin = " + linearValue); + debugOutputLn(VALUES, " tension = " + tension); + debugOutputLn(VALUES, " continuity = " + continuity); + debugOutputLn(VALUES, " bias = " + bias); + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeLightIntensity.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeLightIntensity.java new file mode 100644 index 0000000..e241b5c --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsEnvelopeLightIntensity.java @@ -0,0 +1,153 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import javax.media.j3d.TransformGroup; + + +/** + * This class creates a LightIntensityPathInterpolator object from the + * keyframe-based envelope specified in a Scene file. + */ + +class LwsEnvelopeLightIntensity extends LwsEnvelope { + + + /** + * Constructor: Calls superclass, which will parse the stream + * and store the envelope data + */ + LwsEnvelopeLightIntensity(StreamTokenizer st, + int frames, float time) { + super(st, frames, time); + } + + /** + * Creates Java3d behaviors given the stored envelope data. The + * Behavior created is a LightIntensityPathInterpolator + */ + void createJava3dBehaviors(Object target) { + if (numFrames <= 1) + behaviors = null; + else { + long alphaAtOne = 0; + int loopCount; + if (loop) + loopCount = -1; + else + loopCount = 1; + // Note: hardcoded to always loop... + loopCount = -1; + debugOutputLn(VALUES, "totalTime = " + totalTime); + debugOutputLn(VALUES, "loopCount = " + loopCount); + float animTime = 1000.0f * totalTime * + (float)(frames[numFrames-1].getFrameNum()/(float)totalFrames); + debugOutputLn(VALUES, " anim time: " + animTime); + debugOutputLn(VALUES, " totalFrames = " + totalFrames); + debugOutputLn(VALUES, " lastFrame = " + + frames[numFrames-1].getFrameNum()); + if (!loop) + alphaAtOne = (long)(1000.0*totalTime - animTime); + Alpha theAlpha = + new Alpha(loopCount, Alpha.INCREASING_ENABLE, + 0, 0, (long)animTime, 0, + alphaAtOne, 0, 0, 0); + float knots[] = new float[numFrames]; + float values[] = new float[numFrames]; + for (int i=0; i < numFrames; ++i) { + values[i] = (float)frames[i].getValue(); + knots[i] = (float)(frames[i].getFrameNum())/ + (float)(frames[numFrames-1].getFrameNum()); + debugOutputLn(VALUES, "value, knot = " + + values[i] + ", " + knots[i]); + } + LightIntensityPathInterpolator l = new + LightIntensityPathInterpolator(theAlpha, + knots, + values, + target); + if (l != null) { + behaviors = l; + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000000.0); + behaviors.setSchedulingBounds(bounds); + ((TransformGroup)target).setCapability + (TransformGroup.ALLOW_TRANSFORM_WRITE); + ((TransformGroup)target).addChild(behaviors); + } + } + } + + + Behavior getBehaviors() { + return behaviors; + } + + + LwsEnvelopeFrame getFirstFrame() { + if (numFrames > 0) + return frames[0]; + else + return null; + } + + + void printVals() { + debugOutputLn(VALUES, " name = " + name); + debugOutputLn(VALUES, " numChannels = " + numChannels); + debugOutputLn(VALUES, " numFrames = " + numFrames); + debugOutputLn(VALUES, " loop = " + loop); + for (int i = 0; i < numFrames; ++i) { + debugOutputLn(VALUES, " FRAME " + i); + frames[i].printVals(); + } + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFog.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFog.java new file mode 100644 index 0000000..2f8f83f --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFog.java @@ -0,0 +1,143 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.Enumeration; +import com.sun.j3d.loaders.ParsingErrorException; + + +/** + * This class creates a Fog object from the data in a Scene file. + */ + +class LwsFog extends TextfileParser { + + // data from the file + float minDist, maxDist, minAmount, maxAmount; + int backdropFog; + Color3f color; + int type; + Fog fogObject = null; + + /** + * Constructor: parses stream and stores fog data + */ + LwsFog(StreamTokenizer st, int debugVals) throws ParsingErrorException { + debugPrinter.setValidOutput(debugVals); + debugOutput(TRACE, "LwsFog()"); + color = new Color3f(0f, 0f, 0f); + + while (!isCurrentToken(st, "DitherIntensity")) { + debugOutputLn(LINE_TRACE, "currentToken = " + st.sval); + + if (isCurrentToken(st, "FogMinDist")) { + minDist = (float)getNumber(st); + } + else if (isCurrentToken(st, "FogMaxDist")) { + maxDist = (float)getNumber(st); + } + else if (isCurrentToken(st, "FogMinAmount")) { + minAmount = (float)getNumber(st); + } + else if (isCurrentToken(st, "FogMaxAmount")) { + maxAmount = (float)getNumber(st); + } + else if (isCurrentToken(st, "BackdropFog")) { + backdropFog = (int)getNumber(st); + } + else if (isCurrentToken(st, "FogColor")) { + color.x = (float)getNumber(st)/255f; + color.y = (float)getNumber(st)/255f; + color.z = (float)getNumber(st)/255f; + } + try { + st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + st.pushBack(); // push token back on stack + } + + /** + * Creates Java3d Fog object given the fog parameters in the file. + * Note that various fog parameters in lw3d are not currently handled. + */ + void createJava3dObject() { + // TODO: there are various attributes of lw fog that + // we're not currently handing, including non-linear fog + // (need to understand the two different types - these may + // map onto java3d's expontential fog node), non-solid + // backdrop colors (how to handle this?), min/max amount + // (j3d only handles 0 -> 1 case) + + fogObject = new LinearFog(color, minDist, maxDist); + debugOutputLn(VALUES, + "just set linearFog with color, minDist, maxDist = " + + color + ", " + + minDist + ", " + + maxDist); + BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100000.0); + fogObject.setInfluencingBounds(bounds); + } + + Fog getObjectNode() + { + return fogObject; + } + + void printVals() + { + debugOutputLn(VALUES, " FOG vals: "); + } + + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFrame.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFrame.java new file mode 100644 index 0000000..f779eb4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsFrame.java @@ -0,0 +1,341 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import javax.vecmath.Matrix4d; +import javax.vecmath.Vector3d; +import javax.vecmath.Point3f; + +/** + * This class is responsible for parsing the data in a Scene file + * associated with a single keyframe. This data includes the position, + * orientation, and scaling information, in addition to the frame number + * of that keyframe and some spline controls which are currently + * ignored. + */ + +class LwsFrame extends TextfileParser { + + // data from the file + double x, y, z; + double heading, pitch, bank; + double xScale, yScale, zScale; + double frameNumber; + int linearValue; + double tension, continuity, bias; + + /** + * Constructor: parses and stores all data associated with a particular + * keyframe + */ + LwsFrame(StreamTokenizer st) { + x = getNumber(st); + y = getNumber(st); + z = -getNumber(st); + debugOutputLn(VALUES, "x, y, z " + x + ", " + y + ", " + z); + heading = getNumber(st); + pitch = getNumber(st); + bank = getNumber(st); + debugOutputLn(VALUES, "(degrees) h, p, b = " + heading + ", " + pitch + ", " + bank); + heading *= (Math.PI / 180.0); // Java3d works with radians + pitch *= (Math.PI / 180.0); + bank *= (Math.PI / 180.0); + debugOutputLn(VALUES, "(radians) h, p, b = " + heading + ", " + pitch + ", " + bank); + debugOutputLn(LINE_TRACE, "got pos and ori"); + xScale = getNumber(st); + yScale = getNumber(st); + zScale = getNumber(st); + debugOutputLn(VALUES, "xs, ys, zs " + xScale +", " + yScale + ", " + zScale); + frameNumber = (int)getNumber(st); + // Note: The following spline controls are ignored + linearValue = (int)getNumber(st); + debugOutputLn(VALUES, "framenum, linear " + frameNumber + " , " + linearValue); + tension = getNumber(st); + continuity = getNumber(st); + bias = getNumber(st); + debugOutputLn(VALUES, "tension, cont, bias = " + tension + ", " + continuity + ", " + bias); + } + + + + /** + * Construct new frame that's in-between two given frames + * Ratio gives the interpolation value for how far in-between + * the new frame should be (0.5 is half-way, etc) + */ + LwsFrame(LwsFrame prevFrame, LwsFrame nextFrame, double ratio) { + + x = prevFrame.x + (nextFrame.x - prevFrame.x) * ratio; + y = prevFrame.y + (nextFrame.y - prevFrame.y) * ratio; + z = prevFrame.z + (nextFrame.z - prevFrame.z) * ratio; + + heading = prevFrame.heading + + (nextFrame.heading - prevFrame.heading) * ratio; + pitch = prevFrame.pitch + + (nextFrame.pitch - prevFrame.pitch) * ratio; + bank = prevFrame.bank + + (nextFrame.bank - prevFrame.bank) * ratio; + xScale = prevFrame.xScale + + (nextFrame.xScale - prevFrame.xScale) * ratio; + yScale = prevFrame.yScale + + (nextFrame.yScale - prevFrame.yScale) * ratio; + zScale = prevFrame.zScale + + (nextFrame.zScale - prevFrame.zScale) * ratio; + frameNumber = prevFrame.frameNumber + + (nextFrame.frameNumber - prevFrame.frameNumber) * ratio; + + // The following are not interpolated + linearValue = prevFrame.linearValue; + tension = prevFrame.tension; + continuity = prevFrame.continuity; + bias = prevFrame.bias; + } + + /** + * Using hermite interpolation construct a new frame that's + * in-between two given frames. We also need to be given a + * frame before the first frame and a frame after the second + * frame. The calling function will make sure that we get the + * four appropriate frames. + * + * Ratio gives the interpolation value for how far in-between + * the new frame should be. (.5 is half-way, etc.) + */ + LwsFrame(LwsFrame prevFrame, LwsFrame frame1, + LwsFrame frame2, LwsFrame nextFrame, double u, + double adj0, double adj1) { + + double h1, h2, h3, h4; + double dd0a, dd0b, ds1a, ds1b; + + // pre-compute spline coefficients + double u2, u3, z1; + u2 = u * u; + u3 = u2 *u; + z1 = 3.0f *u2 - u3 - u3; + h1 = 1.0f - z1; + h2 = z1; + h3 = u3 - u2 - u2 + u; + h4 = u3 - u2; + + dd0a = (1.0f - frame1.tension) * (1.0f + frame1.continuity) + * (1.0f + frame1.bias); + + dd0b = (1.0f - frame1.tension) * (1.0f - frame1.continuity) + * (1.0f - frame1.bias); + + ds1a = (1.0f - frame2.tension) * (1.0f - frame2.continuity) + * (1.0f + frame2.bias); + + ds1b = (1.0f - frame2.tension) * (1.0f + frame2.continuity) + * (1.0f - frame2.bias); + + double[] v = new double[4]; + + // interpolate x, y, z + v[0] = prevFrame.x; v[1] = frame1.x; + v[2] = frame2.x; v[3] = nextFrame.x; + x = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + v[0] = prevFrame.y; v[1] = frame1.y; + v[2] = frame2.y; v[3] = nextFrame.y; + y = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + v[0] = prevFrame.z; v[1] = frame1.z; + v[2] = frame2.z; v[3] = nextFrame.z; + z = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + + // interpolate heading pitch and bank + v[0] = prevFrame.heading; v[1] = frame1.heading; + v[2] = frame2.heading ; v[3] = nextFrame.heading; + heading = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + + v[0] = prevFrame.pitch; v[1] = frame1.pitch; + v[2] = frame2.pitch; v[3] = nextFrame.pitch; + pitch = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + + v[0] = prevFrame.bank; v[1] = frame1.bank; + v[2] = frame2.bank; v[3] = nextFrame.bank; + bank = computeInterpolation (v, dd0a, dd0b, ds1a, ds1b, + adj0, adj1, h1, h2, h3, h4); + + // interpolate scale - scale interpolation is assumed to be linear + xScale = frame1.xScale + (frame2.xScale - frame1.xScale) * u; + yScale = frame1.yScale + (frame2.yScale - frame1.yScale) * u; + zScale = frame1.zScale + (frame2.zScale - frame1.zScale) * u; + + // interpolate frame number + frameNumber = frame1.frameNumber + + (frame2.frameNumber - frame1.frameNumber) * u; + + // The following are not interpolated + linearValue = frame2.linearValue; + + // We need to keep the spline smooth between knot points + tension = 0.0; + continuity = 0.0; + bias = 0.0; + } + + + double computeInterpolation(double[] value, double dd0a, + double dd0b, double ds1a, + double ds1b, double adj0, + double adj1, double h1, + double h2, double h3, double h4) { + + double dd0, ds1; + double delta = value[2] - value[1] ; + double result; + + // if adj != 0 + if (adj0 < -0.0001 || adj0 > 0.0001) + dd0 = adj0 * (dd0a * (value[1] - value[0]) + dd0b * delta); + else + dd0 = 0.5f * (dd0a + dd0b) * delta; + + // if adj != 0 + if (adj1 < -0.0001 || adj1 > 0.0001) + ds1 = adj1 * (ds1a * delta + ds1b * (value[3] - value[2])); + else + ds1 = 0.5f * (ds1a + ds1b) * delta; + + result = value[1] * h1 + value[2] * h2 + dd0 * h3 + ds1 * h4; + + return (result); + } + + double getHeading() { + return heading; + } + + double getPitch() { + return pitch; + } + + double getBank() { + return bank; + } + + /** + * Sets the given matrix to contain the position, orientation, and + * scale values for the keyframe + */ + void setMatrix(Matrix4d mat) { + setRotationMatrix(mat); + mat.setTranslation(new Vector3d(x, y, z)); + Matrix4d m = new Matrix4d(); + m.setColumn(0, xScale, 0, 0, 0); // setScale not yet implemented + m.setColumn(1, 0, yScale, 0, 0); + m.setColumn(2, 0, 0, zScale, 0); + m.setColumn(3, 0, 0, 0, 1); + mat.mul(m); + } + + /** + * Sets the given matrix to contain the orientation for this keyframe + */ + void setRotationMatrix(Matrix4d mat) + { + debugOutputLn(TRACE, "setRotMat()"); + debugOutputLn(VALUES, " p, h, b = " + + pitch + ", " + + heading + ", " + + bank); + Matrix4d pitchMat = new Matrix4d(); + pitchMat.rotX(-pitch); + Matrix4d bankMat = new Matrix4d(); + bankMat.rotZ(bank); + mat.rotY(-heading); + mat.mul(pitchMat); + mat.mul(bankMat); + debugOutputLn(VALUES, "setRotMat(), mat = " + mat); + } + + Point3f getPosition() { + return (new Point3f((float)x, (float)y, (float)z)); + } + + Point3f getScale() { + // Make sure we don't have zero scale components + if ((xScale < -0.0001 || xScale > 0.0001) && + (yScale < -0.0001 || yScale > 0.0001) && + (zScale < -0.0001 || zScale > 0.0001)) { + return (new Point3f((float)xScale, (float)yScale, (float)zScale)); + } else { + return (new Point3f(1.0f, 1.0f, 1.0f)); + } + } + + double getFrameNum() { + return frameNumber; + } + + void printVals() { + debugOutputLn(VALUES, " x = " + x); + debugOutputLn(VALUES, " y = " + y); + debugOutputLn(VALUES, " z = " + z); + debugOutputLn(VALUES, " xScale = " + xScale); + debugOutputLn(VALUES, " yScale = " + yScale); + debugOutputLn(VALUES, " zScale = " + zScale); + debugOutputLn(VALUES, " heading = " + heading); + debugOutputLn(VALUES, " pitch = " + pitch); + debugOutputLn(VALUES, " bank = " + bank); + debugOutputLn(VALUES, " frameNum = " + frameNumber); + debugOutputLn(VALUES, " lin = " + linearValue); + debugOutputLn(VALUES, " tension = " + tension); + debugOutputLn(VALUES, " continuity = " + continuity); + debugOutputLn(VALUES, " bias = " + bias); + } + +} + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsLight.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsLight.java new file mode 100644 index 0000000..89bd19b --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsLight.java @@ -0,0 +1,269 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.io.*; +import java.util.Vector; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.Enumeration; +import com.sun.j3d.loaders.ParsingErrorException; + +/** + * This class creates a light object from the data in a Scene file. It + * instantiates an LwsMotion object to create any associated + * animations. + */ + +class LwsLight extends TextfileParser implements LwsPrimitive { + + // data from the file + String fileName; + String objName; + LwsMotion motion; + int parent; + TransformGroup objectTransform; + Vector objectBehavior; + Color3f color; + int type; + Point3f attenuation = new Point3f(1.0f, 0.0f, 0.0f); + float spotConeAngle = (float)(Math.PI); + // Meta object, used for holding light and + LwLightObject lwLight; + // light parameters + LwsEnvelopeLightIntensity intensityEnvelope = null; + Light light = null; + final static int DIRECTIONAL = 0, POINT = 1, SPOT = 2; + + /** + * Constructor: parses stream and creates data structures for all + * light parameters currently handled by the loader + */ + LwsLight(StreamTokenizer st, int totalFrames, float totalTime, + int debugVals) throws ParsingErrorException { + + debugPrinter.setValidOutput(debugVals); + + debugOutput(TRACE, "LwsLight()"); + color = new Color3f(1f, 1f, 1f); + lwLight = new LwLightObject(null, 0.0f, null); + + parent = -1; + debugOutputLn(LINE_TRACE, "about to get LightName"); + getAndCheckString(st, "LightName"); + debugOutputLn(LINE_TRACE, "about to get LightName value"); + objName = getName(st); + debugOutputLn(LINE_TRACE, "got LightName"); + skip(st, "ShowLight", 2); + debugOutputLn(LINE_TRACE, "got ShowLight"); + getAndCheckString(st, "LightMotion"); + debugOutputLn(LINE_TRACE, "got LightMotion"); + motion = new LwsMotion(st, totalFrames, totalTime); + debugOutputLn(LINE_TRACE, "got motions"); + + // TODO: buggy way to stop processing the light. Should actually + // process required/optional fields in order and stop when there's + // no more. However, spec says "ShadowCasing" but the files say + // "ShadowType". + + while (!isCurrentToken(st, "ShowCamera") && + !isCurrentToken(st, "AddLight")) { + // TODO: + // Things that we're not yet processing (and should): + // - EdgeAngle: for spotlights, this is the angle which + // contains the linear falloff toward the edge of the + // ConeAngle. This doesn't directly map to J3d's + // "concentration" value, so it's left out for now. + + debugOutputLn(LINE_TRACE, "currentToken = " + st.sval); + + if (isCurrentToken(st, "ParentObject")) { + parent = (int)getNumber(st); + } + else if (isCurrentToken(st, "LightColor")) { + color.x = (float)getNumber(st)/255f; + color.y = (float)getNumber(st)/255f; + color.z = (float)getNumber(st)/255f; + lwLight.setColor(color); + } + else if (isCurrentToken(st, "LgtIntensity")) { + // TODO: must be able to handle envelopes here + String className = getClass().getName(); + int classIndex = className.lastIndexOf('.'); + String packageName; + if (classIndex < 0) + packageName = ""; + else + packageName = className.substring(0, classIndex) + "."; + EnvelopeHandler env = + new EnvelopeHandler(st, totalFrames, totalTime, + packageName + "LwsEnvelopeLightIntensity"); + if (env.hasValue) { + float intensity = (float)env.theValue; + color.x *= intensity; + color.y *= intensity; + color.z *= intensity; + lwLight.setIntensity(intensity); + } + else { + intensityEnvelope = + (LwsEnvelopeLightIntensity)env.theEnvelope; + } + } + else if (isCurrentToken(st, "LightType")) { + type = (int)getNumber(st); + } + else if (isCurrentToken(st, "Falloff")) { + float falloff = (float)getNumber(st); + attenuation.y = 1.0f/(1.0f - falloff) - 1.0f; + } + else if (isCurrentToken(st, "ConeAngle")) { + spotConeAngle = (float)getNumber(st) * (float)(Math.PI / 180.0); + } + try { + st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + st.pushBack(); // push "ShowCamera" or "AddLight" back on stack + } + + int getParent() { + return parent; + } + + /** + * Create Java3D objects from the data we got from the file + */ + void createJava3dObject(int loadBehaviors) { + Matrix4d mat = new Matrix4d(); + mat.setIdentity(); + // Set the node's transform matrix according to the first frame + // of the object's motion + LwsFrame firstFrame = motion.getFirstFrame(); + firstFrame.setMatrix(mat); + debugOutputLn(VALUES, "Light transform = " + mat); + Transform3D t1 = new Transform3D(); + t1.set(mat); + objectTransform = new TransformGroup(t1); + objectTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + Vector3f defaultDir = new Vector3f(0f, 0f, -1f); + Point3f defaultPos = new Point3f(0f, 0f, 0f); + + switch (type) { + case DIRECTIONAL: + light = new DirectionalLight(color, defaultDir); + break; + case POINT: + light = new PointLight(color, defaultPos, attenuation); + break; + case SPOT: + // Note: spotConeAngle in lw3d is half that of Java3d... + light = new SpotLight(color, defaultPos, attenuation, defaultDir, + 2 * spotConeAngle, 0.0f); + break; + default: + // Shouldn't get here + break; + } + + light.setCapability(Light.ALLOW_COLOR_WRITE); + if (light != null) { + lwLight.setLight(light); + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 100000.0); + light.setInfluencingBounds(bounds); + objectTransform.addChild(light); + + // load behaviors if we have to + objectBehavior = new Vector(); + if (loadBehaviors != 0) { + Behavior b; + b = null; + motion.createJava3dBehaviors(objectTransform); + b = motion.getBehaviors(); + if (b != null) + objectBehavior.addElement(b); + + if (intensityEnvelope != null) { + b = null; + intensityEnvelope.createJava3dBehaviors(lwLight); + b = intensityEnvelope.getBehaviors(); + if (b != null) + objectBehavior.addElement(b); + } + } + } + } + + public TransformGroup getObjectNode() + { + return objectTransform; + } + + Light getLight() { + return light; + } + + public Vector getObjectBehaviors() + { + debugOutputLn(TRACE, "getObjectBehaviors()"); + return objectBehavior; + } + + + void printVals() + { + debugOutputLn(VALUES, " LIGHT vals: "); + debugOutputLn(VALUES, " objName = " + objName); + motion.printVals(); + } + + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsMotion.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsMotion.java new file mode 100644 index 0000000..472a2f9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsMotion.java @@ -0,0 +1,688 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.io.*; +import java.util.Enumeration; +import java.util.Vector; +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.utils.behaviors.interpolators.*; +import com.sun.j3d.internal.J3dUtilsI18N; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.IncorrectFormatException; + +/** + * This class is responsible for parsing the data in a Scene file related to + * an object's animation and constructing the appropriate Java3D + * Behavior objects. For each keyframe defined for the animation in the + * Lightwave file, this class creates a LwsFrame object to parse that + * keyframe data and create the appropriate data structures. Then for + * each of those LwsFrame objects created, LwsMotion creates a knot + * value for a PathInterpolator and fills in the appropriate field. Finally, + * the class creates a RotPosScalePathInterpolator with all of the data + * from the animation. There are also some utility functions in this + * class for dealing with special cases of animations, such as animations + * that begin after the first frame of the scene and animations that + * define frames in a way that Java3D cannot easily interpret. + */ + +class LwsMotion extends TextfileParser { + + // data from the file + String motionName; + LwsFrame frames[]; + int numFrames; + int numChannels; + boolean loop; + float totalTime; + int firstFrame; + int totalFrames; + Behavior behaviors; + + /** + * Constructor + */ + LwsMotion(StreamTokenizer st, int frames, float time) { + this(st, 0, frames, time, EXCEPTION); + + } + + /** + * Constructor: takes tokenizer, 1st frame of this animation, total + * number of frames, total time of animation, and the debug settings + */ + LwsMotion(StreamTokenizer st, int firstFrame, + int frames, float time, int debugVals) + throws ParsingErrorException, IncorrectFormatException { + + debugPrinter.setValidOutput(debugVals); + numFrames = 0; + totalTime = time; + this.firstFrame = firstFrame; + totalFrames = frames; + debugOutputLn(LINE_TRACE, "about to get motion name"); + motionName = getName(st); + debugOutputLn(LINE_TRACE, "about to get motion"); + getMotion(st); + } + + /** + * This method parses the tokenizer and creates the data structures + * that hold the data from that file. For each separate keyframe, + * this method calls LwsFrame to parse and interpret that data. + */ + void getMotion(StreamTokenizer st) + throws ParsingErrorException, IncorrectFormatException + { + debugOutputLn(TRACE, "getMotion()"); + numChannels = (int)getNumber(st); + if (numChannels != 9) { + throw new IncorrectFormatException( + J3dUtilsI18N.getString("LwsMotion0")); + } + debugOutputLn(LINE_TRACE, "got channels"); + + numFrames = (int)getNumber(st); + frames = new LwsFrame[numFrames]; + debugOutputLn(VALUES, "got frames" + numFrames); + + for (int i = 0; i < numFrames; ++i) { + frames[i] = new LwsFrame(st); + } + + debugOutput(LINE_TRACE, "got all frames"); + + getAndCheckString(st, "EndBehavior"); + int repeatVal = (int)getNumber(st); + if (repeatVal == 1) + loop = false; + else + loop = true; + + // need to make sure frame info is in sycn with j3d + // fixFrames(); + } + + /** + * The previous version of this method looked for sucessive frames with + * the same rotation value (mod 2PI). If it found such frames, it would + * divide that interval into 4 separate frames. + * This fix is not sufficient for various rotation cases, though. For + * instance, if the rotation difference between two frames is more than + * 2PI, the rotation will not case a flag to be fixed and the resulting + * rotation will only be between the delta of the two rotations, mod 2PI. + * The real fix should behave as follows: + * - Iterate through all sucessive frames + * - For any two frames that have rotation components that differ by more + * than PI/2 (one quarter rotation - no reason for this, but let's pick a + * small value to give our resulting path interpolations a better chance + * of behaving correctly), figure out how many frames we need to create to + * get increments of <= PI/2 between each frame. + * - Create these new frames + * - Set the odl frames pointer to the new frames structures. + */ + + void fixFrames() { + + boolean addedFrames = false; + Vector newFramesList = new Vector(); + double halfPI = (float)(Math.PI/2); + LwsFrame finalFrame = null; + + for (int i = 1 ; i < numFrames; ++i) { + LwsFrame prevFrame; + LwsFrame lastFrame = frames[i-1]; + LwsFrame thisFrame = frames[i]; + LwsFrame nextFrame; + + finalFrame = thisFrame; + newFramesList.add(lastFrame); + + double largestAngleDifference = 0; + double thisAngle = thisFrame.getHeading(); + double lastAngle = lastFrame.getHeading(); + double angleDifference = Math.abs(thisAngle - lastAngle); + if (angleDifference > largestAngleDifference) + largestAngleDifference = angleDifference; + + thisAngle = thisFrame.getPitch(); + lastAngle = lastFrame.getPitch(); + angleDifference = Math.abs(thisAngle - lastAngle); + if (angleDifference > largestAngleDifference) + largestAngleDifference = angleDifference; + + thisAngle = thisFrame.getBank(); + lastAngle = lastFrame.getBank(); + angleDifference = Math.abs(thisAngle - lastAngle); + if (angleDifference > largestAngleDifference) + largestAngleDifference = angleDifference; + + if (largestAngleDifference > halfPI) { + // Angles too big - create new frames + addedFrames = true; + int numNewFrames = (int)(largestAngleDifference/halfPI); + double increment = 1.0/(double)(numNewFrames+1); + double currentRatio = increment; + + double totalf = frames[numFrames-1].getFrameNum(); + double tlength = (thisFrame.getFrameNum() - + lastFrame.getFrameNum())/totalf; + double adj0; + double adj1; + + // get the previous and next frames + if ((i-1) < 1) { + prevFrame = frames[i-1]; + adj0 = 0.0; + } else { + prevFrame = frames[i-2]; + adj0 = tlength/((thisFrame.getFrameNum() - + prevFrame.getFrameNum())/totalf); + } + + if ((i+1) < numFrames) { + nextFrame = frames[i+1]; + adj1 = tlength/((nextFrame.getFrameNum()- + lastFrame.getFrameNum())/totalf); + } else { + nextFrame = frames[i]; + adj1 = 1.0; + } + + for (int j = 0; j < numNewFrames; ++j) { + + LwsFrame newFrame; + + // if linear interpolation + if (thisFrame.linearValue == 1) { + newFrame = new LwsFrame(lastFrame, + thisFrame, currentRatio); + + // if spline interpolation + } else { + newFrame = new LwsFrame(prevFrame, lastFrame, + thisFrame, nextFrame, + currentRatio, adj0, adj1); + } + + currentRatio += increment; + newFramesList.add(newFrame); + } + } + } + + // Now add in final frame + if (finalFrame != null) + newFramesList.add(finalFrame); + if (addedFrames) { + + // Recreate frames array from newFramesList + LwsFrame newFrames[] = new LwsFrame[newFramesList.size()]; + Enumeration elements = newFramesList.elements(); + int index = 0; + while (elements.hasMoreElements()) { + newFrames[index++] = (LwsFrame)elements.nextElement(); + } + frames = newFrames; + numFrames = frames.length; + for (int i = 0; i < numFrames; ++i) { + debugOutputLn(VALUES, "frame " + i + " = " + frames[i]); + frames[i].printVals(); + } + } + } + + /** + * Utility for getting integer mod value + */ + int intMod(int divisee, int divisor) { + int tmpDiv = divisee; + int tmpDivisor = divisor; + if (tmpDiv < 0) + tmpDiv = -tmpDiv; + if (tmpDivisor < 0) + tmpDivisor = -tmpDivisor; + while (tmpDiv > tmpDivisor) { + tmpDiv -= tmpDivisor; + } + return tmpDiv; + } + + /** + * Class that associates a particular frame with its effective frame + * number (which accounts for animations that start after frame 1) + */ + class FrameHolder { + double frameNumber; + LwsFrame frame; + + FrameHolder(LwsFrame theFrame, double number) { + frame = theFrame; + frameNumber = number; + } + } + + + /** + * This method was added to account for animations that start after + * the first frame (e.g., Juggler.lws starts at frame 30). We need + * to alter some of the information for the frames in this "frame subset" + */ + void playWithFrameTimes(Vector framesVector) { + debugOutputLn(TRACE, "playWithFrameTimes: firstFrame = " + + firstFrame); + if (firstFrame == 1) { + return; + } + else if (frames[numFrames-1].getFrameNum() < totalFrames) { + // First, create a vector that holds all LwsFrame's in frame + // increasing order (where order is started at firstFrame Modulo + // this motion's last frame + int motionLastFrame = + (int)(frames[numFrames-1].getFrameNum() + .4999999); + int newFirstFrame = intMod(firstFrame, motionLastFrame); + int newLastFrame = intMod(totalFrames, motionLastFrame); + int index = 0; + while (index < numFrames) { + if (frames[index].getFrameNum() >= newFirstFrame) + break; + ++index; + } + int startIndex = index; + if (frames[startIndex].getFrameNum() > firstFrame && + startIndex > 0) + startIndex--; // Actually, should interpolate + index = startIndex; + if (newFirstFrame < newLastFrame) { + while (index < numFrames && + frames[index].getFrameNum() <= newLastFrame) { + FrameHolder frameHolder = + new FrameHolder(frames[index], + frames[index].getFrameNum() - + newFirstFrame); + framesVector.addElement(frameHolder); + ++index; + } + } + else { + double currentNewFrameNumber = -1.0; + while (index < numFrames) { + currentNewFrameNumber = frames[index].getFrameNum() - + newFirstFrame; + FrameHolder frameHolder = + new FrameHolder(frames[index], + currentNewFrameNumber); + framesVector.addElement(frameHolder); + ++index; + } + index = 0; + while (index <= startIndex && + frames[index].getFrameNum() <= newLastFrame) { + if (index == 0) { + LwsFrame newFrame = + new LwsFrame(frames[index], + frames[index+1], + 1.0/(frames[index+1].getFrameNum() - + frames[index].getFrameNum())); + FrameHolder frameHolder = + new FrameHolder(newFrame, + newFrame.getFrameNum() + + currentNewFrameNumber); + framesVector.addElement(frameHolder); + } + else { + FrameHolder frameHolder = + new FrameHolder(frames[index], + frames[index].getFrameNum() + + currentNewFrameNumber); + framesVector.addElement(frameHolder); + } + ++index; + } + } + } + else { + int index = 0; + while (index < numFrames) { + if (frames[index].getFrameNum() >= firstFrame) + break; + ++index; + } + int startIndex = index; + if (frames[startIndex].getFrameNum() > firstFrame && + startIndex > 0) { + // Interpolate to first frame + double ratio = (double)firstFrame / + (frames[startIndex].getFrameNum() - + frames[startIndex-1].getFrameNum()); + LwsFrame newFrame = new LwsFrame(frames[startIndex-1], + frames[startIndex], + ratio); + FrameHolder frameHolder = + new FrameHolder(newFrame, newFrame.getFrameNum() - + firstFrame); + framesVector.addElement(frameHolder); + } + index = startIndex; + while (index < numFrames && + frames[index].getFrameNum() <= totalFrames) { + FrameHolder frameHolder = + new FrameHolder(frames[index], + frames[index].getFrameNum() - + firstFrame); + framesVector.addElement(frameHolder); + ++index; + } + if (frames[index-1].getFrameNum() < totalFrames) { + // Interpolate to last frame + double ratio = (double)(totalFrames - + frames[index-1].getFrameNum()) / + (frames[index].getFrameNum() - + frames[index-1].getFrameNum()); + LwsFrame newFrame = new LwsFrame(frames[index-1], + frames[index], + ratio); + FrameHolder frameHolder = + new FrameHolder(newFrame, totalFrames - firstFrame); + framesVector.addElement(frameHolder); + } + } + } + + /** + * Normally, we just create j3d behaviors from the frames. But if the + * animation's first frame is after frame number one, then we have to + * shuffle things around to account for playing/looping on this subset + * of the total frames of the animation + */ + void createJava3dBehaviorsForFramesSubset(TransformGroup target) { + + debugOutputLn(TRACE, "createJava3dBehaviorsForFramesSubset"); + Vector frameHolders = new Vector(); + playWithFrameTimes(frameHolders); + long alphaAtOne = 0; + + // determine looping + int loopCount; + if (loop) + loopCount = -1; + else + loopCount = 1; + loopCount = -1; + + int numFrames = frameHolders.size(); + + debugOutputLn(VALUES, "totalTime = " + totalTime); + debugOutputLn(VALUES, "loopCount = " + loopCount); + + FrameHolder lastFrameHolder = + (FrameHolder)frameHolders.elementAt(frameHolders.size() - 1); + LwsFrame lastFrame = lastFrameHolder.frame; + float animTime = 1000.0f * totalTime * + (float)(lastFrameHolder.frameNumber/(float)(totalFrames - + firstFrame)); + debugOutputLn(VALUES, " anim time: " + animTime); + debugOutputLn(VALUES, " totalFrames = " + totalFrames); + + if (!loop) + alphaAtOne = (long)(1000.0*totalTime - animTime); + Alpha theAlpha = + new Alpha(loopCount, Alpha.INCREASING_ENABLE, + 0, 0, (long)animTime, 0, + alphaAtOne, 0, 0, 0); + + float knots[] = new float[numFrames]; + Point3f[] positions = new Point3f[numFrames]; + Quat4f[] quats = new Quat4f[numFrames]; + Point3f[] scales = new Point3f[numFrames]; + Transform3D yAxis = new Transform3D(); + Matrix4d mat = new Matrix4d(); + KBKeyFrame[] keyFrames = new KBKeyFrame[numFrames]; + + for (int i=0; i < numFrames; ++i) { + + FrameHolder frameHolder = (FrameHolder)frameHolders.elementAt(i); + LwsFrame frame = frameHolder.frame; + + // copy position + positions[i] = frame.getPosition(); + + // copy scale + // Used to hardcode no-scale: scales[i] = 1.0f, 1.0f, 1.0f; + // Note that we can't do non-uniform scaling in the current Path + // interpolators. The interpolator just uses the x scale. + // getScale makes sure that we don't have any zero scale component + scales[i] = frame.getScale(); + + // copy rotation information + frame.setRotationMatrix(mat); + debugOutputLn(VALUES, "LwsMotion::createj3dbeh, mat = " + mat); + quats[i] = new Quat4f(); + quats[i].set(mat); + debugOutputLn(VALUES, " and quat = " + quats[i]); + + // calculate knot points from frame numbers + if (i == 0) + knots[i] = 0.0f; + else + knots[i] = (float)(frameHolder.frameNumber)/ + (float)(lastFrameHolder.frameNumber); + + // Create KB key frames + keyFrames[i] = new KBKeyFrame(knots[i], frame.linearValue, + positions[i], + (float)frame.heading, + (float)frame.pitch, + (float)frame.bank, + scales[i], + (float)frame.tension, + (float)frame.continuity, + (float)frame.bias); + + debugOutputLn(VALUES, "pos, knots, quat = " + + positions[i] + knots[i] + quats[i]); + } + + // Pass the KeyFrames to the interpolator an let it do its thing + KBRotPosScaleSplinePathInterpolator b = new + KBRotPosScaleSplinePathInterpolator(theAlpha, + target, + yAxis, + keyFrames); + if (b != null) { + behaviors = b; + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000000.0); + b.setSchedulingBounds(bounds); + target.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + target.addChild(behaviors); + } + } + + /** + * Create j3d behaviors for the data stored in this animation. This is + * done by creating a RotPosScalePathInterpolator object that contains + * all of the position, orientation, scale data for each keyframe. + */ + void createJava3dBehaviors(TransformGroup target) { + + if (numFrames <= 1) + behaviors = null; + else { + if (firstFrame > 1) { + createJava3dBehaviorsForFramesSubset(target); + return; + } + + long alphaAtOne = 0; + + // determine looping + int loopCount; + if (loop) + loopCount = -1; + else + loopCount = 1; + loopCount = -1; + + debugOutputLn(VALUES, "totalTime = " + totalTime); + debugOutputLn(VALUES, "loopCount = " + loopCount); + + float animTime = 1000.0f * totalTime * + (float)(frames[numFrames-1].getFrameNum()/(float)totalFrames); + + debugOutputLn(VALUES, " anim time: " + animTime); + debugOutputLn(VALUES, " totalFrames = " + totalFrames); + debugOutputLn(VALUES, " lastFrame = " + + frames[numFrames-1].getFrameNum()); + + if (!loop) + alphaAtOne = (long)(1000.0*totalTime - animTime); + Alpha theAlpha = + new Alpha(loopCount, Alpha.INCREASING_ENABLE, + 0, 0, (long)animTime, 0, + alphaAtOne, 0, 0, 0); + + float knots[] = new float[numFrames]; + Point3f[] positions = new Point3f[numFrames]; + Quat4f[] quats = new Quat4f[numFrames]; + Point3f[] scales = new Point3f[numFrames]; + Transform3D yAxis = new Transform3D(); + Matrix4d mat = new Matrix4d(); + KBKeyFrame[] keyFrames = new KBKeyFrame[numFrames]; + + for (int i=0; i < numFrames; ++i) { + + // copy position + positions[i] = frames[i].getPosition(); + + // copy scale + // Used to hardcode no-scale: scales[i] = 1.0f, 1.0f, 1.0f; + // Note that we can't do non-uniform scaling in the current Path + // interpolators. The interpolator just uses the x scale. + // getScale makes sure that we don't have any 0 scale component + scales[i] = frames[i].getScale(); + + // copy rotation information + frames[i].setRotationMatrix(mat); + debugOutputLn(VALUES, "LwsMotion::createj3dbeh, mat = " + mat); + quats[i] = new Quat4f(); + quats[i].set(mat); + debugOutputLn(VALUES, " and quat = " + quats[i]); + + // calculate knot points from frame numbers + if (i == 0) + knots[i] = 0.0f; + else + knots[i] = (float)(frames[i].getFrameNum())/ + (float)(frames[numFrames-1].getFrameNum()); + + // Create KB key frames + keyFrames[i] = new KBKeyFrame(knots[i],frames[i].linearValue, + positions[i], + (float)frames[i].heading, + (float)frames[i].pitch, + (float)frames[i].bank, + scales[i], + (float)frames[i].tension, + (float)frames[i].continuity, + (float)frames[i].bias); + + + debugOutputLn(VALUES, "pos, knots, quat = " + + positions[i] + knots[i] + quats[i]); + } + + // Pass the KeyFrames to the interpolator an let it do its thing + KBRotPosScaleSplinePathInterpolator b = new + KBRotPosScaleSplinePathInterpolator(theAlpha, + target, + yAxis, + keyFrames); + if (b != null) { + behaviors = b; + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000000.0); + b.setSchedulingBounds(bounds); + target.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + target.addChild(behaviors); + } + } + + } + + /** + * Returns the Behavior object created for this animation + */ + Behavior getBehaviors() { + return behaviors; + } + + /** + * Returns the first LwsFrame object (which contains the initial + * setup for a given object) + */ + LwsFrame getFirstFrame() { + if (numFrames > 0) + return frames[0]; + else + return null; + } + + /** + * Utility function for printing values + */ + void printVals() { + debugOutputLn(VALUES, " motionName = " + motionName); + debugOutputLn(VALUES, " numChannels = " + numChannels); + debugOutputLn(VALUES, " numFrames = " + numFrames); + debugOutputLn(VALUES, " loop = " + loop); + for (int i = 0; i < numFrames; ++i) { + debugOutputLn(VALUES, " FRAME " + i); + frames[i].printVals(); + } + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsObject.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsObject.java new file mode 100644 index 0000000..9a4d384 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsObject.java @@ -0,0 +1,570 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + +import java.awt.Component; +import java.io.*; +import java.util.Vector; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import com.sun.j3d.utils.geometry.ColorCube; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.IncorrectFormatException; +import java.net.MalformedURLException; + +import java.net.*; + +/** + * An LwsObject is passed a handle to the text file that contains the scene + * and is responsible for parsing a particular section of that file that + * describes a single object. This section defines the type of object + * (either a group or some geometry specified by an Object file) and + * some keyframe data that describes the an animation of the + * orientation/position/scale of the object. For geometry objects, this + * class instantiates a J3dLwoParser object to parse the binary data file. + * For the keyframe data, the class instantiates an LwsMotion object to + * parse and store that data. + */ + +class LwsObject extends TextfileParser implements LwsPrimitive { + + // data from the file + String fileName; + String objName; + LwsMotion motion; + int parent; + TransformGroup objectTransform; + Vector objectBehavior; + Vector shapeList = null; + boolean hasPivot = false; + TransformGroup pivotTransGroup = null; + + URL urlName; + String protocol; + int fileType; + + /** + * Constructor: parses object section of this scene file and + * creates all appropriate data structures to hold the information + * @param st StreamTokenizer for scene file + * @param loadObject boolean specifying that object is not a lw3d Null + * object + * @param firstFrame int holding the first frame of the scene's animation + * @param totalFrames int holding the total number of frames in the scene + * @param totalTime float holding the total time of the animation + * @param loader Lw3dLoader loader object that was created by user + * @param debugVals in holding current debug flags + */ + LwsObject(StreamTokenizer st, boolean loadObject, + int firstFrame, int totalFrames, float totalTime, + Lw3dLoader loader, int debugVals) + throws java.io.FileNotFoundException, + ParsingErrorException { + debugPrinter.setValidOutput(debugVals); + parent = -1; + + fileType = loader.getFileType(); + + try { + if (loadObject) { + // If this is true, then the object is actually described + // in an external geometry file. Get that filename + fileName = getString(st); + String path = null; + switch (loader.getFileType()) { + case Lw3dLoader.FILE_TYPE_FILENAME: + // No other case is current implemented in this loader + path = loader.getBasePath(); + if (path == null) + path = loader.getInternalBasePath(); + if (path != null) { + // It's not sufficient to just use the base path. + // Lightwave scene files tend to embed path names + // to object files that are only correct if you + // start from a certain directory. For example, a + // scene file in data/ may point to an object in + // data/Objects - but in this case + // getInternalBasePath() would be data/, so you'd + // look for the object in data/data/Objects... + // To attempt to overcome this confusing state of + // affairs, let's check path/filename + // first, then iterate all the way up the path + // until there are no more members of path. This + // will ensure that we'll at least pick up data + // files if they exist off of one of the parent + // directories of wherever the scene file is + // stored. + // No, I don't really like this solution, but I don't + // know of a better general approach for now... + + fileName = getQualifiedFilename(path, fileName); + } + break; + case Lw3dLoader.FILE_TYPE_URL: + path = ""; + URL pathUrl = loader.getBaseUrl(); + if (pathUrl != null) { + path = pathUrl.toString(); + // store the protocol + protocol = pathUrl.getProtocol(); + } + else { + path = loader.getInternalBaseUrl(); + // store the protocol + protocol = (new URL(path)).getProtocol(); + } + + urlName = getQualifiedURL(path, fileName); + break; + } + } + else + // else the object is a lw3d Null object; essentially a group + // which contains other objects + objName = getString(st); + skip(st, "ShowObject", 2); + debugOutputLn(LINE_TRACE, + "skipped showobject, about to get objectmotion"); + getAndCheckString(st, "ObjectMotion"); + debugOutputLn(LINE_TRACE, "got string " + st.sval); + // Create an LwsMotion object to parse the animation data + motion = new LwsMotion(st, firstFrame, totalFrames, + totalTime, debugVals); + debugOutputLn(LINE_TRACE, "got motion"); + boolean hasParent = false; // keeps bones prim from reassigning par + + // TODO: This isn't the greatest way to stop parsing an object + // (keying on the ShowOptions parameter), but it seems to be valid + // for the current lw3d format + while (!isCurrentToken(st, "ShadowOptions")) { + if (!hasParent && + isCurrentToken(st, "ParentObject")) { + parent = (int)getNumber(st); + hasParent = true; + } + else if (isCurrentToken(st, "PivotPoint")) { + // PivotPoint objects are tricky - they make the object + // rotate about this new point instead of the default + // So setup transform groups such that this happens + // correctly. + hasPivot = true; + float x = (float)getNumber(st); + float y = (float)getNumber(st); + float z = (float)getNumber(st); + Vector3f pivotPoint = new Vector3f(-x, -y, z); + Transform3D pivotTransform = new Transform3D(); + pivotTransform.set(pivotPoint); + pivotTransGroup = new TransformGroup(pivotTransform); + pivotTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + } + + else if (isCurrentToken(st, "ObjDissolve")) { + // Just handle it for now, don't care about value + EnvelopeHandler env = + new EnvelopeHandler(st, totalFrames, totalTime); + } + st.nextToken(); + } + getNumber(st); // skip shadow options parameter + debugOutputLn(LINE_TRACE, "done with LwsObject constructor"); + } + catch (MalformedURLException e) { + throw new FileNotFoundException(e.getMessage()); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + catch (NumberFormatException e) { + throw new ParsingErrorException("Expected a number, got " + + e.getMessage()); + } + } + + /** + * This method takes the given path and filename and checks whether + * that file exists. If not, it chops off the last part of pathname + * and recurse to try again. This has the effect of searching all the + * way up a given pathname for the existence of the file anywhere + * along that path. This is somewhat of a hack to get around the + * constraining way that Lightwave uses to define its data object + * locations in its scene files. + * + * If the filename is absolute, it will use the path information from + * the filename first, then the path information from the lws file. + * If the file can not be found in these locations, the local directory + * will be searched. + * In addition, it will look for filenames converted to lowercase to + * make it easier to use between Windows and Unix. + */ + + String getQualifiedFilename(String pathname, String filename) + throws java.io.FileNotFoundException { + + int index; + String pathname2 = ""; + + // System.out.println ("pathname:"+pathname+" filename:"+filename); + + // Do we have an absolute filename ? + if (filename.indexOf (File.separator) == 0) { + if ((index = filename.lastIndexOf (File.separator)) != -1) { + pathname2 = filename.substring (0, index+1); + filename = filename.substring (index+1); + } + else { + return null; // something out of the ordinary happened + } + } + + // See if we can find the file + // --------------------------- + + // Try pathname from absolute filename + try { + if (new File(pathname2 + filename).exists()) { + return (pathname2 + filename); + } + } + catch (NullPointerException ex) { + ex.printStackTrace(); + } + // Try lowercase filename + if (new File(pathname2 + filename.toLowerCase()).exists()) { + return (pathname2 + filename.toLowerCase()); + } + + // Try original pathname + if (new File(pathname + filename).exists()) { + return (pathname + filename); + } + // Try lowercase filename + if (new File(pathname + filename.toLowerCase()).exists()) { + return (pathname + filename.toLowerCase()); + } + + // Finally, let's check the local directory + if (new File(filename).exists()) { + return (filename); + } + // Try lowercase filename + if (new File(filename.toLowerCase()).exists()) { + return (filename.toLowerCase()); + } + + // Conditions that determine when we give up on the recursive search + if ((pathname.equals(File.separator)) || + (pathname == null) || + (pathname.equals(""))) { + + throw new java.io.FileNotFoundException(filename); + } + + // Try to find the file in the upper directories + // Chop off the last directory from pathname and recurse + StringBuffer newPathName = new StringBuffer(128); + StringTokenizer st = new StringTokenizer(pathname, File.separator); + int tokenCount = st.countTokens() - 1; + if (pathname.startsWith(java.io.File.separator)) + newPathName.append(File.separator); + for (int i = 0; i < tokenCount; ++i) { + String directory = st.nextToken(); + newPathName.append(directory); + newPathName.append(File.separator); + } + + String newPath = newPathName.toString(); + return getQualifiedFilename(newPath, filename); + } + + URL getQualifiedURL(String path, String file) + throws MalformedURLException { + + URL url = null; + + // try the path and the file -- this is the lightwave spec + try { + // create url + url = new URL(path + file); + // see if file exists + url.getContent(); + // return url if no exception is thrown + return url; + } + catch (IOException e) { + // Ignore - try something different + } + + // try a couple other options, but trying to open connections is slow, + // so don't try as many options as getQualifiedFilename + + // try absolute path + try { + url = new URL(file); + url.getContent(); + } + catch (IOException ex) { + // Ignore - try something different + } + + // try the absolute path with the protocol + try { + url = new URL(protocol + ":" + file); + url.getContent(); + return url; + } + catch (IOException ex) { + // Nothing else to try so give up + throw new MalformedURLException(path + file); + } + } + + /** + * Returns parent object + */ + int getParent() { + return parent; + } + + /** + * Adds the given child to the transform of this node (its parent). + */ + void addChild(LwsPrimitive child) { + debugOutputLn(TRACE, "addChild()"); + if (objectTransform != null) { + debugOutputLn(LINE_TRACE, "objectTransform = " + objectTransform); + if (child.getObjectNode() != null) { + debugOutputLn(LINE_TRACE, "child has object node"); + if (hasPivot) + pivotTransGroup.addChild(child.getObjectNode()); + else + objectTransform.addChild(child.getObjectNode()); + } +/* + if (child.getObjectBehaviors() != null) { + debugOutputLn(LINE_TRACE, "child has behaviors"); + Group bg = child.getObjectBehaviors(); + debugOutputLn(VALUES, " child behaviors = " + bg); + // TODO: should remove intermediate group nodes + objectBehavior.addChild(bg); + } +*/ + } + } + + /** + * Creates Java3d objects from the data stored for this object. + * The objects created consist of: A TransformGroup that holds the + * transform specified by the first keyframe, a Behavior that acts + * on the TransformGroup if there are more than 1 keyframes, and + * some geometry (created by J3dLwoParser) from an external geometry + * file (if the object wasn't an lw3d Null object (group)). + */ + void createJava3dObject(LwsObject cloneObject, int loadBehaviors) + throws IncorrectFormatException, ParsingErrorException, + FileNotFoundException + { + String seqToken = new String("_sequence_"); + Matrix4d mat = new Matrix4d(); + mat.setIdentity(); + // Set the node's transform matrix according to the first frame + // of the object's motion + LwsFrame firstFrame = motion.getFirstFrame(); + firstFrame.setMatrix(mat); + Transform3D t1 = new Transform3D(); + t1.set(mat); + objectTransform = new TransformGroup(t1); + objectTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + + // This following bit is a hack and should probably be removed. + // It was put in here in order to handle the "Tloop" functionality + // of holosketch, which was needed for the 1998 Javaone conference + // (the HighNoon demo, in particular). Having the code here, or + // using it, means that some object file names are tagged as special + // because they contain the phrase "_sequence_", which tells this + // object file reader that that object file is, in fact, a + // sequence file. It then creates a SequenceReader object to + // read that file and create an animation from the objects defined + // in that file. + // See SequenceReader.java for more information on this. + // By the way, a better/fuller implementation of this functionality + // would involve investigating a standard Plug-In for Lightwave + // that allows the writing out of sequence files from Bones data. + // i think it would be better to base any Tloop stuff on that + // standard than on some proprietary hack of our own. + + if (fileName != null && fileName.indexOf(seqToken) != -1) { // Tloop + + int index = fileName.indexOf(seqToken); + index += seqToken.length(); + String seqFilename = fileName.substring(index); + int endIndex = seqFilename.indexOf(".lwo"); + if (endIndex != -1) + seqFilename = seqFilename.substring(0, endIndex); + if ((new File(seqFilename)).exists()) { + SequenceReader sr = + new SequenceReader(seqFilename, + motion.totalTime, + (int)motion.totalFrames); + sr.printLines(); + sr.createJava3dObjects(debugPrinter.getValidOutput(), + loadBehaviors); + Group g = sr.getObjectNode(); + if (g != null) + objectTransform.addChild(g); + + // Sequence reader's getObjectBehaviors creates new Vector + objectBehavior = sr.getObjectBehaviors(); + + return; + } + } + + // Okay, now that that hack is out of the way, let's get on with + // "normal" Lightwave object files. + if (fileName != null || urlName != null) { + // If this object refers to an obj file, load it and create + // geometry from it. + if (cloneObject == null) { + debugOutputLn(VALUES, + "About to load binary file for " + fileName); + // Create a J3dLwoParser object to parse the geometry file + // and create the appropriate geometry + J3dLwoParser objParser = null; + switch (fileType) { + case Lw3dLoader.FILE_TYPE_FILENAME: + objParser = + new J3dLwoParser(fileName, + debugPrinter.getValidOutput()); + break; + case Lw3dLoader.FILE_TYPE_URL: + objParser = new J3dLwoParser(urlName, + debugPrinter.getValidOutput()); + break; + } + objParser.createJava3dGeometry(); + // pivot points change the parent transform + if (hasPivot) { + objectTransform.addChild(pivotTransGroup); + } + if (objParser.getJava3dShapeList() != null) { + shapeList = objParser.getJava3dShapeList(); + for (Enumeration e = shapeList.elements() ; + e.hasMoreElements() ;) { + if (!hasPivot || pivotTransGroup == null) + objectTransform.addChild((Shape3D)e.nextElement()); + else + pivotTransGroup.addChild((Shape3D)e.nextElement()); + } + } + } + else { + // Already read that file: Clone original object + debugOutputLn(LINE_TRACE, "Cloning shapes"); + Vector cloneShapeList = cloneObject.getShapeList(); + for (Enumeration e = cloneShapeList.elements() ; + e.hasMoreElements() ;) { + debugOutputLn(LINE_TRACE, " shape clone"); + Shape3D shape = (Shape3D)e.nextElement(); + Shape3D cloneShape = (Shape3D)shape.cloneTree(); + objectTransform.addChild(cloneShape); + } + } + } + + // Create j3d behaviors for the object's animation + objectBehavior = new Vector(); + if (loadBehaviors != 0) { + motion.createJava3dBehaviors(objectTransform); + Behavior b = motion.getBehaviors(); + if (b != null) + objectBehavior.addElement(b); + } + } + + /** + * Return list of Shape3D objects for this object file. This is used + * when cloning objects (if the scene file requests the same object file + * more than once, that object will be cloned instead of recreated each + * time). + */ + Vector getShapeList() { + return shapeList; + } + + /** + * Return the TransformGroup that holds this object file + */ + public TransformGroup getObjectNode() { + return objectTransform; + } + + /** + * Return the Group that holds this object's behaviors. The behaviors + * are grouped separately from the geometry so that they can be handled + * differently by the parent application. + */ + public Vector getObjectBehaviors() + { + debugOutputLn(TRACE, "getObjectBehaviors()"); + return objectBehavior; + } + + + /** + * Utiliy function to print some of the object values. Used in + * debugging. + */ + void printVals() + { + debugOutputLn(VALUES, " OBJECT vals: "); + debugOutputLn(VALUES, " fileName = " + fileName); + debugOutputLn(VALUES, " objName = " + objName); + motion.printVals(); + } +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/LwsPrimitive.java b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsPrimitive.java new file mode 100644 index 0000000..a0b457d --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/LwsPrimitive.java @@ -0,0 +1,68 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + + + +import java.util.Vector; +import javax.media.j3d.Group; +import javax.media.j3d.TransformGroup; + +/** + * This is an interface which is implemented by LwsObject, + * LwsFog, LwsBackground, LwsLight, etc. It provides a generic method + * for objects to access some of the common data in these classes. + */ + +interface LwsPrimitive { + + // interface from which other Lws types (Object, Camera, etc.) + // inherit methods + + public Vector getObjectBehaviors(); + + public TransformGroup getObjectNode(); + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/ParserObject.java b/src/classes/share/com/sun/j3d/loaders/lw3d/ParserObject.java new file mode 100644 index 0000000..1504f22 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/ParserObject.java @@ -0,0 +1,91 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +/** + * This class is a superclass of the binary parsing classes. It provides + * some basic debugging utilities. + */ + +class ParserObject { + + + final static int TRACE = DebugOutput.TRACE, VALUES = DebugOutput.VALUES; + final static int MISC = DebugOutput.MISC, LINE_TRACE = DebugOutput.LINE_TRACE; + final static int NONE = DebugOutput.NONE, EXCEPTION = DebugOutput.EXCEPTION; + final static int TIME = DebugOutput.TIME, WARNING = DebugOutput.WARNING; + + protected DebugOutput debugPrinter; + + + ParserObject() { + debugPrinter = new DebugOutput(EXCEPTION); + } + + ParserObject(int debugVals) { + this(); + debugPrinter.setValidOutput(debugVals); + } + + + protected void debugOutputLn(int outputType, String theOutput) { + if (theOutput.equals("")) + debugPrinter.println(outputType, theOutput); + else + debugPrinter.println(outputType, + getClass().getName() + "::" + theOutput); + } + + protected void debugOutput(int outputType, String theOutput) { + debugPrinter.print(outputType, theOutput); + } + + + +} + + + + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceLine.java b/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceLine.java new file mode 100644 index 0000000..b557474 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceLine.java @@ -0,0 +1,269 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.Component; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.StreamTokenizer; +import java.io.IOException; +import javax.media.j3d.*; +import javax.vecmath.Point3d; + +import com.sun.j3d.loaders.IncorrectFormatException; +import com.sun.j3d.loaders.ParsingErrorException; +import java.io.FileNotFoundException; + +/** + * This class was created to handle "sequence files", which allow + * holosketch-type Tloop sequences to be loaded through the lw3d loader. + * The class reads a sequence file line by line and uses SequenceLine to + * load the file specified in each line.
+ * Idea behind the Tloop process:
+ * Artist creates "tloops" (animations with each keyframe's + * geometry saved out explicitly) where the geometries are spaced + * one frame apart. Then I can automatically create a SwitchValueInterpolator + * based on this spacing. If the number of frames in the sequence is + * greater than the number of frames in the tloop, then it will automatically + * loop until the end of the sequence.
+ * Process:
+ * 1) Artist creates an animation of a null group that has a child with some + * special name, such as "bucket_sequence_bucketsequence.txt.lwo", which tells + * the lw3d loader that it should look for a sequence file by the name of + * bucketsequence.txt. What happens to this object is irrelevant (as far as + * the loader is concerned); all animation information is taken from its + * parent instead.
+ * 2) Artist saves out the geometry of the bucket at whatever frames she wants + * to. If she's saving a tloop (a sequence of frames), she should save them + * under the names xxx.lwo, where xxx is the 3-digit sequence number + * (000, 001, 002, etc.).
+ * 3) Artist creates the sequence file, which lists all saved geometry files + * (except sequences - these can be referred to simply by the first file + * (...000.lwo)), along with their associated start/end frames. She also lists + * the number of files in the sequence, although this parameter is implied + * anyway, through the existence of the sequence files and their naming + * convention. Maybe we should trash this guy.
+ * 4) In the lw3d loader, when LwsObject encounters an object with the + * filename "..._sequence_.lwo", it searches for filename. If + * found, it parses the file (using the SequenceReader class) to retrieve + * all parameters.
+ * 5) Each SequenceLine creates a Java3D group containing its objects. This + * is either a plain-old-Group (if there is only one object) or a Switch group + * with a SwitchValueInterpolator.
+ * 6) SequenceReader constructs a Switch group and adds all SequenceLine groups + * to this new group. It also creates a SwitchPathInterpolator (child of + * PathInterolator) that contsructs an Alpha based on the startFrame values of + * each SequenceLine. It creates a group and adds the SwitchPathInterpolator + * plus any SequenceLine SwitchValueInterpolators to this group.
+ * 7) LwsObject adds the SequenceReader Switch group to its objectTransform. + * It does a getBehaviors() from SequenceReader and adds the result (the + * SwitchPathInterpolator group) to its objectBehaviors group.
+ * 8) Done. + */ + +class SequenceLine { + + int startFrame; + int endFrame; + String fileName; + + Group geometryGroup = null; + Behavior behaviors; + int numFrames; + float totalTime; + int totalFrames; + + // storedRefList keeps references to already loaded objects + static Hashtable storedRefList = new Hashtable(); + + SequenceLine(StreamTokenizer st, float time, int frames) + throws ParsingErrorException { + try { + totalTime = time; + totalFrames = frames; + startFrame = (int)st.nval; + st.nextToken(); + endFrame = (int)st.nval; + st.nextToken(); + fileName = st.sval; + numFrames = endFrame - startFrame + 1; + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + + /** + * Creates a SwitchValueInterpolator which is used to switch between + * the objects specified for the sequence. This is done for files + * that end in "000", meaning that there are a sequence of files + * with that same base name that should specify every frame of a + * sequence for an object. The Switch node is used to hold all of the + * files and the Switch Behavior node is used to activate switching + * at the right time and to the right object. + */ + private void createSwitchBehavior(Switch target) { + + int loopCount = -1; + float animTime = 1000.0f * totalTime * + (float)(target.numChildren())/(float)totalFrames; + float startTime = 1000f * totalTime * + (float)startFrame/(float)totalFrames; + Alpha theAlpha = + new Alpha(-1, (long)startTime, 0, (long)animTime, 0, 0); + + SwitchValueInterpolator b=new SwitchValueInterpolator(theAlpha,target); + behaviors = b; + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000000.0); + b.setSchedulingBounds(bounds); + target.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + target.addChild(behaviors); + + } + + /** + * Create Java3d objects from the data in the sequence line. This + * means that for a tloop file (ends in "000"), we're going to create + * the appropriate geometry for each file, put them all in a Switch + * node, then create a SwitchValueInterpolator to swap between the + * frames of the tloop. If it's not a tloop, then we're just going to + * create the geometry for that file. + */ + void createJava3dObjects(int debugVals, int loadBehaviors) + throws IncorrectFormatException, FileNotFoundException { + if (fileName.indexOf("000") != -1) { // Tloop + int index = fileName.indexOf("000"); + String fileNameBase = fileName.substring(0, index); + Switch s = new Switch(); + s.setCapability(Switch.ALLOW_SWITCH_READ); + s.setCapability(Switch.ALLOW_SWITCH_WRITE); + String tempFileName = fileName; + int fileNum = 0; + while ((new File(tempFileName)).exists()) { + if (storedRefList.get(tempFileName) != null) { + // System.out.println("retrieve stored version of " + + // tempFileName); + SharedGroup storedGroup = + (SharedGroup)storedRefList.get(tempFileName); + Link newLink = new Link(storedGroup); + s.addChild(newLink); + } + else { + // System.out.println("reading " + tempFileName); + J3dLwoParser objParser = new J3dLwoParser(tempFileName, + debugVals); + objParser.createJava3dGeometry(); + TransformGroup t = new TransformGroup(); + SharedGroup newSharedGroup = new SharedGroup(); + storedRefList.put(tempFileName, newSharedGroup); + newSharedGroup.addChild(t); + Link newLink = new Link(newSharedGroup); + s.addChild(newLink); + if (objParser.getJava3dShapeList() != null) { + for (Enumeration e = + objParser.getJava3dShapeList().elements() ; + e.hasMoreElements() ;) { + t.addChild((Shape3D)e.nextElement()); + } + } + } + ++fileNum; + String fileNumString = String.valueOf(fileNum); + if (fileNum < 10) + fileNumString = "00" + fileNumString; + else if (fileNum < 100) + fileNumString = "0" + fileNumString; + tempFileName = fileNameBase + fileNumString + ".lwo"; + } + behaviors = null; + if (loadBehaviors != 0) { + createSwitchBehavior(s); + } + geometryGroup = (Group)s; + } + else {// Not a tloop, just a file + geometryGroup = new Group(); + if (storedRefList.get(fileName) != null) { + // System.out.println("getting old ref to " + fileName); + SharedGroup storedGroup = + (SharedGroup)storedRefList.get(fileName); + Link newLink = new Link(storedGroup); + geometryGroup.addChild(newLink); + } + else { + // System.out.println("reading " + fileName); + J3dLwoParser objParser = new J3dLwoParser(fileName, + debugVals); + objParser.createJava3dGeometry(); + TransformGroup t = new TransformGroup(); + if (objParser.getJava3dShapeList() != null) { + for (Enumeration e = objParser.getJava3dShapeList().elements() ; + e.hasMoreElements() ;) { + t.addChild((Shape3D)e.nextElement()); + } + } + SharedGroup newSharedGroup = new SharedGroup(); + newSharedGroup.addChild(t); + Link newLink = new Link(newSharedGroup); + geometryGroup.addChild(newLink); + storedRefList.put(fileName, newSharedGroup); + } + } + } + + Group getGeometry() { + return geometryGroup; + } + + Behavior getBehavior() { + return behaviors; + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceReader.java b/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceReader.java new file mode 100644 index 0000000..0ba239f --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/SequenceReader.java @@ -0,0 +1,172 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.Component; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.StreamTokenizer; +import java.io.IOException; +import javax.media.j3d.*; +import javax.vecmath.Point3d; + +import com.sun.j3d.loaders.IncorrectFormatException; +import com.sun.j3d.loaders.ParsingErrorException; +import java.io.FileNotFoundException; + +/** + * This class was created to read a special file format devised for + * JavaOne '98 that allowed Tloop functionality inside of Lightwave. It + * would be best to find a more standard solution, including using some + * plug-in for lw3d that I've heard of that allows artists to automatically + * save out the geometry for a file at every frame. + */ + +class SequenceReader { + + + Vector sequenceLines; + float totalTime; + int totalFrames; + + TransformGroup objectTransform; + Vector behaviorVector; + + /** + * Constructor: parses a sequence file and creates a new SequenceLine + * object to read in every line of the file + */ + SequenceReader(String filename, float time, int frames) + throws ParsingErrorException { + totalTime = time; + totalFrames = frames; + sequenceLines = new Vector(); + try { + // System.out.println("reading sequence from " + filename); + StreamTokenizer st = new StreamTokenizer(new BufferedReader( + new FileReader(filename))); + st.wordChars('_', '_'); + st.wordChars('/', '/'); + int type = st.nextToken(); + while (st.ttype != StreamTokenizer.TT_EOF) { + sequenceLines.addElement(new SequenceLine(st, + totalTime, + totalFrames)); + st.nextToken(); + } + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + + /** + * Creates Java3D objects from the data defined in the sequence + * file. Calls each sequenceLine object to create its own + * j3d objects, then puts all of those objects in a single Switch + * node. Finally, it creates a SwitchPathInterpolator object which + * handles switching between each object/s defined by each line + */ + void createJava3dObjects(int debugVals, int loadBehaviors) + throws FileNotFoundException { + + objectTransform = new TransformGroup(); + behaviorVector = new Vector(); + Enumeration e = sequenceLines.elements(); + Switch switchNode = new Switch(); + switchNode.setCapability(Switch.ALLOW_SWITCH_READ); + switchNode.setCapability(Switch.ALLOW_SWITCH_WRITE); + objectTransform.addChild(switchNode); + while (e.hasMoreElements()) { + SequenceLine line = (SequenceLine)e.nextElement(); + line.createJava3dObjects(debugVals, loadBehaviors); + if (line.getGeometry() != null) + switchNode.addChild(line.getGeometry()); + //objectTransform.addChild(line.getGeometry()); + if (line.getBehavior() != null) { + behaviorVector.addElement(line.getBehavior()); + } + } + float knots[] = new float[sequenceLines.size() + 1]; + for (int i = 0; i < knots.length-1; ++i) { + SequenceLine sl = (SequenceLine)sequenceLines.elementAt(i); + knots[i] = (float)sl.startFrame/(float)totalFrames; + } + knots[knots.length-1] = 1.0f; + Alpha theAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE, + 0, 0, (long)(1000f * totalTime), 0, + 0, 0, 0, 0); + + SwitchPathInterpolator switchPath = + new SwitchPathInterpolator(theAlpha, + knots, + switchNode); + BoundingSphere bounds = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000000.0); + switchPath.setSchedulingBounds(bounds); + switchNode.addChild(switchPath); + behaviorVector.addElement(switchPath); + } + + TransformGroup getObjectNode() { + return objectTransform; + } + + Vector getObjectBehaviors() { + return behaviorVector; + } + + void printLines() { + Enumeration e = sequenceLines.elements(); + while (e.hasMoreElements()) { + SequenceLine line = (SequenceLine)e.nextElement(); + } + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/ShapeHolder.java b/src/classes/share/com/sun/j3d/loaders/lw3d/ShapeHolder.java new file mode 100644 index 0000000..38f9d66 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/ShapeHolder.java @@ -0,0 +1,267 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.util.Vector; +import javax.vecmath.Vector3f; + + +/** + * This class holds all of the vertex/facet/normal/surface-reference + * data for a particular object. It has utilities to calculate facet normals, + * but this is no longer in use since using the new GeomInfo utilities. + */ + +class ShapeHolder extends ParserObject { + + + Vector facetSizesList; + Vector facetIndicesList; + int facetIndicesArray[]; + int currentNumIndices = 0; + int numSurf; + int numVerts; + int facetIndices[]; + int facetSizes[]; + int normalIndices[]; + float normalCoords[]; + float coordsArray[]; + + ShapeHolder() { + } + + ShapeHolder(int debugVals) { + super(debugVals); + } + + + /** + * Print out (to stdout) the geometry data (coords, indices, + * and facet sizes). This is a debugging utility. + */ + void printGeometryData(LwoSurface surface) { + int i, j; + int indicesIndex = 0; + System.out.println("\nPolygon Data:"); + System.out.println(" Surface color = " + surface.color); + System.out.println(" Surface diffuse = " + surface.diffuseColor); + for (i = 0; i < facetSizes.length; ++i) { + int polySize = facetSizes[i]; + System.out.println("Facet of size " + polySize); + for (j = 0; j < polySize; ++j) { + int coordIndex = 3 * facetIndices[indicesIndex++]; + System.out.println("x, y, z = " + + coordsArray[coordIndex] + ", " + + coordsArray[coordIndex+1] + ", " + + coordsArray[coordIndex+2]); + } + } + } + + /** + * Constructs geometry arrays given a winding rule (it turns out that + * lw3d winding is opposite of j3d winding, so this is always set to + * true in J3dLwoParser) + */ + void createArrays(boolean reverseWinding) { + debugOutputLn(TRACE, "createArrays()"); + // debugOutputLn(VALUES, "facetIndices, faceSizesList = " + + // facetIndicesList + facetSizesList); + //debugOutputLn(VALUES, "ind and sizes size " + + // facetIndicesList.size() + ", " + + // facetSizesList.size()); + //facetIndices = + // new int[facetIndicesList.size()]; + facetIndices = new int[currentNumIndices]; + if (reverseWinding) { + int facetBeginIndex = 0; + for (int facetIndex = 0; + facetIndex < facetSizesList.size(); + ++facetIndex) { + int currFaceSize = + ((Integer)facetSizesList.elementAt(facetIndex)).intValue(); + int tempFace[] = new int[currFaceSize]; + for (int j = 0; j < currFaceSize; ++j) { + facetIndices[facetBeginIndex + j] = + facetIndicesArray[facetBeginIndex + + currFaceSize - j - 1]; + } + facetBeginIndex += currFaceSize; + } + + } + else { + for (int i = 0; i < facetIndices.length; ++i) { + facetIndices[i] = facetIndicesArray[i]; + } + } + + debugOutputLn(LINE_TRACE, "facetIndices.len and coordsArray.len = " + + facetIndices.length + ", " + coordsArray.length); + if (((Integer)facetSizesList.elementAt(0)).intValue() < 3) { + // if we're dealing with point/line primitives, then let's abandon + // the indexed route and simply construct a new coordsArray + // that holds the direct values we need for a GeometryArray + // object + debugOutputLn(LINE_TRACE, "Using direct geometry because " + + "facetIndices is of size " + + facetIndices.length + + " and coordsArray is of length "+ + coordsArray.length); + float newCoordsArray[] = new float[facetIndices.length * 3]; + int newCoordsIndex = 0; + for (int i = 0; i < facetIndices.length; ++i) { + newCoordsArray[newCoordsIndex++] = + coordsArray[facetIndices[i]*3]; + newCoordsArray[newCoordsIndex++] = + coordsArray[facetIndices[i]*3+1]; + newCoordsArray[newCoordsIndex++] = + coordsArray[facetIndices[i]*3+2]; + } + coordsArray = newCoordsArray; + facetIndices = null; + } + + facetSizes = + new int[facetSizesList.size()]; + for (int i = 0; i < facetSizes.length; ++i) { + facetSizes[i] = + ((Integer)facetSizesList.elementAt(i)).intValue(); + } + + facetSizesList = null; // Force garbage collection on Vectors + facetIndicesList = null; + facetIndicesArray = null; + } + + /** + * Force gc on all array objects + */ + void nullify() { + facetSizesList = null; // Force garbage collection on everything + facetIndicesList = null; + facetIndicesArray = null; + facetSizes = null; + facetIndices = null; + normalCoords = null; + normalIndices = null; + } + + /** + * This method calculates facet normals for the geometry. It is no + * longer used, as we're now using the GeometryInfo utility to calculate + * smooth normals + */ + void calcNormals() { + debugOutputLn(TRACE, "calcNormals()"); + debugOutputLn(LINE_TRACE, "coordsLength, facetsizes.len = " + + coordsArray.length + ", " + facetSizes.length); + if (facetSizes[0] > 2) { + // points and lines don't need normals, polys do + if (facetIndices != null) { + normalIndices = new int[facetIndices.length]; + normalCoords = new float[facetIndices.length * 3]; + } + else { + normalCoords = new float[coordsArray.length]; + } + debugOutputLn(LINE_TRACE, "normalCoords, incides len = " + + normalCoords.length + ", " + + ((facetIndices == null) ? 0 : normalIndices.length)); + int facetIndex = 0; + int tempIndex = -1; + for (int i = 0; i < facetSizes.length; i += 1) { + Vector3f norm; + int currFacetSize = facetSizes[i]; + //debugOutputLn(LINE_TRACE, " i, facetIndex, currSize = " + + // i + ", " + facetIndex + ", " + currFacetSize); + if (currFacetSize < 3) { + // This shouldn't occur + norm = new Vector3f(0f, 0f, 1f); + } + else { + Vector3f v1, v2; + int index1, index2, index3; + if (facetIndices != null) { + index1 = facetIndices[facetIndex]; + index2 = facetIndices[facetIndex+1]; + index3 = facetIndices[facetIndex+2]; + //debugOutputLn(VALUES, " index123 = " + + // index1 + ", " + index2 + ", " + index3); + } + else { + index1 = facetIndex; + index2 = facetIndex+1; + index3 = facetIndex+2; + } + v1 = new + Vector3f(coordsArray[index2*3] - coordsArray[index1*3], + coordsArray[index2*3+1] - coordsArray[index1*3+1], + coordsArray[index2*3+2] - coordsArray[index1*3+2]); + v2 = new + Vector3f(coordsArray[index3*3] - coordsArray[index1*3], + coordsArray[index3*3+1] - coordsArray[index1*3+1], + coordsArray[index3*3+2] - coordsArray[index1*3+2]); + //debugOutputLn(VALUES, "v1, v2 = " + v1 + v2); + norm = new Vector3f(); + norm.cross(v1, v2); + norm.normalize(norm); + } + + for (int j = 0; j < currFacetSize; ++j) { + int normIndex = facetIndex + j; + normalCoords[normIndex*3] = norm.x; + normalCoords[normIndex*3+1] = norm.y; + normalCoords[normIndex*3+2] = norm.z; + if (facetIndices != null) + normalIndices[normIndex] = normIndex; + } + facetIndex += currFacetSize; + } + } + debugOutputLn(TRACE, "done with calcNormals()"); + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/SwitchPathInterpolator.java b/src/classes/share/com/sun/j3d/loaders/lw3d/SwitchPathInterpolator.java new file mode 100644 index 0000000..eb167da --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/SwitchPathInterpolator.java @@ -0,0 +1,129 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + + package com.sun.j3d.loaders.lw3d; + + +import javax.vecmath.*; +import java.util.BitSet; +import java.util.Enumeration; + +import javax.media.j3d.Alpha; +import javax.media.j3d.Node; +import javax.media.j3d.NodeReferenceTable; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.Switch; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class was used in conjunction with SequenceReader to create + * Tloop functionality inside of Lightwave files. This behavior handles + * the switching between objects defined in separate lines of a + * sequence file. That is, each line in a sequence file has the name + * of an object (or an object sequence, if the name ends in "000") + * and details the start and end frames that that object should be active. + * This class determines which object/s defined in the file should be active + * at any given time during the animation. + */ + +class SwitchPathInterpolator extends FloatValueInterpolator { + + Switch target; + int firstSwitchIndex; + int lastSwitchIndex; + int currentChild; + int childCount; + + /** + * Constructs a new SwitchPathInterpolator object. + * @param alpha the alpha object for this interpolator + * @param knots an array of knot values that specify a spline + */ + SwitchPathInterpolator(Alpha alpha, float knots[], Switch target) { + + super(alpha, knots, new float[knots.length]); + + if (knots.length != (target.numChildren() + 1)) + throw new IllegalArgumentException(J3dUtilsI18N.getString("SwitchPathInterpolator0")); + + this.target = target; + firstSwitchIndex = 0; + lastSwitchIndex = target.numChildren() - 1; + childCount = lastSwitchIndex + 1; + } + + /** + * This method sets the correct child for the Switch node according + * to alpha + * @param criteria enumeration of criteria that have triggered this wakeup + */ + + public void processStimulus(Enumeration criteria) { + + int child; + + // Handle stimulus + if (this.getAlpha() != null) { + + // Let PathInterpolator calculate the correct + // interpolated knot point + computePathInterpolation(); + + if (currentKnotIndex > 0) + child = currentKnotIndex - 1; + else + child = 0; + + if (target.getWhichChild() != child) { + target.setWhichChild(child); + } + + if ((this.getAlpha()).finished()) + return; + } + + wakeupOn(defaultWakeupCriterion); + } + +} diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/TargaReader.java b/src/classes/share/com/sun/j3d/loaders/lw3d/TargaReader.java new file mode 100644 index 0000000..a96acad --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/TargaReader.java @@ -0,0 +1,199 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.applet.Applet; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.MediaTracker; +import java.awt.Frame; +import java.awt.Image; +import java.awt.image.MemoryImageSource; +import java.awt.Toolkit; +import java.io.FileNotFoundException; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.BufferedInputStream; +import com.sun.j3d.loaders.IncorrectFormatException; +import com.sun.j3d.loaders.ParsingErrorException; +import java.io.IOException; + +/** + * This class parses a standard Targa file and retrieves the image stored + * therein, storing the pixel data in a BufferedImage. + */ + +class TargaReader extends ParserObject { + + BufferedInputStream bufferedReader; + Image theImage = null; + + /** + * Constructor: creates file reader and calls parseFile() to do the real + * work + */ + TargaReader(String fileName, int debugVals) throws FileNotFoundException { + super(debugVals); + debugOutputLn(TRACE, "constructor"); + bufferedReader = new BufferedInputStream( + new DataInputStream(new FileInputStream(fileName))); + if (bufferedReader != null) + parseFile(); + } + + /** + * Returns the image that was created from parsing the targa file (null + * if the file reading failed) + */ + Image getImage() { + return theImage; + } + + /** + * This method parses the file and stores the pixel data in a + * BufferedImage. The basic file format is: + * Byte Description + * + * 0 Image ID Length + * 1 Colormap type + * 2 Image Type + * 3-4 Colormap spec: 1st entry index + * 5-6 Colormap spec: length + * 7 Colormap spec: entry size + * 8-9 X-origin of lower-left corner + * 10-11 Y-origin of lower-left corner + * 12-13 Image width + * 14-15 Image height + * 16 Pixel depth + * 17 00(origin)(alpha) + * first 2 bytes 0, next 2 starting corner, + * last four number of overlay bits per pixel + * 18- Image ID + * ?? Colormap data + * ?? Image Data + * ?? Developer Area + * ?? Extension Area + * ?? File Footer + * + * We're going to make some assumptions about the format of files we're + * asked to load. In particular, we're not going to do any colormpa-based + * images: the images need to be either 24-bit or 32-bit true color. + * We're also going to ignore vaiours parameters in the header block, since + * they complicate life and don't appear to be used in Lightwave image + * files. In particular, the following fields will be ignored: + * Image ID, colormap info, xy origins, and alpha/overlay bits. + */ + + void parseFile() + throws IncorrectFormatException, ParsingErrorException { + try { + int idLength = bufferedReader.read(); + int colormapPresent = bufferedReader.read(); + int imageType = bufferedReader.read(); + bufferedReader.skip(9); // skipping camp and xy origin data + int width = bufferedReader.read() | bufferedReader.read() << 8; + int height = bufferedReader.read() | bufferedReader.read() << 8; + int depth = bufferedReader.read(); + int flags = bufferedReader.read(); + boolean bottomToTop = ((flags & 0x20) == 0); + boolean leftToRight = ((flags & 0x10) == 0); + bufferedReader.skip(idLength); + + // Check on the file parameters to see whether we should punt + if ((colormapPresent == 1) || + imageType != 2 || + (depth != 24 && + depth != 32)) { + // Punt + throw new IncorrectFormatException( + "This format is not readable by the Lightwave " + + "loader. Only 24- or 32-bit true-color " + + "uncompressed Targa images will work"); + } + + // Image format must be okay for us to read + BufferedImage bImage = + new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + int[] imageBits = + ((DataBufferInt)bImage.getRaster().getDataBuffer()).getData(); + + int row; + int column; + + for (int i = 0; i < height; ++i) { + if (bottomToTop) + row = (height - i - 1); + else + row = i; + for (int j = 0; j < width; ++j) { + + if (leftToRight) + column = j; + else + column = (width - j - 1); + + int blue = bufferedReader.read(); + int green = bufferedReader.read(); + int red = bufferedReader.read(); + int alpha = 0xff; + if (depth == 32) + alpha = bufferedReader.read(); + imageBits[row*width + column] = alpha << 24 | + red << 16 | + green << 8 | + blue; + } + } + theImage = bImage; + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + +} + diff --git a/src/classes/share/com/sun/j3d/loaders/lw3d/TextfileParser.java b/src/classes/share/com/sun/j3d/loaders/lw3d/TextfileParser.java new file mode 100644 index 0000000..13f9611 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/lw3d/TextfileParser.java @@ -0,0 +1,245 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.lw3d; + +import java.io.*; +import com.sun.j3d.loaders.ParsingErrorException; + +/** + * This class is a superclass for most of the Lws* Scene-file parsing + * classes. It provides some debugging utilities, as well as utilities for + * reading the types of data common to this loader. + */ + +class TextfileParser { + + // class variables + static int WORD = StreamTokenizer.TT_WORD; + static int NUMBER = StreamTokenizer.TT_NUMBER; + int currentLevel = 3; + final static int TRACE = DebugOutput.TRACE, VALUES = DebugOutput.VALUES; + final static int MISC = DebugOutput.MISC, LINE_TRACE = DebugOutput.LINE_TRACE; + final static int NONE = DebugOutput.NONE, EXCEPTION = DebugOutput.EXCEPTION; + final static int TIME = DebugOutput.TIME; + protected DebugOutput debugPrinter; + char lineSeparatorChar = 0; + + TextfileParser() { + debugPrinter = new DebugOutput(EXCEPTION); + String lineSeparator = System.getProperty("line.separator"); + lineSeparatorChar = lineSeparator.charAt(0); + debugOutputLn(VALUES, "lineSeparatorChar = " + (int)lineSeparatorChar); + } + + + protected void debugOutputLn(int outputType, String theOutput) { + if (theOutput.equals("")) + debugPrinter.println(outputType, theOutput); + else { + debugPrinter.println(outputType, + getClass().getName() + "::" + theOutput); + } + } + + protected void debugOutput(int outputType, String theOutput) { + debugPrinter.print(outputType, theOutput); + } + + /** + * Utility method to advance the tokenizer until we see the given + * string. This is used to skip by various parameters that we + * currently ignore in the loader. + */ + void skipUntilString(StreamTokenizer st, String theString) + throws ParsingErrorException { + boolean done = false; + try { + while (!done) { + st.nextToken(); + if (st.ttype == WORD && + st.sval.equals(theString)) + done = true; + } + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + + + /** + * Returns number from the tokenizer. Note that we don't recognize + * numbers in the tokenizer automatically because numbers might be in + * scientific notation, which isn't processed correctly by + * StreamTokenizer + */ + double getNumber(StreamTokenizer st) + throws ParsingErrorException, NumberFormatException { + try { + int token = st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + checkType(st, WORD); + return ((Double.valueOf(st.sval)).doubleValue()); + } + + /** + * Returns String from the tokenizer + */ + String getString(StreamTokenizer st) throws ParsingErrorException { + try { + st.nextToken(); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + checkType(st, WORD); + return (st.sval); + } + + /** + * Returns a "name" from the stream. This is different from simply a + * String because the name could contain whitespace characters + * (such as "object 1" or "objectname (sequence)") that would confuse + * the string parser. So we just grab all characters until EOL and + * concatenate them together to form the name + */ + String getName(StreamTokenizer st) throws ParsingErrorException { + String theName = ""; + st.ordinaryChar(lineSeparatorChar); + st.ordinaryChar('\n'); + st.ordinaryChar('\r'); + try { + st.nextToken(); + while (st.ttype != lineSeparatorChar && + st.ttype != '\r' && + st.ttype != '\n') { + if (st.ttype != '(' && + st.ttype != ')') + theName += st.sval; + st.nextToken(); + } + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + st.whitespaceChars(lineSeparatorChar, lineSeparatorChar); + st.whitespaceChars('\n', '\n'); + st.whitespaceChars('\r', '\r'); + debugOutputLn(VALUES, "name = " + theName); + return theName; + } + + /** + * Gets the next token and ensures that it is the string we were + * expecting to see + */ + void getAndCheckString(StreamTokenizer st, String expectedValue) + throws ParsingErrorException { + try { + st.nextToken(); + checkString(st, expectedValue); + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + + /** + * Error checking routine - makes sure the current token is the string + * we were expecting + */ + void checkString(StreamTokenizer st, String theString) throws + ParsingErrorException { + if (!(st.ttype == StreamTokenizer.TT_WORD) || + !st.sval.equals(theString)) + throw new ParsingErrorException( + "Bad String Token (wanted " + theString + ", got " + st.sval + + ": " + st.toString()); + } + + /** + * Error checking routine - makes sure the current token is of the right + * type + */ + void checkType(StreamTokenizer st, int theType) + throws ParsingErrorException { + if (!(st.ttype == theType)) + throw new ParsingErrorException( + "Bad Type Token, Expected " + theType + " and received" + + st.ttype); + } + + /** + * Utility routine - gets next token, checks it against our expectation, + * then skips a given number of tokens. This can be used to parse + * through (and ignore) certain parameter/value sets in the files + */ + void skip(StreamTokenizer st, String tokenString, int skipVals) + throws ParsingErrorException { + try { + st.nextToken(); + checkString(st, tokenString); + for (int i = 0; i < skipVals; ++i) { + st.nextToken(); + } + } + catch (IOException e) { + throw new ParsingErrorException(e.getMessage()); + } + } + + /** + * Utility method- used to check whether the current token is equal + * to the given string + */ + boolean isCurrentToken(StreamTokenizer st, String tokenString) { + if (st.ttype == WORD) + return (st.sval.equals(tokenString)); + return false; + } +} diff --git a/src/classes/share/com/sun/j3d/loaders/objectfile/DefaultMaterials.java b/src/classes/share/com/sun/j3d/loaders/objectfile/DefaultMaterials.java new file mode 100644 index 0000000..b573272 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/objectfile/DefaultMaterials.java @@ -0,0 +1,952 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.objectfile; + +/** + * This class provides default materials for the object file loader + */ +class DefaultMaterials { + /** + * String that describes the default materials. + */ + static final String materials = + "newmtl amber\n" + + "Ka 0.0531 0.0531 0.0531\n" + + "Kd 0.5755 0.2678 0.0000\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl amber_trans\n" + + "Ka 0.0531 0.0531 0.0531\n" + + "Kd 0.5755 0.2678 0.0000\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "d 0.8400\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl charcoal\n" + + "Ka 0.0082 0.0082 0.0082\n" + + "Kd 0.0041 0.0041 0.0041\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl lavendar\n" + + "Ka 0.1281 0.0857 0.2122\n" + + "Kd 0.2187 0.0906 0.3469\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl navy_blue\n" + + "Ka 0.0000 0.0000 0.0490\n" + + "Kd 0.0000 0.0000 0.0531\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl pale_green\n" + + "Ka 0.0444 0.0898 0.0447\n" + + "Kd 0.0712 0.3796 0.0490\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl pale_pink\n" + + "Ka 0.0898 0.0444 0.0444\n" + + "Kd 0.6531 0.2053 0.4160\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl pale_yellow\n" + + "Ka 0.3606 0.3755 0.0935\n" + + "Kd 0.6898 0.6211 0.1999\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl peach\n" + + "Ka 0.3143 0.1187 0.0167\n" + + "Kd 0.6367 0.1829 0.0156\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl periwinkle\n" + + "Ka 0.0000 0.0000 0.1184\n" + + "Kd 0.0000 0.0396 0.8286\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl redwood\n" + + "Ka 0.0204 0.0027 0.0000\n" + + "Kd 0.2571 0.0330 0.0000\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl smoked_glass\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0041 0.0041 0.0041\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "d 0.9800\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl aqua_filter\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.3743 0.6694 0.5791\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "d 0.9800\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl yellow_green\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.1875 0.4082 0.0017\n" + + "Ks 0.1878 0.1878 0.1878\n" + + "illum 2\n" + + "Ns 91.4700\n" + + "\n" + + "newmtl bluetint\n" + + "Ka 0.1100 0.4238 0.5388\n" + + "Kd 0.0468 0.7115 0.9551\n" + + "Ks 0.3184 0.3184 0.3184\n" + + "illum 9\n" + + "d 0.5700\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl plasma\n" + + "Ka 0.4082 0.0816 0.2129\n" + + "Kd 1.0000 0.0776 0.4478\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 9\n" + + "d 0.7500\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl emerald\n" + + "Ka 0.0470 1.0000 0.0000\n" + + "Kd 0.0470 1.0000 0.0000\n" + + "Ks 0.2000 0.2000 0.2000\n" + + "illum 9\n" + + "d 0.7500\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl ruby\n" + + "Ka 1.0000 0.0000 0.0000\n" + + "Kd 1.0000 0.0000 0.0000\n" + + "Ks 0.2000 0.2000 0.2000\n" + + "illum 9\n" + + "d 0.7500\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl sapphire\n" + + "Ka 0.0235 0.0000 1.0000\n" + + "Kd 0.0235 0.0000 1.0000\n" + + "Ks 0.2000 0.2000 0.2000\n" + + "illum 9\n" + + "d 0.7500\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl white\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 1.0000 1.0000 1.0000\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl red\n" + + "Ka 0.4449 0.0000 0.0000\n" + + "Kd 0.7714 0.0000 0.0000\n" + + "Ks 0.8857 0.0000 0.0000\n" + + "illum 2\n" + + "Ns 136.4300\n" + + "\n" + + "newmtl blue_pure\n" + + "Ka 0.0000 0.0000 0.5000\n" + + "Kd 0.0000 0.0000 1.0000\n" + + "Ks 0.0000 0.0000 0.5000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl lime\n" + + "Ka 0.0000 0.5000 0.0000\n" + + "Kd 0.0000 1.0000 0.0000\n" + + "Ks 0.0000 0.5000 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl green\n" + + "Ka 0.0000 0.2500 0.0000\n" + + "Kd 0.0000 0.2500 0.0000\n" + + "Ks 0.0000 0.2500 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl yellow\n" + + "Ka 1.0000 0.6667 0.0000\n" + + "Kd 1.0000 0.6667 0.0000\n" + + "Ks 1.0000 0.6667 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl purple\n" + + "Ka 0.5000 0.0000 1.0000\n" + + "Kd 0.5000 0.0000 1.0000\n" + + "Ks 0.5000 0.0000 1.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl orange\n" + + "Ka 1.0000 0.1667 0.0000\n" + + "Kd 1.0000 0.1667 0.0000\n" + + "Ks 1.0000 0.1667 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl grey\n" + + "Ka 0.5000 0.5000 0.5000\n" + + "Kd 0.1837 0.1837 0.1837\n" + + "Ks 0.5000 0.5000 0.5000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl rubber\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0100 0.0100 0.0100\n" + + "Ks 0.1000 0.1000 0.1000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl flaqua\n" + + "Ka 0.0000 0.4000 0.4000\n" + + "Kd 0.0000 0.5000 0.5000\n" + + "illum 1\n" + + "\n" + + "newmtl flblack\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0041 0.0041 0.0041\n" + + "illum 1\n" + + "\n" + + "newmtl flblue_pure\n" + + "Ka 0.0000 0.0000 0.5592\n" + + "Kd 0.0000 0.0000 0.7102\n" + + "illum 1\n" + + "\n" + + "newmtl flgrey\n" + + "Ka 0.2163 0.2163 0.2163\n" + + "Kd 0.5000 0.5000 0.5000\n" + + "illum 1\n" + + "\n" + + "newmtl fllime\n" + + "Ka 0.0000 0.3673 0.0000\n" + + "Kd 0.0000 1.0000 0.0000\n" + + "illum 1\n" + + "\n" + + "newmtl florange\n" + + "Ka 0.6857 0.1143 0.0000\n" + + "Kd 1.0000 0.1667 0.0000\n" + + "illum 1\n" + + "\n" + + "newmtl flpurple\n" + + "Ka 0.2368 0.0000 0.4735\n" + + "Kd 0.3755 0.0000 0.7510\n" + + "illum 1\n" + + "\n" + + "newmtl flred\n" + + "Ka 0.4000 0.0000 0.0000\n" + + "Kd 1.0000 0.0000 0.0000\n" + + "illum 1\n" + + "\n" + + "newmtl flyellow\n" + + "Ka 0.7388 0.4925 0.0000\n" + + "Kd 1.0000 0.6667 0.0000\n" + + "illum 1\n" + + "\n" + + "newmtl pink\n" + + "Ka 0.9469 0.0078 0.2845\n" + + "Kd 0.9878 0.1695 0.6702\n" + + "Ks 0.7429 0.2972 0.2972\n" + + "illum 2\n" + + "Ns 106.2000\n" + + "\n" + + "newmtl flbrown\n" + + "Ka 0.0571 0.0066 0.0011\n" + + "Kd 0.1102 0.0120 0.0013\n" + + "illum 1\n" + + "\n" + + "newmtl brown\n" + + "Ka 0.1020 0.0185 0.0013\n" + + "Kd 0.0857 0.0147 0.0000\n" + + "Ks 0.1633 0.0240 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl glass\n" + + "Ka 1.0000 1.0000 1.0000\n" + + "Kd 0.4873 0.4919 0.5306\n" + + "Ks 0.6406 0.6939 0.9020\n" + + "illum 2\n" + + "Ns 200.0000\n" + + "\n" + + "newmtl flesh\n" + + "Ka 0.4612 0.3638 0.2993\n" + + "Kd 0.5265 0.4127 0.3374\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl aqua\n" + + "Ka 0.0000 0.4000 0.4000\n" + + "Kd 0.0000 0.5000 0.5000\n" + + "Ks 0.5673 0.5673 0.5673\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl black\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0020 0.0020 0.0020\n" + + "Ks 0.5184 0.5184 0.5184\n" + + "illum 2\n" + + "Ns 157.3600\n" + + "\n" + + "newmtl silver\n" + + "Ka 0.9551 0.9551 0.9551\n" + + "Kd 0.6163 0.6163 0.6163\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl dkblue_pure\n" + + "Ka 0.0000 0.0000 0.0449\n" + + "Kd 0.0000 0.0000 0.1347\n" + + "Ks 0.0000 0.0000 0.5673\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl fldkblue_pure\n" + + "Ka 0.0000 0.0000 0.0449\n" + + "Kd 0.0000 0.0000 0.1347\n" + + "illum 1\n" + + "\n" + + "newmtl dkgreen\n" + + "Ka 0.0000 0.0122 0.0000\n" + + "Kd 0.0058 0.0245 0.0000\n" + + "Ks 0.0000 0.0490 0.0000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl dkgrey\n" + + "Ka 0.0490 0.0490 0.0490\n" + + "Kd 0.0490 0.0490 0.0490\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl ltbrown\n" + + "Ka 0.1306 0.0538 0.0250\n" + + "Kd 0.2776 0.1143 0.0531\n" + + "Ks 0.3000 0.1235 0.0574\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl fldkgreen\n" + + "Ka 0.0000 0.0122 0.0000\n" + + "Kd 0.0058 0.0245 0.0000\n" + + "illum 1\n" + + "\n" + + "newmtl flltbrown\n" + + "Ka 0.1306 0.0538 0.0250\n" + + "Kd 0.2776 0.1143 0.0531\n" + + "illum 1\n" + + "\n" + + "newmtl tan\n" + + "Ka 0.4000 0.3121 0.1202\n" + + "Kd 0.6612 0.5221 0.2186\n" + + "Ks 0.5020 0.4118 0.2152\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl fltan\n" + + "Ka 0.4000 0.3121 0.1202\n" + + "Kd 0.6612 0.4567 0.1295\n" + + "illum 1\n" + + "\n" + + "newmtl brzskin\n" + + "Ka 0.4408 0.2694 0.1592\n" + + "Kd 0.3796 0.2898 0.2122\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl lips\n" + + "Ka 0.4408 0.2694 0.1592\n" + + "Kd 0.9265 0.2612 0.2898\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl redorange\n" + + "Ka 0.3918 0.0576 0.0000\n" + + "Kd 0.7551 0.0185 0.0000\n" + + "Ks 0.4694 0.3224 0.1667\n" + + "illum 2\n" + + "Ns 132.5600\n" + + "\n" + + "newmtl blutan\n" + + "Ka 0.4408 0.2694 0.1592\n" + + "Kd 0.0776 0.2571 0.2041\n" + + "Ks 0.1467 0.1469 0.0965\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl bluteal\n" + + "Ka 0.0041 0.1123 0.1224\n" + + "Kd 0.0776 0.2571 0.2041\n" + + "Ks 0.1467 0.1469 0.0965\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl pinktan\n" + + "Ka 0.4408 0.2694 0.1592\n" + + "Kd 0.6857 0.2571 0.2163\n" + + "Ks 0.1467 0.1469 0.0965\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl brnhair\n" + + "Ka 0.0612 0.0174 0.0066\n" + + "Kd 0.0898 0.0302 0.0110\n" + + "Ks 0.1306 0.0819 0.0352\n" + + "illum 2\n" + + "Ns 60.4700\n" + + "\n" + + "newmtl blondhair\n" + + "Ka 0.4449 0.2632 0.0509\n" + + "Kd 0.5714 0.3283 0.0443\n" + + "Ks 0.7755 0.4602 0.0918\n" + + "illum 2\n" + + "Ns 4.6500\n" + + "\n" + + "newmtl flblonde\n" + + "Ka 0.4449 0.2632 0.0509\n" + + "Kd 0.5714 0.3283 0.0443\n" + + "illum 1\n" + + "\n" + + "newmtl yelloworng\n" + + "Ka 0.5837 0.1715 0.0000\n" + + "Kd 0.8857 0.2490 0.0000\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl bone\n" + + "Ka 0.3061 0.1654 0.0650\n" + + "Kd 0.9000 0.7626 0.4261\n" + + "Ks 0.8939 0.7609 0.5509\n" + + "illum 2\n" + + "Ns 200.0000\n" + + "\n" + + "newmtl teeth\n" + + "Ka 0.6408 0.5554 0.3845\n" + + "Kd 0.9837 0.7959 0.4694\n" + + "illum 1\n" + + "\n" + + "newmtl brass\n" + + "Ka 0.2490 0.1102 0.0000\n" + + "Kd 0.4776 0.1959 0.0000\n" + + "Ks 0.5796 0.5796 0.5796\n" + + "illum 2\n" + + "Ns 134.8800\n" + + "\n" + + "newmtl dkred\n" + + "Ka 0.0939 0.0000 0.0000\n" + + "Kd 0.2286 0.0000 0.0000\n" + + "Ks 0.2490 0.0000 0.0000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl taupe\n" + + "Ka 0.1061 0.0709 0.0637\n" + + "Kd 0.2041 0.1227 0.1058\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 84.5000\n" + + "\n" + + "newmtl dkteal\n" + + "Ka 0.0000 0.0245 0.0163\n" + + "Kd 0.0000 0.0653 0.0449\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 55.0400\n" + + "\n" + + "newmtl dkdkgrey\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0122 0.0122 0.0122\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl dkblue\n" + + "Ka 0.0000 0.0029 0.0408\n" + + "Kd 0.0000 0.0041 0.0571\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl gold\n" + + "Ka 0.7224 0.1416 0.0000\n" + + "Kd 1.0000 0.4898 0.0000\n" + + "Ks 0.7184 0.3695 0.3695\n" + + "illum 2\n" + + "Ns 123.2600\n" + + "\n" + + "newmtl redbrick\n" + + "Ka 0.1102 0.0067 0.0067\n" + + "Kd 0.3306 0.0398 0.0081\n" + + "illum 1\n" + + "\n" + + "newmtl flmustard\n" + + "Ka 0.4245 0.2508 0.0000\n" + + "Kd 0.8898 0.3531 0.0073\n" + + "illum 1\n" + + "\n" + + "newmtl flpinegreen\n" + + "Ka 0.0367 0.0612 0.0204\n" + + "Kd 0.1061 0.2163 0.0857\n" + + "illum 1\n" + + "\n" + + "newmtl fldkred\n" + + "Ka 0.0939 0.0000 0.0000\n" + + "Kd 0.2286 0.0082 0.0082\n" + + "illum 1\n" + + "\n" + + "newmtl fldkgreen2\n" + + "Ka 0.0025 0.0122 0.0014\n" + + "Kd 0.0245 0.0694 0.0041\n" + + "illum 1\n" + + "\n" + + "newmtl flmintgreen\n" + + "Ka 0.0408 0.1429 0.0571\n" + + "Kd 0.1306 0.2898 0.1673\n" + + "illum 1\n" + + "\n" + + "newmtl olivegreen\n" + + "Ka 0.0167 0.0245 0.0000\n" + + "Kd 0.0250 0.0367 0.0000\n" + + "Ks 0.2257 0.2776 0.1167\n" + + "illum 2\n" + + "Ns 97.6700\n" + + "\n" + + "newmtl skin\n" + + "Ka 0.2286 0.0187 0.0187\n" + + "Kd 0.1102 0.0328 0.0139\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 17.8300\n" + + "\n" + + "newmtl redbrown\n" + + "Ka 0.1469 0.0031 0.0000\n" + + "Kd 0.2816 0.0060 0.0000\n" + + "Ks 0.3714 0.3714 0.3714\n" + + "illum 2\n" + + "Ns 141.0900\n" + + "\n" + + "newmtl deepgreen\n" + + "Ka 0.0000 0.0050 0.0000\n" + + "Kd 0.0000 0.0204 0.0050\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 113.1800\n" + + "\n" + + "newmtl flltolivegreen\n" + + "Ka 0.0167 0.0245 0.0000\n" + + "Kd 0.0393 0.0531 0.0100\n" + + "illum 1\n" + + "\n" + + "newmtl jetflame\n" + + "Ka 0.7714 0.0000 0.0000\n" + + "Kd 0.9510 0.4939 0.0980\n" + + "Ks 0.8531 0.5222 0.0000\n" + + "illum 2\n" + + "Ns 132.5600\n" + + "\n" + + "newmtl brownskn\n" + + "Ka 0.0122 0.0041 0.0000\n" + + "Kd 0.0204 0.0082 0.0000\n" + + "Ks 0.0735 0.0508 0.0321\n" + + "illum 2\n" + + "Ns 20.1600\n" + + "\n" + + "newmtl greenskn\n" + + "Ka 0.0816 0.0449 0.0000\n" + + "Kd 0.0000 0.0735 0.0000\n" + + "Ks 0.0490 0.1224 0.0898\n" + + "illum 3\n" + + "Ns 46.5100\n" + + "sharpness 146.5100\n" + + "\n" + + "newmtl ltgrey\n" + + "Ka 0.5000 0.5000 0.5000\n" + + "Kd 0.3837 0.3837 0.3837\n" + + "Ks 0.5000 0.5000 0.5000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl bronze\n" + + "Ka 0.0449 0.0204 0.0000\n" + + "Kd 0.0653 0.0367 0.0122\n" + + "Ks 0.0776 0.0408 0.0000\n" + + "illum 3\n" + + "Ns 137.2100\n" + + "sharpness 125.5800\n" + + "\n" + + "newmtl bone1\n" + + "Ka 0.6408 0.5554 0.3845\n" + + "Kd 0.9837 0.7959 0.4694\n" + + "illum 1\n" + + "\n" + + "newmtl flwhite1\n" + + "Ka 0.9306 0.9306 0.9306\n" + + "Kd 1.0000 1.0000 1.0000\n" + + "illum 1\n" + + "\n" + + "newmtl flwhite\n" + + "Ka 0.6449 0.6116 0.5447\n" + + "Kd 0.9837 0.9309 0.8392\n" + + "Ks 0.8082 0.7290 0.5708\n" + + "illum 2\n" + + "Ns 200.0000\n" + + "\n" + + "newmtl shadow\n" + + "Kd 0.0350 0.0248 0.0194\n" + + "illum 0\n" + + "d 0.7500\n" + + "\n" + + "newmtl fldkolivegreen\n" + + "Ka 0.0056 0.0082 0.0000\n" + + "Kd 0.0151 0.0204 0.0038\n" + + "illum 1\n" + + "\n" + + "newmtl fldkdkgrey\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 0.0122 0.0122 0.0122\n" + + "illum 1\n" + + "\n" + + "newmtl lcdgreen\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.5878 1.0000 0.5061\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl brownlips\n" + + "Ka 0.1143 0.0694 0.0245\n" + + "Kd 0.1429 0.0653 0.0408\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl muscle\n" + + "Ka 0.2122 0.0077 0.0154\n" + + "Kd 0.4204 0.0721 0.0856\n" + + "Ks 0.1184 0.1184 0.1184\n" + + "illum 2\n" + + "Ns 25.5800\n" + + "\n" + + "newmtl flltgrey\n" + + "Ka 0.5224 0.5224 0.5224\n" + + "Kd 0.8245 0.8245 0.8245\n" + + "illum 1\n" + + "\n" + + "newmtl offwhite.warm\n" + + "Ka 0.5184 0.4501 0.3703\n" + + "Kd 0.8367 0.6898 0.4490\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl offwhite.cool\n" + + "Ka 0.5184 0.4501 0.3703\n" + + "Kd 0.8367 0.6812 0.5703\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl yellowbrt\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 1.0000 0.7837 0.0000\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl chappie\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.5837 0.1796 0.0367\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl archwhite\n" + + "Ka 0.2816 0.2816 0.2816\n" + + "Kd 0.9959 0.9959 0.9959\n" + + "illum 1\n" + + "\n" + + "newmtl archwhite2\n" + + "Ka 0.2816 0.2816 0.2816\n" + + "Kd 0.8408 0.8408 0.8408\n" + + "illum 1\n" + + "\n" + + "newmtl lighttan\n" + + "Ka 0.0980 0.0536 0.0220\n" + + "Kd 0.7020 0.4210 0.2206\n" + + "Ks 0.8286 0.8057 0.5851\n" + + "illum 2\n" + + "Ns 177.5200\n" + + "\n" + + "newmtl lighttan2\n" + + "Ka 0.0980 0.0492 0.0144\n" + + "Kd 0.3143 0.1870 0.0962\n" + + "Ks 0.8286 0.8057 0.5851\n" + + "illum 2\n" + + "Ns 177.5200\n" + + "\n" + + "newmtl lighttan3\n" + + "Ka 0.0980 0.0492 0.0144\n" + + "Kd 0.1796 0.0829 0.0139\n" + + "Ks 0.8286 0.8057 0.5851\n" + + "illum 2\n" + + "Ns 177.5200\n" + + "\n" + + "newmtl lightyellow\n" + + "Ka 0.5061 0.1983 0.0000\n" + + "Kd 1.0000 0.9542 0.3388\n" + + "Ks 1.0000 0.9060 0.0000\n" + + "illum 2\n" + + "Ns 177.5200\n" + + "\n" + + "newmtl lighttannew\n" + + "Ka 0.0980 0.0492 0.0144\n" + + "Kd 0.7878 0.6070 0.3216\n" + + "Ks 0.8286 0.8057 0.5851\n" + + "illum 2\n" + + "Ns 177.5200\n" + + "\n" + + "newmtl default\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.7102 0.7020 0.6531\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 128.0000\n" + + "\n" + + "newmtl ship2\n" + + "Ka 0.0000 0.0000 0.0000\n" + + "Kd 1.0000 1.0000 1.0000\n" + + "Ks 0.1143 0.1143 0.1143\n" + + "illum 2\n" + + "Ns 60.0000\n" + + "\n" + + "newmtl dkpurple\n" + + "Ka 0.0082 0.0000 0.0163\n" + + "Kd 0.0245 0.0000 0.0490\n" + + "Ks 0.1266 0.0000 0.2531\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl dkorange\n" + + "Ka 0.4041 0.0123 0.0000\n" + + "Kd 0.7143 0.0350 0.0000\n" + + "Ks 0.7102 0.0870 0.0000\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl mintgrn\n" + + "Ka 0.0101 0.1959 0.0335\n" + + "Kd 0.0245 0.4776 0.0816\n" + + "Ks 0.0245 0.4776 0.0816\n" + + "illum 2\n" + + "Ns 65.8900\n" + + "\n" + + "newmtl fgreen\n" + + "Ka 0.0000 0.0449 0.0000\n" + + "Kd 0.0000 0.0449 0.0004\n" + + "Ks 0.0062 0.0694 0.0000\n" + + "illum 2\n" + + "Ns 106.2000\n" + + "\n" + + "newmtl glassblutint\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.5551 0.8000 0.7730\n" + + "Ks 0.7969 0.9714 0.9223\n" + + "illum 4\n" + + "d 0.3300\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl bflesh\n" + + "Ka 0.0122 0.0122 0.0122\n" + + "Kd 0.0245 0.0081 0.0021\n" + + "Ks 0.0531 0.0460 0.0153\n" + + "illum 2\n" + + "Ns 20.1600\n" + + "\n" + + "newmtl meh\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.5551 0.8000 0.7730\n" + + "Ks 0.7969 0.9714 0.9223\n" + + "illum 4\n" + + "d 0.7500\n" + + "Ns 183.7200\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl violet\n" + + "Ka 0.0083 0.0000 0.1265\n" + + "Kd 0.0287 0.0269 0.1347\n" + + "Ks 0.2267 0.4537 0.6612\n" + + "illum 2\n" + + "Ns 96.9000\n" + + "\n" + + "newmtl iris\n" + + "Ka 0.3061 0.0556 0.0037\n" + + "Kd 0.0000 0.0572 0.3184\n" + + "Ks 0.8041 0.6782 0.1477\n" + + "illum 2\n" + + "Ns 188.3700\n" + + "\n" + + "newmtl blugrn\n" + + "Ka 0.4408 0.4144 0.1592\n" + + "Kd 0.0811 0.6408 0.2775\n" + + "Ks 0.1467 0.1469 0.0965\n" + + "illum 2\n" + + "Ns 25.0000\n" + + "\n" + + "newmtl glasstransparent\n" + + "Ka 0.2163 0.2163 0.2163\n" + + "Kd 0.4694 0.4694 0.4694\n" + + "Ks 0.6082 0.6082 0.6082\n" + + "illum 4\n" + + "d 0.7500\n" + + "Ns 200.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl fleshtransparent\n" + + "Ka 0.4000 0.2253 0.2253\n" + + "Kd 0.6898 0.2942 0.1295\n" + + "Ks 0.7388 0.4614 0.4614\n" + + "illum 4\n" + + "d 0.7500\n" + + "Ns 6.2000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl fldkgrey\n" + + "Ka 0.0449 0.0449 0.0449\n" + + "Kd 0.0939 0.0939 0.0939\n" + + "illum 1\n" + + "\n" + + "newmtl sky_blue\n" + + "Ka 0.1363 0.2264 0.4122\n" + + "Kd 0.1241 0.5931 0.8000\n" + + "Ks 0.0490 0.0490 0.0490\n" + + "illum 2\n" + + "Ns 13.9500\n" + + "\n" + + "newmtl fldkpurple\n" + + "Ka 0.0443 0.0257 0.0776\n" + + "Kd 0.1612 0.0000 0.3347\n" + + "Ks 0.0000 0.0000 0.0000\n" + + "illum 2\n" + + "Ns 13.9500\n" + + "\n" + + "newmtl dkbrown\n" + + "Ka 0.0143 0.0062 0.0027\n" + + "Kd 0.0087 0.0038 0.0016\n" + + "Ks 0.2370 0.2147 0.1821\n" + + "illum 3\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl bone2\n" + + "Ka 0.6408 0.5388 0.3348\n" + + "Kd 0.9837 0.8620 0.6504\n" + + "illum 1\n" + + "\n" + + "newmtl bluegrey\n" + + "Ka 0.4000 0.4000 0.4000\n" + + "Kd 0.1881 0.2786 0.2898\n" + + "Ks 0.3000 0.3000 0.3000\n" + + "illum 2\n" + + "Ns 14.7300\n" + + "\n" + + "newmtl metal\n" + + "Ka 0.9102 0.8956 0.1932\n" + + "Kd 0.9000 0.7626 0.4261\n" + + "Ks 0.8939 0.8840 0.8683\n" + + "illum 2\n" + + "Ns 200.0000\n" + + "\n" + + "newmtl sand_stone\n" + + "Ka 0.1299 0.1177 0.0998\n" + + "Kd 0.1256 0.1138 0.0965\n" + + "Ks 0.2370 0.2147 0.1821\n" + + "illum 3\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n" + + "\n" + + "newmtl hair\n" + + "Ka 0.0013 0.0012 0.0010\n" + + "Kd 0.0008 0.0007 0.0006\n" + + "Ks 0.0000 0.0000 0.0000\n" + + "illum 3\n" + + "Ns 60.0000\n" + + "sharpness 60.0000\n"; +} diff --git a/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFile.java b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFile.java new file mode 100644 index 0000000..d3fcd67 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFile.java @@ -0,0 +1,1334 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.objectfile; + +import com.sun.j3d.loaders.Scene; +import com.sun.j3d.loaders.SceneBase; +import com.sun.j3d.loaders.Loader; +import com.sun.j3d.loaders.IncorrectFormatException; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.objectfile.ObjectFileParser; +import com.sun.j3d.loaders.objectfile.ObjectFileMaterials; +import com.sun.j3d.utils.geometry.GeometryInfo; +import com.sun.j3d.utils.geometry.NormalGenerator; +import com.sun.j3d.utils.geometry.Stripifier; +import java.io.FileNotFoundException; +import java.io.StreamTokenizer; +import java.io.Reader; +import java.io.BufferedReader; +import java.io.BufferedInputStream; +import java.io.FileReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.HashMap; +import java.util.StringTokenizer; +import javax.media.j3d.*; +import javax.vecmath.Color3f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; +import javax.vecmath.TexCoord2f; +import java.net.MalformedURLException; + + + +/** + * The ObjectFile class implements the Loader interface for the Wavefront + * .obj file format, a standard 3D object file format created for use with + * Wavefront's Advanced Visualizer (tm) and available for purchase from + * Viewpoint DataLabs, as well as other 3D model companies. Object Files + * are text based + * files supporting both polygonal and free-form geometry (curves + * and surfaces). The Java 3D .obj file loader supports a subset of the + * file format, but it is enough to load almost all commonly available + * Object Files. Free-form geometry is not supported.

+ * + * The Object File tokens currently supported by this loader are:

+ * v float float float

+ *
A single vertex's geometric position in space. The first vertex + * listed in the file has index 1, + * and subsequent vertices are numbered sequentially.

+ * vn float float float

+ *
A normal. The first normal in the file is index 1, and + * subsequent normals are numbered sequentially.

+ * vt float float

+ *
A texture coordinate. The first texture coordinate in the file is + * index 1, and subsequent normals are numbered sequentially.

+ * f int int int . . .

+ *
or

+ * f int/int int/int int/int . . .

+ *
or

+ * f int/int/int int/int/int int/int/int . . .

+ *
A polygonal face. The numbers are indexes into the arrays of + * vertex positions, texture coordinates, and normals respectively. + * There is no maximum number of vertices that a single polygon may + * contain. The .obj file specification says that each face must + * be flat and convex, but if the TRIANGULATE flag is sent to the + * ObjectFile constructor, each face will be triangulated by the + * Java 3D Triangulator, and therefore may be concave. + * A number may be omitted if, for example, texture coordinates are + * not being defined in the model. Numbers are normally positive + * indexes, but may also be negative. An index of -1 means the last + * member added to the respective array, -2 is the one before that, + * and so on.

+ * g name

+ *
Faces defined after this token will be added to the named group. + * These geometry groups are returned as separated Shape3D objects + * attached to the parent SceneGroup. Each named Shape3D will also + * be in the Hashtable returned by Scene.getNamedObjects(). It is + * legal to add faces to a group, switch to another group, and then + * add more faces to the original group by reissuing the same name + * with the g token. If faces are added to the model before the g + * token is seen, the faces are put into the default group called + * "default."

+ * s int

+ *
or

+ * s off

+ *
If the vn token is not used in the file to specify vertex normals + * for the model, this token may be used to put faces into groups + * for normal calculation ("smoothing groups") in the same manner as + * the 'g' token + * is used to group faces geometrically. Faces in the same smoothing + * group will have their normals calculated as if they are part of + * the same smooth surface. To do this, we use the Java 3D NormalGenerator + * utility with the creaseAngle parameter set to PI (180 degrees - + * smooth shading, no creases) or to whatever the user has set the + * creaseAngle. Faces in group 0 or 'off' use a + * creaseAngle of zero, meaning there is no smoothing (the normal + * of the face is used at all vertices giving the surface a faceted + * look; there will be a + * crease, or "Hard Edge," between each face in group zero). There is + * also an implied hard edge between each smoothing group, where they + * meet each other.

+ *

+ * If neither the vn nor the s token is used in the file, then normals + * are calculated using the creaseAngle set in the contructor. + * Normals are calculated on each geometry + * group separately, meaning there will be a hard edge between each + * geometry group.

+ *

+ * usemtl name

+ *
The current (and subsequent) geometry groups (specified with + * the 'g' token) have applied + * to them the named material property. The following set of material + * properties are available by default:

+ *
+ *     amber           amber_trans       aqua            aqua_filter
+ *     archwhite       archwhite2        bflesh          black
+ *     blondhair       blue_pure         bluegrey        bluetint
+ *     blugrn          blutan            bluteal         bone
+ *     bone1           bone2             brass           brnhair
+ *     bronze          brown             brownlips       brownskn
+ *     brzskin         chappie           charcoal        deepgreen
+ *     default         dkblue            dkblue_pure     dkbrown
+ *     dkdkgrey        dkgreen           dkgrey          dkorange
+ *     dkpurple        dkred             dkteal          emerald
+ *     fgreen          flaqua            flblack         flblonde
+ *     flblue_pure     flbrown           fldkblue_pure   fldkdkgrey
+ *     fldkgreen       fldkgreen2        fldkgrey        fldkolivegreen
+ *     fldkpurple      fldkred           flesh           fleshtransparent
+ *     flgrey          fllime            flltbrown       flltgrey
+ *     flltolivegreen  flmintgreen       flmustard       florange
+ *     flpinegreen     flpurple          flred           fltan
+ *     flwhite         flwhite1          flyellow        glass
+ *     glassblutint    glasstransparent  gold            green
+ *     greenskn        grey              hair            iris
+ *     jetflame        lavendar          lcdgreen        lighttan
+ *     lighttan2       lighttan3         lighttannew     lightyellow
+ *     lime            lips              ltbrown         ltgrey
+ *     meh             metal             mintgrn         muscle
+ *     navy_blue       offwhite.cool     offwhite.warm   olivegreen
+ *     orange          pale_green        pale_pink       pale_yellow
+ *     peach           periwinkle        pink            pinktan
+ *     plasma          purple            red             redbrick
+ *     redbrown        redorange         redwood         rubber
+ *     ruby            sand_stone        sapphire        shadow
+ *     ship2           silver            skin            sky_blue
+ *     smoked_glass    tan               taupe           teeth
+ *     violet          white             yellow          yellow_green
+ *     yellowbrt       yelloworng
+ *   
+ * mtllib filename

+ *
Load material properties from the named file. Materials + * with the same name as the predefined materials above will override + * the default value. Any directory path information in (filename) + * is ignored. The .mtl files are assumed to be in the same directory + * as the .obj file. If they are in a different directory, use + * Loader.setBasePath() (or Loader.setBaseUrl() ). The format of the + * material properties files + * are as follows:

+ * newmtl name

+ *
Start the definition of a new named material property.

+ * Ka float float float

+ *
Ambient color.

+ * Kd float float float

+ *
Diffuse color.

+ * Ks float float float

+ *
Specular color.

+ * illum (0, 1, or 2)

+ *
0 to disable lighting, 1 for ambient & diffuse only (specular + * color set to black), 2 for full lighting.

+ * Ns float

+ *
Shininess (clamped to 1.0 - 128.0).

+ * map_Kd filename

+ *
Texture map. Supports .rgb, .rgba, .int, .inta, .sgi, and + * .bw files in addition to those supported by + * TextureLoader. + *

+ */ + +public class ObjectFile implements Loader { + // 0=Input file assumed good + // 1=Input file checked for inconsistencies + // 2=path names + // 4=flags + // 8=Timing Info + // 16=Tokens + // 32=Token details (use with 16) + // 64=limits of model coordinates + private static final int DEBUG = 1; + + /** + * Flag sent to constructor. The object's vertices will be changed + * so that the object is centered at (0,0,0) and the coordinate + * positions are all in the range of (-1,-1,-1) to (1,1,1). + */ + public static final int RESIZE = LOAD_SOUND_NODES << 1; + + /** + * Flag sent to constructor. The Shape3D object will be created + * by using the GeometryInfo POLYGON_ARRAY primitive, causing + * them to be Triangulated by GeometryInfo. Use + * this if you suspect concave or other non-behaving polygons + * in your model. + */ + public static final int TRIANGULATE = RESIZE << 1; + + /** + * Flag sent to constructor. Use if the vertices in your .obj + * file were specified with clockwise winding (Java 3D wants + * counter-clockwise) so you see the back of the polygons and + * not the front. Calls GeometryInfo.reverse(). + */ + public static final int REVERSE = TRIANGULATE << 1; + + /** + * Flag sent to contructor. After normals are generated the data + * will be analyzed to find triangle strips. Use this if your + * hardware supports accelerated rendering of strips. + */ + public static final int STRIPIFY = REVERSE << 1; + + private static final char BACKSLASH = '\\'; + + private int flags; + private String basePath = null; + private URL baseUrl = null; + private boolean fromUrl = false; + private float radians; + + // First, lists of points are read from the .obj file into these arrays. . . + private ArrayList coordList; // Holds Point3f + private ArrayList texList; // Holds TexCoord2f + private ArrayList normList; // Holds Vector3f + + // . . . and index lists are read into these arrays. + private ArrayList coordIdxList; // Holds Integer index into coordList + private ArrayList texIdxList; // Holds Integer index into texList + private ArrayList normIdxList; // Holds Integer index into normList + + // The length of each face is stored in this array. + private ArrayList stripCounts; // Holds Integer + + // Each face's Geometry Group membership is kept here. . . + private HashMap groups; // key=Integer index into stripCounts + // value=String name of group + private String curGroup; + + // . . . and Smoothing Group membership is kept here + private HashMap sGroups; // key=Integer index into stripCounts + // value=String name of group + private String curSgroup; + + // The name of each group's "usemtl" material property is kept here + private HashMap groupMaterials; // key=String name of Group + // value=String name of material + + + // After reading the entire file, the faces are converted into triangles. + // The Geometry Group information is converted into these structures. . . + private HashMap triGroups; // key=String name of group + // value=ArrayList of Integer + // indices into coordIdxList + private ArrayList curTriGroup; + + // . . . and Smoothing Group info is converted into these. + private HashMap triSgroups; // key=String name of group + // value=ArrayList of Integer + // indices into coordIdxList + private ArrayList curTriSgroup; + + + // Finally, coordList, texList, and normList are converted to arrays for + // use with GeometryInfo + private Point3f coordArray[] = null; + private Vector3f normArray[] = null; + private TexCoord2f texArray[] = null; + + // Used for debugging + private long time; + + private ObjectFileMaterials materials = null; + + + void readVertex(ObjectFileParser st) throws ParsingErrorException { + Point3f p = new Point3f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + st.getNumber(); + p.z = (float)st.nval; + + if ((DEBUG & 32) != 0) + System.out.println(" (" + p.x + "," + p.y + "," + p.z + ")"); + + st.skipToNextLine(); + + // Add this vertex to the array + coordList.add(p); + } // End of readVertex + + + /** + * readNormal + */ + void readNormal(ObjectFileParser st) throws ParsingErrorException { + Vector3f p = new Vector3f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + st.getNumber(); + p.z = (float)st.nval; + + if ((DEBUG & 32) != 0) + System.out.println(" (" + p.x + "," + p.y + "," + p.z + ")"); + + st.skipToNextLine(); + + // Add this vertex to the array + normList.add(p); + } // End of readNormal + + + /** + * readTexture + */ + void readTexture(ObjectFileParser st) throws ParsingErrorException { + TexCoord2f p = new TexCoord2f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + + if ((DEBUG & 32) != 0) + System.out.println(" (" + p.x + "," + p.y + ")"); + + st.skipToNextLine(); + + // Add this vertex to the array + texList.add(p); + } // End of readTexture + + + /** + * readFace + * + * Adds the indices of the current face to the arrays. + * + * ViewPoint files can have up to three arrays: Vertex Positions, + * Texture Coordinates, and Vertex Normals. Each vertex can + * contain indices into all three arrays. + */ + void readFace(ObjectFileParser st) throws ParsingErrorException { + int vertIndex, texIndex = 0, normIndex = 0; + int count = 0; + + // There are n vertices on each line. Each vertex is comprised + // of 1-3 numbers separated by slashes ('/'). The slashes may + // be omitted if there's only one number. + + st.getToken(); + + while (st.ttype != st.TT_EOL) { + // First token is always a number (or EOL) + st.pushBack(); + st.getNumber(); + vertIndex = (int)st.nval - 1; + if (vertIndex < 0) vertIndex += coordList.size() + 1; + coordIdxList.add(new Integer(vertIndex)); + + // Next token is a slash, a number, or EOL. Continue on slash + st.getToken(); + if (st.ttype == '/') { + + // If there's a number after the first slash, read it + st.getToken(); + if (st.ttype == st.TT_WORD) { + // It's a number + st.pushBack(); + st.getNumber(); + texIndex = (int)st.nval - 1; + if (texIndex < 0) texIndex += texList.size() + 1; + texIdxList.add(new Integer(texIndex)); + st.getToken(); + } + + // Next token is a slash, a number, or EOL. Continue on slash + if (st.ttype == '/') { + + // There has to be a number after the 2nd slash + st.getNumber(); + normIndex = (int)st.nval - 1; + if (normIndex < 0) normIndex += normList.size() + 1; + normIdxList.add(new Integer(normIndex)); + st.getToken(); + } + } + if ((DEBUG & 32) != 0) { + System.out.println(" " + vertIndex + '/' + texIndex + + '/' + normIndex); + } + count++; + } + + Integer faceNum = new Integer(stripCounts.size()); + stripCounts.add(new Integer(count)); + + // Add face to current groups + groups.put(faceNum, curGroup); + if (curSgroup != null) sGroups.put(faceNum, curSgroup); + + // In case we exited early + st.skipToNextLine(); + } // End of readFace + + + /** + * readPartName + */ + void readPartName(ObjectFileParser st) { + st.getToken(); + + // Find the Material Property of the current group + String curMat = (String)groupMaterials.get(curGroup); + + // New faces will be added to the curGroup + if (st.ttype != ObjectFileParser.TT_WORD) curGroup = "default"; + else curGroup = st.sval; + if ((DEBUG & 32) != 0) System.out.println(" Changed to group " + curGroup); + + // See if this group has Material Properties yet + if (groupMaterials.get(curGroup) == null) { + // It doesn't - carry over from last group + groupMaterials.put(curGroup, curMat); + } + + st.skipToNextLine(); + } // End of readPartName + + + /** + * readMaterialName + */ + void readMaterialName(ObjectFileParser st) throws ParsingErrorException { + st.getToken(); + if (st.ttype == ObjectFileParser.TT_WORD) { + groupMaterials.put(curGroup, new String(st.sval)); + if ((DEBUG & 32) != 0) { + System.out.println(" Material Property " + st.sval + + " assigned to group " + curGroup); + } + } + st.skipToNextLine(); + } // End of readMaterialName + + + /** + * loadMaterialFile + * + * Both types of slashes are returned as tokens from our parser, + * so we go through the line token by token and keep just the + * last token on the line. This should be the filename without + * any directory info. + */ + void loadMaterialFile(ObjectFileParser st) throws ParsingErrorException { + String s = null; + + // Filenames are case sensitive + st.lowerCaseMode(false); + + // Get name of material file (skip path) + do { + st.getToken(); + if (st.ttype == ObjectFileParser.TT_WORD) s = st.sval; + } while (st.ttype != ObjectFileParser.TT_EOL); + + materials.readMaterialFile(fromUrl, + fromUrl ? baseUrl.toString() : basePath, s); + + st.lowerCaseMode(true); + st.skipToNextLine(); + } // End of loadMaterialFile + + + /** + * readSmoothingGroup + */ + void readSmoothingGroup(ObjectFileParser st) throws ParsingErrorException { + st.getToken(); + if (st.ttype != ObjectFileParser.TT_WORD) { + st.skipToNextLine(); + return; + } + if (st.sval.equals("off")) curSgroup = "0"; + else curSgroup = st.sval; + if ((DEBUG & 32) != 0) System.out.println(" Smoothing group " + curSgroup); + st.skipToNextLine(); + } // End of readSmoothingGroup + + + /** + * readFile + * + * Read the model data from the file. + */ + void readFile(ObjectFileParser st) throws ParsingErrorException { + int t; + + st.getToken(); + while (st.ttype != ObjectFileParser.TT_EOF) { + + // Print out one token for each line + if ((DEBUG & 16) != 0) { + System.out.print("Token "); + if (st.ttype == ObjectFileParser.TT_EOL) System.out.println("EOL"); + else if (st.ttype == ObjectFileParser.TT_WORD) + System.out.println(st.sval); + else System.out.println((char)st.ttype); + } + + if (st.ttype == ObjectFileParser.TT_WORD) { + if (st.sval.equals("v")) { + readVertex(st); + } else if (st.sval.equals("vn")) { + readNormal(st); + } else if (st.sval.equals("vt")) { + readTexture(st); + } else if (st.sval.equals("f")) { + readFace(st); + } else if (st.sval.equals("fo")) { // Not sure what the dif is + readFace(st); + } else if (st.sval.equals("g")) { + readPartName(st); + } else if (st.sval.equals("s")) { + readSmoothingGroup(st); + } else if (st.sval.equals("p")) { + st.skipToNextLine(); + } else if (st.sval.equals("l")) { + st.skipToNextLine(); + } else if (st.sval.equals("mtllib")) { + loadMaterialFile(st); + } else if (st.sval.equals("usemtl")) { + readMaterialName(st); + } else if (st.sval.equals("maplib")) { + st.skipToNextLine(); + } else if (st.sval.equals("usemap")) { + st.skipToNextLine(); + } else { + throw new ParsingErrorException( + "Unrecognized token, line " + st.lineno()); + } + } + + st.skipToNextLine(); + + // Get next token + st.getToken(); + } + } // End of readFile + + + /** + * Constructor. + * + * @param flags The constants from above or from + * com.sun.j3d.loaders.Loader, possibly "or'ed" (|) together. + * @param radians Ignored if the vn token is present in the model (user + * normals supplied). Otherwise, crease angle to use within smoothing + * groups, or within geometry groups if the s token isn't present either. + */ + public ObjectFile(int flags, float radians) { + setFlags(flags); + this.radians = radians; + } // End of ObjectFile(int, float) + + + /** + * Constructor. Crease Angle set to default of + * 44 degrees (see NormalGenerator utility for details). + * @param flags The constants from above or from + * com.sun.j3d.loaders.Loader, possibly "or'ed" (|) together. + */ + public ObjectFile(int flags) { + this(flags, -1.0f); + } // End of ObjectFile(int) + + + /** + * Default constructor. Crease Angle set to default of + * 44 degrees (see NormalGenerator utility for details). Flags + * set to zero (0). + */ + public ObjectFile() { + this(0, -1.0f); + } // End of ObjectFile() + + + /** + * Takes a file name and sets the base path to the directory + * containing that file. + */ + private void setBasePathFromFilename(String fileName) { + if (fileName.lastIndexOf(java.io.File.separator) == -1) { + // No path given - current directory + setBasePath("." + java.io.File.separator); + } else { + setBasePath( + fileName.substring(0, fileName.lastIndexOf(java.io.File.separator))); + } + } // End of setBasePathFromFilename + + + /** + * The Object File is loaded from the .obj file specified by + * the filename. + * To attach the model to your scene, call getSceneGroup() on + * the Scene object passed back, and attach the returned + * BranchGroup to your scene graph. For an example, see + * j3d-examples/ObjLoad/ObjLoad.java. + */ + public Scene load(String filename) throws FileNotFoundException, + IncorrectFormatException, + ParsingErrorException { + + setBasePathFromFilename(filename); + + Reader reader = new BufferedReader(new FileReader(filename)); + return load(reader); + } // End of load(String) + + + private void setBaseUrlFromUrl(URL url) throws FileNotFoundException { + String u = url.toString(); + String s; + if (u.lastIndexOf('/') == -1) { + s = url.getProtocol() + ":"; + } else { + s = u.substring(0, u.lastIndexOf('/') + 1); + } + try { + baseUrl = new URL(s); + } + catch (MalformedURLException e) { + throw new FileNotFoundException(e.getMessage()); + } + } // End of setBaseUrlFromUrl + + + /** + * The object file is loaded off of the web. + * To attach the model to your scene, call getSceneGroup() on + * the Scene object passed back, and attach the returned + * BranchGroup to your scene graph. For an example, see + * j3d-examples/ObjLoad/ObjLoad.java. + */ + public Scene load(URL url) throws FileNotFoundException, + IncorrectFormatException, + ParsingErrorException { + BufferedReader reader; + + if (baseUrl == null) setBaseUrlFromUrl(url); + + try { + reader = new BufferedReader(new InputStreamReader(url.openStream())); + } + catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); + } + fromUrl = true; + return load(reader); + } // End of load(URL) + + + /** + * getLimits + * + * Returns an array of Point3f which form a bounding box around the + * object. Element 0 is the low value, element 1 is the high value. + * See normalize() below for an example of how to use this method. + */ + private Point3f[] getLimits() { + Point3f cur_vtx = new Point3f(); + + // Find the limits of the model + Point3f[] limit = new Point3f[2]; + limit[0] = new Point3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); + limit[1] = new Point3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); + for (int i = 0 ; i < coordList.size() ; i++) { + + cur_vtx = (Point3f)coordList.get(i); + + // Keep track of limits for normalization + if (cur_vtx.x < limit[0].x) limit[0].x = cur_vtx.x; + if (cur_vtx.x > limit[1].x) limit[1].x = cur_vtx.x; + if (cur_vtx.y < limit[0].y) limit[0].y = cur_vtx.y; + if (cur_vtx.y > limit[1].y) limit[1].y = cur_vtx.y; + if (cur_vtx.z < limit[0].z) limit[0].z = cur_vtx.z; + if (cur_vtx.z > limit[1].z) limit[1].z = cur_vtx.z; + } + + if ((DEBUG & 64) != 0) { + System.out.println("Model range: (" + + limit[0].x + "," + limit[0].y + "," + limit[0].z + ") to (" + + limit[1].x + "," + limit[1].y + "," + limit[1].z + ")"); + } + + return limit; + } // End of getLimits + + + + /** + * Center the object and make it (-1,-1,-1) to (1,1,1). + */ + private void resize() { + int i, j; + Point3f cur_vtx = new Point3f(); + float biggest_dif; + + Point3f[] limit = getLimits(); + + // Move object so it's centered on (0,0,0) + Vector3f offset = new Vector3f(-0.5f * (limit[0].x + limit[1].x), + -0.5f * (limit[0].y + limit[1].y), + -0.5f * (limit[0].z + limit[1].z)); + + if ((DEBUG & 64) != 0) { + System.out.println("Offset amount: (" + + offset.x + "," + offset.y + "," + offset.z + ")"); + } + + // Find the divide-by value for the normalization + biggest_dif = limit[1].x - limit[0].x; + if (biggest_dif < limit[1].y - limit[0].y) + biggest_dif = limit[1].y - limit[0].y; + if (biggest_dif < limit[1].z - limit[0].z) + biggest_dif = limit[1].z - limit[0].z; + biggest_dif /= 2.0f; + + for (i = 0 ; i < coordList.size() ; i++) { + + cur_vtx = (Point3f)coordList.get(i); + + cur_vtx.add(cur_vtx, offset); + + cur_vtx.x /= biggest_dif; + cur_vtx.y /= biggest_dif; + cur_vtx.z /= biggest_dif; + + // coordList.setElementAt(cur_vtx, i); + } + } // End of resize + + + private int[] objectToIntArray(ArrayList inList) { + int outList[] = new int[inList.size()]; + for (int i = 0 ; i < inList.size() ; i++) { + outList[i] = ((Integer)inList.get(i)).intValue(); + } + return outList; + } // End of objectToIntArray + + + private Point3f[] objectToPoint3Array(ArrayList inList) { + Point3f outList[] = new Point3f[inList.size()]; + for (int i = 0 ; i < inList.size() ; i++) { + outList[i] = (Point3f)inList.get(i); + } + return outList; + } // End of objectToPoint3Array + + + + private TexCoord2f[] objectToTexCoord2Array(ArrayList inList) { + TexCoord2f outList[] = new TexCoord2f[inList.size()]; + for (int i = 0 ; i < inList.size() ; i++) { + outList[i] = (TexCoord2f)inList.get(i); + } + return outList; + } // End of objectToTexCoord2Array + + + private Vector3f[] objectToVectorArray(ArrayList inList) { + Vector3f outList[] = new Vector3f[inList.size()]; + for (int i = 0 ; i < inList.size() ; i++) { + outList[i] = (Vector3f)inList.get(i); + } + return outList; + } // End of objectToVectorArray + + + /** + * Each group is a list of indices into the model's index lists, + * indicating the starting index of each triangle in the group. + * This method converts those data structures + * into an integer array to use with GeometryInfo. + */ + private int[] groupIndices(ArrayList sourceList, ArrayList group) { + int indices[] = new int[group.size() * 3]; + for (int i = 0 ; i < group.size() ; i++) { + int j = ((Integer)group.get(i)).intValue(); + indices[i * 3 + 0] = ((Integer)sourceList.get(j + 0)).intValue(); + indices[i * 3 + 1] = ((Integer)sourceList.get(j + 1)).intValue(); + indices[i * 3 + 2] = ((Integer)sourceList.get(j + 2)).intValue(); + } + return indices; + } // end of groupIndices + + + /** + * smoothingGroupNormals + * + * Smoothing groups are groups of faces who should be grouped + * together for normal calculation purposes. The faces are + * put into a GeometryInfo object and normals are calculated + * with a 180 degree creaseAngle (no creases) or whatever the + * user has specified. The normals + * are then copied out of the GeometryInfo and back into + * ObjectFile data structures. + */ + private void smoothingGroupNormals() { + NormalGenerator ng = + new NormalGenerator(radians == -1.0f ? Math.PI : radians); + NormalGenerator ng0 = new NormalGenerator(0.0); + normList.clear(); + normIdxList = null; + int newNormIdxArray[] = new int[coordIdxList.size()]; + + Iterator e = triSgroups.keySet().iterator(); + while (e.hasNext()) { + String curname = (String)e.next(); + ArrayList triList = (ArrayList)triSgroups.get(curname); + + // Check for group with no faces + if (triList.size() > 0) { + + GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); + + gi.setCoordinateIndices(groupIndices(coordIdxList, triList)); + gi.setCoordinates(coordArray); + + if (curname.equals("0")) ng0.generateNormals(gi); + else ng.generateNormals(gi); + + // Get the generated normals and indices + Vector3f genNorms[] = gi.getNormals(); + int genNormIndices[] = gi.getNormalIndices(); + + // Now we need to copy the generated normals into ObjectFile + // data structures (normList and normIdxList). The variable + // normIdx is the index of the index of the normal currently + // being put into the list. It takes some calculation to + // figure out the new index and where to put it. + int normIdx = 0; + // Repeat for each triangle in the smoothing group + for (int i = 0 ; i < triList.size() ; i++) { + + // Get the coordIdxList index of the first index in this face + int idx = ((Integer)triList.get(i)).intValue(); + + // Repeat for each vertex in the triangle + for (int j = 0 ; j < 3 ; j++) { + + // Put the new normal's index into the index list + newNormIdxArray[idx + j] = normList.size(); + + // Add the vertex's normal to the normal list + normList.add(genNorms[genNormIndices[normIdx++]]); + } + } + } + } + normIdxList = new ArrayList(coordIdxList.size()); + for (int i = 0 ; i < coordIdxList.size() ; i++) { + normIdxList.add(new Integer(newNormIdxArray[i])); + } + normArray = objectToVectorArray(normList); + } // end of smoothingGroupNormals + + + /** + * Each face is converted to triangles. As each face is converted, + * we look up which geometry group and smoothing group the face + * belongs to. The generated triangles are added to each of these + * groups, which are also being converted to a new triangle based format. + * + * We need to convert to triangles before normals are generated + * because of smoothing groups. The faces in a smoothing group + * are copied into a GeometryInfo to have their normals calculated, + * and then the normals are copied out of the GeometryInfo using + * GeometryInfo.getNormalIndices. As part of Normal generation, + * the geometry gets converted to Triangles. So we need to convert + * to triangles *before* Normal generation so that the normals we + * read out of the GeometryInfo match up with the vertex data + * that we sent in. If we sent in TRIANGLE_FAN data, the normal + * generator would convert it to triangles and we'd read out + * normals formatted for Triangle data. This would not match up + * with our original Fan data, so we couldn't tell which normals + * go with which vertices. + */ + private void convertToTriangles() { + boolean triangulate = (flags & TRIANGULATE) != 0; + boolean textures = !texList.isEmpty() && !texIdxList.isEmpty() && + (texIdxList.size() == coordIdxList.size()); + boolean normals = !normList.isEmpty() && !normIdxList.isEmpty() && + (normIdxList.size() == coordIdxList.size()); + int numFaces = stripCounts.size(); + boolean haveSgroups = curSgroup != null; + + triGroups = new HashMap(50); + if (haveSgroups) triSgroups = new HashMap(50); + + ArrayList newCoordIdxList = null; + ArrayList newTexIdxList = null; + ArrayList newNormIdxList = null; + + if (triangulate) { + GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); + gi.setStripCounts(objectToIntArray(stripCounts)); + gi.setCoordinates(coordArray); + gi.setCoordinateIndices(objectToIntArray(coordIdxList)); + if (textures) { + gi.setTextureCoordinateParams(1, 2); + gi.setTextureCoordinates(0, texArray); + gi.setTextureCoordinateIndices(0, objectToIntArray(texIdxList)); + } + if (normals) { + gi.setNormals(normArray); + gi.setNormalIndices(objectToIntArray(normIdxList)); + } + gi.convertToIndexedTriangles(); + + // Data is now indexed triangles. Next step is to take the data + // out of the GeometryInfo and put into internal data structures + + int coordIndicesArray[] = gi.getCoordinateIndices(); + + // Fix for #4366060 + // Make sure triangulated geometry has the correct number of triangles + int tris = 0; + for (int i = 0 ; i < numFaces ; i++) + tris += ((Integer)stripCounts.get(i)).intValue() - 2; + + if (coordIndicesArray.length != (tris * 3)) { + // Model contains bad polygons that didn't triangulate into the + // correct number of triangles. Fall back to "simple" triangulation + triangulate = false; + } else { + + int texIndicesArray[] = gi.getTextureCoordinateIndices(); + int normIndicesArray[] = gi.getNormalIndices(); + + // Convert index arrays to internal ArrayList format + coordIdxList.clear(); + texIdxList.clear(); + normIdxList.clear(); + for (int i = 0 ; i < coordIndicesArray.length ; i++) { + coordIdxList.add(new Integer(coordIndicesArray[i])); + if (textures) texIdxList.add(new Integer(texIndicesArray[i])); + if (normals) normIdxList.add(new Integer(normIndicesArray[i])); + } + } + } + + if (!triangulate) { + newCoordIdxList = new ArrayList(); + if (textures) newTexIdxList = new ArrayList(); + if (normals) newNormIdxList = new ArrayList(); + } + + // Repeat for each face in the model - add the triangles from each + // face to the Geometry and Smoothing Groups + int baseVertex = 0; + for (int f = 0 ; f < numFaces ; f++) { + int faceSize = ((Integer)stripCounts.get(f)).intValue(); + + // Find out the name of the group to which this face belongs + Integer curFace = new Integer(f); + curGroup = (String)groups.get(curFace); + + // Change to a new geometry group, create if it doesn't exist + curTriGroup = (ArrayList)triGroups.get(curGroup); + if (curTriGroup == null) { + curTriGroup = new ArrayList(); + triGroups.put(curGroup, curTriGroup); + } + + // Change to a new smoothing group, create if it doesn't exist + if (haveSgroups) { + curSgroup = (String)sGroups.get(curFace); + if (curSgroup == null) { + // Weird case - this face has no smoothing group. Happens if the + // first 's' token comes after some faces have already been defined. + // Assume they wanted no smoothing for these faces + curSgroup = "0"; + } + curTriSgroup = (ArrayList)triSgroups.get(curSgroup); + if (curTriSgroup == null) { + curTriSgroup = new ArrayList(); + triSgroups.put(curSgroup, curTriSgroup); + } + } + + if (triangulate) { + + // Each polygon of n vertices is now n-2 triangles + for (int t = 0 ; t < faceSize - 2 ; t++) { + + // The groups just remember the first vertex of each triangle + Integer triBaseVertex = new Integer(baseVertex); + curTriGroup.add(triBaseVertex); + if (haveSgroups) curTriSgroup.add(triBaseVertex); + + baseVertex += 3; + } + } else { + // Triangulate simply + for (int v = 0 ; v < faceSize - 2 ; v++) { + // Add this triangle to the geometry group and the smoothing group + Integer triBaseVertex = new Integer(newCoordIdxList.size()); + curTriGroup.add(triBaseVertex); + if (haveSgroups) curTriSgroup.add(triBaseVertex); + + newCoordIdxList.add(coordIdxList.get(baseVertex)); + newCoordIdxList.add(coordIdxList.get(baseVertex + v + 1)); + newCoordIdxList.add(coordIdxList.get(baseVertex + v + 2)); + + if (textures) { + newTexIdxList.add(texIdxList.get(baseVertex)); + newTexIdxList.add(texIdxList.get(baseVertex + v + 1)); + newTexIdxList.add(texIdxList.get(baseVertex + v + 2)); + } + + if (normals) { + newNormIdxList.add(normIdxList.get(baseVertex)); + newNormIdxList.add(normIdxList.get(baseVertex + v + 1)); + newNormIdxList.add(normIdxList.get(baseVertex + v + 2)); + } + } + baseVertex += faceSize; + } + } + + // No need to keep these around + stripCounts = null; + groups = null; + sGroups = null; + + if (!triangulate) { + coordIdxList = newCoordIdxList; + texIdxList = newTexIdxList; + normIdxList = newNormIdxList; + } + } // End of convertToTriangles + + + private SceneBase makeScene() { + // Create Scene to pass back + SceneBase scene = new SceneBase(); + BranchGroup group = new BranchGroup(); + scene.setSceneGroup(group); + + boolean gen_norms = normList.isEmpty() || normIdxList.isEmpty() || + (normIdxList.size() != coordIdxList.size()); + boolean do_tex = !texList.isEmpty() && !texIdxList.isEmpty() && + (texIdxList.size() == coordIdxList.size()); + + // Convert ArrayLists to arrays + coordArray = objectToPoint3Array(coordList); + if (!gen_norms) normArray = objectToVectorArray(normList); + if (do_tex) texArray = objectToTexCoord2Array(texList); + + convertToTriangles(); + + if ((DEBUG & 8) != 0) { + time = System.currentTimeMillis() - time; + System.out.println("Convert to triangles: " + time + " ms"); + time = System.currentTimeMillis(); + } + + if ((gen_norms) && (curSgroup != null)) { + smoothingGroupNormals(); + gen_norms = false; + if ((DEBUG & 8) != 0) { + time = System.currentTimeMillis() - time; + System.out.println("Smoothing group normals: " + time + " ms"); + time = System.currentTimeMillis(); + } + } + + NormalGenerator ng = null; + if (gen_norms) ng = new NormalGenerator(radians); + + Stripifier strippy = null; + if ((flags & STRIPIFY) != 0) strippy = new Stripifier(); + + long t1 = 0, t2 = 0, t3 = 0, t4 = 0; + + // Each "Group" of faces in the model will be one Shape3D + Iterator e = triGroups.keySet().iterator(); + while (e.hasNext()) { + + String curname = (String)e.next(); + ArrayList triList = (ArrayList)triGroups.get(curname); + + // Check for group with no faces + if (triList.size() > 0) { + + GeometryInfo gi = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY); + + gi.setCoordinateIndices(groupIndices(coordIdxList, triList)); + gi.setCoordinates(coordArray); + + if (do_tex) { + gi.setTextureCoordinateParams(1, 2); + gi.setTextureCoordinates(0, texArray); + gi.setTextureCoordinateIndices(0, groupIndices(texIdxList, triList)); + } + + if ((DEBUG & 8) != 0) time = System.currentTimeMillis(); + if (gen_norms) { + if ((flags & REVERSE) != 0) gi.reverse(); + ng.generateNormals(gi); + if ((DEBUG & 8) != 0) { + t2 += System.currentTimeMillis() - time; + System.out.println("Generate normals: " + t2 + " ms"); + time = System.currentTimeMillis(); + } + } else { + gi.setNormalIndices(groupIndices(normIdxList, triList)); + gi.setNormals(normArray); + if ((flags & REVERSE) != 0) gi.reverse(); + } + + if ((flags & STRIPIFY) != 0) { + strippy.stripify(gi); + if ((DEBUG & 8) != 0) { + t3 += System.currentTimeMillis() - time; + System.out.println("Stripify: " + t3 + " ms"); + time = System.currentTimeMillis(); + } + } + + // Put geometry into Shape3d + Shape3D shape = new Shape3D(); + + shape.setGeometry(gi.getGeometryArray(true, true, false)); + + String matName = (String)groupMaterials.get(curname); + materials.assignMaterial(matName, shape); + + group.addChild(shape); + scene.addNamedObject(curname, shape); + + if ((DEBUG & 8) != 0) { + t4 += System.currentTimeMillis() - time; + System.out.println("Shape 3D: " + t4 + " ms"); + time = System.currentTimeMillis(); + } + } + } + + return scene; + } // end of makeScene + + + /** + * The Object File is loaded from the already opened file. + * To attach the model to your scene, call getSceneGroup() on + * the Scene object passed back, and attach the returned + * BranchGroup to your scene graph. For an example, see + * j3d-examples/ObjLoad/ObjLoad.java. + */ + public Scene load(Reader reader) throws FileNotFoundException, + IncorrectFormatException, + ParsingErrorException { + // ObjectFileParser does lexical analysis + ObjectFileParser st = new ObjectFileParser(reader); + + coordList = new ArrayList(); + texList = new ArrayList(); + normList = new ArrayList(); + coordIdxList = new ArrayList(); + texIdxList = new ArrayList(); + normIdxList = new ArrayList(); + groups = new HashMap(50); + curGroup = "default"; + sGroups = new HashMap(50); + curSgroup = null; + stripCounts = new ArrayList(); + groupMaterials = new HashMap(50); + groupMaterials.put(curGroup, "default"); + materials = new ObjectFileMaterials(); + + time = 0L; + if ((DEBUG & 8) != 0) { + time = System.currentTimeMillis(); + } + + readFile(st); + + if ((DEBUG & 8) != 0) { + time = System.currentTimeMillis() - time; + System.out.println("Read file: " + time + " ms"); + time = System.currentTimeMillis(); + } + + if ((flags & RESIZE) != 0) resize(); + + return makeScene(); + } // End of load(Reader) + + + /** + * For an .obj file loaded from a URL, set the URL where associated files + * (like material properties files) will be found. + * Only needs to be called to set it to a different URL + * from that containing the .obj file. + */ + public void setBaseUrl(URL url) { + baseUrl = url; + } // End of setBaseUrl + + + /** + * Return the URL where files associated with this .obj file (like + * material properties files) will be found. + */ + public URL getBaseUrl() { + return baseUrl; + } // End of getBaseUrl + + + /** + * Set the path where files associated with this .obj file are + * located. + * Only needs to be called to set it to a different directory + * from that containing the .obj file. + */ + public void setBasePath(String pathName) { + basePath = pathName; + if (basePath == null || basePath == "") + basePath = "." + java.io.File.separator; + basePath = basePath.replace('/', java.io.File.separatorChar); + basePath = basePath.replace('\\', java.io.File.separatorChar); + if (!basePath.endsWith(java.io.File.separator)) + basePath = basePath + java.io.File.separator; + } // End of setBasePath + + + /** + * Return the path where files associated with this .obj file (like material + * files) are located. + */ + public String getBasePath() { + return basePath; + } // End of getBasePath + + + /** + * Set parameters for loading the model. + * Flags defined in Loader.java are ignored by the ObjectFile Loader + * because the .obj file format doesn't include lights, fog, background, + * behaviors, views, or sounds. However, several flags are defined + * specifically for use with the ObjectFile Loader (see above). + */ + public void setFlags(int flags) { + this.flags = flags; + if ((DEBUG & 4) != 0) System.out.println("Flags = " + flags); + } // End of setFlags + + + /** + * Get the parameters currently defined for loading the model. + * Flags defined in Loader.java are ignored by the ObjectFile Loader + * because the .obj file format doesn't include lights, fog, background, + * behaviors, views, or sounds. However, several flags are defined + * specifically for use with the ObjectFile Loader (see above). + */ + public int getFlags() { + return flags; + } // End of getFlags + +} // End of class ObjectFile + +// End of file ObjectFile.java diff --git a/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileMaterials.java b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileMaterials.java new file mode 100644 index 0000000..8dd3b2c --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileMaterials.java @@ -0,0 +1,425 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.objectfile; + +import javax.media.j3d.Appearance; +import javax.media.j3d.Material; +import javax.media.j3d.Shape3D; +import javax.vecmath.Color3f; +import com.sun.j3d.loaders.ParsingErrorException; +import com.sun.j3d.loaders.IncorrectFormatException; +import java.io.FileNotFoundException; +import java.io.StringReader; +import java.io.Reader; +import java.io.FileReader; +import java.io.BufferedReader; +import java.io.BufferedInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.FileInputStream; +import java.util.HashMap; +import com.sun.j3d.loaders.objectfile.DefaultMaterials; +import java.net.URL; +import java.net.MalformedURLException; +import java.awt.Toolkit; +import java.awt.Image; +import java.awt.image.BufferedImage; +import javax.media.j3d.Texture2D; +import java.awt.image.ImageObserver; +import java.awt.image.PixelGrabber; +import java.awt.image.DataBufferInt; +import javax.media.j3d.ImageComponent2D; +import javax.media.j3d.TexCoordGeneration; +import java.security.AccessController; +import java.security.PrivilegedAction; +import javax.media.j3d.GeometryArray; +import com.sun.j3d.utils.image.TextureLoader; +import javax.media.j3d.TransparencyAttributes; + + +class ObjectFileMaterials implements ImageObserver { + // DEBUG + // 1 = Name of materials + // 16 = Tokens + private static final int DEBUG = 0; + + private String curName = null; + private ObjectFileMaterial cur = null; + + private HashMap materials; // key=String name of material + // value=ObjectFileMaterial + + private String basePath; + private boolean fromUrl; + + + private class ObjectFileMaterial { + + public Color3f Ka; + public Color3f Kd; + public Color3f Ks; + public int illum; + public float Ns; + public Texture2D t; + public boolean transparent; + + public void ObjectFileMaterial() { + Ka = null; + Kd = null; + Ks = null; + illum = -1; + Ns = -1.0f; + t = null; + } // End of ObjectFileMaterial + } + + + void assignMaterial(String matName, Shape3D shape) { + ObjectFileMaterial p = null; + + if ((DEBUG & 1) != 0) System.out.println("Color " + matName); + + Material m = new Material(); + p = (ObjectFileMaterial)materials.get(matName); + Appearance a = new Appearance(); + + if (p != null) { + // Set ambient & diffuse color + if (p.Ka != null) m.setAmbientColor(p.Ka); + if (p.Kd != null) m.setDiffuseColor(p.Kd); + + // Set specular color + if ((p.Ks != null) && (p.illum != 1)) m.setSpecularColor(p.Ks); + else if (p.illum == 1) m.setSpecularColor(0.0f, 0.0f, 0.0f); + + if (p.illum >= 1) m.setLightingEnable(true); + else if (p.illum == 0) m.setLightingEnable(false); + + if (p.Ns != -1.0f) m.setShininess(p.Ns); + + if (p.t != null) { + a.setTexture(p.t); + // Create Texture Coordinates if not already present + if ((((GeometryArray)shape.getGeometry()).getVertexFormat() & + GeometryArray.TEXTURE_COORDINATE_2) == 0) { + TexCoordGeneration tcg = new TexCoordGeneration(); + a.setTexCoordGeneration(tcg); + } + } + + if (p.transparent) + a.setTransparencyAttributes( + new TransparencyAttributes(TransparencyAttributes.NICEST, + 0.0f)); + } + a.setMaterial(m); + if ((DEBUG & 1) != 0) System.out.println(m); + shape.setAppearance(a); + } // End of assignMaterial + + + private void readName(ObjectFileParser st) throws ParsingErrorException { + st.getToken(); + + if (st.ttype == ObjectFileParser.TT_WORD) { + + if (curName != null) materials.put(curName, cur); + curName = new String(st.sval); + cur = new ObjectFileMaterial(); + } + st.skipToNextLine(); + } // End of readName + + + private void readAmbient(ObjectFileParser st) throws ParsingErrorException { + Color3f p = new Color3f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + st.getNumber(); + p.z = (float)st.nval; + + cur.Ka = p; + + st.skipToNextLine(); + } // End of readAmbient + + + private void readDiffuse(ObjectFileParser st) throws ParsingErrorException { + Color3f p = new Color3f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + st.getNumber(); + p.z = (float)st.nval; + + cur.Kd = p; + + st.skipToNextLine(); + } // End of readDiffuse + + + private void readSpecular(ObjectFileParser st) throws ParsingErrorException { + Color3f p = new Color3f(); + + st.getNumber(); + p.x = (float)st.nval; + st.getNumber(); + p.y = (float)st.nval; + st.getNumber(); + p.z = (float)st.nval; + + cur.Ks = p; + + st.skipToNextLine(); + } // End of readSpecular + + + private void readIllum(ObjectFileParser st) throws ParsingErrorException { + float f; + + st.getNumber(); + cur.illum = (int)st.nval; + + st.skipToNextLine(); + } // End of readSpecular + + + private void readShininess(ObjectFileParser st) throws ParsingErrorException { + float f; + + st.getNumber(); + cur.Ns = (float)st.nval; + if (cur.Ns < 1.0f) cur.Ns = 1.0f; + else if (cur.Ns > 128.0f) cur.Ns = 128.0f; + + st.skipToNextLine(); + } // End of readSpecular + + + public void readMapKd(ObjectFileParser st) { + // Filenames are case sensitive + st.lowerCaseMode(false); + + // Get name of texture file (skip path) + String tFile = null; + do { + st.getToken(); + if (st.ttype == ObjectFileParser.TT_WORD) tFile = st.sval; + } while (st.ttype != ObjectFileParser.TT_EOL); + + st.lowerCaseMode(true); + + if (tFile != null) { + // Check for filename with no extension + if (tFile.lastIndexOf('.') != -1) { + try { + // Convert filename to lower case for extension comparisons + String suffix = + tFile.substring(tFile.lastIndexOf('.') + 1).toLowerCase(); + + TextureLoader t = null; + + if ((suffix.equals("int")) || (suffix.equals("inta")) || + (suffix.equals("rgb")) || (suffix.equals("rgba")) || + (suffix.equals("bw")) || (suffix.equals("sgi"))) { + RgbFile f; + if (fromUrl) { + f = new RgbFile(new URL(basePath + tFile).openStream()); + } else { + f = new RgbFile(new FileInputStream(basePath + tFile)); + } + BufferedImage bi = f.getImage(); + + boolean luminance = suffix.equals("int") || suffix.equals("inta"); + boolean alpha = suffix.equals("inta") || suffix.equals("rgba"); + cur.transparent = alpha; + + String s = null; + if (luminance && alpha) s = "LUM8_ALPHA8"; + else if (luminance) s = "LUMINANCE"; + else if (alpha) s = "RGBA"; + else s = "RGB"; + + t = new TextureLoader(bi, s, TextureLoader.GENERATE_MIPMAP); + } else { + // For all other file types, use the TextureLoader + if (fromUrl) { + t = new TextureLoader(new URL(basePath + tFile), "RGB", + TextureLoader.GENERATE_MIPMAP, null); + } else { + t = new TextureLoader(basePath + tFile, "RGB", + TextureLoader.GENERATE_MIPMAP, null); + } + } + Texture2D texture = (Texture2D)t.getTexture(); + if (texture != null) cur.t = texture; + } + catch (FileNotFoundException e) { + // Texture won't get loaded if file can't be found + } + catch (MalformedURLException e) { + // Texture won't get loaded if file can't be found + } + catch (IOException e) { + // Texture won't get loaded if file can't be found + } + } + } + st.skipToNextLine(); + } // End of readMapKd + + + private void readFile(ObjectFileParser st) throws ParsingErrorException { + int t; + st.getToken(); + while (st.ttype != ObjectFileParser.TT_EOF) { + + // Print out one token for each line + if ((DEBUG & 16) != 0) { + System.out.print("Token "); + if (st.ttype == ObjectFileParser.TT_EOL) System.out.println("EOL"); + else if (st.ttype == ObjectFileParser.TT_WORD) + System.out.println(st.sval); + else System.out.println((char)st.ttype); + } + + if (st.ttype == ObjectFileParser.TT_WORD) { + if (st.sval.equals("newmtl")) { + readName(st); + } else if (st.sval.equals("ka")) { + readAmbient(st); + } else if (st.sval.equals("kd")) { + readDiffuse(st); + } else if (st.sval.equals("ks")) { + readSpecular(st); + } else if (st.sval.equals("illum")) { + readIllum(st); + } else if (st.sval.equals("d")) { + st.skipToNextLine(); + } else if (st.sval.equals("ns")) { + readShininess(st); + } else if (st.sval.equals("tf")) { + st.skipToNextLine(); + } else if (st.sval.equals("sharpness")) { + st.skipToNextLine(); + } else if (st.sval.equals("map_kd")) { + readMapKd(st); + } else if (st.sval.equals("map_ka")) { + st.skipToNextLine(); + } else if (st.sval.equals("map_ks")) { + st.skipToNextLine(); + } else if (st.sval.equals("map_ns")) { + st.skipToNextLine(); + } else if (st.sval.equals("bump")) { + st.skipToNextLine(); + } + } + + st.skipToNextLine(); + + // Get next token + st.getToken(); + } + if (curName != null) materials.put(curName, cur); + } // End of readFile + + + void readMaterialFile(boolean fromUrl, String basePath, String fileName) + throws ParsingErrorException { + + Reader reader; + + this.basePath = basePath; + this.fromUrl = fromUrl; + + try { + if (fromUrl) { + reader = (Reader) + (new InputStreamReader( + new BufferedInputStream( + (new URL(basePath + fileName).openStream())))); + } else { + reader = new BufferedReader(new FileReader(basePath + fileName)); + } + } + catch (IOException e) { + // couldn't find it - ignore mtllib + return; + } + if ((DEBUG & 1) != 0) + System.out.println("Material file: " + basePath + fileName); + + ObjectFileParser st = new ObjectFileParser(reader); + readFile(st); + } // End of readMaterialFile + + + ObjectFileMaterials() throws ParsingErrorException { + Reader reader = new StringReader(DefaultMaterials.materials); + + ObjectFileParser st = new ObjectFileParser(reader); + materials = new HashMap(50); + readFile(st); + } // End of ObjectFileMaterials + + + /** + * Implement the ImageObserver interface. Needed to load jpeg and gif + * files using the Toolkit. + */ + public boolean imageUpdate(Image img, int flags, + int x, int y, int w, int h) { + + return (flags & (ALLBITS | ABORT)) == 0; + } // End of imageUpdate + +} // End of class ObjectFileMaterials + +// End of file ObjectFileMaterials.java diff --git a/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileParser.java b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileParser.java new file mode 100644 index 0000000..ded314a --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/objectfile/ObjectFileParser.java @@ -0,0 +1,179 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.objectfile; + +import java.io.StreamTokenizer; +import java.io.IOException; +import java.io.Reader; +import com.sun.j3d.loaders.ParsingErrorException; + +class ObjectFileParser extends StreamTokenizer { + + private static final char BACKSLASH = '\\'; + + + /** + * setup + * + * Sets up StreamTokenizer for reading ViewPoint .obj file format. + */ + void setup() { + resetSyntax(); + eolIsSignificant(true); + lowerCaseMode(true); + + // All printable ascii characters + wordChars('!', '~'); + + // Comment from ! to end of line + commentChar('!'); + + whitespaceChars(' ', ' '); + whitespaceChars('\n', '\n'); + whitespaceChars('\r', '\r'); + whitespaceChars('\t', '\t'); + + // These characters returned as tokens + ordinaryChar('#'); + ordinaryChar('/'); + ordinaryChar(BACKSLASH); + } // End of setup + + + /** + * getToken + * + * Gets the next token from the stream. Puts one of the four + * constants (TT_WORD, TT_NUMBER, TT_EOL, or TT_EOF) or the token value + * for single character tokens into ttype. Handles backslash + * continuation of lines. + */ + void getToken() throws ParsingErrorException { + int t; + boolean done = false; + + try { + do { + t = nextToken(); + if (t == BACKSLASH) { + t = nextToken(); + if (ttype != TT_EOL) done = true; + } else done = true; + } while (!done); + } + catch (IOException e) { + throw new ParsingErrorException( + "IO error on line " + lineno() + ": " + e.getMessage()); + } + } // End of getToken + + + void printToken() { + switch (ttype) { + case TT_EOL: + System.out.println("Token EOL"); + break; + case TT_EOF: + System.out.println("Token EOF"); + break; + case TT_WORD: + System.out.println("Token TT_WORD: " + sval); + break; + case '/': + System.out.println("Token /"); + break; + case BACKSLASH: + System.out.println("Token " + BACKSLASH); + break; + case '#': + System.out.println("Token #"); + break; + } + } // end of printToken + + + /** + * skipToNextLine + * + * Skips all tokens on the rest of this line. Doesn't do anything if + * We're already at the end of a line + */ + void skipToNextLine() throws ParsingErrorException { + while (ttype != TT_EOL) { + getToken(); + } + } // end of skipToNextLine + + + /** + * getNumber + * + * Gets a number from the stream. Note that we don't recognize + * numbers in the tokenizer automatically because numbers might be in + * scientific notation, which isn't processed correctly by + * StreamTokenizer. The number is returned in nval. + */ + void getNumber() throws ParsingErrorException { + int t; + + try { + getToken(); + if (ttype != TT_WORD) + throw new ParsingErrorException("Expected number on line " + lineno()); + nval = (Double.valueOf(sval)).doubleValue(); + } + catch (NumberFormatException e) { + throw new ParsingErrorException(e.getMessage()); + } + } // end of getNumber + + + // ObjectFileParser constructor + ObjectFileParser(Reader r) { + super(r); + setup(); + } // end of ObjectFileParser + +} // End of file ObjectFileParser.java diff --git a/src/classes/share/com/sun/j3d/loaders/objectfile/RgbFile.java b/src/classes/share/com/sun/j3d/loaders/objectfile/RgbFile.java new file mode 100644 index 0000000..5512ec2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/loaders/objectfile/RgbFile.java @@ -0,0 +1,202 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.loaders.objectfile; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.FileInputStream; +import java.io.InputStream; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.ComponentColorModel; +import java.awt.image.WritableRaster; +import java.awt.color.ColorSpace; +import java.awt.Transparency; + +class RgbFile extends BufferedInputStream { + + // Header data + short dimension; + short xSize; + short ySize; + short zSize; + + String filename; + + private static final int DEBUG = 0; + + + short getShort() throws IOException { + int t1 = (short)read(); + if (t1 == -1) throw new IOException("Unexpected EOF"); + int t2 = (short)read(); + if (t2 == -1) throw new IOException("Unexpected EOF"); + return (short)((t1 << 8) | t2); + } // End of getShort() + + + byte getByte() throws IOException { + int t = read(); + if (t == -1) throw new IOException("Unexpected EOF"); + return (byte)t; + } // End of getByte + + + int getInt() throws IOException { + int ret = 0; + for (int i = 0 ; i < 4 ; i++) { + int t = read(); + if (t == -1) throw new IOException("Unexpected EOF"); + ret = (ret << 8) | t; + } + return ret; + } // end of getInt + + + public BufferedImage getImage() throws IOException { + short magic = getShort(); + + if (magic != 474) throw new IOException("Unrecognized file format."); + + byte storage = getByte(); + + if (storage != 0) + throw new IOException("RLE Compressed files not supported"); + + byte bpc = getByte(); + dimension = getShort(); + xSize = getShort(); + ySize = getShort(); + zSize = getShort(); + int pixMin = getInt(); + int pixMax = getInt(); + skip(84l); + int colorMap = getInt(); + + if ((DEBUG & 1) != 0) { + System.out.println(filename + ":"); + System.out.println(" bpc = " + bpc); + System.out.println(" dimension = " + dimension); + System.out.println(" xSize = " + xSize); + System.out.println(" ySize = " + ySize); + System.out.println(" zSize = " + zSize); + System.out.println(" pixMin = " + pixMin); + System.out.println(" pixMax = " + pixMax); + System.out.println(" colorMap = " + colorMap); + } + + if ((pixMin != 0) || (pixMax != 0xff) || (colorMap != 0) || (bpc != 1)) + throw new IOException("Unsupported options in file"); + + skip(404l); + + ComponentColorModel cm = null; + if (zSize == 1) { + // Black and White image + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + + int[] nBits = {8}; + cm = new ComponentColorModel(cs, nBits, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + } else if (zSize == 2) { + // Black and White image with alpha + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + + int[] nBits = {8, 8}; + cm = new ComponentColorModel(cs, nBits, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + + } else if (zSize == 3) { + // RGB Image + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + + int[] nBits = {8, 8, 8}; + cm = new ComponentColorModel(cs, nBits, false, false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + } else if (zSize == 4) { + // RGBA Image + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + + int[] nBits = {8, 8, 8, 8}; + cm = new ComponentColorModel(cs, nBits, true, false, + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + } else { + throw new IOException("Unsupported options in file"); + } + + WritableRaster r = cm.createCompatibleWritableRaster(xSize, ySize); + BufferedImage bi = new BufferedImage(cm, r, false, null); + + int t; + byte image[] = ((DataBufferByte)r.getDataBuffer()).getData(); + for (short z = 0 ; z < zSize ; z++) { + for (int y = ySize - 1 ; y >= 0 ; y--) { + for (short x = 0 ; x < xSize ; x++) { + t = read(); + if (t == -1) throw new IOException("Unexpected EOF"); + image[y * (xSize * zSize) + (x * zSize) + z] = (byte)t; + } + } + } + + return bi; + } // End of getImage + + + public RgbFile(InputStream s) { + super(s); + } // End of RgbFile(URL) + +} // End of class RgbFile + +// End of file RgbFile.java diff --git a/src/classes/share/com/sun/j3d/utils/applet/JMainFrame.java b/src/classes/share/com/sun/j3d/utils/applet/JMainFrame.java new file mode 100644 index 0000000..b4a99e1 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/applet/JMainFrame.java @@ -0,0 +1,347 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// JMainFrame - run an Applet as an application +// +// Copyright (C) 1996 by Jef Poskanzer . All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +// --------------------------------------------------------------------- + +package com.sun.j3d.utils.applet; + +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.io.*; +import java.net.*; +import javax.swing.*; +import java.util.*; + +public class JMainFrame extends JFrame + implements Runnable, AppletStub, AppletContext { + + private String[] args = null; + private static int instances = 0; + private String name; + private Applet applet; + private Label label = null; + private Dimension appletSize; + + private static final String PARAM_PROP_PREFIX = "parameter."; + + public JMainFrame(Applet applet, String[] args, int width, int height) { + build(applet, args, width, height); + } + public JMainFrame(Applet applet, String[] args) { + build(applet, args, -1, -1); + } + + public JMainFrame(Applet applet, int width, int height) { + build(applet, null, width, height); + } + + private void build(Applet applet, String[] args, int width, int height) { + ++instances; + this.applet = applet; + this.args = args; + applet.setStub( this ); + name = applet.getClass().getName(); + setTitle( name ); + + // Set up properties. + Properties props = System.getProperties(); + props.put( "browser", "Acme.MainFrame" ); + props.put( "browser.version", "11jul96" ); + props.put( "browser.vendor", "Acme Laboratories" ); + props.put( "browser.vendor.url", "http://www.acme.com/" ); + + // Turn args into parameters by way of the properties list. + if ( args != null ) + parseArgs( args, props ); + + // If width and height are specified in the parameters, override + // the compiled-in values. + String widthStr = getParameter( "width" ); + if ( widthStr != null ) + width = Integer.parseInt( widthStr ); + String heightStr = getParameter( "height" ); + if ( heightStr != null ) + height = Integer.parseInt( heightStr ); + + // Were width and height specified somewhere? + if ( width == -1 || height == -1 ) { + System.err.println( "Width and height must be specified." ); + return; + } + + // Lay out components. + Container contentPane = getContentPane(); + contentPane.add(applet, BorderLayout.CENTER); + + // Set up size. + pack(); + validate(); + appletSize = applet.getSize(); + applet.setSize( width, height ); + setVisible(true); + + /* + Added WindowListener inner class to detect close events. + */ + SecurityManager sm = System.getSecurityManager(); + boolean doExit = true; + if (sm != null) { + try { + sm.checkExit(0); + } catch (SecurityException e) { + doExit = false; + } + } + + final boolean _doExit = doExit; + + // WindowListener inner class to detect close events. + addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent winEvent) + { + if (JMainFrame.this.applet != null) { + JMainFrame.this.applet.destroy(); + } + hide(); + try { + dispose(); + } catch (IllegalStateException e) {} + + if (_doExit) { + System.exit(0); + } + + } + }); + + // Start a separate thread to call the applet's init() and start() + // methods, in case they take a long time. + (new Thread( this )).start(); + + } + + // Turn command-line arguments into Applet parameters, by way of the + // properties list. + private static void parseArgs( String[] args, Properties props ) { + for ( int i = 0; i < args.length; ++i ) { + String arg = args[i]; + int ind = arg.indexOf( '=' ); + if ( ind == -1 ) + props.put( PARAM_PROP_PREFIX + arg.toLowerCase(), "" ); + else + props.put( + PARAM_PROP_PREFIX + arg.substring( 0, ind ).toLowerCase(), + arg.substring( ind + 1 ) ); + } + } + + // Methods from Runnable. + + /// Separate thread to call the applet's init() and start() methods. + public void run() { + showStatus( name + " initializing..." ); + applet.init(); + validate(); + showStatus( name + " starting..." ); + applet.start(); + validate(); + showStatus( name + " running..." ); + } + + // Methods from AppletStub. + + public boolean isActive() { + return true; + } + + public URL getDocumentBase() { + // Returns the current directory. + String dir = System.getProperty( "user.dir" ); + String urlDir = dir.replace( File.separatorChar, '/' ); + try { + return new URL( "file:" + urlDir + "/"); + } + catch ( MalformedURLException e ) { + return null; + } + } + + public URL getCodeBase() { + // Hack: loop through each item in CLASSPATH, checking if + // the appropriately named .class file exists there. But + // this doesn't account for .zip files. + String path = System.getProperty( "java.class.path" ); + Enumeration st = new StringTokenizer( path, ":" ); + while ( st.hasMoreElements() ) { + String dir = (String) st.nextElement(); + String filename = dir + File.separatorChar + name + ".class"; + File file = new File( filename ); + if ( file.exists() ) { + String urlDir = dir.replace( File.separatorChar, '/' ); + try { + return new URL( "file:" + urlDir + "/" ); + } + catch ( MalformedURLException e ) { + return null; + } + } + } + return null; + } + + public String getParameter( String name ) { + // Return a parameter via the munged names in the properties list. + return System.getProperty( PARAM_PROP_PREFIX + name.toLowerCase() ); + } + + public void appletResize( int width, int height ) { + // Change the frame's size by the same amount that the applet's + // size is changing. + Dimension frameSize = getSize(); + frameSize.width += width - appletSize.width; + frameSize.height += height - appletSize.height; + setSize( frameSize ); + appletSize = applet.getSize(); + } + + public AppletContext getAppletContext() { + return this; + } + + + // Methods from AppletContext. + + public AudioClip getAudioClip( URL url ) { + // This is an internal undocumented routine. However, it + // also provides needed functionality not otherwise available. + // I suspect that in a future release, JavaSoft will add an + // audio content handler which encapsulates this, and then + // we can just do a getContent just like for images. + return new sun.applet.AppletAudioClip( url ); + } + + public Image getImage( URL url ) { + Toolkit tk = Toolkit.getDefaultToolkit(); + try { + ImageProducer prod = (ImageProducer) url.getContent(); + return tk.createImage( prod ); + } + catch ( IOException e ) { + return null; + } + } + + public Applet getApplet( String name ) { + // Returns this Applet or nothing. + if ( name.equals( this.name ) ) + return applet; + return null; + } + + public Enumeration getApplets() { + // Just yields this applet. + Vector v = new Vector(); + v.addElement( applet ); + return v.elements(); + } + + public void showDocument( URL url ) { + // Ignore. + } + + public void showDocument( URL url, String target ) { + // Ignore. + } + + public void showStatus( String status ) { + if ( label != null ) + label.setText( status ); + } + + public void setStream( String key, java.io.InputStream stream ) { + throw new RuntimeException("Not Implemented"); + // TODO implement setStream method + } + + public java.io.InputStream getStream( String key ) { + throw new RuntimeException("Not Implemented"); + // TODO implement getStream method + } + + public java.util.Iterator getStreamKeys() { + throw new RuntimeException("Not Implemented"); + // TODO implement getStreamKeys method + } +} diff --git a/src/classes/share/com/sun/j3d/utils/applet/MainFrame.java b/src/classes/share/com/sun/j3d/utils/applet/MainFrame.java new file mode 100644 index 0000000..0d31082 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/applet/MainFrame.java @@ -0,0 +1,397 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// MainFrame - run an Applet as an application +// +// Copyright (C) 1996 by Jef Poskanzer . All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +// SUCH DAMAGE. +// +// Visit the ACME Labs Java page for up-to-date versions of this and other +// fine Java utilities: http://www.acme.com/java/ + +// --------------------------------------------------------------------- + +package com.sun.j3d.utils.applet; + +import java.applet.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.*; +import java.net.*; +import java.io.*; +import java.util.*; + +/// Run an Applet as an application. +//

+// Using this class you can add a trivial main program to any Applet +// and run it directly, as well as from a browser or the appletviewer. +// And unlike some versions of this concept, MainFrame implements both +// images and sound. +//

+// Sample main program: +//

+// public static void main( String[] args )
+//     {
+//     new Acme.MainFrame( new ThisApplet(), args, 400, 400 );
+//     }
+// 
+// The only methods you need to know about are the constructors. +//

+// You can specify Applet parameters on the command line, as name=value. +// For instance, the equivalent of: +//

+// <PARAM NAME="pause" VALUE="200">
+// 
+// would just be: +//
+// pause=200
+// 
+// You can also specify three special parameters: +//
+// width=N          Width of the Applet.
+// height=N         Height of the Applet.
+// barebones=true   Leave off the menu bar and status area.
+// 
+//

+// Fetch the software.
+// Fetch the entire Acme package. + +public class MainFrame extends Frame implements + Runnable, AppletStub, AppletContext +{ + + private String[] args = null; + private static int instances = 0; + private String name; + private boolean barebones = true; + private Applet applet; + private Label label = null; + private Dimension appletSize; + + private static final String PARAM_PROP_PREFIX = "parameter."; + + /// Constructor with everything specified. + public MainFrame(Applet applet, String[] args, + int width, int height) { + build(applet, args, width, height); + } + + /// Constructor with no default width/height. + public MainFrame(Applet applet, String[] args ) { + build(applet, args, -1, -1); + } + + /// Constructor with no arg parsing. + public MainFrame(Applet applet, int width, int height) { + build( applet, null, width, height ); + } + + // Internal constructor routine. + private void build( Applet applet, String[] args, + int width, int height) { + ++instances; + this.applet = applet; + this.args = args; + applet.setStub( this ); + name = applet.getClass().getName(); + setTitle( name ); + + // Set up properties. + Properties props = System.getProperties(); + props.put( "browser", "Acme.MainFrame" ); + props.put( "browser.version", "11jul96" ); + props.put( "browser.vendor", "Acme Laboratories" ); + props.put( "browser.vendor.url", "http://www.acme.com/" ); + + // Turn args into parameters by way of the properties list. + if ( args != null ) + parseArgs( args, props ); + + // If width and height are specified in the parameters, override + // the compiled-in values. + String widthStr = getParameter( "width" ); + if ( widthStr != null ) { + width = Integer.parseInt( widthStr ); + } + + String heightStr = getParameter( "height" ); + if ( heightStr != null ) { + height = Integer.parseInt( heightStr ); + } + + // Were width and height specified somewhere? + if ((width == -1) || (height == -1)) { + System.err.println( "Width and height must be specified." ); + return; + } + + // Do we want to run bare-bones? + String bonesStr = getParameter( "barebones" ); + if ((bonesStr != null) && bonesStr.equals( "true" )) { + barebones = true; + } + + // Lay out components. + setLayout( new BorderLayout() ); + add( "Center", applet ); + + // Set up size. + pack(); + validate(); + appletSize = applet.getSize(); + applet.setSize( width, height ); + setVisible(true); + + + /* + Added WindowListener inner class to detect close events. + */ + SecurityManager sm = System.getSecurityManager(); + boolean doExit = true; + + if (sm != null) { + try { + sm.checkExit(0); + } catch (SecurityException e) { + doExit = false; + } + } + + final boolean _doExit = doExit; + + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent winEvent) { + if (MainFrame.this.applet != null) { + MainFrame.this.applet.destroy(); + } + Window w = winEvent.getWindow(); + w.hide(); + try { + w.dispose(); + } catch (IllegalStateException e) {} + + if (_doExit) { + System.exit(0); + } + } + }); + + // Start a separate thread to call the applet's init() and start() + // methods, in case they take a long time. + (new Thread( this )).start(); + } + + // Turn command-line arguments into Applet parameters, by way of the + // properties list. + private static void parseArgs( String[] args, Properties props) { + String arg; + + for (int i = 0; i < args.length; ++i) { + arg = args[i]; + int ind = arg.indexOf( '=' ); + if ( ind == -1 ) { + props.put(PARAM_PROP_PREFIX + arg.toLowerCase(), "" ); + } else { + props.put(PARAM_PROP_PREFIX + arg.substring( 0, ind ).toLowerCase(), + arg.substring( ind + 1 ) ); + } + } + } + + // Methods from Runnable. + + /// Separate thread to call the applet's init() and start() methods. + public void run() { + showStatus( name + " initializing..." ); + applet.init(); + validate(); + showStatus( name + " starting..." ); + applet.start(); + validate(); + showStatus( name + " running..." ); + } + + + // Methods from AppletStub. + public boolean isActive() { + return true; + } + + public URL getDocumentBase() { + // Returns the current directory. + String dir = System.getProperty( "user.dir" ); + String urlDir = dir.replace( File.separatorChar, '/' ); + try { + return new URL( "file:" + urlDir + "/"); + } catch ( MalformedURLException e ) { + return null; + } + } + + public URL getCodeBase() { + // Hack: loop through each item in CLASSPATH, checking if + // the appropriately named .class file exists there. But + // this doesn't account for .zip files. + String path = System.getProperty( "java.class.path" ); + Enumeration st = new StringTokenizer( path, ":" ); + while ( st.hasMoreElements() ) { + String dir = (String) st.nextElement(); + String filename = dir + File.separatorChar + name + ".class"; + File file = new File( filename ); + if (file.exists()) { + String urlDir = dir.replace( File.separatorChar, '/' ); + try { + return new URL( "file:" + urlDir + "/" ); + } catch (MalformedURLException e) { + return null; + } + } + } + return null; + } + + public String getParameter(String name) { + // Return a parameter via the munged names in the properties list. + return System.getProperty( PARAM_PROP_PREFIX + name.toLowerCase() ); + } + + public void appletResize(int width, int height) { + // Change the frame's size by the same amount that the applet's + // size is changing. + Dimension frameSize = getSize(); + frameSize.width += width - appletSize.width; + frameSize.height += height - appletSize.height; + setSize( frameSize ); + appletSize = applet.getSize(); + } + + public AppletContext getAppletContext() { + return this; + } + + + // Methods from AppletContext. + public AudioClip getAudioClip( URL url ) { + // This is an internal undocumented routine. However, it + // also provides needed functionality not otherwise available. + // I suspect that in a future release, JavaSoft will add an + // audio content handler which encapsulates this, and then + // we can just do a getContent just like for images. + return new sun.applet.AppletAudioClip( url ); + } + + public Image getImage( URL url ) { + Toolkit tk = Toolkit.getDefaultToolkit(); + try { + ImageProducer prod = (ImageProducer) url.getContent(); + return tk.createImage( prod ); + } catch ( IOException e ) { + return null; + } + } + + public Applet getApplet(String name) { + // Returns this Applet or nothing. + if (name.equals( this.name )) { + return applet; + } + return null; + } + + public Enumeration getApplets() { + // Just yields this applet. + Vector v = new Vector(); + v.addElement( applet ); + return v.elements(); + } + + public void showDocument( URL url ) { + // Ignore. + } + + public void showDocument( URL url, String target ) { + // Ignore. + } + + public void showStatus( String status ) { + if (label != null) { + label.setText(status); + } + } + + public void setStream( String key, java.io.InputStream stream ) { + throw new RuntimeException("Not Implemented"); + // TODO implement setStream method + } + + public java.io.InputStream getStream( String key ) { + throw new RuntimeException("Not Implemented"); + // TODO implement getStream method + } + + public java.util.Iterator getStreamKeys() { + throw new RuntimeException("Not Implemented"); + // TODO implement getStreamKeys method + } +} diff --git a/src/classes/share/com/sun/j3d/utils/audio/DistanceAttenuation.java b/src/classes/share/com/sun/j3d/utils/audio/DistanceAttenuation.java new file mode 100644 index 0000000..01821df --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/audio/DistanceAttenuation.java @@ -0,0 +1,134 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + + /* + * Sound Distance Attenuation utilities + * + * Various methods to create PointSound and ConeSound distance attenuation + * arrays. + */ + +package com.sun.j3d.utils.audio; + +import java.io.* ; +import javax.vecmath.* ; +import java.lang.String; +import javax.media.j3d.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +public class DistanceAttenuation +{ + // private fields + + // public fields + /** + * Equation types + */ + static final int DOUBLE_DISTANCE_HALF_GAIN = 1; + + // methods + /** + * Fill a Distance Attenuation array + * + * recommend that the distance attenuation Point2f array is defined to + * be allocated to be 10 for DOUBLE_DISTANCE_HALF_GAIN - since 1/(2^10) + * exceeds 1/1000 scale that is agreed to be affective zero gain + * + * First method assumes that: + * type is half gain for every double of distance + * inner radius is 0.0 but region between 0th and 1st elements is constant + * since gains for these two elements are the same + * min gain approches zero. + */ + public void fillDistanceAttenuation( + float unitDistance, float unitGain, + Point2f[] distanceAttenuation ) { + if (distanceAttenuation == null) + throw new SoundException(J3dUtilsI18N.getString("DistanceAttenuation0")); + + int length = distanceAttenuation.length; + distanceAttenuation[0].x = 0.0f; + distanceAttenuation[0].y = unitGain; + float nextDistance = unitDistance; + float nextGain = unitGain; + + for (int i=1; ith + * and (i+1)th key frame then the four key frames that need to + * be specified are the (i-1)th, ith, (i+1)th + * and (i+2)th keyframes in order. The CubicSegmentClass + * then pre-computes the hermite interpolation basis coefficients if the + * (i+1)th frame has the linear flag set to zero. These are used to + * calculate the interpolated position, scale and quaternions when they + * requested by the user using the getInterpolated* methods. If the the + * (i+1)th frame's linear flag is set to 1 then the class uses + * linear interpolation to calculate the interpolated position, sccale and + * quaternions it returns through the getInterpolated* methods. + * + * @since Java3D 1.1 + */ + +public class CubicSplineSegment { + + // Legendre polynomial information for Gaussian quadrature of speed + // for the domain [0,u], 0 <= u <= 1. + + // Legendre roots mapped to (root+1)/2 + static final double modRoot[] = + { + 0.046910077, + 0.230765345, + 0.5, + 0.769234655, + 0.953089922 + }; + + // original coefficients divided by 2 + static final double modCoeff[] = + { + 0.118463442, + 0.239314335, + 0.284444444, + 0.239314335, + 0.118463442 + }; + + // Key Frames + TCBKeyFrame[] keyFrame = new TCBKeyFrame[4]; + + // H.C + Point3f c0, c1, c2, c3; // coefficients for position + Point3f e0, e1, e2, e3; // coefficients for scale + + // variables for destination derivative + float one_minus_t_in; + float one_minus_c_in; + float one_minus_b_in; + float one_plus_c_in; + float one_plus_b_in; + float ddb; + float dda; + + // variables for source derivative + float one_minus_t_out; + float one_minus_c_out; + float one_minus_b_out; + float one_plus_c_out; + float one_plus_b_out; + float dsb; + float dsa; + + // Length of the spline segment + float length; + + // interpolation type + int linear; + + /** + * Default constructor + */ + CubicSplineSegment () { + + length = 0; + + } + + /** + * Creates a cubic spline segment between two key frames using the + * key frames provided. If creating a spline between the ith frame and + * the (i+1)th frame then send down the (i - 1)th, + * ith , (i+1)th and the (i+2)th key + * frames. + * + * @param kf0 (i - 1)th Key Frame + * @param kf1 ith Key Frame + * @param kf2 (i + 1)th Key Frame + * @param kf3 (i + 2)th Key Frame + */ + + CubicSplineSegment (TCBKeyFrame kf0, TCBKeyFrame kf1, TCBKeyFrame kf2, + TCBKeyFrame kf3) { + + // Copy KeyFrame information + keyFrame[0] = new TCBKeyFrame(kf0); + keyFrame[1] = new TCBKeyFrame(kf1); + keyFrame[2] = new TCBKeyFrame(kf2); + keyFrame[3] = new TCBKeyFrame(kf3); + + // if linear interpolation is requested then just set linear flag + // if spline interpolation is needed then compute spline coefficients + if (kf2.linear == 1) { + this.linear = 1; + } else { + this.linear = 0; + computeCommonCoefficients (kf0, kf1, kf2, kf3); + computeHermiteCoefficients (kf0, kf1, kf2, kf3); + } + + length = computeLength (1.0f); + // System.out.println ("Segment length = " + length); + + } + + // compute the common coefficients + private void computeCommonCoefficients (TCBKeyFrame kf0, + TCBKeyFrame kf1, + TCBKeyFrame kf2, + TCBKeyFrame kf3) { + + // variables for destination derivative + float one_minus_t_in = 1.0f - kf1.tension; + float one_minus_c_in = 1.0f - kf1.continuity; + float one_minus_b_in = 1.0f - kf1.bias; + float one_plus_c_in = 1.0f + kf1.continuity; + float one_plus_b_in = 1.0f + kf1.bias; + + // coefficients for the incoming Tangent + ddb = one_minus_t_in * one_minus_c_in * one_minus_b_in; + dda = one_minus_t_in * one_plus_c_in * one_plus_b_in; + + // variables for source derivative + float one_minus_t_out = 1.0f - kf2.tension; + float one_minus_c_out = 1.0f - kf2.continuity; + float one_minus_b_out = 1.0f - kf2.bias; + float one_plus_c_out = 1.0f + kf2.continuity; + float one_plus_b_out = 1.0f + kf2.bias; + + // coefficients for the outgoing Tangent + dsb = one_minus_t_in * one_plus_c_in * one_minus_b_in; + dsa = one_minus_t_in * one_minus_c_in * one_plus_b_in; + } + + + // compute the hermite interpolation basis coefficients + private void computeHermiteCoefficients (TCBKeyFrame kf0, + TCBKeyFrame kf1, + TCBKeyFrame kf2, + TCBKeyFrame kf3) { + + + Point3f deltaP = new Point3f(); + Point3f deltaS = new Point3f(); + + // Find the difference in position and scale + deltaP.x = kf2.position.x - kf1.position.x; + deltaP.y = kf2.position.y - kf1.position.y; + deltaP.z = kf2.position.z - kf1.position.z; + + deltaS.x = kf2.scale.x - kf1.scale.x; + deltaS.y = kf2.scale.y - kf1.scale.y; + deltaS.z = kf2.scale.z - kf1.scale.z; + + // Incoming Tangent + Point3f dd_pos = new Point3f(); + Point3f dd_scale = new Point3f(); + + // If this is the first keyframe of the animation + if (kf0.knot == kf1.knot) { + + float ddab = 0.5f * (dda + ddb); + + // Position + dd_pos.x = ddab * deltaP.x; + dd_pos.y = ddab * deltaP.y; + dd_pos.z = ddab * deltaP.z; + + // Scale + dd_scale.x = ddab * deltaS.x; + dd_scale.y = ddab * deltaS.y; + dd_scale.z = ddab * deltaS.z; + + } else { + + float adj0 = (kf1.knot - kf0.knot)/(kf2.knot - kf0.knot); + + // Position + dd_pos.x = adj0 * + ((ddb * deltaP.x) + (dda * (kf1.position.x - kf0.position.x))); + dd_pos.y = adj0 * + ((ddb * deltaP.y) + (dda * (kf1.position.y - kf0.position.y))); + dd_pos.z = adj0 * + ((ddb * deltaP.z) + (dda * (kf1.position.z - kf0.position.z))); + + // Scale + dd_scale.x = adj0 * + ((ddb * deltaS.x) + (dda * (kf1.scale.x - kf0.scale.x))); + dd_scale.y = adj0 * + ((ddb * deltaS.y) + (dda * (kf1.scale.y - kf0.scale.y))); + dd_scale.z = adj0 * + ((ddb * deltaS.z) + (dda * (kf1.scale.z - kf0.scale.z))); + } + + // Outgoing Tangent + Point3f ds_pos = new Point3f(); + Point3f ds_scale = new Point3f(); + + // If this is the last keyframe of the animation + if (kf2.knot == kf3.knot) { + + float dsab = 0.5f * (dsa + dsb); + + // Position + ds_pos.x = dsab * deltaP.x; + ds_pos.y = dsab * deltaP.y; + ds_pos.z = dsab * deltaP.z; + + // Scale + ds_scale.x = dsab * deltaS.x; + ds_scale.y = dsab * deltaS.y; + ds_scale.z = dsab * deltaS.z; + + } else { + + float adj1 = (kf2.knot - kf1.knot)/(kf3.knot - kf1.knot); + + // Position + ds_pos.x = adj1 * + ((dsb * (kf3.position.x - kf2.position.x)) + (dsa * deltaP.x)); + ds_pos.y = adj1 * + ((dsb * (kf3.position.y - kf2.position.y)) + (dsa * deltaP.y)); + ds_pos.z = adj1 * + ((dsb * (kf3.position.z - kf2.position.z)) + (dsa * deltaP.z)); + + // Scale + ds_scale.x = adj1 * + ((dsb * (kf3.scale.x - kf2.scale.x)) + (dsa * deltaS.x)); + ds_scale.y = adj1 * + ((dsb * (kf3.scale.y - kf2.scale.y)) + (dsa * deltaS.y)); + ds_scale.z = adj1 * + ((dsb * (kf3.scale.z - kf2.scale.z)) + (dsa * deltaS.z)); + } + + // Calculate the coefficients of the polynomial for position + c0 = new Point3f(); + c0.x = kf1.position.x; + c0.y = kf1.position.y; + c0.z = kf1.position.z; + + c1 = new Point3f(); + c1.x = dd_pos.x; + c1.y = dd_pos.y; + c1.z = dd_pos.z; + + c2 = new Point3f(); + c2.x = 3*deltaP.x - 2*dd_pos.x - ds_pos.x; + c2.y = 3*deltaP.y - 2*dd_pos.y - ds_pos.y; + c2.z = 3*deltaP.z - 2*dd_pos.z - ds_pos.z; + + c3 = new Point3f(); + c3.x = -2*deltaP.x + dd_pos.x + ds_pos.x; + c3.y = -2*deltaP.y + dd_pos.y + ds_pos.y; + c3.z = -2*deltaP.z + dd_pos.z + ds_pos.z; + + // Calculate the coefficients of the polynomial for scale + e0 = new Point3f(); + e0.x = kf1.scale.x; + e0.y = kf1.scale.y; + e0.z = kf1.scale.z; + + e1 = new Point3f(); + e1.x = dd_scale.x; + e1.y = dd_scale.y; + e1.z = dd_scale.z; + + e2 = new Point3f(); + e2.x = 3*deltaS.x - 2*dd_scale.x - ds_scale.x; + e2.y = 3*deltaS.y - 2*dd_scale.y - ds_scale.y; + e2.z = 3*deltaS.z - 2*dd_scale.z - ds_scale.z; + + e3 = new Point3f(); + e3.x = -2*deltaS.x + dd_scale.x + ds_scale.x; + e3.y = -2*deltaS.y + dd_scale.y + ds_scale.y; + e3.z = -2*deltaS.z + dd_scale.z + ds_scale.z; + } + + + /** + * Computes the length of the curve at a given point between + * key frames. + * @param u specifies the point between keyframes where 0 <= u <= 1. + */ + + public float computeLength (float u) { + + float result = 0f; + + // if linear interpolation + if (linear == 1) { + result = u*keyFrame[2].position.distance(keyFrame[1].position); + } else { + // Need to transform domain [0,u] to [-1,1]. If 0 <= x <= u + // and -1 <= t <= 1, then x = u*(t+1)/2. + int degree = 5; + for (int i = 0; i < degree; i++) + result += (float)modCoeff[i]*computeSpeed(u*(float)modRoot[i]); + result *= u; + } + + return result; + } + + // Velocity along curve + private float computeSpeed (float u) { + Point3f v = new Point3f(); + + v.x = c1.x + u * (2 * c2.x + 3 * u * c3.x); + v.y = c1.y + u * (2 * c2.y + 3 * u * c3.y); + v.z = c1.z + u * (2 * c2.z + 3 * u * c3.z); + + return (float)(Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z)); + } + + + /** + * Computes the interpolated quaternion along the curve at + * a given point between key frames. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newQuat returns the value of the interpolated quaternion + */ + + public void getInterpolatedQuaternion (float u, Quat4f newQuat) { + + // if linear interpolation + if (this.linear == 1) { + double quatDot; + + quatDot = keyFrame[1].quat.x * keyFrame[2].quat.x + + keyFrame[1].quat.y * keyFrame[2].quat.y + + keyFrame[1].quat.z * keyFrame[2].quat.z + + keyFrame[1].quat.w * keyFrame[2].quat.w; + + if (quatDot < 0) { + newQuat.x = keyFrame[1].quat.x + + (-keyFrame[2].quat.x - keyFrame[1].quat.x) * u; + newQuat.y = keyFrame[1].quat.y + + (-keyFrame[2].quat.y - keyFrame[1].quat.y) * u; + newQuat.z = keyFrame[1].quat.z + + (-keyFrame[2].quat.z - keyFrame[1].quat.z) * u; + newQuat.w = keyFrame[1].quat.w + + (-keyFrame[2].quat.w - keyFrame[1].quat.w) * u; + } else { + newQuat.x = keyFrame[1].quat.x + + (keyFrame[2].quat.x - keyFrame[1].quat.x) * u; + newQuat.y = keyFrame[1].quat.y + + (keyFrame[2].quat.y - keyFrame[1].quat.y) * u; + newQuat.z = keyFrame[1].quat.z + + (keyFrame[2].quat.z - keyFrame[1].quat.z) * u; + newQuat.w = keyFrame[1].quat.w + + (keyFrame[2].quat.w - keyFrame[1].quat.w) * u; + } + + } else { + + // TODO: + // Currently we just use the great circle spherical interpolation + // for quaternions irrespective of the linear flag. Eventually + // we might want to do cubic interpolation of quaternions + newQuat.interpolate (keyFrame[1].quat, keyFrame[2].quat, u); + } + + } + + + + /** + * Computes the interpolated scale along the curve at a given point + * between key frames and returns a Point3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newScale returns the interpolated x,y,z scale value in a Point3f + */ + + public void getInterpolatedScale (float u, Point3f newScale) { + + // if linear interpolation + if (this.linear == 1) { + + newScale.x = keyFrame[1].scale.x + + ((keyFrame[2].scale.x - keyFrame[1].scale.x) * u); + newScale.y = keyFrame[1].scale.y + + ((keyFrame[2].scale.y - keyFrame[1].scale.y) * u); + newScale.z = keyFrame[1].scale.z + + ((keyFrame[2].scale.z - keyFrame[1].scale.z) * u); + + } else { + + newScale.x = e0.x + u * (e1.x + u * (e2.x + u * e3.x)); + newScale.y = e0.y + u * (e1.y + u * (e2.y + u * e3.y)); + newScale.z = e0.z + u * (e1.z + u * (e2.z + u * e3.z)); + + } + } + + + /** + * Computes the interpolated position along the curve at a given point + * between key frames and returns a Point3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newPos returns the interpolated x,y,z position in a Point3f + */ + + public void getInterpolatedPosition (float u, Point3f newPos) { + + // if linear interpolation + if (this.linear == 1) { + newPos.x = keyFrame[1].position.x + + ((keyFrame[2].position.x - keyFrame[1].position.x) * u); + newPos.y = keyFrame[1].position.y + + ((keyFrame[2].position.y - keyFrame[1].position.y) * u); + newPos.z = keyFrame[1].position.z + + ((keyFrame[2].position.z - keyFrame[1].position.z) * u); + } else { + + newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x)); + newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y)); + newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z)); + + } + } + + + /** + * Computes the interpolated position along the curve at a given point + * between key frames and returns a Vector3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newPos returns the interpolated x,y,z position in a Vector3f. + */ + + public void getInterpolatedPositionVector (float u, Vector3f newPos) { + // if linear interpolation + if (this.linear == 1) { + newPos.x = keyFrame[1].position.x + + ((keyFrame[2].position.x - keyFrame[1].position.x) * u); + newPos.y = keyFrame[1].position.y + + ((keyFrame[2].position.y - keyFrame[1].position.y) * u); + newPos.z = keyFrame[1].position.z + + ((keyFrame[2].position.z - keyFrame[1].position.z) * u); + } else { + + newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x)); + newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y)); + newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z)); + + } + } + + /** + * Computes the ratio of the length of the spline from the ith + * key frame to the position specified by u to the length of the entire + * spline segment from the ith key frame to the (i+1) + * th key frame. When the (i+1)th key frame's linear + * value is equal to 1, this is meaninful otherwise it should return u. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @return the interpolated ratio + */ + + public float getInterpolatedValue (float u) { + return (computeLength(u)/this.length); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineCurve.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineCurve.java new file mode 100644 index 0000000..84fdc1d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineCurve.java @@ -0,0 +1,174 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * KBCubicSplineCurve is a container class that holds a number of + * KBCubicSplineSegments + * + * @since Java3D 1.2 + */ + +public class KBCubicSplineCurve { + + private float totalCurveLength; + private KBCubicSplineSegment[] cubicSplineSegment; + public int numSegments; + + + // default constructor + KBCubicSplineCurve () { + numSegments = 0; + totalCurveLength = 0f; + } + + /** + * This method takes a list of key frames and creates spline segments + * from it. It requires at least four key frames to be passed to it. + * Given n key frames, it creates n-3 KBCubicSplineSegments. + * @param the list of key frames that specify the motion path + */ + + KBCubicSplineCurve (KBKeyFrame keys[]) { + + int keyLength = keys.length; + // Require at least 4 key frames for cubic spline curve + if (keyLength < 4) + throw new + IllegalArgumentException(J3dUtilsI18N.getString("KBCubicSplineCurve0")); + + numSegments = keyLength - 3; + this.cubicSplineSegment = new KBCubicSplineSegment[numSegments]; + + // intialize and calculate coefficients for each segment + int k0 = 0; int k1 = 1; int k2 = 2; int k3 = 3; + for (; k0 < numSegments; k0++, k1++, k2++, k3++) { + this.cubicSplineSegment[k0] = new KBCubicSplineSegment + (keys[k0], keys[k1], keys[k2], keys[k3]); + } + + // compute total curve length + computeTotalCurveLength (); + } + + /** + * This method takes a list of spline segments creates the + * KBCubicSplineCurve. + * @param the list of segments that comprise the complete motion path + */ + + KBCubicSplineCurve (KBCubicSplineSegment s[]) { + + cubicSplineSegment = new KBCubicSplineSegment[s.length]; + numSegments = cubicSplineSegment.length; + for (int i = 0; i < numSegments; i++) { + this.cubicSplineSegment[i] = s[i]; + } + + // compute total curve length + computeTotalCurveLength (); + } + + /** + * This method takes a list of spline segments to replace the existing + * set of KBCubicSplineSegments that comprise the current + * KBCubicSplineCurve motion path. + * @param s the list of segments that comprise the complete motion path + */ + + public void setSegments (KBCubicSplineSegment s[]) { + + cubicSplineSegment = new KBCubicSplineSegment[s.length]; + numSegments = cubicSplineSegment.length; + for (int i = 0; i < numSegments; i++) { + this.cubicSplineSegment[i] = s[i]; + } + + // compute total curve length + computeTotalCurveLength (); + } + + /** + * This method returns the KBCubicSplineSegments pointed to by index + * @param index the index of the KBCubicSplineSegment required + * @return the KBCubicSplineSegment pointed to by index + */ + public KBCubicSplineSegment getSegment (int index) { + + return this.cubicSplineSegment[index]; + + } + + + // computes the total length of the curve + private void computeTotalCurveLength () { + + totalCurveLength = 0f; + for (int i = 0; i < numSegments; i++) { + totalCurveLength += cubicSplineSegment[i].length; + } + + } + + /** + * This method returns the total length of the entire KBCubicSplineCurve + * motion path. + * + * @return the length of the KBCubicSplineCurve motion path + */ + + public float getTotalCurveLength () { + + return this.totalCurveLength; + + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineSegment.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineSegment.java new file mode 100644 index 0000000..396b01e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBCubicSplineSegment.java @@ -0,0 +1,630 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; + + +/** + * The KBCubicSplineSegment class creates the representation of a + * Kochanek-Bartel's (also known as the TCB or Tension-Continuity-Bias + * Spline. This class takes 4 key frames as its input (using KBKeyFrame). + * If interpolating between the ith and (i+1)th key + * frame then the four key frames that need to be specified are the + * (i-1)th, ith, (i+1)th + * and (i+2)th keyframes in order. The KBCubicSegmentClass + * then pre-computes the hermite interpolation basis coefficients if the + * (i+1)th frame has the linear flag set to zero. These are used to + * calculate the interpolated position, scale and quaternions when they + * requested by the user using the getInterpolated* methods. If the the + * (i+1)th frame's linear flag is set to 1 then the class uses + * linear interpolation to calculate the interpolated position, scale, heading + * pitch and bank it returns through the getInterpolated* methods. + * + * @since Java3D 1.2 + */ +public class KBCubicSplineSegment { + + // Legendre polynomial information for Gaussian quadrature of speed + // for the domain [0,u], 0 <= u <= 1. + + // Legendre roots mapped to (root+1)/2 + static final double modRoot[] = + { + 0.046910077, + 0.230765345, + 0.5, + 0.769234655, + 0.953089922 + }; + + // original coefficients divided by 2 + static final double modCoeff[] = + { + 0.118463442, + 0.239314335, + 0.284444444, + 0.239314335, + 0.118463442 + }; + + // Key Frames + KBKeyFrame[] keyFrame = new KBKeyFrame[4]; + + // H.C + Point3f c0, c1, c2, c3; // coefficients for position + Point3f e0, e1, e2, e3; // coefficients for scale + float h0, h1, h2, h3; // coefficients for heading + float p0, p1, p2, p3; // coefficients for pitch + float b0, b1, b2, b3; // coefficients for bank + + // variables for destination derivative + float one_minus_t_in; + float one_minus_c_in; + float one_minus_b_in; + float one_plus_c_in; + float one_plus_b_in; + float ddb; + float dda; + + // variables for source derivative + float one_minus_t_out; + float one_minus_c_out; + float one_minus_b_out; + float one_plus_c_out; + float one_plus_b_out; + float dsb; + float dsa; + + // Length of the spline segment + float length; + + // interpolation type + int linear; + + // Default constructor + KBCubicSplineSegment () { + + length = 0; + + } + + /** + * Creates a cubic spline segment between two key frames using the + * key frames provided. If creating a spline between the ith frame and + * the (i+1)th frame then send down the (i - 1)th, + * ith , (i+1)th and the (i+2)th key + * frames. + * + * @param kf0 (i - 1)th Key Frame + * @param kf1 ith Key Frame + * @param kf2 (i + 1)th Key Frame + * @param kf3 (i + 2)th Key Frame + */ + + KBCubicSplineSegment (KBKeyFrame kf0, KBKeyFrame kf1, KBKeyFrame kf2, + KBKeyFrame kf3) { + + // Copy KeyFrame information + keyFrame[0] = new KBKeyFrame(kf0); + keyFrame[1] = new KBKeyFrame(kf1); + keyFrame[2] = new KBKeyFrame(kf2); + keyFrame[3] = new KBKeyFrame(kf3); + + // if linear interpolation is requested then just set linear flag + // if spline interpolation is needed then compute spline coefficients + if (kf2.linear == 1) { + this.linear = 1; + } else { + this.linear = 0; + computeCommonCoefficients (kf0, kf1, kf2, kf3); + computeHermiteCoefficients (kf0, kf1, kf2, kf3); + } + + length = computeLength (1.0f); + // System.out.println ("Segment length = " + length); + + } + + // compute the common coefficients + private void computeCommonCoefficients (KBKeyFrame kf0, + KBKeyFrame kf1, + KBKeyFrame kf2, + KBKeyFrame kf3) { + + // variables for destination derivative + float one_minus_t_in = 1.0f - kf1.tension; + float one_minus_c_in = 1.0f - kf1.continuity; + float one_minus_b_in = 1.0f - kf1.bias; + float one_plus_c_in = 1.0f + kf1.continuity; + float one_plus_b_in = 1.0f + kf1.bias; + + // coefficients for the incoming Tangent + ddb = one_minus_t_in * one_minus_c_in * one_minus_b_in; + dda = one_minus_t_in * one_plus_c_in * one_plus_b_in; + + // variables for source derivative + float one_minus_t_out = 1.0f - kf2.tension; + float one_minus_c_out = 1.0f - kf2.continuity; + float one_minus_b_out = 1.0f - kf2.bias; + float one_plus_c_out = 1.0f + kf2.continuity; + float one_plus_b_out = 1.0f + kf2.bias; + + // coefficients for the outgoing Tangent + dsb = one_minus_t_in * one_plus_c_in * one_minus_b_in; + dsa = one_minus_t_in * one_minus_c_in * one_plus_b_in; + } + + + // compute the hermite interpolation basis coefficients + private void computeHermiteCoefficients (KBKeyFrame kf0, + KBKeyFrame kf1, + KBKeyFrame kf2, + KBKeyFrame kf3) { + + + Point3f deltaP = new Point3f(); + Point3f deltaS = new Point3f(); + float deltaH; + float deltaT; + float deltaB; + + // Find the difference in position and scale + deltaP.x = kf2.position.x - kf1.position.x; + deltaP.y = kf2.position.y - kf1.position.y; + deltaP.z = kf2.position.z - kf1.position.z; + + deltaS.x = kf2.scale.x - kf1.scale.x; + deltaS.y = kf2.scale.y - kf1.scale.y; + deltaS.z = kf2.scale.z - kf1.scale.z; + + // Find the difference in heading, pitch, and bank + deltaH = kf2.heading - kf1.heading; + deltaT = kf2.pitch - kf1.pitch; + deltaB = kf2.bank - kf1.bank; + + // Incoming Tangent + Point3f dd_pos = new Point3f(); + Point3f dd_scale = new Point3f(); + float dd_heading, dd_pitch, dd_bank; + + // If this is the first keyframe of the animation + if (kf0.knot == kf1.knot) { + + float ddab = 0.5f * (dda + ddb); + + // Position + dd_pos.x = ddab * deltaP.x; + dd_pos.y = ddab * deltaP.y; + dd_pos.z = ddab * deltaP.z; + + // Scale + dd_scale.x = ddab * deltaS.x; + dd_scale.y = ddab * deltaS.y; + dd_scale.z = ddab * deltaS.z; + + // Heading, Pitch and Bank + dd_heading = ddab * deltaH; + dd_pitch = ddab * deltaT; + dd_bank = ddab * deltaB; + + } else { + + float adj0 = (kf1.knot - kf0.knot)/(kf2.knot - kf0.knot); + + // Position + dd_pos.x = adj0 * + ((ddb * deltaP.x) + (dda * (kf1.position.x - kf0.position.x))); + dd_pos.y = adj0 * + ((ddb * deltaP.y) + (dda * (kf1.position.y - kf0.position.y))); + dd_pos.z = adj0 * + ((ddb * deltaP.z) + (dda * (kf1.position.z - kf0.position.z))); + + // Scale + dd_scale.x = adj0 * + ((ddb * deltaS.x) + (dda * (kf1.scale.x - kf0.scale.x))); + dd_scale.y = adj0 * + ((ddb * deltaS.y) + (dda * (kf1.scale.y - kf0.scale.y))); + dd_scale.z = adj0 * + ((ddb * deltaS.z) + (dda * (kf1.scale.z - kf0.scale.z))); + + // Heading, Pitch and Bank + dd_heading = adj0 * + ((ddb * deltaH) + (dda * (kf1.heading - kf0.heading))); + dd_pitch = adj0 * + ((ddb * deltaT) + (dda * (kf1.pitch - kf0.pitch))); + dd_bank = adj0 * + ((ddb * deltaB) + (dda * (kf1.bank - kf0.bank))); + } + + // Outgoing Tangent + Point3f ds_pos = new Point3f(); + Point3f ds_scale = new Point3f(); + float ds_heading, ds_pitch, ds_bank; + + // If this is the last keyframe of the animation + if (kf2.knot == kf3.knot) { + + float dsab = 0.5f * (dsa + dsb); + + // Position + ds_pos.x = dsab * deltaP.x; + ds_pos.y = dsab * deltaP.y; + ds_pos.z = dsab * deltaP.z; + + // Scale + ds_scale.x = dsab * deltaS.x; + ds_scale.y = dsab * deltaS.y; + ds_scale.z = dsab * deltaS.z; + + // Heading, Pitch and Bank + ds_heading = dsab * deltaH; + ds_pitch = dsab * deltaT; + ds_bank = dsab * deltaB; + + } else { + + float adj1 = (kf2.knot - kf1.knot)/(kf3.knot - kf1.knot); + + // Position + ds_pos.x = adj1 * + ((dsb * (kf3.position.x - kf2.position.x)) + (dsa * deltaP.x)); + ds_pos.y = adj1 * + ((dsb * (kf3.position.y - kf2.position.y)) + (dsa * deltaP.y)); + ds_pos.z = adj1 * + ((dsb * (kf3.position.z - kf2.position.z)) + (dsa * deltaP.z)); + + // Scale + ds_scale.x = adj1 * + ((dsb * (kf3.scale.x - kf2.scale.x)) + (dsa * deltaS.x)); + ds_scale.y = adj1 * + ((dsb * (kf3.scale.y - kf2.scale.y)) + (dsa * deltaS.y)); + ds_scale.z = adj1 * + ((dsb * (kf3.scale.z - kf2.scale.z)) + (dsa * deltaS.z)); + + // Heading, Pitch and Bank + ds_heading = adj1 * + ((dsb * (kf3.heading - kf2.heading)) + (dsa * deltaH)); + ds_pitch = adj1 * + ((dsb * (kf3.pitch - kf2.pitch)) + (dsa * deltaT)); + ds_bank = adj1 * + ((dsb * (kf3.bank - kf2.bank)) + (dsa * deltaB)); + } + + // Calculate the coefficients of the polynomial for position + c0 = new Point3f(); + c0.x = kf1.position.x; + c0.y = kf1.position.y; + c0.z = kf1.position.z; + + c1 = new Point3f(); + c1.x = dd_pos.x; + c1.y = dd_pos.y; + c1.z = dd_pos.z; + + c2 = new Point3f(); + c2.x = 3*deltaP.x - 2*dd_pos.x - ds_pos.x; + c2.y = 3*deltaP.y - 2*dd_pos.y - ds_pos.y; + c2.z = 3*deltaP.z - 2*dd_pos.z - ds_pos.z; + + c3 = new Point3f(); + c3.x = -2*deltaP.x + dd_pos.x + ds_pos.x; + c3.y = -2*deltaP.y + dd_pos.y + ds_pos.y; + c3.z = -2*deltaP.z + dd_pos.z + ds_pos.z; + + // Calculate the coefficients of the polynomial for scale + e0 = new Point3f(); + e0.x = kf1.scale.x; + e0.y = kf1.scale.y; + e0.z = kf1.scale.z; + + e1 = new Point3f(); + e1.x = dd_scale.x; + e1.y = dd_scale.y; + e1.z = dd_scale.z; + + e2 = new Point3f(); + e2.x = 3*deltaS.x - 2*dd_scale.x - ds_scale.x; + e2.y = 3*deltaS.y - 2*dd_scale.y - ds_scale.y; + e2.z = 3*deltaS.z - 2*dd_scale.z - ds_scale.z; + + e3 = new Point3f(); + e3.x = -2*deltaS.x + dd_scale.x + ds_scale.x; + e3.y = -2*deltaS.y + dd_scale.y + ds_scale.y; + e3.z = -2*deltaS.z + dd_scale.z + ds_scale.z; + + // Calculate the coefficients of the polynomial for heading, pitch + // and bank + h0 = kf1.heading; + p0 = kf1.pitch; + b0 = kf1.bank; + + h1 = dd_heading; + p1 = dd_pitch; + b1 = dd_bank; + + h2 = 3*deltaH - 2*dd_heading - ds_heading; + p2 = 3*deltaT - 2*dd_pitch - ds_pitch; + b2 = 3*deltaB - 2*dd_bank - ds_bank; + + h3 = -2*deltaH + dd_heading + ds_heading; + p3 = -2*deltaT + dd_pitch + ds_pitch; + b3 = -2*deltaB + dd_bank + ds_bank; + } + + + /** + * Computes the length of the curve at a given point between + * key frames. + * @param u specifies the point between keyframes where 0 <= u <= 1. + */ + + public float computeLength (float u) { + + float result = 0f; + + // if linear interpolation + if (linear == 1) { + result = u*keyFrame[2].position.distance(keyFrame[1].position); + } else { + // Need to transform domain [0,u] to [-1,1]. If 0 <= x <= u + // and -1 <= t <= 1, then x = u*(t+1)/2. + int degree = 5; + for (int i = 0; i < degree; i++) + result += (float)modCoeff[i]*computeSpeed(u*(float)modRoot[i]); + result *= u; + } + + return result; + } + + // Velocity along curve + private float computeSpeed (float u) { + Point3f v = new Point3f(); + + v.x = c1.x + u * (2 * c2.x + 3 * u * c3.x); + v.y = c1.y + u * (2 * c2.y + 3 * u * c3.y); + v.z = c1.z + u * (2 * c2.z + 3 * u * c3.z); + + return (float)(Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z)); + } + + + /** + * Computes the interpolated scale along the curve at a given point + * between key frames and returns a Point3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newScale returns the interpolated x,y,z scale value in a Point3f + */ + + public void getInterpolatedScale (float u, Point3f newScale) { + + // if linear interpolation + if (this.linear == 1) { + + newScale.x = keyFrame[1].scale.x + + ((keyFrame[2].scale.x - keyFrame[1].scale.x) * u); + newScale.y = keyFrame[1].scale.y + + ((keyFrame[2].scale.y - keyFrame[1].scale.y) * u); + newScale.z = keyFrame[1].scale.z + + ((keyFrame[2].scale.z - keyFrame[1].scale.z) * u); + + } else { + + newScale.x = e0.x + u * (e1.x + u * (e2.x + u * e3.x)); + newScale.y = e0.y + u * (e1.y + u * (e2.y + u * e3.y)); + newScale.z = e0.z + u * (e1.z + u * (e2.z + u * e3.z)); + + } + } + + + /** + * Computes the interpolated position along the curve at a given point + * between key frames and returns a Point3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newPos returns the interpolated x,y,z position in a Point3f + */ + + public void getInterpolatedPosition (float u, Point3f newPos) { + + // if linear interpolation + if (this.linear == 1) { + newPos.x = keyFrame[1].position.x + + ((keyFrame[2].position.x - keyFrame[1].position.x) * u); + newPos.y = keyFrame[1].position.y + + ((keyFrame[2].position.y - keyFrame[1].position.y) * u); + newPos.z = keyFrame[1].position.z + + ((keyFrame[2].position.z - keyFrame[1].position.z) * u); + } else { + + newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x)); + newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y)); + newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z)); + + } + } + + + /** + * Computes the interpolated position along the curve at a given point + * between key frames and returns a Vector3f with the interpolated + * x, y, and z scale components. This routine uses linear + * interpolation if the (i+1)th key frame's linear + * value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @param newPos returns the interpolated x,y,z position in a Vector3f. + */ + + public void getInterpolatedPositionVector (float u, Vector3f newPos) { + // if linear interpolation + if (this.linear == 1) { + newPos.x = keyFrame[1].position.x + + ((keyFrame[2].position.x - keyFrame[1].position.x) * u); + newPos.y = keyFrame[1].position.y + + ((keyFrame[2].position.y - keyFrame[1].position.y) * u); + newPos.z = keyFrame[1].position.z + + ((keyFrame[2].position.z - keyFrame[1].position.z) * u); + } else { + + newPos.x = c0.x + u * (c1.x + u * (c2.x + u * c3.x)); + newPos.y = c0.y + u * (c1.y + u * (c2.y + u * c3.y)); + newPos.z = c0.z + u * (c1.z + u * (c2.z + u * c3.z)); + + } + } + + /** + * Computes the interpolated heading along the curve at a given point + * between key frames and returns the interpolated value as a float + * This routine uses linear interpolation if the (i+1)th + * key frame's linear value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @return returns the interpolated heading value + */ + + public float getInterpolatedHeading (float u) { + + float newHeading; + + // if linear interpolation + if (this.linear == 1) { + + newHeading = keyFrame[1].heading + + ((keyFrame[2].heading - keyFrame[1].heading) * u); + } else { + + newHeading = h0 + u * (h1 + u * (h2 + u * h3)); + + } + + return newHeading; + } + + + /** + * Computes the interpolated pitch along the curve at a given point + * between key frames and returns the interpolated value as a float + * This routine uses linear interpolation if the (i+1)th + * key frame's linear value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @return returns the interpolated pitch value + */ + + public float getInterpolatedPitch (float u) { + + float newPitch; + + // if linear interpolation + if (this.linear == 1) { + + newPitch = keyFrame[1].pitch + + ((keyFrame[2].pitch - keyFrame[1].pitch) * u); + } else { + + newPitch = p0 + u * (p1 + u * (p2 + u * p3)); + + } + + return newPitch; + } + + /** + * Computes the interpolated bank along the curve at a given point + * between key frames and returns the interpolated value as a float + * This routine uses linear interpolation if the (i+1)th + * key frame's linear value is equal to 1. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @return returns the interpolated bank value + */ + + public float getInterpolatedBank (float u) { + + float newBank; + + // if linear interpolation + if (this.linear == 1) { + + newBank = keyFrame[1].bank + + ((keyFrame[2].bank - keyFrame[1].bank) * u); + } else { + + newBank = b0 + u * (b1 + u * (b2 + u * b3)); + + } + + return newBank; + } + + + /** + * Computes the ratio of the length of the spline from the ith + * key frame to the position specified by u to the length of the entire + * spline segment from the ith key frame to the (i+1) + * th key frame. When the (i+1)th key frame's linear + * value is equal to 1, this is meaninful otherwise it should return u. + * + * @param u specifies the point between keyframes where 0 <= u <= 1. + * @return the interpolated ratio + */ + + public float getInterpolatedValue (float u) { + return (computeLength(u)/this.length); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBKeyFrame.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBKeyFrame.java new file mode 100644 index 0000000..7c2bb33 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBKeyFrame.java @@ -0,0 +1,150 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class represents a Key Frame that can be used for Kochanek-Bartels + * (also called TCB or Tension-Continuity-Bias Splines) spline interpolation. + * + * @since Java3D 1.2 + */ +public class KBKeyFrame { + + // Position, Rotation and Scale + public Point3f position; + public float heading; + public float pitch; + public float bank; + public Point3f scale; + + // Tension, Continuity & Bias + public float tension; + public float continuity; + public float bias; + + // Sample Time + public float knot; + + // Interpolation type (linear = 0 -> spline interpolation) + public int linear; + + // default constructor + KBKeyFrame () { + tension = continuity = bias = 0.0f; + } + + public KBKeyFrame (KBKeyFrame kf) { + this(kf.knot, kf.linear, kf.position, kf.heading, kf.pitch, kf.bank, + kf.scale, kf.tension, kf.continuity, kf.bias); + + } + + + /** + * Creates a key frame using the given inputs. + * + * @param k knot value for this key frame + * @param l the linear flag (0 - Spline Interp, 1, Linear Interp + * @param pos the position at the key frame + * @param hd the heading value at the key frame + * @param pi the pitch value at the key frame + * @param bk the bank value at the key frame + * @param s the scales at the key frame + * @param t tension (-1.0 < t < 1.0) + * @param c continuity (-1.0 < c < 1.0) + * @param b bias (-1.0 < b < 1.0) + */ + public KBKeyFrame (float k, int l, Point3f pos, float hd, float pi, + float bk, Point3f s, float t, float c, float b) { + + knot = k; + linear = l; + position = new Point3f(pos); + heading = hd; + pitch = pi; + bank = bk; + scale = new Point3f(s); + + // Check for valid tension continuity and bias values + if (t < -1.0f || t > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBKeyFrame0")); + } + + if (b < -1.0f || b > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBKeyFrame1")); + } + + if (c < -1.0f || c > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBKeyFrame2")); + } + + // copy valid tension, continuity and bias values + tension = t; + continuity = c; + bias = b; + } + + /** + * Prints information comtained in this key frame + * @param tag string tag for identifying debug message + */ + public void debugPrint (String tag) { + System.out.println ("\n" + tag); + System.out.println (" knot = " + knot); + System.out.println (" linear = " + linear); + System.out.println (" position(x,y,z) = " + position.x + " " + + position.y + " " + position.z); + + System.out.println (" tension = " + tension); + System.out.println (" continuity = " + continuity); + System.out.println (" bias = " + bias); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBRotPosScaleSplinePathInterpolator.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBRotPosScaleSplinePathInterpolator.java new file mode 100644 index 0000000..e684396 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBRotPosScaleSplinePathInterpolator.java @@ -0,0 +1,313 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; + + +/** + * KBRotPosScaleSplinePathInterpolator; A rotation and position path + * interpolation behavior node using Kochanek-Bartels cubic splines + * (also known as TCB or Tension-Continuity-Bias Splines). + * + * @since Java3D 1.2 + */ + +/** + * KBRotPosScaleSplinePathInterpolator behavior. This class defines a + * behavior that varies the rotational, translational, and scale components + * of its target TransformGroup by using the Kochanek-Bartels cubic spline + * interpolation to interpolate among a series of key frames + * (using the value generated by the specified Alpha object). The + * interpolated position, orientation, and scale are used to generate + * a transform in the local coordinate system of this interpolator. + */ + +public class KBRotPosScaleSplinePathInterpolator + extends KBSplinePathInterpolator { + + private Transform3D rotation = new Transform3D(); + + private Matrix4d pitchMat = new Matrix4d(); // pitch matrix + private Matrix4d bankMat = new Matrix4d(); // bank matrix + private Matrix4d tMat = new Matrix4d(); // transformation matrix + private Matrix4d sMat = new Matrix4d(); // scale matrix + //Quat4f iQuat = new Quat4f(); // interpolated quaternion + private Vector3f iPos = new Vector3f(); // interpolated position + private Point3f iScale = new Point3f(); // interpolated scale + float iHeading, iPitch, iBank; // interpolated heading, + // pitch and bank + + KBCubicSplineCurve cubicSplineCurve = new KBCubicSplineCurve(); + KBCubicSplineSegment cubicSplineSegments[]; + int numSegments; + int currentSegmentIndex; + KBCubicSplineSegment currentSegment; + + // non-public, default constructor used by cloneNode + KBRotPosScaleSplinePathInterpolator() { + } + + /** + * Constructs a new KBRotPosScaleSplinePathInterpolator object that + * varies the rotation, translation, and scale of the target + * TransformGroup's transform. At least 2 key frames are required for + * this interpolator. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that specifies the local + * coordinate system in which this interpolator operates. + * @param keys an array of key frames that defien the motion path + */ + public KBRotPosScaleSplinePathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + KBKeyFrame keys[]) { + super(alpha,target, axisOfTransform, keys); + + // Create a spline curve using the derived key frames + cubicSplineCurve = new KBCubicSplineCurve(this.keyFrames); + numSegments = cubicSplineCurve.numSegments; + + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * TransformInterpolator.setTransformAxis(Transform3D) + */ + public void setAxisOfRotPosScale(Transform3D axisOfRotPosScale) { + setTransformAxis(axisOfRotPosScale); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * TransformInterpolator.getTransformAxis() + */ + public Transform3D getAxisOfRotPosScale() { + return getTransformAxis(); + } + + /** + * Set the key frame at the specified index to keyFrame + * @param index Index of the key frame to change + * @param keyFrame The new key frame + */ + public void setKeyFrame( int index, KBKeyFrame keyFrame ) { + super.setKeyFrame( index, keyFrame ); + + // TODO Optimize this + // Create a spline curve using the derived key frames + cubicSplineCurve = new KBCubicSplineCurve(this.keyFrames); + numSegments = cubicSplineCurve.numSegments; + } + + /** + * Set all the key frames + * @param keyFrame The new key frames + */ + public void setKeyFrames( KBKeyFrame[] keyFrame ) { + super.setKeyFrames( keyFrame ); + + // Create a spline curve using the derived key frames + cubicSplineCurve = new KBCubicSplineCurve(this.keyFrames); + numSegments = cubicSplineCurve.numSegments; + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + // compute the current value of u from alpha and the + // determine lower and upper knot points + computePathInterpolation( alphaValue ); + + // Determine the segment within which we will be interpolating + currentSegmentIndex = this.lowerKnot - 1; + + // if we are at the start of the curve + if (currentSegmentIndex == 0 && currentU == 0f) { + + iHeading = keyFrames[1].heading; + iPitch = keyFrames[1].pitch; + iBank = keyFrames[1].bank; + iPos.set(keyFrames[1].position); + iScale.set(keyFrames[1].scale); + + // if we are at the end of the curve + } else if (currentSegmentIndex == (numSegments-1) && + currentU == 1.0) { + + iHeading = keyFrames[upperKnot].heading; + iPitch = keyFrames[upperKnot].pitch; + iBank = keyFrames[upperKnot].bank; + iPos.set(keyFrames[upperKnot].position); + iScale.set(keyFrames[upperKnot].scale); + + // if we are somewhere in between the curve + } else { + + // Get a reference to the current spline segment i.e. the + // one bounded by lowerKnot and upperKnot + currentSegment + = cubicSplineCurve.getSegment(currentSegmentIndex); + + // interpolate quaternions + iHeading = currentSegment.getInterpolatedHeading (currentU); + iPitch = currentSegment.getInterpolatedPitch (currentU); + iBank = currentSegment.getInterpolatedBank (currentU); + + // interpolate position + currentSegment.getInterpolatedPositionVector(currentU,iPos); + + // interpolate position + currentSegment.getInterpolatedScale(currentU,iScale); + + } + + // Generate a transformation matrix in tMat using interpolated + // heading, pitch and bank + pitchMat.setIdentity(); + pitchMat.rotX(-iPitch); + bankMat.setIdentity(); + bankMat.rotZ(iBank); + tMat.setIdentity(); + tMat.rotY(-iHeading); + tMat.mul(pitchMat); + tMat.mul(bankMat); + + // TODO: Handle Non-Uniform scale + // Currently this interpolator does not handle non uniform scale + // We cheat by just taking the x scale component + + // Scale the transformation matrix + sMat.set((double)iScale.x); + tMat.mul(sMat); + + // Set the translation components. + tMat.m03 = iPos.x; + tMat.m13 = iPos.y; + tMat.m23 = iPos.z; + rotation.set(tMat); + + // construct a Transform3D from: axis * rotation * axisInverse + transform.mul(axis, rotation); + transform.mul(transform, axisInverse); + } + + /** + * Copies KBRotPosScaleSplinePathInterpolator information from + * originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method.

+ * + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + KBRotPosScaleSplinePathInterpolator spline = + new KBRotPosScaleSplinePathInterpolator(); + + spline.duplicateNode(this, forceDuplicate); + return spline; + } + + /** + * Copies KBRotPosScaleSplinePathInterpolator information from + * originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method.

+ * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + + KBRotPosScaleSplinePathInterpolator interpolator = + (KBRotPosScaleSplinePathInterpolator)originalNode; + setAxisOfRotPosScale(interpolator.axis); + target = interpolator.target; + cubicSplineCurve = new KBCubicSplineCurve(interpolator.keyFrames); + numSegments = cubicSplineCurve.numSegments; + } +} + + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBSplinePathInterpolator.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBSplinePathInterpolator.java new file mode 100644 index 0000000..d67c5a9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/KBSplinePathInterpolator.java @@ -0,0 +1,301 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * KBSplinePathInterpolator behavior. This class defines the base class for + * all Kochanek-Bartels (also known as TCB or Tension-Continuity-Bias) + * Spline Path Interpolators. + * + * @since Java3D 1.2 + */ + +public abstract class KBSplinePathInterpolator extends TransformInterpolator { + + private int keysLength; + /** + * An array of KBKeyFrame for interpolator + */ + protected KBKeyFrame[] keyFrames; + + /** + * This value is the distance between knots + * value which can be used in further calculations by the subclass. + */ + protected float currentU; + + /** + * The lower knot + */ + protected int lowerKnot; + /** + * The upper knot + */ + protected int upperKnot; + + /** + * Constructs a KBSplinePathInterpolator node with a null alpha value and + * a null target of TransformGroup + * + * @since Java 3D 1.3 + */ + KBSplinePathInterpolator() { + } + + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * KBSplinePathInterpolator(Alpha, TransformGroup, TCBKeyFrame[]) + */ + public KBSplinePathInterpolator(Alpha alpha, KBKeyFrame keys[]) { + this(alpha, null, keys); + } + + /** + * Constructs a new KBSplinePathInterpolator object that interpolates + * between keyframes with specified alpha, target and an default + * axisOfTranform set to identity. + * It takes at least two key frames. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. Once this constructor has all the valid key frames + * it creates its own list of key fames that duplicates the first key frame + * at the beginning of the list and the last key frame at the end of the + * list. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param keys an array of KBKeyFrame. Requires at least two key frames. + * + * @since Java 3D 1.3 + */ + public KBSplinePathInterpolator(Alpha alpha, TransformGroup target, KBKeyFrame keys[]) { + super(alpha, target); + processKeyFrames( keys ); + } + + /** + * Constructs a new KBSplinePathInterpolator object that interpolates + * between keyframes with specified alpha, target and axisOfTransform. + * It takes at least two key frames. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. Once this constructor has all the valid key frames + * it creates its own list of key fames that duplicates the first key frame + * at the beginning of the list and the last key frame at the end of the + * list. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that defines the local coordinate + * @param keys an array of KBKeyFrame. Requires at least two key frames + * + * @since Java 3D 1.3 + */ + public KBSplinePathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + KBKeyFrame keys[]) { + super(alpha, target, axisOfTransform); + processKeyFrames( keys ); + } + + /** + * Process the new array of key frames + */ + private void processKeyFrames( KBKeyFrame[] keys ) { + + // Make sure that we have at least two key frames + keysLength = keys.length; + if (keysLength < 2) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBSplinePathInterpolator0")); + + } + + // Make sure that the first key frame's knot is equal to 0.0 + if (keys[0].knot < -0.0001 || keys[0].knot > 0.0001) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBSplinePathInterpolator1")); + } + + // Make sure that the last key frames knot is equal to 1.0 + if (keys[keysLength-1].knot -1.0 < -0.0001 || keys[keysLength-1].knot -1.0 > 0.0001) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBSplinePathInterpolator2")); + } + + // Make sure that all the knots are in sequence + for (int i = 0; i < keysLength; i++) { + if (i>0 && keys[i].knot < keys[i-1].knot) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("KBSplinePathInterpolator3")); + } + } + + // Make space for a leading and trailing key frame in addition to + // the keys passed in + keyFrames = new KBKeyFrame[keysLength+2]; + keyFrames[0] = new KBKeyFrame(); + keyFrames[0] = keys[0]; + for (int i = 1; i < keysLength+1; i++) { + keyFrames[i] = keys[i-1]; + } + keyFrames[keysLength+1] = new KBKeyFrame(); + keyFrames[keysLength+1] = keys[keysLength-1]; + + // Make key frame length reflect the 2 added key frames + keysLength += 2; + } + + /** + * This method retrieves the length of the key frame array. + * @return the number of key frames + */ + public int getArrayLength(){ + return keysLength-2; + } + + /** + * This method retrieves the key frame at the specified index. + * @param index the index of the key frame requested + * @return the key frame at the associated index + */ + public KBKeyFrame getKeyFrame (int index) { + + // We add 1 to index because we have added a leading keyFrame + return this.keyFrames[index+1]; + } + + /** + * Set the key frame at the specified index to keyFrame + * @param index Index of the key frame to change + * @param keyFrame The new key frame + * @since Java 3D 1.3 + */ + public void setKeyFrame( int index, KBKeyFrame keyFrame ) { + this.keyFrames[index+1] = keyFrame; + } + + /** + * Set allthe key frames + * @param keyFrames The new key frame + * @since Java 3D 1.3 + */ + public void setKeyFrames( KBKeyFrame[] keyFrames ) { + processKeyFrames( keyFrames ); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * computePathInterpolation(float) + */ + protected void computePathInterpolation() { + computePathInterpolation(this.getAlpha().value()); + } + + /** + * This method computes the bounding knot indices and interpolation value + * "CurrentU" given the current value of the knots[] array and the + * specified alpha value + * @param alphaValue alpha value between 0.0 and 1.0 + * + * @since Java 3D 1.3 + */ + protected void computePathInterpolation( float alphaValue ) { + + // skip knots till we find the two we fall between + int i = 1; + int len = keysLength - 2; + while ((alphaValue > keyFrames[i].knot) && (i < len)) { + i++; + } + + if (i == 1) { + currentU = 0f; + lowerKnot = 1; + upperKnot = 2; + } else { + currentU = (alphaValue - keyFrames[i-1].knot)/ + (keyFrames[i].knot - keyFrames[i-1].knot); + lowerKnot = i-1; + upperKnot = i; + } + } + + /** + * Copies all KBSplinePathInterpolator information from + * originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method.

+ * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + KBSplinePathInterpolator originalSpline = + (KBSplinePathInterpolator) originalNode; + setAlpha(originalSpline.getAlpha()); + keysLength = originalSpline.keysLength; + keyFrames = new KBKeyFrame[keysLength]; + System.arraycopy(originalSpline.keyFrames, 0, + keyFrames, 0, keysLength); + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/RotPosScaleTCBSplinePathInterpolator.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/RotPosScaleTCBSplinePathInterpolator.java new file mode 100644 index 0000000..3367ae3 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/RotPosScaleTCBSplinePathInterpolator.java @@ -0,0 +1,247 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; + + +/** + * RotPosScaleTCBSplinePathInterpolator behavior. This class defines a + * behavior that varies the rotational, translational, and scale components + * of its target TransformGroup by using the Kochanek-Bartels cubic spline + * interpolation to interpolate among a series of key frames + * (using the value generated by the specified Alpha object). The + * interpolated position, orientation, and scale are used to generate + * a transform in the local coordinate system of this interpolator. + * + * @since Java3D 1.1 + */ + +public class RotPosScaleTCBSplinePathInterpolator +extends TCBSplinePathInterpolator { + + private Transform3D rotation = new Transform3D(); + private Matrix4d tMat = new Matrix4d(); + private Matrix4d sMat = new Matrix4d(); + private Quat4f iQuat = new Quat4f(); // interpolated quaternion + private Vector3f iPos = new Vector3f(); // interpolated position + private Point3f iScale = new Point3f(); // interpolated scale + + CubicSplineCurve cubicSplineCurve = new CubicSplineCurve(); + CubicSplineSegment cubicSplineSegments[]; + int numSegments; + int currentSegmentIndex; + CubicSplineSegment currentSegment; + + // non-public, default constructor used by cloneNode + RotPosScaleTCBSplinePathInterpolator() { + } + + /** + * Constructs a new RotPosScaleTCBSplinePathInterpolator object that + * varies the rotation, translation, and scale of the target + * TransformGroup's transform. At least 2 key frames are required for + * this interpolator. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that specifies the local + * coordinate system in which this interpolator operates. + * @param keys an array of key frames that defien the motion path + */ + public RotPosScaleTCBSplinePathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + TCBKeyFrame keys[]) { + super(alpha, target, axisOfTransform, keys); + // Create a spline curve using the derived key frames + cubicSplineCurve = new CubicSplineCurve(this.keyFrames); + numSegments = cubicSplineCurve.numSegments; + + } + + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * TransformInterpolator.setTransformAxis(Transform3D) + */ + public void setAxisOfRotPosScale(Transform3D axisOfRotPosScale) { + setTransformAxis(axisOfRotPosScale); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * TransformInterpolator.getTransformAxis() + */ + public Transform3D getAxisOfRotPosScale() { + return getTransformAxis(); + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + + // compute the current value of u from alpha and the + // determine lower and upper knot points + computePathInterpolation(alphaValue); + + // Determine the segment within which we will be interpolating + currentSegmentIndex = this.lowerKnot - 1; + + // if we are at the start of the curve + if (currentSegmentIndex == 0 && currentU == 0f) { + + iQuat.set(keyFrames[1].quat); + iPos.set(keyFrames[1].position); + iScale.set(keyFrames[1].scale); + + // if we are at the end of the curve + } else if (currentSegmentIndex == (numSegments-1) && + currentU == 1.0) { + + iQuat.set(keyFrames[upperKnot].quat); + iPos.set(keyFrames[upperKnot].position); + iScale.set(keyFrames[upperKnot].scale); + + // if we are somewhere in between the curve + } else { + + // Get a reference to the current spline segment i.e. the + // one bounded by lowerKnot and upperKnot + currentSegment + = cubicSplineCurve.getSegment(currentSegmentIndex); + + // interpolate quaternions + currentSegment.getInterpolatedQuaternion(currentU,iQuat); + + // interpolate position + currentSegment.getInterpolatedPositionVector(currentU,iPos); + + // interpolate position + currentSegment.getInterpolatedScale(currentU,iScale); + + } + + // Alway normalize the quaternion + iQuat.normalize(); + tMat.set(iQuat); + + // Set the translation components. + tMat.m03 = iPos.x; + tMat.m13 = iPos.y; + tMat.m23 = iPos.z; + rotation.set(tMat); + + // construct a Transform3D from: axis * rotation * axisInverse + transform.mul(axis, rotation); + transform.setScale(new Vector3d(iScale)); + transform.mul(transform, axisInverse); + + } + + /** + * Used to create a new instance of the node. This routine is called + * by cloneTree to duplicate the current node. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + RotPosScaleTCBSplinePathInterpolator spline = + new RotPosScaleTCBSplinePathInterpolator(); + spline.duplicateNode(this, forceDuplicate); + return spline; + } + + /** + * Copies RotPosScaleTCBSplinePathInterpolator information from + * originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method.

+ * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + RotPosScaleTCBSplinePathInterpolator interpolator = + (RotPosScaleTCBSplinePathInterpolator)originalNode; + + cubicSplineCurve = new CubicSplineCurve(interpolator.keyFrames); + numSegments = cubicSplineCurve.numSegments; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBKeyFrame.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBKeyFrame.java new file mode 100644 index 0000000..39c7143 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBKeyFrame.java @@ -0,0 +1,144 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class represents a Key Frame that can be used for Kochanek-Bartels + * (TCB) spline interpolation. + * + * @since Java3D 1.1 + */ + +public class TCBKeyFrame { + + // Position, Rotation and Scale + public Point3f position; + public Quat4f quat; + public Point3f scale; + + // Tension, Continuity & Bias + public float tension; + public float continuity; + public float bias; + + // Sample Time + public float knot; + + // Interpolation type (linear = 0 -> spline interpolation) + public int linear; + + // default constructor + TCBKeyFrame () { + tension = continuity = bias = 0.0f; + } + + public TCBKeyFrame (TCBKeyFrame kf) { + this(kf.knot, kf.linear, kf.position, kf.quat, kf.scale, + kf.tension, kf.continuity, kf.bias); + + } + + /** + * Creates a key frame using the given inputs. + * + * @param k knot value for this key frame + * @param l the linear flag (0 - Spline Interp, 1, Linear Interp + * @param pos the position at the key frame + * @param q the rotations at the key frame + * @param s the scales at the key frame + * @param t tension (-1.0 < t < 1.0) + * @param c continuity (-1.0 < c < 1.0) + * @param b bias (-1.0 < b < 1.0) + */ + public TCBKeyFrame (float k, int l, Point3f pos, Quat4f q, Point3f s, + float t, float c, float b) { + + knot = k; + linear = l; + position = new Point3f(pos); + quat = new Quat4f(q); + scale = new Point3f(s); + + // Check for valid tension continuity and bias values + if (t < -1.0f || t > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBKeyFrame0")); + } + + if (b < -1.0f || b > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBKeyFrame1")); + } + + if (c < -1.0f || c > 1.0f) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBKeyFrame2")); + } + + // copy valid tension, continuity and bias values + tension = t; + continuity = c; + bias = b; + } + + /** + * Prints information comtained in this key frame + * @param tag string tag for identifying debug message + */ + public void debugPrint (String tag) { + System.out.println ("\n" + tag); + System.out.println (" knot = " + knot); + System.out.println (" linear = " + linear); + System.out.println (" position(x,y,z) = " + position.x + " " + + position.y + " " + position.z); + + System.out.println (" tension = " + tension); + System.out.println (" continuity = " + continuity); + System.out.println (" bias = " + bias); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBSplinePathInterpolator.java b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBSplinePathInterpolator.java new file mode 100644 index 0000000..adafb3b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/interpolators/TCBSplinePathInterpolator.java @@ -0,0 +1,278 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.interpolators; + +import javax.media.j3d.*; +import java.util.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * TCBSplinePathInterpolator behavior. This class defines the base class for + * all TCB (Kochanek-Bartels) Spline Path Interpolators. + * + * @since Java3D 1.1 + */ + +public abstract class TCBSplinePathInterpolator extends TransformInterpolator { + + private int keysLength; + + /** + * An array of KBKeyFrame for interpolator + */ + protected TCBKeyFrame[] keyFrames; + + /** + * This value is the distance between knots + * value which can be used in further calculations by the subclass. + */ + protected float currentU; + + /** + * The lower knot + */ + protected int lowerKnot; + + /** + * The upper knot + */ + protected int upperKnot; + + /** + * Constructs a TCBSplinePathInterpolator node with a null alpha value and + * a null target of TransformGroup + * + * @since Java 3D 1.3 + */ + + TCBSplinePathInterpolator() { + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * TCBSplinePathInterpolator(Alpha, TransformGroup, TCBKeyFrame[]) + */ + public TCBSplinePathInterpolator(Alpha alpha, TCBKeyFrame keys[]) { + this(alpha, null, keys); + } + + /** + * Constructs a new TCBSplinePathInterpolator object that interpolates + * between keyframes with specified alpha, target and default axisOfTransform + * set to identity. It takes at least two key frames. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. Once this constructor has all the valid key frames + * it creates its own list of key fames that duplicates the first key frame + * at the beginning of the list and the last key frame at the end of the + * list. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node effected by this TCBSplinePathInterpolator + * @param keys an array of TCBKeyFrame. Requires at least two key frames. + * + * @since Java 3D 1.3 + */ + public TCBSplinePathInterpolator(Alpha alpha, TransformGroup target, TCBKeyFrame keys[]) { + super(alpha, target); + processKeyFrames(keys); + } + + /** + * Constructs a new TCBSplinePathInterpolator object that interpolates + * between keyframes with specified alpha, target and axisOfTransform. + * It takes at least two key frames. The first key + * frame's knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate key frame with index k must have a + * knot value strictly greater than the knot value of a key frame with + * index less than k. Once this constructor has all the valid key frames + * it creates its own list of key fames that duplicates the first key frame + * at the beginning of the list and the last key frame at the end of the + * list. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node effected by this TCBSplinePathInterpolator + * @param axisOfTransform the transform that defines the local coordinate + * @param keys an array of TCBKeyFrame. Requires at least two key frames. + * + * @since Java 3D 1.3 + */ + public TCBSplinePathInterpolator(Alpha alpha, TransformGroup target, Transform3D axisOfTransform, + TCBKeyFrame keys[]) { + super(alpha, target, axisOfTransform); + processKeyFrames(keys); + } + + /** + * Process the new array of key frames + */ + private void processKeyFrames( TCBKeyFrame keys[] ){ + + // Make sure that we have at least two key frames + keysLength = keys.length; + if (keysLength < 2) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBSplinePathInterpolator0")); + + } + + // Make sure that the first key frame's knot is equal to 0.0 + if (keys[0].knot < -0.0001 || keys[0].knot > 0.0001) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBSplinePathInterpolator1")); + } + + // Make sure that the last key frames knot is equal to 1.0 + if (keys[keysLength-1].knot -1.0 < -0.0001 || keys[keysLength-1].knot -1.0 > 0.0001) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBSplinePathInterpolator2")); + } + + // Make sure that all the knots are in sequence + for (int i = 0; i < keysLength; i++) { + if (i>0 && keys[i].knot < keys[i-1].knot) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("TCBSplinePathInterpolator3")); + } + } + + // Make space for a leading and trailing key frame in addition to + // the keys passed in + keyFrames = new TCBKeyFrame[keysLength+2]; + keyFrames[0] = new TCBKeyFrame(); + keyFrames[0] = keys[0]; + for (int i = 1; i < keysLength+1; i++) { + keyFrames[i] = keys[i-1]; + } + keyFrames[keysLength+1] = new TCBKeyFrame(); + keyFrames[keysLength+1] = keys[keysLength-1]; + + // Make key frame length reflect the 2 added key frames + keysLength += 2; + } + + /** + * This method retrieves the length of the key frame array. + * @return the number of key frames + */ + public int getArrayLength(){ + return keysLength-2; + } + + /** + * This method retrieves the key frame at the specified index. + * @param index the index of the key frame requested + * @return the key frame at the associated index + */ + public TCBKeyFrame getKeyFrame (int index) { + + // We add 1 to index because we have added a leading keyFrame + return this.keyFrames[index+1]; + } + + /** + * This method computes the bounding knot indices and interpolation value + * "CurrentU" given the specified value of alpha and the knots[] array. + * @param alphaValue alpha value between 0.0 and 1.0 + * + * @since Java 3D 1.3 + */ + protected void computePathInterpolation(float alphaValue) { + + // skip knots till we find the two we fall between + int i = 1; + int len = keysLength - 2; + while ((alphaValue > keyFrames[i].knot) && (i < len)) { + i++; + } + + if (i == 1) { + currentU = 0f; + lowerKnot = 1; + upperKnot = 2; + } else { + currentU = (alphaValue - keyFrames[i-1].knot)/ + (keyFrames[i].knot - keyFrames[i-1].knot); + lowerKnot = i-1; + upperKnot = i; + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * computePathInterpolation(float) + */ + protected void computePathInterpolation() { + float value = (this.getAlpha()).value(); + computePathInterpolation(value); + } + + /** + * Copies all TCBSplinePathInterpolator information from + * originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method.

+ * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + TCBSplinePathInterpolator originalSpline = (TCBSplinePathInterpolator) originalNode; + setAlpha(originalSpline.getAlpha()); + keysLength = originalSpline.keysLength; + keyFrames = new TCBKeyFrame[keysLength]; + System.arraycopy(originalSpline.keyFrames, 0, + keyFrames, 0, keysLength); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigator.java b/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigator.java new file mode 100644 index 0000000..d9b0f6c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigator.java @@ -0,0 +1,499 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.keyboard; + +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.awt.event.*; + +/** + * This is the KeyNavigator class. It accumulates AWT key events (key + * press and key release) and computes a new transform based on the + * accumulated events and elapsed time. + */ +public class KeyNavigator { + + private Vector3d navVec; + private long time; + + private Vector3d fwdAcc; + private Vector3d bwdAcc; + private Vector3d leftAcc; + private Vector3d rightAcc; + private Vector3d upAcc; + private Vector3d downAcc; + + private Vector3d fwdDrag; + private Vector3d bwdDrag; + private Vector3d leftDrag; + private Vector3d rightDrag; + private Vector3d upDrag; + private Vector3d downDrag; + + private double fwdVMax; + private double bwdVMax; + private double leftVMax; + private double rightVMax; + private double upVMax; + private double downVMax; + + private float leftRotAngle; + private float rightRotAngle; + private float upRotAngle; + private float downRotAngle; + + private double mmx; + + private Vector3d a = new Vector3d(); + private Vector3d dv = new Vector3d(); + private Point3d dp = new Point3d(); + private Quat4d udQuat = new Quat4d(); + private Quat4d lrQuat = new Quat4d(); + private Vector3d vpPos = new Vector3d(); + private double vpScale; + private Quat4d vpQuat = new Quat4d(); + private Matrix4d vpMatrix = new Matrix4d(); + private Transform3D vpTrans = new Transform3D(); + private Matrix4d mat = new Matrix4d(); + private Vector3d nda = new Vector3d(); + private Vector3d temp = new Vector3d(); + private Transform3D nominal = new Transform3D(); + private TransformGroup targetTG; + + private static final int UP_ARROW = (1<<0); + private static final int DOWN_ARROW = (1<<1); + private static final int LEFT_ARROW = (1<<2); + private static final int RIGHT_ARROW = (1<<3); + private static final int PLUS_SIGN = (1<<4); + private static final int MINUS_SIGN = (1<<5); + private static final int PAGE_UP = (1<<6); + private static final int PAGE_DOWN = (1<<7); + private static final int HOME_DIR = (1<<8); + private static final int HOME_NOMINAL = (1<<9); + + private static final int SHIFT = (1<<10); + private static final int ALT = (1<<11); + private static final int META = (1<<12); + + private static final int KEY_UP = (1<<13); + private static final int KEY_DOWN = (1<<14); + + private int key_state = 0; + private int modifier_key_state = 0; + + + /** + * Constructs a new key navigator object that operates on the specified + * transform group. All parameters are set to their default, idle state. + * @param targetTG the target transform group + */ + public KeyNavigator(TransformGroup targetTG) { + this.targetTG = targetTG; + targetTG.getTransform(nominal); + + mmx = 128.0; + navVec = new Vector3d(0.0,0.0,0.0); + + fwdAcc = new Vector3d( 0.0, 0.0,-mmx); + bwdAcc = new Vector3d( 0.0, 0.0, mmx); + leftAcc = new Vector3d(-mmx, 0.0, 0.0); + rightAcc = new Vector3d( mmx, 0.0, 0.0); + upAcc = new Vector3d( 0.0, mmx, 0.0); + downAcc = new Vector3d( 0.0,-mmx, 0.0); + + fwdDrag = new Vector3d( 0.0, 0.0, mmx); + bwdDrag = new Vector3d( 0.0, 0.0,-mmx); + leftDrag = new Vector3d( mmx, 0.0, 0.0); + rightDrag = new Vector3d(-mmx, 0.0, 0.0); + upDrag = new Vector3d( 0.0,-mmx, 0.0); + downDrag = new Vector3d( 0.0, mmx, 0.0); + + fwdVMax = -mmx; + bwdVMax = mmx; + leftVMax = -mmx; + rightVMax = mmx; + upVMax = mmx; + downVMax = -mmx; + + leftRotAngle = (float) (-Math.PI*2.0/3.0); + rightRotAngle = (float) (Math.PI*2.0/3.0); + upRotAngle = (float) (Math.PI*2.0/3.0); + downRotAngle = (float) (-Math.PI*2.0/3.0); + + // Create Timer here. + time = System.currentTimeMillis(); + + } + + + private long getDeltaTime() { + long newTime = System.currentTimeMillis(); + long deltaTime = newTime - time; + time = newTime; + if (deltaTime > 2000) return 0; + else return deltaTime; + } + + /* Generate a quaterion as a rotation of radians av about 0/x 1/y 2/z axis */ + private void genRotQuat(double av, int axis, Quat4d q) { + double b; + + q.x = q.y = q.z = 0.0; + q.w = Math.cos(av/2.0); + + b = 1.0 - q.w*q.w; + + if (b > 0.0) + b = Math.sqrt(b); + else + return; + + if (av < 0.0) + b = -b; + if (axis == 0) + q.x = b; + else if (axis == 1) + q.y = b; + else + q.z = b; + + } + + private void accKeyAdd(Vector3d a, Vector3d da, Vector3d drag, double scaleVel) { + + /* Scaling of acceleration due to modification keys */ + nda.scale(scaleVel, da); + /* Addition of sufficent acceleration to counteract drag */ + nda.sub(drag); + + /* Summing into overall acceleration */ + a.add(nda); + + } + + + /** + * Computes a new transform for the next frame based on + * the current transform, accumulated keyboard inputs, and + * elapsed time. This new transform is written into the target + * transform group. + * This method should be called once per frame. + */ + public void integrateTransformChanges() { + double scaleVel, scaleRot, scaleScale, pre; + double udAng, lrAng, r; + + // Get the current View Platform transform into a transform3D object. + targetTG.getTransform(vpTrans); + // Extract the position, quaterion, and scale from the transform3D. + vpScale = vpTrans.get(vpQuat, vpPos); + + + double deltaTime = (double) getDeltaTime(); + deltaTime *= 0.001; + + /* Calculate scale due to modification keys */ + if ((modifier_key_state & SHIFT) != 0 && + (modifier_key_state & META) == 0) { + scaleVel = 3.0; scaleRot = 2.0; scaleScale = 4.0; + } + else if ((modifier_key_state & SHIFT) == 0 && + (modifier_key_state & META) != 0) { + scaleVel = 0.1; scaleRot = 0.1; scaleScale = 0.1; + } + else if ((modifier_key_state & SHIFT) != 0 && + (modifier_key_state & META) != 0) { + scaleVel = 0.3; scaleRot = 0.5; scaleScale = 0.1; + } + else { + scaleRot = scaleVel = 1.0; scaleScale = 4.0; + } + + /* + * Processing of rectiliear motion keys. + */ + + a.x = a.y = a.z = 0.0; /* acceleration initially 0 */ + + /* Acceleration due to keys being down */ + if ((key_state & UP_ARROW) != 0 && (key_state & DOWN_ARROW) == 0) + accKeyAdd(a, fwdAcc, fwdDrag, scaleVel); + else + if ((key_state & UP_ARROW) == 0 && (key_state & DOWN_ARROW) != 0) + accKeyAdd(a, bwdAcc, bwdDrag, scaleVel); + + if (((modifier_key_state & ALT) != 0) && + (key_state & LEFT_ARROW) != 0 && (key_state & RIGHT_ARROW) == 0) { + accKeyAdd(a, leftAcc, leftDrag, scaleVel); + } else + if (((modifier_key_state & ALT) != 0) && + (key_state & LEFT_ARROW) == 0 && (key_state & RIGHT_ARROW) != 0) + accKeyAdd(a, rightAcc, rightDrag, scaleVel); + + if (((modifier_key_state & ALT) != 0) && + (key_state & PAGE_UP) != 0 && (key_state & PAGE_DOWN) == 0) + accKeyAdd(a, upAcc, upDrag, scaleVel); + else + if (((modifier_key_state & ALT) != 0) && + (key_state & PAGE_UP) == 0 && (key_state & PAGE_DOWN) != 0) + accKeyAdd(a, downAcc, downDrag, scaleVel); + + + /* + * Drag due to new or existing motion + */ + pre = navVec.z + a.z * deltaTime; + if (pre < 0.0) { + if (pre + fwdDrag.z * deltaTime < 0.0) + a.add(fwdDrag); + else + a.z -= pre/deltaTime; + } else if (pre > 0.0) { + if (pre + bwdDrag.z * deltaTime > 0.0) + a.add(bwdDrag); + else + a.z -= pre/deltaTime; + } + + pre = navVec.x + a.x * deltaTime; + if (pre < 0.0) { + if (pre + leftDrag.x * deltaTime < 0.0) + a.add(leftDrag); + else + a.x -= pre/deltaTime; + } else if (pre > 0.0) { + if (pre + rightDrag.x * deltaTime > 0.0) + a.add(rightDrag); + else + a.x -= pre/deltaTime; + } + + pre = navVec.y + a.y * deltaTime; + if (pre < 0.0) { + if (pre + downDrag.y * deltaTime < 0.0) + a.add(downDrag); + else + a.y -= pre/deltaTime; + } else if (pre > 0.0) { + if (pre + upDrag.y * deltaTime > 0.0) + a.add(upDrag); + else + a.y -= pre/deltaTime; + } + + /* Integration of acceleration to velocity */ + dv.scale(deltaTime, a); + navVec.add(dv); + + /* Speed limits */ + if (navVec.z < scaleVel * fwdVMax) navVec.z = scaleVel * fwdVMax; + if (navVec.z > scaleVel * bwdVMax) navVec.z = scaleVel * bwdVMax; + if (navVec.x < scaleVel * leftVMax) navVec.x = scaleVel * leftVMax; + if (navVec.x > scaleVel * rightVMax) navVec.x = scaleVel* rightVMax; + if (navVec.y > scaleVel * upVMax) navVec.y = scaleVel * upVMax; + if (navVec.y < scaleVel * downVMax) navVec.y = scaleVel * downVMax; + + /* Integration of velocity to distance */ + dp.scale(deltaTime, navVec); + + /* Scale our motion to the current avatar scale */ + // 1.0 eventually needs to be a more complex value (see hs). + // r = workplace_coexistence_to_vworld_ori.scale/ + // one_to_one_coexistence_to_vworld_ori.scale; + r = vpScale/1.0; + dp.scale(r, dp); + + /* + * Processing of rotation motion keys. + */ + udAng = lrAng = 0.0; + + /* Rotation due to keys being down */ + if (((modifier_key_state & ALT) == 0) && + (key_state & LEFT_ARROW) != 0 && (key_state & RIGHT_ARROW) == 0) + lrAng = (double) leftRotAngle; + else if (((modifier_key_state & ALT) == 0) && + (key_state & LEFT_ARROW) == 0 && (key_state & RIGHT_ARROW) != 0) + lrAng = (double) rightRotAngle; + + if (((modifier_key_state & ALT) == 0) && + (key_state & PAGE_UP) != 0 && (key_state & PAGE_DOWN) == 0) + udAng = (double) upRotAngle; + else if (((modifier_key_state & ALT) == 0) && + (key_state & PAGE_UP) == 0 && (key_state & PAGE_DOWN) != 0) + udAng = (double) downRotAngle; + + lrAng *= scaleRot; + udAng *= scaleRot; + + /* Scaling of angle change to delta time */ + lrAng *= deltaTime; + udAng *= deltaTime; + + + /* Addition to existing orientation */ + // vr_quat_inverse(&workplace_coexistence_to_vworld_ori.quat, &vpQuat); + // vpQuat gotten at top of method. + vpQuat.inverse(); + + if(lrAng != 0.0) { + genRotQuat(lrAng, 1, lrQuat); + vpQuat.mul(lrQuat, vpQuat); + } + + if(udAng != 0.0) { + genRotQuat(udAng, 0, udQuat); + vpQuat.mul(udQuat, vpQuat); + } + + /* Rotation of distance vector */ + vpQuat.inverse(); + vpQuat.normalize(); /* Improvment over HoloSketch */ + mat.set(vpQuat); + mat.transform(dp); + + /* Processing of scale */ + if ((key_state & PLUS_SIGN) != 0) { + vpScale *= (1.0 + (scaleScale*deltaTime)); + if (vpScale > 10e+14) vpScale = 1.0; + } else if ((key_state & MINUS_SIGN) != 0) { + vpScale /= (1.0 + (scaleScale*deltaTime)); + if (vpScale < 10e-14) vpScale = 1.0; + } + + // add dp into current vp position. + vpPos.add(dp); + + if ((key_state & HOME_NOMINAL) != 0) { + resetVelocity(); + // Extract the position, quaterion, and scale from the nominal + // transform + vpScale = nominal.get(vpQuat, vpPos); + } + + + /* Final update of view platform */ + // Put the transform back into the transform group. + vpTrans.set(vpQuat, vpPos, vpScale); + targetTG.setTransform(vpTrans); + } + + + /** + * Resets the keyboard navigation velocity to 0. + */ + private void resetVelocity() { + navVec.x = navVec.y = navVec.z = 0.0; + } + + + /** + * Processed a keyboard event. This routine should be called + * every time a KEY_PRESSED or KEY_RELEASED event is received. + * @param keyEvent the AWT key event + */ + public void processKeyEvent(KeyEvent keyEvent) { + int keyCode = keyEvent.getKeyCode(); + int keyChar = keyEvent.getKeyChar(); + +//System.err.println("keyCode " + keyCode + " keyChar " + keyChar); + + if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { + if (keyChar == '+') key_state &= ~PLUS_SIGN; + else + switch (keyCode) { + case KeyEvent.VK_UP: key_state &= ~UP_ARROW; break; + case KeyEvent.VK_DOWN: key_state &= ~DOWN_ARROW; break; + case KeyEvent.VK_LEFT: key_state &= ~LEFT_ARROW; break; + case KeyEvent.VK_RIGHT: key_state &= ~RIGHT_ARROW; break; + case KeyEvent.VK_PAGE_UP: key_state &= ~PAGE_UP; break; + case KeyEvent.VK_PAGE_DOWN: key_state &= ~PAGE_DOWN; break; + case KeyEvent.VK_EQUALS: key_state &= ~HOME_NOMINAL;break; + default: switch(keyChar) { + case '-': key_state &= ~MINUS_SIGN; break; + } + } + } else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) { + if (keyChar == '+') key_state |= PLUS_SIGN; + switch (keyCode) { + case KeyEvent.VK_UP: key_state |= UP_ARROW; break; + case KeyEvent.VK_DOWN: key_state |= DOWN_ARROW; break; + case KeyEvent.VK_LEFT: key_state |= LEFT_ARROW; break; + case KeyEvent.VK_RIGHT: key_state |= RIGHT_ARROW; break; + case KeyEvent.VK_PAGE_UP: key_state |= PAGE_UP; break; + case KeyEvent.VK_PAGE_DOWN: key_state |= PAGE_DOWN; break; + case KeyEvent.VK_EQUALS: key_state |= HOME_NOMINAL;break; + default: switch(keyChar) { + case '-': key_state |= MINUS_SIGN; break; + } + } + } + + /* Check modifier keys */ + if (keyEvent.isShiftDown()) + modifier_key_state |= SHIFT; + else + modifier_key_state &= ~SHIFT; + + if (keyEvent.isMetaDown()) + modifier_key_state |= META; + else + modifier_key_state &= ~META; + + if (keyEvent.isAltDown()) + modifier_key_state |= ALT; + else + modifier_key_state &= ~ALT; + +//System.err.println("keyCode " + keyEvent.getKeyCode() + " modifiers " + keyEvent.getModifiers()); +//System.err.println("SHIFT_MASK " + keyEvent.SHIFT_MASK); +//System.err.println("CTRL_MASK " + keyEvent.CTRL_MASK); +//System.err.println("META_MASK " + keyEvent.META_MASK); +//System.err.println("ALT_MASK " + keyEvent.ALT_MASK); + + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigatorBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigatorBehavior.java new file mode 100644 index 0000000..226fe6b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/keyboard/KeyNavigatorBehavior.java @@ -0,0 +1,216 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.keyboard; + +import java.awt.event.*; +import java.awt.AWTEvent; +import java.util.Enumeration; +import java.awt.Component; +import java.util.LinkedList; +import javax.vecmath.*; +import javax.media.j3d.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class is a simple behavior that invokes the KeyNavigator + * to modify the view platform transform. + */ +public class KeyNavigatorBehavior extends Behavior implements KeyListener { + private WakeupCriterion w1 = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED); + private WakeupCriterion w2 = new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED); + private WakeupOnElapsedFrames w3 = new WakeupOnElapsedFrames(0); + private WakeupCriterion[] warray = { w1, w2, w3 }; + private WakeupCondition w = new WakeupOr(warray); + private KeyEvent eventKey; + private KeyNavigator keyNavigator; + private boolean listener = false; + + private LinkedList eventq; + + + /** + * Override Behavior's initialize method to setup wakeup criteria. + */ + public void initialize() { + // Establish initial wakeup criteria + if (listener) { + w1 = new WakeupOnBehaviorPost(this, KeyEvent.KEY_PRESSED); + w2 = new WakeupOnBehaviorPost(this, KeyEvent.KEY_RELEASED); + warray[0] = w1; + warray[1] = w2; + w = new WakeupOr(warray); + eventq = new LinkedList(); + } + wakeupOn(w); + } + + /** + * Override Behavior's stimulus method to handle the event. + */ + public void processStimulus(Enumeration criteria) { + WakeupOnAWTEvent ev; + WakeupCriterion genericEvt; + AWTEvent[] events; + boolean sawFrame = false; + + while (criteria.hasMoreElements()) { + genericEvt = (WakeupCriterion) criteria.nextElement(); + if (genericEvt instanceof WakeupOnAWTEvent) { + ev = (WakeupOnAWTEvent) genericEvt; + events = ev.getAWTEvent(); + processAWTEvent(events); + } else if (genericEvt instanceof WakeupOnElapsedFrames && + eventKey != null) { + sawFrame = true; + } else if ((genericEvt instanceof WakeupOnBehaviorPost)) { + while(true) { + // access to the queue must be synchronized + synchronized (eventq) { + if (eventq.isEmpty()) break; + eventKey = (KeyEvent)eventq.remove(0); + if (eventKey.getID() == KeyEvent.KEY_PRESSED || + eventKey.getID() == KeyEvent.KEY_RELEASED) { + keyNavigator.processKeyEvent(eventKey); + } + } + } + } + } + if (sawFrame) + keyNavigator.integrateTransformChanges(); + + // Set wakeup criteria for next time + wakeupOn(w); + } + + /** + * Process a keyboard event + */ + private void processAWTEvent(AWTEvent[] events) { + for (int loop = 0; loop < events.length; loop++) { + if (events[loop] instanceof KeyEvent) { + eventKey = (KeyEvent) events[loop]; + // change the transformation; for example to zoom + if (eventKey.getID() == KeyEvent.KEY_PRESSED || + eventKey.getID() == KeyEvent.KEY_RELEASED) { + //System.out.println("Keyboard is hit! " + eventKey); + keyNavigator.processKeyEvent(eventKey); + } + } + } + } + + /** + * Adds this behavior as a KeyListener to the specified component. + * This method can only be called if + * the behavior was created with one of the constructors that takes + * a Component as a parameter. + * @param c The component to add the KeyListener to. + * @exception IllegalStateException if the behavior was not created + * as a listener + * @since Java 3D 1.2.1 + */ + public void addListener(Component c) { + if (!listener) { + throw new IllegalStateException(J3dUtilsI18N.getString("Behavior0")); + } + c.addKeyListener(this); + } + + /** + * Constructs a new key navigator behavior node that operates + * on the specified transform group. + * @param targetTG the target transform group + */ + public KeyNavigatorBehavior(TransformGroup targetTG) { + keyNavigator = new KeyNavigator(targetTG); + } + + /** + * Constructs a key navigator behavior that uses AWT listeners + * and behavior posts rather than WakeupOnAWTEvent. The behavior + * is added to the specified Component and works on the given + * TransformGroup. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * @param c The component to add the KeyListener to. + * @param targetTG The target transform group. + * @since Java 3D 1.2.1 + */ + public KeyNavigatorBehavior(Component c, TransformGroup targetTG) { + this(targetTG); + if (c != null) { + c.addKeyListener(this); + } + listener = true; + } + + public void keyPressed(KeyEvent evt) { +// System.out.println("keyPressed"); + + // add new event to the queue + // must be MT safe + synchronized (eventq) { + eventq.add(evt); + // only need to post if this is the only event in the queue + if (eventq.size() == 1) postId(KeyEvent.KEY_PRESSED); + } + } + + public void keyReleased(KeyEvent evt) { +// System.out.println("keyReleased"); + + // add new event to the queue + // must be MT safe + synchronized (eventq) { + eventq.add(evt); + // only need to post if this is the only event in the queue + if (eventq.size() == 1) postId(KeyEvent.KEY_RELEASED); + } + } + + public void keyTyped(KeyEvent evt) {} + +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehavior.java new file mode 100644 index 0000000..20e8f08 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehavior.java @@ -0,0 +1,329 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.mouse; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; + + +/** + * Base class for all mouse manipulators (see MouseRotate, MouseZoom + * and MouseTranslate for + * examples of how to extend this base class). + */ + +public abstract class MouseBehavior extends Behavior + implements MouseListener, MouseMotionListener { + + private boolean listener = false; + + protected WakeupCriterion[] mouseEvents; + protected WakeupOr mouseCriterion; + protected int x, y; + protected int x_last, y_last; + protected TransformGroup transformGroup; + protected Transform3D transformX; + protected Transform3D transformY; + protected Transform3D currXform; + protected boolean buttonPress = false; + protected boolean reset = false; + protected boolean invert = false; + protected boolean wakeUp = false; + protected int flags = 0; + + // to queue the mouse events + protected LinkedList mouseq; + + // true if this behavior is enable + protected boolean enable = true; + + /** + * Set this flag if you want to manually wakeup the behavior. + */ + public static final int MANUAL_WAKEUP = 0x1; + + /** + * Set this flag if you want to invert the inputs. This is useful when + * the transform for the view platform is being changed instead of the + * transform for the object. + */ + public static final int INVERT_INPUT = 0x2; + + /** + * Creates a mouse behavior object with a given transform group. + * @param transformGroup The transform group to be manipulated. + */ + public MouseBehavior(TransformGroup transformGroup) { + super(); + // need to remove old behavior from group + this.transformGroup = transformGroup; + currXform = new Transform3D(); + transformX = new Transform3D(); + transformY = new Transform3D(); + reset = true; + } + + /** + * Initializes standard fields. Note that this behavior still + * needs a transform group to work on (use setTransformGroup(tg)) and + * the transform group must add this behavior. + * @param format flags + */ + public MouseBehavior(int format) { + super(); + flags = format; + currXform = new Transform3D(); + transformX = new Transform3D(); + transformY = new Transform3D(); + reset = true; + } + + /** + * Creates a mouse behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behaviors is added to + * the specified Component and works on the given TransformGroup. + * A null component can be passed to specify the behaviors should use + * listeners. Components can then be added to the behavior with the + * addListener(Component c) method. + * @param c The Component to add the MouseListener and + * MouseMotionListener to. + * @param transformGroup The TransformGroup to operate on. + * @since Java 3D 1.2.1 + */ + public MouseBehavior(Component c, TransformGroup transformGroup) { + this(transformGroup); + if (c != null) { + c.addMouseListener(this); + c.addMouseMotionListener(this); + } + listener = true; + } + + /** + * Creates a mouse behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and the transform + * group must add this behavior. + * @param format interesting flags (wakeup conditions). + * @since Java 3D 1.2.1 + */ + public MouseBehavior(Component c, int format) { + this(format); + if (c != null) { + c.addMouseListener(this); + c.addMouseMotionListener(this); + } + listener = true; + } + + /** + * Swap a new transformGroup replacing the old one. This allows + * manipulators to operate on different nodes. + * + * @param transformGroup The *new* transform group to be manipulated. + */ + public void setTransformGroup(TransformGroup transformGroup){ + // need to remove old behavior from group + this.transformGroup = transformGroup; + currXform = new Transform3D(); + transformX = new Transform3D(); + transformY = new Transform3D(); + reset = true; + } + + /** + * Return the transformGroup on which this node is operating + */ + public TransformGroup getTransformGroup() { + return this.transformGroup; + } + + /** Initializes the behavior. + */ + + public void initialize() { + mouseEvents = new WakeupCriterion[3]; + if (!listener) { + mouseEvents[0] = new WakeupOnAWTEvent(MouseEvent.MOUSE_DRAGGED); + mouseEvents[1] = new WakeupOnAWTEvent(MouseEvent.MOUSE_PRESSED); + mouseEvents[2] = new WakeupOnAWTEvent(MouseEvent.MOUSE_RELEASED); + } + else { + mouseEvents[0] = new WakeupOnBehaviorPost(this, + MouseEvent.MOUSE_DRAGGED); + mouseEvents[1] = new WakeupOnBehaviorPost(this, + MouseEvent.MOUSE_PRESSED); + mouseEvents[2] = new WakeupOnBehaviorPost(this, + MouseEvent.MOUSE_RELEASED); + mouseq = new LinkedList(); + } + mouseCriterion = new WakeupOr(mouseEvents); + wakeupOn (mouseCriterion); + x = 0; + y = 0; + x_last = 0; + y_last = 0; + } + + /** + * Manually wake up the behavior. If MANUAL_WAKEUP flag was set upon + * creation, you must wake up this behavior each time it is handled. + */ + + public void wakeup() + { + wakeUp = true; + } + + /** + * Handles mouse events + */ + public void processMouseEvent(MouseEvent evt) { + if (evt.getID()==MouseEvent.MOUSE_PRESSED) { + buttonPress = true; + return; + } + else if (evt.getID()==MouseEvent.MOUSE_RELEASED){ + buttonPress = false; + wakeUp = false; + } + else if (evt.getID() == MouseEvent.MOUSE_MOVED) { + // Process mouse move event + } + } + + /** + * All mouse manipulators must implement this. + */ + public abstract void processStimulus (Enumeration criteria); + + /** + * Adds this behavior as a MouseListener and MouseMotionListener to + * the specified component. This method can only be called if + * the behavior was created with one of the constructors that takes + * a Component as a parameter. + * @param c The component to add the MouseListener and + * MouseMotionListener to. + * @exception IllegalStateException if the behavior was not created + * as a listener + * @since Java 3D 1.2.1 + */ + public void addListener(Component c) { + if (!listener) { + throw new IllegalStateException(J3dUtilsI18N.getString("Behavior0")); + } + c.addMouseListener(this); + c.addMouseMotionListener(this); + } + + public void mouseClicked(MouseEvent e) {} + public void mouseEntered(MouseEvent e) {} + public void mouseExited(MouseEvent e) {} + + public void mousePressed(MouseEvent e) { +// System.out.println("mousePressed"); + + // add new event to the queue + // must be MT safe + if (enable) { + synchronized (mouseq) { + mouseq.add(e); + // only need to post if this is the only event in the queue + if (mouseq.size() == 1) + postId(MouseEvent.MOUSE_PRESSED); + } + } + } + + public void mouseReleased(MouseEvent e) { +// System.out.println("mouseReleased"); + + // add new event to the queue + // must be MT safe + if (enable) { + synchronized (mouseq) { + mouseq.add(e); + // only need to post if this is the only event in the queue + if (mouseq.size() == 1) + postId(MouseEvent.MOUSE_RELEASED); + } + } + } + + public void mouseDragged(MouseEvent e) { +// System.out.println("mouseDragged"); + + // add new event to the to the queue + // must be MT safe. + if (enable) { + synchronized (mouseq) { + mouseq.add(e); + // only need to post if this is the only event in the queue + if (mouseq.size() == 1) + postId(MouseEvent.MOUSE_DRAGGED); + } + } + } + + public void mouseMoved(MouseEvent e) {} + + public void setEnable(boolean state) { + super.setEnable(state); + this.enable = state; + if (!enable && (mouseq != null)) { + mouseq.clear(); + } + } +} + + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehaviorCallback.java b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehaviorCallback.java new file mode 100644 index 0000000..14495de --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseBehaviorCallback.java @@ -0,0 +1,73 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.mouse; + +import javax.media.j3d.Transform3D; + +/** + * The MouseBehaviorCallback interface allows a class to be notified + * when the transform is changed by one of the MouseBehaviors. The + * class that is interested in transform changes implements this + * interface, and the object created with that class is registered + * with the desired subclass of MouseBehavior using the + * setupCallback method. When the transform changes, the + * registered object's transformChanged method is + * invoked. + */ + +public interface MouseBehaviorCallback { + + public final static int ROTATE=0; + public final static int TRANSLATE=1; + public final static int ZOOM=2; + + /** + * Classes implementing this interface that are registered with + * one of the MouseBehaviors will be called every time the + * behavior updates the Transform + * @param type will be one of ROTATE, TRANSLATE or ZOOM + */ + public void transformChanged( int type, Transform3D transform ); +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseRotate.java b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseRotate.java new file mode 100644 index 0000000..07309de --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseRotate.java @@ -0,0 +1,315 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.mouse; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * MouseRotate is a Java3D behavior object that lets users control the + * rotation of an object via a mouse. + *

+ * To use this utility, first create a transform group that this + * rotate behavior will operate on. Then, + *

+ * 
+ *   MouseRotate behavior = new MouseRotate();
+ *   behavior.setTransformGroup(objTrans);
+ *   objTrans.addChild(behavior);
+ *   behavior.setSchedulingBounds(bounds);
+ *
+ *
+ * The above code will add the rotate behavior to the transform + * group. The user can rotate any object attached to the objTrans. + */ + +public class MouseRotate extends MouseBehavior { + double x_angle, y_angle; + double x_factor = .03; + double y_factor = .03; + + private MouseBehaviorCallback callback = null; + + /** + * Creates a rotate behavior given the transform group. + * @param transformGroup The transformGroup to operate on. + */ + public MouseRotate(TransformGroup transformGroup) { + super(transformGroup); + } + + /** + * Creates a default mouse rotate behavior. + **/ + public MouseRotate() { + super(0); + } + + /** + * Creates a rotate behavior. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and + * the transform group must add this behavior. + * @param flags interesting flags (wakeup conditions). + */ + public MouseRotate(int flags) { + super(flags); + } + + /** + * Creates a rotate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * @param c The Component to add the MouseListener + * and MouseMotionListener to. + * @since Java 3D 1.2.1 + */ + public MouseRotate(Component c) { + super(c, 0); + } + + /** + * Creates a rotate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behaviors is added to + * the specified Component and works on the given TransformGroup. + * A null component can be passed to specify the behavior should use + * listeners. Components can then be added to the behavior with the + * addListener(Component c) method. + * @param c The Component to add the MouseListener and + * MouseMotionListener to. + * @param transformGroup The TransformGroup to operate on. + * @since Java 3D 1.2.1 + */ + public MouseRotate(Component c, TransformGroup transformGroup) { + super(c, transformGroup); + } + + /** + * Creates a rotate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added to + * the behavior with the addListener(Component c) method. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and the transform + * group must add this behavior. + * @param flags interesting flags (wakeup conditions). + * @since Java 3D 1.2.1 + */ + public MouseRotate(Component c, int flags) { + super(c, flags); + } + + public void initialize() { + super.initialize(); + x_angle = 0; + y_angle = 0; + if ((flags & INVERT_INPUT) == INVERT_INPUT) { + invert = true; + x_factor *= -1; + y_factor *= -1; + } + } + + /** + * Return the x-axis movement multipler. + **/ + public double getXFactor() { + return x_factor; + } + + /** + * Return the y-axis movement multipler. + **/ + public double getYFactor() { + return y_factor; + } + + + /** + * Set the x-axis amd y-axis movement multipler with factor. + **/ + public void setFactor( double factor) { + x_factor = y_factor = factor; + } + + /** + * Set the x-axis amd y-axis movement multipler with xFactor and yFactor + * respectively. + **/ + public void setFactor( double xFactor, double yFactor) { + x_factor = xFactor; + y_factor = yFactor; + } + + public void processStimulus (Enumeration criteria) { + WakeupCriterion wakeup; + AWTEvent[] events; + MouseEvent evt; +// int id; +// int dx, dy; + + while (criteria.hasMoreElements()) { + wakeup = (WakeupCriterion) criteria.nextElement(); + if (wakeup instanceof WakeupOnAWTEvent) { + events = ((WakeupOnAWTEvent)wakeup).getAWTEvent(); + if (events.length > 0) { + evt = (MouseEvent) events[events.length-1]; + doProcess(evt); + } + } + + else if (wakeup instanceof WakeupOnBehaviorPost) { + while (true) { + // access to the queue must be synchronized + synchronized (mouseq) { + if (mouseq.isEmpty()) break; + evt = (MouseEvent)mouseq.remove(0); + // consolidate MOUSE_DRAG events + while ((evt.getID() == MouseEvent.MOUSE_DRAGGED) && + !mouseq.isEmpty() && + (((MouseEvent)mouseq.get(0)).getID() == + MouseEvent.MOUSE_DRAGGED)) { + evt = (MouseEvent)mouseq.remove(0); + } + } + doProcess(evt); + } + } + + } + wakeupOn (mouseCriterion); + } + + void doProcess(MouseEvent evt) { + int id; + int dx, dy; + + processMouseEvent(evt); + if (((buttonPress)&&((flags & MANUAL_WAKEUP) == 0)) || + ((wakeUp)&&((flags & MANUAL_WAKEUP) != 0))) { + id = evt.getID(); + if ((id == MouseEvent.MOUSE_DRAGGED) && + !evt.isMetaDown() && ! evt.isAltDown()){ + x = evt.getX(); + y = evt.getY(); + + dx = x - x_last; + dy = y - y_last; + + if (!reset){ + x_angle = dy * y_factor; + y_angle = dx * x_factor; + + transformX.rotX(x_angle); + transformY.rotY(y_angle); + + transformGroup.getTransform(currXform); + + Matrix4d mat = new Matrix4d(); + // Remember old matrix + currXform.get(mat); + + // Translate to origin + currXform.setTranslation(new Vector3d(0.0,0.0,0.0)); + if (invert) { + currXform.mul(currXform, transformX); + currXform.mul(currXform, transformY); + } else { + currXform.mul(transformX, currXform); + currXform.mul(transformY, currXform); + } + + // Set old translation back + Vector3d translation = new + Vector3d(mat.m03, mat.m13, mat.m23); + currXform.setTranslation(translation); + + // Update xform + transformGroup.setTransform(currXform); + + transformChanged( currXform ); + + if (callback!=null) + callback.transformChanged( MouseBehaviorCallback.ROTATE, + currXform ); + } + else { + reset = false; + } + + x_last = x; + y_last = y; + } + else if (id == MouseEvent.MOUSE_PRESSED) { + x_last = evt.getX(); + y_last = evt.getY(); + } + } + } + + /** + * Users can overload this method which is called every time + * the Behavior updates the transform + * + * Default implementation does nothing + */ + public void transformChanged( Transform3D transform ) { + } + + + /** + * The transformChanged method in the callback class will + * be called every time the transform is updated + */ + public void setupCallback( MouseBehaviorCallback callback ) { + this.callback = callback; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseTranslate.java b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseTranslate.java new file mode 100644 index 0000000..896359a --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseTranslate.java @@ -0,0 +1,290 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.mouse; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * MouseTranslate is a Java3D behavior object that lets users control the + * translation (X, Y) of an object via a mouse drag motion with the third + * mouse button (alt-click on PC). See MouseRotate for similar usage info. + */ + +public class MouseTranslate extends MouseBehavior { + + double x_factor = .02; + double y_factor = .02; + Vector3d translation = new Vector3d(); + + private MouseBehaviorCallback callback = null; + + /** + * Creates a mouse translate behavior given the transform group. + * @param transformGroup The transformGroup to operate on. + */ + public MouseTranslate(TransformGroup transformGroup) { + super(transformGroup); + } + + /** + * Creates a default translate behavior. + */ + public MouseTranslate(){ + super(0); + } + + /** + * Creates a translate behavior. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and + * the transform group must add this behavior. + * @param flags + */ + public MouseTranslate(int flags) { + super(flags); + } + + /** + * Creates a translate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * @param c The Component to add the MouseListener + * and MouseMotionListener to. + * @since Java 3D 1.2.1 + */ + public MouseTranslate(Component c) { + super(c, 0); + } + + /** + * Creates a translate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behaviors is added to + * the specified Component and works on the given TransformGroup. + * A null component can be passed to specify the behavior should use + * listeners. Components can then be added to the behavior with the + * addListener(Component c) method. + * @param c The Component to add the MouseListener and + * MouseMotionListener to. + * @param transformGroup The TransformGroup to operate on. + * @since Java 3D 1.2.1 + */ + public MouseTranslate(Component c, TransformGroup transformGroup) { + super(c, transformGroup); + } + + /** + * Creates a translate behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added to + * the behavior with the addListener(Component c) method. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and the transform + * group must add this behavior. + * @param flags interesting flags (wakeup conditions). + * @since Java 3D 1.2.1 + */ + public MouseTranslate(Component c, int flags) { + super(c, flags); + } + + public void initialize() { + super.initialize(); + if ((flags & INVERT_INPUT) == INVERT_INPUT) { + invert = true; + x_factor *= -1; + y_factor *= -1; + } + } + + /** + * Return the x-axis movement multipler. + **/ + public double getXFactor() { + return x_factor; + } + + /** + * Return the y-axis movement multipler. + **/ + public double getYFactor() { + return y_factor; + } + + /** + * Set the x-axis amd y-axis movement multipler with factor. + **/ + public void setFactor( double factor) { + x_factor = y_factor = factor; + } + + /** + * Set the x-axis amd y-axis movement multipler with xFactor and yFactor + * respectively. + **/ + public void setFactor( double xFactor, double yFactor) { + x_factor = xFactor; + y_factor = yFactor; + } + + public void processStimulus (Enumeration criteria) { + WakeupCriterion wakeup; + AWTEvent[] events; + MouseEvent evt; +// int id; +// int dx, dy; + + while (criteria.hasMoreElements()) { + wakeup = (WakeupCriterion) criteria.nextElement(); + + if (wakeup instanceof WakeupOnAWTEvent) { + events = ((WakeupOnAWTEvent)wakeup).getAWTEvent(); + if (events.length > 0) { + evt = (MouseEvent) events[events.length-1]; + doProcess(evt); + } + } + + else if (wakeup instanceof WakeupOnBehaviorPost) { + while (true) { + // access to the queue must be synchronized + synchronized (mouseq) { + if (mouseq.isEmpty()) break; + evt = (MouseEvent)mouseq.remove(0); + // consolodate MOUSE_DRAG events + while ((evt.getID() == MouseEvent.MOUSE_DRAGGED) && + !mouseq.isEmpty() && + (((MouseEvent)mouseq.get(0)).getID() == + MouseEvent.MOUSE_DRAGGED)) { + evt = (MouseEvent)mouseq.remove(0); + } + } + doProcess(evt); + } + } + + } + wakeupOn(mouseCriterion); + } + + void doProcess(MouseEvent evt) { + int id; + int dx, dy; + + processMouseEvent(evt); + + if (((buttonPress)&&((flags & MANUAL_WAKEUP) == 0)) || + ((wakeUp)&&((flags & MANUAL_WAKEUP) != 0))){ + id = evt.getID(); + if ((id == MouseEvent.MOUSE_DRAGGED) && + !evt.isAltDown() && evt.isMetaDown()) { + + x = evt.getX(); + y = evt.getY(); + + dx = x - x_last; + dy = y - y_last; + + if ((!reset) && ((Math.abs(dy) < 50) && (Math.abs(dx) < 50))) { + //System.out.println("dx " + dx + " dy " + dy); + transformGroup.getTransform(currXform); + + translation.x = dx*x_factor; + translation.y = -dy*y_factor; + + transformX.set(translation); + + if (invert) { + currXform.mul(currXform, transformX); + } else { + currXform.mul(transformX, currXform); + } + + transformGroup.setTransform(currXform); + + transformChanged( currXform ); + + if (callback!=null) + callback.transformChanged( MouseBehaviorCallback.TRANSLATE, + currXform ); + + } + else { + reset = false; + } + x_last = x; + y_last = y; + } + else if (id == MouseEvent.MOUSE_PRESSED) { + x_last = evt.getX(); + y_last = evt.getY(); + } + } + } + + /** + * Users can overload this method which is called every time + * the Behavior updates the transform + * + * Default implementation does nothing + */ + public void transformChanged( Transform3D transform ) { + } + + /** + * The transformChanged method in the callback class will + * be called every time the transform is updated + */ + public void setupCallback( MouseBehaviorCallback callback ) { + this.callback = callback; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseZoom.java b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseZoom.java new file mode 100644 index 0000000..1888ba1 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/mouse/MouseZoom.java @@ -0,0 +1,271 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.mouse; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + + +/** + * MouseZoom is a Java3D behavior object that lets users control the + * Z axis translation of an object via a mouse drag motion with the second + * mouse button. See MouseRotate for similar usage info. + */ + +public class MouseZoom extends MouseBehavior { + + double z_factor = .04; + Vector3d translation = new Vector3d(); + + private MouseBehaviorCallback callback = null; + + /** + * Creates a zoom behavior given the transform group. + * @param transformGroup The transformGroup to operate on. + */ + public MouseZoom(TransformGroup transformGroup) { + super(transformGroup); + } + + /** + * Creates a default mouse zoom behavior. + **/ + public MouseZoom(){ + super(0); + } + + /** + * Creates a zoom behavior. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and + * the transform group must add this behavior. + * @param flags + */ + public MouseZoom(int flags) { + super(flags); + } + + /** + * Creates a zoom behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * @param c The Component to add the MouseListener + * and MouseMotionListener to. + * @since Java 3D 1.2.1 + */ + public MouseZoom(Component c) { + super(c, 0); + } + + /** + * Creates a zoom behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behaviors is added to + * the specified Component and works on the given TransformGroup. + * @param c The Component to add the MouseListener and + * MouseMotionListener to. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * @param transformGroup The TransformGroup to operate on. + * @since Java 3D 1.2.1 + */ + public MouseZoom(Component c, TransformGroup transformGroup) { + super(c, transformGroup); + } + + /** + * Creates a zoom behavior that uses AWT listeners and behavior + * posts rather than WakeupOnAWTEvent. The behavior is added to the + * specified Component. A null component can be passed to specify + * the behavior should use listeners. Components can then be added + * to the behavior with the addListener(Component c) method. + * Note that this behavior still needs a transform + * group to work on (use setTransformGroup(tg)) and the transform + * group must add this behavior. + * @param flags interesting flags (wakeup conditions). + * @since Java 3D 1.2.1 + */ + public MouseZoom(Component c, int flags) { + super(c, flags); + } + + public void initialize() { + super.initialize(); + if ((flags & INVERT_INPUT) == INVERT_INPUT) { + z_factor *= -1; + invert = true; + } + } + + /** + * Return the y-axis movement multipler. + **/ + public double getFactor() { + return z_factor; + } + + /** + * Set the y-axis movement multipler with factor. + **/ + public void setFactor( double factor) { + z_factor = factor; + } + + + public void processStimulus (Enumeration criteria) { + WakeupCriterion wakeup; + AWTEvent[] events; + MouseEvent evt; +// int id; +// int dx, dy; + + while (criteria.hasMoreElements()) { + wakeup = (WakeupCriterion) criteria.nextElement(); + if (wakeup instanceof WakeupOnAWTEvent) { + events = ((WakeupOnAWTEvent)wakeup).getAWTEvent(); + if (events.length > 0) { + evt = (MouseEvent) events[events.length-1]; + doProcess(evt); + } + } + + else if (wakeup instanceof WakeupOnBehaviorPost) { + while (true) { + synchronized (mouseq) { + if (mouseq.isEmpty()) break; + evt = (MouseEvent)mouseq.remove(0); + // consolodate MOUSE_DRAG events + while((evt.getID() == MouseEvent.MOUSE_DRAGGED) && + !mouseq.isEmpty() && + (((MouseEvent)mouseq.get(0)).getID() == + MouseEvent.MOUSE_DRAGGED)) { + evt = (MouseEvent)mouseq.remove(0); + } + } + doProcess(evt); + } + } + + } + wakeupOn (mouseCriterion); + } + + void doProcess(MouseEvent evt) { + int id; + int dx, dy; + + processMouseEvent(evt); + + if (((buttonPress)&&((flags & MANUAL_WAKEUP) == 0)) || + ((wakeUp)&&((flags & MANUAL_WAKEUP) != 0))){ + id = evt.getID(); + if ((id == MouseEvent.MOUSE_DRAGGED) && + evt.isAltDown() && !evt.isMetaDown()){ + + x = evt.getX(); + y = evt.getY(); + + dx = x - x_last; + dy = y - y_last; + + if (!reset){ + transformGroup.getTransform(currXform); + + translation.z = dy*z_factor; + + transformX.set(translation); + + if (invert) { + currXform.mul(currXform, transformX); + } else { + currXform.mul(transformX, currXform); + } + + transformGroup.setTransform(currXform); + + transformChanged( currXform ); + + if (callback!=null) + callback.transformChanged( MouseBehaviorCallback.ZOOM, + currXform ); + + } + else { + reset = false; + } + + x_last = x; + y_last = y; + } + else if (id == MouseEvent.MOUSE_PRESSED) { + x_last = evt.getX(); + y_last = evt.getY(); + } + } + } + + + /** + * Users can overload this method which is called every time + * the Behavior updates the transform + * + * Default implementation does nothing + */ + public void transformChanged( Transform3D transform ) { + } + + /** + * The transformChanged method in the callback class will + * be called every time the transform is updated + */ + public void setupCallback( MouseBehaviorCallback callback ) { + this.callback = callback; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/Intersect.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/Intersect.java new file mode 100644 index 0000000..b53e18e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/Intersect.java @@ -0,0 +1,1297 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.picking; + +import javax.media.j3d.*; +import javax.vecmath.*; +import java.lang.Math; +import com.sun.j3d.internal.J3dUtilsI18N; + +/* + * Contains static methods to aid in the intersection test between + * various PickShape classes and geometry primitives (such as quad, + * triangle, line and point). + */ + + +/** + * @deprecated As of Java 3D version 1.2, this class is no + * longer needed + */ + +public class Intersect +{ + + /** + * Determines if the PickRay and quadrilateral + * objects intersect. + * The quadrilateral is defined as coordinates[index] to + * coordinates[index+3]. + * + * @param ray The ray to use in the intersection test. + * @param coordinates An array holding the quadrilateral data. + * @param index An array index that designates the starting position + * in the array of the quadrilateral to test. + * @param dist On return dist[0] will be set to the distance between ray's + * origin and the point of intersection, if it exists. + * The dist array should be allocated by the user. + * @return true if the ray intersects the quad, + * false if the ray does not intersect the object. + */ + public static boolean rayAndQuad( PickRay ray, Point3d coordinates[], + int index, double dist[] ) { + + if((coordinates.length - index) < 4) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect0")); + + Point3d pnts[] = new Point3d[4]; + + for(int i=0; i<4; i++) + pnts[i] = coordinates[index+i]; + + return rayAndPoly(pnts, ray, dist); + + } + + /** + * Return true if triangle intersects with ray and the distance, from + * the origin of ray to the intersection point, is stored in dist[0]. + * The triangle is defined by coordinates[index] to coordinates[index+2] + * coordinates[index+2]. + * + * @param ray The ray to use in the intersection test. + * @param coordinates An array holding the triangle data. + * @param index An array index that designates the starting position + * in the array of the triangle to test. + * @param dist On return dist[0] will be set to the distance between ray's origin and the + * point of intersection, if it exists. The dist array should be + * allocated by the user. + * @return true if the ray intersects the triangle, + * false if the ray does not intersect the object. + */ + public static boolean rayAndTriangle( PickRay ray, Point3d coordinates[], + int index, double dist[] ) { + + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect1")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = coordinates[index+i]; + + return rayAndPoly(pnts, ray, dist); + + } + + /** + * Return true if triangle intersects with ray and the distance, from + * the origin of ray to the intersection point, is stored in dist[0]. + * The triangle is defined by coordinates[index] to coordinates[index+2] + * + * @param ray The ray that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @param dist On return dist[0] will be set to the distance between ray's origin and the point intersection, if + * exist. + * @return true if ray intersects triangle, else return false. + */ + + public static boolean rayAndTriangle( PickRay ray, Point3f coordinates[], + int index, double dist[] ) { + + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect1")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = new Point3d(coordinates[index+i]); + + return rayAndPoly(pnts, ray, dist); + + } + + + /** + * Caluates the intersection between a PickSegment + * object and a quadrilateral. + * The quad is defined as coordinates[index] to coordinates[index+3] + * + * @param segment The segment to use in the intersection test. + * @param coordinates An array holding the quadrilateral data. + * @param index An array index that designates the starting position + * in the array of the quadrilateral to test. + * @param dist On return dist[0] will be set to the distance between the start of the segment + * and the point of intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the segment intersects the quad, + * false if the segment does not intersect the object. + */ + public static boolean segmentAndQuad( PickSegment segment, + Point3d coordinates[], + int index, double dist[] ) { + if((coordinates.length - index) < 4) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect3")); + + Point3d pnts[] = new Point3d[4]; + + for(int i=0; i<4; i++) + pnts[i] = coordinates[index+i]; + + return segmentAndPoly(pnts, segment, dist); + + } + + /** + * Return true if quad intersects with segment and the distance, from + * the start of segment to the intersection point, is stored in dist[0]. + * The quad is defined by coordinates[index] to coordinates[index+3] + * + * @param segment The segment that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @param dist On return dist[0] will be set to the distance between segment's start and the point + * intersection, if exist. + * @return true if segment intersects quad, else return false. + */ + + public static boolean segmentAndQuad( PickSegment segment, Point3f coordinates[], + int index, double dist[] ) { + if((coordinates.length - index) < 4) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect3")); + + Point3d pnts[] = new Point3d[4]; + + for(int i=0; i<4; i++) + pnts[i] = new Point3d(coordinates[index+i]); + + return segmentAndPoly(pnts, segment, dist); + + } + + /** + * Caluates the intersection between a PickSegment + * object and a triangle. + * The triangle is defined as coordinates[index] to coordinates[index+2] + * + * @param segment The segment to use in the intersection test. + * @param coordinates An array holding the triangle data. + * @param index An array index that designates the starting position + * in the array of the triangle to test. + * @param dist On return dist[0] contains the distance between the start of the segment + * and the point of intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the segment intersects the triangle, + * false if the segment does not intersect the object. + */ + public static boolean segmentAndTriangle( PickSegment segment, + Point3d coordinates[], + int index, + double dist[] ) { + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect5")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = coordinates[index+i]; + + return segmentAndPoly(pnts, segment, dist); + + } + + /** + * Return true if triangle intersects with segment and the distance, from + * the start of segment to the intersection point, is stored in dist[0]. + * The triangle is defined by coordinates[index] to coordinates[index+2] + * + * @param segment The segment that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @param dist On return dist[0] will be set to the distance between segment's start and the point + * intersection, if exist. + * @return true if segment intersects triangle, else return false. + */ + + public static boolean segmentAndTriangle( PickSegment segment, + Point3f coordinates[], int index, + double dist[] ) { + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect6")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = new Point3d(coordinates[index+i]); + + return segmentAndPoly(pnts, segment, dist); + + } + + /** + * Caluates the intersection between a PickPoint + * object and a quadrilateral. + * The quad is defined as coordinates[index] to + * coordinates[index+3]. + * + * @param point The point to use in the intersection test. + * @param coordinates An array holding the quadrilateral data. + * @param index An array index that designates the starting position + * in the array of the quadrilateral to test. + * @return true if the point intersects the quad, + * false if the point does not intersect the object. + */ + private static boolean pointAndQuad( PickPoint point, + Point3d coordinates[], + int index) { + + if((coordinates.length - index) < 4) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect7")); + + Point3d pnts[] = new Point3d[4]; + + for(int i=0; i<4; i++) + pnts[i] = coordinates[index+i]; + + return pointAndPoly( pnts, point); + + } + + /** + * Return true if quad intersects with point. + * The triangle is defined by coordinates[index] to coordinates[index+3] + * + * @param point The point that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @return true if point intersects quad, else return false. + */ + + private static boolean pointAndQuad( PickPoint point, Point3f coordinates[], + int index) { + + if((coordinates.length - index) < 4) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect7")); + + Point3d pnts[] = new Point3d[4]; + + for(int i=0; i<4; i++) + pnts[i] = new Point3d(coordinates[index+i]); + + return pointAndPoly( pnts, point); + + } + + /** + * Caluates the intersection between a PickPoint + * object and a triangle. + * The triangle is defined by coordinates[index] to + * coordinates[index+2]. + * + * @param point The point to use in the intersection test. + * @param coordinates An array holding the triangle data. + * @param index An array index that designates the starting position + * in the array of the triangle to test. + * @return true if the point intersects the triangle, + * false if the point does not intersect the object. + */ + private static boolean pointAndTriangle( PickPoint point, + Point3d coordinates[], + int index) { + + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect9")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = coordinates[index+i]; + + return pointAndPoly( pnts, point); + + } + + /** + * Return true if triangle intersects with point. + * The triangle is defined by coordinates[index] to coordinates[index+2] + * + * @param point The point that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @return true if point intersects triangle, else return false. + */ + + private static boolean pointAndTriangle( PickPoint point, Point3f coordinates[], + int index) { + + if((coordinates.length - index) < 3) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect10")); + + Point3d pnts[] = new Point3d[3]; + + for(int i=0; i<3; i++) + pnts[i] = new Point3d(coordinates[index+i]); + + return pointAndPoly( pnts, point); + + } + + /** + * Determines if the PickRay and Point3d + * objects intersect. + * + * @param ray The ray that is used in the intersection test. + * @param pnt The point that is used in intersection test. + * @param dist On return dist[0] will be set to the distance between ray's origin and the point + * of intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the ray intersects the point, + * false if the ray does not intersect the object. + */ + public static boolean rayAndPoint( PickRay ray, Point3d pnt, + double dist[] ) { + + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + + ray.get(origin, direction); + + return rayAndPoint(pnt, origin, direction, dist); + } + + /** + * Return true if point intersects with ray and the distance, from + * the origin of ray to the intersection point, is stored in dist[0]. + * + * @param ray The ray that is used in intersection test. + * @param pnt The point that is used in intersection test. + * @param dist On return dist[0] contains the distance between ray's origin and the point + * intersection, if exist. + * @return true if ray intersects point, else return false. + */ + + public static boolean rayAndPoint( PickRay ray, Point3f pnt, double dist[] ) { + + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + + ray.get(origin, direction); + + return rayAndPoint(new Point3d(pnt), origin, direction, dist); + } + + /** + * Determines if the PickSegment and Point3d + * objects intersect. + * + * @param segment The segment that is used in the intersection test. + * @param pnt The point that is used in intersection test. + * @param dist On return dist[0] contains the distance between segment's origin and the point + * of intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the segment intersects the point, + * false if the segment does not intersect the object. + */ + public static boolean segmentAndPoint( PickSegment segment, Point3d pnt, + double dist[] ) { + + Point3d start = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + + segment.get(start, end); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + if((rayAndPoint(pnt, start, direction, dist)==true) && (dist[0] <= 1.0)) + return true; + + return false; + } + + /** + * Return true if point intersects with segment and the distance, from + * the start of segment to the intersection point, is stored in dist[0]. + * + * @param segment The segment that is used in intersection test. + * @param pnt The point that is used in intersection test. + * @param dist On return dist[0] contains the distance between segment's start and the point + * intersection, if exist. + * @return true if segment intersects point, else return false. + */ + + public static boolean segmentAndPoint( PickSegment segment, Point3f pnt, + double dist[] ) { + + Point3d start = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + + segment.get(start, end); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + if((rayAndPoint(new Point3d(pnt), start, direction, dist)==true) + && (dist[0] <= 1.0)) + return true; + + return false; + } + + /** + * Determines if the PickPoint and Point3d + * objects intersect. + * + * @param point The PickPoint that is used in the intersection test. + * @param pnt The Point3d that is used in intersection test. + * @return true if the PickPoint and Point3d objects + * intersect, false if the do not intersect. + */ + public static boolean pointAndPoint( PickPoint point, Point3d pnt) { + + Point3d location = new Point3d(); + + point.get(location); + + if ((location.x == pnt.x) && (location.y == pnt.y) && + (location.z == pnt.z)) + return true; + + return false; + } + + /** + * Return true if pnt intersects with point. + * + * @param point The point that is used in intersection test. + * @param pnt The point that is used in intersection test. + * @return true if point intersects pnt, else return false. + */ + + public static boolean pointAndPoint( PickPoint point, Point3f pnt) { + + Point3d location = new Point3d(); + + point.get(location); + + if(((float) location.x == pnt.x) && ((float) location.y == pnt.y) + && ((float) location.z == pnt.z)) + return true; + + return false; + } + + /** + * Determines if the PickRay and Line + * objects intersect. + * The line is defined as coordinates[index] to + * coordinates[index+1]. + * + * @param ray The ray that is used in the intersection test. + * @param coordinates An array holding the line data. + * @param dist On return dist[0] contains the distance between ray's origin and the point of + * intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the ray intersects the line, + * false if the ray does not intersect the object. + */ + public static boolean rayAndLine(PickRay ray, Point3d coordinates[], + int index, + double dist[] ) { + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect11")); + + ray.get(origin, direction); + Point3d start = coordinates[index++]; + Point3d end = coordinates[index]; + + return lineAndRay( start, end, origin, direction, dist ); + + } + + /** + * Return true if line intersects with ray and the distance, from + * the origin of ray to the intersection point, is stored in dist[0]. + * The line is defined by coordinates[index] to coordinates[index+1] + * + * @param ray The ray that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @param dist On return dist[0] contains the distance between ray's origin and the point intersection, if + * exist. + * @return true if ray intersects line, else return false. + */ + + public static boolean rayAndLine(PickRay ray, Point3f coordinates[], int index, + double dist[] ) { + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect11")); + + ray.get(origin, direction); + Point3d start = new Point3d(coordinates[index++]); + Point3d end = new Point3d(coordinates[index]); + + return lineAndRay( start, end, origin, direction, dist ); + + } + + /** + * Determines if the PickSegment and Line + * objects intersect. + * The line is defined as coordinates[index] to + * coordinates[index+1]. + * + * @param segment The segment that is used in the intersection test. + * @param coordinates An array holding the line data. + * @param dist On return dist[0] contains the distance between segment's origin and the point of + * intersection, if it exists. The dist array + * should be allocated by the user. + * @return true if the segment intersects the line, + * false if the segment does not intersect the object. + */ + public static boolean segmentAndLine(PickSegment segment, + Point3d coordinates[], + int index, double dist[] ) { + + Point3d start = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect13")); + + segment.get(start, end); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + Point3d startpnt = coordinates[index++]; + Point3d endpnt = coordinates[index]; + + if(lineAndRay(startpnt, endpnt, start, direction, dist)==true) + if(dist[0] <= 1.0) + return true; + + return false; + } + + /** + * Return true if line intersects with segment and the distance, from + * the start of segment to the intersection point, is stored in dist[0]. + * The line is defined by coordinates[index] to coordinates[index+1] + * + * @param segment The segment that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @param dist On return dist[0] contains the distance between segment's start and the point + * intersection, if exist. + * @return true if segment intersects line, else return false. + */ + + public static boolean segmentAndLine(PickSegment segment, Point3f coordinates[], + int index, double dist[] ) { + + Point3d start = new Point3d(); + Point3d end = new Point3d(); + Vector3d direction = new Vector3d(); + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect13")); + + segment.get(start, end); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + Point3d startpnt = new Point3d(coordinates[index++]); + Point3d endpnt = new Point3d(coordinates[index]); + + if(lineAndRay(startpnt, endpnt, start, direction, dist)==true) + if(dist[0] <= 1.0) + return true; + + return false; + } + + /** + * Determines if the PickPoint and Line + * objects intersect. + * The line is defined as coordinates[index] to + * coordinates[index+1]. + * + * @param point The point that is used in the intersection test. + * @param coordinates An array holding the line data. + * @return true if the the point intersects the line, + * false if the the point does not intersect the object. + */ + public static boolean pointAndLine(PickPoint point, Point3d coordinates[], + int index ) { + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect13")); + + double dist[] = new double[1]; + Point3d start = coordinates[index++]; + Point3d end = coordinates[index]; + Point3d location = new Point3d(); + Vector3d direction = new Vector3d(); + + point.get(location); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + if ((rayAndPoint(location, start, direction, dist)==true) && + (dist[0] <= 1.0)) + return true; + + return false; + + } + + /** + * Return true if line intersects with point. + * The line is defined by coordinates[index] to coordinates[index+1] + * + * @param point The point that is used in intersection test. + * @param coordinates an array of vertices. + * @param index the vertex index + * @return true if point intersects line, else return false. + */ + + public static boolean pointAndLine(PickPoint point, Point3f coordinates[], + int index ) { + + if((coordinates.length - index) < 2) + throw new RuntimeException(J3dUtilsI18N.getString("Intersect13")); + + double dist[] = new double[1]; + Point3d start = new Point3d(coordinates[index++]); + Point3d end = new Point3d(coordinates[index]); + Point3d location = new Point3d(); + Vector3d direction = new Vector3d(); + + point.get(location); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + if((rayAndPoint(location, start, direction, dist)==true) && (dist[0] <= 1.0)) + return true; + + return false; + + } + + /** + * Return true if point is on the inside of halfspace test. The + * halfspace is + * partition by the plane of triangle or quad. + * */ + + private static boolean pointAndPoly( Point3d coordinates[], PickPoint point) { + + Vector3d vec0 = new Vector3d(); // Edge vector from point 0 to point 1; + Vector3d vec1 = new Vector3d(); // Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = new Vector3d(); + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + Vector3d tempV3d = new Vector3d(); + double pNrmDotrDir = 0.0; + + double tempD; + + int i, j; + + // Compute plane normal. + for(i=0; i 0.0) + break; + } + + for(j=i; j 0.0) + break; + } + + if(j == (coordinates.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + System.out.println("Ray orgin : " + ray.origin + " dir " + ray.direction); + System.out.println("Triangle/Quad :"); + for(i=0; i1.0)) // Before or after the end points of line. + return false; + + tmp1 = ori.z + s * dir.z; + tmp2 = start.z + t * lDir.z; + + if((tmp1 < (tmp2 - Double.MIN_VALUE)) || (tmp1 > (tmp2 + Double.MIN_VALUE))) + return false; + + dist[0] = s; + return true; + } + + private static boolean rayAndPoint( Point3d pnt, Point3d ori, + Vector3d dir, double dist[] ) { + int flag = 0; + double temp; + + if(dir.x != 0.0) { + flag = 0; + dist[0] = (pnt.x - ori.x)/dir.x; + } + else if(dir.y != 0.0) { + if(pnt.x != ori.x) + return false; + flag = 1; + dist[0] = (pnt.y - ori.y)/dir.y; + } + else if(dir.z != 0.0) { + if((pnt.x != ori.x)||(pnt.y != ori.y)) + return false; + flag = 2; + dist[0] = (pnt.z - ori.z)/dir.z; + + } + else + return false; + + if(dist[0] < 0.0) + return false; + + if(flag == 0) { + temp = ori.y + dist[0] * dir.y; + if((pnt.y < (temp - Double.MIN_VALUE)) || (pnt.y > (temp + Double.MIN_VALUE))) + return false; + } + + if(flag < 2) { + temp = ori.z + dist[0] * dir.z; + if((pnt.z < (temp - Double.MIN_VALUE)) || (pnt.z > (temp + Double.MIN_VALUE))) + return false; + } + + return true; + + } + + private static boolean rayAndPoly( Point3d coordinates[], + PickRay ray, double dist[] ) { + + Vector3d vec0 = new Vector3d(); // Edge vector from point 0 to point 1; + Vector3d vec1 = new Vector3d(); // Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = new Vector3d(); + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + Vector3d tempV3d = new Vector3d(); + double pNrmDotrDir = 0.0; + int axis, nc, sh, nsh; + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + + Point3d iPnt = new Point3d(); // Point of intersection. + + double uCoor[] = new double[4]; // Only need to support up to quad. + double vCoor[] = new double[4]; + double tempD; + + int i, j; + + // Compute plane normal. + for(i=0; i 0.0) + break; + } + + for(j=i; j 0.0) + break; + } + + if(j == (coordinates.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + System.out.println("Triangle/Quad :"); + for(i=0; i absNrmY) + axis = 0; + else + axis = 1; + + if(axis == 0) { + if(absNrmX < absNrmZ) + axis = 2; + } + else if(axis == 1) { + if(absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + pNrm + " axis " + axis ); + + for(i=0; i 0.0) && (uCoor[j] > 0.0)) { + // This line must cross U+. + nc++; + } + else if((uCoor[i] > 0.0) || (uCoor[j] > 0.0)) { + // This line might cross U+. We need to compute intersection on U azis. + tempD = uCoor[i]-vCoor[i]*(uCoor[j]-uCoor[i])/(vCoor[j]-vCoor[i]); + if(tempD > 0) + // This line cross U+. + nc++; + } + sh = nsh; + } // sh != nsh + } + + // System.out.println("nc " + nc); + + if((nc%2) == 1) { + + // calculate the distance + dist[0] *= direction.length(); + + // System.out.println("Ray Intersected!"); + /* + System.out.println("Ray orgin : " + origin + " dir " + direction); + System.out.println("Triangle/Quad :"); + for(i=0; i 0.0) + break; + } + + for(j=i; j 0.0) + break; + } + + if(j == (coordinates.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + System.out.println("Triangle/Quad :"); + for(i=0; i 1.0 )) { + // System.out.println("Segment intersects the plane behind the start or exceed end."); + return false; + } + + // Now, one thing for sure the segment intersect the plane. + // Find the intersection point. + iPnt.x = start.x + direction.x * dist[0]; + iPnt.y = start.y + direction.y * dist[0]; + iPnt.z = start.z + direction.z * dist[0]; + + // System.out.println("dist " + dist[0] + " iPnt : " + iPnt); + + // Project 3d points onto 2d plane and apply Jordan curve theorem. + // Note : Area of polygon is not preserve in this projection, but + // it doesn't matter here. + + // Find the axis of projection. + absNrmX = Math.abs(pNrm.x); + absNrmY = Math.abs(pNrm.y); + absNrmZ = Math.abs(pNrm.z); + + if(absNrmX > absNrmY) + axis = 0; + else + axis = 1; + + if(axis == 0) { + if(absNrmX < absNrmZ) + axis = 2; + } + else if(axis == 1) { + if(absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + pNrm + " axis " + axis ); + + for(i=0; i 0.0) && (uCoor[j] > 0.0)) { + // This line must cross U+. + nc++; + } + else if((uCoor[i] > 0.0) || (uCoor[j] > 0.0)) { + // This line might cross U+. We need to compute intersection on U azis. + tempD = uCoor[i]-vCoor[i]*(uCoor[j]-uCoor[i])/(vCoor[j]-vCoor[i]); + if(tempD > 0) + // This line cross U+. + nc++; + } + sh = nsh; + } // sh != nsh + } + + // System.out.println("nc " + nc); + + if((nc%2) == 1) { + + // calculate the distance + dist[0] *= direction.length(); + + // System.out.println("Segment Intersected!"); + /* + System.out.println("Segment orgin : " + start + " dir " + direction); + System.out.println("Triangle/Quad :"); + for(i=0; icom.sun.j3d.utils.picking.behaviors.PickMouseBehavior + * + * @see com.sun.j3d.utils.picking.behaviors.PickMouseBehavior + */ + +public abstract class PickMouseBehavior extends Behavior { + + /** + * Portion of the scene graph to operate picking on. + */ + protected PickObject pickScene; + + protected WakeupCriterion[] conditions; + protected WakeupOr wakeupCondition; + protected boolean buttonPress = false; + + protected TransformGroup currGrp; + protected static final boolean debug = false; + protected MouseEvent mevent; + + /** + * Creates a PickMouseBehavior given current canvas, root of the tree to + * operate on, and the bounds. + */ + public PickMouseBehavior(Canvas3D canvas, BranchGroup root, Bounds bounds){ + super(); + currGrp = new TransformGroup(); + currGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + currGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); + root.addChild(currGrp); + pickScene = new PickObject(canvas, root); + } + + public void initialize() { + + conditions = new WakeupCriterion[2]; + conditions[0] = new WakeupOnAWTEvent(Event.MOUSE_MOVE); + conditions[1] = new WakeupOnAWTEvent(Event.MOUSE_DOWN); + wakeupCondition = new WakeupOr(conditions); + + wakeupOn(wakeupCondition); + } + + private void processMouseEvent(MouseEvent evt) { + buttonPress = false; + + if (evt.getID()==MouseEvent.MOUSE_PRESSED | + evt.getID()==MouseEvent.MOUSE_CLICKED) { + buttonPress = true; + return; + } + else if (evt.getID() == MouseEvent.MOUSE_MOVED) { + // Process mouse move event + } + } + + public void processStimulus (Enumeration criteria) { + WakeupCriterion wakeup; + AWTEvent[] evt = null; + int xpos = 0, ypos = 0; + + while(criteria.hasMoreElements()) { + wakeup = (WakeupCriterion)criteria.nextElement(); + if (wakeup instanceof WakeupOnAWTEvent) + evt = ((WakeupOnAWTEvent)wakeup).getAWTEvent(); + } + + if (evt[0] instanceof MouseEvent){ + mevent = (MouseEvent) evt[0]; + + if (debug) + System.out.println("got mouse event"); + processMouseEvent((MouseEvent)evt[0]); + xpos = mevent.getPoint().x; + ypos = mevent.getPoint().y; + } + + if (debug) + System.out.println("mouse position " + xpos + " " + ypos); + + if (buttonPress){ + updateScene(xpos, ypos); + } + wakeupOn (wakeupCondition); + } + + /** Subclasses shall implement this update function + */ + public abstract void updateScene(int xpos, int ypos); +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickObject.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickObject.java new file mode 100644 index 0000000..3a85bc8 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickObject.java @@ -0,0 +1,841 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* +New Base level methods: + +ISSUE : + How about PickPoint and PickSegment ? + +DONE : + PickShape generatePickRay(int x, int y) + + SceneGraphPath[] pickAll(int x, int y) + SceneGraphPath[] pickAllSorted(int x, int y) + SceneGraphPath pickAny(int x, int y) + SceneGraphPath pickClosest(int x, int y) + + Node getPickedNode(SceneGraphPath, flag) + Node getPickedNode(SceneGraphPath, flag, int count) + where flag can be any combo of: + Group, Morph, Primitive, Shape3D, + TransformGroup, Switch + + +TODO : + SceneGraphPath[] pickGeomAll(int x, int y) + SceneGraphPath[] pickGeomAllSorted(int x, int y) + SceneGraphPath pickGeomAny(int x, int y) + SceneGraphPath pickGeomClosest(int x, int y) + + bool intersect(SceneGraphPath, PickShape) + + + Eventually: + getClosestVtx(ScenGraphPath, PickShape) + + +Misc: + Mouse should stay on top of object it is dragging + + */ + +package com.sun.j3d.utils.behaviors.picking; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import com.sun.j3d.utils.geometry.Primitive; +import javax.vecmath.*; + +/* + * Contains methods to aid in picking. A PickObject is created + * for a given Canvas3D and a BranchGroup. SceneGraphObjects + * under the specified BranchGroup can then be checked to determine + * if they have been picked. + */ + +/** + * @deprecated As of Java 3D version 1.2, replaced by + * com.sun.j3d.utils.picking.PickCanvas + * + * @see com.sun.j3d.utils.picking.PickCanvas + */ + +public class PickObject extends Object { + + // Have to rethink what to support. Is this complete. + + /** + * A flag to indicate to the pickNode method to return a + * Shape3D node from + * a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int SHAPE3D = 0x1; + + /** + * A flag to indicate to the pickNode method to return a + * Morph node from + * a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int MORPH = 0x2; + + /** + * A flag to indicate to the pickNode method to return a + * Primitive node + * from a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int PRIMITIVE = 0x4; + + /** + * A flag to indicate to the pickNode method to return a + * Link node from + * a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int LINK = 0x8; + + /** + * A flag to indicate to the pickNode method to return a + * Group node from + * a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int GROUP = 0x10; + + /** + * A flag to indicate to the pickNode method to return a + * TransformGroup + * node from a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int TRANSFORM_GROUP = 0x20; + + /** + * A flag to indicate to the pickNode method to return a + * BranchGroup + * node from a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int BRANCH_GROUP = 0x40; + + /** + * A flag to indicate to the pickNode method to return a + * Switch node from + * a given SceneGraphPath. + * + * @see PickObject#pickNode + */ + public static final int SWITCH = 0x80; + + + /** + * Set this flag if you want to pick by geometry. + */ + public static final int USE_GEOMETRY = 0x100; + + + /** + * Set this flag if you want to pick by bounds. + */ + public static final int USE_BOUNDS = 0x200; + + BranchGroup pickRoot; + Canvas3D canvas; + Point3d origin = new Point3d(); + Vector3d direction = new Vector3d(); + PickRay pickRay = new PickRay(); + SceneGraphPath sceneGraphPath = null; + SceneGraphPath sceneGraphPathArr[] = null; + int pickBy; // To pick by Bounds or Geometry. + + static final boolean debug = false; + + /** + * Creates a PickObject. + * @param c Current J3D canvas. + * @param root The portion of the scenegraph for which picking is to occur + * on. It has to be a BranchGroup. + * + * @see BranchGroup + * @see Canvas3D + */ + public PickObject(Canvas3D c, BranchGroup root) + { + pickRoot = root; + canvas = c; + } + + /** + * Creates a PickRay that starts at the viewer position and points into + * the scene in the direction of (xpos, ypos) specified in window space. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @return A PickShape object that is the constructed PickRay. + */ + public PickShape generatePickRay(int xpos, int ypos) + { + + Transform3D motion=new Transform3D(); + Point3d eyePosn = new Point3d(); + Point3d mousePosn = new Point3d(); + Vector3d mouseVec=new Vector3d(); + + canvas.getCenterEyeInImagePlate(eyePosn); + canvas.getPixelLocationInImagePlate(xpos,ypos,mousePosn); + if (canvas.getView().getProjectionPolicy() == + View.PARALLEL_PROJECTION) { + // Correct for the parallel projection: keep the eye's z + // coordinate, but make x,y be the same as the mouse, this + // simulates the eye being at "infinity" + eyePosn.x = mousePosn.x; + eyePosn.y = mousePosn.y; + } + + canvas.getImagePlateToVworld(motion); + + if (debug) { + System.out.println("mouse position " + xpos + " " + ypos); + System.out.println("before, mouse " + mousePosn + " eye " + eyePosn); + } + + motion.transform(eyePosn); + motion.transform(mousePosn); + mouseVec.sub(mousePosn, eyePosn); + mouseVec.normalize(); + + if (debug) { + System.out.println(motion + "\n"); + System.out.println("after, mouse " + mousePosn + " eye " + eyePosn + + " mouseVec " + mouseVec); + } + + pickRay.set(eyePosn, mouseVec); + + return (PickShape) pickRay; + + } + + /** + * Returns an array referencing all the items that are pickable below the + * BranchGroup (specified in the PickObject constructor) that + * intersect with a ray that starts at the + * viewer position and points into the scene in the direction of (xpos, ypos) + * specified in window space. The resultant array is unordered. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @return The array of SceneGraphPath objects that contain Objects that + * were picked + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath[] pickAll(int xpos, int ypos) + { + pickRay = (PickRay) generatePickRay(xpos, ypos); + sceneGraphPathArr = pickRoot.pickAll(pickRay); + return sceneGraphPathArr; + } + + /** + * Returns a sorted array of references to all the Pickable items below the + * BranchGroup (specified in the PickObject constructor) that + * intersect with the ray that starts at the viewer + * position and points into the scene in the direction of (xpos, ypos) + * in the window space. + * Element [0] references the item closest to viewer. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @return A sorted arrayof SceneGraphPath objects that contain Objects that + * were picked. The array is sorted from closest to farthest from the + * viewer + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath[] pickAllSorted(int xpos, int ypos) + { + pickRay = (PickRay) generatePickRay(xpos, ypos); + sceneGraphPathArr = pickRoot.pickAllSorted(pickRay); + return sceneGraphPathArr; + } + + /** + * Returns a reference to any item that is Pickable below the specified + * BranchGroup (specified in the PickObject constructor) which + * intersects with the ray that starts at the viewer + * position and points into the scene in the direction of (xpos, ypos) in + * window space. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @return A SceneGraphPath of an object that was picked. This is not + * guarenteed to return the same result for multiple picks + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath pickAny(int xpos, int ypos) + { + pickRay = (PickRay) generatePickRay(xpos, ypos); + sceneGraphPath = pickRoot.pickAny(pickRay); + return sceneGraphPath; + } + + /** + * Returns a reference to the item that is closest to the viewer and is + * Pickable below the BranchGroup (specified in the PickObject + * constructor) which intersects with the ray that starts at + * the viewer position and points into the scene in the direction of + * (xpos, ypos) in the window space. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @return A SceneGraphPath which contains the closest pickable object. + * If no pickable object is found, null is returned. + * + * @see SceneGraphPath + */ + public SceneGraphPath pickClosest(int xpos, int ypos) + { + pickRay = (PickRay) generatePickRay(xpos, ypos); + sceneGraphPath = pickRoot.pickClosest(pickRay); + return sceneGraphPath; + } + + + /** + * Returns an array referencing all the items that are pickable below the + * BranchGroup (specified in the PickObject constructor) that + * intersect with a ray that starts at the + * viewer position and points into the scene in the direction of (xpos, ypos) + * specified in window space. The resultant array is unordered. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @param flag Specifys picking by Geometry or Bounds. + * @return The array of SceneGraphPath objects that contain Objects that + * were picked + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath[] pickAll(int xpos, int ypos, int flag) + { + + if(flag == USE_BOUNDS) { + return pickAll(xpos, ypos); + } + else if(flag == USE_GEOMETRY) { + return pickGeomAll(xpos, ypos); + } + else + return null; + } + + /** + * Returns a sorted array of references to all the Pickable items below the + * BranchGroup (specified in the PickObject constructor) that + * intersect with the ray that starts at the viewer + * position and points into the scene in the direction of (xpos, ypos) + * in the window space. + * Element [0] references the item closest to viewer. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @param flag Specifys picking by Geometry or Bounds. + * @return A sorted arrayof SceneGraphPath objects that contain Objects that + * were picked. The array is sorted from closest to farthest from the + * viewer + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath[] pickAllSorted(int xpos, int ypos, int flag) + { + + if(flag == USE_BOUNDS) { + return pickAllSorted(xpos, ypos); + } + else if(flag == USE_GEOMETRY) { + return pickGeomAllSorted(xpos, ypos); + } + else + return null; + + } + + /** + * Returns a reference to any item that is Pickable below the specified + * BranchGroup (specified in the PickObject constructor) which + * intersects with the ray that starts at the viewer + * position and points into the scene in the direction of (xpos, ypos) in + * window space. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @param flag Specifys picking by Geometry or Bounds. + * @return A SceneGraphPath of an object that was picked. This is not + * guarenteed to return the same result for multiple picks + * If no pickable object is found null is returned.. + * + * @see SceneGraphPath + */ + public SceneGraphPath pickAny(int xpos, int ypos, int flag) + { + + if(flag == USE_BOUNDS) { + return pickAny(xpos, ypos); + } + else if(flag == USE_GEOMETRY) { + return pickGeomAny(xpos, ypos); + } + else + return null; + } + + /** + * Returns a reference to the item that is closest to the viewer and is + * Pickable below the BranchGroup (specified in the PickObject + * constructor) which intersects with the ray that starts at + * the viewer position and points into the scene in the direction of + * (xpos, ypos) in the window space. + * + * @param xpos The value along the x-axis. + * @param ypos The value along the y-axis. + * @param flag Specifys picking by Geometry or Bounds. + * @return A SceneGraphPath which contains the closest pickable object. + * If no pickable object is found, null is returned. + * + * @see SceneGraphPath + */ + public SceneGraphPath pickClosest(int xpos, int ypos, int flag) + { + + if(flag == USE_BOUNDS) { + return pickClosest(xpos, ypos); + } + else if(flag == USE_GEOMETRY) { + return pickGeomClosest(xpos, ypos); + } + else + return null; + } + + private SceneGraphPath[] pickGeomAll(int xpos, int ypos) + { + Node obj; + int i, cnt=0; + + pickRay = (PickRay) generatePickRay(xpos, ypos); + sceneGraphPathArr = pickRoot.pickAll(pickRay); + + if(sceneGraphPathArr == null) + return null; + + boolean found[] = new boolean[sceneGraphPathArr.length]; + + for(i=0; inull is returned. + */ + public Node pickNode(SceneGraphPath sgPath, int flags) + { + + if (sgPath != null) { + Node pickedNode = sgPath.getObject(); + + if ((pickedNode instanceof Shape3D) && ((flags & SHAPE3D) != 0)){ + if (debug) System.out.println("Shape3D found"); + return pickedNode; + } + else if ((pickedNode instanceof Morph) && ((flags & MORPH) != 0)){ + if (debug) System.out.println("Morph found"); + return pickedNode; + } + else { + for (int j=sgPath.nodeCount()-1; j>=0; j--){ + pickedNode = sgPath.getNode(j); + if (debug) System.out.println("looking at node " + pickedNode); + + if ((pickedNode instanceof Primitive) && + ((flags & PRIMITIVE) != 0)){ + if (debug) System.out.println("Primitive found"); + return pickedNode; + } + else if ((pickedNode instanceof Link) && ((flags & LINK) != 0)){ + if (debug) System.out.println("Link found"); + return pickedNode; + } + else if ((pickedNode instanceof Switch) && ((flags & SWITCH) != 0)){ + if (debug) System.out.println("Switch found"); + return pickedNode; + } + else if ((pickedNode instanceof TransformGroup) && + ((flags & TRANSFORM_GROUP) != 0)){ + if (debug) System.out.println("xform group found"); + return pickedNode; + } + else if ((pickedNode instanceof BranchGroup) && + ((flags & BRANCH_GROUP) != 0)){ + if (debug) System.out.println("Branch group found"); + return pickedNode; + } + else if ((pickedNode instanceof Group) && ((flags & GROUP) != 0)){ + if (debug) System.out.println("Group found"); + return pickedNode; + } + } + + if (pickedNode == null) + if (debug) System.out.println("ERROR: null SceneGraphPath"); + } + + } + + return null; + + } + + + /** + * Returns a reference to a Pickable Node that + * is of the specified type + * that is contained in the specified SceneGraphPath. + * The Node returned is the nth occurrence + * of a Node that is of the specified type. + * + * @param sgPath the SceneGraphPath to be traversed. + * @param flags the Node types interested. + * @param occurrence the occurrence of a Node that + * matches the specified type to return. An occurrence of + * 1 means to return the first occurrence of that object type (the object + * closest to the Locale). + * @return the nth occurrence of a Node + * of type flags, starting from the Locale. If no pickable object is + * found, null is returned. + */ + public Node pickNode(SceneGraphPath sgPath, int flags, int occurrence) + { + int curCnt=0; + + if (sgPath != null) { + Node pickedNode = sgPath.getObject(); + + // Shape3D and Morph are leaf nodes and have no children. It doesn't + // make sense to do occurrence check here. We'll just return it for now. + if ((pickedNode instanceof Shape3D) && ((flags & SHAPE3D) != 0)){ + if (debug) System.out.println("Shape3D found"); + return pickedNode; + } else if ((pickedNode instanceof Morph) && ((flags & MORPH) != 0)){ + if (debug) System.out.println("Morph found"); + return pickedNode; + } + else { + for (int j = 0; j < sgPath.nodeCount(); j++){ + pickedNode = sgPath.getNode(j); + if (debug) System.out.println("looking at node " + pickedNode); + + if ((pickedNode instanceof Group) && ((flags & GROUP) != 0)){ + if (debug) System.out.println("Group found"); + curCnt++; + if(curCnt == occurrence) + return pickedNode; + } + else if ((pickedNode instanceof BranchGroup) && + ((flags & BRANCH_GROUP) != 0)){ + if (debug) System.out.println("Branch group found"); + curCnt++; + if(curCnt == occurrence) + return pickedNode; + } + else if ((pickedNode instanceof TransformGroup) && + ((flags & TRANSFORM_GROUP) != 0)){ + if (debug) System.out.println("xform group found"); + curCnt++; + if(curCnt == occurrence) + return pickedNode; + } + else if ((pickedNode instanceof Primitive) && + ((flags & PRIMITIVE) != 0)){ + if (debug) System.out.println("Primitive found"); + curCnt++; + if(curCnt == occurrence) + return pickedNode; + } + else if ((pickedNode instanceof Link) && ((flags & LINK) != 0)){ + if (debug) System.out.println("Link found"); + curCnt++; + if(curCnt == occurrence) + return pickedNode; + } + } + + if (pickedNode == null) + if (debug) System.out.println("ERROR: null SceneGraphPath"); + } + + } + + return null; + + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickRotateBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickRotateBehavior.java new file mode 100644 index 0000000..a51fa18 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickRotateBehavior.java @@ -0,0 +1,194 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.picking; + +import com.sun.j3d.utils.behaviors.mouse.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/* + * A mouse behavior that allows user to pick and drag scene graph objects. + * Common usage: + *

+ * 1. Create your scene graph. + *

+ * 2. Create this behavior with root and canvas. + *

+ *

+ *	PickRotateBehavior behavior = new PickRotateBehavior(canvas, root, bounds);
+ *      root.addChild(behavior);
+ * 
+ *

+ * The above behavior will monitor for any picking events on + * the scene graph (below root node) and handle mouse drags on pick hits. + * Note the root node can also be a subgraph node of the scene graph (rather + * than the topmost). + */ + +/** + * @deprecated As of Java 3D version 1.2, replaced by + * com.sun.j3d.utils.picking.behaviors.PickRotateBehavior + * + * @see com.sun.j3d.utils.picking.behaviors.PickRotateBehavior + */ + +public class PickRotateBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseRotate drag; + int pickMode = PickObject.USE_BOUNDS; + private PickingCallback callback=null; + private TransformGroup currentTG; + + /** + * Creates a pick/rotate behavior that waits for user mouse events for + * the scene graph. This method has its pickMode set to BOUNDS picking. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickRotateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + drag = new MouseRotate(MouseRotate.MANUAL_WAKEUP); + drag.setTransformGroup(currGrp); + currGrp.addChild(drag); + drag.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/rotate behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickObject.USE_BOUNDS or PickObject.USE_GEOMETRY. + * Note: If pickMode is set to PickObject.USE_GEOMETRY, all geometry object in + * the scene graph that allows pickable must have its ALLOW_INTERSECT bit set. + **/ + + public PickRotateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds, + int pickMode){ + super(canvas, root, bounds); + drag = new MouseRotate(MouseRotate.MANUAL_WAKEUP); + drag.setTransformGroup(currGrp); + currGrp.addChild(drag); + drag.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.pickMode = pickMode; + } + + /** + * Sets the pickMode component of this PickRotateBehavior to the value of + * the passed pickMode. + * @param pickMode the pickMode to be copied. + **/ + + + public void setPickMode(int pickMode) { + this.pickMode = pickMode; + } + + /** + * Return the pickMode component of this PickRotateBehavior. + **/ + + public int getPickMode() { + return pickMode; + } + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (!mevent.isMetaDown() && !mevent.isAltDown()){ + + // tg = (TransformGroup) pickScene.pickNode(pickScene.pickClosest(xpos, ypos), + // PickObject.TRANSFORM_GROUP); + + tg =(TransformGroup)pickScene.pickNode(pickScene.pickClosest(xpos, ypos,pickMode), + PickObject.TRANSFORM_GROUP); + // Make sure the selection exists and is movable. + if ((tg != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + drag.setTransformGroup(tg); + drag.wakeup(); + currentTG = tg; + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + } + + /** + * Callback method from MouseRotate + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.ROTATE, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + drag.setupCallback( null ); + else + drag.setupCallback( this ); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickTranslateBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickTranslateBehavior.java new file mode 100644 index 0000000..13f08d8 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickTranslateBehavior.java @@ -0,0 +1,178 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.picking; + +import com.sun.j3d.utils.behaviors.mouse.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +// A mouse behavior that allows user to pick and translate scene graph objects. +// Common usage: 1. Create your scene graph. 2. Create this behavior with +// the root and canvas. See PickRotateBehavior for more details. + +/** + * @deprecated As of Java 3D version 1.2, replaced by + * com.sun.j3d.utils.picking.behaviors.PickTranslateBehavior + * + * @see com.sun.j3d.utils.picking.behaviors.PickTranslateBehavior + */ + +public class PickTranslateBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseTranslate translate; + int pickMode = PickObject.USE_BOUNDS; + private PickingCallback callback = null; + private TransformGroup currentTG; + + /** + * Creates a pick/translate behavior that waits for user mouse events for + * the scene graph. This method has its pickMode set to BOUNDS picking. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickTranslateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + translate = new MouseTranslate(MouseBehavior.MANUAL_WAKEUP); + translate.setTransformGroup(currGrp); + currGrp.addChild(translate); + translate.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/translate behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickObject.USE_BOUNDS or PickObject.USE_GEOMETRY. + * Note: If pickMode is set to PickObject.USE_GEOMETRY, all geometry object in + * the scene graph that allows pickable must have its ALLOW_INTERSECT bit set. + **/ + + public PickTranslateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds, + int pickMode){ + super(canvas, root, bounds); + translate = new MouseTranslate(MouseBehavior.MANUAL_WAKEUP); + translate.setTransformGroup(currGrp); + currGrp.addChild(translate); + translate.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.pickMode = pickMode; + } + + /** + * Sets the pickMode component of this PickTranslateBehavior to the value of + * the passed pickMode. + * @param pickMode the pickMode to be copied. + **/ + + public void setPickMode(int pickMode) { + this.pickMode = pickMode; + } + + /** + * Return the pickMode component of this PickTranslaeBehavior. + **/ + + public int getPickMode() { + return pickMode; + } + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (!mevent.isAltDown() && mevent.isMetaDown()){ + + tg =(TransformGroup)pickScene.pickNode(pickScene.pickClosest(xpos, ypos, pickMode), + PickObject.TRANSFORM_GROUP); + //Check for valid selection. + if ((tg != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + + translate.setTransformGroup(tg); + translate.wakeup(); + currentTG = tg; + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + + } + + /** + * Callback method from MouseTranslate + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.TRANSLATE, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + translate.setupCallback( null ); + else + translate.setupCallback( this ); + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickZoomBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickZoomBehavior.java new file mode 100644 index 0000000..a2159c7 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickZoomBehavior.java @@ -0,0 +1,180 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.picking; + +import com.sun.j3d.utils.behaviors.mouse.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + + +// A mouse behavior that allows user to pick and zoom scene graph objects. +// Common usage: 1. Create your scene graph. 2. Create this behavior with +// the root and canvas. See PickRotateBehavior for more details. + +/** + * @deprecated As of Java 3D version 1.2, replaced by + * com.sun.j3d.utils.picking.behaviors.PickZoomBehavior + * + * @see com.sun.j3d.utils.picking.behaviors.PickZoomBehavior + */ + +public class PickZoomBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseZoom zoom; + int pickMode = PickObject.USE_BOUNDS; + private PickingCallback callback = null; + private TransformGroup currentTG; + + /** + * Creates a pick/zoom behavior that waits for user mouse events for + * the scene graph. This method has its pickMode set to BOUNDS picking. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickZoomBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + zoom = new MouseZoom(MouseBehavior.MANUAL_WAKEUP); + zoom.setTransformGroup(currGrp); + currGrp.addChild(zoom); + zoom.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/zoom behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickObject.USE_BOUNDS or PickObject.USE_GEOMETRY. + * Note: If pickMode is set to PickObject.USE_GEOMETRY, all geometry object in + * the scene graph that allows pickable must have its ALLOW_INTERSECT bit set. + **/ + + public PickZoomBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds, + int pickMode){ + super(canvas, root, bounds); + zoom = new MouseZoom(MouseBehavior.MANUAL_WAKEUP); + zoom.setTransformGroup(currGrp); + currGrp.addChild(zoom); + zoom.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.pickMode = pickMode; + } + + /** + * Sets the pickMode component of this PickZoomBehavior to the value of + * the passed pickMode. + * @param pickMode the pickMode to be copied. + **/ + + public void setPickMode(int pickMode) { + this.pickMode = pickMode; + } + + + /** + * Return the pickMode component of this PickZoomBehavior. + **/ + + public int getPickMode() { + return pickMode; + } + + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (mevent.isAltDown() && !mevent.isMetaDown()){ + + tg =(TransformGroup)pickScene.pickNode(pickScene.pickClosest(xpos, ypos, pickMode), + PickObject.TRANSFORM_GROUP); + + // Check for valid selection + if ((tg != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + zoom.setTransformGroup(tg); + zoom.wakeup(); + currentTG = tg; + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + } + + /** + * Callback method from MouseZoom + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.ZOOM, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + zoom.setupCallback( null ); + else + zoom.setupCallback( this ); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickingCallback.java b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickingCallback.java new file mode 100644 index 0000000..d873f2b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/picking/PickingCallback.java @@ -0,0 +1,73 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.picking; + +import javax.media.j3d.TransformGroup; + +/** + * @deprecated As of Java 3D version 1.2, replaced by + * com.sun.j3d.utils.picking.behaviors.PickingCallback + * + * @see com.sun.j3d.utils.picking.behaviors.PickingCallback + */ + +public interface PickingCallback { + + public final static int ROTATE=0; + public final static int TRANSLATE=1; + public final static int ZOOM=2; + + /** + * The user made a selection but nothing was + * actually picked + */ + public final static int NO_PICK=3; + + /** + * Called by the Pick Behavior with which this callback + * is registered each time the Picked object is moved + */ + public void transformChanged( int type, TransformGroup tg ); +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/Mouse6DPointerBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/Mouse6DPointerBehavior.java new file mode 100644 index 0000000..c4f1e61 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/Mouse6DPointerBehavior.java @@ -0,0 +1,195 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +import java.util.Enumeration ; +import javax.media.j3d.* ; +import javax.vecmath.Point3d ; +import javax.vecmath.Vector3f ; + +/** + * This class provides basic behavior for a 6DOF mouse sensor. It + * generates a visible 3D cursor echo in the virtual world which tracks the + * position and orientation of the 6DOF mouse in the physical world. It + * can be extended to provide other functions by accessing its + * SensorEventAgent. + * + * @see SensorEventAgent + * @since Java 3D 1.3 + */ +public class Mouse6DPointerBehavior extends Behavior { + private Sensor sensor = null ; + private SensorEventAgent eventAgent = null ; + private TransformGroup echoTransformGroup = null ; + private WakeupCondition conditions = new WakeupOnElapsedFrames(0) ; + + /** + * Constructs the behavior with a default echo. To make the echo visible, + * call getEcho() to retrieve the TransformGroup that parents the echo + * geometry, and then add that TransformGroup to the scene graph. + *

+ * The default echo is a solid 6-pointed star where each point is aligned + * with the axes of the local coordinate system of the sensor, and with + * the center of the star at the location of the sensor hotspot. + * + * @param sensor a 6 degree of freedom Sensor which generates position + * and orientation relative to the tracker base. + * @param size the physical width of the echo in centimeters. + * @param enableLighting a boolean indicating whether the echo geometry + * should have lighting enabled. + */ + public Mouse6DPointerBehavior(Sensor sensor, double size, + boolean enableLighting) { + + this.sensor = sensor ; + echoTransformGroup = new TransformGroup() ; + echoTransformGroup.setCapability + (TransformGroup.ALLOW_TRANSFORM_WRITE) ; + + Point3d hotspot = new Point3d() ; + sensor.getHotspot(hotspot) ; + + Transform3D t3d = new Transform3D() ; + Vector3f v3f = new Vector3f(hotspot) ; + t3d.set(v3f) ; + + Shape3D echo = + new SensorGnomonEcho(t3d, 0.001*size, 0.005*size, enableLighting) ; + echoTransformGroup.addChild(echo) ; + + eventAgent = new SensorEventAgent(this) ; + eventAgent.addSensorReadListener(sensor, new EchoReadListener()) ; + } + + /** + * Constructs the behavior with an echo parented by the specified + * TransformGroup. + * + * @param sensor a 6 degree of freedom Sensor which generates position + * and orientation relative to the tracker base. + * @param tg a TransformGroup with a child defining the visible echo + * which will track the Sensor position and orientation; the Transform3D + * associated with the TransformGroup will be updated in order to effect + * the behavior, so it must have the ALLOW_TRANSFORM_WRITE capability + * set before the scene graph is set live + */ + public Mouse6DPointerBehavior(Sensor sensor, TransformGroup tg) { + this.sensor = sensor ; + echoTransformGroup = tg ; + eventAgent = new SensorEventAgent(this) ; + eventAgent.addSensorReadListener(sensor, new EchoReadListener()) ; + } + + /** + * Gets the sensor used by this behavior. + * + * @return the sensor used by this behavior + */ + public Sensor getSensor() { + return sensor ; + } + + /** + * Gets the echo used by this behavior. + * + * @return the TransformGroup parenting this behavior's echo geometry + */ + public TransformGroup getEcho() { + return echoTransformGroup ; + } + + /** + * Gets the SensorEventAgent used by this behavior. This can be used to + * add customized event bindings to this behavior. + * + * @return the SensorEventAgent + */ + public SensorEventAgent getSensorEventAgent() { + return eventAgent ; + } + + /** + * Initializes the behavior. + * NOTE: Applications should not call this method. It is called by the + * Java 3D behavior scheduler. + */ + public void initialize() { + wakeupOn(conditions) ; + } + + /** + * Processes a stimulus meant for this behavior. + * NOTE: Applications should not call this method. It is called by the + * Java 3D behavior scheduler. + */ + public void processStimulus(Enumeration criteria) { + eventAgent.dispatchEvents() ; + wakeupOn(conditions) ; + } + + /** + * This member class updates the echo transform in response to sensor + * reads. + */ + public class EchoReadListener implements SensorReadListener { + private Transform3D t3d = new Transform3D() ; + + public void read(SensorEvent e) { + // Get the Transform3D that transforms points from local sensor + // coordinates to virtual world coordinates, based on the primary + // view associated with this Behavior. This view is defined to be + // the first View attached to a live ViewPlatform. + // + // Note that this will display frame lag if another behavior such + // as OrbitBehavior is used to manipulate the view transform while + // the echo is visible. In order to eliminate frame lag the + // behavior driving the view transform must also compute the echo + // transform as well. See the WandViewBehavior utility for the + // appropriate techniques. + getView().getSensorToVworld(e.getSensor(), t3d) ; + echoTransformGroup.setTransform(t3d) ; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorBeamEcho.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorBeamEcho.java new file mode 100644 index 0000000..387ec5c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorBeamEcho.java @@ -0,0 +1,231 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +import javax.media.j3d.Shape3D ; +import javax.media.j3d.Material ; +import javax.media.j3d.Appearance ; +import javax.media.j3d.Transform3D ; +import javax.media.j3d.GeometryArray ; +import javax.media.j3d.TriangleStripArray ; +import javax.media.j3d.TransparencyAttributes; +import javax.vecmath.AxisAngle4f ; +import javax.vecmath.Point3d ; +import javax.vecmath.Point3f ; +import javax.vecmath.Vector3f ; + +/** + * A Shape3D representing a beam pointing from the origin of a + * sensor's local coordinate system to its hotspot. + * + * @since Java 3D 1.3 + */ +public class SensorBeamEcho extends Shape3D { + /** + * Creates a SensorBeamEcho. Read and write capabilities are granted + * for the Appearance, Material, TransparencyAttributes, and + * TransparencyAttributes mode and value. + * + * @param hotspot location of the sensor's hotspot in the sensor's + * local coordinate system; this must not be (0, 0, 0) + * @param baseWidth width of the beam in meters + * @param enableLighting boolean indicating whether normals should be + * generated and lighting enabled + * @exception IllegalArgumentException if hotspot is (0, 0, 0) + */ + public SensorBeamEcho(Point3d hotspot, double baseWidth, + boolean enableLighting) { + super() ; + + if (hotspot.distance(new Point3d()) == 0.0) + throw new IllegalArgumentException + ("\nBeam echo can't have hotspot at origin") ; + + Vector3f axis = new Vector3f((float)hotspot.x, + (float)hotspot.y, + (float)hotspot.z) ; + + Vector3f axis1 = new Vector3f() ; + axis1.normalize(axis) ; + + // Choose an arbitrary vector normal to the beam axis. + Vector3f normal = new Vector3f(0.0f, 1.0f, 0.0f) ; + normal.cross(axis1, normal) ; + if (normal.lengthSquared() < 0.5f) { + normal.set(0.0f, 0.0f, 1.0f) ; + normal.cross(axis1, normal) ; + } + normal.normalize() ; + + // Create cap vertices and normals. + int divisions = 18 ; + Point3f[] cap0 = new Point3f[divisions] ; + Point3f[] cap1 = new Point3f[divisions] ; + Vector3f[] capNormals = new Vector3f[divisions] ; + Vector3f cap0Normal = new Vector3f(axis1) ; + Vector3f cap1Normal = new Vector3f(axis1) ; + cap0Normal.negate() ; + + AxisAngle4f aa4f = new AxisAngle4f + (axis1, -(float)Math.PI/((float)divisions/2.0f)) ; + Transform3D t3d = new Transform3D() ; + t3d.set(aa4f) ; + + float halfWidth = (float)baseWidth / 2.0f ; + for (int i = 0 ; i < divisions ; i++) { + capNormals[i] = new Vector3f(normal) ; + cap0[i] = new Point3f(normal) ; + cap0[i].scale(halfWidth) ; + cap1[i] = new Point3f(cap0[i]) ; + cap1[i].add(axis) ; + t3d.transform(normal) ; + } + + // The beam cylinder is created with 3 triangle strips. The first + // strip contains the side facets (2 + 2*divisions vertices), and + // the other two strips are the caps (divisions vertices each). + int vertexCount = 2 + (4 * divisions) ; + Point3f[] vertices = new Point3f[vertexCount] ; + Vector3f[] normals = new Vector3f[vertexCount] ; + + // Side facets. + for (int i = 0 ; i < divisions ; i++) { + vertices[i*2] = cap0[i] ; + vertices[(i*2) + 1] = cap1[i] ; + + normals[i*2] = capNormals[i] ; + normals[(i*2) + 1] = capNormals[i] ; + } + + vertices[divisions*2] = cap0[0] ; + vertices[(divisions*2) + 1] = cap1[0] ; + + normals[divisions*2] = capNormals[0] ; + normals[(divisions*2) + 1] = capNormals[0] ; + + // Strips for caps created by criss-crossing the interior. + int v = (divisions+1) * 2 ; + vertices[v] = cap0[0] ; + normals[v++] = cap0Normal ; + + int j = 1 ; + int k = divisions - 1 ; + while (j <= k) { + vertices[v] = cap0[j++] ; + normals[v++] = cap0Normal ; + if (j > k) break ; + vertices[v] = cap0[k--] ; + normals[v++] = cap0Normal ; + } + + vertices[v] = cap1[0] ; + normals[v++] = cap1Normal ; + + j = 1 ; + k = divisions - 1 ; + while (j <= k) { + vertices[v] = cap1[k--] ; + normals[v++] = cap1Normal ; + if (j > k) break ; + vertices[v] = cap1[j++] ; + normals[v++] = cap1Normal ; + } + + // Create the TriangleStripArray. + int vertexFormat ; + Material m = new Material() ; + m.setCapability(Material.ALLOW_COMPONENT_READ) ; + m.setCapability(Material.ALLOW_COMPONENT_WRITE) ; + + if (enableLighting) { + vertexFormat = + GeometryArray.COORDINATES | GeometryArray.NORMALS ; + m.setLightingEnable(true) ; + } + else { + vertexFormat = GeometryArray.COORDINATES ; + m.setLightingEnable(false) ; + } + + int[] stripCounts = new int[3] ; + stripCounts[0] = 2 + (2 * divisions) ; + stripCounts[1] = divisions ; + stripCounts[2] = divisions ; + + TriangleStripArray tsa = + new TriangleStripArray(vertexCount, + vertexFormat, stripCounts) ; + + tsa.setCoordinates(0, vertices) ; + if (enableLighting) + tsa.setNormals(0, normals) ; + + Appearance a = new Appearance() ; + a.setMaterial(m) ; + a.setCapability(Appearance.ALLOW_MATERIAL_READ) ; + a.setCapability(Appearance.ALLOW_MATERIAL_WRITE) ; + + TransparencyAttributes ta = new TransparencyAttributes() ; + ta.setCapability(TransparencyAttributes.ALLOW_MODE_READ) ; + ta.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE) ; + ta.setCapability(TransparencyAttributes.ALLOW_VALUE_READ) ; + ta.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE) ; + ta.setCapability + (TransparencyAttributes.ALLOW_BLEND_FUNCTION_READ) ; + ta.setCapability + (TransparencyAttributes.ALLOW_BLEND_FUNCTION_WRITE) ; + + a.setTransparencyAttributes(ta) ; + a.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ) ; + a.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE) ; + + setGeometry(tsa) ; + setAppearance(a) ; + + setCapability(ALLOW_APPEARANCE_READ) ; + setCapability(ALLOW_APPEARANCE_WRITE) ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorButtonListener.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorButtonListener.java new file mode 100644 index 0000000..121f05b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorButtonListener.java @@ -0,0 +1,94 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +/** + * This defines the interface for handling a sensor's button events in + * conjunction with a SensorEventAgent instance. + *

+ * The events passed to this listener's methods are ephemeral; they + * are only valid until the listener has returned. If a listener needs to + * retain the event it must be copied using the + * SensorEvent(SensorEvent) constructor. + * + * @see SensorEvent + * @see SensorEventAgent + * @see SensorReadListener + * @since Java 3D 1.3 + */ +public interface SensorButtonListener { + /** + * This method is called when a sensor's button is pressed. + * + * @param e the sensor event + */ + public void pressed(SensorEvent e) ; + + /** + * This method is called when a sensor's button is released. + * + * @param e the sensor event + */ + public void released(SensorEvent e) ; + + /** + * This method is called with each invocation of the + * dispatchEvents method of SensorEventAgent + * if any button bound to the listener is down and has not changed + * state since the last invocation. The sensor value has not + * necessarily changed from the last drag event. + * + * @param e the sensor event + */ + public void dragged(SensorEvent e) ; + + /** + * This method is currently not used by SensorEventAgent, + * but is included here for future possible development. Its + * implementations should remain empty for the present. + */ + public void clicked(SensorEvent e) ; +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEvent.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEvent.java new file mode 100644 index 0000000..de0a65d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEvent.java @@ -0,0 +1,336 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +import javax.media.j3d.Sensor ; +import javax.media.j3d.Transform3D ; + +/** + * This class defines the event object that is created by a + * SensorEventAgent and passed to registered + * SensorReadListener and SensorButtonListener + * implementations. + *

+ * The events passed to the listeners are ephemeral; they are only + * valid until the listener has returned. This is done to avoid + * allocating large numbers of mostly temporary objects, especially for + * behaviors that wake up every frame. If a listener needs to retain the + * event it must be copied using the SensorEvent(SensorEvent) + * constructor. + * + * @see SensorEventAgent + * @see SensorButtonListener + * @see SensorReadListener + * @since Java 3D 1.3 + */ +public class SensorEvent { + /** + * A button pressed event. + */ + public static final int PRESSED = 1 ; + + /** + * A button released event. + */ + public static final int RELEASED = 2 ; + + /** + * A button dragged event. + */ + public static final int DRAGGED = 3 ; + + /** + * A sensor read event. + */ + public static final int READ = 4 ; + + /** + * The value that is returned by getButton when no + * buttons have changed state. + */ + public static final int NOBUTTON = -1 ; + + private int id = 0 ; + private Object source = null ; + private Sensor sensor = null ; + private int button = NOBUTTON ; + private int[] buttonState = null ; + private Transform3D sensorRead = null ; + private long time = 0 ; + private long lastTime = 0 ; + private boolean ephemeral = false ; + + /** + * Creates a new SensorEvent. + * + * @param source a reference to the originating object which + * instantiated the SensorEventAgent, usually a + * Behavior; may be null + * @param id event type + * @param sensor a reference to the provoking sensor + * @param sensorRead the sensor's read value at the time of the event + * @param buttonState the state of the sensor's buttons at the time of + * the event, where a 1 in the array indicates that the button at that + * index is down, and a 0 indicates that button is up; may be null + * @param button index of the button that changed state, from 0 to + * (buttonCount - 1), or the value NOBUTTON + * @param time the time in nanoseconds at which the + * dispatchEvents method of + * SensorEventAgent was called to generate this event, + * usually from the processStimulus method of a Behavior + * @param lastTime the time in nanoseconds at which the + * dispatchEvents method of + * SensorEventAgent was last called to generate + * events, usually from the processStimulus method of a + * Behavior; may be used to measure frame time in + * behaviors that wake up every frame + */ + public SensorEvent(Object source, int id, Sensor sensor, + Transform3D sensorRead, int[] buttonState, + int button, long time, long lastTime) { + + this.source = source ; + this.id = id ; + this.sensor = sensor ; + this.button = button ; + this.time = time ; + this.lastTime = lastTime ; + if (sensorRead == null) + throw new NullPointerException("sensorRead can't be null") ; + this.sensorRead = new Transform3D(sensorRead) ; + if (buttonState != null) { + this.buttonState = new int[buttonState.length] ; + for (int i = 0 ; i < buttonState.length ; i++) + this.buttonState[i] = buttonState[i] ; + } + this.ephemeral = false ; + } + + /** + * Creates a new ephemeral SensorEvent. In order + * to avoid creating large numbers of sensor event objects, the events + * passed to the button and read listeners by the + * dispatchEvents method of SensorEventAgent + * are valid only until the listener returns. If the event needs to + * be retained then they must be copied with the + * SensorEvent(SensorEvent) constructor. + */ + public SensorEvent() { + this.ephemeral = true ; + } + + /** + * Creates a copy of the given SensorEvent. Listeners + * must use this constructor to copy events that need to be retained. + * NOTE: The Sensor and Object references + * returned by getSensor and getSource + * remain references to the original objects. + * + * @param e the event to be copied + */ + public SensorEvent(SensorEvent e) { + this.source = e.source ; + this.id = e.id ; + this.sensor = e.sensor ; + this.button = e.button ; + this.time = e.time ; + this.lastTime = e.lastTime ; + if (e.sensorRead == null) + throw new NullPointerException("sensorRead can't be null") ; + this.sensorRead = new Transform3D(e.sensorRead) ; + if (e.buttonState != null) { + this.buttonState = new int[e.buttonState.length] ; + for (int i = 0 ; i < e.buttonState.length ; i++) + this.buttonState[i] = e.buttonState[i] ; + } + this.ephemeral = false ; + } + + /** + * Sets the fields of an ephemeral event. No objects are copied. An + * IllegalStateException will be thrown if this event + * is not ephemeral. + * + * @param source a reference to the originating object which + * instantiated the SensorEventAgent, usually a + * Behavior; may be null + * @param id event type + * @param sensor a reference to the provoking sensor + * @param sensorRead the sensor's read value at the time of the event + * @param buttonState the state of the sensor's buttons at the time of + * the event; a 1 in the array indicates that the button at that + * index is down, while a 0 indicates that button is up + * @param button index of the button that changed state, from 0 to + * (buttonCount - 1), or the value NOBUTTON + * @param time the time in nanoseconds at which the + * dispatchEvents method of + * SensorEventAgent was called to generate this event, + * usually from the processStimulus method of a Behavior + * @param lastTime the time in nanoseconds at which the + * dispatchEvents method of + * SensorEventAgent was last called to generate + * events, usually from the processStimulus method of a + * Behavior; may be used to measure frame time in + * behaviors that wake up every frame + */ + public void set(Object source, int id, Sensor sensor, + Transform3D sensorRead, int[] buttonState, + int button, long time, long lastTime) { + + if (!ephemeral) + throw new IllegalStateException + ("Can't set the fields of non-ephemeral events") ; + + this.source = source ; + this.id = id ; + this.sensor = sensor ; + if (sensorRead == null) + throw new NullPointerException("sensorRead can't be null") ; + this.sensorRead = sensorRead ; + this.buttonState = buttonState ; + this.button = button ; + this.time = time ; + this.lastTime = lastTime ; + } + + /** + * Gets a reference to the originating object which instantiated the + * SensorEventAgent, usually a Behavior; may + * be null. + * @return the originating object + */ + public Object getSource() { + return source ; + } + + /** + * Gets the event type. + * @return the event id + */ + public int getID() { + return id ; + } + + + /** + * Gets a reference to the provoking sensor. + * @return the provoking sensor + */ + public Sensor getSensor() { + return sensor ; + } + + /** + * Gets the time in nanoseconds at which the + * dispatchEvents method of SensorEventAgent + * was called to generate this event, usually from the + * processStimulus method of a Behavior. + * @return time in nanoseconds + */ + public long getTime() { + return time ; + } + + /** + * Gets the time in nanoseconds at which the + * dispatchEvents method of SensorEventAgent + * was last called to generate events, usually from the + * processStimulus method of a Behavior; may + * be used to measure frame time in behaviors that wake up every + * frame. + * @return last time in nanoseconds + */ + public long getLastTime() { + return lastTime ; + } + + /** + * Copies the sensor's read value at the time of the event into the + * given Transform3D. + * + * @param t the transform to receive the sensor read + */ + public void getSensorRead(Transform3D t) { + t.set(sensorRead) ; + } + + /** + * Gets the index of the button that changed state when passed to a + * pressed or released callback. The index + * may range from 0 to (sensor.getSensorButtonCount() - + * 1). The value returned is NOBUTTON for events + * passed to a read or dragged callback. + * @return the button index + */ + public int getButton() { + return button ; + } + + /** + * Copies the state of the sensor's buttons at the time of the event + * into the given array. A 1 in the array indicates that the button + * at that index is down, while a 0 indicates that button is up. + * @param buttonState the state of the sensor buttons + */ + public void getButtonState(int[] buttonState) { + if (buttonState.length != this.buttonState.length) + throw new ArrayIndexOutOfBoundsException + ("buttonState array is the wrong length") ; + + for (int i = 0 ; i < buttonState.length ; i++) + buttonState[i] = this.buttonState[i] ; + } + + /** + * Returns true if this event is ephemeral and is valid only + * until the listener returns. A copy of the event can be created by + * passing it to the SensorEvent(SensorEvent) + * constructor. + */ + public boolean isEphemeral() { + return ephemeral ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEventAgent.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEventAgent.java new file mode 100644 index 0000000..1c1a1bf --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorEventAgent.java @@ -0,0 +1,715 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +import java.util.Iterator ; +import java.util.List ; +import java.util.ArrayList ; +import javax.media.j3d.Sensor ; +import javax.media.j3d.Transform3D ; +import com.sun.j3d.utils.timer.J3DTimer ; + +/** + * This class works in conjunction with the SensorButtonListener + * and SensorReadListener interfaces to support an event-driven + * model of sensor interaction. Java 3D defines sensors as delivering + * continuous input data which must be polled to retrieve their values, but in + * practice it is often convenient to structure application code to respond to + * events such as button state transitions. + *

+ * Listeners registered with this class are invoked when its + * dispatchEvents method is called. This is usually called from + * the processStimulus method of a Behavior, but may + * also be called directly from the pollAndProcessInput method of + * an event-driven implementation of InputDevice. In either case + * the device is still polled by the Java 3D input device scheduling thread to + * get its current values; however, in the former, dispatchEvents + * is called from the behavior scheduler thread regardless of whether any new + * events are available, while in the latter, the InputDevice + * implementation may choose to call dispatchEvents only if new + * events are actually generated. + *

+ * Button events are generated by changes in sensor button state, from pressed + * to released and vice versa. Button state changes are examined with each + * invocation of the dispatchEvents method. Events are + * distributed to interested parties through the button listener interface + * using the pressed and released callbacks. + *

+ * The dragged method is not necessarily called in response to a + * motion event generated by a sensor. dispatchEvents will call + * dragged whenever any button assigned to the listener is down + * and has not changed state since the last time it was called. If + * dispatchEvents is called in the processStimulus + * of a Behavior, then dragged may be called even if + * the sensor value has not changed. This is as a consequence of the core + * Java 3D API definition of sensors as continuous devices. + *

+ * Like dragged, the read method of + * SensorReadListener is not necessarily invoked in response to a + * real event. It is called by dispatchEvents whenever a button + * listener has not been called for that sensor. This usually means that no + * buttons are down, but clients are free to leave a button listener null, or + * to explicitly bind a null button listener to a button so that button's + * events are ignored. The sensor value has not necessarily changed since the + * last read callback. + *

+ * A mutual exclusion policy can be applied between button + * listeners when they are grouped in an array mapped to the sensor's + * buttons. If multiple sensor buttons are held down at the same time, + * then a listener in the array is invoked only for the button that was + * depressed first. The read callback is separated from the + * pressed, released, and dragged + * callbacks in a separate interface in order to support this policy. + *

+ * The events passed to the listeners are ephemeral; they are only + * valid until the listener has returned. This is done to avoid + * allocating large numbers of mostly temporary objects, especially for + * behaviors that wake up every frame. If a listener needs to retain the + * event it must be copied using the SensorEvent(SensorEvent) + * constructor. + *

+ * It is safe to add and remove listeners in response to a callback. + * + * @see SensorEvent + * @see SensorButtonListener + * @see SensorReadListener + * @since Java 3D 1.3 + */ +public class SensorEventAgent { + private long t0 = 0 ; + private Object source = null ; + private SensorEvent e = new SensorEvent() ; + + // List of SensorBinding objects and corresponding array. + private List bindingsList = new ArrayList() ; + private SensorBinding[] bindings = new SensorBinding[0] ; + + // Indicates that lists must be converted to arrays. Need to do this + // to allow listeners to add and remove themselves or other listeners + // safely during event dispatch. + private boolean listsDirty = false ; + + /** + * Create a SensorEventAgent to generate and dispatch + * sensor events to registered listeners. + * + * @param source reference to the originating object for inclusion in + * generated SensorEvents; intended to refer to the + * instantiating Behavior but may be any reference, or null + */ + public SensorEventAgent(Object source) { + this.source = source ; + } + + /** + * This class contains all the button and read listeners registered + * with a sensor. + */ + private static class SensorBinding { + Sensor sensor = null ; + int[] buttons = null ; + Transform3D read = null ; + + // List of SensorButtonBinding objects and corresponding array. + List buttonBindingsList = new ArrayList() ; + SensorButtonBinding[] buttonBindings = new SensorButtonBinding[0] ; + + // List of SensorReadListener objects and corresponding array. + List readBindingsList = new ArrayList() ; + SensorReadListener[] readBindings = new SensorReadListener[0] ; + + SensorBinding(Sensor sensor) { + this.sensor = sensor ; + buttons = new int[sensor.getSensorButtonCount()] ; + read = new Transform3D() ; + } + + void updateArrays() { + buttonBindings = + (SensorButtonBinding[])buttonBindingsList.toArray + (new SensorButtonBinding[buttonBindingsList.size()]) ; + readBindings = + (SensorReadListener[])readBindingsList.toArray + (new SensorReadListener[readBindingsList.size()]) ; + } + + public String toString() { + String s = new String() ; + s = "sensor " + sensor + "\nbutton listener arrays:\n" ; + for (int i = 0 ; i < buttonBindingsList.size() ; i++) + s = s + ((SensorButtonBinding)buttonBindingsList.get(i)) ; + s = s + "read listeners:\n" ; + for (int i = 0 ; i < readBindingsList.size() ; i++) + s = s + " " + + ((SensorReadListener)readBindingsList.get(i)) + "\n" ; + return s ; + } + } + + /** + * This class contains an array of SensorButtonListener + * implementations, one for each sensor button. This array is used to + * support a mutual exclusion callback policy. There may be multiple + * instances of this class associated with a single sensor. + */ + private static class SensorButtonBinding { + int buttonsHandled = 0 ; + boolean[] prevButtons = null ; + boolean multiButton = false ; + SensorButtonListener[] listeners = null ; + + SensorButtonBinding(SensorButtonListener[] listeners, + boolean multiButtonEnable) { + + prevButtons = new boolean[listeners.length] ; + this.listeners = new SensorButtonListener[listeners.length] ; + + for (int i = 0 ; i < listeners.length ; i++) { + prevButtons[i] = false ; + this.listeners[i] = listeners[i] ; + } + + this.multiButton = multiButtonEnable ; + } + + public String toString() { + String s = new String() ; + s = " length " + listeners.length + + ", mutual exclusion " + (!multiButton) + "\n" ; + for (int i = 0 ; i < listeners.length ; i++) + s = s + " " + + (listeners[i] == null? + "null" : listeners[i].toString()) + "\n" ; + return s ; + } + } + + /** + * Look up the sensor listeners bound to the given sensor. + */ + private SensorBinding getSensorBinding(Sensor sensor) { + for (int i = 0 ; i < bindingsList.size() ; i++) { + SensorBinding sb = (SensorBinding)bindingsList.get(i) ; + if (sb.sensor == sensor) + return sb ; + } + return null ; + } + + /** + * Creates a binding of the specified sensor button to the given + * SensorButtonListener implementation. + * + * @param sensor the sensor with the button to be bound + * @param button the index of the button to be bound on the specified + * sensor; may range from 0 to + * (sensor.getSensorButtonCount() - 1) + * @param buttonListener the SensorButtonListener + * implementation that will be invoked for the sensor's button + */ + public synchronized void addSensorButtonListener + (Sensor sensor, int button, SensorButtonListener buttonListener) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + if (button >= sensor.getSensorButtonCount()) + throw new ArrayIndexOutOfBoundsException + ("\nbutton " + button + " >= sensor button count " + + sensor.getSensorButtonCount()) ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) { + sb = new SensorBinding(sensor) ; + bindingsList.add(sb) ; + } + + SensorButtonListener[] listeners = + new SensorButtonListener[sb.buttons.length] ; + + // Assign only the specified button; others remain null. + listeners[button] = buttonListener ; + SensorButtonBinding sbb = + new SensorButtonBinding(listeners, true) ; + + sb.buttonBindingsList.add(sbb) ; + listsDirty = true ; + } + + /** + * Creates a binding from all the buttons on the specified sensor to + * the given SensorButtonListener implementation. If + * multiple sensor buttons are held down at the same time, the press + * and release callbacks are called for each button in the order that + * they occur. This allows actions to be bound to combinations of + * button presses, but is also convenient for listeners that don't + * care which button was pressed. + * + * @param sensor the sensor to be bound + * @param buttonListener the SensorButtonListener + * implementation that will be called for all button events + */ + public synchronized void addSensorButtonListener + (Sensor sensor, SensorButtonListener buttonListener) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) { + sb = new SensorBinding(sensor) ; + bindingsList.add(sb) ; + } + + SensorButtonListener[] listeners = + new SensorButtonListener[sb.buttons.length] ; + + // All buttons are bound to the same listener. + for (int i = 0 ; i < sb.buttons.length ; i++) + listeners[i] = buttonListener ; + + SensorButtonBinding sbb = + new SensorButtonBinding(listeners, true) ; + + sb.buttonBindingsList.add(sbb) ; + listsDirty = true ; + } + + /** + * Creates a binding of the specified sensor to the given array of + * SensorButtonListener implementations. The array index + * of the listener indicates the index of the sensor button to which + * it will be bound. + *

+ * This method enforces a mutually exclusive callback policy + * among the listeners specified in the array. If multiple sensor + * buttons are held down at the same time, callbacks are invoked only + * for the button that was depressed first. + * + * @param sensor the sensor to be bound + * @param buttonListeners array of implementations of + * SensorButtonListener; array entries may be null or + * duplicates but the array length must equal the sensor's button + * count + */ + public synchronized void addSensorButtonListeners + (Sensor sensor, SensorButtonListener[] buttonListeners) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) { + sb = new SensorBinding(sensor) ; + bindingsList.add(sb) ; + } + + if (sb.buttons.length != buttonListeners.length) + throw new IllegalArgumentException + ("\nbuttonListeners length " + buttonListeners.length + + " must equal sensor button count " + sb.buttons.length) ; + + SensorButtonBinding sbb = + new SensorButtonBinding(buttonListeners, false) ; + + sb.buttonBindingsList.add(sbb) ; + listsDirty = true ; + } + + /** + * Gets the SensorButtonListener implementations bound to + * the given sensor and button. + * + * @param sensor the sensor of interest + * @param button the button of interest + * @return array of SensorButtonListener implementations + * bound to the given sensor and button, or null + */ + public SensorButtonListener[] getSensorButtonListeners(Sensor sensor, + int button) { + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + if (button >= sensor.getSensorButtonCount()) + throw new ArrayIndexOutOfBoundsException + ("\nbutton " + button + " >= sensor button count " + + sensor.getSensorButtonCount()) ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) + return null ; + + ArrayList listeners = new ArrayList() ; + for (int i = 0 ; i < sb.buttonBindingsList.size() ; i++) { + SensorButtonBinding sbb = + (SensorButtonBinding)sb.buttonBindingsList.get(i) ; + + if (sbb.listeners[button] != null) + listeners.add(sbb.listeners[button]) ; + } + + if (listeners.size() == 0) + return null ; + else + return (SensorButtonListener[])listeners.toArray + (new SensorButtonListener[listeners.size()]) ; + } + + /** + * Remove the SensorButtonListener from the given SensorBinding. + */ + private void removeSensorButtonListener + (SensorBinding sb, SensorButtonListener listener) { + + Iterator i = sb.buttonBindingsList.iterator() ; + while (i.hasNext()) { + int instanceCount = 0 ; + SensorButtonBinding sbb = (SensorButtonBinding)i.next() ; + + for (int j = 0 ; j < sbb.listeners.length ; j++) { + if (sbb.listeners[j] == listener) + sbb.listeners[j] = null ; + else if (sbb.listeners[j] != null) + instanceCount++ ; + } + if (instanceCount == 0) { + i.remove() ; + } + } + listsDirty = true ; + } + + /** + * Remove the given SensorButtonListener binding from the + * specified sensor. + * + * @param sensor the sensor from which to remove the listener + * @param listener the listener to be removed + */ + public synchronized void removeSensorButtonListener + (Sensor sensor, SensorButtonListener listener) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) + return ; + + removeSensorButtonListener(sb, listener) ; + if (sb.buttonBindingsList.size() == 0 && + sb.readBindingsList.size() == 0) + removeSensorBinding(sensor) ; + + listsDirty = true ; + } + + /** + * Remove the given SensorButtonListener from all sensors. + * + * @param listener the listener to remove + */ + public synchronized void removeSensorButtonListener + (SensorButtonListener listener) { + + Iterator i = bindingsList.iterator() ; + while (i.hasNext()) { + SensorBinding sb = (SensorBinding)i.next() ; + removeSensorButtonListener(sb, listener) ; + + if (sb.buttonBindingsList.size() == 0 && + sb.readBindingsList.size() == 0) { + i.remove() ; + } + } + listsDirty = true ; + } + + /** + * Creates a binding of the specified sensor to the given + * SensorReadListener. The read listener is invoked + * every time dispatchEvents is called and a button + * listener is not invoked. + * + * @param sensor the sensor to be bound + * @param readListener the SensorReadListener + * implementation + */ + public synchronized void addSensorReadListener + (Sensor sensor, SensorReadListener readListener) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) { + sb = new SensorBinding(sensor) ; + bindingsList.add(sb) ; + } + sb.readBindingsList.add(readListener) ; + listsDirty = true ; + } + + /** + * Gets the SensorReadListeners bound to the specified + * sensor. + * + * @param sensor the sensor of interest + * @return array of SensorReadListeners bound to the + * given sensor, or null + */ + public SensorReadListener[] getSensorReadListeners(Sensor sensor) { + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) + return null ; + else if (sb.readBindingsList.size() == 0) + return null ; + else + return (SensorReadListener[])sb.readBindingsList.toArray + (new SensorReadListener[sb.readBindingsList.size()]) ; + } + + /** + * Remove the SensorReadListener from the given SensorBinding. + */ + private void removeSensorReadListener + (SensorBinding sb, SensorReadListener listener) { + + Iterator i = sb.readBindingsList.iterator() ; + while (i.hasNext()) { + if (((SensorReadListener)i.next()) == listener) + i.remove() ; + } + listsDirty = true ; + } + + /** + * Remove the given SensorReadListener binding from the + * specified sensor. + * + * @param sensor the sensor from which to remove the listener + * @param listener the listener to be removed + */ + public synchronized void removeSensorReadListener + (Sensor sensor, SensorReadListener listener) { + + if (sensor == null) + throw new NullPointerException("\nsensor is null") ; + + SensorBinding sb = getSensorBinding(sensor) ; + if (sb == null) + return ; + + removeSensorReadListener(sb, listener) ; + if (sb.buttonBindingsList.size() == 0 && + sb.readBindingsList.size() == 0) + removeSensorBinding(sensor) ; + + listsDirty = true ; + } + + /** + * Remove the given SensorReadListener from all sensors. + * + * @param listener the listener to remove + */ + public synchronized void removeSensorReadListener + (SensorReadListener listener) { + + Iterator i = bindingsList.iterator() ; + while (i.hasNext()) { + SensorBinding sb = (SensorBinding)i.next() ; + removeSensorReadListener(sb, listener) ; + + if (sb.buttonBindingsList.size() == 0 && + sb.readBindingsList.size() == 0) { + i.remove() ; + } + } + listsDirty = true ; + } + + /** + * Remove all sensor listeners bound to the given sensor. + */ + public synchronized void removeSensorBinding(Sensor sensor) { + Iterator i = bindingsList.iterator() ; + while (i.hasNext()) { + SensorBinding sb = (SensorBinding)i.next() ; + if (sb.sensor == sensor) { + i.remove() ; + break ; + } + } + listsDirty = true ; + } + + /** + * Returns an array of references to all sensors that have been bound + * to listeners. + * @return an array of sensors, or null if no sensors have been bound + */ + public Sensor[] getSensors() { + if (bindingsList.size() == 0) + return null ; + + Sensor[] s = new Sensor[bindingsList.size()] ; + for (int i = 0 ; i < bindingsList.size() ; i++) + s[i] = ((SensorBinding)bindingsList.get(i)).sensor ; + + return s ; + } + + /** + * Copies binding lists to arrays for event dispatch. This allows + * listeners to add or remove themselves or other listeners safely. + */ + private synchronized void updateArrays() { + bindings = (SensorBinding[])bindingsList.toArray + (new SensorBinding[bindingsList.size()]) ; + + for (int i = 0 ; i < bindings.length ; i++) { + bindings[i].updateArrays() ; + } + } + + /** + * Reads all sensor button state and dispatches events to registered + * button and read listeners. This method is intended to be called from + * the processStimulus implementation of a + * Behavior or the pollAndProcessInput method of + * an event-driven implementation of InputDevice. + */ + public void dispatchEvents() { + long t1 = t0 ; + t0 = J3DTimer.getValue() ; + + if (listsDirty) { + updateArrays() ; + listsDirty = false ; + } + + // Loop through all sensor bindings. + for (int k = 0 ; k < bindings.length ; k++) { + SensorBinding sb = bindings[k] ; + Sensor s = sb.sensor ; + Transform3D read = sb.read ; + int[] buttons = sb.buttons ; + int dragButton = 0 ; + boolean callReadListeners = true ; + boolean callDraggedListener = false ; + + // Get this sensor's readings. + s.getRead(read) ; + s.lastButtons(buttons) ; + + // Dispatch button listeners. + for (int j = 0 ; j < sb.buttonBindings.length ; j++) { + SensorButtonBinding sbb = sb.buttonBindings[j] ; + for (int i = 0 ; i < buttons.length ; i++) { + if (sbb.listeners[i] == null) + continue ; + + // Check for button release. + if (sbb.prevButtons[i]) { + if (buttons[i] == 0) { + e.set(source, SensorEvent.RELEASED, s, read, + buttons, i, t0, t1) ; + sbb.listeners[i].released(e) ; + sbb.prevButtons[i] = false ; + sbb.buttonsHandled-- ; + } + else { + callDraggedListener = true ; + dragButton = i ; + } + callReadListeners = false ; + } + // Check for button press. + // Ignore multiple button presses if not enabled; + // otherwise, one listener is bound to all buttons. + else if (buttons[i] == 1) { + if (sbb.buttonsHandled == 0 || sbb.multiButton) { + e.set(source, SensorEvent.PRESSED, s, read, + buttons, i, t0, t1) ; + sbb.listeners[i].pressed(e) ; + sbb.prevButtons[i] = true ; + sbb.buttonsHandled++ ; + callReadListeners = false ; + } + } + } + if (callDraggedListener) { + // One drag event even if multiple buttons down. + // Called after all pressed() and released() calls. + e.set(source, SensorEvent.DRAGGED, s, read, buttons, + SensorEvent.NOBUTTON, t0, t1) ; + sbb.listeners[dragButton].dragged(e) ; + } + } + // Dispatch read listeners. + if (callReadListeners) { + e.set(source, SensorEvent.READ, s, read, + buttons, SensorEvent.NOBUTTON, t0, t1) ; + for (int r = 0 ; r < sb.readBindings.length ; r++) { + sb.readBindings[r].read(e) ; + } + } + } + } + + public String toString() { + String s = "SensorEventAgent@" + Integer.toHexString(hashCode()) ; + s += "\nsensor bindings:\n\n" ; + for (int i = 0 ; i < bindingsList.size() ; i++) { + s += ((SensorBinding)bindingsList.get(i)).toString() + "\n" ; + } + return s ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorGnomonEcho.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorGnomonEcho.java new file mode 100644 index 0000000..8cdb576 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorGnomonEcho.java @@ -0,0 +1,225 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +import javax.media.j3d.Shape3D ; +import javax.media.j3d.Material ; +import javax.media.j3d.Appearance ; +import javax.media.j3d.Transform3D ; +import javax.media.j3d.GeometryArray ; +import javax.media.j3d.TriangleArray ; +import javax.media.j3d.TransparencyAttributes; +import javax.vecmath.Point3f ; +import javax.vecmath.Vector3f ; + +/** + * A Shape3D representing a gnomon pointing along each coordinate + * axis. The base of the gnomon is a cube, and the coordinate axes are + * represented by pyramids attached to each face of the cube. + * + * @since Java 3D 1.3 + */ +public class SensorGnomonEcho extends Shape3D { + /** + * Constructs a SensorGnomonEcho. Read and write capabilities are + * granted for the Appearance, Material, TransparencyAttributes, + * and TransparencyAttributes mode and value. + * + * @param transform translation and/or rotation to apply to the gnomon + * geometry; this should be the position and orientation of the sensor + * hotspot in the sensor's local coordinate system + * @param baseWidth width of each edge of the base cube in meters + * @param axisLength distance in meters from the gnomon center to + * the apex of the pyramid attached to each face of the base cube + * @param enableLighting boolean indicating whether normals should be + * generated and lighting enabled + */ + public SensorGnomonEcho(Transform3D transform, + double baseWidth, + double axisLength, + boolean enableLighting) { + super() ; + + int FRONT = 0 ; + int BACK = 1 ; + int LEFT = 2 ; + int RIGHT = 3 ; + int TOP = 4 ; + int BOTTOM = 5 ; + Point3f[] axes = new Point3f[6] ; + float length = (float)axisLength ; + + axes[FRONT] = new Point3f(0f, 0f, length) ; + axes[BACK] = new Point3f(0f, 0f, -length) ; + axes[LEFT] = new Point3f(-length, 0f, 0f) ; + axes[RIGHT] = new Point3f( length, 0f, 0f) ; + axes[TOP] = new Point3f(0f, length, 0f) ; + axes[BOTTOM] = new Point3f(0f, -length, 0f) ; + + if (transform != null) + for (int i = FRONT ; i <= BOTTOM ; i++) + transform.transform(axes[i]) ; + + float offset = (float)baseWidth / 2.0f ; + Point3f[][] cube = new Point3f[6][4] ; + + cube[FRONT][0] = new Point3f(-offset, -offset, offset) ; + cube[FRONT][1] = new Point3f( offset, -offset, offset) ; + cube[FRONT][2] = new Point3f( offset, offset, offset) ; + cube[FRONT][3] = new Point3f(-offset, offset, offset) ; + + cube[BACK][0] = new Point3f( offset, -offset, -offset) ; + cube[BACK][1] = new Point3f(-offset, -offset, -offset) ; + cube[BACK][2] = new Point3f(-offset, offset, -offset) ; + cube[BACK][3] = new Point3f( offset, offset, -offset) ; + + if (transform != null) + for (int i = FRONT ; i <= BACK ; i++) + for (int j = 0 ; j < 4 ; j++) + transform.transform(cube[i][j]) ; + + cube[LEFT][0] = cube[BACK][1] ; + cube[LEFT][1] = cube[FRONT][0] ; + cube[LEFT][2] = cube[FRONT][3] ; + cube[LEFT][3] = cube[BACK][2] ; + + cube[RIGHT][0] = cube[FRONT][1] ; + cube[RIGHT][1] = cube[BACK][0] ; + cube[RIGHT][2] = cube[BACK][3] ; + cube[RIGHT][3] = cube[FRONT][2] ; + + cube[TOP][0] = cube[FRONT][3] ; + cube[TOP][1] = cube[FRONT][2] ; + cube[TOP][2] = cube[BACK][3] ; + cube[TOP][3] = cube[BACK][2] ; + + cube[BOTTOM][0] = cube[BACK][1] ; + cube[BOTTOM][1] = cube[BACK][0] ; + cube[BOTTOM][2] = cube[FRONT][1] ; + cube[BOTTOM][3] = cube[FRONT][0] ; + + int v = 0 ; + Point3f[] vertices = new Point3f[72] ; + + for (int i = 0 ; i < 6 ; i++) { + vertices[v++] = cube[i][0] ; + vertices[v++] = cube[i][1] ; + vertices[v++] = axes[i] ; + vertices[v++] = cube[i][1] ; + vertices[v++] = cube[i][2] ; + vertices[v++] = axes[i] ; + vertices[v++] = cube[i][2] ; + vertices[v++] = cube[i][3] ; + vertices[v++] = axes[i] ; + vertices[v++] = cube[i][3] ; + vertices[v++] = cube[i][0] ; + vertices[v++] = axes[i] ; + } + + int vertexFormat ; + Material m = new Material() ; + m.setCapability(Material.ALLOW_COMPONENT_READ) ; + m.setCapability(Material.ALLOW_COMPONENT_WRITE) ; + + if (enableLighting) { + vertexFormat = + GeometryArray.COORDINATES | GeometryArray.NORMALS ; + m.setLightingEnable(true) ; + } + else { + vertexFormat = GeometryArray.COORDINATES ; + m.setLightingEnable(false) ; + } + + TriangleArray ta = new TriangleArray(72, vertexFormat) ; + ta.setCoordinates(0, vertices) ; + + if (enableLighting) { + Vector3f v0 = new Vector3f() ; + Vector3f v1 = new Vector3f() ; + Vector3f[] normals = new Vector3f[72] ; + + for (int i = 0 ; i < 72 ; i += 3) { + v0.sub(vertices[i+1], vertices[i]) ; + v1.sub(vertices[i+2], vertices[i]) ; + + Vector3f n = new Vector3f() ; + n.cross(v0, v1) ; + n.normalize() ; + + normals[i] = n ; + normals[i+1] = n ; + normals[i+2] = n ; + } + ta.setNormals(0, normals) ; + } + + Appearance a = new Appearance() ; + a.setMaterial(m) ; + a.setCapability(Appearance.ALLOW_MATERIAL_READ) ; + a.setCapability(Appearance.ALLOW_MATERIAL_WRITE) ; + + TransparencyAttributes tra = new TransparencyAttributes() ; + tra.setCapability(TransparencyAttributes.ALLOW_MODE_READ) ; + tra.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE) ; + tra.setCapability(TransparencyAttributes.ALLOW_VALUE_READ) ; + tra.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE) ; + ta.setCapability + (TransparencyAttributes.ALLOW_BLEND_FUNCTION_READ) ; + ta.setCapability + (TransparencyAttributes.ALLOW_BLEND_FUNCTION_WRITE) ; + + a.setTransparencyAttributes(tra) ; + a.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ) ; + a.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE) ; + + setGeometry(ta) ; + setAppearance(a) ; + + setCapability(ALLOW_APPEARANCE_READ) ; + setCapability(ALLOW_APPEARANCE_WRITE) ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorInputAdaptor.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorInputAdaptor.java new file mode 100644 index 0000000..ed91bac --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorInputAdaptor.java @@ -0,0 +1,71 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +/** + * The adaptor which receives sensor button and read events. The methods + * in this class are empty; the ones of interest should be overridden by + * classes extending this adaptor. + * + * @since Java 3D 1.3 + */ +public class SensorInputAdaptor + implements SensorButtonListener, SensorReadListener { + + public void pressed(SensorEvent e) { + } + + public void released(SensorEvent e) { + } + + public void dragged(SensorEvent e) { + } + + public void clicked(SensorEvent e) { + } + + public void read(SensorEvent e) { + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorReadListener.java b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorReadListener.java new file mode 100644 index 0000000..ceee4f7 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/sensor/SensorReadListener.java @@ -0,0 +1,72 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.sensor ; + +/** + * This defines the interface for handling a sensor's read events in + * conjuction with a SensorEventAgent instance. + *

+ * The events passed to this listener's methods are ephemeral; they + * are only valid until the listener has returned. If a listener needs to + * retain the event it must be copied using the + * SensorEvent(SensorEvent) constructor. + * + * @see SensorEvent + * @see SensorEventAgent + * @see SensorButtonListener + * @since Java 3D 1.3 + */ +public interface SensorReadListener { + /** + * This method is called each time the dispatchEvents + * method of SensorEventAgent is called and none of a + * sensor's buttons have been handled by a button listener. The + * sensor read value has not necessarily changed since the last read + * event. + * + * @param e the sensor event + */ + public void read(SensorEvent e) ; +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/vp/OrbitBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/vp/OrbitBehavior.java new file mode 100644 index 0000000..9327c1f --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/vp/OrbitBehavior.java @@ -0,0 +1,1047 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.vp; + +import java.awt.event.ComponentEvent; +import java.awt.event.MouseEvent; +import java.awt.event.KeyEvent; +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Cursor; +import javax.swing.SwingUtilities; + +import javax.media.j3d.WakeupOnAWTEvent; +import javax.media.j3d.WakeupOnElapsedFrames; +import javax.media.j3d.TransformGroup; +import javax.media.j3d.Transform3D; +import javax.media.j3d.View; +import javax.media.j3d.Canvas3D; + +import javax.vecmath.Vector3d; +import javax.vecmath.Point3d; +import javax.vecmath.Matrix3d; + +import com.sun.j3d.utils.universe.ViewingPlatform; + +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * Moves the View around a point of interest when the mouse is dragged with + * a mouse button pressed. Includes rotation, zoom, and translation + * actions. + *

+ * This behavior must be added to the ViewingPlatform + * using the ViewingPlatform.setViewPlatformBehavior method. + *

+ * The rotate action rotates the ViewPlatform around the point of interest + * when the mouse is moved with the main mouse button pressed. The + * rotation is in the direction of the mouse movement, with a default + * rotation of 0.01 radians for each pixel of mouse movement. + *

+ * The zoom action moves the ViewPlatform closer to or further from the + * point of interest when the mouse is moved with the middle mouse button + * pressed (or Alt-main mouse button on systems without a middle mouse button). + * The default zoom action is to translate the ViewPlatform 0.01 units for each + * pixel of mouse movement. Moving the mouse up moves the ViewPlatform closer, + * moving the mouse down moves the ViewPlatform further away. + *

+ * By default, the zoom action allows the ViewPlatform to move through + * the center of rotation to orbit at a negative radius. + * The STOP_ZOOM constructor flag will stop the ViewPlatform at + * a minimum radius from the center. The default minimum radius is 0.0 + * and can be set using the setMinRadius method. + *

+ * The PROPORTIONAL_ZOOM constructor flag changes the zoom action + * to move the ViewPlatform proportional to its distance from the center + * of rotation. For this mode, the default action is to move the ViewPlatform + * by 1% of its distance from the center of rotation for each pixel of + * mouse movement. + *

+ * The translate action translates the ViewPlatform when the mouse is moved + * with the right mouse button pressed (Shift-main mouse button on systems + * without a right mouse button). The translation is in the direction of the + * mouse movement, with a default translation of 0.01 units for each pixel + * of mouse movement. + *

+ * The sensitivity of the actions can be scaled using the + * setActionFactor() methods which scale + * the default movement by the factor. The rotate and translate actions + * have separate factors for x and y. + *

+ * The actions can be reversed using the REVERSE_ACTION + * constructor flags. The default action moves the ViewPlatform around the + * objects in the scene. The REVERSE_ACTION flags can + * make the objects in the scene appear to be moving in the direction + * of the mouse movement. + *

+ * The actions can be disabled by either using the + * DISABLE_ACTION constructor flags or the + * setActionEnable methods. + *

+ * The default center of rotation is (0, 0, 0) and can be set using the + * setRotationCenter() method. + * + * @since Java 3D 1.2.1 + */ +public class OrbitBehavior extends ViewPlatformAWTBehavior { + + private Transform3D velocityTransform = new Transform3D(); + private Transform3D longditudeTransform = new Transform3D(); + private Transform3D rollTransform = new Transform3D(); + private Transform3D latitudeTransform = new Transform3D(); + private Transform3D rotateTransform = new Transform3D(); + + // needed for integrateTransforms but don't want to new every time + private Transform3D temp1 = new Transform3D(); + private Transform3D temp2 = new Transform3D(); + private Transform3D translation = new Transform3D(); + private Vector3d transVector = new Vector3d(); + private Vector3d distanceVector = new Vector3d(); + private Vector3d centerVector = new Vector3d(); + private Vector3d invertCenterVector = new Vector3d(); + + private double longditude = 0.0; + private double latitude = 0.0; + private double rollAngle = 0.0; + private double startDistanceFromCenter = 20.0; + private double distanceFromCenter = 20.0; + private final double MAX_MOUSE_ANGLE = Math.toRadians( 3 ); + private final double ZOOM_FACTOR = 1.0; + private Point3d rotationCenter = new Point3d(); + private Matrix3d rotMatrix = new Matrix3d(); + private Transform3D currentXfm = new Transform3D(); + + private int mouseX = 0; + private int mouseY = 0; + + private double rotXFactor = 1.0; + private double rotYFactor = 1.0; + private double transXFactor = 1.0; + private double transYFactor = 1.0; + private double zoomFactor = 1.0; + + private double xtrans = 0.0; + private double ytrans = 0.0; + private double ztrans = 0.0; + + private boolean zoomEnabled = true; + private boolean rotateEnabled = true; + private boolean translateEnabled = true; + private boolean reverseRotate = false; + private boolean reverseTrans = false; + private boolean reverseZoom = false; + private boolean stopZoom = false; + private boolean proportionalZoom = false; + private double minRadius = 0.0; + private int leftButton = ROTATE; + private int rightButton = TRANSLATE; + private int middleButton = ZOOM; + + /** + * Constructor flag to reverse the rotate behavior + */ + public static final int REVERSE_ROTATE = 0x010; + + /** + * Constructor flag to reverse the translate behavior + */ + public static final int REVERSE_TRANSLATE = 0x020; + + /** + * Constructor flag to reverse the zoom behavior + */ + public static final int REVERSE_ZOOM = 0x040; + + /** + * Constructor flag to reverse all the behaviors + */ + public static final int REVERSE_ALL = (REVERSE_ROTATE | REVERSE_TRANSLATE | + REVERSE_ZOOM); + + /** + * Constructor flag that indicates zoom should stop when it reaches + * the minimum orbit radius set by setMinRadius(). The minimus + * radius default is 0.0. + */ + public static final int STOP_ZOOM = 0x100; + + /** + * Constructor flag to disable rotate + */ + public static final int DISABLE_ROTATE = 0x200; + + /** + * Constructor flag to disable translate + */ + public static final int DISABLE_TRANSLATE = 0x400; + + /** + * Constructor flag to disable zoom + */ + public static final int DISABLE_ZOOM = 0x800; + + /** + * Constructor flag to use proportional zoom, which determines + * how much you zoom based on view's distance from the center of + * rotation. The percentage of distance that the viewer zooms + * is determined by the zoom factor. + */ + public static final int PROPORTIONAL_ZOOM = 0x1000; + + /** + * Used to set the fuction for a mouse button to Rotate + */ + private static final int ROTATE = 0; + + /** + * Used to set the function for a mouse button to Translate + */ + private static final int TRANSLATE = 1; + + /** + * Used to set the function for a mouse button to Zoom + */ + private static final int ZOOM = 2; + + private static final double NOMINAL_ZOOM_FACTOR = .01; + private static final double NOMINAL_PZOOM_FACTOR = 1.0; + private static final double NOMINAL_ROT_FACTOR = .01; + private static final double NOMINAL_TRANS_FACTOR = .01; + + private double rotXMul = NOMINAL_ROT_FACTOR * rotXFactor; + private double rotYMul = NOMINAL_ROT_FACTOR * rotYFactor; + private double transXMul = NOMINAL_TRANS_FACTOR * transXFactor; + private double transYMul = NOMINAL_TRANS_FACTOR * transYFactor; + private double zoomMul = NOMINAL_ZOOM_FACTOR * zoomFactor; + + /** + * Parameterless constructor for this behavior. This is intended for use + * by ConfiguredUniverse, which requires such a constructor for + * configurable behaviors. The Canvas3D used to listen for mouse and + * mouse motion events is obtained from the superclass + * setViewingPlatform() method. + * @since Java 3D 1.3 + */ + public OrbitBehavior() { + super(MOUSE_LISTENER | MOUSE_MOTION_LISTENER); + } + + /** + * Creates a new OrbitBehavior + * + * @param c The Canvas3D to add the behavior to + */ + public OrbitBehavior(Canvas3D c) { + this(c, 0 ); + } + + /** + * Creates a new OrbitBehavior + * + * @param c The Canvas3D to add the behavior to + * @param flags The option flags + */ + public OrbitBehavior(Canvas3D c, int flags) { + super(c, MOUSE_LISTENER | MOUSE_MOTION_LISTENER | flags ); + + if ((flags & DISABLE_ROTATE) != 0) rotateEnabled = false; + if ((flags & DISABLE_ZOOM) != 0) zoomEnabled = false; + if ((flags & DISABLE_TRANSLATE) != 0) translateEnabled = false; + if ((flags & REVERSE_TRANSLATE) != 0) reverseTrans = true; + if ((flags & REVERSE_ROTATE) != 0) reverseRotate = true; + if ((flags & REVERSE_ZOOM) != 0) reverseZoom = true; + if ((flags & STOP_ZOOM) != 0) stopZoom = true; + if ((flags & PROPORTIONAL_ZOOM) !=0) { + proportionalZoom = true; + zoomMul = NOMINAL_PZOOM_FACTOR * zoomFactor; + } + } + + protected synchronized void processAWTEvents( final AWTEvent[] events ) { + motion = false; + for(int i=0; i + minRadius) { + distanceFromCenter -= (zoomMul*ychange* + distanceFromCenter/100.0); + } + else { + distanceFromCenter = minRadius; + } + } + else { + if ((distanceFromCenter + + (zoomMul*ychange*distanceFromCenter/100.0)) + > minRadius) { + distanceFromCenter += (zoomMul*ychange* + distanceFromCenter/100.0); + } + else { + distanceFromCenter = minRadius; + } + } + } + else { + if (stopZoom) { + if (reverseZoom) { + if ((distanceFromCenter - ychange*zoomMul) > minRadius) { + distanceFromCenter -= ychange*zoomMul; + } + else { + distanceFromCenter = minRadius; + } + } + else { + if ((distanceFromCenter + ychange*zoomMul) > minRadius) { + distanceFromCenter += ychange * zoomMul; + } + else { + distanceFromCenter = minRadius; + } + } + } + else { + if (reverseZoom) { + distanceFromCenter -= ychange*zoomMul; + } + else { + distanceFromCenter += ychange*zoomMul; + } + } + } + } + mouseX = evt.getX(); + mouseY = evt.getY(); + motion = true; + } else if (evt.getID()==MouseEvent.MOUSE_RELEASED ) { + } + + } + + /** + * Sets the ViewingPlatform for this behavior. This method is + * called by the ViewingPlatform. + * If a sub-calls overrides this method, it must call + * super.setViewingPlatform(vp). + * NOTE: Applications should not call this method. + */ + public void setViewingPlatform(ViewingPlatform vp) { + super.setViewingPlatform( vp ); + + if (vp!=null) { + resetView(); + integrateTransforms(); + } + } + + /** + * Reset the orientation and distance of this behavior to the current + * values in the ViewPlatform Transform Group + */ + private void resetView() { + Vector3d centerToView = new Vector3d(); + + targetTG.getTransform( targetTransform ); + + targetTransform.get( rotMatrix, transVector ); + centerToView.sub( transVector, rotationCenter ); + distanceFromCenter = centerToView.length(); + startDistanceFromCenter = distanceFromCenter; + + targetTransform.get( rotMatrix ); + rotateTransform.set( rotMatrix ); + + // compute the initial x/y/z offset + temp1.set(centerToView); + rotateTransform.invert(); + rotateTransform.mul(temp1); + rotateTransform.get(centerToView); + xtrans = centerToView.x; + ytrans = centerToView.y; + ztrans = centerToView.z; + + // reset rotMatrix + rotateTransform.set( rotMatrix ); + } + + protected synchronized void integrateTransforms() { + // Check if the transform has been changed by another + // behavior + targetTG.getTransform(currentXfm) ; + if (! targetTransform.equals(currentXfm)) + resetView() ; + + longditudeTransform.rotY( longditude ); + latitudeTransform.rotX( latitude ); + rotateTransform.mul(rotateTransform, latitudeTransform); + rotateTransform.mul(rotateTransform, longditudeTransform); + + distanceVector.z = distanceFromCenter - startDistanceFromCenter; + + temp1.set(distanceVector); + temp1.mul(rotateTransform, temp1); + + // want to look at rotationCenter + transVector.x = rotationCenter.x + xtrans; + transVector.y = rotationCenter.y + ytrans; + transVector.z = rotationCenter.z + ztrans; + + translation.set(transVector); + targetTransform.mul(temp1, translation); + + // handle rotationCenter + temp1.set(centerVector); + temp1.mul(targetTransform); + + invertCenterVector.x = -centerVector.x; + invertCenterVector.y = -centerVector.y; + invertCenterVector.z = -centerVector.z; + + temp2.set(invertCenterVector); + targetTransform.mul(temp1, temp2); + + targetTG.setTransform(targetTransform); + + // reset yaw and pitch angles + longditude = 0.0; + latitude = 0.0; + } + + /** + * Sets the center around which the View rotates. + * The default is (0,0,0). + * @param center The Point3d to set the center of rotation to + */ + public synchronized void setRotationCenter(Point3d center) { + rotationCenter.x = center.x; + rotationCenter.y = center.y; + rotationCenter.z = center.z; + centerVector.set(rotationCenter); + } + + /** + * Property which sets the center around which the View rotates. + * Used by ConfiguredUniverse. + * @param center array of length 1 containing an instance of Point3d + * @since Java 3D 1.3 + */ + public void RotationCenter(Object[] center) { + if (! (center.length == 1 && center[0] instanceof Point3d)) + throw new IllegalArgumentException + ("RotationCenter must be a single Point3d"); + + setRotationCenter((Point3d)center[0]); + } + + /** + * Places the value of the center around which the View rotates + * into the Point3d. + * @param center The Point3d + */ + public void getRotationCenter(Point3d center) { + center.x = rotationCenter.x; + center.y = rotationCenter.y; + center.z = rotationCenter.z; + } + + // TODO + // Need to add key factors for Rotate, Translate and Zoom + // Method calls should just update MAX_KEY_ANGLE, KEY_TRANSLATE and + // KEY_ZOOM + // + // Methods also need to correctly set sign of variables depending on + // the Reverse settings. + + /** + * Sets the rotation x and y factors. The factors are used to determine + * how many radians to rotate the view for each pixel of mouse movement. + * The view is rotated factor * 0.01 radians for each pixel of mouse + * movement. The default factor is 1.0. + * @param xfactor The x movement multiplier + * @param yfactor The y movement multiplier + **/ + public synchronized void setRotFactors(double xfactor, double yfactor) { + rotXFactor = xfactor; + rotYFactor = yfactor; + rotXMul = NOMINAL_ROT_FACTOR * xfactor; + rotYMul = NOMINAL_ROT_FACTOR * yfactor; + } + + /** + * Property which sets the rotation x and y factors. + * Used by ConfiguredUniverse. + * @param factors array of length 2 containing instances of Double + * @since Java 3D 1.3 + */ + public void RotFactors(Object[] factors) { + if (! (factors.length == 2 && + factors[0] instanceof Double && factors[1] instanceof Double)) + throw new IllegalArgumentException + ("RotFactors must be two Doubles"); + + setRotFactors(((Double)factors[0]).doubleValue(), + ((Double)factors[1]).doubleValue()); + } + + /** + * Sets the rotation x factor. The factors are used to determine + * how many radians to rotate the view for each pixel of mouse movement. + * The view is rotated factor * 0.01 radians for each pixel of mouse + * movement. The default factor is 1.0. + * @param xfactor The x movement multiplier + **/ + public synchronized void setRotXFactor(double xfactor) { + rotXFactor = xfactor; + rotXMul = NOMINAL_ROT_FACTOR * xfactor; + } + + /** + * Property which sets the rotation x factor. + * Used by ConfiguredUniverse. + * @param xFactor array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void RotXFactor(Object[] xFactor) { + if (! (xFactor.length == 1 && xFactor[0] instanceof Double)) + throw new IllegalArgumentException("RotXFactor must be a Double"); + + setRotXFactor(((Double)xFactor[0]).doubleValue()); + } + + /** + * Sets the rotation y factor. The factors are used to determine + * how many radians to rotate the view for each pixel of mouse movement. + * The view is rotated factor * 0.01 radians for each pixel of mouse + * movement. The default factor is 1.0. + * @param yfactor The y movement multiplier + **/ + public synchronized void setRotYFactor(double yfactor) { + rotYFactor = yfactor; + rotYMul = NOMINAL_ROT_FACTOR * yfactor; + } + + /** + * Property which sets the rotation y factor. + * Used by ConfiguredUniverse. + * @param yFactor array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void RotYFactor(Object[] yFactor) { + if (! (yFactor.length == 1 && yFactor[0] instanceof Double)) + throw new IllegalArgumentException("RotYFactor must be a Double"); + + setRotYFactor(((Double)yFactor[0]).doubleValue()); + } + + /** + * Sets the translation x and y factors. The factors are used to determine + * how many units to translate the view for each pixel of mouse movement. + * The view is translated factor * 0.01 units for each pixel of mouse + * movement. The default factor is 1.0. + * @param xfactor The x movement multiplier + * @param yfactor The y movement multiplier + **/ + public synchronized void setTransFactors(double xfactor, + double yfactor) { + transXFactor = xfactor; + transYFactor = yfactor; + transXMul = NOMINAL_TRANS_FACTOR * xfactor; + transYMul = NOMINAL_TRANS_FACTOR * yfactor; + } + + /** + * Property which sets the translation x and y factors. + * Used by ConfiguredUniverse. + * @param factors array of length 2 containing instances of Double + * @since Java 3D 1.3 + */ + public void TransFactors(Object[] factors) { + if (! (factors.length == 2 && + factors[0] instanceof Double && factors[1] instanceof Double)) + throw new IllegalArgumentException + ("TransFactors must be two Doubles"); + + setTransFactors(((Double)factors[0]).doubleValue(), + ((Double)factors[1]).doubleValue()); + } + + /** + * Sets the translation x factor. The factors are used to determine + * how many units to translate the view for each pixel of mouse movement. + * The view is translated factor * 0.01 units for each pixel of mouse + * movement. The default factor is 1.0. + * @param xfactor The x movement multiplier + **/ + public synchronized void setTransXFactor(double xfactor) { + transXFactor = xfactor; + transXMul = NOMINAL_TRANS_FACTOR * xfactor; + } + + /** + * Property which sets the translation x factor. + * Used by ConfiguredUniverse. + * @param xFactor array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void TransXFactor(Object[] xFactor) { + if (! (xFactor.length == 1 && xFactor[0] instanceof Double)) + throw new IllegalArgumentException("TransXFactor must be a Double"); + + setTransXFactor(((Double)xFactor[0]).doubleValue()); + } + + /** + * Sets the translation y factor. The factors are used to determine + * how many units to translate the view for each pixel of mouse movement. + * The view is translated factor * 0.01 units for each pixel of mouse + * movement. The default factor is 1.0. + * @param yfactor The y movement multiplier + **/ + public synchronized void setTransYFactor(double yfactor) { + transYFactor = yfactor; + transYMul = NOMINAL_TRANS_FACTOR * yfactor; + } + + /** + * Property which sets the translation y factor. + * Used by ConfiguredUniverse. + * @param yFactor array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void TransYFactor(Object[] yFactor) { + if (! (yFactor.length == 1 && yFactor[0] instanceof Double)) + throw new IllegalArgumentException("TransYFactor must be a Double"); + + setTransYFactor(((Double)yFactor[0]).doubleValue()); + } + + /** + * Sets the zoom factor. The factor is used to determine how many + * units to zoom the view for each pixel of mouse movement. + * The view is zoomed factor * 0.01 units for each pixel of mouse + * movement. For proportional zoom, the view is zoomed factor * 1% + * of the distance from the center of rotation for each pixel of + * mouse movement. The default factor is 1.0. + * @param zfactor The movement multiplier + */ + public synchronized void setZoomFactor(double zfactor) { + zoomFactor = zfactor; + if (proportionalZoom) { + zoomMul = NOMINAL_PZOOM_FACTOR * zfactor; + } + else { + zoomMul = NOMINAL_ZOOM_FACTOR * zfactor; + } + } + + /** + * Property which sets the zoom factor. + * Used by ConfiguredUniverse. + * @param zFactor array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void ZoomFactor(Object[] zFactor) { + if (! (zFactor.length == 1 && zFactor[0] instanceof Double)) + throw new IllegalArgumentException("ZoomFactor must be a Double"); + + setZoomFactor(((Double)zFactor[0]).doubleValue()); + } + + /** + * Returns the x rotation movement multiplier + * @return The movement multiplier for x rotation + */ + public double getRotXFactor() { + return rotXFactor; + } + + /** + * Returns the y rotation movement multiplier + * @return The movement multiplier for y rotation + */ + public double getRotYFactor() { + return rotYFactor; + } + + /** + * Returns the x translation movement multiplier + * @return The movement multiplier for x translation + */ + public double getTransXFactor() { + return transXFactor; + } + + /** + * Returns the y translation movement multiplier + * @return The movement multiplier for y translation + */ + public double getTransYFactor() { + return transYFactor; + } + + /** + * Returns the zoom movement multiplier + * @return The movement multiplier for zoom + */ + public double getZoomFactor() { + return zoomFactor; + } + + /** + * Enables or disables rotation. The default is true. + * @param enabled true or false to enable or disable rotate + */ + public synchronized void setRotateEnable(boolean enabled) { + rotateEnabled = enabled; + } + + /** + * Property which enables or disables rotation. + * Used by ConfiguredUniverse. + * @param enabled array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void RotateEnable(Object[] enabled) { + if (! (enabled.length == 1 && enabled[0] instanceof Boolean)) + throw new IllegalArgumentException("RotateEnable must be Boolean"); + + setRotateEnable(((Boolean)enabled[0]).booleanValue()); + } + + /** + * Enables or disables zoom. The default is true. + * @param enabled true or false to enable or disable zoom + */ + public synchronized void setZoomEnable(boolean enabled) { + zoomEnabled = enabled; + } + + /** + * Property which enables or disables zoom. + * Used by ConfiguredUniverse. + * @param enabled array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void ZoomEnable(Object[] enabled) { + if (! (enabled.length == 1 && enabled[0] instanceof Boolean)) + throw new IllegalArgumentException("ZoomEnable must be Boolean"); + + setZoomEnable(((Boolean)enabled[0]).booleanValue()); + } + + /** + * Enables or disables translate. The default is true. + * @param enabled true or false to enable or disable translate + */ + public synchronized void setTranslateEnable(boolean enabled) { + translateEnabled = enabled; + } + + /** + * Property which enables or disables translate. + * Used by ConfiguredUniverse. + * @param enabled array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void TranslateEnable(Object[] enabled) { + if (! (enabled.length == 1 && enabled[0] instanceof Boolean)) + throw new IllegalArgumentException + ("TranslateEnable must be Boolean"); + + setTranslateEnable(((Boolean)enabled[0]).booleanValue()); + } + + /** + * Retrieves the state of rotate enabled + * @return the rotate enable state + */ + public boolean getRotateEnable() { + return rotateEnabled; + } + + /** + * Retrieves the state of zoom enabled + * @return the zoom enable state + */ + public boolean getZoomEnable() { + return zoomEnabled; + } + + /** + * Retrieves the state of translate enabled + * @return the translate enable state + */ + public boolean getTranslateEnable() { + return translateEnabled; + } + + boolean rotate(MouseEvent evt) { + if (rotateEnabled) { + if ((leftButton == ROTATE) && + (!evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((middleButton == ROTATE) && + (evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((rightButton == ROTATE) && + (!evt.isAltDown() && evt.isMetaDown())) { + return true; + } + } + return false; + } + + boolean zoom(MouseEvent evt) { + if (zoomEnabled) { + if ((leftButton == ZOOM) && + (!evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((middleButton == ZOOM) && + (evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((rightButton == ZOOM) && + (!evt.isAltDown() && evt.isMetaDown())) { + return true; + } + } + return false; + } + + boolean translate(MouseEvent evt) { + if (translateEnabled) { + if ((leftButton == TRANSLATE) && + (!evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((middleButton == TRANSLATE) && + (evt.isAltDown() && !evt.isMetaDown())) { + return true; + } + if ((rightButton == TRANSLATE) && + (!evt.isAltDown() && evt.isMetaDown())) { + return true; + } + } + return false; + } + + /** + * Sets the minimum radius for the OrbitBehavior. The zoom will + * stop at this distance from the center of rotation. The default + * is 0.0. The minimum will have no affect if the STOP_ZOOM constructor + * flag is not set. + * @param r the minimum radius + * @exception IllegalArgumentException if the radius is less than 0.0 + */ + public synchronized void setMinRadius(double r) { + if (r < 0.0) { + throw new IllegalArgumentException(J3dUtilsI18N.getString("OrbitBehavior1")); + } + minRadius = r; + } + + /** + * Property which sets the minimum radius for the OrbitBehavior. + * Used by ConfiguredUniverse. + * @param r array of length 1 containing instance of Double + * @since Java 3D 1.3 + */ + public void MinRadius(Object[] r) { + if (! (r.length == 1 && r[0] instanceof Double)) + throw new IllegalArgumentException("MinRadius must be a Double"); + + setMinRadius(((Double)r[0]).doubleValue()); + } + + /** + * Returns the minimum orbit radius. The zoom will stop at this distance + * from the center of rotation if the STOP_ZOOM constructor flag is set. + * @return the minimum radius + */ + public double getMinRadius() { + return minRadius; + } + + /** + * Set reverse translate behavior. The default is false. + * @param state if true, reverse translate behavior + * @since Java 3D 1.3 + */ + public void setReverseTranslate(boolean state) { + reverseTrans = state; + } + + /** + * Property which sets reverse translate behavior. + * Used by ConfiguredUniverse. + * @param state array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void ReverseTranslate(Object[] state) { + if (! (state.length == 1 && state[0] instanceof Boolean)) + throw new IllegalArgumentException + ("ReverseTranslate must be Boolean"); + + setReverseTranslate(((Boolean)state[0]).booleanValue()); + } + + /** + * Set reverse rotate behavior. The default is false. + * @param state if true, reverse rotate behavior + * @since Java 3D 1.3 + */ + public void setReverseRotate(boolean state) { + reverseRotate = state; + } + + /** + * Property which sets reverse rotate behavior. + * Used by ConfiguredUniverse. + * @param state array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void ReverseRotate(Object[] state) { + if (! (state.length == 1 && state[0] instanceof Boolean)) + throw new IllegalArgumentException("ReverseRotate must be Boolean"); + + setReverseRotate(((Boolean)state[0]).booleanValue()); + } + + /** + * Set reverse zoom behavior. The default is false. + * @param state if true, reverse zoom behavior + * @since Java 3D 1.3 + */ + public void setReverseZoom(boolean state) { + reverseZoom = state; + } + + /** + * Property which sets reverse zoom behavior. + * Used by ConfiguredUniverse. + * @param state array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void ReverseZoom(Object[] state) { + if (! (state.length == 1 && state[0] instanceof Boolean)) + throw new IllegalArgumentException("ReverseZoom must be Boolean"); + + setReverseZoom(((Boolean)state[0]).booleanValue()); + } + + /** + * Set proportional zoom behavior. The default is false. + * @param state if true, use proportional zoom behavior + * @since Java 3D 1.3 + */ + public synchronized void setProportionalZoom(boolean state) { + proportionalZoom = state; + + if (state) { + zoomMul = NOMINAL_PZOOM_FACTOR * zoomFactor; + } + else { + zoomMul = NOMINAL_ZOOM_FACTOR * zoomFactor; + } + } + + /** + * Property which sets proportional zoom behavior. + * Used by ConfiguredUniverse. + * @param state array of length 1 containing instance of Boolean + * @since Java 3D 1.3 + */ + public void ProportionalZoom(Object[] state) { + if (! (state.length == 1 && state[0] instanceof Boolean)) + throw new IllegalArgumentException + ("ProportionalZoom must be Boolean"); + + setProportionalZoom(((Boolean)state[0]).booleanValue()); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformAWTBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformAWTBehavior.java new file mode 100644 index 0000000..18ce5b8 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformAWTBehavior.java @@ -0,0 +1,400 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.vp; + +import java.awt.event.ComponentEvent; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Cursor; +import javax.swing.SwingUtilities; +import java.util.ArrayList; + +import javax.media.j3d.WakeupOnBehaviorPost; +import javax.media.j3d.WakeupOnElapsedFrames; +import javax.media.j3d.WakeupOr; +import javax.media.j3d.WakeupCriterion; +import javax.media.j3d.WakeupCondition; +import javax.media.j3d.TransformGroup; +import javax.media.j3d.Transform3D; +import javax.media.j3d.View; +import javax.media.j3d.Canvas3D; + +import javax.vecmath.Vector3f; +import com.sun.j3d.utils.universe.*; + + +/** + * Abstract class which implements much of the event tracking and + * state updating in a thread safe manner. + * + * AWT Events are captured and placed in a queue. + * + * While there are pending events or motion the behavior will wake + * up every frame, call processAWTEvents and integrateTransforms. + * + * @since Java 3D 1.2.1 + */ +public abstract class ViewPlatformAWTBehavior extends ViewPlatformBehavior +implements MouseListener, MouseMotionListener, KeyListener { + + private final static boolean DEBUG = false; + + /** + * Behavior PostId used in this behavior + */ + protected final static int POST_ID = 9998; + + /** + * The different criterion for the behavior to wakeup + */ + protected WakeupOnElapsedFrames frameWakeup; + + /** + * The Or of the different criterion for the behavior to wakeup + */ + protected WakeupOnBehaviorPost postWakeup; + + /** + * The target Transform3D for this behavior + */ + protected Transform3D targetTransform = new Transform3D(); + + /** + * Boolean for whether the mouse is in motion + */ + protected boolean motion = false; + + /** + * Flag indicating Behavior should listen for Mouse Events + */ + public final static int MOUSE_LISTENER = 0x01; + + /** + * Flag indicating Behavior should listen for Mouse Motion Events + */ + public final static int MOUSE_MOTION_LISTENER = 0x02; + + /** + * Flag indicating Behavior should listen for Key Events + */ + public final static int KEY_LISTENER = 0x04; + + /** + * The Canvas3Ds from which this Behavior gets AWT events + */ + protected Canvas3D canvases[]; + + private ArrayList eventQueue = new ArrayList(); + private int listenerFlags = 0; + private boolean firstEvent = false; + + /** + * Parameterless constructor for this behavior, intended for use by + * subclasses instantiated through ConfiguredUniverse. Such a constructor + * is required for configurable behaviors. + * @since Java 3D 1.3 + */ + protected ViewPlatformAWTBehavior() { + super(); + } + + /** + * Construct a behavior which listens for events specified by the given + * flags, intended for use by subclasses instantiated through + * ConfiguredUniverse. + * + * @param listenerFlags Indicates which listener should be registered, + * one or more of MOUSE_LISTENER, MOUSE_MOTION_LISTENER, KEY_LISTENER + * @since Java 3D 1.3 + */ + protected ViewPlatformAWTBehavior(int listenerFlags) { + super(); + setListenerFlags(listenerFlags); + } + + /** + * Constructs a new ViewPlatformAWTBehavior. + * + * @param c The Canvas3D on which to listen for events. If this is null a + * NullPointerException will be thrown. + * @param listenerFlags Indicates which listener should be registered, + * one or more of MOUSE_LISTENER, MOUSE_MOTION_LISTENER, KEY_LISTENER + */ + public ViewPlatformAWTBehavior(Canvas3D c, int listenerFlags ) { + super(); + + if (c == null) + throw new NullPointerException(); + + canvases = new Canvas3D[] { c }; + setListenerFlags(listenerFlags); + } + + /** + * Sets listener flags for this behavior. + * + * @param listenerFlags Indicates which listener should be registered, + * one or more of MOUSE_LISTENER, MOUSE_MOTION_LISTENER, KEY_LISTENER + * @since Java 3D 1.3 + */ + protected void setListenerFlags(int listenerFlags) { + this.listenerFlags = listenerFlags; + } + + /** + * Initializes the behavior. + * NOTE: Applications should not call this method. It is called by the + * Java 3D behavior scheduler. + */ + public void initialize() { + frameWakeup = new WakeupOnElapsedFrames( 0 ); + postWakeup = new WakeupOnBehaviorPost( this, POST_ID ); + + wakeupOn(postWakeup); + } + + /** + * Process a stimulus meant for this behavior. + * NOTE: Applications should not call this method. It is called by the + * Java 3D behavior scheduler. + */ + public void processStimulus( java.util.Enumeration behEnum ) { + boolean hadPost = false; + + while(behEnum.hasMoreElements()) { + WakeupCondition wakeup = (WakeupCondition)behEnum.nextElement(); + if (wakeup instanceof WakeupOnBehaviorPost) { + hadPost = true; + } else if (wakeup instanceof WakeupOnElapsedFrames) { + AWTEvent[] events = null; + // access to event queue must be synchronized + synchronized(eventQueue) { + events = (AWTEvent[])eventQueue.toArray( new AWTEvent[eventQueue.size()] ); + eventQueue.clear(); + } + processAWTEvents(events); + + if (motion) + integrateTransforms(); + } + } + + if (motion || hadPost) { + // wake up on behavior posts and elapsed frames if in motion + wakeupOn( frameWakeup ); + } else { + // only wake up on behavior posts if not in motion + wakeupOn( postWakeup ); + } + } + + /** + * Overload setEnable from Behavior. + * + * Adds/Removes the AWT listeners depending on the requested + * state. + */ + public void setEnable( boolean state ) { + if (state==getEnable()) + return; + + super.setEnable(state); + + if (canvases != null) { + enableListeners(state); + } + } + + private void enableListeners( boolean enable ) { + if (enable) { + firstEvent = true ; + if ( (listenerFlags & MOUSE_LISTENER)!=0) + for(int i=0; inot call this method. + */ + public void setViewingPlatform(ViewingPlatform vp) { + super.setViewingPlatform( vp ); + + if (vp==null) { + enableListeners( false ); + } else { + if (canvases != null) { + // May be switching canvases here. + enableListeners(false); + } + + // Use the canvases associated with viewer[0]; if no viewer is + // available yet, see if a canvas was provided with a constructor. + Viewer[] viewers = vp.getViewers(); + + if (viewers != null && viewers[0] != null) + canvases = viewers[0].getCanvas3Ds(); + + if (canvases == null || canvases[0] == null) + throw new IllegalStateException("No canvases available"); + + if (getEnable()) { + enableListeners(true); + } + } + } + + /** + * This is called once per frame if there are any AWT events to + * process. + * + * The motion variable will be true when the method + * is called. If it is true when the method returns integrateTransforms + * will be called immediately. + * + * The AWTEvents are presented in the array in the order in which they + * arrived from AWT. + */ + protected abstract void processAWTEvents( final java.awt.AWTEvent[] events ); + + /** + * Called once per frame (if the view is moving) to calculate the new + * view platform transform + */ + protected abstract void integrateTransforms(); + + /** + * Queue AWTEvents in a thread safe manner. + * + * If subclasses override this method they must call + * super.queueAWTEvent(e) + */ + protected void queueAWTEvent( AWTEvent e ) { + // add new event to the queue + // must be MT safe + synchronized (eventQueue) { + eventQueue.add(e); + // Only need to post if this is the only event in the queue. + // There have been reports that the first event after + // setViewingPlatform() is sometimes missed, so check the + // firstEvent flag as well. + if (firstEvent || eventQueue.size() == 1) { + firstEvent = false; + postId( POST_ID ); + } + } + } + + public void mouseClicked(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mouseEntered(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mouseExited(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mousePressed(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mouseReleased(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mouseDragged(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void mouseMoved(final MouseEvent e) { + queueAWTEvent( e ); + } + + public void keyReleased(final java.awt.event.KeyEvent e) { + queueAWTEvent( e ); + } + + public void keyPressed(final java.awt.event.KeyEvent e) { + queueAWTEvent( e ); + } + + public void keyTyped(final java.awt.event.KeyEvent e) { + queueAWTEvent( e ); + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformBehavior.java new file mode 100644 index 0000000..a0d4d8a --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/vp/ViewPlatformBehavior.java @@ -0,0 +1,137 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.vp; + +import javax.vecmath.*; +import javax.media.j3d.*; +import com.sun.j3d.utils.universe.*; + +/** + * Abstract class for ViewPlatformBehaviors. A ViewPlatformBehavior must + * be added to the ViewingPlatform with the + * ViewingPlatform.addViewPlatformBehavior() method. The ViewPlatformBehavior + * will operate on the ViewPlatform transform (the TransformGroup return by + * ViewingPlatform.getViewPlatformTransform()). + * @since Java 3D 1.2.1 + */ +abstract public class ViewPlatformBehavior extends Behavior { + + /** + * The ViewingPlatform for this behavior. + */ + protected ViewingPlatform vp; + + /** + * The target TransformGroup for this behavior. + */ + protected TransformGroup targetTG; + + /** + * The "home" transform for this behavior. This is a transform used to + * position and orient the ViewingPlatform to a known point of interest. + * + * @since Java 3D 1.3 + */ + protected Transform3D homeTransform = null; + + /** + * Sets the ViewingPlatform for this behavior. This method is called by + * the ViewingPlatform. If a sub-calls overrides this method, it must + * call super.setViewingPlatform(vp).

+ * + * NOTE: Applications should not call this method. + * + * @param vp the target ViewingPlatform for this behavior + */ + public void setViewingPlatform(ViewingPlatform vp) { + this.vp = vp; + + if (vp!=null) + targetTG = vp.getViewPlatformTransform(); + else + targetTG = null; + } + + /** + * Returns the ViewingPlatform for this behavior + * @return the ViewingPlatform for this behavior + */ + public ViewingPlatform getViewingPlatform() { + return vp; + } + + /** + * Copies the given Transform3D into the "home" transform, used to + * position and reorient the ViewingPlatform to a known point of interest. + * + * @param home source transform to be copied + * @since Java 3D 1.3 + */ + public void setHomeTransform(Transform3D home) { + if (homeTransform == null) + homeTransform = new Transform3D(home); + else + homeTransform.set(home); + } + + /** + * Returns the behaviors "home" transform. + * + * @param home transform to be returned + * @since Java 3D 1.3 + */ + public void getHomeTransform(Transform3D home ) { + home.set( homeTransform ); + } + + /** + * Positions and reorients the ViewingPlatform to its "home" transform. + * @since Java 3D 1.3 + */ + public void goHome() { + if (targetTG != null && homeTransform != null) + targetTG.setTransform(homeTransform); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/behaviors/vp/WandViewBehavior.java b/src/classes/share/com/sun/j3d/utils/behaviors/vp/WandViewBehavior.java new file mode 100644 index 0000000..9f46650 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/behaviors/vp/WandViewBehavior.java @@ -0,0 +1,3843 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.behaviors.vp ; + +import java.util.* ; +import javax.vecmath.* ; +import javax.media.j3d.* ; +import com.sun.j3d.utils.universe.* ; +import com.sun.j3d.utils.behaviors.sensor.* ; + +/** + * Manipulates view platform transforms using a motion-tracked wand or mouse + * equipped with a six degree of freedom (6DOF) sensor. An optional two axis + * (2D) valuator sensor is also directly supported. Default operation is set + * up to enable both direct manipulation of the view transform and translation + * back and forth along the direction the 6DOF sensor is pointing; rotation + * is handled by the 2D valuator if available. An arbitrary number of sensors + * and action bindings can be customized by accessing this behavior's + * SensorEventAgent directly. + *

+ * This behavior can be instantiated from the configuration file read by + * ConfiguredUniverse and fully configured using the + * ViewPlatformBehaviorProperties command to set the properties + * described below, but neither ConfiguredUniverse nor + * SimpleUniverse are required by this behavior. Conventional + * set and get accessors are provided for + * configuring this behavior directly; these methods have the same names as + * the properties but are prefixed with get and set. + * Property values are spelled with mixed case strings while the corresponding + * constant field names for the conventional accessors are spelled with upper + * case strings and underscores. + *

+ * {@link #Sensor6D Sensor6D} is the 6DOF sensor to use. This can also be set + * directly with the appropriate constructor. This sensor must generate 6 + * degree of freedom position and orientation reads relative to the tracker + * base in physical units. By default this behavior provides an echo for the + * 6DOF sensor which indicates its position and orientation in the virtual + * world; the echo attributes can be set by the {@link #EchoType EchoType}, + * {@link #EchoSize EchoSize}, {@link #EchoColor EchoColor}, and {@link + * #EchoTransparency EchoTransparency} properties. See also the {@link + * #NominalSensorRotation NominalSensorRotation} property, and the + * setHotSpot method of the Sensor class. + *

+ * {@link #Sensor2D Sensor2D} is an optional 2D valuator to use in conjunction + * with the 6DOF sensor. This can be set directly with the appropriate + * constructor. The valuator should generate X and Y reads ranging from [-1.0 + * .. +1.0], with a nominal (deadzone) value of 0.0. The default + * configuration expects to find these values along the translation components + * of the read matrix, at indices 3 and 7, but these indices can be also be + * specified by the {@link #MatrixIndices2D MatrixIndices2D} property. + *

+ * {@link #ButtonAction6D ButtonAction6D} sets an action for a specific button + * on a 6DOF sensor. The actions available are: + *

    + *
  • + * GrabView - Directly manipulates the view platform by moving + * it in inverse response to the sensor's position and orientation, + * producing the effect of attaching the virtual world to the sensor's + * movements. If a button is available then this action is bound to button 0 + * by default. + *
  • + *
  • + * TranslateForward - Translates the view platform forward along + * the direction the sensor is pointing; the virtual world appears to move + * towards the sensor. The default is button 1 if two buttons are available. + * Related properties are {@link #TranslationSpeed TranslationSpeed}, {@link + * #AccelerationTime AccelerationTime}, {@link #ConstantSpeedTime + * ConstantSpeedTime}, and {@link #FastSpeedFactor FastSpeedFactor}. + *
  • + *
  • + * TranslateBackward - Translates the view platform backwards + * along the direction the sensor is pointing; the virtual world appears to + * move away from the sensor. The default is button 2 if three buttons are + * available. + *
  • + *
  • + * RotateCCW - Rotates the view platform counter-clockwise about + * a Y axis; the virtual world appears to rotate clockwise. This action is + * not assigned by default. Related properties are {@link #RotationSpeed + * RotationSpeed}, {@link #RotationCoords RotationCoords}, {@link + * #TransformCenterSource TransformCenterSource}, {@link #TransformCenter + * TransformCenter}, and AccelerationTime. + *
  • + *
  • + * RotateCW - Rotates the view platform clockwise about a Y axis; + * the virtual world appears to rotate counter-clockwise. This action is not + * assigned by default. + *
  • + *
  • + * ScaleUp - Scales the view platform larger so that the virtual + * world appears to grow smaller. This action is not assigned by default. + * Related properties are {@link #ScaleSpeed ScaleSpeed}, + * TransformCenterSource, TransformCenter, and + * AccelerationTime. + *
  • + *
  • + * ScaleDown - Scales the view platform smaller so that the + * virtual world appears to grow larger. This action is not assigned by + * default. + *
  • + *
+ *

+ * {@link #ReadAction2D ReadAction2D} sets the action bound to 2D valuator + * reads; that is, non-zero values generated by the device when no buttons + * have been pressed. If the value is (0.0, 0.0) or below the threshold value + * set by {@link #Threshold2D Threshold2D}, then this behavior does nothing; + * otherwise, the following actions can be performed: + *

    + *
  • + * Rotation - Rotates the view platform. This is the default 2D + * valuator action set by this behavior. Related properties are + * RotationSpeed, RotationCoords, + * TransformCenterSource, and TransformCenter. + *
  • + *
  • + * Translation - Translates the view platform. The translation + * occurs relative to the X and Z basis vectors of either the 6DOF sensor or + * the view platform if one is not available. The maximum speed is equal to + * the product of the TranslationSpeed and + * FastSpeedFactor property values. + *
  • + *
  • + * Scale - Scales the view platform smaller with positive Y + * values and larger with negative Y values. The effect is to increase the + * apparent size of the virtual world when pushing the valuator forwards and + * to decrease it when pushing backwards. Related properties are + * ScaleSpeed, TransformCenterSource, and + * TransformCenter. + *
  • + *
+ *

+ * {@link #ButtonAction2D ButtonAction2D} sets an action for a specific button + * on the 2D valuator. The available actions are the same as for + * ReadAction2D. No actions are bound by default to the 2D + * valuator buttons. + *

+ * The view transform may be reset to its home transform by pressing a number + * of buttons simultaneously on the 6DOF sensor. The minimum number of + * buttons that must be pressed is set by {@link #ResetViewButtonCount6D + * ResetViewButtonCount6D}. This value must be greater than one; the default + * is three. This action may be disabled by setting the property value to + * None. The corresponding property for the 2D valuator is {@link + * #ResetViewButtonCount2D ResetViewButtonCount2D}, with a default value of + * None. Note, however, that the reset view action will be ineffectual if an + * action which always modifies the view transform is bound to reads on the + * sensor used to reset the view, since the reset transform will get + * overwritten by the read action. + *

+ * The special value None can in general be assigned to any + * button or read action to prevent any defaults from being bound to it. + * + * @see ConfiguredUniverse + * @see SensorEventAgent + * @since Java 3D 1.3 + */ +public class WandViewBehavior extends ViewPlatformBehavior { + /** + * Indicates a null configuration choice. + */ + public static final int NONE = 0 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to grabbing the view. The default is button 0. + */ + public static final int GRAB_VIEW = 1 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to translating the view forward. The default is button 1. + */ + public static final int TRANSLATE_FORWARD = 2 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to translating the view backward. The default is button 2. + */ + public static final int TRANSLATE_BACKWARD = 3 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to rotate the view plaform counter-clockwise about a Y axis. + */ + public static final int ROTATE_CCW = 4 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to rotate the view platform clockwise about a Y axis. + */ + public static final int ROTATE_CW = 5 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to scaling the view platform larger. + */ + public static final int SCALE_UP = 6 ; + + /** + * Indicates that a 6DOF sensor button action should be bound + * to scaling the view platform smaller. + */ + public static final int SCALE_DOWN = 7 ; + + /** + * Indicates that a 2D sensor button or read action should be bound + * to translation. + */ + public static final int TRANSLATION = 8 ; + + /** + * Indicates that a 2D sensor button or read action should be bound + * to scaling. + */ + public static final int SCALE = 9 ; + + /** + * Indicates that a 2D sensor button or read action should be bound + * to rotation. The default is to bind rotation to the 2D sensor reads. + */ + public static final int ROTATION = 10 ; + + /** + * Indicates that translation, rotation, or scaling speeds are + * per frame. + */ + public static final int PER_FRAME = 11 ; + + /** + * Use to indicate that translation, rotation, or scaling speeds are per + * second. This is the default. + */ + public static final int PER_SECOND = 12 ; + + /** + * Indicates that translation speed is in virtual world units. + */ + public static final int VIRTUAL_UNITS = 13 ; + + /** + * Indicates that translation speed is in physical world units + * (meters per second or per frame). This is the default. + */ + public static final int PHYSICAL_METERS = 14 ; + + /** + * Indicates that rotation speed should be in radians. + */ + public static final int RADIANS = 15 ; + + /** + * Indicates that rotation speed should be in degrees. This is the + * default. + */ + public static final int DEGREES = 16 ; + + /** + * Indicates that rotation should occur in view platform + * coordinates. + */ + public static final int VIEW_PLATFORM = 17 ; + + /** + * Indicates that rotation should occur in head coordinates. + */ + public static final int HEAD = 18 ; + + /** + * Indicates that rotation should occur in sensor coordinates. + * This is the default. + */ + public static final int SENSOR = 19 ; + + /** + * Indicates that rotation or scale should be about a fixed point + * in virtual world coordinates. + */ + public static final int VWORLD_FIXED = 20 ; + + /** + * Indicates that rotation or scale should be about a 6DOF sensor + * hotspot. This is the default. + */ + public static final int HOTSPOT = 21 ; + + /** + * Indicates that the 6DOF sensor read action should be bound to + * displaying the sensor's echo in the virtual world. This is the + * default. + */ + public static final int ECHO = 22 ; + + /** + * Indicates that the echo type is a gnomon displaying the + * directions of the sensor's local coordinate system axes at the location + * of the sensor's hotspot. + */ + public static final int GNOMON = 23 ; + + /** + * Indicates that the echo type is a beam extending from the + * origin of the sensor's local coordinate system to its hotspot. + */ + public static final int BEAM = 24 ; + + /** + * Indicates that a button listener or read listener has not been + * set for a particular target. This allows this behavior to use that + * target for a default listener. + */ + private static final int UNSET = -1 ; + + private View view = null ; + private SensorEventAgent eventAgent = null ; + private String sensor6DName = null ; + private String sensor2DName = null ; + private Shape3D echoGeometry = null ; + private BranchGroup echoBranchGroup = null ; + private TransformGroup echoTransformGroup = null ; + private SensorReadListener echoReadListener6D = null ; + private boolean echoBranchGroupAttached = false ; + private WakeupCondition wakeupConditions = new WakeupOnElapsedFrames(0) ; + private boolean configured = false ; + + // The rest of these private fields are all configurable through + // ConfiguredUniverse. + private Sensor sensor6D = null ; + private Sensor sensor2D = null ; + private int x2D = 3 ; + private int y2D = 7 ; + private double threshold2D = 0.0 ; + + private int readAction6D = UNSET ; + private int readAction2D = UNSET ; + + private ArrayList buttonActions6D = new ArrayList() ; + private ArrayList buttonActions2D = new ArrayList() ; + + private double translationSpeed = 0.1 ; + private int translationUnits = PHYSICAL_METERS ; + private int translationTimeBase = PER_SECOND ; + private double accelerationTime = 1.0 ; + private double constantSpeedTime = 8.0 ; + private double fastSpeedFactor = 10.0 ; + + private double rotationSpeed = 180.0 ; + private int rotationUnits = DEGREES ; + private int rotationTimeBase = PER_SECOND ; + private int rotationCoords = SENSOR ; + + private double scaleSpeed = 2.0 ; + private int scaleTimeBase = PER_SECOND ; + + private int transformCenterSource = HOTSPOT ; + private Point3d transformCenter = new Point3d(0.0, 0.0, 0.0) ; + + private int resetViewButtonCount6D = 3 ; + private int resetViewButtonCount2D = NONE ; + + private int echoType = GNOMON ; + private double echoSize = 0.01 ; + private Color3f echoColor = null ; + private float echoTransparency = 0.0f ; + private Transform3D nominalSensorRotation = null ; + + /** + * Parameterless constructor for this behavior. This is called when this + * behavior is instantiated from a configuration file. + *

+ * Syntax:
(NewViewPlatformBehavior <name> + * com.sun.j3d.utils.behaviors.vp.WandViewBehavior) + */ + public WandViewBehavior() { + // Create an event agent. + eventAgent = new SensorEventAgent(this) ; + + // Set a default SchedulingBounds. + setSchedulingBounds(new BoundingSphere(new Point3d(0.0, 0.0, 0.0), + Double.POSITIVE_INFINITY)) ; + } + + /** + * Creates a new instance with the specified sensors and echo parameters. + * At least one sensor must be non-null. + *

+ * This constructor should only be used if either + * SimpleUniverse or ConfiguredUniverse is used + * to set up the view side of the scene graph, or if it is otherwise to be + * attached to a ViewingPlatform. If this behavior is not + * instantiated from a configuration file then it must then be explicitly + * attached to a ViewingPlatform instance with the + * ViewingPlatform.setViewPlatformBehavior method. + * + * @param sensor6D a six degree of freedom sensor which generates reads + * relative to the tracker base in physical units; may be + * null + * @param sensor2D 2D valuator which generates X and Y reads ranging from + * [-1.0 .. +1.0]; may be null + * @param echoType either GNOMON, BEAM, or + * NONE for the 6DOF sensor echo + * @param echoSize the width of the 6DOF sensor echo in physical meters; + * ignored if echoType is NONE + */ + public WandViewBehavior(Sensor sensor6D, Sensor sensor2D, + int echoType, double echoSize) { + this() ; + this.sensor6D = sensor6D ; + this.sensor2D = sensor2D ; + this.echoType = echoType ; + this.echoSize = echoSize ; + } + + /** + * Creates a new instance with the specified sensors and a 6DOF sensor + * echo parented by the specified TransformGroup. At least + * one sensor must be non-null. + *

+ * This constructor should only be used if either + * SimpleUniverse or ConfiguredUniverse is used + * to set up the view side of the scene graph, or if it is otherwise to be + * attached to a ViewingPlatform. If this behavior is not + * instantiated from a configuration file then it must then be explicitly + * attached to a ViewingPlatform instance with the + * ViewingPlatform.setViewPlatformBehavior method. + *

+ * If the echo TransformGroup is non-null, it + * will be added to a new BranchGroup and attached to the + * ViewingPlatform, where its transform will be updated in + * response to the sensor reads. Capabilities to allow writing its + * transform and to read, write, and extend its children will be set. The + * echo geometry is assumed to incorporate the position and orientation of + * the 6DOF sensor hotspot. + * + * @param sensor6D a six degree of freedom sensor which generates reads + * relative to the tracker base in physical units; may be + * null + * @param sensor2D 2D valuator which generates X and Y reads ranging from + * [-1.0 .. +1.0]; may be null + * @param echo a TransformGroup containing the visible echo + * which will track the 6DOF sensor's position and orientation, or + * null for no echo + */ + public WandViewBehavior(Sensor sensor6D, Sensor sensor2D, + TransformGroup echo) { + this() ; + this.sensor6D = sensor6D ; + this.sensor2D = sensor2D ; + if (echo != null) { + echo.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE) ; + echo.setCapability(Group.ALLOW_CHILDREN_READ) ; + echo.setCapability(Group.ALLOW_CHILDREN_WRITE) ; + echo.setCapability(Group.ALLOW_CHILDREN_EXTEND) ; + } + this.echoTransformGroup = echo ; + } + + /** + * Creates a new instance with the specified sensors and a 6DOF sensor + * echo parented by the specified TransformGroup. At least + * one sensor must be non-null. + *

+ * This constructor should only be used if SimpleUniverse or + * ConfiguredUniverse is not used to set up the view + * side of the scene graph. The application must set up the view side + * itself and supply references to the View and the + * TransformGroup containing the view platform transform. + * ViewingPlatform.setViewPlatformBehavior must not + * be called, and this behavior must be explicitly added to the virtual + * universe by the application. + *

+ * If the echo TransformGroup is non-null, it + * will only be used to update its associated transform with the position + * and orientation of a 6DOF sensor (if supplied). The application is + * responsible for adding the echo to the virtual universe. The echo + * geometry is assumed to incorporate the position and orientation of the + * 6DOF sensor hotspot. + * + * @param sensor6D a six degree of freedom sensor which generates reads + * relative to the tracker base in physical units; may be + * null + * @param sensor2D 2D valuator which generates X and Y reads ranging from + * [-1.0 .. +1.0]; may be null + * @param view a reference to the View attached to the + * ViewPlatform to be manipulated by this behavior + * @param viewTransform a TransformGroup containing the view + * platform transform; appropriate capabilities to update the transform + * must be set + * @param homeTransform a Transform3D containing the + * view transform to be used when the view is reset; may be + * null for identity + * @param echo a TransformGroup containing the visible echo + * which will track the 6DOF sensor's position and orientation, or + * null for no echo; appropriate capabilities to update the + * transform must be set + */ + public WandViewBehavior(Sensor sensor6D, Sensor sensor2D, + View view, TransformGroup viewTransform, + Transform3D homeTransform, TransformGroup echo) { + this() ; + this.sensor6D = sensor6D ; + this.sensor2D = sensor2D ; + this.view = view ; + this.targetTG = viewTransform ; + this.echoTransformGroup = echo ; + + if (homeTransform == null) + setHomeTransform(new Transform3D()) ; + else + setHomeTransform(homeTransform) ; + } + + /** + * Initializes and configures this behavior. + * NOTE: Applications should not call this method. It is called by + * the Java 3D behavior scheduler. + */ + public void initialize() { + // Don't configure the sensors and echo after the first time. + if (!configured) { + configureSensorActions() ; + + // Configure an echo only if a ViewingPlatform is in use. + if (vp != null) { + if (echoTransformGroup == null && + sensor6D != null && readAction6D == ECHO) { + configureEcho() ; + } + if (echoTransformGroup != null) { + echoBranchGroup = new BranchGroup() ; + echoBranchGroup.setCapability + (BranchGroup.ALLOW_DETACH) ; + echoBranchGroup.setCapability + (BranchGroup.ALLOW_CHILDREN_READ) ; + echoBranchGroup.setCapability + (BranchGroup.ALLOW_CHILDREN_WRITE) ; + + echoBranchGroup.addChild(echoTransformGroup) ; + echoBranchGroup.compile() ; + } + attachEcho() ; + } + configured = true ; + } + wakeupOn(wakeupConditions) ; + } + + /** + * Processes a stimulus meant for this behavior. + * NOTE: Applications should not call this method. It is called by + * the Java 3D behavior scheduler. + */ + public void processStimulus(Enumeration criteria) { + // Invoke the sensor event dispatcher. + eventAgent.dispatchEvents() ; + + // Wake up on the next frame. + wakeupOn(wakeupConditions) ; + } + + /** + * Enables or disables this behavior. The default state is enabled. + * @param enable true or false to enable or disable this behavior + */ + public void setEnable(boolean enable) { + if (enable == getEnable()) { + return ; + } + else if (enable) { + attachEcho() ; + } + else { + detachEcho() ; + } + super.setEnable(enable) ; + } + + /** + * Sets the ViewingPlatform for this behavior. If a subclass + * overrides this method, it must call + * super.setViewingPlatform(vp). NOTE: Applications should + * not call this method. It is called by the + * ViewingPlatform. + */ + public void setViewingPlatform(ViewingPlatform vp) { + super.setViewingPlatform(vp) ; + if (vp == null) { + detachEcho() ; + return ; + } + + Viewer[] viewers = vp.getViewers() ; + if (viewers != null) { + // Get the View from the first Viewer attached to the + // ViewingPlatform. Multiple Viewers are not supported. + if (viewers.length != 0 && viewers[0] != null) + view = viewers[0].getView() ; + + if (viewers.length > 1) + throw new RuntimeException("multiple Viewers not supported") ; + } + if (view == null) { + // Fallback to the first View attached to a live ViewPlatform. + view = getView() ; + } + if (view == null) { + // This behavior requires a view. Bail. + throw new RuntimeException("a view is not available") ; + } + + // Get the top-most TransformGroup in the ViewingPlatform. + // ViewPlatformBehavior retrieves the bottom-most which won't work + // if there are multiple TransformGroups. + targetTG = vp.getMultiTransformGroup().getTransformGroup(0) ; + + // Should be an API for checking if homeTransform is null. + if (homeTransform == null) + setHomeTransform(new Transform3D()) ; + + attachEcho() ; + } + + /** + * Attaches the echo BranchGroup to the ViewingPlatform if appropriate. + */ + private void attachEcho() { + if (vp != null && + echoBranchGroup != null && !echoBranchGroupAttached) { + vp.addChild(echoBranchGroup) ; + echoBranchGroupAttached = true ; + } + } + + /** + * Detaches the echo BranchGroup from the ViewingPlatform if appropriate. + */ + private void detachEcho() { + if (echoBranchGroup != null && echoBranchGroupAttached) { + echoBranchGroup.detach() ; + echoBranchGroupAttached = false ; + } + } + + /** + * Creates the sensor listeners for a 6DOF sensor and/or a 2D valuator + * sensor using the predefined button and read listeners and the + * configured action bindings. + *

+ * This is invoked the first time initialize is called. This + * method can be overridden by subclasses to modify the configured + * bindings or introduce other configuration parameters. + */ + protected void configureSensorActions() { + SensorButtonListener[] sbls ; + int buttonCount, buttonActionCount ; + + SimpleUniverse universe = null ; + if (vp != null) universe = vp.getUniverse() ; + if (universe != null && universe instanceof ConfiguredUniverse) { + // Check if sensors were instantiated from a config file. + Map sensorMap = ((ConfiguredUniverse)universe).getNamedSensors() ; + + if (sensor2D == null && sensor2DName != null) { + sensor2D = (Sensor)sensorMap.get(sensor2DName) ; + if (sensor2D == null) + throw new IllegalArgumentException + ("\nsensor " + sensor2DName + " not found") ; + } + + if (sensor6D == null && sensor6DName != null) { + sensor6D = (Sensor)sensorMap.get(sensor6DName) ; + if (sensor6D == null) + throw new IllegalArgumentException + ("\nsensor " + sensor6DName + " not found") ; + } + } + + if (sensor6D != null) { + // Assign default read action. + if (readAction6D == UNSET) + readAction6D = ECHO ; + + // Register the read listener. + if (readAction6D == ECHO) { + echoReadListener6D = new EchoReadListener6D() ; + eventAgent.addSensorReadListener + (sensor6D, echoReadListener6D) ; + } + + // Check for button range. + buttonCount = sensor6D.getSensorButtonCount() ; + buttonActionCount = buttonActions6D.size() ; + if (buttonActionCount > buttonCount) + throw new IllegalArgumentException + ("\nbutton index " + (buttonActionCount-1) + + " >= number of buttons (" + buttonCount +")") ; + + // Assign default button actions. + if (buttonCount > 2 && + (buttonActionCount < 3 || buttonActions6D.get(2) == null)) + setButtonAction6D(2, TRANSLATE_BACKWARD) ; + if (buttonCount > 1 && + (buttonActionCount < 2 || buttonActions6D.get(1) == null)) + setButtonAction6D(1, TRANSLATE_FORWARD) ; + if (buttonCount > 0 && + (buttonActionCount < 1 || buttonActions6D.get(0) == null)) + setButtonAction6D(0, GRAB_VIEW) ; + + buttonActionCount = buttonActions6D.size() ; + if (buttonActionCount > 0) { + // Set up the button listener array. + sbls = new SensorButtonListener[buttonCount] ; + for (int i = 0 ; i < buttonActionCount ; i++) { + Integer button = (Integer)buttonActions6D.get(i) ; + if (button != null) { + int action = button.intValue() ; + if (action == NONE) + sbls[i] = null ; + else if (action == GRAB_VIEW) + sbls[i] = new GrabViewListener6D() ; + else if (action == TRANSLATE_FORWARD) + sbls[i] = new TranslationListener6D(false) ; + else if (action == TRANSLATE_BACKWARD) + sbls[i] = new TranslationListener6D(true) ; + else if (action == ROTATE_CCW) + sbls[i] = new RotationListener6D(false) ; + else if (action == ROTATE_CW) + sbls[i] = new RotationListener6D(true) ; + else if (action == SCALE_UP) + sbls[i] = new ScaleListener6D(false) ; + else if (action == SCALE_DOWN) + sbls[i] = new ScaleListener6D(true) ; + } + } + // Register the button listeners. + eventAgent.addSensorButtonListeners(sensor6D, sbls) ; + } + + // Check for reset view action. + if (resetViewButtonCount6D != NONE) { + SensorInputAdaptor r = + new ResetViewListener(sensor6D, resetViewButtonCount6D) ; + eventAgent.addSensorButtonListener(sensor6D, r) ; + eventAgent.addSensorReadListener(sensor6D, r) ; + } + } + + if (sensor2D != null) { + // Assign default read action + if (readAction2D == UNSET) + readAction2D = ROTATION ; + + // Register the read listener. + if (readAction2D == ROTATION) { + SensorReadListener r = + new RotationListener2D(sensor2D, sensor6D) ; + eventAgent.addSensorReadListener(sensor2D, r) ; + } + else if (readAction2D == TRANSLATION) { + SensorReadListener r = + new TranslationListener2D(sensor2D, sensor6D) ; + eventAgent.addSensorReadListener(sensor2D, r) ; + } + else if (readAction2D == SCALE) { + SensorReadListener r = + new ScaleListener2D(sensor2D, sensor6D) ; + eventAgent.addSensorReadListener(sensor2D, r) ; + } + + // Check for button range. + buttonCount = sensor2D.getSensorButtonCount() ; + buttonActionCount = buttonActions2D.size() ; + if (buttonActionCount > buttonCount) + throw new IllegalArgumentException + ("\nbutton index " + (buttonActionCount-1) + + " >= number of buttons (" + buttonCount +")") ; + + // No default button actions are defined for the 2D sensor. + if (buttonActionCount > 0) { + // Set up the button listener array. + sbls = new SensorButtonListener[buttonCount] ; + for (int i = 0 ; i < buttonActionCount ; i++) { + Integer button = (Integer)buttonActions2D.get(i) ; + if (button != null) { + int action = button.intValue() ; + if (action == NONE) + sbls[i] = null ; + else if (action == ROTATION) + sbls[i] = new RotationListener2D + (sensor2D, sensor6D) ; + else if (action == TRANSLATION) + sbls[i] = new TranslationListener2D + (sensor2D, sensor6D) ; + else if (action == SCALE) + sbls[i] = new ScaleListener2D + (sensor2D, sensor6D) ; + } + } + // Register the button listeners. + eventAgent.addSensorButtonListeners(sensor2D, sbls) ; + } + + // Check for reset view action. + if (resetViewButtonCount2D != NONE) { + SensorInputAdaptor r = + new ResetViewListener(sensor2D, resetViewButtonCount2D) ; + eventAgent.addSensorButtonListener(sensor2D, r) ; + eventAgent.addSensorReadListener(sensor2D, r) ; + } + } + } + + /** + * Creates a 6DOF sensor echo according to configuration parameters. This + * is done only if a 6DOF sensor has been specified, the 6DOF sensor read + * action has been set to echo the sensor position, the echo transform + * group has not already been set, and a ViewingPlatform is in use. This + * is invoked the first time initialize is called to set this + * behavior live, but before the echo transform group is added to a + * BranchGroup and made live. This method can be overridden + * to support other echo geometry. + */ + protected void configureEcho() { + Point3d hotspot = new Point3d() ; + sensor6D.getHotspot(hotspot) ; + + if (echoType == GNOMON) { + Transform3D gnomonTransform = new Transform3D() ; + if (nominalSensorRotation != null) { + gnomonTransform.set(nominalSensorRotation) ; + gnomonTransform.invert() ; + } + gnomonTransform.setTranslation(new Vector3d(hotspot)) ; + echoGeometry = new SensorGnomonEcho + (gnomonTransform, 0.1 * echoSize, 0.5 * echoSize, true) ; + } + else if (echoType == BEAM) { + echoGeometry = new SensorBeamEcho(hotspot, echoSize, true) ; + } + + if (echoGeometry != null) { + Appearance a = echoGeometry.getAppearance() ; + if (echoColor != null) { + Material m = a.getMaterial() ; + m.setDiffuseColor(echoColor) ; + } + if (echoTransparency != 0.0f) { + TransparencyAttributes ta = a.getTransparencyAttributes() ; + ta.setTransparencyMode(TransparencyAttributes.BLENDED) ; + ta.setTransparency(echoTransparency) ; + // Use order independent additive blend for gnomon. + if (echoGeometry instanceof SensorGnomonEcho) + ta.setDstBlendFunction(TransparencyAttributes.BLEND_ONE) ; + } + echoTransformGroup = new TransformGroup() ; + echoTransformGroup.setCapability + (TransformGroup.ALLOW_TRANSFORM_WRITE) ; + echoTransformGroup.setCapability(Group.ALLOW_CHILDREN_READ) ; + echoTransformGroup.setCapability(Group.ALLOW_CHILDREN_WRITE) ; + echoTransformGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND) ; + echoTransformGroup.addChild(echoGeometry) ; + } + } + + /** + * A base class for implementing some of this behavior's listeners. + */ + public class ListenerBase extends SensorInputAdaptor { + /** + * The initial transform from view platform coordinates to virtual + * world coordinates, set by initAction. + */ + protected Transform3D viewPlatformToVworld = new Transform3D() ; + + /** + * The initial transform from tracker base coordinates to virtual + * world coordinates, set by initAction. + */ + protected Transform3D trackerToVworld = new Transform3D() ; + + /** + * The initial transform from sensor coordinates to virtual + * world coordinates, set by initAction. + */ + protected Transform3D sensorToVworld = new Transform3D() ; + + /** + * The initial transform from sensor coordinates to tracker base + * coordinates, set by initAction. + */ + protected Transform3D sensorToTracker = new Transform3D() ; + + // Private fields. + private Transform3D trackerToSensor = new Transform3D() ; + private boolean active = false ; + + // Misc. temporary objects. + private double[] s3Tmp = new double[3] ; + private double[] m16Tmp = new double[16] ; + private Vector3d v3dTmp = new Vector3d() ; + private Transform3D t3dTmp = new Transform3D() ; + + /** + * Initializes the listener action. Subclasses must call this before + * starting the action, either from pressed or when a 2D + * valuator exits the deadzone threshold. + * + * @param s reference to a 6DOF sensor if used by the listener; may + * be null + */ + protected void initAction(Sensor s) { + targetTG.getTransform(viewPlatformToVworld) ; + active = true ; + if (s == null) return ; + + // Kludge to get the static trackerToVworld for this + // frame. This is computed from the two separate sensor reads + // below, which are close enough to identical to work. The + // Java 3D View class needs a getTrackerBaseToVworld() method + // (see Java 3D RFE 4676808). + s.getRead(sensorToTracker) ; + view.getSensorToVworld(s, sensorToVworld) ; + + trackerToSensor.invert(sensorToTracker) ; + trackerToVworld.mul(sensorToVworld, trackerToSensor) ; + } + + /** + * Ends the action. Subclasses must be call this from + * released or when a 2D valuator enters the deadzone + * threshold. + * + * @param s reference to a 6DOF sensor if used by the listener; may + * be null + */ + protected void endAction(Sensor s) { + active = false ; + } + + /** + * Returns true if the listener is currently active; that is, if + * initAction has been called but not yet + * endAction. + * + * @return true if the listener is active, false otherwise + */ + protected boolean isActive() { + return active ; + } + + public void pressed(SensorEvent e) { + initAction(e.getSensor()) ; + } + + public void released(SensorEvent e) { + endAction(e.getSensor()) ; + } + + /** + * Gets the physical to virtual scale. + */ + protected double getPhysicalToVirtualScale() { + view.getCanvas3D(0).getImagePlateToVworld(t3dTmp) ; + t3dTmp.get(m16Tmp) ; + return Math.sqrt(m16Tmp[0]*m16Tmp[0] + + m16Tmp[1]*m16Tmp[1] + + m16Tmp[2]*m16Tmp[2]) ; + } + + /** + * Gets the scale from physical units to view platform units. + */ + protected double getPhysicalToViewPlatformScale() { + double vpToVirtualScale ; + + targetTG.getTransform(t3dTmp) ; + t3dTmp.get(m16Tmp) ; + vpToVirtualScale = Math.sqrt(m16Tmp[0]*m16Tmp[0] + + m16Tmp[1]*m16Tmp[1] + + m16Tmp[2]*m16Tmp[2]) ; + + return getPhysicalToVirtualScale() / vpToVirtualScale ; + } + + /** + * Translates a coordinate system. + * + * @param transform the coordinate system to be translated + * @param translation the vector by which to translate + */ + protected void translateTransform(Transform3D transform, + Vector3d translation) { + transform.get(v3dTmp) ; + v3dTmp.add(translation) ; + transform.setTranslation(v3dTmp) ; + } + + /** + * Transforms the target coordinate system about a center point. + * This can be used for rotation and scaling. + * + * @param target the coordinate system to transform + * @param center the center point about which to transform + * @param transform the transform to apply + */ + protected void transformAboutCenter + (Transform3D target, Point3d center, Transform3D transform) { + + // Translate to the center. + target.get(v3dTmp) ; + v3dTmp.sub(center) ; + target.setTranslation(v3dTmp) ; + + // Apply the transform. + target.mul(transform, target) ; + + // Translate back. + target.get(v3dTmp) ; + v3dTmp.add(center) ; + target.setTranslation(v3dTmp) ; + } + + /** + * Equalizes the scale factors in the view tranform, which must be + * congruent. If successful, the ViewingPlatform + * TransformGroup is updated; otherwise, its transform is reset + * to the home transform. This should be called if multiple + * incremental scale factors are applied to the view transform. + * + * @param viewPlatformToVworld the view transform + */ + protected void conditionViewScale(Transform3D viewPlatformToVworld) { + viewPlatformToVworld.normalize() ; + viewPlatformToVworld.get(m16Tmp) ; + + s3Tmp[0] = m16Tmp[0]*m16Tmp[0] + + m16Tmp[4]*m16Tmp[4] + m16Tmp[8]*m16Tmp[8] ; + s3Tmp[1] = m16Tmp[1]*m16Tmp[1] + + m16Tmp[5]*m16Tmp[5] + m16Tmp[9]*m16Tmp[9] ; + s3Tmp[2] = m16Tmp[2]*m16Tmp[2] + + m16Tmp[6]*m16Tmp[6] + m16Tmp[10]*m16Tmp[10] ; + + if (s3Tmp[0] == s3Tmp[1] && s3Tmp[0] == s3Tmp[2]) + return ; + + s3Tmp[0] = Math.sqrt(s3Tmp[0]) ; + s3Tmp[1] = Math.sqrt(s3Tmp[1]) ; + s3Tmp[2] = Math.sqrt(s3Tmp[2]) ; + + int closestToOne = 0 ; + if (Math.abs(s3Tmp[1] - 1.0) < Math.abs(s3Tmp[0] - 1.0)) + closestToOne = 1 ; + if (Math.abs(s3Tmp[2] - 1.0) < Math.abs(s3Tmp[closestToOne] - 1.0)) + closestToOne = 2 ; + + double scale ; + for (int i = 0 ; i < 3 ; i++) { + if (i == closestToOne) continue ; + scale = s3Tmp[closestToOne] / s3Tmp[i] ; + m16Tmp[i+0] *= scale ; + m16Tmp[i+4] *= scale ; + m16Tmp[i+8] *= scale ; + } + + // Set the view transform and bail out if unsuccessful. + viewPlatformToVworld.set(m16Tmp) ; + if ((viewPlatformToVworld.getType() & Transform3D.CONGRUENT) == 0) + goHome() ; + else + targetTG.setTransform(viewPlatformToVworld) ; + } + } + + /** + * Implements a 6DOF sensor button listener to directly manipulate the + * view platform transform. The view platform moves in inverse response + * to the sensor's position and orientation to give the effect of + * attaching the virtual world to the sensor's echo. + * @see #setButtonAction6D + */ + public class GrabViewListener6D extends ListenerBase { + private Transform3D t3d = new Transform3D() ; + private Transform3D initialVworldToSensor = new Transform3D() ; + + public void pressed(SensorEvent e) { + initAction(e.getSensor()) ; + + // Save the inverse of the initial sensorToVworld. + initialVworldToSensor.invert(sensorToVworld) ; + } + + public void dragged(SensorEvent e) { + // Get sensor read relative to the static view at the time of the + // button-down. + Sensor s = e.getSensor() ; + s.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + + // Solve for T, where T x initialSensorToVworld = sensorToVworld + t3d.mul(sensorToVworld, initialVworldToSensor) ; + + // Move T to the view side by inverting it, and then applying it + // to the static view transform. + t3d.invert() ; + t3d.mul(viewPlatformToVworld) ; + targetTG.setTransform(t3d) ; + } + } + + /** + * Implements a 6DOF sensor button listener that translates the view + * platform along the direction the sensor is pointing. + * @see #setButtonAction6D + * @see #setTranslationSpeed + * @see #setAccelerationTime + * @see #setConstantSpeedTime + * @see #setFastSpeedFactor + */ + public class TranslationListener6D extends ListenerBase { + private long buttonDownTime ; + private double speedScaled ; + private double interval0 ; + private double interval1 ; + private double interval2 ; + private Vector3d v3d = new Vector3d() ; + + /** + * Construct a new translation button listener for a 6DOF sensor. + * + * @param reverse if true, translate the view platform backwards; + * otherwise, translate the view platform forwards + */ + public TranslationListener6D(boolean reverse) { + // Compute translation speed intervals. + interval0 = accelerationTime ; + interval1 = interval0 + constantSpeedTime ; + interval2 = interval1 + accelerationTime ; + + // Apply virtual to physical scale if needed. + if (translationUnits == VIRTUAL_UNITS) + speedScaled = translationSpeed / getPhysicalToVirtualScale() ; + else + speedScaled = translationSpeed ; + + if (reverse) { + speedScaled = -speedScaled ; + } + } + + public void pressed(SensorEvent e) { + initAction(e.getSensor()) ; + buttonDownTime = e.getTime() ; + } + + public void dragged(SensorEvent e) { + long time = e.getTime() ; + long lastTime = e.getLastTime() ; + double currSpeed, transTime ; + double frameTime = 1.0 ; + if (translationTimeBase == PER_SECOND) + frameTime = (time - lastTime) / 1e9 ; + + // Compute speed based on acceleration intervals. + transTime = (time - buttonDownTime) / 1e9 ; + if (transTime <= interval0) { + currSpeed = (transTime / accelerationTime) * speedScaled ; + } + else if (transTime > interval1 && transTime < interval2) { + currSpeed = ((((transTime-interval1) / accelerationTime) * + (fastSpeedFactor-1.0)) + 1.0) * speedScaled ; + } + else if (transTime >= interval2) { + currSpeed = fastSpeedFactor * speedScaled ; + } + else { + currSpeed = speedScaled ; + } + + // Transform the translation direction (0, 0, -1). + v3d.set(0.0, 0.0, -1.0) ; + if (nominalSensorRotation != null) + nominalSensorRotation.transform(v3d) ; + + // To avoid echo frame lag, compute sensorToVworld based on + // computed trackerToVworld. getSensorToVworld() isn't + // current for this frame. + Sensor s = e.getSensor() ; + s.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + sensorToVworld.transform(v3d) ; + + // Translate the view platform. + v3d.scale(frameTime * currSpeed) ; + translateTransform(viewPlatformToVworld, v3d) ; + targetTG.setTransform(viewPlatformToVworld) ; + + // Translate trackerToVworld. + translateTransform(trackerToVworld, v3d) ; + + if (readAction6D == ECHO) { + // Translate sensor echo to compensate for the new view + // platform movement. + translateTransform(sensorToVworld, v3d) ; + updateEcho(s, sensorToVworld) ; + } + } + } + + /** + * Implements a 6DOF sensor button listener that rotates the view platform + * about a Y axis. This axis can be relative to the sensor, user head, or + * view platform. The rotation center can be the sensor hotspot or a + * fixed point in virtual world coordinates. + * + * @see #setButtonAction6D + * @see #setRotationCoords + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #setRotationSpeed + * @see #setAccelerationTime + */ + public class RotationListener6D extends ListenerBase { + private boolean reverse ; + private long buttonDownTime ; + private Vector3d axis = new Vector3d() ; + private Point3d center = new Point3d() ; + private Transform3D t3d = new Transform3D() ; + private AxisAngle4d aa4d = new AxisAngle4d() ; + private Transform3D headToVworld = new Transform3D() ; + private double speedScaled ; + + protected void initAction(Sensor s) { + super.initAction(s) ; + if (rotationCoords == HEAD) { + view.setUserHeadToVworldEnable(true) ; + } + } + + protected void endAction(Sensor s) { + super.endAction(s) ; + viewPlatformToVworld.normalize() ; + targetTG.setTransform(viewPlatformToVworld) ; + if (rotationCoords == HEAD) { + view.setUserHeadToVworldEnable(false) ; + } + } + + /** + * Construct a new rotation button listener for a 6DOF sensor. + * + * @param reverse if true, rotate clockwise; otherwise, rotate + * counter-clockwise + */ + public RotationListener6D(boolean reverse) { + this.reverse = reverse ; + if (rotationUnits == DEGREES) + speedScaled = rotationSpeed * Math.PI / 180.0 ; + else + speedScaled = rotationSpeed ; + } + + public void pressed(SensorEvent e) { + initAction(e.getSensor()) ; + buttonDownTime = e.getTime() ; + } + + public void dragged(SensorEvent e) { + long time = e.getTime() ; + long lastTime = e.getLastTime() ; + double currSpeed, transTime ; + double frameTime = 1.0 ; + if (rotationTimeBase == PER_SECOND) + frameTime = (time - lastTime) / 1e9 ; + + // Compute speed based on acceleration interval. + transTime = (time - buttonDownTime) / 1e9 ; + if (transTime <= accelerationTime) { + currSpeed = (transTime / accelerationTime) * speedScaled ; + } + else { + currSpeed = speedScaled ; + } + + // Set the rotation axis. + if (reverse) + axis.set(0.0, -1.0, 0.0) ; + else + axis.set(0.0, 1.0, 0.0) ; + + // To avoid echo frame lag, compute sensorToVworld based on + // computed trackerToVworld. getSensorToVworld() isn't current + // for this frame. + Sensor s = e.getSensor() ; + s.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + + // Transform rotation axis into target coordinate system. + if (rotationCoords == SENSOR) { + if (nominalSensorRotation != null) + nominalSensorRotation.transform(axis) ; + + sensorToVworld.transform(axis) ; + } + else if (rotationCoords == HEAD) { + view.getUserHeadToVworld(headToVworld) ; + headToVworld.transform(axis) ; + } + else { + viewPlatformToVworld.transform(axis) ; + } + + // Get the rotation center. + if (transformCenterSource == HOTSPOT) { + s.getHotspot(center) ; + sensorToVworld.transform(center) ; + } + else { + center.set(transformCenter) ; + } + + // Construct origin-based rotation about axis. + aa4d.set(axis, currSpeed * frameTime) ; + t3d.set(aa4d) ; + + // Apply the rotation to the view platform. + transformAboutCenter(viewPlatformToVworld, center, t3d) ; + targetTG.setTransform(viewPlatformToVworld) ; + + // Apply the rotation to trackerToVworld. + transformAboutCenter(trackerToVworld, center, t3d) ; + + if (readAction6D == ECHO) { + // Transform sensor echo to compensate for the new view + // platform movement. + transformAboutCenter(sensorToVworld, center, t3d) ; + updateEcho(s, sensorToVworld) ; + } + } + } + + /** + * Implements a 6DOF sensor button listener that scales the view platform. + * The center of scaling can be the sensor hotspot or a fixed location in + * virtual world coordinates. + * + * @see #setButtonAction6D + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #setScaleSpeed + * @see #setAccelerationTime + */ + public class ScaleListener6D extends ListenerBase { + private double direction ; + private long buttonDownTime ; + private Point3d center = new Point3d() ; + private Transform3D t3d = new Transform3D() ; + + protected void endAction(Sensor s) { + super.endAction(s) ; + conditionViewScale(viewPlatformToVworld) ; + } + + /** + * Construct a new scale button listener for a 6DOF sensor. + * + * @param reverse if true, scale the view platform smaller; otherwise, + * scale the view platform larger + */ + public ScaleListener6D(boolean reverse) { + if (reverse) + direction = -1.0 ; + else + direction = 1.0 ; + } + + public void pressed(SensorEvent e) { + initAction(e.getSensor()) ; + buttonDownTime = e.getTime() ; + } + + public void dragged(SensorEvent e) { + long time = e.getTime() ; + long lastTime = e.getLastTime() ; + double scale, exp, transTime ; + double frameTime = 1.0 ; + if (scaleTimeBase == PER_SECOND) + frameTime = (time - lastTime) / 1e9 ; + + // Compute speed based on acceleration interval. + transTime = (time - buttonDownTime) / 1e9 ; + if (transTime <= accelerationTime) { + exp = (transTime / accelerationTime) * frameTime * direction ; + } + else { + exp = frameTime * direction ; + } + scale = Math.pow(scaleSpeed, exp) ; + + // To avoid echo frame lag, compute sensorToVworld based on + // computed trackerToVworld. getSensorToVworld() isn't current + // for this frame. + Sensor s = e.getSensor() ; + s.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + + // Get the scale center. + if (transformCenterSource == HOTSPOT) { + s.getHotspot(center) ; + sensorToVworld.transform(center) ; + } + else { + center.set(transformCenter) ; + } + + // Apply the scale to the view platform. + t3d.set(scale) ; + transformAboutCenter(viewPlatformToVworld, center, t3d) ; + + // Incremental scaling at the extremes can lead to numerical + // instability, so catch BadTransformException to prevent the + // behavior thread from being killed. Using a cumulative scale + // matrix avoids this problem to a better extent, but causes the + // 6DOF sensor hotspot center to jitter excessively. + try { + targetTG.setTransform(viewPlatformToVworld) ; + } + catch (BadTransformException bt) { + conditionViewScale(viewPlatformToVworld) ; + } + + // Apply the scale to trackerToVworld. + transformAboutCenter(trackerToVworld, center, t3d) ; + + if (readAction6D == ECHO) { + // Scale sensor echo to compensate for the new view + // platform scale. + transformAboutCenter(sensorToVworld, center, t3d) ; + updateEcho(s, sensorToVworld) ; + } + } + } + + /** + * Implements a 6DOF sensor read listener that updates the orientation and + * position of the sensor's echo in the virtual world. + * + * @see #setEchoType + * @see #setEchoSize + * @see #setReadAction6D + * @see SensorGnomonEcho + * @see SensorBeamEcho + */ + public class EchoReadListener6D implements SensorReadListener { + private Transform3D sensorToVworld = new Transform3D() ; + + public void read(SensorEvent e) { + Sensor s = e.getSensor() ; + view.getSensorToVworld(s, sensorToVworld) ; + updateEcho(s, sensorToVworld) ; + } + } + + /** + * Implements a 2D valuator listener that rotates the view platform. The + * X and Y values from the valuator should have a continuous range from + * -1.0 to +1.0, although the rotation speed can be scaled to compensate + * for a different range. The X and Y values are found in the sensor's + * read matrix at the indices specified by + * setMatrixIndices2D, with defaults of 3 and 7 respectively. + *

+ * The rotation direction is controlled by the direction the 2D valuator + * is pushed, and the rotation speed is scaled by the magnitude of the 2D + * valuator read values. + *

+ * This listener will work in conjunction with a 6DOF sensor if supplied + * in the constructor. If a 6DOF sensor is provided and + * setRotationCoords has been called with the value + * SENSOR, then the rotation is applied in the 6DOF sensor's + * coordinate system; otherwise the rotation is applied either in head + * coordinates or in view platform coordinates. If a 6DOF sensor is + * provided and setTransformCenterSource has been called with + * the value HOTSPOT, then rotation is about the 6DOF + * sensor's hotspot; otherwise, the rotation center is the value set by + * setTransformCenter. + * + * @see #setReadAction2D + * @see #setButtonAction2D + * @see #setRotationCoords + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #setRotationSpeed + * @see #setThreshold2D + * @see #setMatrixIndices2D + */ + public class RotationListener2D extends ListenerBase { + private Sensor sensor2D, sensor6D ; + private double[] m = new double[16] ; + private Vector3d axis = new Vector3d() ; + private Point3d center = new Point3d() ; + private Transform3D t3d = new Transform3D() ; + private AxisAngle4d aa4d = new AxisAngle4d() ; + private Transform3D sensor2DRead = new Transform3D() ; + private Transform3D headToVworld = new Transform3D() ; + private double speedScaled ; + + protected void initAction(Sensor s) { + super.initAction(s) ; + if (rotationCoords == HEAD) { + view.setUserHeadToVworldEnable(true) ; + } + if (s != null && readAction6D == ECHO) { + // Disable the 6DOF echo. It will be updated in this action. + eventAgent.removeSensorReadListener(s, echoReadListener6D) ; + } + } + + protected void endAction(Sensor s) { + super.endAction(s) ; + viewPlatformToVworld.normalize() ; + targetTG.setTransform(viewPlatformToVworld) ; + if (rotationCoords == HEAD) { + view.setUserHeadToVworldEnable(false) ; + } + if (s != null && readAction6D == ECHO) { + eventAgent.addSensorReadListener(s, echoReadListener6D) ; + } + } + + /** + * Construct an instance of this class with the specified sensors. + * + * @param sensor2D the 2D valuator whose X and Y values drive the + * rotation + * @param sensor6D the 6DOF sensor to use if the rotation coordinate + * system is set to SENSOR or the rotation center source + * is HOTSPOT; may be null + */ + public RotationListener2D(Sensor sensor2D, Sensor sensor6D) { + this.sensor2D = sensor2D ; + this.sensor6D = sensor6D ; + + if (rotationUnits == DEGREES) + speedScaled = rotationSpeed * Math.PI / 180.0 ; + else + speedScaled = rotationSpeed ; + } + + public void read(SensorEvent e) { + sensor2D.getRead(sensor2DRead) ; + sensor2DRead.get(m) ; + + if (m[x2D] > threshold2D || m[x2D] < -threshold2D || + m[y2D] > threshold2D || m[y2D] < -threshold2D) { + // Initialize action on threshold crossing. + if (!isActive()) initAction(sensor6D) ; + + // m[x2D] is the X valuator value and m[y2D] is the Y valuator + // value. Use these to construct the rotation axis. + double length = Math.sqrt(m[x2D]*m[x2D] + m[y2D]*m[y2D]) ; + double iLength = 1.0/length ; + axis.set(m[y2D]*iLength, -m[x2D]*iLength, 0.0) ; + + if (sensor6D != null) { + // To avoid echo frame lag, compute sensorToVworld based + // on computed trackerToVworld. getSensorToVworld() isn't + // current for this frame. + sensor6D.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + } + + // Transform rotation axis into target coordinate system. + if (sensor6D != null && rotationCoords == SENSOR) { + if (nominalSensorRotation != null) + nominalSensorRotation.transform(axis) ; + + sensorToVworld.transform(axis) ; + } + else if (rotationCoords == HEAD) { + view.getUserHeadToVworld(headToVworld) ; + headToVworld.transform(axis) ; + } + else { + viewPlatformToVworld.transform(axis) ; + } + + // Get the rotation center. + if (transformCenterSource == HOTSPOT && sensor6D != null) { + sensor6D.getHotspot(center) ; + sensorToVworld.transform(center) ; + } + else { + center.set(transformCenter) ; + } + + double frameTime = 1.0 ; + if (rotationTimeBase == PER_SECOND) + frameTime = (e.getTime() - e.getLastTime()) / 1e9 ; + + // Construct origin-based rotation about axis. + aa4d.set(axis, speedScaled * frameTime * length) ; + t3d.set(aa4d) ; + + // Apply the rotation to the view platform. + transformAboutCenter(viewPlatformToVworld, center, t3d) ; + targetTG.setTransform(viewPlatformToVworld) ; + + if (sensor6D != null) { + // Apply the rotation to trackerToVworld. + transformAboutCenter(trackerToVworld, center, t3d) ; + } + + if (sensor6D != null && readAction6D == ECHO) { + // Transform sensor echo to compensate for the new view + // platform movement. + transformAboutCenter(sensorToVworld, center, t3d) ; + updateEcho(sensor6D, sensorToVworld) ; + } + } + else { + // Initialize action on next threshold crossing. + if (isActive()) endAction(sensor6D) ; + } + } + + public void pressed(SensorEvent e) { + initAction(sensor6D) ; + } + + public void released(SensorEvent e) { + if (isActive()) endAction(sensor6D) ; + } + + public void dragged(SensorEvent e) { + read(e) ; + } + } + + /** + * Implements a 2D valuator listener that translates the view platform. + * The X and Y values from the valuator should have a continuous range + * from -1.0 to +1.0, although the translation speed can be scaled to + * compensate for a different range. The X and Y values are found in the + * sensor's read matrix at the indices specified by + * setMatrixIndices2D, with defaults of 3 and 7 respectively. + *

+ * The translation direction is controlled by the direction the 2D + * valuator is pushed, and the speed is the translation speed scaled by + * the fast speed factor and the magnitude of the 2D valuator reads. + *

+ * This listener will work in conjunction with a 6DOF sensor if supplied + * in the constructor. If a 6DOF sensor is provided then the translation + * occurs along the basis vectors of the 6DOF sensor's coordinate system; + * otherwise, the translation occurs along the view platform's basis + * vectors. + * + * @see #setReadAction2D + * @see #setButtonAction2D + * @see #setTranslationSpeed + * @see #setFastSpeedFactor + * @see #setThreshold2D + * @see #setMatrixIndices2D + */ + public class TranslationListener2D extends ListenerBase { + private Sensor sensor2D, sensor6D ; + private double[] m = new double[16] ; + private Vector3d v3d = new Vector3d() ; + private Transform3D sensor2DRead = new Transform3D() ; + private double speedScaled ; + + protected void initAction(Sensor s) { + super.initAction(s) ; + if (s != null && readAction6D == ECHO) { + // Disable the 6DOF echo. It will be updated in this action. + eventAgent.removeSensorReadListener(s, echoReadListener6D) ; + } + } + + protected void endAction(Sensor s) { + super.endAction(s) ; + if (s != null && readAction6D == ECHO) { + // Enable the 6DOF sensor echo. + eventAgent.addSensorReadListener(s, echoReadListener6D) ; + } + } + + /** + * Construct an instance of this class using the specified sensors. + * + * @param sensor2D 2D valuator sensor for translation + * @param sensor6D 6DOF sensor for translation direction; may be + * null + */ + public TranslationListener2D(Sensor sensor2D, Sensor sensor6D) { + this.sensor2D = sensor2D ; + this.sensor6D = sensor6D ; + + // Apply virtual to physical scale if needed. + if (translationUnits == VIRTUAL_UNITS) + speedScaled = translationSpeed * + fastSpeedFactor / getPhysicalToVirtualScale() ; + else + speedScaled = translationSpeed * fastSpeedFactor ; + + // Apply physical to view platform scale if needed. + if (sensor6D == null) + speedScaled *= getPhysicalToViewPlatformScale() ; + } + + public void read(SensorEvent e) { + sensor2D.getRead(sensor2DRead) ; + sensor2DRead.get(m) ; + + if (m[x2D] > threshold2D || m[x2D] < -threshold2D || + m[y2D] > threshold2D || m[y2D] < -threshold2D) { + // Initialize action on threshold crossing. + if (!isActive()) initAction(sensor6D) ; + + // m[x2D] is the X valuator value and m[y2D] is the Y valuator + // value. Use these to construct the translation vector. + double length = Math.sqrt(m[x2D]*m[x2D] + m[y2D]*m[y2D]) ; + double iLength = 1.0/length ; + v3d.set(m[x2D]*iLength, 0.0, -m[y2D]*iLength) ; + + // Transform translation vector into target coordinate system. + if (sensor6D != null) { + if (nominalSensorRotation != null) + nominalSensorRotation.transform(v3d) ; + + // To avoid echo frame lag, compute sensorToVworld based + // on computed trackerToVworld. getSensorToVworld() isn't + // current for this frame. + sensor6D.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + sensorToVworld.transform(v3d) ; + } + else { + viewPlatformToVworld.transform(v3d) ; + } + + double frameTime = 1.0 ; + if (translationTimeBase == PER_SECOND) + frameTime = (e.getTime() - e.getLastTime()) / 1e9 ; + + v3d.scale(frameTime * speedScaled * length) ; + + // Translate the view platform. + translateTransform(viewPlatformToVworld, v3d) ; + targetTG.setTransform(viewPlatformToVworld) ; + + if (sensor6D != null) { + // Apply the translation to trackerToVworld. + translateTransform(trackerToVworld, v3d) ; + } + + if (sensor6D != null && readAction6D == ECHO) { + // Translate sensor echo to compensate for the new view + // platform movement. + translateTransform(sensorToVworld, v3d) ; + updateEcho(sensor6D, sensorToVworld) ; + } + } + else { + // Initialize action on next threshold crossing. + if (isActive()) endAction(sensor6D) ; + } + } + + public void pressed(SensorEvent e) { + initAction(sensor6D) ; + } + + public void released(SensorEvent e) { + if (isActive()) endAction(sensor6D) ; + } + + public void dragged(SensorEvent e) { + read(e) ; + } + } + + /** + * Implements a 2D valuator listener that scales the view platform. + * Pushing the valuator forwards gives the appearance of the virtual world + * increasing in size, while pushing the valuator backwards makes the + * virtual world appear to shrink. The X and Y values from the valuator + * should have a continuous range from -1.0 to +1.0, although the scale + * speed can be adjusted to compensate for a different range. + *

+ * This listener will work in conjunction with a 6DOF sensor if supplied + * in the constructor. If setTransformCenterSource has been + * called with the value HOTSPOT, then scaling is about the + * 6DOF sensor's hotspot; otherwise, the scaling center is the value set + * by setTransformCenter. + * + * @see #setReadAction2D + * @see #setButtonAction2D + * @see #setScaleSpeed + * @see #setTransformCenter + * @see #setThreshold2D + * @see #setMatrixIndices2D + */ + public class ScaleListener2D extends ListenerBase { + private Sensor sensor2D, sensor6D ; + private double[] m = new double[16] ; + private Point3d center = new Point3d() ; + private Transform3D t3d = new Transform3D() ; + private Transform3D sensor2DRead = new Transform3D() ; + + protected void initAction(Sensor s) { + super.initAction(s) ; + if (s != null && readAction6D == ECHO) { + // Disable the 6DOF echo. It will be updated in this action. + eventAgent.removeSensorReadListener(s, echoReadListener6D) ; + } + } + + protected void endAction(Sensor s) { + super.endAction(s) ; + conditionViewScale(viewPlatformToVworld) ; + if (s != null && readAction6D == ECHO) { + // Enable the 6DOF sensor echo. + eventAgent.addSensorReadListener(s, echoReadListener6D) ; + } + } + + /** + * Construct an instance of this class with the specified sensors. + * + * @param sensor2D the 2D valuator whose Y value drive the scaling + * @param sensor6D the 6DOF sensor to use if the rotation/scale center + * source is HOTSPOT; may be null + */ + public ScaleListener2D(Sensor sensor2D, Sensor sensor6D) { + this.sensor2D = sensor2D ; + this.sensor6D = sensor6D ; + } + + public void read(SensorEvent e) { + sensor2D.getRead(sensor2DRead) ; + sensor2DRead.get(m) ; + + if (m[y2D] > threshold2D || m[y2D] < -threshold2D) { + // Initialize action on threshold crossing. + if (!isActive()) initAction(sensor6D) ; + + if (sensor6D != null) { + // To avoid echo frame lag, compute sensorToVworld based + // on computed trackerToVworld. getSensorToVworld() isn't + // current for this frame. + sensor6D.getRead(sensorToTracker) ; + sensorToVworld.mul(trackerToVworld, sensorToTracker) ; + } + + // Get the scale center. + if (sensor6D != null && transformCenterSource == HOTSPOT) { + sensor6D.getHotspot(center) ; + sensorToVworld.transform(center) ; + } + else { + center.set(transformCenter) ; + } + + // Compute incremental scale for this frame. + double frameTime = 1.0 ; + if (scaleTimeBase == PER_SECOND) + frameTime = (e.getTime() - e.getLastTime()) / 1e9 ; + + // Map range: [-1.0 .. 0 .. 1.0] to: + // [scaleSpeed**frameTime .. 1 .. 1.0/(scaleSpeed**frameTime)] + double scale = Math.pow(scaleSpeed, (-m[y2D]*frameTime)) ; + + // Apply the scale to the view platform. + t3d.set(scale) ; + transformAboutCenter(viewPlatformToVworld, center, t3d) ; + + // Incremental scaling at the extremes can lead to numerical + // instability, so catch BadTransformException to prevent the + // behavior thread from being killed. Using a cumulative + // scale matrix avoids this problem to a better extent, but + // causes the 6DOF sensor hotspot center to jitter + // excessively. + try { + targetTG.setTransform(viewPlatformToVworld) ; + } + catch (BadTransformException bt) { + conditionViewScale(viewPlatformToVworld) ; + } + + if (sensor6D != null) { + // Apply the scale to trackerToVworld. + transformAboutCenter(trackerToVworld, center, t3d) ; + } + + if (sensor6D != null && readAction6D == ECHO) { + // Scale sensor echo to compensate for the new view + // platform scale. + transformAboutCenter(sensorToVworld, center, t3d) ; + updateEcho(sensor6D, sensorToVworld) ; + } + } + else { + // Initialize action on next threshold crossing. + if (isActive()) endAction(sensor6D) ; + } + } + + public void pressed(SensorEvent e) { + initAction(sensor6D) ; + } + + public void released(SensorEvent e) { + if (isActive()) endAction(sensor6D) ; + } + + public void dragged(SensorEvent e) { + read(e) ; + } + } + + /** + * Resets the view back to the home transform when a specified number of + * buttons are down simultaneously. + * + * @see #setResetViewButtonCount6D + * @see ViewPlatformBehavior#setHomeTransform + * ViewPlatformBehavior.setHomeTransform + */ + public class ResetViewListener extends SensorInputAdaptor { + private int resetCount ; + private int[] buttonState = null ; + private boolean goHomeNextRead = false ; + + /** + * Creates a sensor listener that resets the view when the specified + * number of buttons are down simultaneously. + * + * @param s the sensor to listen to + * @param count the number of buttons that must be down simultaneously + */ + public ResetViewListener(Sensor s, int count) { + resetCount = count ; + buttonState = new int[s.getSensorButtonCount()] ; + } + + public void pressed(SensorEvent e) { + int count = 0 ; + e.getButtonState(buttonState) ; + for (int i = 0 ; i < buttonState.length ; i++) + if (buttonState[i] == 1) count++ ; + + if (count >= resetCount) + // Ineffectual to reset immediately while other listeners may + // be setting the view transform. + goHomeNextRead = true ; + } + + public void read(SensorEvent e) { + if (goHomeNextRead) { + goHome() ; + goHomeNextRead = false ; + } + } + } + + /** + * Updates the echo position and orientation. The echo is placed at the + * sensor hotspot position if applicable. This implementation assumes the + * hotspot position and orientation have been incorporated into the echo + * geometry. + * + * @param sensor the sensor to be echoed + * @param sensorToVworld transform from sensor coordinates to virtual + * world coordinates + * @see #setEchoType + * @see #setEchoSize + * @see #setReadAction6D + * @see SensorGnomonEcho + * @see SensorBeamEcho + */ + protected void updateEcho(Sensor sensor, Transform3D sensorToVworld) { + echoTransformGroup.setTransform(sensorToVworld) ; + } + + /** + * Property which sets a 6DOF sensor for manipulating the view platform. + * This sensor must generate 6 degree of freedom orientation and position + * data in physical meters relative to the sensor's tracker base. + *

+ * This property is set in the configuration file. The first command form + * assumes that a ViewingPlatform is being used and that the + * sensor name can be looked up from a ConfiguredUniverse + * reference retrieved from ViewingPlatform.getUniverse. The + * second form is preferred and accepts the Sensor reference directly. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * Sensor6D <sensorName>) + *

+ * Alternative Syntax:
(ViewPlatformBehaviorProperty + * <name> Sensor6D (Sensor <sensorName>)) + * + * @param sensor array of length 1 containing a String or + * a Sensor + */ + public void Sensor6D(Object[] sensor) { + if (sensor.length != 1) + throw new IllegalArgumentException + ("Sensor6D requires a single name or Sensor instance") ; + + if (sensor[0] instanceof String) + sensor6DName = (String)sensor[0] ; + else if (sensor[0] instanceof Sensor) + sensor6D = (Sensor)sensor[0] ; + else + throw new IllegalArgumentException + ("Sensor6D must be a name or a Sensor instance") ; + } + + /** + * Returns a reference to the 6DOF sensor used for manipulating the view + * platform. + * + * @return the 6DOF sensor + */ + public Sensor getSensor6D() { + return sensor6D ; + } + + /** + * Property which sets a 2D sensor for manipulating the view platform. + * This is intended to support devices which incorporate a separate 2D + * valuator along with the 6DOF sensor. The X and Y values from the + * valuator should have a continuous range from -1.0 to +1.0, although + * rotation, translation, and scaling speeds can be scaled to compensate + * for a different range. The X and Y values are found in the sensor's + * read matrix at the indices specified by the + * MatrixIndices2D property, with defaults of 3 and 7 + * (the X and Y translation components) respectively. + *

+ * This property is set in the configuration file. The first command form + * assumes that a ViewingPlatform is being used and that the + * sensor name can be looked up from a ConfiguredUniverse + * reference retrieved from ViewingPlatform.getUniverse. The + * second form is preferred and accepts the Sensor reference directly. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * Sensor2D <sensorName>) + *

+ * Alternative Syntax:
(ViewPlatformBehaviorProperty + * <name> Sensor2D (Sensor <sensorName>)) + * + * @param sensor array of length 1 containing a String or + * a Sensor + */ + public void Sensor2D(Object[] sensor) { + if (sensor.length != 1) + throw new IllegalArgumentException + ("Sensor2D requires a single name or Sensor instance") ; + + if (sensor[0] instanceof String) + sensor2DName = (String)sensor[0] ; + else if (sensor[0] instanceof Sensor) + sensor2D = (Sensor)sensor[0] ; + else + throw new IllegalArgumentException + ("Sensor2D must be a name or a Sensor instance") ; + } + + /** + * Returns a reference to the 2D valuator used for manipulating the view + * platform. + * + * @return the 2D valuator + */ + public Sensor getSensor2D() { + return sensor2D ; + } + + /** + * Property which sets a button action for the 6DOF sensor. The choices + * are TranslateForward, TranslateBackward, + * GrabView, RotateCCW, RotateCW, + * ScaleUp, ScaleDown, or None. By + * default, button 0 is bound to GrabView, button 1 is bound + * to TranslateForward, and button 2 is bound to + * TranslateBackward. If there are fewer than three buttons + * available, then the default button actions with the lower button + * indices have precedence. A value of None indicates that + * no default action is to be associated with the specified button. + *

+ * TranslateForward moves the view platform forward along the + * direction the sensor is pointing. TranslateBackward does + * the same, in the opposite direction. GrabView directly + * manipulates the view by moving it in inverse response to the sensor's + * position and orientation. RotateCCW and + * RotateCW rotate about a Y axis, while ScaleUp + * and ScaleDown scale the view platform larger and smaller. + *

+ * Specifying a button index that is greater than that available with the + * 6DOF sensor will result in an ArrayOutOfBoundsException + * when the behavior is initialized or attached to a + * ViewingPlatform. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ButtonAction6D <button index> + * [GrabView | TranslateForward | TranslateBackward | RotateCCW | + * RotateCW | ScaleUp | ScaleDown | None]) + * + * @param action array of length 2 containing a Double and a + * String. + * @see #setButtonAction6D + * @see #Sensor6D Sensor6D + * @see #TranslationSpeed TranslationSpeed + * @see #AccelerationTime AccelerationTime + * @see #ConstantSpeedTime ConstantSpeedTime + * @see #FastSpeedFactor FastSpeedFactor + * @see #RotationSpeed RotationSpeed + * @see #RotationCoords RotationCoords + * @see #ScaleSpeed ScaleSpeed + * @see #TransformCenterSource TransformCenterSource + * @see #TransformCenter TransformCenter + * @see GrabViewListener6D + * @see TranslationListener6D + * @see RotationListener6D + * @see ScaleListener6D + */ + public void ButtonAction6D(Object[] action) { + if (! (action.length == 2 && + action[0] instanceof Double && action[1] instanceof String)) + throw new IllegalArgumentException + ("\nButtonAction6D must be a number and a string") ; + + int button = ((Double)action[0]).intValue() ; + String actionString = (String)action[1] ; + + if (actionString.equals("GrabView")) + setButtonAction6D(button, GRAB_VIEW) ; + else if (actionString.equals("TranslateForward")) + setButtonAction6D(button, TRANSLATE_FORWARD) ; + else if (actionString.equals("TranslateBackward")) + setButtonAction6D(button, TRANSLATE_BACKWARD) ; + else if (actionString.equals("RotateCCW")) + setButtonAction6D(button, ROTATE_CCW) ; + else if (actionString.equals("RotateCW")) + setButtonAction6D(button, ROTATE_CW) ; + else if (actionString.equals("ScaleUp")) + setButtonAction6D(button, SCALE_UP) ; + else if (actionString.equals("ScaleDown")) + setButtonAction6D(button, SCALE_DOWN) ; + else if (actionString.equals("None")) + setButtonAction6D(button, NONE) ; + else + throw new IllegalArgumentException + ("\nButtonAction6D must be GrabView, TranslateForward, " + + "TranslateBackward, RotateCCW, RotateCW, ScaleUp, " + + "ScaleDown, or None") ; + } + + /** + * Sets a button action for the 6DOF sensor. The choices are + * TRANSLATE_FORWARD, TRANSLATE_BACKWARD, + * GRAB_VIEW, ROTATE_CCW, + * ROTATE_CW, SCALE_UP, SCALE_DOWN, + * or NONE. By default, button 0 is bound to + * GRAB_VIEW, button 1 is bound to + * TRANSLATE_FORWARD, and button 2 is bound to + * TRANSLATE_BACKWARD. If there are fewer than three buttons + * available, then the default button actions with the lower button + * indices have precedence. A value of NONE indicates that + * no default action is to be associated with the specified button. + *

+ * TRANSLATE_FORWARD moves the view platform forward along + * the direction the sensor is pointing. TRANSLATE_BACKWARD + * does the same, in the opposite direction. GRAB_VIEW + * directly manipulates the view by moving it in inverse response to the + * sensor's position and orientation. ROTATE_CCW and + * ROTATE_CW rotate about a Y axis, while + * SCALE_UP and SCALE_DOWN scale the view + * platform larger and smaller. + *

+ * Specifying a button index that is greater that that available with the + * 6DOF sensor will result in an ArrayOutOfBoundsException + * when the behavior is initialized or attached to a + * ViewingPlatform. + *

+ * This method only configures the button listeners pre-defined by + * this behavior. For complete control over the button actions, access + * the SensorEventAgent used by this behavior directly. + * + * @param button index of the button to bind + * @param action either TRANSLATE_FORWARD, + * TRANSLATE_BACKWARD, GRAB_VIEW, + * ROTATE_CCW, ROTATE_CW, SCALE_UP, + * SCALE_DOWN, or NONE + * @see #setTranslationSpeed + * @see #setAccelerationTime + * @see #setConstantSpeedTime + * @see #setFastSpeedFactor + * @see #setRotationSpeed + * @see #setRotationCoords + * @see #setScaleSpeed + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #getSensorEventAgent + * @see GrabViewListener6D + * @see TranslationListener6D + * @see RotationListener6D + * @see ScaleListener6D + */ + public synchronized void setButtonAction6D(int button, int action) { + if (! (action == TRANSLATE_FORWARD || action == TRANSLATE_BACKWARD || + action == GRAB_VIEW || action == ROTATE_CCW || + action == ROTATE_CW || action == SCALE_UP || + action == SCALE_DOWN || action == NONE)) + throw new IllegalArgumentException + ("\naction must be TRANSLATE_FORWARD, TRANSLATE_BACKWARD, " + + "GRAB_VIEW, ROTATE_CCW, ROTATE_CW, SCALE_UP, SCALE_DOWN, " + + "or NONE") ; + + while (button >= buttonActions6D.size()) { + buttonActions6D.add(null) ; + } + buttonActions6D.set(button, new Integer(action)) ; + } + + + /** + * Gets the action associated with the specified button on the 6DOF sensor. + * + * @return the action associated with the button + */ + public int getButtonAction6D(int button) { + if (button >= buttonActions6D.size()) + return NONE ; + + Integer i = (Integer)buttonActions6D.get(button) ; + if (i == null) + return NONE ; + else + return i.intValue() ; + } + + /** + * Property which sets the action to be bound to 2D valuator reads. This + * action will be performed on each frame whenever no button actions have + * been invoked and the valuator read value is greater than the threshold + * range specified by the Threshold2D property. + *

+ * The X and Y values from the valuator should have a continuous range + * from -1.0 to +1.0, although speeds can be scaled to compensate for a + * different range. The X and Y values are found in the sensor's read + * matrix at the indices specified by MatrixIndices2D, with + * defaults of 3 and 7 respectively. + *

+ * The default property value of Rotation rotates the view + * platform in the direction the valuator is pushed. The rotation + * coordinate system is set by the RotationCoords property, + * with a default of Sensor. The rotation occurs about a + * point in the virtual world set by the + * TransformCenterSource and TransformCenter + * properties, with the default set to rotate about the hotspot of a 6DOF + * sensor, if one is available, or about the origin of the virtual world + * if not. The rotation speed is scaled by the valuator read value up to + * the maximum speed set with the RotationSpeed property; the + * default is 180 degrees per second. + *

+ * A property value of Translation moves the view platform in + * the direction the valuator is pushed. The translation occurs along the + * X and Z basis vectors of either a 6DOF sensor or the view platform if a + * 6DOF sensor is not specified. The translation speed is scaled by the + * valuator read value up to a maximum set by the product of the + * TranslationSpeed and FastSpeedFactor property + * values. + *

+ * If this property value is to Scale, then the view platform + * is scaled smaller or larger when the valuator is pushed forward or + * backward. The scaling occurs about a point in the virtual world set by + * the TransformCenterSource and TransformCenter + * properties. The scaling speed is set with the ScaleSpeed + * property, with a default scale factor of 2.0 per second at the extreme + * negative range of -1.0, and a factor of 0.5 per second at the extreme + * positive range of +1.0. + *

+ * A value of None prevents Rotation from being + * bound to the 2D valuator reads. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ReadAction2D [Rotation | Translation | Scale | None]) + * + * @param action array of length 1 containing a String + * @see #setReadAction2D + * @see #RotationCoords RotationCoords + * @see #RotationSpeed RotationSpeed + * @see #TransformCenterSource TransformCenterSource + * @see #TransformCenter TransformCenter + * @see #TranslationSpeed TranslationSpeed + * @see #FastSpeedFactor FastSpeedFactor + * @see #ScaleSpeed ScaleSpeed + * @see #MatrixIndices2D MatrixIndices2D + * @see RotationListener2D + * @see TranslationListener2D + * @see ScaleListener2D + */ + public void ReadAction2D(Object[] action) { + if (! (action.length == 1 && action[0] instanceof String)) + throw new IllegalArgumentException + ("\nReadAction2D must be a String") ; + + String actionString = (String)action[0] ; + + if (actionString.equals("Rotation")) + setReadAction2D(ROTATION) ; + else if (actionString.equals("Translation")) + setReadAction2D(TRANSLATION) ; + else if (actionString.equals("Scale")) + setReadAction2D(SCALE) ; + else if (actionString.equals("None")) + setReadAction2D(NONE) ; + else + throw new IllegalArgumentException + ("\nReadAction2D must be Rotation, Translation, Scale, " + + "or None") ; + } + + /** + * Sets the action to be bound to 2D valuator reads. This action will be + * performed on each frame whenever no button actions have been invoked + * and the valuator read value is greater than the threshold range + * specified by setThreshold2D. + *

+ * The X and Y values from the valuator should have a continuous range + * from -1.0 to +1.0, although speeds can be scaled to compensate for a + * different range. The X and Y values are found in the sensor's read + * matrix at the indices specified by the setMatrixIndices2D + * method, with defaults of 3 and 7 respectively. + *

+ * The default action of ROTATION rotates the view platform + * in the direction the valuator is pushed. The rotation coordinate + * system is set by setRotationCoords, with a default of + * SENSOR. The rotation occurs about a point in the virtual + * world set by setTransformCenterSource and + * setTransformCenter, with the default set to rotate about + * the hotspot of a 6DOF sensor, if one is available, or about the origin + * of the virtual world if not. The rotation speed is scaled by the + * valuator read value up to the maximum speed set with + * setRotationSpeed; the default is 180 degrees per second. + *

+ * A value of TRANSLATION moves the view platform in the + * direction the valuator is pushed. The translation occurs along the X + * and Z basis vectors of either a 6DOF sensor or the view platform if a + * 6DOF sensor is not specified. The translation speed is scaled by the + * valuator read value up to a maximum set by the product of the + * setTranslationSpeed and setFastSpeedFactor + * values. + *

+ * If the value is to SCALE, then the view platform is scaled + * smaller or larger when the valuator is pushed forward or backward. The + * scaling occurs about a point in the virtual world set by + * setTransformCenterSource and + * setTransformCenter. The scaling speed is set with + * setScaleSpeed, with a default scale factor of 2.0 per + * second at the extreme negative range of -1.0, and a factor of 0.5 per + * second at the extreme positive range of +1.0. + *

+ * A value of NONE prevents ROTATION from being + * bound by default to the 2D valuator reads. + *

+ * This method only configures the read listeners pre-defined by + * this behavior. For complete control over the read actions, access + * the SensorEventAgent used by this behavior directly. + * + * @param action either ROTATION, TRANSLATION, + * SCALE, or NONE + * @see #setRotationCoords + * @see #setRotationSpeed + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #setTranslationSpeed + * @see #setFastSpeedFactor + * @see #setScaleSpeed + * @see #setMatrixIndices2D + * @see #getSensorEventAgent + * @see RotationListener2D + * @see TranslationListener2D + * @see ScaleListener2D + */ + public void setReadAction2D(int action) { + if (! (action == ROTATION || action == TRANSLATION || + action == SCALE || action == NONE)) + throw new IllegalArgumentException + ("\nReadAction2D must be ROTATION, TRANSLATION, SCALE, " + + "or NONE") ; + + this.readAction2D = action ; + } + + /** + * Gets the configured 2D valuator read action. + * + * @return the action associated with the sensor read + */ + public int getReadAction2D() { + if (readAction2D == UNSET) + return NONE ; + else + return readAction2D ; + } + + /** + * Property which sets a button action for the 2D valuator. The possible + * values are Rotation, Translation, + * Scale, or None, with a default of + * None. These actions are the same as those for + * ReadAction2D. + *

+ * Specifying a button index that is greater that that available with the + * 2D valuator will result in an ArrayOutOfBoundsException + * when the behavior is initialized or attached to a + * ViewingPlatform. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ButtonAction2D <button index> + * [Rotation | Translation | Scale | None]) + * + * @param action array of length 2 containing a Double and a + * String. + * @see #setButtonAction2D + * @see #ReadAction2D ReadAction2D + * @see #RotationCoords RotationCoords + * @see #RotationSpeed RotationSpeed + * @see #TransformCenterSource TransformCenterSource + * @see #TransformCenter TransformCenter + * @see #TranslationSpeed TranslationSpeed + * @see #FastSpeedFactor FastSpeedFactor + * @see #ScaleSpeed ScaleSpeed + * @see #MatrixIndices2D MatrixIndices2D + * @see RotationListener2D + * @see TranslationListener2D + * @see ScaleListener2D + */ + public void ButtonAction2D(Object[] action) { + if (! (action.length == 2 && + action[0] instanceof Double && action[1] instanceof String)) + throw new IllegalArgumentException + ("\nButtonAction2D must be a number and a string") ; + + int button = ((Double)action[0]).intValue() ; + String actionString = (String)action[1] ; + + if (actionString.equals("Rotation")) + setButtonAction2D(button, ROTATION) ; + else if (actionString.equals("Translation")) + setButtonAction2D(button, TRANSLATION) ; + else if (actionString.equals("Scale")) + setButtonAction2D(button, SCALE) ; + else if (actionString.equals("None")) + setButtonAction2D(button, NONE) ; + else + throw new IllegalArgumentException + ("\nButtonAction2D must be Rotation, Translation, Scale " + + "or None") ; + } + + /** + * Sets a button action for the 2D valuator. The possible values are + * ROTATION, TRANSLATION, SCALE, or + * NONE, with a default of NONE. These actions + * are the same as those for setReadAction2D. + *

+ * Specifying a button index that is greater that that available with the + * 2D valuator will result in an ArrayOutOfBoundsException + * when the behavior is initialized or attached to a + * ViewingPlatform. + *

+ * This method only configures the button listeners pre-defined by + * this behavior. For complete control over the button actions, access + * the SensorEventAgent used by this behavior directly. + * + * @param button index of the button to bind + * @param action either ROTATION, TRANSLATION, + * SCALE, or NONE + * @see #setReadAction2D + * @see #setRotationCoords + * @see #setRotationSpeed + * @see #setTransformCenterSource + * @see #setTransformCenter + * @see #setTranslationSpeed + * @see #setFastSpeedFactor + * @see #setScaleSpeed + * @see #setMatrixIndices2D + * @see #getSensorEventAgent + * @see RotationListener2D + * @see TranslationListener2D + * @see ScaleListener2D + */ + public synchronized void setButtonAction2D(int button, int action) { + if (! (action == ROTATION || action == TRANSLATION || + action == SCALE || action == NONE)) + throw new IllegalArgumentException + ("\naction must be ROTATION, TRANSLATION, SCALE, or NONE") ; + + while (button >= buttonActions2D.size()) { + buttonActions2D.add(null) ; + } + buttonActions2D.set(button, new Integer(action)) ; + } + + + /** + * Gets the action associated with the specified button on the 2D valuator. + * + * @return the action associated with the button + */ + public int getButtonAction2D(int button) { + if (button >= buttonActions2D.size()) + return NONE ; + + Integer i = (Integer)buttonActions2D.get(button) ; + if (i == null) + return NONE ; + else + return i.intValue() ; + } + + /** + * Property which sets the action to be bound to 6DOF sensor reads. This + * action will be performed every frame whenever a button action has not + * been invoked. + *

+ * The default is Echo, which displays a geometric + * representation of the sensor's current position and orientation in the + * virtual world. A value of None indicates that no default + * action is to be applied to the sensor's read. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ReadAction6D [Echo | None]) + * + * @param action array of length 1 containing a String + * @see #setReadAction6D + * @see EchoReadListener6D + */ + public void ReadAction6D(Object[] action) { + if (! (action.length == 1 && action[0] instanceof String)) + throw new IllegalArgumentException + ("\nReadAction6D must be a String") ; + + String actionString = (String)action[0] ; + + if (actionString.equals("Echo")) + setReadAction6D(ECHO) ; + else if (actionString.equals("None")) + setReadAction6D(NONE) ; + else + throw new IllegalArgumentException + ("\nReadAction6D must be Echo or None") ; + } + + /** + * Sets the action to be bound to 6DOF sensor reads. This action will be + * performed every frame whenever a button action has not been invoked. + *

+ * The default is ECHO, which displays a geometric + * representation of the sensor's current position and orientation in the + * virtual world. A value of NONE indicates that no default + * action is to be associated with the sensor's read. + *

+ * This method only configures the read listeners pre-defined by + * this behavior. For complete control over the read actions, access + * the SensorEventAgent used by this behavior directly. + * + * @param action either ECHO or NONE + * @see EchoReadListener6D + * @see #getSensorEventAgent + */ + public void setReadAction6D(int action) { + if (! (action == ECHO || action == NONE)) + throw new IllegalArgumentException + ("\naction must be ECHO or NONE") ; + + this.readAction6D = action ; + } + + /** + * Gets the configured 6DOF sensor read action. + * + * @return the configured 6DOF sensor read action + */ + public int getReadAction6D() { + if (readAction6D == UNSET) + return NONE ; + else + return readAction6D ; + } + + /** + * Property which sets the normal translation speed. The default is 0.1 + * meters/second in physical units. This property is set in the + * configuration file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * TranslationSpeed <speed> [PhysicalMeters | VirtualUnits] + * [PerFrame | PerSecond]) + * + * @param speed array of length 3; first element is a Double + * for the speed, the second is a String for the units, and + * the third is a String for the time base + * @see #setTranslationSpeed + */ + public void TranslationSpeed(Object[] speed) { + if (! (speed.length == 3 && speed[0] instanceof Double && + speed[1] instanceof String && speed[2] instanceof String)) + throw new IllegalArgumentException + ("\nTranslationSpeed must be number, units, and time base") ; + + double v = ((Double)speed[0]).doubleValue() ; + String unitsString = (String)speed[1] ; + String timeBaseString = (String)speed[2] ; + int units, timeBase ; + + if (unitsString.equals("PhysicalMeters")) + units = PHYSICAL_METERS ; + else if (unitsString.equals("VirtualUnits")) + units = VIRTUAL_UNITS ; + else + throw new IllegalArgumentException + ("\nTranslationSpeed units must be " + + "PhysicalMeters or VirtualUnits") ; + + if (timeBaseString.equals("PerFrame")) + timeBase = PER_FRAME ; + else if (timeBaseString.equals("PerSecond")) + timeBase = PER_SECOND ; + else + throw new IllegalArgumentException + ("\ntime base must be PerFrame or PerSecond") ; + + setTranslationSpeed(v, units, timeBase) ; + } + + /** + * Sets the normal translation speed. The default is 0.1 physical + * meters/second. + * + * @param speed how fast to translate + * @param units either PHYSICAL_METERS or + * VIRTUAL_UNITS + * @param timeBase either PER_SECOND or + * PER_FRAME + */ + public void setTranslationSpeed(double speed, int units, int timeBase) { + this.translationSpeed = speed ; + + if (units == PHYSICAL_METERS || units == VIRTUAL_UNITS) + this.translationUnits = units ; + else + throw new IllegalArgumentException + ("\ntranslation speed units must be " + + "PHYSICAL_METERS or VIRTUAL_UNITS") ; + + if (timeBase == PER_FRAME || timeBase == PER_SECOND) + this.translationTimeBase = timeBase ; + else + throw new IllegalArgumentException + ("\ntranslation time base must be PER_FRAME or PER_SECOND") ; + } + + /** + * Gets the normal speed at which to translate the view platform. + * + * @return the normal translation speed + */ + public double getTranslationSpeed() { + return translationSpeed ; + } + + /** + * Gets the translation speed units. + * + * @return the translation units + */ + public int getTranslationUnits() { + return translationUnits ; + } + + /** + * Gets the time base for translation speed. + * + * @return the translation time base + */ + public int getTranslationTimeBase() { + return translationTimeBase ; + } + + /** + * Property which sets the time interval for accelerating to the + * translation, rotation, or scale speeds and for transitioning between + * the normal and fast translation speeds. The default is 1 second. This + * property is set in the configuration file read by + * ConfiguredUniverse.

+ * + * Syntax:
(ViewPlatformBehaviorProperty <name> + * AccelerationTime <seconds>) + * + * @param time array of length 1 containing a Double + * @see #setAccelerationTime + */ + public void AccelerationTime(Object[] time) { + if (! (time.length == 1 && time[0] instanceof Double)) + throw new IllegalArgumentException + ("\nAccelerationTime must be a number") ; + + setAccelerationTime(((Double)time[0]).doubleValue()) ; + } + + /** + * Sets the time interval for accelerating to the translation, rotation, + * or scale speeds and for transitioning between the normal and fast + * translation speeds. The default is 1 second. + * + * @param time number of seconds to accelerate to normal or fast speed + */ + public void setAccelerationTime(double time) { + this.accelerationTime = time ; + } + + /** + * Gets the time interval for accelerating to normal speed and for + * transitioning between the normal and fast translation speeds. + * + * @return the acceleration time + */ + public double getAccelerationTime() { + return accelerationTime ; + } + + /** + * Property which sets the time interval for which the translation occurs + * at the normal speed. The default is 8 seconds. This property is set + * in the configuration file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ConstantSpeedTime <seconds>) + * + * @param time array of length 1 containing a Double + * @see #setConstantSpeedTime + */ + public void ConstantSpeedTime(Object[] time) { + if (! (time.length == 1 && time[0] instanceof Double)) + throw new IllegalArgumentException + ("\nConstantSpeedTime must be a number") ; + + setConstantSpeedTime(((Double)time[0]).doubleValue()) ; + } + + /** + * Sets the time interval for which the translation occurs at the normal + * speed. The default is 8 seconds. + * + * @param time number of seconds to translate at a constant speed + */ + public void setConstantSpeedTime(double time) { + this.constantSpeedTime = time ; + } + + /** + * Gets the time interval for which the translation occurs at the + * normal speed. + * + * @return the constant speed time + */ + public double getConstantSpeedTime() { + return constantSpeedTime ; + } + + /** + * Property which sets the fast translation speed factor. The default is + * 10 times the normal speed. This property is set in the configuration + * file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * FastSpeedFactor <factor>) + * + * @param factor array of length 1 containing a Double + * @see #setFastSpeedFactor + */ + public void FastSpeedFactor(Object[] factor) { + if (! (factor.length == 1 && factor[0] instanceof Double)) + throw new IllegalArgumentException + ("\nFastSpeedFactor must be a number") ; + + setFastSpeedFactor(((Double)factor[0]).doubleValue()) ; + } + + /** + * Sets the fast translation speed factor. The default is 10 times the + * normal speed. + * + * @param factor scale by which the normal translation speed is multiplied + */ + public void setFastSpeedFactor(double factor) { + this.fastSpeedFactor = factor ; + } + + /** + * Gets the factor by which the normal translation speed is multiplied + * after the constant speed time interval. + * + * @return the fast speed factor + */ + public double getFastSpeedFactor() { + return fastSpeedFactor ; + } + + /** + * Property which sets the threshold for 2D valuator reads. The default + * is 0.0. It can be set higher to handle noisy valuators. This property + * is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * Threshold2D <threshold>) + * + * @param threshold array of length 1 containing a Double + * @see #setThreshold2D + */ + public void Threshold2D(Object[] threshold) { + if (! (threshold.length == 1 && threshold[0] instanceof Double)) + throw new IllegalArgumentException + ("\nThreshold2D must be a number") ; + + setThreshold2D(((Double)threshold[0]).doubleValue()) ; + } + + /** + * Sets the threshold for 2D valuator reads. The default is 0.0. It can + * be set higher to handle noisy valuators. + * + * @param threshold if the absolute values of both the X and Y valuator + * reads are less than this value then the values are ignored + */ + public void setThreshold2D(double threshold) { + this.threshold2D = threshold ; + } + + /** + * Gets the 2D valuator threshold. + * + * @return the threshold value + */ + public double getThreshold2D() { + return threshold2D ; + } + + /** + * Property which specifies where to find the X and Y values in the matrix + * read generated by a 2D valuator. The defaults are along the + * translation components of the matrix, at indices 3 and 7. This + * property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * MatrixIndices2D <X index> <Y index>) + * + * @param indices array of length 2 containing Doubles + * @see #setMatrixIndices2D + */ + public void MatrixIndices2D(Object[] indices) { + if (! (indices.length == 2 && + indices[0] instanceof Double && indices[1] instanceof Double)) + throw new IllegalArgumentException + ("\nMatrixIndices2D must be a numbers") ; + + setMatrixIndices2D(((Double)indices[0]).intValue(), + ((Double)indices[1]).intValue()) ; + } + + /** + * Specifies where to find the X and Y values in the matrix read generated + * by a 2D valuator. The defaults are along the translation components of + * the matrix, at indices 3 and 7. + * + * @param xIndex index of the X valuator value + * @param yIndex index of the Y valuator value + */ + public void setMatrixIndices2D(int xIndex, int yIndex) { + this.x2D = xIndex ; + this.y2D = yIndex ; + } + + /** + * Gets the index where the X value of a 2D valuator read matrix can be + * found. + * + * @return the X index in the read matrix + */ + public int getMatrixXIndex2D() { + return x2D ; + } + + /** + * Gets the index where the Y value of a 2D valuator read matrix can be + * found. + * + * @return the Y index in the read matrix + */ + public int getMatrixYIndex2D() { + return y2D ; + } + + /** + * Property which sets the rotation speed. The default is 180 + * degrees/second. This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * RotationSpeed <speed> [Degrees | Radians] + * [PerFrame | PerSecond]) + * + * @param speed array of length 3; first element is a Double + * for the speed, the second is a String for the units, and + * the third is a String for the time base + * @see #setRotationSpeed + */ + public void RotationSpeed(Object[] speed) { + if (! (speed.length == 3 && speed[0] instanceof Double && + speed[1] instanceof String && speed[2] instanceof String)) + throw new IllegalArgumentException + ("\nRotationSpeed must be number, units, and time base") ; + + double v = ((Double)speed[0]).doubleValue() ; + String unitsString = (String)speed[1] ; + String timeBaseString = (String)speed[2] ; + int units, timeBase ; + + if (unitsString.equals("Degrees")) + units = DEGREES ; + else if (unitsString.equals("Radians")) + units = RADIANS ; + else + throw new IllegalArgumentException + ("\nRotationSpeed units must be Degrees or Radians") ; + + if (timeBaseString.equals("PerFrame")) + timeBase = PER_FRAME ; + else if (timeBaseString.equals("PerSecond")) + timeBase = PER_SECOND ; + else + throw new IllegalArgumentException + ("\nRotationSpeed time base must be PerFrame or PerSecond") ; + + setRotationSpeed(v, units, timeBase) ; + } + + /** + * Sets the rotation speed. The default is 180 degrees/second. + * + * @param speed how fast to rotate + * @param units either DEGREES or RADIANS + * @param timeBase either PER_SECOND or PER_FRAME + */ + public void setRotationSpeed(double speed, int units, int timeBase) { + this.rotationSpeed = speed ; + + if (units == DEGREES || units == RADIANS) + this.rotationUnits = units ; + else + throw new IllegalArgumentException + ("\nrotation speed units must be DEGREES or RADIANS") ; + + if (timeBase == PER_FRAME || timeBase == PER_SECOND) + this.rotationTimeBase = timeBase ; + else + throw new IllegalArgumentException + ("\nrotation time base must be PER_FRAME or PER_SECOND") ; + } + + /** + * Gets the rotation speed. + * + * @return the rotation speed + */ + public double getRotationSpeed() { + return rotationSpeed ; + } + + /** + * Gets the rotation speed units + * + * @return the rotation units + */ + public int getRotationUnits() { + return rotationUnits ; + } + + /** + * Gets the time base for rotation speed. + * + * @return the rotation time base + */ + public int getRotationTimeBase() { + return rotationTimeBase ; + } + + /** + * Property which sets the rotation coordinate system. The default is + * Sensor, which means that the rotation axis is parallel to + * the XY plane of the current orientation of a specified 6DOF sensor. A + * value of ViewPlatform means the rotation axis is parallel + * to the XY plane of the view platform. The latter is also the fallback + * if a 6DOF sensor is not specified. If the value is Head, + * then the rotation occurs in head coordinates. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * RotationCoords [Sensor | ViewPlatform | Head]) + * + * @param coords array of length 1 containing a String + * @see #setRotationCoords + */ + public void RotationCoords(Object[] coords) { + if (! (coords.length == 1 && coords[0] instanceof String)) + throw new IllegalArgumentException + ("\nRotationCoords must be a String") ; + + String coordsString = (String)coords[0] ; + + if (coordsString.equals("Sensor")) + setRotationCoords(SENSOR) ; + else if (coordsString.equals("ViewPlatform")) + setRotationCoords(VIEW_PLATFORM) ; + else if (coordsString.equals("Head")) + setRotationCoords(HEAD) ; + else + throw new IllegalArgumentException + ("\nRotationCoords must be Sensor, ViewPlatform, or Head") ; + } + + /** + * Sets the rotation coordinate system. The default is + * SENSOR, which means that the rotation axis is parallel to + * the XY plane of the current orientation of a specified 6DOF sensor. A + * value of VIEW_PLATFORM means the rotation axis is parallel + * to the XY plane of the view platform. The latter is also the fallback + * if a 6DOF sensor is not specified. If the value is HEAD, + * then rotation occurs in head coordinates. + * + * @param coords either SENSOR, VIEW_PLATFORM, or + * HEAD + */ + public void setRotationCoords(int coords) { + if (! (coords == SENSOR || coords == VIEW_PLATFORM || coords == HEAD)) + throw new IllegalArgumentException + ("\nrotation coordinates be SENSOR, VIEW_PLATFORM, or HEAD") ; + + this.rotationCoords = coords ; + } + + /** + * Gets the rotation coordinate system. + * + * @return the rotation coordinate system + */ + public int getRotationCoords() { + return rotationCoords ; + } + + /** + * Property which sets the scaling speed. The default is 2.0 per second, + * which means magnification doubles the apparent size of the virtual + * world every second, and minification halves the apparent size of the + * virtual world every second. + *

+ * The scaling applied with each frame is Math.pow(scaleSpeed, + * frameTime), where frameTime is the time in seconds + * that the last frame took to render if the time base is + * PerSecond, or 1.0 if the time base is + * PerFrame. If scaling is performed with the 2D valuator, + * then the valuator's Y value as specified by + * MatrixIndices2D is an additional scale applied to the + * exponent. If scaling is performed by the 6DOF sensor, then the scale + * speed can be inverted with a negative exponent by using the appropriate + * listener constructor flag. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ScaleSpeed <speed> [PerFrame | PerSecond]) + * + * @param speed array of length 2; first element is a Double + * for the speed, and the second is a String for the time + * base + * @see #setScaleSpeed + */ + public void ScaleSpeed(Object[] speed) { + if (! (speed.length == 2 && + speed[0] instanceof Double && speed[1] instanceof String)) + throw new IllegalArgumentException + ("\nScalingSpeed must be a number and a string") ; + + double v = ((Double)speed[0]).doubleValue() ; + String timeBaseString = (String)speed[2] ; + int timeBase ; + + if (timeBaseString.equals("PerFrame")) + timeBase = PER_FRAME ; + else if (timeBaseString.equals("PerSecond")) + timeBase = PER_SECOND ; + else + throw new IllegalArgumentException + ("\nScalingSpeed time base must be PerFrame or PerSecond") ; + + setScaleSpeed(v, timeBase) ; + } + + /** + * Sets the scaling speed. The default is 2.0 per second, which means + * magnification doubles the apparent size of the virtual world every + * second, and minification halves the apparent size of the virtual world + * every second. + *

+ * The scaling applied with each frame is Math.pow(scaleSpeed, + * frameTime), where frameTime is the time in seconds + * that the last frame took to render if the time base is + * PER_SECOND, or 1.0 if the time base is + * PER_FRAME. If scaling is performed with the 2D valuator, + * then the valuator's Y value as specified by + * setMatrixIndices2D is an additional scale applied to the + * exponent. If scaling is performed by the 6DOF sensor, then the scale + * speed can be inverted with a negative exponent by using the appropriate + * listener constructor flag. + * + * @param speed specifies the scale speed + * @param timeBase either PER_SECOND or PER_FRAME + */ + public void setScaleSpeed(double speed, int timeBase) { + this.scaleSpeed = speed ; + + if (timeBase == PER_FRAME || timeBase == PER_SECOND) + this.scaleTimeBase = timeBase ; + else + throw new IllegalArgumentException + ("\nscaling time base must be PER_FRAME or PER_SECOND") ; + } + + /** + * Gets the scaling speed. + * + * @return the scaling speed + */ + public double getScaleSpeed() { + return scaleSpeed ; + } + + /** + * Gets the time base for scaling speed. + * + * @return the scaling time base + */ + public int getScaleTimeBase() { + return scaleTimeBase ; + } + + /** + * Property which sets the source of the center of rotation and scale. + * The default is Hotspot, which means the center of rotation + * or scale is a 6DOF sensor's current hotspot location. The alternative + * is VworldFixed, which uses the fixed virtual world + * coordinates specified by the TransformCenter property as + * the center. The latter is also the fallback if a 6DOF sensor is not + * specified. This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * TransformCenterSource [Hotspot | VworldFixed]) + * + * @param source array of length 1 containing a String + * @see #setTransformCenterSource + */ + public void TransformCenterSource(Object[] source) { + if (! (source.length == 1 && source[0] instanceof String)) + throw new IllegalArgumentException + ("\nTransformCenterSource must be a String") ; + + String sourceString = (String)source[0] ; + + if (sourceString.equals("Hotspot")) + setTransformCenterSource(HOTSPOT) ; + else if (sourceString.equals("VworldFixed")) + setTransformCenterSource(VWORLD_FIXED) ; + else + throw new IllegalArgumentException + ("\nTransformCenterSource must be Hotspot or " + + "VworldFixed") ; + } + + /** + * Sets the source of the center of rotation and scale. The default is + * HOTSPOT, which means the center of rotation or scale is a + * 6DOF sensor's current hotspot location. The alternative is + * VWORLD_FIXED, which uses the fixed virtual world + * coordinates specified by setTransformCenter as the center. + * The latter is also the fallback if a 6DOF sensor is not specified. + *

+ * The transform center source can be dynamically updated while the + * behavior is running. + * + * @param source either HOTSPOT or VWORLD_FIXED + */ + public void setTransformCenterSource(int source) { + if (! (source == HOTSPOT || source == VWORLD_FIXED)) + throw new IllegalArgumentException + ("\nrotation/scale center source must be HOTSPOT or " + + "VWORLD_FIXED") ; + + this.transformCenterSource = source ; + } + + /** + * Gets the rotation/scale center source. + * + * @return the rotation/scale center source + */ + public int getTransformCenterSource() { + return transformCenterSource ; + } + + /** + * Property which sets the center of rotation and scale if the + * TransformCenterSource property is VworldFixed + * or if a 6DOF sensor is not specified. The default is (0.0, 0.0, 0.0) + * in virtual world coordinates. This property is set in the + * configuration file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * TransformCenter <Point3d>) + * + * @param center array of length 1 containing a Point3d + * @see #setTransformCenter + */ + public void TransformCenter(Object[] center) { + if (! (center.length == 1 && center[0] instanceof Point3d)) + throw new IllegalArgumentException + ("\nTransformCenter must be a Point3d") ; + + setTransformCenter((Point3d)center[0]) ; + } + + /** + * Sets the center of rotation and scale if + * setTransformCenterSource is called with + * VWORLD_FIXED or if a 6DOF sensor is not specified. The + * default is (0.0, 0.0, 0.0) in virtual world coordinates. + *

+ * The transform center can be dynamically updated while the behavior is + * running. + * + * @param center point in virtual world coordinates about which to rotate + * and scale + */ + public void setTransformCenter(Point3d center) { + this.transformCenter.set(center) ; + } + + /** + * Gets the rotation/scale center in virtual world coordinates. + * @param center Point3d to receive a copy of the + * rotation/scale center + */ + public void getTransformCenter(Point3d center) { + center.set(transformCenter) ; + } + + /** + * Property which sets the nominal sensor rotation. The default is the + * identity transform. + *

+ * This behavior assumes that when a hand-held wand is pointed directly at + * a screen in an upright position, then its 6DOF sensor's local + * coordinate system axes (its basis vectors) are nominally aligned with + * the image plate basis vectors; specifically, that the sensor's -Z axis + * points toward the screen, the +Y axis points up, and the +X axis points + * to the right. The translation and rotation listeners provided by this + * behavior assume this orientation to determine the transforms to be + * applied to the view platform; for example, translation applies along + * the sensor Z axis, while rotation applies about axes defined in the + * sensor XY plane. + *

+ * This nominal alignment may not hold true depending upon how the sensor + * is mounted and how the specific InputDevice supporting the + * sensor handles its orientation. The NominalSensorRotation + * property can be used to correct the alignment by specifying the + * rotation needed to transform vectors from the nominal sensor coordinate + * system, aligned with the image plate coordinate system as described + * above, to the sensor's actual local coordinate system. + *

+ * NOTE: the nominal sensor transform applies only to the + * translation directions and rotation axes created by the listeners + * defined in this behavior; for compatibility with the core Java 3D API, + * sensor reads and the sensor hotspot location are still expressed in the + * sensor's local coordinate system. + *

+ * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * NominalSensorRotation [<Matrix4d> | + * <Matrix3d>]) + * + * @param matrix array of length 1 containing a Matrix4d or + * Matrix3d + * @see #setNominalSensorRotation + */ + public void NominalSensorRotation(Object[] matrix) { + if (! (matrix.length == 1 && (matrix[0] instanceof Matrix3d || + matrix[0] instanceof Matrix4d))) + throw new IllegalArgumentException + ("\nNominalSensorRotation must be a Matrix3d or Matrix4d") ; + + Transform3D t3d = new Transform3D() ; + + if (matrix[0] instanceof Matrix3d) + t3d.set((Matrix3d)matrix[0]) ; + else + t3d.set((Matrix4d)matrix[0]) ; + + setNominalSensorRotation(t3d) ; + } + + /** + * Sets the nominal sensor transform. The default is the identity + * transform. + *

+ * This behavior assumes that when a hand-held wand is pointed directly at + * a screen in an upright position, then its 6DOF sensor's local + * coordinate system axes (its basis vectors) are nominally aligned with + * the image plate basis vectors; specifically, that the sensor's -Z axis + * points toward the screen, the +Y axis points up, and the +X axis points + * to the right. The translation and rotation listeners provided by this + * behavior assume this orientation to determine the transforms to be + * applied to the view platform, in that translation applies along the + * sensor Z axis, and rotation applies about axes defined in the sensor XY + * plane. + *

+ * This nominal alignment may not hold true depending upon how the sensor + * is mounted and how the specific InputDevice supporting the + * sensor handles its orientation. setNominalSensorRotation + * can be called to correct the alignment by specifying the rotation + * needed to transform vectors from the nominal sensor coordinate system, + * aligned with the image plate coordinate system as described above, to + * the sensor's actual local coordinate system. + *

+ * NOTE: the nominal sensor transform applies only to the + * translation directions and rotation axes created by the listeners + * defined in this behavior; for compatibility with the core Java 3D API, + * sensor reads and the sensor hotspot location are still expressed in the + * sensor's local coordinate system. + * + * @param transform Rotates vectors from the nominal sensor coordinate + * system system to the sensor's local coordinate system; only the + * rotational components are used. May be set null for + * identity. + */ + public void setNominalSensorRotation(Transform3D transform) { + if (transform == null) { + nominalSensorRotation = null ; + return ; + } + + if (nominalSensorRotation == null) + nominalSensorRotation = new Transform3D() ; + + // Set transform and make sure it is a rotation only. + nominalSensorRotation.set(transform) ; + nominalSensorRotation.setTranslation(new Vector3d()) ; + } + + /** + * Gets the nominal sensor transform. + * + * @param t3d Transform3D to receive a copy of the + * nominal sensor transform + */ + public void getNominalSensorRotation(Transform3D t3d) { + t3d.set(nominalSensorRotation) ; + } + + /** + * Property which sets the number of buttons to be pressed simultaneously + * on the 6DOF sensor in order to reset the view back to the home + * transform. The value must be greater than 1; the default is 3. A + * value of None disables this action. This property is set + * in the configuration file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ResetViewButtonCount6D [<count> | None]) + * + * @param count array of length 1 containing a Double or + * String + * @see #setResetViewButtonCount6D + * @see ViewPlatformBehavior#setHomeTransform + * ViewPlatformBehavior.setHomeTransform + */ + public void ResetViewButtonCount6D(Object[] count) { + if (! (count.length == 1 && + (count[0] instanceof Double || count[0] instanceof String))) + throw new IllegalArgumentException + ("\nResetViewButtonCount6D must be a number or None") ; + + if (count[0] instanceof String) { + String s = (String)count[0] ; + if (s.equals("None")) + setResetViewButtonCount6D(NONE) ; + else + throw new IllegalArgumentException + ("\nResetViewButtonCount6D string value must be None") ; + } + else { + setResetViewButtonCount6D(((Double)count[0]).intValue()) ; + } + } + + /** + * Sets the number of buttons to be pressed simultaneously + * on the 6DOF sensor in order to reset the view back to the home + * transform. The value must be greater than 1; the default is 3. A + * value of NONE disables this action. + * + * @param count either NONE or button count > 1 + * @see ViewPlatformBehavior#setHomeTransform + * ViewPlatformBehavior.setHomeTransform + */ + public void setResetViewButtonCount6D(int count) { + if (count == NONE || count > 1) { + resetViewButtonCount6D = count ; + } + else { + throw new IllegalArgumentException + ("reset view button count must be > 1") ; + } + } + + /** + * Gets the number of buttons to be pressed simultaneously on the 6DOF + * sensor in order to reset the view back to the home transform. A value + * of NONE indicates this action is disabled. + * + * @return the number of buttons to press simultaneously for a view reset + */ + public int getResetViewButtonCount6D() { + return resetViewButtonCount6D ; + } + + /** + * Property which sets the number of buttons to be pressed simultaneously + * on the 2D valuator in order to reset the view back to the home + * transform. The value must be greater than 1; the default is + * None. A value of None disables this action. + * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * ResetViewButtonCount2D [<count> | None]) + * + * @param count array of length 1 containing a Double or + * String + * @see #setResetViewButtonCount2D + * @see ViewPlatformBehavior#setHomeTransform + * ViewPlatformBehavior.setHomeTransform + */ + public void ResetViewButtonCount2D(Object[] count) { + if (! (count.length == 1 && + (count[0] instanceof Double || count[0] instanceof String))) + throw new IllegalArgumentException + ("\nResetViewButtonCount2D must be a number or None") ; + + if (count[0] instanceof String) { + String s = (String)count[0] ; + if (s.equals("None")) + setResetViewButtonCount2D(NONE) ; + else + throw new IllegalArgumentException + ("\nResetViewButtonCount2D string value must be None") ; + } + else { + setResetViewButtonCount2D(((Double)count[0]).intValue()) ; + } + } + + /** + * Sets the number of buttons to be pressed simultaneously on the 2D + * valuator in order to reset the view back to the home transform. The + * value must be greater than 1; the default is NONE. A + * value of NONE disables this action. + * + * @param count either NONE or button count > 1 + * @see ViewPlatformBehavior#setHomeTransform + * ViewPlatformBehavior.setHomeTransform + */ + public void setResetViewButtonCount2D(int count) { + if (count == NONE || count > 1) { + resetViewButtonCount2D = count ; + } + else { + throw new IllegalArgumentException + ("reset view button count must be > 1") ; + } + } + + /** + * Gets the number of buttons to be pressed simultaneously on the 2D + * valuator in order to reset the view back to the home transform. A value + * of NONE indicates this action is disabled. + * + * @return the number of buttons to press simultaneously for a view reset + */ + public int getResetViewButtonCount2D() { + return resetViewButtonCount2D ; + } + + /** + * Property which sets the 6DOF sensor echo type. The default is + * Gnomon, which displays an object with points indicating + * the direction of each of the sensor's coordinate system axes at the + * location of the sensor's hotspot. The alternative is + * Beam, which displays a beam from the sensor's origin to + * the location of the sensor's hotspot; the hotspot must not be (0, 0, 0) + * or an IllegalArgumentException will result. The width of + * each of these echo types is specified by the EchoSize + * property. The EchoType property is set in the + * configuration file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * EchoType [Gnomon | Beam | None]) + * + * @param type array of length 1 containing a String + * @see #setEchoType + */ + public void EchoType(Object[] type) { + if (! (type.length == 1 && type[0] instanceof String)) + throw new IllegalArgumentException + ("\nEchoType must be a String") ; + + String typeString = (String)type[0] ; + + if (typeString.equals("Gnomon")) + setEchoType(GNOMON) ; + else if (typeString.equals("Beam")) + setEchoType(BEAM) ; + else if (typeString.equals("None")) + setEchoType(NONE) ; + else + throw new IllegalArgumentException + ("\nEchoType must be Gnomon, Beam, or None") ; + } + + /** + * Sets the 6DOF sensor echo type. The default is GNOMON, + * which displays an object with points indicating the direction of each + * of the sensor's coordinate axes at the location of the sensor's + * hotspot. The alternative is BEAM, which displays a beam + * from the sensor's origin to the location of the sensor's hotspot; the + * hotspot must not be (0, 0, 0) or an + * IllegalArgumentException will result. The width of each + * of these echo types is specified by setEchoSize. + * + * @param type GNOMON, BEAM, or + * NONE are recognized + */ + public void setEchoType(int type) { + this.echoType = type ; + } + + /** + * Gets the echo type. + * + * @return the echo type + */ + public int getEchoType() { + return echoType ; + } + + /** + * Property which sets the size of the 6DOF sensor echo in physical + * meters. This is used for the width of the gnomon and beam echoes. The + * default is 1 centimeter. This property is set in the configuration + * file read by ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * EchoSize <size>) + * + * @param echoSize array of length 1 containing a Double + * @see #setEchoSize + */ + public void EchoSize(Object[] echoSize) { + if (! (echoSize.length == 1 && echoSize[0] instanceof Double)) + throw new IllegalArgumentException + ("\nEchoSize must be a Double") ; + + setEchoSize(((Double)echoSize[0]).doubleValue()) ; + } + + /** + * Sets the size of the 6DOF sensor echo in physical meters. This is used + * for the width of the gnomon and beam echoes. The default is 1 + * centimeter. + * + * @param echoSize the size in meters + */ + public void setEchoSize(double echoSize) { + this.echoSize = echoSize ; + } + + /** + * Gets the size of the 6DOF sensor echo in meters. + * + * @return the echo size + */ + public double getEchoSize() { + return echoSize ; + } + + /** + * Property which sets the color of the 6DOF sensor echo. The default is + * white. This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * EchoColor <red> <green> <blue>) + * + * @param color array of length 3 containing Doubles + * @see #setEchoColor + */ + public void EchoColor(Object[] color) { + if (! (color.length == 3 && color[0] instanceof Double && + color[1] instanceof Double && color[2] instanceof Double)) + throw new IllegalArgumentException + ("\nEchoColor must be 3 numbers for red, green, and blue") ; + + setEchoColor(new Color3f(((Double)color[0]).floatValue(), + ((Double)color[1]).floatValue(), + ((Double)color[2]).floatValue())) ; + } + + /** + * Sets the color of the 6DOF sensor echo. The default is white. This + * can be called to set the color before or after the echo geometry is + * created. + * + * @param color the echo color + */ + public void setEchoColor(Color3f color) { + if (echoColor == null) + echoColor = new Color3f(color) ; + else + echoColor.set(color) ; + + if (echoGeometry != null) { + Appearance a = echoGeometry.getAppearance() ; + Material m = a.getMaterial() ; + m.setDiffuseColor(echoColor) ; + } + } + + /** + * Gets the 6DOF sensor echo color. + * + * @param color the Color3f into which to copy the echo color + */ + public void getEchoColor(Color3f color) { + if (echoColor == null) + color.set(1.0f, 1.0f, 1.0f) ; + else + color.set(echoColor) ; + } + + /** + * Property which sets the 6DOF sensor echo transparency. The default is + * opaque. A value of 0.0 is fully opaque and 1.0 is fully transparent. + * This property is set in the configuration file read by + * ConfiguredUniverse. + *

+ * Syntax:
(ViewPlatformBehaviorProperty <name> + * EchoTransparency <transparency>) + * + * @param transparency array of length 1 containing a Double + * @see #setEchoTransparency + */ + public void EchoTransparency(Object[] transparency) { + if (! (transparency.length == 1 && transparency[0] instanceof Double)) + throw new IllegalArgumentException + ("\nEchoTransparency must be a number") ; + + setEchoTransparency(((Double)transparency[0]).floatValue()) ; + } + + /** + * Sets the 6DOF sensor echo transparency. The default is opaque. A + * value of 0.0 is fully opaque and 1.0 is fully transparent. This can be + * called to set the transparency before or after the echo geometry is + * created. + * + * @param transparency the transparency value + */ + public void setEchoTransparency(float transparency) { + echoTransparency = transparency ; + + if (echoGeometry != null) { + Appearance a = echoGeometry.getAppearance() ; + TransparencyAttributes ta = a.getTransparencyAttributes() ; + if (echoTransparency == 0.0f) { + ta.setTransparencyMode(TransparencyAttributes.NONE) ; + ta.setTransparency(0.0f) ; + } + else { + ta.setTransparencyMode(TransparencyAttributes.BLENDED) ; + ta.setTransparency(echoTransparency) ; + // Use order independent additive blend for gnomon. + if (echoGeometry instanceof SensorGnomonEcho) + ta.setDstBlendFunction(TransparencyAttributes.BLEND_ONE) ; + } + } + } + + /** + * Gets the 6DOF sensor echo transparency value. + * + * @return the transparency value + */ + public float getEchoTransparency() { + return echoTransparency ; + } + + /** + * Sets the transform group containing a 6DOF sensor's echo geometry. + * This is used to specify a custom echo. Its transform will be + * manipulated to represent the position and orientation of the 6DOF + * sensor. Capabilities to allow writing its transform and to read, + * write, and extend its children will be set. + *

+ * This method must be called before the behavior is made live in order to + * have an effect. + * + * @param echo the TransformGroup containing the + * echo geometry + */ + public void setEchoTransformGroup(TransformGroup echo) { + echo.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE) ; + echo.setCapability(Group.ALLOW_CHILDREN_READ) ; + echo.setCapability(Group.ALLOW_CHILDREN_WRITE) ; + echo.setCapability(Group.ALLOW_CHILDREN_EXTEND) ; + this.echoTransformGroup = echo ; + } + + /** + * Gets the transform group containing a 6DOF sensor's echo geometry. + * Capabilities to write its transform and read, write, and extend its + * children are granted. + * + * @return the echo's transform group + */ + public TransformGroup getEchoTransformGroup() { + return echoTransformGroup ; + } + + /** + * Gets the Shape3D defining the 6DOF sensor's echo geometry + * and appearance. The returned Shape3D allows appearance + * read and write. If a custom echo was supplied by providing the echo + * transform group directly then the return value will be + * null. + * + * @return the echo geometry, or null if a custom echo was + * supplied + */ + public Shape3D getEchoGeometry() { + return echoGeometry ; + } + + /** + * Gets the SensorEventAgent used by this behavior. Sensor + * event generation is delegated to this agent. This can be accessed to + * manipulate the sensor button and read action bindings directly. + * + * @return the sensor event agent + */ + public SensorEventAgent getSensorEventAgent() { + return eventAgent ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java b/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java new file mode 100644 index 0000000..b8aff95 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CommandStream.java @@ -0,0 +1,265 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; + +/** + * This class is used to build the bit-level compression command stream which + * is the final result of the compression process. It defines the bit + * representations of the compression commands and provides a mechanism for + * the interleaving and forwarding of command headers and bodies required by + * the geometry compression specification. + */ +class CommandStream { + // Geometry compression commands. + static final int SET_NORM = 0xC0 ; + static final int SET_COLOR = 0x80 ; + static final int VERTEX = 0x40 ; + static final int MESH_B_R = 0x20 ; + static final int SET_STATE = 0x18 ; + static final int SET_TABLE = 0x10 ; + static final int V_NO_OP = 0x01 ; + + // Huffman table indices. + static final int POSITION_TABLE = 0 ; + static final int COLOR_TABLE = 1 ; + static final int NORMAL_TABLE = 2 ; + + // The buffer of compressed data and the current offset. + private byte bytes[] ; + private int byteOffset ; + private int bitOffset ; + + // Last command body for header forwarding. + private long lastBody ; + private int lastBodyLength ; + + /** + * Create an empty CommandStream with a default initial size. + */ + CommandStream() { + this(65536) ; + } + + /** + * Create an empty CommandStream with the given initial size. + * + * @param initSize initial capacity of CommandStream in bytes + */ + CommandStream(int initSize) { + bytes = new byte[initSize] ; + clear() ; + } + + /** + * Mark the CommandStream as empty so that its storage will be reused. + */ + void clear() { + // Initialize the first byte to 0. + // Subsequent bytes are cleared as they are written. + bytes[0] = 0 ; + + // Reset the number of valid bits. + bitOffset = 0 ; + byteOffset = 0 ; + + // The first command header is always followed by the body of an + // implicit variable length no-op to start the header-forwarding + // interleave required by hardware decompressor implementations. The + // only necessary bits are 5 bits of length set to zeros to indicate a + // fill of zero length. + lastBody = 0 ; + lastBodyLength = 5 ; + } + + /** + * Add a compression command to this instance.

+ * + * A compression command includes an 8-bit header and can range up to 72 + * bits in length. The command with the maximum length is a 2-bit color + * command with a 6-bit tag in the header, followed by four 16-bit color + * components of data.

+ * + * A subcommand is either a position, normal, or color, though in practice + * a position subcommand can only be part of a vertex command. Normal and + * color subcommands can be parts of separate global normal and color + * commands as well as parts of a vertex command.

+ * + * A subcommand includes a 6-bit header. Its length is 2 bits less than + * the length of the corresponding command. + * + * @param header contains compression command header bits, right-justified + * within the bits of the int + * @param headerLength number of bits in header, either 8 for commands or + * 6 for subcommands + * @param body contains the body of the compression command, + * right-justified within the bits of the long + * @param bodyLength number of bits in the body + */ + void addCommand(int header, int headerLength, long body, int bodyLength) { + addByte(header, headerLength) ; + addLong(lastBody, lastBodyLength) ; + + lastBody = body ; + lastBodyLength = bodyLength ; + } + + // + // Add the rightmost bitCount bits of b to the end of the command stream. + // + private void addByte(int b, int bitCount) { + int bitsEmpty = 8 - bitOffset ; + b &= (int)CompressionStreamElement.lengthMask[bitCount] ; + + if (bitCount <= bitsEmpty) { + bytes[byteOffset] |= (b << (bitsEmpty - bitCount)) ; + bitOffset += bitCount ; + return ; + } + + if (bytes.length == byteOffset + 1) { + byte newBytes[] = new byte[bytes.length * 2] ; + System.arraycopy(bytes, 0, newBytes, 0, bytes.length) ; + bytes = newBytes ; + } + + bitOffset = bitCount - bitsEmpty ; + bytes[byteOffset] |= (b >>> bitOffset) ; + + byteOffset++ ; + bytes[byteOffset] = (byte)(b << (8 - bitOffset)) ; + } + + // + // Add the rightmost bitCount bits of l to the end of the command stream. + // + private void addLong(long l, int bitCount) { + int byteCount = bitCount / 8 ; + int excessBits = bitCount - byteCount * 8 ; + + if (excessBits > 0) + addByte((int)(l >>> (byteCount * 8)), excessBits) ; + + while (byteCount > 0) { + addByte((int)((l >>> ((byteCount - 1) * 8)) & 0xff), 8) ; + byteCount-- ; + } + } + + /** + * Add a no-op and the last command body. Pad out with additional no-ops + * to a 64-bit boundary if necessary. A call to this method is required + * in order to create a valid compression command stream. + */ + void end() { + int excessBytes, padBits ; + + // Add the 1st no-op and the last body. + addByte(V_NO_OP, 8) ; + addLong(lastBody, lastBodyLength) ; + + excessBytes = (byteOffset + 1) % 8 ; + if (excessBytes == 0 && bitOffset == 8) + // No padding necessary. + return ; + + // Need to add padding with a 2nd no-op. + addByte(V_NO_OP, 8) ; + excessBytes = (byteOffset + 1) % 8 ; + + if (excessBytes == 0) + padBits = 8 - bitOffset ; + else { + int fillBytes = 8 - excessBytes ; + padBits = (8 * fillBytes) + (8 - bitOffset) ; + } + + // The minimum length for a no-op command body is 5 bits. + if (padBits < 5) + // Have to cross the next 64-bit boundary. + padBits += 64 ; + + // The maximum length of a no-op body is a 5-bit length + 31 bits of + // fill for a total of 36. + if (padBits < 37) { + // Pad with the body of the 1st no-op. + addLong((padBits - 5) << (padBits - 5), padBits) ; + return ; + } + + // The number of bits to pad at this point is [37..68]. Knock off 24 + // bits with the body of the 1st no-op to reduce the number of pad + // bits to [13..44], which can be filled with 1 more no-op. + addLong(19 << 19, 24) ; + padBits -= 24 ; + + // Add a 3rd no-op. + addByte(V_NO_OP, 8) ; + padBits -= 8 ; + + // Complete padding with the body of the 2nd no-op. + addLong((padBits - 5) << (padBits - 5), padBits) ; + } + + /** + * Get the number of bytes in the compression command stream. + * + * @return size of compressed data in bytes + */ + int getByteCount() { + if (byteOffset + bitOffset == 0) + return 0 ; + else + return byteOffset + 1 ; + } + + /** + * Get the bytes composing the compression command stream. + * + * @return reference to array of bytes containing the compressed data + */ + byte[] getBytes() { + return bytes ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java b/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java new file mode 100644 index 0000000..9cb6048 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressedGeometryFile.java @@ -0,0 +1,1007 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; + +import java.io.* ; +import javax.media.j3d.* ; + +// +// The compressed geometry file format supported by this class has a 32 +// byte header followed by multiple compressed geometry objects. +// +// Each object consists of a block of compressed data and an 8-byte +// individual block header describing its contents. +// +// The file ends with a directory data structure used for random access, +// containing a 64-bit offset for each object in the order in which it +// appears in the file. This is also used to find the size of the largest +// object in the file and must be present. +// + +/** + * This class provides methods to read and write compressed geometry resource + * files. These files usually end with the .cg extension and support + * sequential as well as random access to multiple compressed geometry + * objects. + */ +public class CompressedGeometryFile { + private static final boolean print = false ; + private static final boolean benchmark = false ; + + /** + * The magic number which identifies the compressed geometry file type. + */ + static final int MAGIC_NUMBER = 0xbaddfab4 ; + + /** + * Byte offset of the magic number from start of file. + */ + static final int MAGIC_NUMBER_OFFSET = 0 ; + + /** + * Byte offset of the major version number from start of file. + */ + static final int MAJOR_VERSION_OFFSET = 4 ; + + /** + * Byte offset of the minor version number from start of file. + */ + static final int MINOR_VERSION_OFFSET = 8 ; + + /** + * Byte offset of the minor minor version number from start of file. + */ + static final int MINOR_MINOR_VERSION_OFFSET = 12 ; + + /** + * Byte offset of the number of objects from start of file. + */ + static final int OBJECT_COUNT_OFFSET = 16 ; + + /** + * Byte offset of the directory offset from start of file. + * This offset is long word aligned since the directory offset is a long. + */ + static final int DIRECTORY_OFFSET_OFFSET = 24 ; + + /** + * File header total size in bytes. + */ + static final int HEADER_SIZE = 32 ; + + /** + * Byte offset of the object size from start of individual compressed + * geometry block. + */ + static final int OBJECT_SIZE_OFFSET = 0 ; + + /** + * Byte offset of the compressed geometry data descriptor from start of + * individual compressed geometry block. + */ + static final int GEOM_DATA_OFFSET = 4 ; + + /** + * Bits in compressed geometry data descriptor which encode the buffer type. + */ + static final int TYPE_MASK = 0x03 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of normals. + */ + static final int NORMAL_PRESENT_MASK = 0x04 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of colors. + */ + static final int COLOR_PRESENT_MASK = 0x08 ; + + /** + * Bit in compressed geometry data descriptor encoding presence of alphas. + */ + static final int ALPHA_PRESENT_MASK = 0x10 ; + + /** + * Value in compressed geometry data descriptor for a point buffer type. + */ + static final int TYPE_POINT = 1 ; + + /** + * Value in compressed geometry data descriptor for a line buffer type. + */ + static final int TYPE_LINE = 2 ; + + /** + * Value in compressed geometry data descriptor for a triangle buffer type. + */ + static final int TYPE_TRIANGLE = 3 ; + + /** + * Block header total size in bytes. + */ + static final int BLOCK_HEADER_SIZE = 8 ; + + // The name of the compressed geometry resource file. + String fileName = null ; + + // The major, minor, and subminor version number of the most recent + // compressor used to compress any of the objects in the compressed + // geometry resource file. + int majorVersionNumber ; + int minorVersionNumber ; + int minorMinorVersionNumber ; + + // The number of objects in the compressed geometry resource file. + int objectCount ; + + // The index of the current object in the file. + int objectIndex = 0 ; + + // The random access file associated with this instance. + RandomAccessFile cgFile = null ; + + // The magic number identifying the file type. + int magicNumber ; + + // These fields are set from each individual block of compressed geometry. + byte cgBuffer[] ; + int geomSize ; + int geomStart ; + int geomDataType ; + + // The directory of object offsets is read from the end of the file. + long directory[] ; + long directoryOffset ; + + // The object sizes are computed from the directory offsets. These are + // used to allocate a buffer large enough to hold the largest object and + // to determine how many consecutive objects can be read into that buffer. + int objectSizes[] ; + int bufferObjectStart ; + int bufferObjectCount ; + int bufferNextObjectCount ; + int bufferNextObjectOffset ; + + // The shared compressed geometry header object. + CompressedGeometryHeader cgh ; + + // Flag indicating file update. + boolean fileUpdate = false ; + + /** + * Construct a new CompressedGeometryFile instance associated with the + * specified file. An attempt is made to open the file with read-only + * access; if this fails then a FileNotFoundException is thrown. + * + * @param file path to the compressed geometry resource file + * @exception FileNotFoundException if file doesn't exist or + * cannot be read + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(String file) throws IOException { + this(file, false) ; + } + + /** + * Construct a new CompressedGeometryFile instance associated with the + * specified file. + * + * @param file path to the compressed geometry resource file + * @param rw if true, opens the file for read and write access or attempts + * to create one if it doesn't exist; if false, opens the file with + * read-only access + * @exception FileNotFoundException if file doesn't exist or + * access permissions disallow access + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(String file, boolean rw) throws IOException { + // Open the file and read the file header. + open(file, rw) ; + + // Copy the file name. + fileName = new String(file) ; + + // Set up the file fields. + initialize() ; + } + + /** + * Construct a new CompressedGeometryFile instance associated with a + * currently open RandomAccessFile. + * + * @param file currently open RandomAccessFile + * @exception IllegalArgumentException if the file is not a compressed + * geometry resource file + * @exception IOException if there is a header or directory read error + */ + public CompressedGeometryFile(RandomAccessFile file) throws IOException { + // Copy the file reference. + cgFile = file ; + + // Set up the file fields. + initialize() ; + } + + /** + * Delete all compressed objects from this instance. This method may only + * be called after successfully creating a CompressedGeometryFile instance + * with read-write access, so a corrupted or otherwise invalid resource + * must be removed manually before it can be rewritten. The close() + * method must be called sometime after invoking clear() in order to write + * out the new directory structure. + * + * @exception IOException if clear fails + */ + public void clear() throws IOException { + // Truncate the file. + cgFile.setLength(0) ; + + // Set up the file fields. + initialize() ; + } + + /** + * Return a string containing the file name associated with this instance + * or null if there is none. + * + * @return file name associated with this instance or null if there is + * none + */ + public String getFileName() { + return fileName ; + } + + /** + * Return the major version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return major version number + */ + public int getMajorVersionNumber() { + return majorVersionNumber ; + } + + /** + * Return the minor version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return minor version number + */ + public int getMinorVersionNumber() { + return minorVersionNumber ; + } + + /** + * Return the subminor version number of the most recent compressor used to + * compress any of the objects in this instance. + * + * @return subminor version number + */ + public int getMinorMinorVersionNumber() { + return minorMinorVersionNumber ; + } + + /** + * Return the number of compressed objects in this instance. + * + * @return number of compressed objects + */ + public int getObjectCount() { + return objectCount ; + } + + /** + * Return the current object index associated with this instance. This is + * the index of the object that would be returned by an immediately + * following call to the readNext() method. Its initial value is 0; -1 + * is returned if the last object has been read. + * + * @return current object index, or -1 if at end + */ + public int getCurrentIndex() { + if (objectIndex == objectCount) + return -1 ; + else + return objectIndex ; + } + + /** + * Read the next compressed geometry object in the instance. This is + * initially the first object (index 0) in the instance; otherwise, it is + * whatever object is next after the last one read. The current object + * index is incremented by 1 after the read. When the last object is read + * the index becomes invalid and an immediately subsequent call to + * readNext() returns null. + * + * @return a CompressedGeometry node component, or null if the last object + * has been read + * @exception IOException if read fails + */ + public CompressedGeometry readNext() throws IOException { + return readNext(cgBuffer.length) ; + } + + /** + * Read all compressed geometry objects contained in the instance. The + * current object index becomes invalid; an immediately following call + * to readNext() will return null. + * + * @return an array of CompressedGeometry node components. + * @exception IOException if read fails + */ + public CompressedGeometry[] read() throws IOException { + long startTime = 0 ; + CompressedGeometry cg[] = new CompressedGeometry[objectCount] ; + + if (benchmark) + startTime = System.currentTimeMillis() ; + + objectIndex = 0 ; + setFilePointer(directory[0]) ; + bufferNextObjectCount = 0 ; + + for (int i = 0 ; i < objectCount ; i++) + cg[i] = readNext(cgBuffer.length) ; + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println("read " + objectCount + + " objects " + cgFile.length() + + " bytes in " + (t/1000f) + " sec.") ; + System.out.println((cgFile.length()/(float)t) + " Kbytes/sec.") ; + } + + return cg ; + } + + /** + * Read the compressed geometry object at the specified index. The + * current object index is set to the subsequent object unless the last + * object has been read, in which case the index becomes invalid and an + * immediately following call to readNext() will return null. + * + * @param index compressed geometry object to read + * @return a CompressedGeometry node component + * @exception IndexOutOfBoundsException if object index is + * out of range + * @exception IOException if read fails + */ + public CompressedGeometry read(int index) throws IOException { + objectIndex = index ; + + if (objectIndex < 0) { + throw new IndexOutOfBoundsException + ("\nobject index must be >= 0") ; + } + if (objectIndex >= objectCount) { + throw new IndexOutOfBoundsException + ("\nobject index must be < " + objectCount) ; + } + + // Check if object is in cache. + if ((objectIndex >= bufferObjectStart) && + (objectIndex < bufferObjectStart + bufferObjectCount)) { + if (print) System.out.println("\ngetting object from cache\n") ; + + bufferNextObjectOffset = (int) + (directory[objectIndex] - directory[bufferObjectStart]) ; + + bufferNextObjectCount = + bufferObjectCount - (objectIndex - bufferObjectStart) ; + + return readNext() ; + + } else { + // Move file pointer to correct offset. + setFilePointer(directory[objectIndex]) ; + + // Force a read from current offset. Disable cache read-ahead + // since cache hits are unlikely with random access. + bufferNextObjectCount = 0 ; + return readNext(objectSizes[objectIndex]) ; + } + } + + + /** + * Add a compressed geometry node component to the end of the instance. + * The current object index becomes invalid; an immediately following call + * to readNext() will return null. The close() method must be called at + * some later time in order to create a valid compressed geometry file. + * + * @param cg a compressed geometry node component + * @exception CapabilityNotSetException if unable to get compressed + * geometry data from the node component + * @exception IOException if write fails + */ + public void write(CompressedGeometry cg) throws IOException { + CompressedGeometryHeader cgh = new CompressedGeometryHeader() ; + cg.getCompressedGeometryHeader(cgh) ; + + // Update the read/write buffer size if necessary. + if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) { + cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE] ; + if (print) System.out.println("\ncgBuffer: reallocated " + + (cgh.size+BLOCK_HEADER_SIZE) + + " bytes") ; + } + + cg.getCompressedGeometry(cgBuffer) ; + write(cgh, cgBuffer) ; + } + + /** + * Add a buffer of compressed geometry data to the end of the + * resource. The current object index becomes invalid; an immediately + * following call to readNext() will return null. The close() method must + * be called at some later time in order to create a valid compressed + * geometry file. + * + * @param cgh a CompressedGeometryHeader object describing the data. + * @param geometry the compressed geometry data + * @exception IOException if write fails + */ + public void write(CompressedGeometryHeader cgh, byte geometry[]) + throws IOException { + + // Update the read/write buffer size if necessary. It won't be used + // in this method, but should be big enough to read any object in + // the file, including the one to be written. + if (cgh.size + BLOCK_HEADER_SIZE > cgBuffer.length) { + cgBuffer = new byte[cgh.size + BLOCK_HEADER_SIZE] ; + if (print) System.out.println("\ncgBuffer: reallocated " + + (cgh.size+BLOCK_HEADER_SIZE) + + " bytes") ; + } + + // Assuming backward compatibility, the version number of the file + // should be the maximum of all individual compressed object versions. + if ((cgh.majorVersionNumber > majorVersionNumber) + || + ((cgh.majorVersionNumber == majorVersionNumber) && + (cgh.minorVersionNumber > minorVersionNumber)) + || + ((cgh.majorVersionNumber == majorVersionNumber) && + (cgh.minorVersionNumber == minorVersionNumber) && + (cgh.minorMinorVersionNumber > minorMinorVersionNumber))) { + + majorVersionNumber = cgh.majorVersionNumber ; + minorVersionNumber = cgh.minorVersionNumber ; + minorMinorVersionNumber = cgh.minorMinorVersionNumber ; + + this.cgh.majorVersionNumber = cgh.majorVersionNumber ; + this.cgh.minorVersionNumber = cgh.minorVersionNumber ; + this.cgh.minorMinorVersionNumber = cgh.minorMinorVersionNumber ; + } + + // Get the buffer type and see what vertex components are present. + int geomDataType = 0 ; + + switch (cgh.bufferType) { + case CompressedGeometryHeader.POINT_BUFFER: + geomDataType = TYPE_POINT ; + break ; + case CompressedGeometryHeader.LINE_BUFFER: + geomDataType = TYPE_LINE ; + break ; + case CompressedGeometryHeader.TRIANGLE_BUFFER: + geomDataType = TYPE_TRIANGLE ; + break ; + } + + if ((cgh.bufferDataPresent & + CompressedGeometryHeader.NORMAL_IN_BUFFER) != 0) + geomDataType |= NORMAL_PRESENT_MASK ; + + if ((cgh.bufferDataPresent & + CompressedGeometryHeader.COLOR_IN_BUFFER) != 0) + geomDataType |= COLOR_PRESENT_MASK ; + + if ((cgh.bufferDataPresent & + CompressedGeometryHeader.ALPHA_IN_BUFFER) != 0) + geomDataType |= ALPHA_PRESENT_MASK ; + + // Allocate new directory and object size arrays if necessary. + if (objectCount == directory.length) { + long newDirectory[] = new long[2*objectCount] ; + int newObjectSizes[] = new int[2*objectCount] ; + + System.arraycopy(directory, 0, + newDirectory, 0, objectCount) ; + System.arraycopy(objectSizes, 0, + newObjectSizes, 0, objectCount) ; + + directory = newDirectory ; + objectSizes = newObjectSizes ; + + if (print) + System.out.println("\ndirectory and size arrays: reallocated " + + (2*objectCount) + " entries") ; + } + + // Update directory and object size array. + directory[objectCount] = directoryOffset ; + objectSizes[objectCount] = cgh.size + BLOCK_HEADER_SIZE ; + objectCount++ ; + + // Seek to the directory and overwrite from there. + setFilePointer(directoryOffset) ; + cgFile.writeInt(cgh.size) ; + cgFile.writeInt(geomDataType) ; + cgFile.write(geometry, 0, cgh.size) ; + if (print) + System.out.println("\nwrote " + cgh.size + + " byte compressed object to " + fileName + + "\nfile offset " + directoryOffset) ; + + // Update the directory offset. + directoryOffset += cgh.size + BLOCK_HEADER_SIZE ; + + // Return end-of-file on next read. + objectIndex = objectCount ; + + // Flag file update so close() will write out the directory. + fileUpdate = true ; + } + + /** + * Release the resources associated with this instance. + * Write out final header and directory if contents were updated. + * This method must be called in order to create a valid compressed + * geometry resource file if any updates were made. + */ + public void close() { + if (cgFile != null) { + try { + if (fileUpdate) { + writeFileDirectory() ; + writeFileHeader() ; + } + cgFile.close() ; + } + catch (IOException e) { + // Don't propagate this exception. + System.out.println("\nException: " + e.getMessage()) ; + System.out.println("failed to close " + fileName) ; + } + } + cgFile = null ; + cgBuffer = null ; + directory = null ; + objectSizes = null ; + } + + + // + // Open the file. Specifying a non-existent file creates a new one if + // access permissions allow. + // + void open(String fname, boolean rw) + throws FileNotFoundException, IOException { + + cgFile = null ; + String mode ; + + if (rw) + mode = "rw" ; + else + mode = "r" ; + + try { + cgFile = new RandomAccessFile(fname, mode) ; + if (print) System.out.println("\n" + fname + + ": opened mode " + mode) ; + } + catch (FileNotFoundException e) { + // N.B. this exception is also thrown on access permission errors + throw new FileNotFoundException(e.getMessage() + "\n" + fname + + ": open mode " + mode + " failed") ; + } + } + + // + // Seek to the specified offset in the file. + // + void setFilePointer(long offset) throws IOException { + cgFile.seek(offset) ; + + // Reset number of objects that can be read sequentially from cache. + bufferNextObjectCount = 0 ; + } + + // + // Initialize directory, object size array, read/write buffer, and the + // shared compressed geometry header. + // + void initialize() throws IOException { + int maxSize = 0 ; + + if (cgFile.length() == 0) { + // New file for writing: allocate nominal initial sizes for arrays. + objectCount = 0 ; + cgBuffer = new byte[32768] ; + directory = new long[16] ; + objectSizes = new int[directory.length] ; + + // Set fields as if they have been read. + magicNumber = MAGIC_NUMBER ; + majorVersionNumber = 1 ; + minorVersionNumber = 0 ; + minorMinorVersionNumber = 0 ; + directoryOffset = HEADER_SIZE ; + + // Write the file header. + writeFileHeader() ; + + } else { + // Read the file header. + readFileHeader() ; + + // Check file type. + if (magicNumber != MAGIC_NUMBER) { + close() ; + throw new IllegalArgumentException + ("\n" + fileName + " is not a compressed geometry file") ; + } + + // Read the directory and determine object sizes. + directory = new long[objectCount] ; + readDirectory(directoryOffset, directory) ; + + objectSizes = new int[objectCount] ; + for (int i = 0 ; i < objectCount-1 ; i++) { + objectSizes[i] = (int)(directory[i+1] - directory[i]) ; + if (objectSizes[i] > maxSize) maxSize = objectSizes[i] ; + } + + if (objectCount > 0) { + objectSizes[objectCount-1] = + (int)(directoryOffset - directory[objectCount-1]) ; + + if (objectSizes[objectCount-1] > maxSize) + maxSize = objectSizes[objectCount-1] ; + } + + // Allocate a buffer big enough to read the largest object. + cgBuffer = new byte[maxSize] ; + + // Move to the first object. + setFilePointer(HEADER_SIZE) ; + } + + // Set up common parts of the compressed geometry object header. + cgh = new CompressedGeometryHeader() ; + cgh.majorVersionNumber = this.majorVersionNumber ; + cgh.minorVersionNumber = this.minorVersionNumber ; + cgh.minorMinorVersionNumber = this.minorMinorVersionNumber ; + + if (print) { + System.out.println(fileName + ": " + objectCount + " objects") ; + System.out.println("magic number 0x" + + Integer.toHexString(magicNumber) + + ", version number " + majorVersionNumber + + "." + minorVersionNumber + + "." + minorMinorVersionNumber) ; + System.out.println("largest object is " + maxSize + " bytes") ; + } + } + + // + // Read the file header. + // + void readFileHeader() throws IOException { + byte header[] = new byte[HEADER_SIZE] ; + + try { + setFilePointer(0) ; + if (cgFile.read(header) != HEADER_SIZE) { + close() ; + throw new IOException("failed header read") ; + } + } + catch (IOException e) { + if (cgFile != null) { + close() ; + } + throw e ; + } + + magicNumber = + ((header[MAGIC_NUMBER_OFFSET+0] & 0xff) << 24) | + ((header[MAGIC_NUMBER_OFFSET+1] & 0xff) << 16) | + ((header[MAGIC_NUMBER_OFFSET+2] & 0xff) << 8) | + ((header[MAGIC_NUMBER_OFFSET+3] & 0xff)) ; + + majorVersionNumber = + ((header[MAJOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MAJOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MAJOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MAJOR_VERSION_OFFSET+3] & 0xff)) ; + + minorVersionNumber = + ((header[MINOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MINOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MINOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MINOR_VERSION_OFFSET+3] & 0xff)) ; + + minorMinorVersionNumber = + ((header[MINOR_MINOR_VERSION_OFFSET+0] & 0xff) << 24) | + ((header[MINOR_MINOR_VERSION_OFFSET+1] & 0xff) << 16) | + ((header[MINOR_MINOR_VERSION_OFFSET+2] & 0xff) << 8) | + ((header[MINOR_MINOR_VERSION_OFFSET+3] & 0xff)) ; + + objectCount = + ((header[OBJECT_COUNT_OFFSET+0] & 0xff) << 24) | + ((header[OBJECT_COUNT_OFFSET+1] & 0xff) << 16) | + ((header[OBJECT_COUNT_OFFSET+2] & 0xff) << 8) | + ((header[OBJECT_COUNT_OFFSET+3] & 0xff)) ; + + directoryOffset = + ((long)(header[DIRECTORY_OFFSET_OFFSET+0] & 0xff) << 56) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+1] & 0xff) << 48) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+2] & 0xff) << 40) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+3] & 0xff) << 32) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+4] & 0xff) << 24) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+5] & 0xff) << 16) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+6] & 0xff) << 8) | + ((long)(header[DIRECTORY_OFFSET_OFFSET+7] & 0xff)) ; + } + + // + // Write the file header based on current field values. + // + void writeFileHeader() throws IOException { + setFilePointer(0) ; + try { + cgFile.writeInt(MAGIC_NUMBER) ; + cgFile.writeInt(majorVersionNumber) ; + cgFile.writeInt(minorVersionNumber) ; + cgFile.writeInt(minorMinorVersionNumber) ; + cgFile.writeInt(objectCount) ; + cgFile.writeInt(0) ; // long word alignment + cgFile.writeLong(directoryOffset) ; + if (print) + System.out.println("wrote file header for " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write file header for " + fileName) ; + } + } + + // + // Read the directory of compressed geometry object offsets. + // + void readDirectory(long offset, long[] directory) + throws IOException { + + byte buff[] = new byte[directory.length * 8] ; + setFilePointer(offset) ; + + try { + cgFile.read(buff) ; + if (print) + System.out.println("read " + buff.length + " byte directory") ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\nfailed to read " + buff.length + + " byte directory, offset " + offset + " in file " + fileName) ; + } + + for (int i = 0 ; i < directory.length ; i++) { + directory[i] = + ((long)(buff[i*8+0] & 0xff) << 56) | + ((long)(buff[i*8+1] & 0xff) << 48) | + ((long)(buff[i*8+2] & 0xff) << 40) | + ((long)(buff[i*8+3] & 0xff) << 32) | + ((long)(buff[i*8+4] & 0xff) << 24) | + ((long)(buff[i*8+5] & 0xff) << 16) | + ((long)(buff[i*8+6] & 0xff) << 8) | + ((long)(buff[i*8+7] & 0xff)) ; + } + } + + // + // Write the file directory. + // + void writeFileDirectory() throws IOException { + setFilePointer(directoryOffset) ; + + int directoryAlign = (int)(directoryOffset % 8) ; + if (directoryAlign != 0) { + // Align to long word before writing directory of long offsets. + byte bytes[] = new byte[8-directoryAlign] ; + + try { + cgFile.write(bytes) ; + if (print) + System.out.println ("wrote " + (8-directoryAlign) + + " bytes long alignment") ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write " + directoryAlign + + " bytes to long word align directory for " + fileName) ; + } + directoryOffset += 8-directoryAlign ; + } + + try { + for (int i = 0 ; i < objectCount ; i++) + cgFile.writeLong(directory[i]) ; + + if (print) + System.out.println("wrote file directory for " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\ncould not write directory for " + fileName) ; + } + } + + // + // Get the next compressed object in the file, either from the read-ahead + // cache or from the file itself. + // + CompressedGeometry readNext(int bufferReadLimit) + throws IOException { + if (objectIndex == objectCount) + return null ; + + if (bufferNextObjectCount == 0) { + // No valid objects are in the cache. + int curSize = 0 ; + bufferObjectCount = 0 ; + + // See how much we have room to read. + for (int i = objectIndex ; i < objectCount ; i++) { + if (curSize + objectSizes[i] > bufferReadLimit) break ; + curSize += objectSizes[i] ; + bufferObjectCount++ ; + } + + // Try to read that amount. + try { + int n = cgFile.read(cgBuffer, 0, curSize) ; + if (print) + System.out.println("\nread " + n + + " bytes from " + fileName) ; + } + catch (IOException e) { + throw new IOException + (e.getMessage() + + "\nfailed to read " + curSize + + " bytes, object " + objectIndex + " in file " + fileName) ; + } + + // Point at the first object in the buffer. + bufferObjectStart = objectIndex ; + bufferNextObjectCount = bufferObjectCount ; + bufferNextObjectOffset = 0 ; + } + + // Get block header info. + geomSize = + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+0]&0xff)<<24) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+1]&0xff)<<16) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+2]&0xff)<< 8) | + ((cgBuffer[bufferNextObjectOffset+OBJECT_SIZE_OFFSET+3]&0xff)) ; + + geomDataType = + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+0]&0xff) << 24) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+1]&0xff) << 16) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+2]&0xff) << 8) | + ((cgBuffer[bufferNextObjectOffset+GEOM_DATA_OFFSET+3]&0xff)) ; + + // Get offset of compressed geometry data from start of buffer. + geomStart = bufferNextObjectOffset + BLOCK_HEADER_SIZE ; + + if (print) { + System.out.println("\nobject " + objectIndex + + "\nfile offset " + directory[objectIndex] + + ", buffer offset " + bufferNextObjectOffset) ; + System.out.println("size " + geomSize + " bytes, " + + "data descriptor 0x" + + Integer.toHexString(geomDataType)) ; + } + + // Update cache info. + bufferNextObjectOffset += objectSizes[objectIndex] ; + bufferNextObjectCount-- ; + objectIndex++ ; + + return newCG(geomSize, geomStart, geomDataType) ; + } + + + // + // Construct and return a compressed geometry node. + // + CompressedGeometry newCG(int geomSize, + int geomStart, + int geomDataType) { + cgh.size = geomSize ; + cgh.start = geomStart ; + + if ((geomDataType & TYPE_MASK) == TYPE_POINT) + cgh.bufferType = CompressedGeometryHeader.POINT_BUFFER ; + else if ((geomDataType & TYPE_MASK) == TYPE_LINE) + cgh.bufferType = CompressedGeometryHeader.LINE_BUFFER ; + else if ((geomDataType & TYPE_MASK) == TYPE_TRIANGLE) + cgh.bufferType = CompressedGeometryHeader.TRIANGLE_BUFFER ; + + cgh.bufferDataPresent = 0 ; + + if ((geomDataType & NORMAL_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryHeader.NORMAL_IN_BUFFER ; + + if ((geomDataType & COLOR_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryHeader.COLOR_IN_BUFFER ; + + if ((geomDataType & ALPHA_PRESENT_MASK) != 0) + cgh.bufferDataPresent |= + CompressedGeometryHeader.ALPHA_IN_BUFFER ; + + return new CompressedGeometry(cgh, cgBuffer) ; + } + + /** + * Release file resources when this object is garbage collected. + */ + protected void finalize() { + close() ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java new file mode 100644 index 0000000..add5799 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStream.java @@ -0,0 +1,2294 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; + +import java.util.* ; +import javax.vecmath.* ; +import javax.media.j3d.* ; +import com.sun.j3d.utils.geometry.* ; +import com.sun.j3d.internal.ByteBufferWrapper ; +import com.sun.j3d.internal.BufferWrapper ; +import com.sun.j3d.internal.FloatBufferWrapper ; +import com.sun.j3d.internal.DoubleBufferWrapper ; + +/** + * This class is used as input to a geometry compressor. It collects elements + * such as vertices, normals, colors, mesh references, and quantization + * parameters in an ordered stream. This stream is then traversed during + * the compression process and used to build the compressed output buffer. + * + * @see GeometryCompressor + */ +public class CompressionStream { + // + // NOTE: For now, copies are made of all GeometryArray vertex components + // even when by-reference access is available. + // + // TODO: Retrofit all CompressionStreamElements and MeshBuffer to handle + // offsets to vertex data array references so that vertex components don't + // have to be copied. New CompressionStreamElements could be defined to + // set the current array reference during the quantization pass, or the + // reference could be included in every CompressionStreamElement along + // with the data offsets. + // + // TODO: Quantize on-the-fly when adding GeometryArray vertex data so that + // CompressionStreamElements don't need references to the original float, + // double, or byte data. Quantization is currently a separate pass since + // the 1st pass adds vertex data and gets the total object bounds, but + // this can be computed by merging the bounds of each GeometryArray + // compressed into a single object. The 2nd pass quantization is still + // needed for vertex data which isn't retrieved from a GeometryArray; for + // example, apps that might use the addVertex() methods directly instead + // of addGeometryArray(). + // + // TODO: To further optimize memory, create new subclasses of + // CompressionStream{Color, Normal} for bundled attributes and add them as + // explicit stream elements. Then CompressionStreamVertex won't need to + // carry references to them. This memory savings might be negated by the + // extra overhead of adding more elements to the stream, however. + // + // TODO: Keep the absolute quantized values in the mesh buffer mirror so + // that unmeshed CompressionStreamElements don't need to carry them. + // + // TODO: Support texture coordinate compression even though Level II is + // not supported by any hardware decompressor on any graphics card. + // Software decompression is still useful for applications interested in + // minimizing file space, transmission time, and object loading time. + // + private static final boolean debug = false ; + private static final boolean benchmark = false ; + + // Mesh buffer normal substitution is unavailable in Level I. + private static final boolean noMeshNormalSubstitution = true ; + + /** + * This flag indicates that a vertex starts a new triangle or line strip. + */ + static final int RESTART = 1 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the middle vertex of the previous triangle in the strip. + * Equivalent to REPLACE_OLDEST for line strips. + */ + static final int REPLACE_MIDDLE = 2 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the oldest vertex of the previous triangle in the strip. + * Equivalent to REPLACE_MIDDLE for line strips. + */ + static final int REPLACE_OLDEST = 3 ; + + /** + * This flag indicates that a vertex is to be pushed into the mesh buffer. + */ + static final int MESH_PUSH = 1 ; + + /** + * This flag indicates that a vertex does not use the mesh buffer. + */ + static final int NO_MESH_PUSH = 0 ; + + /** + * Byte to float scale factor for scaling byte color components. + */ + static final float ByteToFloatScale = 1.0f/255.0f; + + /** + * Type of this stream, either CompressedGeometryHeader.POINT_BUFFER, + * CompressedGeometryHeader.LINE_BUFFER, or + * CompressedGeometryHeader.TRIANGLE_BUFFER + */ + int streamType ; + + /** + * A mask indicating which components are present in each vertex, as + * defined by GeometryArray. + */ + int vertexComponents ; + + /** + * Boolean indicating colors are bundled with the vertices. + */ + boolean vertexColors ; + + /** + * Boolean indicating RGB colors are bundled with the vertices. + */ + boolean vertexColor3 ; + + /** + * Boolean indicating RGBA colors are bundled with the vertices. + */ + boolean vertexColor4 ; + + /** + * Boolean indicating normals are bundled with the vertices. + */ + boolean vertexNormals ; + + /** + * Boolean indicating texture coordinates are present. + */ + boolean vertexTextures ; + + /** + * Boolean indicating that 2D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture2 ; + + /** + * Boolean indicating that 3D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture3 ; + + /** + * Boolean indicating that 4D texture coordinates are used. + * Currently only used to skip over textures in interleaved data. + */ + boolean vertexTexture4 ; + + /** + * Axes-aligned box enclosing all vertices in model coordinates. + */ + Point3d mcBounds[] = new Point3d[2] ; + + /** + * Axes-aligned box enclosing all vertices in normalized coordinates. + */ + Point3d ncBounds[] = new Point3d[2] ; + + /** + * Axes-aligned box enclosing all vertices in quantized coordinates. + */ + Point3i qcBounds[] = new Point3i[2] ; + + /** + * Center for normalizing positions to the unit cube. + */ + double center[] = new double[3] ; + + /** + * Maximum position range along the 3 axes. + */ + double positionRangeMaximum ; + + /** + * Scale for normalizing positions to the unit cube. + */ + double scale ; + + /** + * Current position component (X, Y, and Z) quantization value. This can + * range from 1 to 16 bits and has a default of 16.

+ * + * At 1 bit of quantization it is not possible to express positive + * absolute or delta positions. + */ + int positionQuant ; + + /** + * Current color component (R, G, B, A) quantization value. This can + * range from 2 to 16 bits and has a default of 9.

+ * + * A color component is represented with a signed fixed-point value in + * order to be able express negative deltas; the default of 9 bits + * corresponds to the 8-bit color component range of the graphics hardware + * commonly available. Colors must be non-negative, so the lower limit of + * quantization is 2 bits. + */ + int colorQuant ; + + /** + * Current normal component (U and V) quantization value. This can range + * from 0 to 6 bits and has a default of 6.

+ * + * At 0 bits of quantization normals are represented only as 6 bit + * sextant/octant pairs and 14 specially encoded normals (the 6 axis + * normals and the 8 octant midpoint normals); since U and V can only be 0 + * at the minimum quantization, the totally number of unique normals is + * 12 + 14 = 26. + */ + int normalQuant ; + + /** + * Flag indicating position quantization change. + */ + boolean positionQuantChanged ; + + /** + * Flag indicating color quantization change. + */ + boolean colorQuantChanged ; + + /** + * Flag indicating normal quantization change. + */ + boolean normalQuantChanged ; + + /** + * Last quantized position. + */ + int lastPosition[] = new int[3] ; + + /** + * Last quantized color. + */ + int lastColor[] = new int[4] ; + + /** + * Last quantized normal's sextant. + */ + int lastSextant ; + + /** + * Last quantized normal's octant. + */ + int lastOctant ; + + /** + * Last quantized normal's U encoding parameter. + */ + int lastU ; + + /** + * Last quantized normal's V encoding parameter. + */ + int lastV ; + + /** + * Flag indicating last normal used a special encoding. + */ + boolean lastSpecialNormal ; + + /** + * Flag indicating the first position in this stream. + */ + boolean firstPosition ; + + /** + * Flag indicating the first color in this stream. + */ + boolean firstColor ; + + /** + * Flag indicating the first normal in this stream. + */ + boolean firstNormal ; + + /** + * The total number of bytes used to create the uncompressed geometric + * elements in this stream, useful for performance analysis. This + * excludes mesh buffer references. + */ + int byteCount ; + + /** + * The number of vertices created for this stream, excluding mesh buffer + * references. + */ + int vertexCount ; + + /** + * The number of mesh buffer references created for this stream. + */ + int meshReferenceCount ; + + /** + * Mesh buffer mirror used for computing deltas during quantization pass + * and a limited meshing algorithm for unstripped data. + */ + MeshBuffer meshBuffer = new MeshBuffer() ; + + + // Collection which holds the elements of this stream. + private Collection stream ; + + // True if preceding stream elements were colors or normals. Used to flag + // color and normal mesh buffer substitution when computing deltas during + // quantization pass. + private boolean lastElementColor = false ; + private boolean lastLastElementColor = false ; + private boolean lastElementNormal = false ; + private boolean lastLastElementNormal = false ; + + // Some convenient temporary holding variables. + private Point3f p3f = new Point3f() ; + private Color3f c3f = new Color3f() ; + private Color4f c4f = new Color4f() ; + private Vector3f n3f = new Vector3f() ; + + + // Private constructor for common initializations. + private CompressionStream() { + this.stream = new LinkedList() ; + + byteCount = 0 ; + vertexCount = 0 ; + meshReferenceCount = 0 ; + + mcBounds[0] = new Point3d(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY) ; + mcBounds[1] = new Point3d(Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY) ; + + qcBounds[0] = new Point3i(Integer.MAX_VALUE, + Integer.MAX_VALUE, + Integer.MAX_VALUE) ; + qcBounds[1] = new Point3i(Integer.MIN_VALUE, + Integer.MIN_VALUE, + Integer.MIN_VALUE) ; + + /* normalized bounds computed from quantized bounds */ + ncBounds[0] = new Point3d() ; + ncBounds[1] = new Point3d() ; + } + + /** + * Creates a new CompressionStream for the specified geometry type and + * vertex format.

+ * + * @param streamType type of data in this stream, either + * CompressedGeometryHeader.POINT_BUFFER, + * CompressedGeometryHeader.LINE_BUFFER, or + * CompressedGeometryHeader.TRIANGLE_BUFFER + * + * @param vertexComponents a mask indicating which components are present + * in each vertex, as defined by GeometryArray: COORDINATES, NORMALS, and + * COLOR_3 or COLOR_4. + * + * @see GeometryCompressor + * @see GeometryArray + */ + CompressionStream(int streamType, int vertexComponents) { + this() ; + this.streamType = streamType ; + this.vertexComponents = getVertexComponents(vertexComponents) ; + } + + // See what vertex geometry components are present. The byReference, + // interleaved, useNIOBuffer, and useCoordIndexOnly flags are not + // examined. + private int getVertexComponents(int vertexFormat) { + int components = 0 ; + + vertexColors = vertexColor3 = vertexColor4 = vertexNormals = + vertexTextures = vertexTexture2 = vertexTexture3 = vertexTexture4 = + false ; + + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + vertexNormals = true ; + components &= GeometryArray.NORMALS ; + if (debug) System.out.println("vertexNormals") ; + } + + if ((vertexFormat & GeometryArray.COLOR_3) != 0) { + vertexColors = true ; + + if ((vertexFormat & GeometryArray.COLOR_4) != 0) { + vertexColor4 = true ; + components &= GeometryArray.COLOR_4 ; + if (debug) System.out.println("vertexColor4") ; + } + else { + vertexColor3 = true ; + components &= GeometryArray.COLOR_3 ; + if (debug) System.out.println("vertexColor3") ; + } + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + vertexTextures = true ; + vertexTexture2 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_2 ; + if (debug) System.out.println("vertexTexture2") ; + } + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + vertexTextures = true ; + vertexTexture3 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_3 ; + if (debug) System.out.println("vertexTexture3") ; + } + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + vertexTextures = true ; + vertexTexture4 = true ; + components &= GeometryArray.TEXTURE_COORDINATE_4 ; + if (debug) System.out.println("vertexTexture4") ; + } + + if (vertexTextures) + // Throw exception for now until texture is supported. + throw new UnsupportedOperationException + ("\ncompression of texture coordinates is not supported") ; + + return components ; + } + + // Get the streamType associated with a GeometryArray instance. + private int getStreamType(GeometryArray ga) { + if (ga instanceof TriangleStripArray || + ga instanceof IndexedTriangleStripArray || + ga instanceof TriangleFanArray || + ga instanceof IndexedTriangleFanArray || + ga instanceof TriangleArray || + ga instanceof IndexedTriangleArray || + ga instanceof QuadArray || + ga instanceof IndexedQuadArray) + + return CompressedGeometryHeader.TRIANGLE_BUFFER ; + + else if (ga instanceof LineArray || + ga instanceof IndexedLineArray || + ga instanceof LineStripArray || + ga instanceof IndexedLineStripArray) + + return CompressedGeometryHeader.LINE_BUFFER ; + + else + return CompressedGeometryHeader.POINT_BUFFER ; + } + + /** + * Iterates across all compression stream elements and applies + * quantization parameters, encoding consecutive vertices as delta values + * whenever possible. Each geometric element is mapped to a HuffmanNode + * object containing its resulting bit length, right shift (trailing 0 + * count), and absolute or relative status.

+ * + * Positions are normalized to span a unit cube via an offset and a + * uniform scale factor that maps the midpoint of the object extents along + * each dimension to the origin, and the longest dimension of the object to + * the open interval (-1.0 .. +1.0). The geometric endpoints along that + * dimension are both one quantum away from unity; for example, at a + * position quantization of 6 bits, an object would be normalized so that + * its most negative dimension is at (-1 + 1/64) and the most positive is + * at (1 - 1/64).

+ * + * Normals are assumed to be of unit length. Color components are clamped + * to the [0..1) range, where the right endpoint is one quantum less + * than 1.0.

+ * + * @param huffmanTable Table which will map geometric compression stream + * elements to HuffmanNode objects describing each element's data + * representation. This table can then be processed with Huffman's + * algorithm to optimize the bit length of descriptor tags according to + * the number of geometric elements mapped to each tag. + */ + void quantize(HuffmanTable huffmanTable) { + // Set up default initial quantization parameters. The position and + // color parameters specify the number of bits for each X, Y, Z, R, G, + // B, or A component. The normal quantization parameter specifies the + // number of bits for each U and V component. + positionQuant = 16 ; + colorQuant = 9 ; + normalQuant = 6 ; + + // Compute position center and scaling for normalization to the unit + // cube. This is a volume bounded by the open intervals (-1..1) on + // each axis. + center[0] = (mcBounds[1].x + mcBounds[0].x) / 2.0 ; + center[1] = (mcBounds[1].y + mcBounds[0].y) / 2.0 ; + center[2] = (mcBounds[1].z + mcBounds[0].z) / 2.0 ; + + double xRange = mcBounds[1].x - mcBounds[0].x ; + double yRange = mcBounds[1].y - mcBounds[0].y ; + double zRange = mcBounds[1].z - mcBounds[0].z ; + + if (xRange > yRange) + positionRangeMaximum = xRange ; + else + positionRangeMaximum = yRange ; + + if (zRange > positionRangeMaximum) + positionRangeMaximum = zRange ; + + // Adjust the range of the unit cube to match the default + // quantization. + // + // This scale factor along with the center values computed above will + // produce 16-bit integer representations of the floating point + // position coordinates ranging symmetrically about 0 from -32767 to + // +32767. -32768 is not used and the normalized floating point + // position coordinates of -1.0 as well as +1.0 will not be + // represented. + // + // Applications which wish to seamlessly stitch together compressed + // objects will need to be aware that the range of normalized + // positions will be one quantum away from the [-1..1] endpoints of + // the unit cube and should adjust scale factors accordingly. + scale = (2.0 / positionRangeMaximum) * (32767.0 / 32768.0) ; + + // Flag quantization change. + positionQuantChanged = colorQuantChanged = normalQuantChanged = true ; + + // Flag first position, color, and normal. + firstPosition = firstColor = firstNormal = true ; + + // Apply quantization. + Iterator i = stream.iterator() ; + while (i.hasNext()) { + Object o = i.next() ; + + if (o instanceof CompressionStreamElement) { + ((CompressionStreamElement)o).quantize(this, huffmanTable) ; + + // Keep track of whether last two elements were colors or + // normals for mesh buffer component substitution semantics. + lastLastElementColor = lastElementColor ; + lastLastElementNormal = lastElementNormal ; + lastElementColor = lastElementNormal = false ; + + if (o instanceof CompressionStreamColor) + lastElementColor = true ; + else if (o instanceof CompressionStreamNormal) + lastElementNormal = true ; + } + } + + // Compute the bounds in normalized coordinates. + ncBounds[0].x = (double)qcBounds[0].x / 32768.0 ; + ncBounds[0].y = (double)qcBounds[0].y / 32768.0 ; + ncBounds[0].z = (double)qcBounds[0].z / 32768.0 ; + + ncBounds[1].x = (double)qcBounds[1].x / 32768.0 ; + ncBounds[1].y = (double)qcBounds[1].y / 32768.0 ; + ncBounds[1].z = (double)qcBounds[1].z / 32768.0 ; + } + + /** + * Iterates across all compression stream elements and builds the + * compressed geometry command stream output.

+ * + * @param huffmanTable Table which maps geometric elements in this stream + * to tags describing the encoding parameters (length, shift, and + * absolute/relative status) to be used for their representations in the + * compressed output. All tags must be 6 bits or less in length, and the + * sum of the number of bits in the tag plus the number of bits in the + * data it describes must be at least 6 bits in length. + * + * @param outputBuffer CommandStream to use for collecting the compressed + * bits. + */ + void outputCommands(HuffmanTable huffmanTable, CommandStream outputBuffer) { + // + // The first command output is setState to indicate what data is + // bundled with each vertex. Although the semantics of geometry + // decompression allow setState to appear anywhere in the stream, this + // cannot be handled by the current Java 3D software decompressor, + // which internally decompresses an entire compressed buffer into a + // single retained object sharing a single consistent vertex format. + // This limitation may be removed in subsequent releases of Java 3D. + // + int bnv = (vertexNormals? 1 : 0) ; + int bcv = ((vertexColor3 || vertexColor4)? 1 : 0) ; + int cap = (vertexColor4? 1 : 0) ; + + int command = CommandStream.SET_STATE | bnv ; + long data = (bcv << 2) | (cap << 1) ; + + // Output the setState command. + outputBuffer.addCommand(command, 8, data, 3) ; + + // Output the Huffman table commands. + huffmanTable.outputCommands(outputBuffer) ; + + // Output each compression stream element's data. + Iterator i = stream.iterator() ; + while (i.hasNext()) { + Object o = i.next() ; + if (o instanceof CompressionStreamElement) + ((CompressionStreamElement)o).outputCommand(huffmanTable, + outputBuffer) ; + } + + // Finish the header-forwarding interleave and long-word align. + outputBuffer.end() ; + } + + /** + * Retrieve the total size of the uncompressed geometric data in bytes, + * excluding mesh buffer references. + * @return uncompressed byte count + */ + int getByteCount() { + return byteCount ; + } + + /** + * Retrieve the the number of vertices created for this stream, excluding + * mesh buffer references. + * @return vertex count + */ + int getVertexCount() { + return vertexCount ; + } + + /** + * Retrieve the number of mesh buffer references created for this stream. + * @return mesh buffer reference count + */ + int getMeshReferenceCount() { + return meshReferenceCount ; + } + + /** + * Stream element that sets position quantization during quantize pass. + */ + private class PositionQuant extends CompressionStreamElement { + int value ; + + PositionQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + positionQuant = value ; + positionQuantChanged = true ; + + // Adjust range of unit cube scaling to match quantization. + scale = (2.0 / positionRangeMaximum) * + (((double)((1 << (value-1)) - 1))/((double)(1 << (value-1)))) ; + } + + public String toString() { + return "positionQuant: " + value ; + } + } + + /** + * Stream element that sets normal quantization during quantize pass. + */ + private class NormalQuant extends CompressionStreamElement { + int value ; + + NormalQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + normalQuant = value ; + normalQuantChanged = true ; + } + + public String toString() { + return "normalQuant: " + value ; + } + } + + /** + * Stream element that sets color quantization during quantize pass. + */ + private class ColorQuant extends CompressionStreamElement { + int value ; + + ColorQuant(int value) { + this.value = value ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + colorQuant = value ; + colorQuantChanged = true ; + } + + public String toString() { + return "colorQuant: " + value ; + } + } + + /** + * Stream element that references the mesh buffer. + */ + private class MeshReference extends CompressionStreamElement { + int stripFlag, meshIndex ; + + MeshReference(int stripFlag, int meshIndex) { + this.stripFlag = stripFlag ; + this.meshIndex = meshIndex ; + meshReferenceCount++ ; + } + + void quantize(CompressionStream s, HuffmanTable t) { + // Retrieve the vertex from the mesh buffer mirror and set up the + // data needed for the next stream element to compute its deltas. + CompressionStreamVertex v = meshBuffer.getVertex(meshIndex) ; + lastPosition[0] = v.xAbsolute ; + lastPosition[1] = v.yAbsolute ; + lastPosition[2] = v.zAbsolute ; + + // Set up last color data if it exists and previous elements + // don't override it. + if (v.color != null && !lastElementColor && + !(lastElementNormal && lastLastElementColor)) { + lastColor[0] = v.color.rAbsolute ; + lastColor[1] = v.color.gAbsolute ; + lastColor[2] = v.color.bAbsolute ; + lastColor[3] = v.color.aAbsolute ; + } + + // Set up last normal data if it exists and previous element + // doesn't override it. + if (v.normal != null && !lastElementNormal && + !(lastElementColor && lastLastElementNormal)) { + lastSextant = v.normal.sextant ; + lastOctant = v.normal.octant ; + lastU = v.normal.uAbsolute ; + lastV = v.normal.vAbsolute ; + lastSpecialNormal = v.normal.specialNormal ; + } + } + + void outputCommand(HuffmanTable t, CommandStream outputBuffer) { + int command = CommandStream.MESH_B_R ; + long data = stripFlag & 0x1 ; + + command |= (((meshIndex & 0xf) << 1) | (stripFlag >> 1)) ; + outputBuffer.addCommand(command, 8, data, 1) ; + } + + public String toString() { + return + "meshReference: stripFlag " + stripFlag + + " meshIndex " + meshIndex ; + } + } + + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, int stripFlag) { + stream.add(new CompressionStreamVertex(this, pos, + (Vector3f)null, (Color3f)null, + stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)null, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Color3f color, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Color4f color, int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, Color3f color, + int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, + * REPLACE_OLDEST, or REPLACE_MIDDLE + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, + int stripFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, NO_MESH_PUSH)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, (Color3f)null, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)null, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Color3f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Color4f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, (Vector3f)null, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, Color3f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, + int stripFlag, int meshFlag) { + stream.add(new CompressionStreamVertex + (this, pos, norm, color, stripFlag, meshFlag)) ; + } + + /** + * Copy vertex data and add it to the end of this stream. + * @param pos position data + * @param norm normal data + * @param color color data, either Color3f or Color4f, determined by + * current vertex format + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshFlag if MESH_PUSH the vertex is pushed into the mesh buffer + */ + void addVertex(Point3f pos, Vector3f norm, + Object color, int stripFlag, int meshFlag) { + + if (vertexColor3) + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color3f)color, stripFlag, meshFlag)) ; + else + stream.add(new CompressionStreamVertex + (this, pos, norm, (Color4f)color, stripFlag, meshFlag)) ; + } + + /** + * Add a mesh buffer reference to this stream. + * @param stripFlag vertex replacement flag, either RESTART, REPLACE_OLDEST, + * or REPLACE_MIDDLE + * @param meshIndex index of vertex to retrieve from the mesh buffer + */ + void addMeshReference(int stripFlag, int meshIndex) { + stream.add(new MeshReference(stripFlag, meshIndex)) ; + } + + /** + * Copy the given color to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addColor(Color3f c3f) { + stream.add(new CompressionStreamColor(this, c3f)) ; + } + + /** + * Copy the given color to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addColor(Color4f c4f) { + stream.add(new CompressionStreamColor(this, c4f)) ; + } + + /** + * Copy the given normal to the end of this stream and use it as a global + * state change that applies to all subsequent vertices. + */ + void addNormal(Vector3f n) { + stream.add(new CompressionStreamNormal(this, n)) ; + } + + /** + * Add a new position quantization value to the end of this stream that + * will apply to all subsequent vertex positions. + * + * @param value number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 with a default of 16 + */ + void addPositionQuantization(int value) { + stream.add(new PositionQuant(value)) ; + } + + /** + * Add a new color quantization value to the end of this stream that will + * apply to all subsequent colors. + * + * @param value number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 with a default of 9 + */ + void addColorQuantization(int value) { + stream.add(new ColorQuant(value)) ; + } + + /** + * Add a new normal quantization value to the end of this stream that will + * apply to all subsequent normals. This value specifies the number of + * bits for each normal's U and V components. + * + * @param value number of bits for quantizing U and V, ranging from 0 to + * 6 with a default of 6 + */ + void addNormalQuantization(int value) { + stream.add(new NormalQuant(value)) ; + } + + /** + * Interface to access GeometryArray vertex components and add them to the + * compression stream. + * + * A processVertex() implementation retrieves vertex components using the + * appropriate access semantics of a particular GeometryArray, and adds + * them to the compression stream. + * + * The implementation always pushes vertices into the mesh buffer unless + * they match ones already there; if they do, it generates mesh buffer + * references instead. This reduces the number of vertices when + * non-stripped abutting facets are added to the stream. + * + * Note: Level II geometry compression semantics allow the mesh buffer + * normals to be substituted with the value of an immediately + * preceding SetNormal command, but this is unavailable in Level I. + * + * @param index vertex offset from the beginning of its data array + * @param stripFlag RESTART, REPLACE_MIDDLE, or REPLACE_OLDEST + */ + private interface GeometryAccessor { + void processVertex(int index, int stripFlag) ; + } + + /** + * This class implements the GeometryAccessor interface for geometry + * arrays accessed with by-copy semantics. + */ + private class ByCopyGeometry implements GeometryAccessor { + Point3f[] positions = null ; + Vector3f[] normals = null ; + Color3f[] colors3 = null ; + Color4f[] colors4 = null ; + + ByCopyGeometry(GeometryArray ga) { + this(ga, ga.getInitialVertexIndex(), ga.getValidVertexCount()) ; + } + + ByCopyGeometry(GeometryArray ga, + int firstVertex, int validVertexCount) { + int i ; + positions = new Point3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + positions[i] = new Point3f() ; + + ga.getCoordinates(firstVertex, positions) ; + + if (vertexNormals) { + normals = new Vector3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + normals[i] = new Vector3f() ; + + ga.getNormals(firstVertex, normals) ; + } + + if (vertexColor3) { + colors3 = new Color3f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + colors3[i] = new Color3f() ; + + ga.getColors(firstVertex, colors3) ; + } + else if (vertexColor4) { + colors4 = new Color4f[validVertexCount] ; + for (i = 0 ; i < validVertexCount ; i++) + colors4[i] = new Color4f() ; + + ga.getColors(firstVertex, colors4) ; + } + } + + public void processVertex(int v, int stripFlag) { + Point3f p = positions[v] ; + int r = meshBuffer.getMeshReference(p) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (! normals[v].equals(meshBuffer.getNormal(r))))) { + + Vector3f n = vertexNormals? normals[v] : null ; + Object c = vertexColor3? (Object)colors3[v] : + vertexColor4? (Object)colors4[v] : null ; + + addVertex(p, n, c, stripFlag, MESH_PUSH) ; + meshBuffer.push(p, c, n) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + (! normals[v].equals(meshBuffer.getNormal(r)))) + addNormal(normals[v]) ; + + if (vertexColor3 && + (! colors3[v].equals(meshBuffer.getColor3(r)))) + addColor(colors3[v]) ; + + else if (vertexColor4 && + (! colors4[v].equals(meshBuffer.getColor4(r)))) + addColor(colors4[v]) ; + + addMeshReference(stripFlag, r) ; + } + } + } + + /** + * Class which holds index array references for a geometry array. + */ + private static class IndexArrays { + int colorIndices[] = null ; + int normalIndices[] = null ; + int positionIndices[] = null ; + } + + /** + * Retrieves index array references for the specified IndexedGeometryArray. + * Index arrays are copied starting from initialIndexIndex. + */ + private void getIndexArrays(GeometryArray ga, IndexArrays ia) { + IndexedGeometryArray iga = (IndexedGeometryArray)ga ; + + int initialIndexIndex = iga.getInitialIndexIndex() ; + int indexCount = iga.getValidIndexCount() ; + int vertexFormat = iga.getVertexFormat() ; + + boolean useCoordIndexOnly = false ; + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if (debug) System.out.println("useCoordIndexOnly") ; + useCoordIndexOnly = true ; + } + + ia.positionIndices = new int[indexCount] ; + iga.getCoordinateIndices(initialIndexIndex, ia.positionIndices) ; + + if (vertexNormals) { + if (useCoordIndexOnly) { + ia.normalIndices = ia.positionIndices ; + } + else { + ia.normalIndices = new int[indexCount] ; + iga.getNormalIndices(initialIndexIndex, ia.normalIndices) ; + } + } + if (vertexColor3 || vertexColor4) { + if (useCoordIndexOnly) { + ia.colorIndices = ia.positionIndices ; + } + else { + ia.colorIndices = new int[indexCount] ; + iga.getColorIndices(initialIndexIndex, ia.colorIndices) ; + } + } + } + + /** + * Class which holds indices for a specific vertex of an + * IndexedGeometryArray. + */ + private static class VertexIndices { + int pi, ni, ci ; + } + + /** + * Retrieves vertex indices for a specific vertex in an + * IndexedGeometryArray. + */ + private void getVertexIndices(int v, IndexArrays ia, VertexIndices vi) { + vi.pi = ia.positionIndices[v] ; + if (vertexNormals) + vi.ni = ia.normalIndices[v] ; + if (vertexColors) + vi.ci = ia.colorIndices[v] ; + } + + /** + * This class implements the GeometryAccessor interface for indexed + * geometry arrays accessed with by-copy semantics. + */ + private class IndexedByCopyGeometry extends ByCopyGeometry { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByCopyGeometry(GeometryArray ga) { + super(ga, 0, ga.getVertexCount()) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + int r = meshBuffer.getMeshReference(vi.pi) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (vi.ni != meshBuffer.getNormalIndex(r)))) { + + Point3f p = positions[vi.pi] ; + Vector3f n = vertexNormals? normals[vi.ni] : null ; + Object c = vertexColor3? (Object)colors3[vi.ci] : + vertexColor4? (Object)colors4[vi.ci] : null ; + + addVertex(p, n, c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vi.pi, vi.ci, vi.ni) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + vi.ni != meshBuffer.getNormalIndex(r)) + addNormal(normals[vi.ni]) ; + + if (vertexColor3 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(colors3[vi.ci]) ; + + else if (vertexColor4 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(colors4[vi.ci]) ; + + addMeshReference(stripFlag, r) ; + } + } + } + + // + // NOTE: For now, copies are made of all GeometryArray vertex components + // even when by-reference access is available. + // + private static class VertexCopy { + Object c = null ; + Point3f p = null ; + Vector3f n = null ; + Color3f c3 = null ; + Color4f c4 = null ; + } + + private void processVertexCopy(VertexCopy vc, int stripFlag) { + int r = meshBuffer.getMeshReference(vc.p) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (! vc.n.equals(meshBuffer.getNormal(r))))) { + + addVertex(vc.p, vc.n, vc.c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vc.p, vc.c, vc.n) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + (! vc.n.equals(meshBuffer.getNormal(r)))) + addNormal(vc.n) ; + + if (vertexColor3 && (! vc.c3.equals(meshBuffer.getColor3(r)))) + addColor(vc.c3) ; + + else if (vertexColor4 && (! vc.c4.equals(meshBuffer.getColor4(r)))) + addColor(vc.c4) ; + + addMeshReference(stripFlag, r) ; + } + } + + private void processIndexedVertexCopy(VertexCopy vc, + VertexIndices vi, + int stripFlag) { + + int r = meshBuffer.getMeshReference(vi.pi) ; + + if ((r == meshBuffer.NOT_FOUND) || + (vertexNormals && noMeshNormalSubstitution && + (vi.ni != meshBuffer.getNormalIndex(r)))) { + + addVertex(vc.p, vc.n, vc.c, stripFlag, MESH_PUSH) ; + meshBuffer.push(vi.pi, vi.ci, vi.ni) ; + } + else { + if (vertexNormals && !noMeshNormalSubstitution && + vi.ni != meshBuffer.getNormalIndex(r)) + addNormal(vc.n) ; + + if (vertexColor3 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(vc.c3) ; + + else if (vertexColor4 && vi.ci != meshBuffer.getColorIndex(r)) + addColor(vc.c4) ; + + addMeshReference(stripFlag, r) ; + } + } + + /** + * This abstract class implements the GeometryAccessor interface for + * concrete subclasses which handle float and NIO interleaved geometry + * arrays. + */ + private abstract class InterleavedGeometry implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + int vstride = 0 ; + int coffset = 0 ; + int noffset = 0 ; + int poffset = 0 ; + int tstride = 0 ; + int tcount = 0 ; + + InterleavedGeometry(GeometryArray ga) { + if (vertexTextures) { + if (vertexTexture2) tstride = 2 ; + else if (vertexTexture3) tstride = 3 ; + else if (vertexTexture4) tstride = 4 ; + + tcount = ga.getTexCoordSetCount() ; + vstride += tcount * tstride ; + } + + if (vertexColors) { + coffset = vstride ; + if (vertexColor3) vstride += 3 ; + else vstride += 4 ; + } + + if (vertexNormals) { + noffset = vstride ; + vstride += 3 ; + } + + poffset = vstride ; + vstride += 3 ; + } + + abstract void copyVertex(int pi, int ni, int ci, VertexCopy vc) ; + + public void processVertex(int v, int stripFlag) { + copyVertex(v, v, v, vc) ; + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for float + * interleaved geometry arrays. + */ + private class InterleavedGeometryFloat extends InterleavedGeometry { + float[] vdata = null ; + + InterleavedGeometryFloat(GeometryArray ga) { + super(ga) ; + vdata = ga.getInterleavedVertices() ; + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + int voffset ; + voffset = pi * vstride ; + vc.p = new Point3f(vdata[voffset + poffset + 0], + vdata[voffset + poffset + 1], + vdata[voffset + poffset + 2]) ; + + if (vertexNormals) { + voffset = ni * vstride ; + vc.n = new Vector3f(vdata[voffset + noffset + 0], + vdata[voffset + noffset + 1], + vdata[voffset + noffset + 2]) ; + } + if (vertexColor3) { + voffset = ci * vstride ; + vc.c3 = new Color3f(vdata[voffset + coffset + 0], + vdata[voffset + coffset + 1], + vdata[voffset + coffset + 2]) ; + vc.c = vc.c3 ; + } + else if (vertexColor4) { + voffset = ci * vstride ; + vc.c4 = new Color4f(vdata[voffset + coffset + 0], + vdata[voffset + coffset + 1], + vdata[voffset + coffset + 2], + vdata[voffset + coffset + 3]) ; + vc.c = vc.c4 ; + } + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * interleaved geometry arrays. + */ + private class IndexedInterleavedGeometryFloat + extends InterleavedGeometryFloat { + + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedInterleavedGeometryFloat(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * interleaved NIO geometry arrays. + */ + private class InterleavedGeometryNIO extends InterleavedGeometry { + FloatBufferWrapper fbw = null ; + + InterleavedGeometryNIO(GeometryArray ga) { + super(ga) ; + J3DBuffer buffer = ga.getInterleavedVertexBuffer() ; + if (BufferWrapper.getBufferType(buffer) == + BufferWrapper.TYPE_FLOAT) { + fbw = new FloatBufferWrapper(buffer) ; + } + else { + throw new IllegalArgumentException + ("\ninterleaved vertex buffer must be FloatBuffer") ; + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + int voffset ; + voffset = pi * vstride ; + vc.p = new Point3f(fbw.get(voffset + poffset + 0), + fbw.get(voffset + poffset + 1), + fbw.get(voffset + poffset + 2)) ; + + if (vertexNormals) { + voffset = ni * vstride ; + vc.n = new Vector3f(fbw.get(voffset + noffset + 0), + fbw.get(voffset + noffset + 1), + fbw.get(voffset + noffset + 2)) ; + } + if (vertexColor3) { + voffset = ci * vstride ; + vc.c3 = new Color3f(fbw.get(voffset + coffset + 0), + fbw.get(voffset + coffset + 1), + fbw.get(voffset + coffset + 2)) ; + vc.c = vc.c3 ; + } + else if (vertexColor4) { + voffset = ci * vstride ; + vc.c4 = new Color4f(fbw.get(voffset + coffset + 0), + fbw.get(voffset + coffset + 1), + fbw.get(voffset + coffset + 2), + fbw.get(voffset + coffset + 3)) ; + vc.c = vc.c4 ; + } + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * interleaved NIO geometry arrays. + */ + private class IndexedInterleavedGeometryNIO extends InterleavedGeometryNIO { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedInterleavedGeometryNIO(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved geometry arrays accessed with by-reference semantics. + */ + private class ByRefGeometry implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + byte[] colorsB = null ; + float[] colorsF = null ; + float[] normals = null ; + float[] positionsF = null ; + double[] positionsD = null ; + + int initialPositionIndex = 0 ; + int initialNormalIndex = 0 ; + int initialColorIndex = 0 ; + + ByRefGeometry(GeometryArray ga) { + positionsF = ga.getCoordRefFloat() ; + if (debug && positionsF != null) + System.out.println("float positions") ; + + positionsD = ga.getCoordRefDouble() ; + if (debug && positionsD != null) + System.out.println("double positions") ; + + if (positionsF == null && positionsD == null) + throw new UnsupportedOperationException + ("\nby-reference access to Point3{d,f} arrays") ; + + initialPositionIndex = ga.getInitialCoordIndex() ; + + if (vertexColors) { + colorsB = ga.getColorRefByte() ; + if (debug && colorsB != null) + System.out.println("byte colors") ; + + colorsF = ga.getColorRefFloat() ; + if (debug && colorsF != null) + System.out.println("float colors") ; + + if (colorsB == null && colorsF == null) + throw new UnsupportedOperationException + ("\nby-reference access to Color{3b,3f,4b,4f} arrays") ; + + initialColorIndex = ga.getInitialColorIndex() ; + } + + if (vertexNormals) { + normals = ga.getNormalRefFloat() ; + if (debug && normals != null) + System.out.println("float normals") ; + + if (normals == null) + throw new UnsupportedOperationException + ("\nby-reference access to Normal3f array") ; + + initialNormalIndex = ga.getInitialNormalIndex() ; + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + pi *= 3 ; + if (positionsF != null) { + vc.p = new Point3f(positionsF[pi + 0], + positionsF[pi + 1], + positionsF[pi + 2]) ; + } + else { + vc.p = new Point3f((float)positionsD[pi + 0], + (float)positionsD[pi + 1], + (float)positionsD[pi + 2]) ; + } + + ni *= 3 ; + if (vertexNormals) { + vc.n = new Vector3f(normals[ni + 0], + normals[ni + 1], + normals[ni + 2]) ; + } + + if (vertexColor3) { + ci *= 3 ; + if (colorsB != null) { + vc.c3 = new Color3f + ((colorsB[ci + 0] & 0xff) * ByteToFloatScale, + (colorsB[ci + 1] & 0xff) * ByteToFloatScale, + (colorsB[ci + 2] & 0xff) * ByteToFloatScale) ; + } + else { + vc.c3 = new Color3f(colorsF[ci + 0], + colorsF[ci + 1], + colorsF[ci + 2]) ; + } + vc.c = vc.c3 ; + } + else if (vertexColor4) { + ci *= 4 ; + if (colorsB != null) { + vc.c4 = new Color4f + ((colorsB[ci + 0] & 0xff) * ByteToFloatScale, + (colorsB[ci + 1] & 0xff) * ByteToFloatScale, + (colorsB[ci + 2] & 0xff) * ByteToFloatScale, + (colorsB[ci + 3] & 0xff) * ByteToFloatScale) ; + } + else { + vc.c4 = new Color4f(colorsF[ci + 0], + colorsF[ci + 1], + colorsF[ci + 2], + colorsF[ci + 3]) ; + } + vc.c = vc.c4 ; + } + } + + public void processVertex(int v, int stripFlag) { + copyVertex(v + initialPositionIndex, + v + initialNormalIndex, + v + initialColorIndex, vc) ; + + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for indexed + * non-interleaved geometry arrays accessed with by-reference semantics. + */ + private class IndexedByRefGeometry extends ByRefGeometry { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByRefGeometry(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved geometry arrays accessed with NIO. + */ + private class ByRefGeometryNIO implements GeometryAccessor { + VertexCopy vc = new VertexCopy() ; + + ByteBufferWrapper colorsB = null ; + FloatBufferWrapper colorsF = null ; + FloatBufferWrapper normals = null ; + FloatBufferWrapper positionsF = null ; + DoubleBufferWrapper positionsD = null ; + + int initialPositionIndex = 0 ; + int initialNormalIndex = 0 ; + int initialColorIndex = 0 ; + + ByRefGeometryNIO(GeometryArray ga) { + J3DBuffer buffer ; + buffer = ga.getCoordRefBuffer() ; + initialPositionIndex = ga.getInitialCoordIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_FLOAT: + positionsF = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float positions buffer") ; + break ; + case BufferWrapper.TYPE_DOUBLE: + positionsD = new DoubleBufferWrapper(buffer) ; + if (debug) System.out.println("double positions buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\nposition buffer must be FloatBuffer or DoubleBuffer") ; + } + + if (vertexColors) { + buffer = ga.getColorRefBuffer() ; + initialColorIndex = ga.getInitialColorIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_BYTE: + colorsB = new ByteBufferWrapper(buffer) ; + if (debug) System.out.println("byte colors buffer") ; + break ; + case BufferWrapper.TYPE_FLOAT: + colorsF = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float colors buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\ncolor buffer must be ByteBuffer or FloatBuffer") ; + } + } + + if (vertexNormals) { + buffer = ga.getNormalRefBuffer() ; + initialNormalIndex = ga.getInitialNormalIndex() ; + + switch (BufferWrapper.getBufferType(buffer)) { + case BufferWrapper.TYPE_FLOAT: + normals = new FloatBufferWrapper(buffer) ; + if (debug) System.out.println("float normals buffer") ; + break ; + default: + throw new IllegalArgumentException + ("\nnormal buffer must be FloatBuffer") ; + } + } + } + + void copyVertex(int pi, int ni, int ci, VertexCopy vc) { + pi *= 3 ; + if (positionsF != null) { + vc.p = new Point3f(positionsF.get(pi + 0), + positionsF.get(pi + 1), + positionsF.get(pi + 2)) ; + } + else { + vc.p = new Point3f((float)positionsD.get(pi + 0), + (float)positionsD.get(pi + 1), + (float)positionsD.get(pi + 2)) ; + } + + ni *= 3 ; + if (vertexNormals) { + vc.n = new Vector3f(normals.get(ni + 0), + normals.get(ni + 1), + normals.get(ni + 2)) ; + } + + if (vertexColor3) { + ci *= 3 ; + if (colorsB != null) { + vc.c3 = new Color3f + ((colorsB.get(ci + 0) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 1) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 2) & 0xff) * ByteToFloatScale) ; + } + else { + vc.c3 = new Color3f(colorsF.get(ci + 0), + colorsF.get(ci + 1), + colorsF.get(ci + 2)) ; + } + vc.c = vc.c3 ; + } + else if (vertexColor4) { + ci *= 4 ; + if (colorsB != null) { + vc.c4 = new Color4f + ((colorsB.get(ci + 0) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 1) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 2) & 0xff) * ByteToFloatScale, + (colorsB.get(ci + 3) & 0xff) * ByteToFloatScale) ; + } + else { + vc.c4 = new Color4f(colorsF.get(ci + 0), + colorsF.get(ci + 1), + colorsF.get(ci + 2), + colorsF.get(ci + 3)) ; + } + vc.c = vc.c4 ; + } + } + + public void processVertex(int v, int stripFlag) { + copyVertex(v + initialPositionIndex, + v + initialNormalIndex, + v + initialColorIndex, vc) ; + + processVertexCopy(vc, stripFlag) ; + } + } + + /** + * This class implements the GeometryAccessor interface for + * non-interleaved indexed geometry arrays accessed with NIO. + */ + private class IndexedByRefGeometryNIO extends ByRefGeometryNIO { + IndexArrays ia = new IndexArrays() ; + VertexIndices vi = new VertexIndices() ; + + IndexedByRefGeometryNIO(GeometryArray ga) { + super(ga) ; + getIndexArrays(ga, ia) ; + } + + public void processVertex(int v, int stripFlag) { + getVertexIndices(v, ia, vi) ; + copyVertex(vi.pi, vi.ni, vi.ci, vc) ; + processIndexedVertexCopy(vc, vi, stripFlag) ; + } + } + + /** + * Convert a GeometryArray to compression stream elements and add them to + * this stream. + * + * @param ga GeometryArray to convert + * @exception IllegalArgumentException if GeometryArray has a + * dimensionality or vertex format inconsistent with the CompressionStream + */ + void addGeometryArray(GeometryArray ga) { + int firstVertex = 0 ; + int validVertexCount = 0 ; + int vertexFormat = ga.getVertexFormat() ; + GeometryAccessor geometryAccessor = null ; + + if (streamType != getStreamType(ga)) + throw new IllegalArgumentException + ("GeometryArray has inconsistent dimensionality") ; + + if (vertexComponents != getVertexComponents(vertexFormat)) + throw new IllegalArgumentException + ("GeometryArray has inconsistent vertex components") ; + + // Set up for vertex data access semantics. + boolean NIO = (vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 ; + boolean byRef = (vertexFormat & GeometryArray.BY_REFERENCE) != 0 ; + boolean interleaved = (vertexFormat & GeometryArray.INTERLEAVED) != 0 ; + boolean indexedGeometry = ga instanceof IndexedGeometryArray ; + + if (indexedGeometry) { + if (debug) System.out.println("indexed") ; + // Index arrays will be copied such that valid indices start at + // offset 0 in the copied arrays. + firstVertex = 0 ; + validVertexCount = ((IndexedGeometryArray)ga).getValidIndexCount() ; + } + + if (!byRef) { + if (debug) System.out.println("by-copy") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByCopyGeometry(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByCopyGeometry(ga) ; + } + } + else if (interleaved && NIO) { + if (debug) System.out.println("interleaved NIO") ; + if (indexedGeometry) { + geometryAccessor = new IndexedInterleavedGeometryNIO(ga) ; + } + else { + firstVertex = ga.getInitialVertexIndex() ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new InterleavedGeometryNIO(ga) ; + } + } + else if (interleaved && !NIO) { + if (debug) System.out.println("interleaved") ; + if (indexedGeometry) { + geometryAccessor = new IndexedInterleavedGeometryFloat(ga) ; + } + else { + firstVertex = ga.getInitialVertexIndex() ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new InterleavedGeometryFloat(ga) ; + } + } + else if (!interleaved && NIO) { + if (debug) System.out.println("non-interleaved NIO") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByRefGeometryNIO(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByRefGeometryNIO(ga) ; + } + } + else if (!interleaved && !NIO) { + if (debug) System.out.println("non-interleaved by-ref") ; + if (indexedGeometry) { + geometryAccessor = new IndexedByRefGeometry(ga) ; + } + else { + firstVertex = 0 ; + validVertexCount = ga.getValidVertexCount() ; + geometryAccessor = new ByRefGeometry(ga) ; + } + } + + // Set up for topology. + int stripCount = 0 ; + int stripCounts[] = null ; + int constantStripLength = 0 ; + int replaceCode = RESTART ; + boolean strips = false ; + boolean implicitStrips = false ; + + if (ga instanceof TriangleStripArray || + ga instanceof IndexedTriangleStripArray || + ga instanceof LineStripArray || + ga instanceof IndexedLineStripArray) { + + strips = true ; + replaceCode = REPLACE_OLDEST ; + if (debug) System.out.println("strips") ; + } + else if (ga instanceof TriangleFanArray || + ga instanceof IndexedTriangleFanArray) { + + strips = true ; + replaceCode = REPLACE_MIDDLE ; + if (debug) System.out.println("fans") ; + } + else if (ga instanceof QuadArray || + ga instanceof IndexedQuadArray) { + + // Handled as fan arrays with 4 vertices per fan. + implicitStrips = true ; + constantStripLength = 4 ; + replaceCode = REPLACE_MIDDLE ; + if (debug) System.out.println("quads") ; + } + + // Get strip counts. + if (strips) { + if (indexedGeometry) { + IndexedGeometryStripArray igsa ; + igsa = (IndexedGeometryStripArray)ga ; + + stripCount = igsa.getNumStrips() ; + stripCounts = new int[stripCount] ; + igsa.getStripIndexCounts(stripCounts) ; + + } else { + GeometryStripArray gsa ; + gsa = (GeometryStripArray)ga ; + + stripCount = gsa.getNumStrips() ; + stripCounts = new int[stripCount] ; + gsa.getStripVertexCounts(stripCounts) ; + } + } + + // Build the compression stream for this shape's geometry. + int v = firstVertex ; + if (strips) { + for (int i = 0 ; i < stripCount ; i++) { + geometryAccessor.processVertex(v++, RESTART) ; + for (int j = 1 ; j < stripCounts[i] ; j++) { + geometryAccessor.processVertex(v++, replaceCode) ; + } + } + } + else if (implicitStrips) { + while (v < firstVertex + validVertexCount) { + geometryAccessor.processVertex(v++, RESTART) ; + for (int j = 1 ; j < constantStripLength ; j++) { + geometryAccessor.processVertex(v++, replaceCode) ; + } + } + } + else { + while (v < firstVertex + validVertexCount) { + geometryAccessor.processVertex(v++, RESTART) ; + } + } + } + + /** + * Print the stream to standard output. + */ + void print() { + System.out.println("\nstream has " + stream.size() + " entries") ; + System.out.println("uncompressed size " + byteCount + " bytes") ; + System.out.println("upper position bound: " + mcBounds[1].toString()) ; + System.out.println("lower position bound: " + mcBounds[0].toString()) ; + System.out.println("X, Y, Z centers (" + + ((float)center[0]) + " " + + ((float)center[1]) + " " + + ((float)center[2]) + ")\n" + + "scale " + ((float)scale) + "\n") ; + + Iterator i = stream.iterator() ; + while (i.hasNext()) { + System.out.println(i.next().toString() + "\n") ; + } + } + + + //////////////////////////////////////////////////////////////////////////// + // // + // The following constructors and methods are currently the only public // + // members of this class. All other members are subject to revision. // + // // + //////////////////////////////////////////////////////////////////////////// + + /** + * Creates a CompressionStream from an array of Shape3D scene graph + * objects. These Shape3D objects may only consist of a GeometryArray + * component and an optional Appearance component. The resulting stream + * may be used as input to the GeometryCompressor methods.

+ * + * Each Shape3D in the array must be of the same dimensionality (point, + * line, or surface) and have the same vertex format as the others. + * Texture coordinates are ignored.

+ * + * If a color is specified in the material attributes for a Shape3D then + * that color is added to the CompressionStream as the current global + * color. Subsequent colors as well as any colors bundled with vertices + * will override it. Only the material diffuse colors are used; all other + * appearance attributes are ignored.

+ * + * @param positionQuant + * number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 + * + * @param colorQuant + * number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 + * + * @param normalQuant + * number of bits for quantizing each normal's U and V components, ranging + * from 0 to 6 + * + * @param shapes + * an array of Shape3D scene graph objects containing + * GeometryArray objects, all with the same vertex format and + * dimensionality + * + * @exception IllegalArgumentException if any Shape3D has an inconsistent + * dimensionality or vertex format, or if any Shape3D contains a geometry + * component that is not a GeometryArray + * + * @see Shape3D + * @see GeometryArray + * @see GeometryCompressor + */ + public CompressionStream(int positionQuant, int colorQuant, + int normalQuant, Shape3D shapes[]) { + this() ; + if (debug) System.out.println("CompressionStream(Shape3D[]):") ; + + if (shapes == null) + throw new IllegalArgumentException("null Shape3D array") ; + + if (shapes.length == 0) + throw new IllegalArgumentException("zero-length Shape3D array") ; + + if (shapes[0] == null) + throw new IllegalArgumentException("Shape3D at index 0 is null") ; + + long startTime = 0 ; + if (benchmark) startTime = System.currentTimeMillis() ; + + Geometry g = shapes[0].getGeometry() ; + if (! (g instanceof GeometryArray)) + throw new IllegalArgumentException + ("Shape3D at index 0 is not a GeometryArray") ; + + GeometryArray ga = (GeometryArray)g ; + this.streamType = getStreamType(ga) ; + this.vertexComponents = getVertexComponents(ga.getVertexFormat()) ; + + // Add global quantization parameters to the start of the stream. + addPositionQuantization(positionQuant) ; + addColorQuantization(colorQuant) ; + addNormalQuantization(normalQuant) ; + + // Loop through all shapes. + for (int s = 0 ; s < shapes.length ; s++) { + if (debug) System.out.println("\nShape3D " + s + ":") ; + + g = shapes[s].getGeometry() ; + if (! (g instanceof GeometryArray)) + throw new IllegalArgumentException + ("Shape3D at index " + s + " is not a GeometryArray") ; + + // Check for material color and add it to the stream if it exists. + Appearance a = shapes[s].getAppearance() ; + if (a != null) { + Material m = a.getMaterial() ; + if (m != null) { + m.getDiffuseColor(c3f) ; + if (vertexColor4) { + c4f.set(c3f.x, c3f.y, c3f.z, 1.0f) ; + addColor(c4f) ; + } else + addColor(c3f) ; + } + } + + // Add the geometry array to the stream. + addGeometryArray((GeometryArray)g) ; + } + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println + ("\nCompressionStream:\n" + shapes.length + " shapes in " + + (t / 1000f) + " sec") ; + } + } + + /** + * Creates a CompressionStream from an array of Shape3D scene graph + * objects. These Shape3D objects may only consist of a GeometryArray + * component and an optional Appearance component. The resulting stream + * may be used as input to the GeometryCompressor methods.

+ * + * Each Shape3D in the array must be of the same dimensionality (point, + * line, or surface) and have the same vertex format as the others. + * Texture coordinates are ignored.

+ * + * If a color is specified in the material attributes for a Shape3D then + * that color is added to the CompressionStream as the current global + * color. Subsequent colors as well as any colors bundled with vertices + * will override it. Only the material diffuse colors are used; all other + * appearance attributes are ignored.

+ * + * Defaults of 16, 9, and 6 bits are used as the quantization values for + * positions, colors, and normals respectively. These are the maximum + * resolution values defined for positions and normals; the default of 9 + * for color is the equivalent of the 8 bits of RGBA component resolution + * commonly available in graphics frame buffers.

+ * + * @param shapes + * an array of Shape3D scene graph objects containing + * GeometryArray objects, all with the same vertex format and + * dimensionality. + * + * @exception IllegalArgumentException if any Shape3D has an inconsistent + * dimensionality or vertex format, or if any Shape3D contains a geometry + * component that is not a GeometryArray + * + * @see Shape3D + * @see GeometryArray + * @see GeometryCompressor + */ + public CompressionStream(Shape3D shapes[]) { + this(16, 9, 6, shapes) ; + } + + /** + * Creates a CompressionStream from an array of GeometryInfo objects. The + * resulting stream may be used as input to the GeometryCompressor + * methods.

+ * + * Each GeometryInfo in the array must be of the same dimensionality + * (point, line, or surface) and have the same vertex format as the + * others. Texture coordinates are ignored.

+ * + * @param positionQuant + * number of bits to quantize each position's X, Y, + * and Z components, ranging from 1 to 16 + * + * @param colorQuant + * number of bits to quantize each color's R, G, B, and + * alpha components, ranging from 2 to 16 + * + * @param normalQuant + * number of bits for quantizing each normal's U and V components, ranging + * from 0 to 6 + * + * @param geometry + * an array of GeometryInfo objects, all with the same + * vertex format and dimensionality + * + * @exception IllegalArgumentException if any GeometryInfo object has an + * inconsistent dimensionality or vertex format + * + * @see GeometryInfo + * @see GeometryCompressor + */ + public CompressionStream(int positionQuant, int colorQuant, + int normalQuant, GeometryInfo geometry[]) { + this() ; + if (debug) System.out.println("CompressionStream(GeometryInfo[])") ; + + if (geometry == null) + throw new IllegalArgumentException("null GeometryInfo array") ; + + if (geometry.length == 0) + throw new IllegalArgumentException + ("zero-length GeometryInfo array") ; + + if (geometry[0] == null) + throw new IllegalArgumentException + ("GeometryInfo at index 0 is null") ; + + long startTime = 0 ; + if (benchmark) startTime = System.currentTimeMillis() ; + + GeometryArray ga = geometry[0].getGeometryArray() ; + this.streamType = getStreamType(ga) ; + this.vertexComponents = getVertexComponents(ga.getVertexFormat()) ; + + // Add global quantization parameters to the start of the stream. + addPositionQuantization(positionQuant) ; + addColorQuantization(colorQuant) ; + addNormalQuantization(normalQuant) ; + + // Loop through all GeometryInfo objects and add them to the stream. + for (int i = 0 ; i < geometry.length ; i++) { + if (debug) System.out.println("\nGeometryInfo " + i + ":") ; + addGeometryArray(geometry[i].getGeometryArray()) ; + } + + if (benchmark) { + long t = System.currentTimeMillis() - startTime ; + System.out.println + ("\nCompressionStream:\n" + geometry.length + + " GeometryInfo objects in " + (t / 1000f) + " sec") ; + } + } + + /** + * Creates a CompressionStream from an array of GeometryInfo objects. The + * resulting stream may be used as input to the GeometryCompressor + * methods.

+ * + * Each GeometryInfo in the array must be of the same dimensionality + * (point, line, or surface) and have the same vertex format as the + * others. Texture coordinates are ignored.

+ * + * Defaults of 16, 9, and 6 bits are used as the quantization values for + * positions, colors, and normals respectively. These are the maximum + * resolution values defined for positions and normals; the default of 9 + * for color is the equivalent of the 8 bits of RGBA component resolution + * commonly available in graphics frame buffers.

+ * + * @param geometry + * an array of GeometryInfo objects, all with the same + * vertex format and dimensionality + * + * @exception IllegalArgumentException if any GeometryInfo object has an + * inconsistent dimensionality or vertex format + * + * @see GeometryInfo + * @see GeometryCompressor + */ + public CompressionStream(GeometryInfo geometry[]) { + this(16, 9, 6, geometry) ; + } + + /** + * Get the original bounds of the coordinate data, in modeling coordinates. + * Coordinate data is positioned and scaled to a normalized cube after + * compression. + * + * @return Point3d array of length 2, where the 1st Point3d is the lower + * bounds and the 2nd Point3d is the upper bounds. + * @since Java 3D 1.3 + */ + public Point3d[] getModelBounds() { + Point3d[] bounds = new Point3d[2] ; + bounds[0] = new Point3d(mcBounds[0]) ; + bounds[1] = new Point3d(mcBounds[1]) ; + return bounds ; + } + + /** + * Get the bounds of the compressed object in normalized coordinates. + * These have an maximum bounds by [-1.0 .. +1.0] across each axis. + * + * @return Point3d array of length 2, where the 1st Point3d is the lower + * bounds and the 2nd Point3d is the upper bounds. + * @since Java 3D 1.3 + */ + public Point3d[] getNormalizedBounds() { + Point3d[] bounds = new Point3d[2] ; + bounds[0] = new Point3d(ncBounds[0]) ; + bounds[1] = new Point3d(ncBounds[1]) ; + return bounds ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java new file mode 100644 index 0000000..88fd202 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamColor.java @@ -0,0 +1,270 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import javax.vecmath.* ; + +/** + * This class represents a color in a compression stream. It maintains both + * floating-point and quantized representations. This color may be bundled + * with a vertex or exist separately as a global color. + */ +class CompressionStreamColor extends CompressionStreamElement { + private int R, G, B, A ; + private boolean color3 ; + private boolean color4 ; + private float colorR, colorG, colorB, colorA ; + + int rAbsolute, gAbsolute, bAbsolute, aAbsolute ; + + /** + * Create a CompressionStreamColor. + * + * @param stream CompressionStream associated with this element + * @param color3 floating-point representation to be encoded + */ + CompressionStreamColor(CompressionStream stream, Color3f c3) { + this.color4 = false ; + this.color3 = true ; + colorR = c3.x ; + colorG = c3.y ; + colorB = c3.z ; + colorA = 0.0f ; + stream.byteCount += 12 ; + } + + /** + * Create a CompressionStreamColor. + * + * @param stream CompressionStream associated with this element + * @param color4 floating-point representation to be encoded + */ + CompressionStreamColor(CompressionStream stream, Color4f c4) { + this.color3 = false ; + this.color4 = true ; + colorR = c4.x ; + colorG = c4.y ; + colorB = c4.z ; + colorA = c4.w ; + stream.byteCount += 16 ; + } + + /** + * Quantize a floating point color to fixed point integer components of + * the specified number of bits. The bit length can range from a maximum + * of 16 to a minimum of 2 bits since negative colors are not defined.

+ * + * The bit length is the total number of bits in the signed version of the + * fixed point representation of the input color, which is assumed to + * be normalized into the [0..1) range. With the maximum bit length of + * 16, 15 bits of positive colors can be represented; a bit length of 9 is + * needed to get the 8 bit positive color size in common use.

+ * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + // Clamp quantization. + int quant = + (stream.colorQuant < 2? 2 : + (stream.colorQuant > 16? 16 : stream.colorQuant)) ; + + absolute = false ; + if (stream.firstColor || stream.colorQuantChanged) { + absolute = true ; + stream.lastColor[0] = 0 ; + stream.lastColor[1] = 0 ; + stream.lastColor[2] = 0 ; + stream.lastColor[3] = 0 ; + stream.firstColor = false ; + stream.colorQuantChanged = false ; + } + + // Convert the floating point position to s.15 2's complement. + if (color3) { + R = (int)(colorR * 32768.0) ; + G = (int)(colorG * 32768.0) ; + B = (int)(colorB * 32768.0) ; + A = 0 ; + } else if (color4) { + R = (int)(colorR * 32768.0) ; + G = (int)(colorG * 32768.0) ; + B = (int)(colorB * 32768.0) ; + A = (int)(colorA * 32768.0) ; + } + + // Clamp color components. + R = (R > 32767? 32767: (R < 0? 0: R)) ; + G = (G > 32767? 32767: (G < 0? 0: G)) ; + B = (B > 32767? 32767: (B < 0? 0: B)) ; + A = (A > 32767? 32767: (A < 0? 0: A)) ; + + // Compute quantized values. + R &= quantizationMask[quant] ; + G &= quantizationMask[quant] ; + B &= quantizationMask[quant] ; + A &= quantizationMask[quant] ; + + // Copy and retain absolute color for mesh buffer lookup. + rAbsolute = R ; + gAbsolute = G ; + bAbsolute = B ; + aAbsolute = A ; + + // Compute deltas. + R -= stream.lastColor[0] ; + G -= stream.lastColor[1] ; + B -= stream.lastColor[2] ; + A -= stream.lastColor[3] ; + + // Update last values. + stream.lastColor[0] += R ; + stream.lastColor[1] += G ; + stream.lastColor[2] += B ; + stream.lastColor[3] += A ; + + // Compute length and shift common to all components. + if (color3) + computeLengthShift(R, G, B) ; + + else if (color4) + computeLengthShift(R, G, B, A) ; + + // 0-length components are allowed only for normals. + if (length == 0) + length = 1 ; + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addColorEntry(length, shift, absolute) ; + } + + /** + * Output a setColor command. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + outputColor(table, output, CommandStream.SET_COLOR, 8) ; + } + + /** + * Output a color subcommand. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputSubcommand(HuffmanTable table, CommandStream output) { + + outputColor(table, output, 0, 6) ; + } + + // + // Output the final compressed bits to the output command stream. + // + private void outputColor(HuffmanTable table, CommandStream output, + int header, int headerLength) { + HuffmanNode t ; + + // Look up the Huffman token for this compression stream element. + t = table.getColorEntry(length, shift, absolute) ; + + // Construct the color subcommand components. The maximum length of a + // color subcommand is 70 bits (a tag with a length of 6 followed by 4 + // components of 16 bits each). The subcommand is therefore + // constructed initially using just the first 3 components, with the + // 4th component added later after the tag has been shifted into the + // subcommand header. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = t.tagLength + (3 * componentLength) ; + + R = (R >> t.shift) & (int)lengthMask[componentLength] ; + G = (G >> t.shift) & (int)lengthMask[componentLength] ; + B = (B >> t.shift) & (int)lengthMask[componentLength] ; + + long colorSubcommand = + (((long)t.tag) << (3 * componentLength)) | + (((long)R) << (2 * componentLength)) | + (((long)G) << (1 * componentLength)) | + (((long)B) << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + header |= (int)(colorSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + header |= (int)(colorSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Add alpha if present. + if (color4) { + A = (A >> t.shift) & (int)lengthMask[componentLength] ; + colorSubcommand = (colorSubcommand << componentLength) | A ; + subcommandLength += componentLength ; + } + + // Add the header and body to the output buffer. + output.addCommand(header, headerLength, + colorSubcommand, subcommandLength) ; + } + + public String toString() { + String d = absolute? "" : "delta " ; + String c = (colorR + " " + colorG + " " + colorB + + (color4? (" " + colorA): "")) ; + + return + "color: " + c + "\n" + + " fixed point " + d + + R + " " + G + " " + B + "\n" + + " length " + length + " shift " + shift + + (absolute? " absolute" : " relative") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java new file mode 100644 index 0000000..6000a99 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamElement.java @@ -0,0 +1,359 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; + +/** + * Instances of this class are used as elements in a CompressionStream. + * @see CompressionStream + */ +abstract class CompressionStreamElement { + /** + * Bit length of quantized geometric components. + */ + int length ; + + /** + * Number of trailing zeros in quantized geometric components. + */ + int shift ; + + /** + * If false, geometric component values are represented as differences + * from those of the preceding element in the stream. + */ + boolean absolute ; + + /** + * Array with elements that can be used as masks to apply a quantization + * to the number of bits indicated by the referencing index [0..16]. + */ + static final int quantizationMask[] = { + 0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000, + 0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00, + 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0, + 0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE, + 0xFFFFFFFF + } ; + + /** + * Array with elements that can be used as masks to retain the number of + * trailing bits of data indicated by the referencing index [0..64]. Used + * to clear the leading sign bits of fixed-point 2's complement numbers + * and in building the compressed output stream. + */ + static final long lengthMask[] = { + 0x0000000000000000L, 0x0000000000000001L, + 0x0000000000000003L, 0x0000000000000007L, + 0x000000000000000FL, 0x000000000000001FL, + 0x000000000000003FL, 0x000000000000007FL, + 0x00000000000000FFL, 0x00000000000001FFL, + 0x00000000000003FFL, 0x00000000000007FFL, + 0x0000000000000FFFL, 0x0000000000001FFFL, + 0x0000000000003FFFL, 0x0000000000007FFFL, + 0x000000000000FFFFL, 0x000000000001FFFFL, + 0x000000000003FFFFL, 0x000000000007FFFFL, + 0x00000000000FFFFFL, 0x00000000001FFFFFL, + 0x00000000003FFFFFL, 0x00000000007FFFFFL, + 0x0000000000FFFFFFL, 0x0000000001FFFFFFL, + 0x0000000003FFFFFFL, 0x0000000007FFFFFFL, + 0x000000000FFFFFFFL, 0x000000001FFFFFFFL, + 0x000000003FFFFFFFL, 0x000000007FFFFFFFL, + 0x00000000FFFFFFFFL, 0x00000001FFFFFFFFL, + 0x00000003FFFFFFFFL, 0x00000007FFFFFFFFL, + 0x0000000FFFFFFFFFL, 0x0000001FFFFFFFFFL, + 0x0000003FFFFFFFFFL, 0x0000007FFFFFFFFFL, + 0x000000FFFFFFFFFFL, 0x000001FFFFFFFFFFL, + 0x000003FFFFFFFFFFL, 0x000007FFFFFFFFFFL, + 0x00000FFFFFFFFFFFL, 0x00001FFFFFFFFFFFL, + 0x00003FFFFFFFFFFFL, 0x00007FFFFFFFFFFFL, + 0x0000FFFFFFFFFFFFL, 0x0001FFFFFFFFFFFFL, + 0x0003FFFFFFFFFFFFL, 0x0007FFFFFFFFFFFFL, + 0x000FFFFFFFFFFFFFL, 0x001FFFFFFFFFFFFFL, + 0x003FFFFFFFFFFFFFL, 0x007FFFFFFFFFFFFFL, + 0x00FFFFFFFFFFFFFFL, 0x01FFFFFFFFFFFFFFL, + 0x03FFFFFFFFFFFFFFL, 0x07FFFFFFFFFFFFFFL, + 0x0FFFFFFFFFFFFFFFL, 0x1FFFFFFFFFFFFFFFL, + 0x3FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL, + 0xFFFFFFFFFFFFFFFFL + } ; + + + /** + * Computes the quantized representation of this stream element. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + abstract void quantize(CompressionStream stream, HuffmanTable table) ; + + /** + * Outputs the compressed bits representing this stream element. + * Some instances of CompressionStreamElement don't require an + * implementation and will inherit the stub provided here. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + } + + /** + * Finds the minimum bits needed to represent the given 16-bit signed 2's + * complement integer. For positive integers, this include the first + * 1 starting from the left, plus a 0 sign bit; for negative integers, + * this includes the first 0 starting from the left, plus a 1 sign bit. + * 0 is a special case returning 0; however, 0-length components are valid + * ONLY for normals. + * + * The decompressor uses the data length to determine how many bits of + * sign extension to add to the data coming in from the compressed stream + * in order to create a 16-bit signed 2's complement integer. E.g., a data + * length of 12 indicates that 16-12=4 bits of sign are to be extended.

+ * + * @param number a signed 2's complement integer representable in 16 bits + * or less + * @return minimum number of bits to represent the number + */ + private static final int getLength(int number) { + if (number == 0) + return 0 ; + + else if ((number & 0x8000) > 0) { + // negative numbers + if ((number & 0x4000) == 0) return 16 ; + if ((number & 0x2000) == 0) return 15 ; + if ((number & 0x1000) == 0) return 14 ; + if ((number & 0x0800) == 0) return 13 ; + if ((number & 0x0400) == 0) return 12 ; + if ((number & 0x0200) == 0) return 11 ; + if ((number & 0x0100) == 0) return 10 ; + if ((number & 0x0080) == 0) return 9 ; + if ((number & 0x0040) == 0) return 8 ; + if ((number & 0x0020) == 0) return 7 ; + if ((number & 0x0010) == 0) return 6 ; + if ((number & 0x0008) == 0) return 5 ; + if ((number & 0x0004) == 0) return 4 ; + if ((number & 0x0002) == 0) return 3 ; + if ((number & 0x0001) == 0) return 2 ; + + return 1 ; + + } else { + // positive numbers + if ((number & 0x4000) > 0) return 16 ; + if ((number & 0x2000) > 0) return 15 ; + if ((number & 0x1000) > 0) return 14 ; + if ((number & 0x0800) > 0) return 13 ; + if ((number & 0x0400) > 0) return 12 ; + if ((number & 0x0200) > 0) return 11 ; + if ((number & 0x0100) > 0) return 10 ; + if ((number & 0x0080) > 0) return 9 ; + if ((number & 0x0040) > 0) return 8 ; + if ((number & 0x0020) > 0) return 7 ; + if ((number & 0x0010) > 0) return 6 ; + if ((number & 0x0008) > 0) return 5 ; + if ((number & 0x0004) > 0) return 4 ; + if ((number & 0x0002) > 0) return 3 ; + + return 2 ; + } + } + + /** + * Finds the rightmost 1 bit in the given 16-bit integer. This value is + * used by the decompressor to indicate the number of trailing zeros to be + * added to the end of the data coming in from the compressed stream, + * accomplished by left shifting the data by the indicated amount. + * 0 is a special case returning 0.

+ * + * @param number an integer representable in 16 bits or less + * @return number of trailing zeros + */ + private static final int getShift(int number) { + if (number == 0) return 0 ; + + if ((number & 0x0001) > 0) return 0 ; + if ((number & 0x0002) > 0) return 1 ; + if ((number & 0x0004) > 0) return 2 ; + if ((number & 0x0008) > 0) return 3 ; + if ((number & 0x0010) > 0) return 4 ; + if ((number & 0x0020) > 0) return 5 ; + if ((number & 0x0040) > 0) return 6 ; + if ((number & 0x0080) > 0) return 7 ; + if ((number & 0x0100) > 0) return 8 ; + if ((number & 0x0200) > 0) return 9 ; + if ((number & 0x0400) > 0) return 10 ; + if ((number & 0x0800) > 0) return 11 ; + if ((number & 0x1000) > 0) return 12 ; + if ((number & 0x2000) > 0) return 13 ; + if ((number & 0x4000) > 0) return 14 ; + + return 15 ; + } + + /** + * Computes common length and shift of 2 numbers. + */ + final void computeLengthShift(int n0, int n1) { + int s0 = n0 & 0x8000 ; + int s1 = n1 & 0x8000 ; + + // equal sign optimization + if (s0 == s1) + if (s0 == 0) + this.length = getLength(n0 | n1) ; + else + this.length = getLength(n0 & n1) ; + else + this.length = getMaximum(getLength(n0), getLength(n1)) ; + + this.shift = getShift(n0 | n1) ; + } + + + /** + * Computes common length and shift of 3 numbers. + */ + final void computeLengthShift(int n0, int n1, int n2) { + int s0 = n0 & 0x8000 ; + int s1 = n1 & 0x8000 ; + int s2 = n2 & 0x8000 ; + + // equal sign optimization + if (s0 == s1) + if (s1 == s2) + if (s2 == 0) + this.length = getLength(n0 | n1 | n2) ; + else + this.length = getLength(n0 & n1 & n2) ; + else + if (s1 == 0) + this.length = getMaximum(getLength(n0 | n1), + getLength(n2)) ; + else + this.length = getMaximum(getLength(n0 & n1), + getLength(n2)) ; + else + if (s1 == s2) + if (s2 == 0) + this.length = getMaximum(getLength(n1 | n2), + getLength(n0)) ; + else + this.length = getMaximum(getLength(n1 & n2), + getLength(n0)) ; + else + if (s0 == 0) + this.length = getMaximum(getLength(n0 | n2), + getLength(n1)) ; + else + this.length = getMaximum(getLength(n0 & n2), + getLength(n1)) ; + + this.shift = getShift(n0 | n1 | n2) ; + } + + + /** + * Computes common length and shift of 4 numbers. + */ + final void computeLengthShift(int n0, int n1, int n2, int n3) { + this.length = getMaximum(getLength(n0), getLength(n1), + getLength(n2), getLength(n3)) ; + + this.shift = getShift(n0 | n1 | n2 | n3) ; + } + + + /** + * Finds the maximum of two integers. + */ + private static final int getMaximum(int x, int y) { + if (x > y) + return x ; + else + return y ; + } + + /** + * Finds the maximum of three integers. + */ + private static final int getMaximum(int x, int y, int z) { + if (x > y) + if (x > z) + return x ; + else + return z ; + else + if (y > z) + return y ; + else + return z ; + } + + /** + * Finds the maximum of four integers. + */ + private static final int getMaximum(int x, int y, int z, int w) { + int n0, n1 ; + + if (x > y) + n0 = x ; + else + n0 = y ; + + if (z > w) + n1 = z ; + else + n1 = w ; + + if (n0 > n1) + return n0 ; + else + return n1 ; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java new file mode 100644 index 0000000..b70ba4e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamNormal.java @@ -0,0 +1,673 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import javax.vecmath.* ; + +/** + * This class represents a normal in a compression stream. It maintains both + * floating-point and quantized representations. This normal may be bundled + * with a vertex or exist separately as a global normal. + */ +class CompressionStreamNormal extends CompressionStreamElement { + private int u, v ; + private int specialOctant, specialSextant ; + private float normalX, normalY, normalZ ; + + int octant, sextant ; + boolean specialNormal ; + int uAbsolute, vAbsolute ; + + /** + * Create a CompressionStreamNormal. + * + * @param stream CompressionStream associated with this element + * @param normal floating-point representation to be encoded + */ + CompressionStreamNormal(CompressionStream stream, Vector3f normal) { + this.normalX = normal.x ; + this.normalY = normal.y ; + this.normalZ = normal.z ; + stream.byteCount += 12 ; + } + + // + // Normal Encoding Parameterization + // + // A floating point normal is quantized to a desired number of bits by + // comparing it to candidate entries in a table of every possible normal + // at that quantization and finding the closest match. This table of + // normals is indexed by the following encoding: + // + // First, points on a unit radius sphere are parameterized by two angles, + // th and psi, using usual spherical coordinates. th is the angle about + // the y axis, psi is the inclination to the plane containing the point. + // The mapping between rectangular and spherical coordinates is: + // + // x = cos(th)*cos(psi) + // y = sin(psi) + // z = sin(th)*cos(psi) + // + // Points on sphere are folded first by octant, and then by sort order + // of xyz into one of six sextants. All the table encoding takes place in + // the positive octant, in the region bounded by the half spaces: + // + // x >= z + // z >= y + // y >= 0 + // + // This triangular shaped patch runs from 0 to 45 degrees in th, and + // from 0 to as much as 0.615479709 (MAX_Y_ANG) in psi. The xyz bounds + // of the patch is: + // + // (1, 0, 0) (1/sqrt(2), 0, 1/sqrt(2)) (1/sqrt(3), 1/sqrt(3), 1/sqrt(3)) + // + // When dicing this space up into discrete points, the choice for y is + // linear quantization in psi. This means that if the y range is to be + // divided up into n segments, the angle of segment j is: + // + // psi(j) = MAX_Y_ANG*(j/n) + // + // The y height of the patch (in arc length) is *not* the same as the xz + // dimension. However, the subdivision quantization needs to treat xz and + // y equally. To achieve this, the th angles are re-parameterized as + // reflected psi angles. That is, the i-th point's th is: + // + // th(i) = asin(tan(psi(i))) = asin(tan(MAX_Y_ANG*(i/n))) + // + // To go the other direction, the angle th corresponds to the real index r + // (in the same 0-n range as i): + // + // r(th) = n*atan(sin(th))/MAX_Y_ANG + // + // Rounded to the nearest integer, this gives the closest integer index i + // to the xz angle th. Because the triangle has a straight edge on the + // line x=z, it is more intuitive to index the xz angles in reverse + // order. Thus the two equations above are replaced by: + // + // th(i) = asin(tan(psi(i))) = asin(tan(MAX_Y_ANG*((n-i)/n))) + // + // r(th) = n*(1 - atan(sin(th))/MAX_Y_ANG) + // + // Each level of quantization subdivides the triangular patch twice as + // densely. The case in which only the three vertices of the triangle are + // present is the first logical stage of representation, but because of + // how the table is encoded the first usable case starts one level of + // sub-division later. This three point level has an n of 2 by the above + // conventions. + // + private static final int MAX_UV_BITS = 6 ; + private static final int MAX_UV_ENTRIES = 64 ; + + private static final double cgNormals[][][][] = + new double[MAX_UV_BITS+1][MAX_UV_ENTRIES+1][MAX_UV_ENTRIES+1][3] ; + + private static final double MAX_Y_ANG = 0.615479709 ; + private static final double UNITY_14 = 16384.0 ; + + private static void computeNormals() { + int inx, iny, inz, n ; + double th, psi, qnx, qny, qnz ; + + for (int quant = 0 ; quant <= MAX_UV_BITS ; quant++) { + n = 1 << quant ; + + for (int j = 0 ; j <= n ; j++) { + for (int i = 0 ; i <= n ; i++) { + if (i+j > n) continue ; + + psi = MAX_Y_ANG*(j/((double) n)) ; + th = Math.asin(Math.tan(MAX_Y_ANG*((n-i)/((double) n)))) ; + + qnx = Math.cos(th)*Math.cos(psi) ; + qny = Math.sin(psi) ; + qnz = Math.sin(th)*Math.cos(psi) ; + + // The normal table uses 16-bit components and must be + // able to represent both +1.0 and -1.0, so convert the + // floating point normal components to fixed point with 14 + // fractional bits, a unity bit, and a sign bit (s1.14). + // Set them back to get the float equivalent. + qnx = qnx*UNITY_14 ; inx = (int)qnx ; + qnx = inx ; qnx = qnx/UNITY_14 ; + + qny = qny*UNITY_14 ; iny = (int)qny ; + qny = iny ; qny = qny/UNITY_14 ; + + qnz = qnz*UNITY_14 ; inz = (int)qnz ; + qnz = inz ; qnz = qnz/UNITY_14 ; + + cgNormals[quant][j][i][0] = qnx ; + cgNormals[quant][j][i][1] = qny ; + cgNormals[quant][j][i][2] = qnz ; + } + } + } + } + + // + // An inverse sine table is used for each quantization level to take the Y + // component of a normal (which is the sine of the inclination angle) and + // obtain the closest quantized Y angle. + // + // At any level of compression, there are a fixed number of different Y + // angles (between 0 and MAX_Y_ANG). The inverse table is built to have + // slightly more than twice as many entries as y angles at any particular + // level; this ensures that the inverse look-up will get within one angle + // of the right one. The size of the table should be as small as + // possible, but with its delta sine still smaller than the delta sine + // between the last two angles to be encoded. + // + // Example: the inverse sine table has a maximum angle of 0.615479709. At + // the maximum resolution of 6 bits there are 65 discrete angles used, + // but twice as many are needed for thresholding between angles, so the + // delta angle is 0.615479709/128. The difference then between the last + // two angles to be encoded is: + // sin(0.615479709*128.0/128.0) - sin(0.615479709*127.0/128.0) = 0.003932730 + // + // Using 8 significent bits below the binary point, fixed point can + // represent sines in increments of 0.003906250, just slightly smaller. + // However, because the maximum Y angle sine is 0.577350269, only 148 + // instead of 256 table entries are needed. + // + private static final short inverseSine[][] = new short[MAX_UV_BITS+1][] ; + + // UNITY_14 * sin(MAX_Y_ANGLE) + private static final short MAX_SIN_14BIT = 9459 ; + + private static void computeInverseSineTables() { + int intSin, deltaSin, intAngle ; + double floatSin, floatAngle ; + short sin14[] = new short[MAX_UV_ENTRIES+1] ; + + // Build table of sines in s1.14 fixed point for each of the + // discrete angles used at maximum resolution. + for (int i = 0 ; i <= MAX_UV_ENTRIES ; i++) { + sin14[i] = (short)(UNITY_14*Math.sin(i*MAX_Y_ANG/MAX_UV_ENTRIES)) ; + } + + for (int quant = 0 ; quant <= MAX_UV_BITS ; quant++) { + switch (quant) { + default: + case 6: + // Delta angle: MAX_Y_ANGLE/128.0 + // Bits below binary point for fixed point delta sine: 8 + // Integer delta sine: 64 + // Inverse sine table size: 148 entries + deltaSin = 1 << (14 - 8) ; + break ; + case 5: + // Delta angle: MAX_Y_ANGLE/64.0 + // Bits below binary point for fixed point delta sine: 7 + // Integer delta sine: 128 + // Inverse sine table size: 74 entries + deltaSin = 1 << (14 - 7) ; + break ; + case 4: + // Delta angle: MAX_Y_ANGLE/32.0 + // Bits below binary point for fixed point delta sine: 6 + // Integer delta sine: 256 + // Inverse sine table size: 37 entries + deltaSin = 1 << (14 - 6) ; + break ; + case 3: + // Delta angle: MAX_Y_ANGLE/16.0 + // Bits below binary point for fixed point delta sine: 5 + // Integer delta sine: 512 + // Inverse sine table size: 19 entries + deltaSin = 1 << (14 - 5) ; + break ; + case 2: + // Delta angle: MAX_Y_ANGLE/8.0 + // Bits below binary point for fixed point delta sine: 4 + // Integer delta sine: 1024 + // Inverse sine table size: 10 entries + deltaSin = 1 << (14 - 4) ; + break ; + case 1: + // Delta angle: MAX_Y_ANGLE/4.0 + // Bits below binary point for fixed point delta sine: 3 + // Integer delta sine: 2048 + // Inverse sine table size: 5 entries + deltaSin = 1 << (14 - 3) ; + break ; + case 0: + // Delta angle: MAX_Y_ANGLE/2.0 + // Bits below binary point for fixed point delta sine: 2 + // Integer delta sine: 4096 + // Inverse sine table size: 3 entries + deltaSin = 1 << (14 - 2) ; + break ; + } + + inverseSine[quant] = new short[(MAX_SIN_14BIT/deltaSin) + 1] ; + + intSin = 0 ; + for (int i = 0 ; i < inverseSine[quant].length ; i++) { + // Compute float representation of integer sine with desired + // number of fractional bits by effectively right shifting 14. + floatSin = intSin/UNITY_14 ; + + // Compute the angle with this sine value and quantize it. + floatAngle = Math.asin(floatSin) ; + intAngle = (int)((floatAngle/MAX_Y_ANG) * (1 << quant)) ; + + // Choose the closest of the three nearest quantized values + // intAngle-1, intAngle, and intAngle+1. + if (intAngle > 0) { + if (Math.abs(sin14[intAngle << (6-quant)] - intSin) > + Math.abs(sin14[(intAngle-1) << (6-quant)] - intSin)) + intAngle = intAngle-1 ; + } + + if (intAngle < (1 << quant)) { + if (Math.abs(sin14[intAngle << (6-quant)] - intSin) > + Math.abs(sin14[(intAngle+1) << (6-quant)] - intSin)) + intAngle = intAngle+1 ; + } + + inverseSine[quant][i] = (short)intAngle ; + intSin += deltaSin ; + } + } + } + + /** + * Compute static tables needed for normal quantization. + */ + static { + computeNormals() ; + computeInverseSineTables() ; + } + + /** + * Quantize the floating point normal to a 6-bit octant/sextant plus u,v + * components of [0..6] bits. Full resolution is 18 bits and the minimum + * is 6 bits. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + double nx, ny, nz, t ; + + // Clamp UV quantization. + int quant = + (stream.normalQuant < 0? 0 : + (stream.normalQuant > 6? 6 : stream.normalQuant)) ; + + nx = normalX ; + ny = normalY ; + nz = normalZ ; + + octant = 0 ; + sextant = 0 ; + u = 0 ; + v = 0 ; + + // Normalize the fixed point normal to the positive signed octant. + if (nx < 0.0) { + octant |= 4 ; + nx = -nx ; + } + if (ny < 0.0) { + octant |= 2 ; + ny = -ny ; + } + if (nz < 0.0) { + octant |= 1 ; + nz = -nz ; + } + + // Normalize the fixed point normal to the proper sextant of the octant. + if (nx < ny) { + sextant |= 1 ; + t = nx ; + nx = ny ; + ny = t ; + } + if (nz < ny) { + sextant |= 2 ; + t = ny ; + ny = nz ; + nz = t ; + } + if (nx < nz) { + sextant |= 4 ; + t = nx ; + nx = nz ; + nz = t ; + } + + // Convert the floating point y component to s1.14 fixed point. + int yInt = (int)(ny * UNITY_14) ; + + // The y component of the normal is the sine of the y angle. Quantize + // the y angle by using the fixed point y component as an index into + // the inverse sine table of the correct size for the quantization + // level. (12 - quant) bits of the s1.14 y normal component are + // rolled off with a right shift; the remaining bits then match the + // number of bits used to represent the delta sine of the table. + int yIndex = inverseSine[quant][yInt >> (12-quant)] ; + + // Search the two xz rows near y for the best match. + int ii = 0 ; + int jj = 0 ; + int n = 1 << quant ; + double dot, bestDot = -1 ; + + for (int j = yIndex-1 ; j < yIndex+1 && j <= n ; j++) { + if (j < 0) + continue ; + + for (int i = 0 ; i <= n ; i++) { + if (i+j > n) + continue ; + + dot = nx * cgNormals[quant][j][i][0] + + ny * cgNormals[quant][j][i][1] + + nz * cgNormals[quant][j][i][2] ; + + if (dot > bestDot) { + bestDot = dot ; + ii = i ; + jj = j ; + } + } + } + + // Convert u and v to standard grid form. + u = ii << (6 - quant) ; + v = jj << (6 - quant) ; + + // Check for special normals and specially encode them. + specialNormal = false ; + if (u == 64 && v == 0) { + // six coordinate axes case + if (sextant == 0 || sextant == 2) { + // +/- x-axis + specialSextant = 0x6 ; + specialOctant = ((octant & 4) != 0)? 0x2 : 0 ; + + } else if (sextant == 3 || sextant == 1) { + // +/- y-axis + specialSextant = 0x6 ; + specialOctant = 4 | (((octant & 2) != 0)? 0x2 : 0) ; + + } else if (sextant == 5 || sextant == 4) { + // +/- z-axis + specialSextant = 0x7 ; + specialOctant = ((octant & 1) != 0)? 0x2 : 0 ; + } + specialNormal = true ; + u = v = 0 ; + + } else if (u == 0 && v == 64) { + // eight mid point case + specialSextant = 6 | (octant >> 2) ; + specialOctant = ((octant & 0x3) << 1) | 1 ; + specialNormal = true ; + u = v = 0 ; + } + + // Compute deltas if possible. + // Use the non-normalized ii and jj indices. + int du = 0 ; + int dv = 0 ; + int uv64 = 64 >> (6 - quant) ; + + absolute = false ; + if (stream.firstNormal || stream.normalQuantChanged || + stream.lastSpecialNormal || specialNormal) { + // The first normal by definition is absolute, and normals cannot + // be represented as deltas to or from special normals, nor from + // normals with a different quantization. + absolute = true ; + stream.firstNormal = false ; + stream.normalQuantChanged = false ; + + } else if (stream.lastOctant == octant && + stream.lastSextant == sextant) { + // Deltas are always allowed within the same sextant/octant. + du = ii - stream.lastU ; + dv = jj - stream.lastV ; + + } else if (stream.lastOctant != octant && + stream.lastSextant == sextant && + (((sextant == 1 || sextant == 5) && + (stream.lastOctant & 3) == (octant & 3)) || + ((sextant == 0 || sextant == 4) && + (stream.lastOctant & 5) == (octant & 5)) || + ((sextant == 2 || sextant == 3) && + (stream.lastOctant & 6) == (octant & 6)))) { + // If the sextants are the same, the octants can differ only when + // they are bordering each other on the same edge that the + // sextant has. + du = ii - stream.lastU ; + dv = -jj - stream.lastV ; + + // Can't delta by less than -64. + if (dv < -uv64) absolute = true ; + + // Can't delta doubly defined points. + if (jj == 0) absolute = true ; + + } else if (stream.lastOctant == octant && + stream.lastSextant != sextant && + ((sextant == 0 && stream.lastSextant == 4) || + (sextant == 4 && stream.lastSextant == 0) || + (sextant == 1 && stream.lastSextant == 5) || + (sextant == 5 && stream.lastSextant == 1) || + (sextant == 2 && stream.lastSextant == 3) || + (sextant == 3 && stream.lastSextant == 2))) { + // If the octants are the same, the sextants must border on + // the i side (this case) or the j side (next case). + du = -ii - stream.lastU ; + dv = jj - stream.lastV ; + + // Can't delta by less than -64. + if (du < -uv64) absolute = true ; + + // Can't delta doubly defined points. + if (ii == 0) absolute = true ; + + } else if (stream.lastOctant == octant && + stream.lastSextant != sextant && + ((sextant == 0 && stream.lastSextant == 2) || + (sextant == 2 && stream.lastSextant == 0) || + (sextant == 1 && stream.lastSextant == 3) || + (sextant == 3 && stream.lastSextant == 1) || + (sextant == 4 && stream.lastSextant == 5) || + (sextant == 5 && stream.lastSextant == 4))) { + // If the octants are the same, the sextants must border on + // the j side (this case) or the i side (previous case). + if (((ii + jj ) != uv64) && (ii != 0) && (jj != 0)) { + du = uv64 - ii - stream.lastU ; + dv = uv64 - jj - stream.lastV ; + + // Can't delta by greater than +63. + if ((du >= uv64) || (dv >= uv64)) + absolute = true ; + } else + // Can't delta doubly defined points. + absolute = true ; + + } else + // Can't delta this normal. + absolute = true ; + + if (absolute == false) { + // Convert du and dv to standard grid form. + u = du << (6 - quant) ; + v = dv << (6 - quant) ; + } + + // Compute length and shift common to all components. + computeLengthShift(u, v) ; + + if (absolute && length > 6) { + // Absolute normal u, v components are unsigned 6-bit integers, so + // truncate the 0 sign bit for values > 0x001f. + length = 6 ; + } + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addNormalEntry(length, shift, absolute) ; + + // Save current normal as last. + stream.lastSextant = sextant ; + stream.lastOctant = octant ; + stream.lastU = ii ; + stream.lastV = jj ; + stream.lastSpecialNormal = specialNormal ; + + // Copy and retain absolute normal for mesh buffer lookup. + uAbsolute = ii ; + vAbsolute = jj ; + } + + /** + * Output a setNormal command. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable table, CommandStream output) { + outputNormal(table, output, CommandStream.SET_NORM, 8) ; + } + + /** + * Output a normal subcommand. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputSubcommand(HuffmanTable table, CommandStream output) { + outputNormal(table, output, 0, 6) ; + } + + // + // Output the final compressed bits to the output command stream. + // + private void outputNormal(HuffmanTable table, CommandStream output, + int header, int headerLength) { + + HuffmanNode t ; + + // Look up the Huffman token for this compression stream element. + t = table.getNormalEntry(length, shift, absolute) ; + + // Construct the normal subcommand. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = 0 ; + long normalSubcommand = 0 ; + + if (absolute) { + // A 3-bit sextant and a 3-bit octant are always present. + subcommandLength = t.tagLength + 6 ; + + if (specialNormal) + // Use the specially-encoded sextant and octant. + normalSubcommand = + (t.tag << 6) | (specialSextant << 3) | specialOctant ; + else + // Use the general encoding rule. + normalSubcommand = + (t.tag << 6) | (sextant << 3) | octant ; + } else { + // The tag is immediately followed by the u and v delta components. + subcommandLength = t.tagLength ; + normalSubcommand = t.tag ; + } + + // Add the u and v values to the subcommand. + subcommandLength += (2 * componentLength) ; + + u = (u >> t.shift) & (int)lengthMask[componentLength] ; + v = (v >> t.shift) & (int)lengthMask[componentLength] ; + + normalSubcommand = + (normalSubcommand << (2 * componentLength)) | + (u << (1 * componentLength)) | + (v << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + header |= (int)(normalSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + header |= (int)(normalSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Add the header and body to the output buffer. + output.addCommand(header, headerLength, + normalSubcommand, subcommandLength) ; + } + + public String toString() { + String fixed ; + + if (specialNormal) + fixed = " special normal, sextant " + specialSextant + + " octant " + specialOctant ; + + else if (absolute) + fixed = " sextant " + sextant + " octant " + octant + + " u " + u + " v " + v ; + else + fixed = " du " + u + " dv " + v ; + + return + "normal: " + normalX + " " + normalY + " " + normalZ + "\n" + + fixed + "\n" + " length " + length + " shift " + shift + + (absolute? " absolute" : " relative") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java new file mode 100644 index 0000000..ad49b28 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/CompressionStreamVertex.java @@ -0,0 +1,318 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import javax.vecmath.* ; + +/** + * This class represents a vertex in a compression stream. It maintains both + * floating-point and quantized representations of the vertex position along + * with meshing and vertex replacement flags for line and surface + * primitives. If normals or colors are bundled with geometry vertices then + * instances of this class will also contain references to normal or color + * stream elements. + */ +class CompressionStreamVertex extends CompressionStreamElement { + private int X, Y, Z ; + private int meshFlag ; + private int stripFlag ; + private float floatX, floatY, floatZ ; + + int xAbsolute, yAbsolute, zAbsolute ; + CompressionStreamColor color = null ; + CompressionStreamNormal normal = null ; + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param c color bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, + Point3f p, Vector3f n, Color3f c, + int stripFlag, int meshFlag) { + + this(stream, p, n, stripFlag, meshFlag) ; + + if (stream.vertexColor3) + color = new CompressionStreamColor(stream, c) ; + } + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param c color bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, + Point3f p, Vector3f n, Color4f c, + int stripFlag, int meshFlag) { + + this(stream, p, n, stripFlag, meshFlag) ; + + if (stream.vertexColor4) + color = new CompressionStreamColor(stream, c) ; + } + + /** + * Create a CompressionStreamVertex with the given parameters. + * + * @param stream CompressionStream associated with this vertex + * @param p position + * @param n normal bundled with this vertex or null if not bundled + * @param stripFlag CompressionStream.RESTART, + * CompressionStream.REPLACE_OLDEST, or CompressionStream.REPLACE_MIDDLE + * @param meshFlag CompressionStream.MESH_PUSH or + * CompressionStream.NO_MESH_PUSH + */ + CompressionStreamVertex(CompressionStream stream, Point3f p, Vector3f n, + int stripFlag, int meshFlag) { + + this.stripFlag = stripFlag ; + this.meshFlag = meshFlag ; + this.floatX = p.x ; + this.floatY = p.y ; + this.floatZ = p.z ; + + stream.byteCount += 12 ; + stream.vertexCount++ ; + + if (p.x < stream.mcBounds[0].x) stream.mcBounds[0].x = p.x ; + if (p.y < stream.mcBounds[0].y) stream.mcBounds[0].y = p.y ; + if (p.z < stream.mcBounds[0].z) stream.mcBounds[0].z = p.z ; + + if (p.x > stream.mcBounds[1].x) stream.mcBounds[1].x = p.x ; + if (p.y > stream.mcBounds[1].y) stream.mcBounds[1].y = p.y ; + if (p.z > stream.mcBounds[1].z) stream.mcBounds[1].z = p.z ; + + if (stream.vertexNormals) + normal = new CompressionStreamNormal(stream, n) ; + } + + /** + * Quantize the floating point position to fixed point integer components + * of the specified number of bits. The bit length can range from 1 to 16. + * + * @param stream CompressionStream associated with this element + * @param table HuffmanTable for collecting data about the quantized + * representation of this element + */ + void quantize(CompressionStream stream, HuffmanTable huffmanTable) { + double px, py, pz ; + + // Clamp quantization. + int quant = + (stream.positionQuant < 1? 1 : + (stream.positionQuant > 16? 16 : stream.positionQuant)) ; + + absolute = false ; + if (stream.firstPosition || stream.positionQuantChanged) { + absolute = true ; + stream.lastPosition[0] = 0 ; + stream.lastPosition[1] = 0 ; + stream.lastPosition[2] = 0 ; + stream.firstPosition = false ; + stream.positionQuantChanged = false ; + } + + // Normalize position to the unit cube. This is bounded by the open + // intervals (-1..1) on each axis. + px = (floatX - stream.center[0]) * stream.scale ; + py = (floatY - stream.center[1]) * stream.scale ; + pz = (floatZ - stream.center[2]) * stream.scale ; + + // Convert the floating point position to s.15 2's complement. + // ~1.0 -> 32767 (0x00007fff) [ ~1.0 = 32767.0/32768.0] + // ~-1.0 -> -32767 (0xffff8001) [~-1.0 = -32767.0/32768.0] + X = (int)(px * 32768.0) ; + Y = (int)(py * 32768.0) ; + Z = (int)(pz * 32768.0) ; + + // Compute quantized values. + X &= quantizationMask[quant] ; + Y &= quantizationMask[quant] ; + Z &= quantizationMask[quant] ; + + // Update quantized bounds. + if (X < stream.qcBounds[0].x) stream.qcBounds[0].x = X ; + if (Y < stream.qcBounds[0].y) stream.qcBounds[0].y = Y ; + if (Z < stream.qcBounds[0].z) stream.qcBounds[0].z = Z ; + + if (X > stream.qcBounds[1].x) stream.qcBounds[1].x = X ; + if (Y > stream.qcBounds[1].y) stream.qcBounds[1].y = Y ; + if (Z > stream.qcBounds[1].z) stream.qcBounds[1].z = Z ; + + // Copy and retain absolute position for mesh buffer lookup. + xAbsolute = X ; + yAbsolute = Y ; + zAbsolute = Z ; + + // Compute deltas. + X -= stream.lastPosition[0] ; + Y -= stream.lastPosition[1] ; + Z -= stream.lastPosition[2] ; + + // Update last values. + stream.lastPosition[0] += X ; + stream.lastPosition[1] += Y ; + stream.lastPosition[2] += Z ; + + // Deltas which exceed the range of 16-bit signed 2's complement + // numbers are handled by sign-extension of the 16th bit in order to + // effect a 16-bit wrap-around. + X = (X << 16) >> 16 ; + Y = (Y << 16) >> 16 ; + Z = (Z << 16) >> 16 ; + + // Compute length and shift common to all components. + computeLengthShift(X, Y, Z) ; + + // 0-length components are allowed only for normals. + if (length == 0) + length = 1 ; + + // Add this element to the Huffman table associated with this stream. + huffmanTable.addPositionEntry(length, shift, absolute) ; + + // Quantize any bundled color or normal. + if (color != null) + color.quantize(stream, huffmanTable) ; + + if (normal != null) + normal.quantize(stream, huffmanTable) ; + + // Push this vertex into the mesh buffer mirror, if necessary, so it + // can be retrieved for computing deltas when mesh buffer references + // are subsequently encountered during the quantization pass. + if (meshFlag == stream.MESH_PUSH) + stream.meshBuffer.push(this) ; + } + + /** + * Output the final compressed bits to the compression command stream. + * + * @param table HuffmanTable mapping quantized representations to + * compressed encodings + * @param output CommandStream for collecting compressed output + */ + void outputCommand(HuffmanTable huffmanTable, CommandStream outputBuffer) { + + HuffmanNode t ; + int command = CommandStream.VERTEX ; + + // Look up the Huffman token for this compression stream element. The + // values of length and shift found there will override the + // corresponding fields in this element, which represent best-case + // compression without regard to tag length. + t = huffmanTable.getPositionEntry(length, shift, absolute) ; + + // Construct the position subcommand. + int componentLength = t.dataLength - t.shift ; + int subcommandLength = t.tagLength + (3 * componentLength) ; + + X = (X >> t.shift) & (int)lengthMask[componentLength] ; + Y = (Y >> t.shift) & (int)lengthMask[componentLength] ; + Z = (Z >> t.shift) & (int)lengthMask[componentLength] ; + + long positionSubcommand = + (((long)t.tag) << (3 * componentLength)) | + (((long)X) << (2 * componentLength)) | + (((long)Y) << (1 * componentLength)) | + (((long)Z) << (0 * componentLength)) ; + + if (subcommandLength < 6) { + // The header will have some empty bits. The Huffman tag + // computation will prevent this if necessary. + command |= (int)(positionSubcommand << (6 - subcommandLength)) ; + subcommandLength = 0 ; + } + else { + // Move the 1st 6 bits of the subcommand into the header. + command |= (int)(positionSubcommand >>> (subcommandLength - 6)) ; + subcommandLength -= 6 ; + } + + // Construct the vertex command body. + long body = + (((long)stripFlag) << (subcommandLength + 1)) | + (((long)meshFlag) << (subcommandLength + 0)) | + (positionSubcommand & lengthMask[subcommandLength]) ; + + // Add the vertex command to the output buffer. + outputBuffer.addCommand(command, 8, body, subcommandLength + 3) ; + + // Output any normal and color subcommands. + if (normal != null) + normal.outputSubcommand(huffmanTable, outputBuffer) ; + + if (color != null) + color.outputSubcommand(huffmanTable, outputBuffer) ; + } + + public String toString() { + String d = absolute? "" : "delta " ; + String c = (color == null? "": "\n\n " + color.toString()) ; + String n = (normal == null? "": "\n\n " + normal.toString()) ; + + return + "position: " + floatX + " " + floatY + " " + floatZ + "\n" + + "fixed point " + d + + X + " " + Y + " " + Z + "\n" + + "length " + length + " shift " + shift + + (absolute? " absolute" : " relative") + "\n" + + "strip flag " + stripFlag + " mesh flag " + meshFlag + + c + n ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java b/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java new file mode 100644 index 0000000..82b3865 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/GeometryCompressor.java @@ -0,0 +1,203 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; + +import javax.media.j3d.* ; +import javax.vecmath.* ; +import java.util.* ; +import java.io.* ; + +/** + * A GeometryCompressor takes a stream of geometric elements and + * quantization parameters (the CompressionStream object) and + * compresses it into a stream of commands as defined by appendix B + * of the Java 3D specification. The resulting data may be output + * in the form of a CompressedGeometry node component or appended + * to a CompressedGeometryFile. + * + * @see CompressionStream + * @see CompressedGeometry + * @see CompressedGeometryFile + */ +public class GeometryCompressor { + private static final boolean benchmark = false ; + private static final boolean printStream = false ; + private static final boolean printHuffman = false ; + + private HuffmanTable huffmanTable ; + private CommandStream outputBuffer ; + private CompressedGeometryHeader cgHeader ; + private long startTime ; + + public GeometryCompressor() { + // Create a compressed geometry header. + cgHeader = new CompressedGeometryHeader() ; + + // v1.0.0 - pre-FCS + // v1.0.1 - fixed winding order, FCS version (J3D 1.1.2) + // v1.0.2 - normal component length maximum 6, LII hardware (J3D 1.2) + cgHeader.majorVersionNumber = 1 ; + cgHeader.minorVersionNumber = 0 ; + cgHeader.minorMinorVersionNumber = 2 ; + } + + /** + * Compress a stream into a CompressedGeometry node component. + * + * @param stream CompressionStream containing the geometry to be compressed + * @return a CompressedGeometry node component + */ + public CompressedGeometry compress(CompressionStream stream) { + CompressedGeometry cg ; + + compressStream(stream) ; + cg = new CompressedGeometry(cgHeader, outputBuffer.getBytes()) ; + + outputBuffer.clear() ; + return cg ; + } + + /** + * Compress a stream and append the output to a CompressedGeometryFile. + * The resource remains open for subsequent updates; its close() method + * must be called to create a valid compressed geometry resource file. + * + * @param stream CompressionStream containing the geometry to be compressed + * @param f a currently open CompressedGeometryFile with write access + * @exception IOException if write fails + */ + public void compress(CompressionStream stream, CompressedGeometryFile f) + throws IOException { + + compressStream(stream) ; + f.write(cgHeader, outputBuffer.getBytes()) ; + + outputBuffer.clear() ; + } + + // + // Compress the stream and put the results in the output buffer. + // Set up the CompressedGeometryHeader object. + // + private void compressStream(CompressionStream stream) { + if (benchmark) startTime = System.currentTimeMillis() ; + + // Create the Huffman table. + huffmanTable = new HuffmanTable() ; + + // Quantize the stream, compute deltas between consecutive elements if + // possible, and histogram the data length distribution. + stream.quantize(huffmanTable) ; + + // Compute tags for stream tokens. + huffmanTable.computeTags() ; + + // Create the output buffer and assemble the compressed output. + outputBuffer = new CommandStream(stream.getByteCount() / 3) ; + stream.outputCommands(huffmanTable, outputBuffer) ; + + // Print any desired info. + if (benchmark) printBench(stream) ; + if (printStream) stream.print() ; + if (printHuffman) huffmanTable.print() ; + + // Set up the compressed geometry header object. + cgHeader.bufferType = stream.streamType ; + cgHeader.bufferDataPresent = 0 ; + cgHeader.lowerBound = new Point3d(stream.ncBounds[0]) ; + cgHeader.upperBound = new Point3d(stream.ncBounds[1]) ; + + if (stream.vertexNormals) + cgHeader.bufferDataPresent |= + CompressedGeometryHeader.NORMAL_IN_BUFFER ; + + if (stream.vertexColor3 || stream.vertexColor4) + cgHeader.bufferDataPresent |= + CompressedGeometryHeader.COLOR_IN_BUFFER ; + + if (stream.vertexColor4) + cgHeader.bufferDataPresent |= + CompressedGeometryHeader.ALPHA_IN_BUFFER ; + + cgHeader.start = 0 ; + cgHeader.size = outputBuffer.getByteCount() ; + + // Clear the huffman table for next use. + huffmanTable.clear() ; + } + + private void printBench(CompressionStream stream) { + long t = System.currentTimeMillis() - startTime ; + int vertexCount = stream.getVertexCount() ; + int meshReferenceCount = stream.getMeshReferenceCount() ; + int totalVertices = meshReferenceCount + vertexCount ; + float meshPercent = 100f * meshReferenceCount/(float)totalVertices ; + + float compressionRatio = + stream.getByteCount() / ((float)outputBuffer.getByteCount()) ; + + int vertexBytes = + 12 + (stream.vertexColor3 ? 12 : 0) + + (stream.vertexColor4 ? 16 : 0) + (stream.vertexNormals ? 12 : 0) ; + + float compressedVertexBytes = + outputBuffer.getByteCount() / (float)totalVertices ; + + System.out.println + ("\nGeometryCompressor:\n" + totalVertices + " total vertices\n" + + vertexCount + " streamed vertices\n" + meshReferenceCount + + " mesh buffer references (" + meshPercent + "%)\n" + + stream.getByteCount() + " bytes streamed geometry compressed to " + + outputBuffer.getByteCount() + " in " + (t/1000f) + " sec\n" + + (stream.getByteCount()/(float)t) + " kbytes/sec, " + + "stream compression ratio " + compressionRatio + "\n\n" + + vertexBytes + " original bytes per vertex, " + + compressedVertexBytes + " compressed bytes per vertex\n" + + "total vertex compression ratio " + + (vertexBytes / (float)compressedVertexBytes) + "\n\n" + + "lower bound " + stream.ncBounds[0].toString() +"\n" + + "upper bound " + stream.ncBounds[1].toString()) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java b/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java new file mode 100644 index 0000000..b75f961 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/HuffmanNode.java @@ -0,0 +1,223 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import java.util.* ; + +/** + * Instances of this class are used as the nodes of binary trees representing + * mappings of tags to compression stream elements. Tags are descriptors + * inserted into the compression command stream that specify the encoding of + * immediately succeeding data elements.

+ * + * The tag assignments in such a tree are computed from the paths taken from + * the root to the leaf nodes. Each leaf node represents the particular way + * one or more compression stream elements wound up being encoded with respect + * to various combinations of data lengths, shifts, and absolute/relative + * status.

+ * + * Huffman's algorithm for constructing binary trees with minimal weighted + * path lengths can be used to optimize the bit lengths of the tags with + * respect to the frequency of occurrence of their associated data encodings + * in the compression stream. The weighted path length is the sum of the + * frequencies of all the leaf nodes times their path lengths to the root of + * the tree.

+ * + * The length of the longest tag determines the size of the table mapping tags + * to data representations. The geometry compression specification limits the + * size of the table to 64 entries, so tags cannot be longer than 6 bits. The + * depth of the tree is reduced through a process of increasing the data + * lengths of less frequently occuring nodes so they can be merged with other + * more frequent nodes. + */ +class HuffmanNode { + int tag, tagLength ; + int shift, dataLength ; + boolean absolute ; + + private int frequency ; + private HuffmanNode child0, child1, mergeNode ; + private boolean merged, unmergeable, cleared ; + + void clear() { + tag = -1 ; + tagLength = -1 ; + + shift = -1 ; + dataLength = -1 ; + absolute = false ; + + child0 = null ; + child1 = null ; + mergeNode = null ; + + frequency = 0 ; + merged = false ; + unmergeable = false ; + cleared = true ; + } + + HuffmanNode() { + clear() ; + } + + HuffmanNode(int length, int shift, boolean absolute) { + this() ; + set(length, shift, absolute) ; + } + + final void set(int length, int shift, boolean absolute) { + this.dataLength = length ; + this.shift = shift ; + this.absolute = absolute ; + this.cleared = false ; + } + + final boolean cleared() { + return cleared ; + } + + final void addCount() { + frequency++ ; + } + + final boolean hasCount() { + return frequency > 0 ; + } + + final boolean tokenEquals(HuffmanNode node) { + return + this.absolute == node.absolute && + this.dataLength == node.dataLength && + this.shift == node.shift ; + } + + void addChildren(HuffmanNode child0, HuffmanNode child1) { + this.child0 = child0 ; + this.child1 = child1 ; + this.frequency = child0.frequency + child1.frequency ; + } + + void collectLeaves(int tag, int tagLength, Collection collection) { + if (child0 == null) { + this.tag = tag ; + this.tagLength = tagLength ; + collection.add(this) ; + } else { + child0.collectLeaves((tag << 1) | 0, tagLength + 1, collection) ; + child1.collectLeaves((tag << 1) | 1, tagLength + 1, collection) ; + } + } + + boolean mergeInto(HuffmanNode node) { + if (this.absolute == node.absolute) { + if (this.dataLength > node.dataLength) + node.dataLength = this.dataLength ; + + if (this.shift < node.shift) + node.shift = this.shift ; + + node.frequency += this.frequency ; + this.mergeNode = node ; + this.merged = true ; + return true ; + + } else + return false ; + } + + int incrementLength() { + if (shift > 0) + shift-- ; + else + dataLength++ ; + + return dataLength - shift ; + } + + final boolean merged() { + return merged ; + } + + final HuffmanNode getMergeNode() { + return mergeNode ; + } + + void setUnmergeable() { + unmergeable = true ; + } + + final boolean unmergeable() { + return unmergeable ; + } + + public String toString() { + return + "shift " + shift + " data length " + dataLength + + (absolute? " absolute " : " relative ") + + "\ntag 0x" + Integer.toHexString(tag) + " tag length " + tagLength + + "\nfrequency: " + frequency ; + } + + /** + * Sorts nodes in ascending order by frequency. + */ + static class FrequencyComparator implements Comparator { + public final int compare(Object o1, Object o2) { + return ((HuffmanNode)o1).frequency - ((HuffmanNode)o2).frequency ; + } + } + + /** + * Sorts nodes in descending order by tag bit length. + */ + static class TagLengthComparator implements Comparator { + public final int compare(Object o1, Object o2) { + return ((HuffmanNode)o2).tagLength - ((HuffmanNode)o1).tagLength ; + } + } + + static FrequencyComparator frequencyComparator = new FrequencyComparator() ; + static TagLengthComparator tagLengthComparator = new TagLengthComparator() ; +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java b/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java new file mode 100644 index 0000000..89c4a0b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/HuffmanTable.java @@ -0,0 +1,477 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import java.util.* ; + +/** + * This class maintains a map from compression stream elements (tokens) onto + * HuffmanNode objects. A HuffmanNode contains a tag describing the + * associated token's data length, right shift value, and absolute/relative + * status.

+ * + * The tags are computed using Huffman's algorithm to build a binary tree with + * a minimal total weighted path length. The frequency of each token is + * used as its node's weight when building the tree. The path length from the + * root to the token's node then indicates the bit length that should be used + * for that token's tag in order to minimize the total size of the compressed + * stream. + */ +class HuffmanTable { + private static final int MAX_TAG_LENGTH = 6 ; + + private HuffmanNode positions[] ; + private HuffmanNode normals[] ; + private HuffmanNode colors[] ; + + /** + * Create a new HuffmanTable with entries for all possible position, + * normal, and color tokens. + */ + HuffmanTable() { + // + // Position and color components can have data lengths up to 16 + // bits, with right shifts up to 15 bits. The position and color + // lookup tables are therefore 2*17*16=544 entries in length to + // account for all possible combinations of data lengths, shifts, + // and relative or absolute status. + // + colors = new HuffmanNode[544] ; + positions = new HuffmanNode[544] ; + + // + // Delta normals can have uv components up to 7 bits in length with + // right shifts up to 6 bits. Absolute normals can have uv components + // up to 6 bits in length with right shifts up to 5 bits. The normal + // lookup table is therefore 2*8*7=112 entries in length. + // + normals = new HuffmanNode[112] ; + } + + private final int getPositionIndex(int len, int shift, boolean absolute) { + return (absolute? 1:0)*272 + len*16 + shift ; + } + + private final int getNormalIndex(int length, int shift, boolean absolute) { + return (absolute? 1:0)*56 + length*7 + shift ; + } + + private final int getColorIndex(int length, int shift, boolean absolute) { + return getPositionIndex(length, shift, absolute) ; + } + + + /** + * Add a position entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each X, Y, and Z component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous vertex in the compression stream + */ + void addPositionEntry(int length, int shift, boolean absolute) { + addEntry(positions, getPositionIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the position entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each X, Y, and Z component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous vertex in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getPositionEntry(int length, int shift, boolean absolute) { + return getEntry(positions, getPositionIndex(length, shift, absolute)) ; + } + + /** + * Add a color entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each R, G, B, and A component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous color in the compression stream + */ + void addColorEntry(int length, int shift, boolean absolute) { + addEntry(colors, getColorIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the color entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each R, G, B, and A component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous color in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getColorEntry(int length, int shift, boolean absolute) { + return getEntry(colors, getColorIndex(length, shift, absolute)) ; + } + + /** + * Add a normal entry with the given length, shift, and absolute + * status. + * + * @param length number of bits in each U and V component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous normal in the compression stream + */ + void addNormalEntry(int length, int shift, boolean absolute) { + addEntry(normals, getNormalIndex(length, shift, absolute), + length, shift, absolute) ; + } + + /** + * Get the normal entry associated with the specified length, shift, and + * absolute status. This will contain a tag indicating the actual + * encoding to be used in the compression command stream, not necessarily + * the same as the original length and shift with which the the entry was + * created. + * + * @param length number of bits in each U and V component + * @param shift number of trailing zeros in each component + * @param absolute if false, value represented is a delta from the + * previous normal in the compression stream + * @return HuffmanNode mapped to the specified parameters + */ + HuffmanNode getNormalEntry(int length, int shift, boolean absolute) { + return getEntry(normals, getNormalIndex(length, shift, absolute)) ; + } + + + private void addEntry(HuffmanNode table[], int index, + int length, int shift, boolean absolute) { + + if (table[index] == null) + table[index] = new HuffmanNode(length, shift, absolute) ; + + else if (table[index].cleared()) + table[index].set(length, shift, absolute) ; + + table[index].addCount() ; + } + + private HuffmanNode getEntry(HuffmanNode table[], int index) { + HuffmanNode t = table[index] ; + + while (t.merged()) + t = t.getMergeNode() ; + + return t ; + } + + private void getEntries(HuffmanNode table[], Collection c) { + for (int i = 0 ; i < table.length ; i++) + if (table[i] != null && !table[i].cleared() && + table[i].hasCount() && !table[i].merged()) + c.add(table[i]) ; + } + + + /** + * Clear this HuffmanTable instance. + */ + void clear() { + for (int i = 0 ; i < positions.length ; i++) + if (positions[i] != null) + positions[i].clear() ; + + for (int i = 0 ; i < colors.length ; i++) + if (colors[i] != null) + colors[i].clear() ; + + for (int i = 0 ; i < normals.length ; i++) + if (normals[i] != null) + normals[i].clear() ; + } + + /** + * Compute optimized tags for each position, color, and normal entry. + */ + void computeTags() { + LinkedList nodeList = new LinkedList() ; + getEntries(positions, nodeList) ; + computeTags(nodeList, 3) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + computeTags(nodeList, 3) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + computeTags(nodeList, 2) ; + } + + // + // Compute tags for a list of Huffman tokens. + // + private void computeTags(LinkedList nodes, int minComponentCount) { + HuffmanNode node0, node1, node2 ; + + // Return if there's nothing to do. + if (nodes.isEmpty()) + return ; + + while (true) { + // Sort the nodes in ascending order by frequency. + Collections.sort(nodes, HuffmanNode.frequencyComparator) ; + + // Apply Huffman's algorithm to construct a binary tree with a + // minimum total weighted path length. + node0 = (HuffmanNode)nodes.removeFirst() ; + while (nodes.size() > 0) { + node1 = (HuffmanNode)nodes.removeFirst() ; + node2 = new HuffmanNode() ; + + node2.addChildren(node0, node1) ; + addNodeInOrder(nodes, node2, HuffmanNode.frequencyComparator) ; + + node0 = (HuffmanNode)nodes.removeFirst() ; + } + + // node0 is the root of the resulting binary tree. Traverse it + // assigning tags and lengths to the leaf nodes. The leaves are + // collected into the now empty node list. + node0.collectLeaves(0, 0, nodes) ; + + // Sort the nodes in descending order by tag length. + Collections.sort(nodes, HuffmanNode.tagLengthComparator) ; + + // Check for tag length overrun. + if (((HuffmanNode)nodes.getFirst()).tagLength > MAX_TAG_LENGTH) { + // Tokens need to be merged and the tree rebuilt with the new + // combined frequencies. + merge(nodes) ; + + } else { + // Increase tag length + data length if they're too small. + expand(nodes, minComponentCount) ; + break ; + } + } + } + + // + // Merge a token with a long tag into some other token. The merged token + // will be removed from the list along with any duplicate node the merge + // created, reducing the size of the list by 1 or 2 elements until only + // unmergeable tokens are left. + // + private void merge(LinkedList nodes) { + ListIterator i = nodes.listIterator(0) ; + HuffmanNode node0, node1, node2 ; + int index = 0 ; + + while (i.hasNext()) { + // Get the node with the longest possibly mergeable tag. + node0 = (HuffmanNode)i.next() ; + if (node0.unmergeable()) continue ; + + // Try to find a node that can be merged with node0. This is any + // node that matches its absolute/relative status. + i.remove() ; + while (i.hasNext()) { + node1 = (HuffmanNode)i.next() ; + if (node0.mergeInto(node1)) { + // Search for a duplicate of the possibly modified node1 + // and merge into it so that node weights remain valid. + // If a duplicate exists it must be further in the list, + // otherwise node0 would have merged into it. + i.remove() ; + while (i.hasNext()) { + node2 = (HuffmanNode)i.next() ; + if (node1.tokenEquals(node2)) { + node1.mergeInto(node2) ; + return ; + } + } + // node1 has no duplicate, so return it to the list. + i.add(node1) ; + return ; + } + } + + // node0 can't be merged with any other node; it must be the only + // relative or absolute node in the list. Mark it as unmergeable + // to avoid unnecessary searches on subsequent calls to merge() + // and return it to the list. + node0.setUnmergeable() ; + i.add(node0) ; + + // Restart the iteration. + i = nodes.listIterator(0) ; + } + } + + // + // Empty bits within a compression command header are not allowed. If + // the tag length plus the total data length is less than 6 bits then + // the token's length must be increased. + // + private void expand(LinkedList nodes, int minComponentCount) { + Iterator i = nodes.iterator() ; + + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + + while (n.tagLength + + (minComponentCount * (n.dataLength - n.shift)) < 6) { + + n.incrementLength() ; + } + } + } + + // + // Insert a node into the correct place in a sorted list of nodes. + // + private void addNodeInOrder(LinkedList l, HuffmanNode node, Comparator c) { + ListIterator i = l.listIterator(0) ; + + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + if (c.compare(n, node) > 0) { + n = (HuffmanNode)i.previous() ; + break ; + } + } + i.add(node) ; + } + + /** + * Create compression stream commands for decompressors to use to set up + * their decompression tables. + * + * @param output CommandStream which receives the compression commands + */ + void outputCommands(CommandStream output) { + LinkedList nodeList = new LinkedList() ; + getEntries(positions, nodeList) ; + outputCommands(nodeList, output, CommandStream.POSITION_TABLE) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + outputCommands(nodeList, output, CommandStream.COLOR_TABLE) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + outputCommands(nodeList, output, CommandStream.NORMAL_TABLE) ; + } + + // + // Output a setTable command for each unique token. + // + private void outputCommands(Collection nodes, + CommandStream output, int tableId) { + + Iterator i = nodes.iterator() ; + while (i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + int addressRange = (1 << n.tagLength) | n.tag ; + int dataLength = (n.dataLength == 16? 0 : n.dataLength) ; + + int command = + CommandStream.SET_TABLE | (tableId << 1) | (addressRange >> 6) ; + + long body = + ((addressRange & 0x3f) << 9) | (dataLength << 5) | + (n.absolute? 0x10 : 0) | n.shift ; + + output.addCommand(command, 8, body, 15) ; + } + } + + /** + * Print a collection of HuffmanNode objects to standard out. + * + * @param header descriptive string + * @param nodes Collection of HuffmanNode objects to print + */ + void print(String header, Collection nodes) { + System.out.println(header + "\nentries: " + nodes.size() + "\n") ; + + Iterator i = nodes.iterator() ; + while(i.hasNext()) { + HuffmanNode n = (HuffmanNode)i.next() ; + System.out.println(n.toString() + "\n") ; + } + } + + /** + * Print the contents of this instance to standard out. + */ + void print() { + LinkedList nodeList = new LinkedList() ; + + getEntries(positions, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\nposition tokens and tags", nodeList) ; + + nodeList.clear() ; + getEntries(colors, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\ncolor tokens and tags", nodeList) ; + + nodeList.clear() ; + getEntries(normals, nodeList) ; + Collections.sort(nodeList, HuffmanNode.frequencyComparator) ; + print("\nnormal tokens and tags", nodeList) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java b/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java new file mode 100644 index 0000000..722d9f2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/compression/MeshBuffer.java @@ -0,0 +1,238 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.compression ; +import javax.vecmath.* ; + +/** + * This class mirrors the vertex mesh buffer stack supported by the geometry + * compression semantics. + */ +class MeshBuffer { + // + // The fixed-length mesh buffer stack is represented by circular buffers. + // Three stack representations are provided: vertices, positions, and + // indices. + // + // The vertex representation stores references to CompressionStreamVertex + // objects. The position representation stores references to Point3f, + // Vector3f, Color3f, and Color4f objects, while the index representation + // stores indices into externally maintained arrays of those objects. All + // these representations may be used independently and all provide access + // to the stored references via a mesh buffer index. + // + // In addition, the position and index representations provide lookup + // mechanisms to check if positions or indices exist in the mesh buffer + // and return their mesh buffer indices if they do. This is used to + // implement a limited meshing algorithm which reduces the number of + // vertices when non-stripped abutting facets are added to a compression + // stream. + // + static final int NOT_FOUND = -1 ; + + private static final int SIZE = 16 ; + private static final int NAN_HASH = + new Point3f(Float.NaN, Float.NaN, Float.NaN).hashCode() ; + + private int topIndex = SIZE - 1 ; + private int positionIndices[] = new int[SIZE] ; + private int normalIndices[] = new int[SIZE] ; + private int colorIndices[] = new int[SIZE] ; + + private int topPosition = SIZE - 1 ; + private int positionHashCodes[] = new int[SIZE] ; + private Point3f positions[] = new Point3f[SIZE] ; + private Vector3f normals[] = new Vector3f[SIZE] ; + private Color3f colors3[] = new Color3f[SIZE] ; + private Color4f colors4[] = new Color4f[SIZE] ; + + private int topVertex = SIZE - 1 ; + private CompressionStreamVertex vertices[] = + new CompressionStreamVertex[SIZE] ; + + MeshBuffer() { + for (int i = 0 ; i < SIZE ; i++) { + positionHashCodes[i] = NAN_HASH ; + + positionIndices[i] = NOT_FOUND ; + normalIndices[i] = NOT_FOUND ; + colorIndices[i] = NOT_FOUND ; + } + } + + private static int nextTop(int top) { + // The stack top references an element in the fixed-length backing + // array in which the stack is stored. Stack elements below it have + // decreasing indices into the backing array until element 0, at which + // point the indices wrap to the end of the backing array and back to + // the top. + // + // A push is accomplished by incrementing the stack top in a circular + // buffer and storing the data into the new stack element it + // references. The bottom of the stack is the element with the next + // higher index from the top in the backing array, and is overwritten + // with each new push. + return (top + 1) % SIZE ; + } + + private static int flipOffset(int top, int offset) { + // Flips an offset relative to the beginning of the backing array to + // an offset from the top of the stack. Also works in reverse, from + // an offset from the top of the stack to an offset from the beginning + // of the backing array. + if (offset > top) offset -= SIZE ; + return top - offset ; + } + + // + // Mesh buffer vertex stack. This is currently only used for vertex + // lookup during the quantization pass in order to compute delta values; + // no mesh reference lookup is necessary. + // + void push(CompressionStreamVertex v) { + topVertex = nextTop(topVertex) ; + vertices[topVertex] = v ; + } + + CompressionStreamVertex getVertex(int meshReference) { + return vertices[flipOffset(topVertex, meshReference)] ; + } + + + // + // Mesh buffer index stack and index reference lookup support. + // + void push(int positionIndex, int normalIndex) { + topIndex = nextTop(topIndex) ; + + positionIndices[topIndex] = positionIndex ; + normalIndices[topIndex] = normalIndex ; + } + + void push(int positionIndex, int colorIndex, int normalIndex) { + push(positionIndex, normalIndex) ; + colorIndices[topIndex] = colorIndex ; + } + + int getMeshReference(int positionIndex) { + int index ; + for (index = 0 ; index < SIZE ; index++) + if (positionIndices[index] == positionIndex) + break ; + + if (index == SIZE) return NOT_FOUND ; + return flipOffset(topIndex, index) ; + } + + int getPositionIndex(int meshReference) { + return positionIndices[flipOffset(topIndex, meshReference)] ; + } + + int getColorIndex(int meshReference) { + return colorIndices[flipOffset(topIndex, meshReference)] ; + } + + int getNormalIndex(int meshReference) { + return normalIndices[flipOffset(topIndex, meshReference)] ; + } + + + // + // Mesh buffer position stack and position reference lookup support. + // + void push(Point3f position, Vector3f normal) { + topPosition = nextTop(topPosition) ; + + positionHashCodes[topPosition] = position.hashCode() ; + positions[topPosition] = position ; + normals[topPosition] = normal ; + } + + void push(Point3f position, Color3f color, Vector3f normal) { + push(position, normal) ; + colors3[topPosition] = color ; + } + + void push(Point3f position, Color4f color, Vector3f normal) { + push(position, normal) ; + colors4[topPosition] = color ; + } + + void push(Point3f position, Object color, Vector3f normal) { + push(position, normal) ; + if (color instanceof Color3f) + colors3[topPosition] = (Color3f)color ; + else + colors4[topPosition] = (Color4f)color ; + } + + int getMeshReference(Point3f position) { + int index ; + int hashCode = position.hashCode() ; + + for (index = 0 ; index < SIZE ; index++) + if (positionHashCodes[index] == hashCode) + if (positions[index].equals(position)) + break ; + + if (index == SIZE) return NOT_FOUND ; + return flipOffset(topPosition, index) ; + } + + Point3f getPosition(int meshReference) { + return positions[flipOffset(topPosition, meshReference)] ; + } + + Color3f getColor3(int meshReference) { + return colors3[flipOffset(topPosition, meshReference)] ; + } + + Color4f getColor4(int meshReference) { + return colors4[flipOffset(topPosition, meshReference)] ; + } + + Vector3f getNormal(int meshReference) { + return normals[flipOffset(topPosition, meshReference)] ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/BBox.java b/src/classes/share/com/sun/j3d/utils/geometry/BBox.java new file mode 100644 index 0000000..ea54783 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/BBox.java @@ -0,0 +1,128 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +/** + * Bounding Box class for Triangulator. + */ +class BBox { + int imin; /* lexicographically smallest point, determines min-x */ + int imax; /* lexicographically largest point, determines max-x */ + double ymin; /* minimum y-coordinate */ + double ymax; /* maximum y-coordinate */ + + /** + * This constructor computes the bounding box of a line segment whose end + * points i, j are sorted according to x-coordinates. + */ + BBox(Triangulator triRef, int i, int j) { + // assert(InPointsList(i)); + // assert(InPointsList(j)); + + imin = Math.min(i, j); + imax = Math.max(i, j); + ymin = Math.min(triRef.points[imin].y, triRef.points[imax].y); + ymax = Math.max(triRef.points[imin].y, triRef.points[imax].y); + } + + + boolean pntInBBox(Triangulator triRef, int i) { + return (((imax < i) ? false : + ((imin > i) ? false : + ((ymax < triRef.points[i].y) ? false : + ((ymin > triRef.points[i].y) ? false : true))))); + } + + + + boolean BBoxOverlap(BBox bb) { + return (((imax < (bb).imin) ? false : + ((imin > (bb).imax) ? false : + ((ymax < (bb).ymin) ? false : + ((ymin > (bb).ymax) ? false : true))))); + } + + boolean BBoxContained(BBox bb) { + return ((imin <= (bb).imin) && (imax >= (bb).imax) && + (ymin <= (bb).ymin) && (ymax >= (bb).ymax)); + } + + + boolean BBoxIdenticalLeaf(BBox bb) { + return ((imin == (bb).imin) && (imax == (bb).imax)); + } + + + void BBoxUnion(BBox bb1, BBox bb3) { + (bb3).imin = Math.min(imin, (bb1).imin); + (bb3).imax = Math.max(imax, (bb1).imax); + (bb3).ymin = Math.min(ymin, (bb1).ymin); + (bb3).ymax = Math.max(ymax, (bb1).ymax); + } + + + double BBoxArea(Triangulator triRef) { + return (triRef.points[imax].x - triRef.points[imin].x) * (ymax - ymin); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Basic.java b/src/classes/share/com/sun/j3d/utils/geometry/Basic.java new file mode 100644 index 0000000..ed23455 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Basic.java @@ -0,0 +1,168 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Basic { + + static final double D_RND_MAX = 2147483647.0; + + + static double detExp(double u_x, double u_y, double u_z, + double v_x, double v_y, double v_z, + double w_x, double w_y, double w_z) { + + return ((u_x) * ((v_y) * (w_z) - (v_z) * (w_y)) - + (u_y) * ((v_x) * (w_z) - (v_z) * (w_x)) + + (u_z) * ((v_x) * (w_y) - (v_y) * (w_x))); + } + + + static double det3D(Tuple3f u, Tuple3f v, Tuple3f w) { + return ((u).x * ((v).y * (w).z - (v).z * (w).y) - + (u).y * ((v).x * (w).z - (v).z * (w).x) + + (u).z * ((v).x * (w).y - (v).y * (w).x)); + } + + + static double det2D(Tuple2f u, Tuple2f v, Tuple2f w) { + return (((u).x - (v).x) * ((v).y - (w).y) + ((v).y - (u).y) * ((v).x - (w).x)); + } + + + static double length2(Tuple3f u) { + return (((u).x * (u).x) + ((u).y * (u).y) + ((u).z * (u).z)); + } + + static double lengthL1(Tuple3f u) { + return (Math.abs((u).x) + Math.abs((u).y) + Math.abs((u).z)); + } + + static double lengthL2(Tuple3f u) { + return Math.sqrt(((u).x * (u).x) + ((u).y * (u).y) + ((u).z * (u).z)); + } + + + static double dotProduct(Tuple3f u, Tuple3f v) { + return (((u).x * (v).x) + ((u).y * (v).y) + ((u).z * (v).z)); + } + + + static double dotProduct2D(Tuple2f u, Tuple2f v) { + return (((u).x * (v).x) + ((u).y * (v).y)); + } + + + static void vectorProduct(Tuple3f p, Tuple3f q, Tuple3f r) { + (r).x = (p).y * (q).z - (q).y * (p).z; + (r).y = (q).x * (p).z - (p).x * (q).z; + (r).z = (p).x * (q).y - (q).x * (p).y; + } + + + static void vectorAdd( Tuple3f p, Tuple3f q, Tuple3f r) { + (r).x = (p).x + (q).x; + (r).y = (p).y + (q).y; + (r).z = (p).z + (q).z; + } + + static void vectorSub( Tuple3f p, Tuple3f q, Tuple3f r) { + (r).x = (p).x - (q).x; + (r).y = (p).y - (q).y; + (r).z = (p).z - (q).z; + } + + + static void vectorAdd2D( Tuple2f p, Tuple2f q, Tuple2f r) { + (r).x = (p).x + (q).x; + (r).y = (p).y + (q).y; + } + + + static void vectorSub2D( Tuple2f p, Tuple2f q, Tuple2f r) { + (r).x = (p).x - (q).x; + (r).y = (p).y - (q).y; + } + + static void invertVector(Tuple3f p) { + (p).x = -(p).x; + (p).y = -(p).y; + (p).z = -(p).z; + } + + static void divScalar(double scalar, Tuple3f u) { + (u).x /= scalar; + (u).y /= scalar; + (u).z /= scalar; + } + + static void multScalar2D(double scalar, Tuple2f u) { + (u).x *= scalar; + (u).y *= scalar; + } + + + static int signEps(double x, double eps) { + return ((x <= eps) ? ((x < -eps) ? -1 : 0) : 1); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/BottleNeck.java b/src/classes/share/com/sun/j3d/utils/geometry/BottleNeck.java new file mode 100644 index 0000000..6b3c56f --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/BottleNeck.java @@ -0,0 +1,167 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class BottleNeck { + + static boolean checkArea(Triangulator triRef, int ind4, int ind5) { + int ind1, ind2; + int i0, i1, i2; + double area = 0.0, area1 = 0, area2 = 0.0; + + i0 = triRef.fetchData(ind4); + ind1 = triRef.fetchNextData(ind4); + i1 = triRef.fetchData(ind1); + + while (ind1 != ind5) { + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + area =Numerics.stableDet2D(triRef, i0, i1, i2); + area1 += area; + ind1 = ind2; + i1 = i2; + } + + if (Numerics.le(area1, triRef.ZERO)) return false; + + ind1 = triRef.fetchNextData(ind5); + i1 = triRef.fetchData(ind1); + while (ind1 != ind4) { + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + area = Numerics.stableDet2D(triRef, i0, i1, i2); + area2 += area; + ind1 = ind2; + i1 = i2; + } + + if (Numerics.le(area2, triRef.ZERO)) return false; + else return true; + } + + + // Yet another check needed in order to handle degenerate cases! + static boolean checkBottleNeck(Triangulator triRef, + int i1, int i2, int i3, int ind4) { + int ind5; + int i4, i5; + boolean flag; + + i4 = i1; + + ind5 = triRef.fetchPrevData(ind4); + i5 = triRef.fetchData(ind5); + if ((i5 != i2) && (i5 != i3)) { + flag = Numerics.pntInTriangle(triRef, i1, i2, i3, i5); + if (flag) return true; + } + + if (i2 <= i3) { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i2, i3, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i2, i3, i5, i4, -1); + } + else { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i3, i2, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i3, i2, i5, i4, -1); + } + if (flag) return true; + + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + + if ((i5 != i2) && (i5 != i3)) { + flag = Numerics.pntInTriangle(triRef, i1, i2, i3, i5); + if (flag) return true; + } + + if (i2 <= i3) { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i2, i3, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i2, i3, i5, i4, -1); + } + else { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i3, i2, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i3, i2, i5, i4, -1); + } + + if (flag) return true; + + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + while (ind5 != ind4) { + if (i4 == i5) { + if (checkArea(triRef, ind4, ind5)) return true; + } + ind5 = triRef.fetchNextData(ind5); + i5 = triRef.fetchData(ind5); + } + + return false; + } +} + + + + + + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Box.java b/src/classes/share/com/sun/j3d/utils/geometry/Box.java new file mode 100644 index 0000000..a0e40ec --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Box.java @@ -0,0 +1,469 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * Box is a geometry primitive created with a given length, width, and height. + * It is centered at the origin. By default, it lies within the bounding + * box, [-1,-1,-1] and [1,1,1]. + * + * When a texture is applied to a box, it is map CCW like on a Cylinder. + * A texture is mapped CCW from the back of the + * body. The top and bottom faces are mapped such that the texture appears + * front facing when the faces are rotated 90 toward the viewer. + *

+ * By default all primitives with the same parameters share their + * geometry (e.g., you can have 50 shperes in your scene, but the + * geometry is stored only once). A change to one primitive will + * effect all shared nodes. Another implication of this + * implementation is that the capabilities of the geometry are shared, + * and once one of the shared nodes is live, the capabilities cannot + * be set. Use the GEOMETRY_NOT_SHARED flag if you do not wish to + * share geometry among primitives with the same parameters. + */ + +public class Box extends Primitive { + + /** + * Used to designate the front side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int FRONT = 0; + + /** + * Used to designate the back side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int BACK = 1; + + /** + * Used to designate the right side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int RIGHT = 2; + + /** + * Used to designate the left side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int LEFT = 3; + + /** + * Used to designate the top side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int TOP = 4; + + /** + * Used to designate the bottom side of the box when using + * getShape(). + * + * @see Box#getShape + */ + public static final int BOTTOM = 5; + + float xDim, yDim, zDim; + + int numTexUnit = 1; + + /** + * Constructs a default box of 1.0 in all dimensions. + * Normals are generated by default, texture coordinates are not. + */ + + public Box() + { + this(1.0f, 1.0f, 1.0f, GENERATE_NORMALS, null); + } + + /** + * Constructs a box of a given dimension and appearance. + * Normals are generated by default, texture coordinates are not. + * + * @param xdim X-dimension size. + * @param ydim Y-dimension size. + * @param zdim Z-dimension size. + * @param ap Appearance + */ + + public Box(float xdim, float ydim, float zdim, Appearance ap) + { + this(xdim, ydim, zdim, GENERATE_NORMALS, ap); + } + + /** + * Constructs a box of a given dimension, flags, and appearance. + * + * @param xdim X-dimension size. + * @param ydim Y-dimension size. + * @param zdim Z-dimension size. + * @param primflags primitive flags. + * @param ap Appearance + */ + + public Box(float xdim, float ydim, float zdim, int primflags, + Appearance ap, int numTexUnit) { + int i; + double sign; + + xDim = xdim; + yDim = ydim; + zDim = zdim; + flags = primflags; + numTexUnit = numTexUnit; + + //Depending on whether normal inward bit is set. + if ((flags & GENERATE_NORMALS_INWARD) != 0) + sign = -1.0; + else sign = 1.0; + +// TransformGroup objTrans = new TransformGroup(); +// objTrans.setCapability(ALLOW_CHILDREN_READ); +// this.addChild(objTrans); + + Shape3D shape[] = new Shape3D[6]; + + GeomBuffer cache = null; + + for (i = FRONT; i <= BOTTOM; i++){ + + cache = getCachedGeometry(Primitive.BOX, xdim, ydim, zdim, i, i, + primflags); + if (cache != null) { +// System.out.println("using cached geometry i = " + i); + shape[i] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + + GeomBuffer gbuf = new GeomBuffer(4, numTexUnit); + + gbuf.begin(GeomBuffer.QUAD_STRIP); + for (int j = 0; j < 2; j++){ + gbuf.normal3d( (double) normals[i].x*sign, + (double) normals[i].y*sign, + (double) normals[i].z*sign); + gbuf.texCoord2d(tcoords[i*8 + j*2], tcoords[i*8 + j*2 + 1]); + gbuf.vertex3d( (double) verts[i*12 + j*3]*xdim, + (double) verts[i*12+ j*3 + 1]*ydim, + (double) verts[i*12+ j*3 + 2]*zdim ); + } + for (int j = 3; j > 1; j--){ + gbuf.normal3d( (double) normals[i].x*sign, + (double) normals[i].y*sign, + (double) normals[i].z*sign); + gbuf.texCoord2d(tcoords[i*8 + j*2], tcoords[i*8 + j*2 + 1]); + gbuf.vertex3d( (double) verts[i*12 + j*3]*xdim, + (double) verts[i*12+ j*3 + 1]*ydim, + (double) verts[i*12+ j*3 + 2]*zdim ); + } + gbuf.end(); + shape[i] = new Shape3D(gbuf.getGeom(flags)); + numVerts = gbuf.getNumVerts(); + numTris = gbuf.getNumTris(); + + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.BOX, xdim, ydim, zdim, i, i, + primflags, gbuf); + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[i]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[i]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[i]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + +// objTrans.addChild(shape[i]); + this.addChild(shape[i]); + } + + if (ap == null){ + setAppearance(); + } + else setAppearance(ap); + } + + public Box(float xdim, float ydim, float zdim, int primflags, + Appearance ap) { + this(xdim, ydim, zdim, primflags, ap, 1); + } + + /** + * Gets one of the faces (Shape3D) from the box that contains the + * geometry and appearance. This allows users to modify the + * appearance or geometry of individual parts. + * @param partId The part to return. + * @return The Shape3D object associated with the partID. If an + * invalid partId is passed in, null is returned. + */ + + public Shape3D getShape(int partId) { + if ((partId >= FRONT) && (partId <= BOTTOM)) +// return (Shape3D)(((Group)getChild(0)).getChild(partId)); + return (Shape3D)getChild(partId); + return null; + } + + /** + * Sets appearance of the box. This will set each face of the + * box to the same appearance. To set each face's appearance + * separately, use getShape(partId) to get the + * individual shape and call shape.setAppearance(ap). + */ + + public void setAppearance(Appearance ap){ +// ((Shape3D)((Group)getChild(0)).getChild(TOP)).setAppearance(ap); +// ((Shape3D)((Group)getChild(0)).getChild(LEFT)).setAppearance(ap); +// ((Shape3D)((Group)getChild(0)).getChild(RIGHT)).setAppearance(ap); +// ((Shape3D)((Group)getChild(0)).getChild(FRONT)).setAppearance(ap); +// ((Shape3D)((Group)getChild(0)).getChild(BACK)).setAppearance(ap); +// ((Shape3D)((Group)getChild(0)).getChild(BOTTOM)).setAppearance(ap); + ((Shape3D)getChild(TOP)).setAppearance(ap); + ((Shape3D)getChild(LEFT)).setAppearance(ap); + ((Shape3D)getChild(RIGHT)).setAppearance(ap); + ((Shape3D)getChild(FRONT)).setAppearance(ap); + ((Shape3D)getChild(BACK)).setAppearance(ap); + ((Shape3D)getChild(BOTTOM)).setAppearance(ap); + } + + /** + * Gets the appearance of the specified part of the box. + * + * @param partId identifier for a given subpart of the box + * + * @return The appearance object associated with the partID. If an + * invalid partId is passed in, null is returned. + * + * @since Java 3D 1.2.1 + */ + public Appearance getAppearance(int partId) { + if (partId > BOTTOM || partId < FRONT) return null; + return getShape(partId).getAppearance(); + } + + + private static final float[] verts = { + // front face + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, +-1.0f, 1.0f, 1.0f, +-1.0f, -1.0f, 1.0f, + // back face +-1.0f, -1.0f, -1.0f, +-1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + // right face + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + // left face +-1.0f, -1.0f, 1.0f, +-1.0f, 1.0f, 1.0f, +-1.0f, 1.0f, -1.0f, +-1.0f, -1.0f, -1.0f, + // top face + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, +-1.0f, 1.0f, -1.0f, +-1.0f, 1.0f, 1.0f, + // bottom face +-1.0f, -1.0f, 1.0f, +-1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + }; + + private static final double[] tcoords = { + // front + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // back + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + //right + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // left + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // top + 1.0, 0.0, + 1.0, 1.0, + 0.0, 1.0, + 0.0, 0.0, + // bottom + 0.0, 1.0, + 0.0, 0.0, + 1.0, 0.0, + 1.0, 1.0 + }; + + + private static final Vector3f[] normals = { + new Vector3f( 0.0f, 0.0f, 1.0f), // front face + new Vector3f( 0.0f, 0.0f, -1.0f), // back face + new Vector3f( 1.0f, 0.0f, 0.0f), // right face + new Vector3f(-1.0f, 0.0f, 0.0f), // left face + new Vector3f( 0.0f, 1.0f, 0.0f), // top face + new Vector3f( 0.0f, -1.0f, 0.0f), // bottom face + }; + + + /** + * Used to create a new instance of the node. This routine is called + * by cloneTree to duplicate the current node. + * cloneNode should be overridden by any user subclassed + * objects. All subclasses must have their cloneNode + * method consist of the following lines: + *

+     *     public Node cloneNode(boolean forceDuplicate) {
+     *         UserSubClass usc = new UserSubClass();
+     *         usc.duplicateNode(this, forceDuplicate);
+     *         return usc;
+     *     }
+     * 
+ * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Box b = new Box(xDim, yDim, zDim, flags, getAppearance()); + b.duplicateNode(this, forceDuplicate); + return b; + } + + /** + * Copies all node information from originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method. + *

+ * For any NodeComponent objects + * contained by the object being duplicated, each NodeComponent + * object's duplicateOnCloneTree value is used to determine + * whether the NodeComponent should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * forceDuplicate parameter in the cloneTree + * method to true. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + } + + /** + * Returns the X-dimension size of the Box + * + * @since Java 3D 1.2.1 + */ + public float getXdimension() { + return xDim; + } + + /** + * Returns the Y-dimension size of the Box + * + * @since Java 3D 1.2.1 + */ + public float getYdimension() { + return yDim; + } + + /** + * Returns the Z-dimension size of the Box + * + * @since Java 3D 1.2.1 + */ + public float getZdimension() { + return zDim; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Bridge.java b/src/classes/share/com/sun/j3d/utils/geometry/Bridge.java new file mode 100644 index 0000000..8dea58c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Bridge.java @@ -0,0 +1,398 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Bridge { + + static void constructBridges(Triangulator triRef, int loopMin, int loopMax) { + int i, j, numDist, numLeftMost; + + int[] i0 = new int[1]; + int[] ind0 = new int[1]; + int[] i1 = new int[1]; + int[] ind1 = new int[1]; + + int[] iTmp = new int[1]; + int[] indTmp = new int[1]; + + if(triRef.noHashingEdges != true) + System.out.println("Bridge:constructBridges noHashingEdges is false"); + if(loopMax <= loopMin) + System.out.println("Bridge:constructBridges loopMax<=loopMin"); + if(loopMin < 0) + System.out.println("Bridge:constructBridges loopMin<0"); + if(loopMax > triRef.numLoops) + System.out.println("Bridge:constructBridges loopMax>triRef.numLoops"); + + numLeftMost = loopMax - loopMin - 1; + + if (numLeftMost > triRef.maxNumLeftMost) { + triRef.maxNumLeftMost = numLeftMost; + triRef.leftMost = new Left[numLeftMost]; + } + + // For each contour, find the left-most vertex. (we will use the fact + // that the vertices appear in sorted order!) + findLeftMostVertex(triRef, triRef.loops[loopMin], ind0, i0); + j = 0; + for (i = loopMin + 1; i < loopMax; ++i) { + findLeftMostVertex(triRef, triRef.loops[i], indTmp, iTmp); + triRef.leftMost[j] = new Left(); + triRef.leftMost[j].ind = indTmp[0]; + triRef.leftMost[j].index = iTmp[0]; + + ++j; + } + + // sort the inner contours according to their left-most vertex + sortLeft(triRef.leftMost, numLeftMost); + + // construct bridges. every bridge will eminate at the left-most point of + // its corresponding inner loop. + numDist = triRef.numPoints + 2 * triRef.numLoops; + triRef.maxNumDist = numDist; + triRef.distances = new Distance[numDist]; + for (int k = 0; k < triRef.maxNumDist; k++) + triRef.distances[k] = new Distance(); + + + for (j = 0; j < numLeftMost; ++j) { + if (!findBridge(triRef, ind0[0], i0[0], triRef.leftMost[j].index, ind1, i1)) { + // if (verbose) + // fprintf(stderr, "\n\n***** yikes! the loops intersect! *****\n"); + } + if (i1[0] == triRef.leftMost[j].index) + // the left-most node of the hole coincides with a node of the + // boundary + simpleBridge(triRef, ind1[0], triRef.leftMost[j].ind); + else + // two bridge edges need to be inserted + insertBridge(triRef, ind1[0], i1[0], triRef.leftMost[j].ind, + triRef.leftMost[j].index); + } + + } + + + /** + * We try to find a vertex i1 on the loop which contains i such that i1 + * is close to start, and such that i1, start is a valid diagonal. + */ + static boolean findBridge(Triangulator triRef, int ind, int i, int start, + int[] ind1, int[] i1) { + int i0, i2, j, numDist = 0; + int ind0, ind2; + BBox bb; + Distance old[] = null; + boolean convex, coneOk; + + // sort the points according to their distance from start. + ind1[0] = ind; + i1[0] = i; + if (i1[0] == start) return true; + if (numDist >= triRef.maxNumDist) { + // System.out.println("(1) Expanding distances array ..."); + triRef.maxNumDist += triRef.INC_DIST_BK; + old = triRef.distances; + triRef.distances = new Distance[triRef.maxNumDist]; + System.arraycopy(old, 0, triRef.distances, 0, old.length); + for (int k = old.length; k < triRef.maxNumDist; k++) + triRef.distances[k] = new Distance(); + } + + triRef.distances[numDist].dist = Numerics.baseLength(triRef.points[start], + triRef.points[i1[0]]); + triRef.distances[numDist].ind = ind1[0]; + ++numDist; + + + ind1[0] = triRef.fetchNextData(ind1[0]); + i1[0] = triRef.fetchData(ind1[0]); + while (ind1[0] != ind) { + if (i1[0] == start) return true; + if (numDist >= triRef.maxNumDist) { + // System.out.println("(2) Expanding distances array ..."); + triRef.maxNumDist += triRef.INC_DIST_BK; + old = triRef.distances; + triRef.distances = new Distance[triRef.maxNumDist]; + System.arraycopy(old, 0, triRef.distances, 0, old.length); + for (int k = old.length; k < triRef.maxNumDist; k++) + triRef.distances[k] = new Distance(); + } + + triRef.distances[numDist].dist = Numerics.baseLength(triRef.points[start], + triRef.points[i1[0]]); + triRef.distances[numDist].ind = ind1[0]; + ++numDist; + ind1[0] = triRef.fetchNextData(ind1[0]); + i1[0] = triRef.fetchData(ind1[0]); + } + + // qsort(distances, num_dist, sizeof(distance), &d_comp); + sortDistance(triRef.distances, numDist); + + // find a valid diagonal. note that no node with index i1 > start can + // be feasible! + for (j = 0; j < numDist; ++j) { + ind1[0] = triRef.distances[j].ind; + i1[0] = triRef.fetchData(ind1[0]); + if (i1[0] <= start) { + ind0 = triRef.fetchPrevData(ind1[0]); + i0 = triRef.fetchData(ind0); + ind2 = triRef.fetchNextData(ind1[0]); + i2 = triRef.fetchData(ind2); + convex = triRef.getAngle(ind1[0]) > 0; + + coneOk = Numerics.isInCone(triRef, i0, i1[0], i2, start, convex); + if (coneOk) { + bb = new BBox(triRef, i1[0], start); + if (!NoHash.noHashEdgeIntersectionExists(triRef, bb, -1, -1, ind1[0], -1)) + return true; + } + } + } + + // the left-most point of the hole does not lie within the outer + // boundary! what is the best bridge in this case??? I make a + // brute-force decision... perhaps this should be refined during a + // revision of the code... + for (j = 0; j < numDist; ++j) { + ind1[0] = triRef.distances[j].ind; + i1[0] = triRef.fetchData(ind1[0]); + ind0 = triRef.fetchPrevData(ind1[0]); + i0 = triRef.fetchData(ind0); + ind2 = triRef.fetchNextData(ind1[0]); + i2 = triRef.fetchData(ind2); + bb = new BBox(triRef, i1[0], start); + if (!NoHash.noHashEdgeIntersectionExists(triRef, bb, -1, -1, ind1[0], -1)) + return true; + } + + // still no diagonal??? yikes! oh well, this polygon is messed up badly! + ind1[0] = ind; + i1[0] = i; + + return false; + } + + + static void findLeftMostVertex(Triangulator triRef, int ind, int[] leftInd, + int[] leftI) { + int ind1, i1; + + ind1 = ind; + i1 = triRef.fetchData(ind1); + leftInd[0] = ind1; + leftI[0] = i1; + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + while (ind1 != ind) { + if (i1 < leftI[0]) { + leftInd[0] = ind1; + leftI[0] = i1; + } + else if (i1 == leftI[0]) { + if (triRef.getAngle(ind1) < 0) { + leftInd[0] = ind1; + leftI[0] = i1; + } + } + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + } + + } + + static void simpleBridge(Triangulator triRef, int ind1, int ind2) { + int prev, next; + int i1, i2, prv, nxt; + int angle; + + + // change the links + triRef.rotateLinks(ind1, ind2); + + // reset the angles + i1 = triRef.fetchData(ind1); + next = triRef.fetchNextData(ind1); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind1); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i1, nxt, ind1); + triRef.setAngle(ind1, angle); + + i2 = triRef.fetchData(ind2); + next = triRef.fetchNextData(ind2); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind2); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i2, nxt, ind2); + triRef.setAngle(ind2, angle); + + } + + + static void insertBridge(Triangulator triRef, int ind1, int i1, + int ind3, int i3) { + int ind2, ind4, prev, next; + int prv, nxt, angle; + int vcntIndex; + + // duplicate nodes in order to form end points of the bridge edges + ind2 = triRef.makeNode(i1); + triRef.insertAfter(ind1, ind2); + + // Need to get the original data, before setting it. + + vcntIndex = triRef.list[ind1].getCommonIndex(); + + triRef.list[ind2].setCommonIndex(vcntIndex); + + + ind4 = triRef.makeNode(i3); + triRef.insertAfter(ind3, ind4); + + vcntIndex = triRef.list[ind3].getCommonIndex(); + triRef.list[ind4].setCommonIndex(vcntIndex); + + // insert the bridge edges into the boundary loops + triRef.splitSplice(ind1, ind2, ind3, ind4); + + // reset the angles + next = triRef.fetchNextData(ind1); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind1); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i1, nxt, ind1); + triRef.setAngle(ind1, angle); + + next = triRef.fetchNextData(ind2); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind2); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i1, nxt, ind2); + triRef.setAngle(ind2, angle); + + next = triRef.fetchNextData(ind3); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind3); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i3, nxt, ind3); + triRef.setAngle(ind3, angle); + + next = triRef.fetchNextData(ind4); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind4); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i3, nxt, ind4); + triRef.setAngle(ind4, angle); + + } + + + static int l_comp(Left a, Left b) { + if (a.index < b.index) return -1; + else if (a.index > b.index) return 1; + else return 0; + } + + static int d_comp(Distance a, Distance b) { + if (a.dist < b.dist) return -1; + else if (a.dist > b.dist) return 1; + else return 0; + } + + + static void sortLeft(Left[] lefts, int numPts) { + int i,j; + Left swap = new Left(); + + for (i = 0; i < numPts; i++){ + for (j = i + 1; j < numPts; j++){ + if (l_comp(lefts[i], lefts[j]) > 0){ + swap.copy(lefts[i]); + lefts[i].copy(lefts[j]); + lefts[j].copy(swap); + } + } + } + } + + + static void sortDistance(Distance[] distances, int numPts) { + int i,j; + Distance swap = new Distance(); + + for (i = 0; i < numPts; i++){ + for (j = i + 1; j < numPts; j++){ + if (d_comp(distances[i], distances[j]) > 0){ + swap.copy(distances[i]); + distances[i].copy(distances[j]); + distances[j].copy(swap); + } + } + } + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Clean.java b/src/classes/share/com/sun/j3d/utils/geometry/Clean.java new file mode 100644 index 0000000..98b2e88 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Clean.java @@ -0,0 +1,193 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Clean { + + static void initPUnsorted(Triangulator triRef, int number) { + if (number > triRef.maxNumPUnsorted) { + triRef.maxNumPUnsorted = number; + triRef.pUnsorted = new Point2f[triRef.maxNumPUnsorted]; + for(int i = 0; i 0){ + swap.set(points[i]); + points[i].set(points[j]); + points[j].set(swap); + } + } + } + /* + for (i = 0; i < numPts; i++) { + System.out.println("pt " + points[i]); + } + */ + } + + static int findPInd(Point2f sorted[], int numPts, Point2f pnt) { + int i; + + for (i = 0; i < numPts; i++){ + if ((pnt.x == sorted[i].x) && + (pnt.y == sorted[i].y)){ + return i; + } + } + return -1; + } + + static int pComp(Point2f a, Point2f b) { + if (a.x < b.x) + return -1; + else if (a.x > b.x) + return 1; + else { + if (a.y < b.y) + return -1; + else if (a.y > b.y) + return 1; + else + return 0; + } + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/ColorCube.java b/src/classes/share/com/sun/j3d/utils/geometry/ColorCube.java new file mode 100644 index 0000000..a64977b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/ColorCube.java @@ -0,0 +1,175 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import javax.media.j3d.*; + +/** + * Simple color-per-vertex cube with a different color for each face + */ +public class ColorCube extends Shape3D { + private static final float[] verts = { + // front face + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + // back face + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + // right face + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + // left face + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + // top face + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + // bottom face + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + }; + + private static final float[] colors = { + // front face (red) + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + // back face (green) + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + // right face (blue) + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + // left face (yellow) + 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + // top face (magenta) + 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 1.0f, + // bottom face (cyan) + 0.0f, 1.0f, 1.0f, + 0.0f, 1.0f, 1.0f, + 0.0f, 1.0f, 1.0f, + 0.0f, 1.0f, 1.0f, + }; + + double scale; + + /** + * Constructs a color cube with unit scale. The corners of the + * color cube are [-1,-1,-1] and [1,1,1]. + */ + public ColorCube() { + QuadArray cube = new QuadArray(24, QuadArray.COORDINATES | + QuadArray.COLOR_3); + + cube.setCoordinates(0, verts); + cube.setColors(0, colors); + + this.setGeometry(cube); + + scale = 1.0; + } + + + /** + * Constructs a color cube with the specified scale. The corners of the + * color cube are [-scale,-scale,-scale] and [scale,scale,scale]. + * @param scale the scale of the cube + */ + public ColorCube(double scale) { + QuadArray cube = new QuadArray(24, QuadArray.COORDINATES | + QuadArray.COLOR_3); + + float scaledVerts[] = new float[verts.length]; + for (int i = 0; i < verts.length; i++) + scaledVerts[i] = verts[i] * (float)scale; + + cube.setCoordinates(0, scaledVerts); + cube.setColors(0, colors); + + this.setGeometry(cube); + + this.scale = scale; + } + + /** + * @deprecated ColorCube now extends shape so it is no longer necessary + * to call this method. + */ + public Shape3D getShape() { + return this; + } + + /** + * Returns the scale of the Cube + * + * @since Java 3D 1.2.1 + */ + public double getScale() { + return scale; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Cone.java b/src/classes/share/com/sun/j3d/utils/geometry/Cone.java new file mode 100644 index 0000000..5673383 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Cone.java @@ -0,0 +1,428 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * Cone is a geometry primitive defined with a radius and a height. + * It is a capped cone centered at the origin with its central axis + * aligned along the Y-axis. The center of the cone is defined to be + * the center of its bounding box (rather than its centroid). + *

+ * If the GENERATE_TEXTURE_COORDS flag is set, the texture coordinates + * are generated such that the texture gets mapped onto the cone similarly + * to how it gets mapped onto a cylinder, the difference being the top + * cap is nonexistent. + *

+ * By default all primitives with the same parameters share their + * geometry (e.g., you can have 50 shperes in your scene, but the + * geometry is stored only once). A change to one primitive will + * effect all shared nodes. Another implication of this + * implementation is that the capabilities of the geometry are shared, + * and once one of the shared nodes is live, the capabilities cannot + * be set. Use the GEOMETRY_NOT_SHARED flag if you do not wish to + * share geometry among primitives with the same parameters. + */ +public class Cone extends Primitive { + float radius, height; + int xdivisions, ydivisions; + static final int MID_REZ_DIV_X = 15; + static final int MID_REZ_DIV_Y = 1; + + /** + * Designates the body of the cone. Used by getShape. + * + * @see Cone#getShape + */ + public static final int BODY = 0; + + /** + * Designates the end-cap of the cone. Used by getShape. + * + * @see Cone#getShape + */ + public static final int CAP = 1; + + /** + * Constructs a default Cone of radius of 1.0 and height + * of 2.0. Resolution defaults to 15 divisions along X and axis + * and 1 along the Y axis. Normals are generated, texture + * coordinates are not. + */ + public Cone(){ + this(1.0f, 2.0f, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, null); + } + + /** + * + * Constructs a default Cone of a given radius and height. Normals + * are generated, texture coordinates are not. + * @param radius Radius + * @param height Height + */ + public Cone (float radius, float height) + { + this(radius, height, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, null); + } + + /** + * + * Constructs a default cone of a given radius, height, + * and appearance. Normals are generated, texture coordinates are not. + * @param radius Radius + * @param height Height + * @param ap Appearance + * + * @since Java 3D 1.2.1 + */ + public Cone (float radius, float height, Appearance ap) + { + this(radius, height, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, ap); + } + + /** + * + * Constructs a default cone of a given radius, height, + * primitive flags, and appearance. + * @param radius Radius + * @param height Height + * @param primflags Primitive flags + * @param ap Appearance + */ + public Cone (float radius, float height, int primflags, Appearance ap) + { + this(radius, height, primflags, MID_REZ_DIV_X, MID_REZ_DIV_Y, ap); + } + + /** + * Obtains the Shape3D node associated with one of the parts of the + * cone (the body or the cap). This allows users to modify the appearance + * or geometry of individual parts. + * @param partId The part to return (BODY or CAP). + * @return The Shape3D object associated with the partId. If an + * invalid partId is passed in, null is returned. + */ + public Shape3D getShape(int partId){ + if (partId > CAP || partId < BODY) return null; + return (Shape3D)getChild(partId); + } + + + /** + * Sets appearance of the cone. This will set each part of the + * cone (cap & body) to the same appearance. To set each + * part's appearance separately, use getShape(partId) to get the + * individual shape and call shape.setAppearance(ap). + */ + public void setAppearance(Appearance ap){ + ((Shape3D)getChild(BODY)).setAppearance(ap); + ((Shape3D)getChild(CAP)).setAppearance(ap); + } + + /** + * Gets the appearance of the specified part of the cone. + * + * @param partId identifier for a given subpart of the cone + * + * @return The appearance object associated with the partID. If an + * invalid partId is passed in, null is returned. + * + * @since Java 3D 1.2.1 + */ + public Appearance getAppearance(int partId) { + if (partId > CAP || partId < BODY) return null; + return getShape(partId).getAppearance(); + } + + + /** + * Constructs a customized Cone of a given radius, height, flags, + * resolution (X and Y dimensions), and appearance. The + * resolution is defined in terms of number of subdivisions + * along the object's X axis (width) and Y axis (height). More divisions + * lead to finer tesselated objects. + *

+ * If appearance is null, the default white appearance will be used. + * @param radius Radius + * @param height Height + * @param xdivision Number of divisions along X direction. + * @param ydivision Number of divisions along the height of cone. + * @param primflags flags + * @param ap Appearance + */ + + public Cone(float radius, float height, int primflags, + int xdivision, int ydivision, + Appearance ap) + { + super(); + + Shape3D shape[] = new Shape3D[2]; + this.radius = radius; + this.height = height; + xdivisions = xdivision; + ydivisions = ydivision; + flags = primflags; + boolean outside = (flags & GENERATE_NORMALS_INWARD) == 0; + Quadrics q = new Quadrics(); + GeomBuffer gbuf = null; + + GeomBuffer cache = getCachedGeometry(Primitive.CONE, + radius, 0.0f, height, + xdivision, ydivision, primflags); + if (cache != null){ +// System.out.println("using cached geometry"); + shape[BODY] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + // the body of the cone consists of the top of the cone and if + // ydivisions is greater than 1, the body of the cone. + gbuf = q.coneTop((double)(height/2.0 - height/ydivisions), + (double)(radius/ydivisions), height/ydivisions, + xdivisions, 1.0-1.0/(double)ydivisions, + outside); + shape[BODY] = new Shape3D(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.CONE, + radius, 0.0f, height, + xdivision, ydivision, primflags, gbuf); + } + } + + // only need to add a body if the ydivisions is greater than 1 + if (ydivisions > 1) { + cache = getCachedGeometry(Primitive.CONE_DIVISIONS, radius, 0.0f, + height, xdivision, ydivision, primflags); + if (cache != null) { +// System.out.println("using cached divisions"); + shape[BODY].addGeometry(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + gbuf = q.coneBody(-(double)(height/2.0), + (double)(height/2.0-height/ydivisions), + (double)radius, (double)(radius/ydivisions), + xdivisions, ydivisions-1, 1.0/(double)ydivisions, + outside); + shape[BODY].addGeometry(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.CONE_DIVISIONS, radius, 0.0f, height, + xdivision, ydivision, primflags, gbuf); + } + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[BODY]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[BODY]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[BODY]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + + this.addChild(shape[BODY]); + + // Create bottom cap. + cache = getCachedGeometry(Primitive.BOTTOM_DISK, radius, + radius, -height/2.0f, xdivision, xdivision, + primflags); + if (cache != null) { +// System.out.println("using cached bottom"); + shape[CAP] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + gbuf = q.disk((double)radius, xdivision, -(double)height/2.0, + !outside); + shape[CAP] = new Shape3D(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.BOTTOM_DISK, radius, radius, -height/2.0f, + xdivision, xdivision, primflags, gbuf); + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[CAP]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[CAP]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[CAP]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + +// Transform3D t2 = new Transform3D(); + + // Flip it to match up the texture coords. +/* This causes the bottom not to match up for odd xdivision values + objectMat = new Matrix4d(); + objectMat.setIdentity(); + rotMat = new Matrix4d(); + rotMat.setIdentity(); + rotMat.rotZ(Math.PI); + objectMat.mul(objectMat, rotMat); + t2.set(objectMat); +*/ + + this.addChild(shape[CAP]); + + if (ap == null){ + setAppearance(); + } + else setAppearance(ap); + } + + /** + * Used to create a new instance of the node. This routine is called + * by cloneTree to duplicate the current node. + * cloneNode should be overridden by any user subclassed + * objects. All subclasses must have their cloneNode + * method consist of the following lines: + *

+     *     public Node cloneNode(boolean forceDuplicate) {
+     *         UserSubClass usc = new UserSubClass();
+     *         usc.duplicateNode(this, forceDuplicate);
+     *         return usc;
+     *     }
+     * 
+ * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Cone c = new Cone(radius, height, flags, xdivisions, + ydivisions, getAppearance()); + c.duplicateNode(this, forceDuplicate); + return c; + } + + /** + * Copies all node information from originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method. + *

+ * For any NodeComponent objects + * contained by the object being duplicated, each NodeComponent + * object's duplicateOnCloneTree value is used to determine + * whether the NodeComponent should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * forceDuplicate parameter in the cloneTree + * method to true. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + } + + /** + * Returns the radius of the cone + * + * @since Java 3D 1.2.1 + */ + public float getRadius() { + return radius; + } + + /** + * Returns the height of the cone + * + * @since Java 3D 1.2.1 + */ + public float getHeight() { + return height; + } + + /** + * Returns the number divisions along the X direction + * + * @since Java 3D 1.2.1 + */ + public int getXdivisions() { + return xdivisions; + } + + /** + * Returns the number of divisions along the height of the cone + * + * @since Java 3D 1.2.1 + */ + public int getYdivisions() { + return ydivisions; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Cylinder.java b/src/classes/share/com/sun/j3d/utils/geometry/Cylinder.java new file mode 100644 index 0000000..64e4ab1 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Cylinder.java @@ -0,0 +1,421 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * Cylinder is a geometry primitive defined with a radius and a height. + * It is a capped cylinder centered at the origin with its central axis + * aligned along the Y-axis. + *

+ * When a texture is applied to a cylinder, the texture is applied to the + * caps and the body different. A texture is mapped CCW from the back of the + * body. The top and bottom caps are mapped such that the texture appears + * front facing when the caps are rotated 90 degrees toward the viewer. + *

+ * By default all primitives with the same parameters share their + * geometry (e.g., you can have 50 shperes in your scene, but the + * geometry is stored only once). A change to one primitive will + * effect all shared nodes. Another implication of this + * implementation is that the capabilities of the geometry are shared, + * and once one of the shared nodes is live, the capabilities cannot + * be set. Use the GEOMETRY_NOT_SHARED flag if you do not wish to + * share geometry among primitives with the same parameters. + */ + +public class Cylinder extends Primitive{ + float radius, height; + int xdivisions, ydivisions; + + static final int MID_REZ_DIV_X = 15; + static final int MID_REZ_DIV_Y = 1; + + /** + * Designates the body of the cylinder. Used by getShape. + * + * @see Cylinder#getShape + */ + public static final int BODY = 0; + + /** + * Designates the top end-cap of the cylinder. + * Used by getShape. + * + * @see Cylinder#getShape + */ + public static final int TOP = 1; + + /** + * Designates the bottom end-cap of the cylinder. + * Used by getShape. + * + * @see Cylinder#getShape + */ + public static final int BOTTOM = 2; + + /** + * Constructs a default cylinder of radius of 1.0 and height + * of 2.0. Normals are generated by default, texture + * coordinates are not. Resolution defaults to 15 divisions + * along X axis and 1 along the Y axis. + */ + public Cylinder() { + this(1.0f, 2.0f, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, null); + } + + /** + * Constructs a default cylinder of a given radius and height. + * Normals are generated by default, texture coordinates are not. + * @param radius Radius + * @param height Height + */ + public Cylinder (float radius, float height) { + this(radius, height, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, + null); + } + + /** + * Constructs a default cylinder of a given radius, height, and + * appearance. Normals are generated by default, texture + * coordinates are not. + * @param radius Radius + * @param height Height + * @param ap Appearance + */ + public Cylinder (float radius, float height, Appearance ap) + { + this(radius, height, GENERATE_NORMALS, MID_REZ_DIV_X, MID_REZ_DIV_Y, + ap); + } + + /** + * + * Constructs a default cylinder of a given radius, height, + * primitive flags and appearance. + * @param radius Radius + * @param height Height + * @param primflags Flags + * @param ap Appearance + */ + public Cylinder (float radius, float height, int primflags, Appearance ap) + { + this(radius, height, primflags, MID_REZ_DIV_X, MID_REZ_DIV_Y, ap); + } + + /** + * Obtains the Shape3D node associated with a given part of the cylinder. + * This allows users to modify the appearance or geometry + * of individual parts. + * @param partId The part to return (BODY, TOP, or BOTTOM). + * @return The Shape3D object associated with the partID. If an + * invalid partId is passed in, null is returned. + */ + public Shape3D getShape(int partId){ + if (partId > BOTTOM || partId < BODY) return null; + return (Shape3D)getChild(partId); + } + + /** Sets appearance of the cylinder. This will set each part of the + * cylinder (TOP,BOTTOM,BODY) to the same appearance. To set each + * part's appearance separately, use getShape(partId) to get the + * individual shape and call shape.setAppearance(ap). + */ + public void setAppearance(Appearance ap) { + ((Shape3D)getChild(BODY)).setAppearance(ap); + ((Shape3D)getChild(TOP)).setAppearance(ap); + ((Shape3D)getChild(BOTTOM)).setAppearance(ap); + } + + /** + * Gets the appearance of the specified part of the cylinder. + * + * @param partId identifier for a given subpart of the cylinder + * + * @return The appearance object associated with the partID. If an + * invalid partId is passed in, null is returned. + * + * @since Java 3D 1.2.1 + */ + public Appearance getAppearance(int partId) { + if (partId > BOTTOM || partId < BODY) return null; + return getShape(partId).getAppearance(); + } + + + /** + * Constructs a customized cylinder of a given radius, height, + * resolution (X and Y dimensions), and appearance. The + * resolution is defined in terms of number of subdivisions + * along the object's X axis (width) and Y axis (height). More divisions + * lead to more finely tesselated objects. + * @param radius Radius + * @param height Height + * @param xdivision Number of divisions along X direction. + * @param ydivision Number of divisions along height of cylinder. + * @param primflags Primitive flags. + * @param ap Appearance + */ + public Cylinder(float radius, float height, int primflags, + int xdivision, int ydivision, Appearance ap) { + super(); + + this.radius = radius; + this.height = height; + this.xdivisions = xdivision; + this.ydivisions = ydivision; + flags = primflags; + boolean outside = (flags & GENERATE_NORMALS_INWARD) == 0; + // Create many body of the cylinder. + Quadrics q = new Quadrics(); + GeomBuffer gbuf = null; + Shape3D shape[] = new Shape3D[3]; + + GeomBuffer cache = getCachedGeometry(Primitive.CYLINDER, + (float)BODY, radius, height, + xdivision, ydivision, primflags); + if (cache != null){ +// System.out.println("using cached geometry"); + shape[BODY] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + gbuf = q.cylinder((double)height, (double)radius, + xdivision, ydivision, outside); + shape[BODY] = new Shape3D(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) + cacheGeometry(Primitive.CYLINDER, + (float)BODY, radius, height, + xdivision, ydivision, primflags, gbuf); + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[BODY]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[BODY]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[BODY]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + + this.addChild(shape[BODY]); + + // Create top of cylinder + cache = getCachedGeometry(Primitive.TOP_DISK, radius, radius, + height/2.0f, xdivision, xdivision, primflags); + if (cache != null) { +// System.out.println("using cached top"); + shape[TOP] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + gbuf = q.disk((double)radius, xdivision, height/2.0, + outside); + shape[TOP] = new Shape3D(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.TOP_DISK, radius, radius, + height/2.0f, xdivision, xdivision, + primflags, gbuf); + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[TOP]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[TOP]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[TOP]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + + this.addChild(shape[TOP]); + + // Create bottom + cache = getCachedGeometry(Primitive.BOTTOM_DISK, radius, radius, + -height/2.0f, xdivision, xdivision, + primflags); + if (cache != null) { +// System.out.println("using cached bottom"); + shape[BOTTOM] = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } + else { + gbuf = q.disk((double)radius, xdivision, -height/2.0, !outside); + shape[BOTTOM] = new Shape3D(gbuf.getGeom(flags)); + numVerts += gbuf.getNumVerts(); + numTris += gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.BOTTOM_DISK, radius, radius, + -height/2.0f, xdivision, xdivision, + primflags, gbuf); + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + (shape[BOTTOM]).setCapability(Shape3D.ALLOW_APPEARANCE_READ); + (shape[BOTTOM]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + (shape[BOTTOM]).setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + + this.addChild(shape[BOTTOM]); + + // Set Appearance + if (ap == null){ + setAppearance(); + } + else setAppearance(ap); + } + + /** + * Used to create a new instance of the node. This routine is called + * by cloneTree to duplicate the current node. + * cloneNode should be overridden by any user subclassed + * objects. All subclasses must have their cloneNode + * method consist of the following lines: + *

+     *     public Node cloneNode(boolean forceDuplicate) {
+     *         UserSubClass usc = new UserSubClass();
+     *         usc.duplicateNode(this, forceDuplicate);
+     *         return usc;
+     *     }
+     * 
+ * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Cylinder c = new Cylinder(radius, height, flags, xdivisions, + ydivisions, getAppearance()); + c.duplicateNode(this, forceDuplicate); + return c; + } + + /** + * Copies all node information from originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method. + *

+ * For any NodeComponent objects + * contained by the object being duplicated, each NodeComponent + * object's duplicateOnCloneTree value is used to determine + * whether the NodeComponent should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * forceDuplicate parameter in the cloneTree + * method to true. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + } + + /** + * Returns the radius of the cylinder + * + * @since Java 3D 1.2.1 + */ + public float getRadius() { + return radius; + } + + /** + * Returns the height of the cylinder + * + * @since Java 3D 1.2.1 + */ + public float getHeight() { + return height; + } + + /** + * Returns the number divisions along the X direction + * + * @since Java 3D 1.2.1 + */ + public int getXdivisions() { + return xdivisions; + } + + /** + * Returns the number of divisions along the height of the cylinder + * + * @since Java 3D 1.2.1 + */ + public int getYdivisions() { + return ydivisions; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Degenerate.java b/src/classes/share/com/sun/j3d/utils/geometry/Degenerate.java new file mode 100644 index 0000000..a7c40da --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Degenerate.java @@ -0,0 +1,170 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Degenerate { + + /** + * This function checks whether the triangle i1, i2, i3 is an ear, where + * the vertex i4 lies on at least one of the two edges i1, i2 or i3, i1. + * basically, we can cut the polygon at i4 into two pieces. the polygon + * touches at i4 back to back if following the next-pointers in one + * subpolygon and following the prev-pointers in the other subpolygon yields + * the same orientation for both subpolygons. otherwise, i4 forms a + * bottle neck of the polygon, and i1, i2, i3 is no valid ear. + * + * Note that this function may come up with the incorrect answer if the + * polygon has self-intersections. + */ + static boolean handleDegeneracies(Triangulator triRef, int i1, int ind1, int i2, + int i3, int i4, int ind4) { + int i0, i5; + int type[] = new int[1]; + int ind0, ind2, ind5; + boolean flag; + double area = 0.0, area1 = 0, area2 = 0.0; + + /* assert(InPointsList(i1)); + assert(InPointsList(i2)); + assert(InPointsList(i3)); + assert(InPointsList(i4)); + */ + + // first check whether the successor or predecessor of i4 is inside the + // triangle, or whether any of the two edges incident at i4 intersects + // i2, i3. + ind5 = triRef.fetchPrevData(ind4); + i5 = triRef.fetchData(ind5); + + // assert(ind4 != ind5); + //assert(InPointsList(i5)); + if ((i5 != i2) && (i5 != i3)) { + flag = Numerics.vtxInTriangle(triRef, i1, i2, i3, i5, type); + if (flag && (type[0] == 0)) return true; + if (i2 <= i3) { + if (i4 <= i5) + flag = Numerics.segIntersect(triRef, i2, i3, i4, i5, -1); + else + flag = Numerics.segIntersect(triRef, i2, i3, i5, i4, -1); + } + else { + if (i4 <= i5) + flag = Numerics.segIntersect(triRef, i3, i2, i4, i5, -1); + else + flag = Numerics.segIntersect(triRef, i3, i2, i5, i4, -1); + } + if (flag) + return true; + } + + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + // assert(ind4 != ind5); + // assert(InPointsList(i5)); + if ((i5 != i2) && (i5 != i3)) { + flag = Numerics.vtxInTriangle(triRef, i1, i2, i3, i5, type); + if (flag && (type[0] == 0)) return true; + if (i2 <= i3) { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i2, i3, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i2, i3, i5, i4, -1); + } + else { + if (i4 <= i5) flag = Numerics.segIntersect(triRef, i3, i2, i4, i5, -1); + else flag = Numerics.segIntersect(triRef, i3, i2, i5, i4, -1); + } + if (flag) return true; + } + + i0 = i1; + ind0 = ind1; + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + while (ind1 != ind4) { + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + area = Numerics.stableDet2D(triRef, i0, i1, i2); + area1 += area; + ind1 = ind2; + i1 = i2; + } + + ind1 = triRef.fetchPrevData(ind0); + i1 = triRef.fetchData(ind1); + while (ind1 != ind4) { + ind2 = triRef.fetchPrevData(ind1); + i2 = triRef.fetchData(ind2); + area = Numerics.stableDet2D(triRef, i0, i1, i2); + area2 += area; + ind1 = ind2; + i1 = i2; + } + + if (Numerics.le(area1, triRef.ZERO) && Numerics.le(area2, triRef.ZERO)) + return false; + else if (Numerics.ge(area1, triRef.ZERO) && Numerics.ge(area2, triRef.ZERO)) + return false; + else + return true; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Desperate.java b/src/classes/share/com/sun/j3d/utils/geometry/Desperate.java new file mode 100644 index 0000000..a9185ff --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Desperate.java @@ -0,0 +1,431 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Desperate { + + /** + * the functions in this file try to ensure that we always end up with + * something that (topologically) is a triangulation. + * + * the more desperate we get, the more aggressive means we choose for making + * diagonals "valid". + */ + static boolean desperate(Triangulator triRef, int ind, int i, boolean[] splitted) { + int[] i1 = new int[1]; + int[] i2 = new int[1]; + int[] i3 = new int[1]; + int[] i4 = new int[1]; + int[] ind1 = new int[1]; + int[] ind2 = new int[1]; + int[] ind3 = new int[1]; + int[] ind4 = new int[1]; + + splitted[0] = false; + + // check whether there exist consecutive vertices i1, i2, i3, i4 such + // that i1, i2 and i3, i4 intersect + if (existsCrossOver(triRef, ind, ind1, i1, ind2, i2, ind3, i3, ind4, i4)) { + // insert two new diagonals around the cross-over without checking + // whether they are intersection-free + handleCrossOver(triRef, ind1[0], i1[0], ind2[0], i2[0], ind3[0], i3[0], + ind4[0], i4[0]); + return false; + } + + NoHash.prepareNoHashEdges(triRef, i, i+1); + + // check whether there exists a valid diagonal that splits the polygon + // into two parts + if (existsSplit(triRef, ind, ind1, i1, ind2, i2)) { + // break up the polygon by inserting this diagonal (which can't be an + // ear -- otherwise, we would not have ended up in this part of the + // code). then, let's treat the two polygons separately. hopefully, + // this will help to handle self-overlapping polygons in the "correct" + // way. + handleSplit(triRef, ind1[0], i1[0], ind2[0], i2[0]); + splitted[0] = true; + return false; + } + + return true; + } + + + static boolean existsCrossOver(Triangulator triRef, int ind, int[] ind1, int[] i1, + int[] ind2, int[] i2, int[] ind3, int[] i3, + int[] ind4, int[] i4) { + BBox bb1, bb2; + + ind1[0] = ind; + i1[0] = triRef.fetchData(ind1[0]); + ind2[0] = triRef.fetchNextData(ind1[0]); + i2[0] = triRef.fetchData(ind2[0]); + ind3[0] = triRef.fetchNextData(ind2[0]); + i3[0] = triRef.fetchData(ind3[0]); + ind4[0] = triRef.fetchNextData(ind3[0]); + i4[0] = triRef.fetchData(ind4[0]); + + do { + bb1 = new BBox(triRef, i1[0], i2[0]); + bb2 = new BBox(triRef, i3[0], i4[0]); + if (bb1.BBoxOverlap(bb2)) { + if (Numerics.segIntersect(triRef, bb1.imin, bb1.imax, bb2.imin, bb2.imax, -1)) + return true; + } + ind1[0] = ind2[0]; + i1[0] = i2[0]; + ind2[0] = ind3[0]; + i2[0] = i3[0]; + ind3[0] = ind4[0]; + i3[0] = i4[0]; + ind4[0] = triRef.fetchNextData(ind3[0]); + i4[0] = triRef.fetchData(ind4[0]); + + } while (ind1[0] != ind); + + return false; + } + + + static void handleCrossOver(Triangulator triRef, int ind1, int i1, int ind2, + int i2, int ind3, int i3, int ind4, int i4) { + double ratio1, ratio4; + boolean first; + int angle1, angle4; + + // which pair of triangles shall I insert?? we can use either i1, i2, i3 + // and i1, i3, i4, or we can use i2, i3, i4 and i1, i2, i4... + angle1 = triRef.getAngle(ind1); + angle4 = triRef.getAngle(ind4); + if (angle1 < angle4) first = true; + else if (angle1 > angle4) first = false; + else if (triRef.earsSorted) { + ratio1 = Numerics.getRatio(triRef, i3, i4, i1); + ratio4 = Numerics.getRatio(triRef, i1, i2, i4); + if (ratio4 < ratio1) first = false; + else first = true; + } + else { + first = true; + } + + if (first) { + // first clip i1, i2, i3, then clip i1, i3, i4 + triRef.deleteLinks(ind2); + // StoreTriangle(GetOriginal(ind1), GetOriginal(ind2), GetOriginal(ind3)); + triRef.storeTriangle(ind1, ind2, ind3); + triRef.setAngle(ind3, 1); + Heap.insertIntoHeap(triRef, 0.0, ind3, ind1, ind4); + } + else { + // first clip i2, i3, i4, then clip i1, i2, i4 + triRef.deleteLinks(ind3); + //StoreTriangle(GetOriginal(ind2), GetOriginal(ind3), GetOriginal(ind4)); + triRef.storeTriangle(ind2, ind3, ind4); + triRef.setAngle(ind2, 1); + Heap.insertIntoHeap(triRef, 0.0, ind2, ind1, ind4); + } + } + + + static boolean letsHope(Triangulator triRef, int ind) { + int ind0, ind1, ind2; + int i0, i1, i2; + + // let's clip the first convex corner. of course, we know that this is no + // ear in an ideal world. but this polygon isn't ideal, either! + ind1 = ind; + i1 = triRef.fetchData(ind1); + + do { + if (triRef.getAngle(ind1) > 0) { + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + Heap.insertIntoHeap(triRef, 0.0, ind1, ind0, ind2); + return true; + } + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + } while (ind1 != ind); + + // no convex corners? so, let's cheat! this code won't stop without some + // triangulation... ;-) g-i-g-o? right! perhaps, this is what you + // call a robust code?! + triRef.setAngle(ind, 1); + ind0 = triRef.fetchPrevData(ind); + i0 = triRef.fetchData(ind0); + ind2 = triRef.fetchNextData(ind); + i2 = triRef.fetchData(ind2); + Heap.insertIntoHeap(triRef, 0.0, ind, ind0, ind2); + i1 = triRef.fetchData(ind); + + return true; + + // see, we never have to return "false"... + /* + return false; + */ + } + + + static boolean existsSplit(Triangulator triRef, int ind, int[] ind1, int[] i1, + int[] ind2, int[] i2) { + int ind3, ind4, ind5; + int i3, i4, i5; + + if (triRef.numPoints > triRef.maxNumDist) { + // System.out.println("Desperate: Expanding distances array ..."); + triRef.maxNumDist = triRef.numPoints; + triRef.distances = new Distance[triRef.maxNumDist]; + for (int k = 0; k < triRef.maxNumDist; k++) + triRef.distances[k] = new Distance(); + } + ind1[0] = ind; + i1[0] = triRef.fetchData(ind1[0]); + ind4 = triRef.fetchNextData(ind1[0]); + i4 = triRef.fetchData(ind4); + // assert(*ind1 != ind4); + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + // assert(*ind1 != *ind2); + ind3 = triRef.fetchPrevData(ind1[0]); + i3 = triRef.fetchData(ind3); + // assert(*ind2 != ind3); + if (foundSplit(triRef, ind5, i5, ind3, ind1[0], i1[0], i3, i4, ind2, i2)) + return true; + i3 = i1[0]; + ind1[0] = ind4; + i1[0] = i4; + ind4 = ind5; + i4 = i5; + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + + while (ind5 != ind) { + if (foundSplit(triRef, ind5, i5, ind, ind1[0], i1[0], i3, i4, ind2, i2)) + return true; + i3 = i1[0]; + ind1[0] = ind4; + i1[0] = i4; + ind4 = ind5; + i4 = i5; + ind5 = triRef.fetchNextData(ind4); + i5 = triRef.fetchData(ind5); + } + + return false; + } + + + /** + * This function computes the winding number of a polygon with respect to a + * point p. no care is taken to handle cases where p lies on the + * boundary of the polygon. (this is no issue in our application, as we will + * always compute the winding number with respect to the mid-point of a + * valid diagonal.) + */ + static int windingNumber(Triangulator triRef, int ind, Point2f p) { + double angle; + int ind2; + int i1, i2, number; + + i1 = triRef.fetchData(ind); + ind2 = triRef.fetchNextData(ind); + i2 = triRef.fetchData(ind2); + angle = Numerics.angle(triRef, p, triRef.points[i1], triRef.points[i2]); + while (ind2 != ind) { + i1 = i2; + ind2 = triRef.fetchNextData(ind2); + i2 = triRef.fetchData(ind2); + angle += Numerics.angle(triRef, p, triRef.points[i1], triRef.points[i2]); + } + + angle += Math.PI; + number = (int)(angle / (Math.PI*2.0)); + + return number; + } + + + + + static boolean foundSplit(Triangulator triRef, int ind5, int i5, int ind, int ind1, + int i1, int i3, int i4, int[] ind2, int[] i2) { + Point2f center; + int numDist = 0; + int j, i6, i7; + int ind6, ind7; + BBox bb; + boolean convex, coneOk; + + // Sort the points according to their distance from i1 + do { + // assert(numDist < triRef.maxNumDist); + triRef.distances[numDist].dist = Numerics.baseLength(triRef.points[i1], + triRef.points[i5]); + triRef.distances[numDist].ind = ind5; + ++numDist; + ind5 = triRef.fetchNextData(ind5); + i5 = triRef.fetchData(ind5); + } while (ind5 != ind); + + Bridge.sortDistance(triRef.distances, numDist); + + // find a valid diagonal. + for (j = 0; j < numDist; ++j) { + ind2[0] = triRef.distances[j].ind; + i2[0] = triRef.fetchData(ind2[0]); + if (i1 != i2[0]) { + ind6 = triRef.fetchPrevData(ind2[0]); + i6 = triRef.fetchData(ind6); + ind7 = triRef.fetchNextData(ind2[0]); + i7 = triRef.fetchData(ind7); + + convex = triRef.getAngle(ind2[0]) > 0; + coneOk = Numerics.isInCone(triRef, i6, i2[0], i7, i1, convex); + if (coneOk) { + convex = triRef.getAngle(ind1) > 0; + coneOk = Numerics.isInCone(triRef, i3, i1, i4, i2[0], convex); + if (coneOk) { + bb = new BBox(triRef, i1, i2[0]); + if (!NoHash.noHashEdgeIntersectionExists(triRef, bb, -1, -1, ind1, -1)) { + // check whether this is a good diagonal; we do not want a + // diagonal that may create figure-8's! + center = new Point2f(); + Basic.vectorAdd2D(triRef.points[i1], triRef.points[i2[0]], center); + Basic.multScalar2D(0.5, center); + if (windingNumber(triRef, ind, center) == 1) return true; + } + } + } + } + } + + return false; + } + + + static void handleSplit(Triangulator triRef, int ind1, int i1, int ind3, int i3) { + int ind2, ind4, prev, next; + int prv, nxt, angle; + int vIndex, comIndex = -1; + + // duplicate nodes in order to form end points of the new diagonal + ind2 = triRef.makeNode(i1); + triRef.insertAfter(ind1, ind2); + + // Need to get the original data, before setting it. + + comIndex = triRef.list[ind1].getCommonIndex(); + + triRef.list[ind2].setCommonIndex(comIndex); + + ind4 = triRef.makeNode(i3); + triRef.insertAfter(ind3, ind4); + + comIndex = triRef.list[ind3].getCommonIndex(); + triRef.list[ind4].setCommonIndex(comIndex); + + // insert the diagonal into the boundary loop, thus splitting the loop + // into two loops + triRef.splitSplice(ind1, ind2, ind3, ind4); + + // store pointers to the two new loops + triRef.storeChain(ind1); + triRef.storeChain(ind3); + + // reset the angles + next = triRef.fetchNextData(ind1); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind1); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i1, nxt, ind1); + triRef.setAngle(ind1, angle); + + next = triRef.fetchNextData(ind2); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind2); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i1, nxt, ind2); + triRef.setAngle(ind2, angle); + + next = triRef.fetchNextData(ind3); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind3); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i3, nxt, ind3); + triRef.setAngle(ind3, angle); + + next = triRef.fetchNextData(ind4); + nxt = triRef.fetchData(next); + prev = triRef.fetchPrevData(ind4); + prv = triRef.fetchData(prev); + angle = Numerics.isConvexAngle(triRef, prv, i3, nxt, ind4); + triRef.setAngle(ind4, angle); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Distance.java b/src/classes/share/com/sun/j3d/utils/geometry/Distance.java new file mode 100644 index 0000000..9772db6 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Distance.java @@ -0,0 +1,73 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +class Distance extends Object { + int ind; + double dist; + + Distance() { + } + + void copy(Distance d) { + ind = d.ind; + dist = d.dist; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/EarClip.java b/src/classes/share/com/sun/j3d/utils/geometry/EarClip.java new file mode 100644 index 0000000..b5f78f0 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/EarClip.java @@ -0,0 +1,348 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class EarClip { + + /** + * Classifies all the internal angles of the loop referenced by ind. + * the following classification is used: + * 0 ... if angle is 180 degrees + * 1 ... if angle between 0 and 180 degrees + * 2 ... if angle is 0 degrees + * -1 ... if angle between 180 and 360 degrees + * -2 ... if angle is 360 degrees + */ + static void classifyAngles(Triangulator triRef, int ind) { + int ind0, ind1, ind2; + int i0, i1, i2; + int angle; + + ind1 = ind; + i1 = triRef.fetchData(ind1); + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + + do { + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + angle = Numerics.isConvexAngle(triRef, i0, i1, i2, ind1); + triRef.setAngle(ind1, angle); + i0 = i1; + i1 = i2; + ind1 = ind2; + } while (ind1 != ind); + + } + + + static void classifyEars(Triangulator triRef, int ind) { + int ind1; + int i1; + int[] ind0, ind2; + double[] ratio; + + ind0 = new int[1]; + ind2 = new int[1]; + ratio = new double[1]; + + Heap.initHeap(triRef); + + ind1 = ind; + i1 = triRef.fetchData(ind1); + + do { + if ((triRef.getAngle(ind1) > 0) && + isEar(triRef, ind1, ind0, ind2, ratio)) { + + Heap.dumpOnHeap(triRef, ratio[0], ind1, ind0[0], ind2[0]); + } + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + } while (ind1 != ind); + + // Not using sorted_ear so don't have to do MakeHeap(); + // MakeHeap(); + + // Heap.printHeapData(triRef); + + } + + + /** + * This function checks whether a diagonal is valid, that is, whether it is + * locally within the polygon, and whether it does not intersect any other + * segment of the polygon. also, some degenerate cases get a special + * handling. + */ + static boolean isEar(Triangulator triRef, int ind2, int[] ind1, int[] ind3, + double[] ratio) { + int i0, i1, i2, i3, i4; + int ind0, ind4; + BBox bb; + boolean convex, coneOk; + + i2 = triRef.fetchData(ind2); + ind3[0] = triRef.fetchNextData(ind2); + i3 = triRef.fetchData(ind3[0]); + ind4 = triRef.fetchNextData(ind3[0]); + i4 = triRef.fetchData(ind4); + ind1[0] = triRef.fetchPrevData(ind2); + i1 = triRef.fetchData(ind1[0]); + ind0 = triRef.fetchPrevData(ind1[0]); + i0 = triRef.fetchData(ind0); + + /* + System.out.println("isEar : i0 " + i0 + " i1 " + i1 + " i2 " + i2 + + " i3 " + i3 + " i4 " + i4); + */ + + if ((i1 == i3) || (i1 == i2) || (i2 == i3) || (triRef.getAngle(ind2) == 2)) { + // oops, this is not a simple polygon! + ratio[0] = 0.0; + return true; + } + + if (i0 == i3) { + // again, this is not a simple polygon! + if ((triRef.getAngle(ind0) < 0) || (triRef.getAngle(ind3[0]) < 0)) { + ratio[0] = 0.0; + return true; + } + else + return false; + } + + if (i1 == i4) { + // again, this is not a simple polygon! + if ((triRef.getAngle(ind1[0]) < 0) || (triRef.getAngle(ind4) < 0)) { + ratio[0] = 0.0; + return true; + } + else + return false; + } + + // check whether the new diagonal i1, i3 locally is within the polygon + convex = triRef.getAngle(ind1[0]) > 0; + coneOk = Numerics.isInCone(triRef, i0, i1, i2, i3, convex); + // System.out.println("isEar :(1) convex " + convex + " coneOk " + coneOk ); + + if (!coneOk) return false; + convex = triRef.getAngle(ind3[0]) > 0; + coneOk = Numerics.isInCone(triRef, i2, i3, i4, i1, convex); + // System.out.println("isEar :(2) convex " + convex + " coneOk " + coneOk ); + + if (coneOk) { + // check whether this diagonal is a valid diagonal. this translates to + // checking either condition CE1 or CE2 (see my paper). If CE1 is to + // to be checked, then we use a BV-tree or a grid. Otherwise, we use + // "buckets" (i.e., a grid) or no hashing at all. + bb = new BBox(triRef, i1, i3); + // use CE2 + no_hashing + if(!NoHash.noHashIntersectionExists(triRef, i2, ind2, i3, i1, bb)) { + if (triRef.earsSorted) { + // determine the quality of the triangle + ratio[0] = Numerics.getRatio(triRef, i1, i3, i2); + } + else { + ratio[0] = 1.0; + } + return true; + } + } + + // System.out.println("isEar : false"); + return false; + } + + + + /** + * This is the main function that drives the ear-clipping. it obtains an ear + * from set of ears maintained in a priority queue, clips this ear, and + * updates all data structures appropriately. (ears are arranged in the + * priority queue (i.e., heap) according to a quality criterion that tries + * to avoid skinny triangles.) + */ + static boolean clipEar(Triangulator triRef, boolean[] done) { + + int ind0, ind1, ind3, ind4; + + int i0, i1, i2, i3, i4; + int angle1, angle3; + + double ratio[] = new double[1]; + int index0[] = new int[1]; + int index1[] = new int[1]; + int index2[] = new int[1]; + int index3[] = new int[1]; + int index4[] = new int[1]; + int ind2[] = new int[1]; + + int testCnt = 0; + + // Heap.printHeapData(triRef); + + do { + + // System.out.println("In clipEarloop " + testCnt++); + + if (!Heap.deleteFromHeap(triRef, ind2, index1, index3)) + // no ear exists?! + return false; + + // get the successors and predecessors in the list of nodes and check + // whether the ear still is part of the boundary + ind1 = triRef.fetchPrevData(ind2[0]); + i1 = triRef.fetchData(ind1); + ind3 = triRef.fetchNextData(ind2[0]); + i3 = triRef.fetchData(ind3); + + } while ((index1[0] != ind1) || (index3[0] != ind3)); + + //System.out.println("Out of clipEarloop "); + + i2 = triRef.fetchData(ind2[0]); + + // delete the clipped ear from the list of nodes, and update the bv-tree + triRef.deleteLinks(ind2[0]); + + // store the ear in a list of ears which have already been clipped + // StoreTriangle(GetOriginal(ind1), GetOriginal(ind2), GetOriginal(ind3)); + triRef.storeTriangle(ind1, ind2[0], ind3); + + /* */ + /* update the angle classification at ind1 and ind3 */ + /* */ + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + if (ind0 == ind3) { + // nothing left + done[0] = true; + return true; + } + angle1 = Numerics.isConvexAngle(triRef, i0, i1, i3, ind1); + + ind4 = triRef.fetchNextData(ind3); + i4 = triRef.fetchData(ind4); + + angle3 = Numerics.isConvexAngle(triRef, i1, i3, i4, ind3); + + if (i1 != i3) { + if ((angle1 >= 0) && (triRef.getAngle(ind1) < 0)) + NoHash.deleteReflexVertex(triRef, ind1); + if ((angle3 >= 0) && (triRef.getAngle(ind3) < 0)) + NoHash.deleteReflexVertex(triRef, ind3); + } + else { + if ((angle1 >= 0) && (triRef.getAngle(ind1) < 0)) + NoHash.deleteReflexVertex(triRef, ind1); + else if ((angle3 >= 0) && (triRef.getAngle(ind3) < 0)) + NoHash.deleteReflexVertex(triRef, ind3); + + } + + triRef.setAngle(ind1, angle1); + triRef.setAngle(ind3, angle3); + + // check whether either of ind1 and ind3 is an ear. (the "ratio" is + // the length of the triangle's longest side divided by the length of the + // height normal onto this side; it is used as a quality criterion.) + if (angle1 > 0) { + if (isEar(triRef, ind1, index0, index2, ratio)) { + // insert the new ear into the priority queue of ears + Heap.insertIntoHeap(triRef, ratio[0], ind1, index0[0], index2[0]); + } + } + + if (angle3 > 0) { + if(isEar(triRef, ind3, index2, index4, ratio)) { + Heap.insertIntoHeap(triRef, ratio[0], ind3, index2[0], index4[0]); + } + } + + // check whether the triangulation is finished. + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + ind4 = triRef.fetchNextData(ind3); + i4 = triRef.fetchData(ind4); + if (ind0 == ind4) { + // only one triangle left -- clip it! + triRef.storeTriangle(ind1, ind3, ind4); + done[0] = true; + } + else { + done[0] = false; + } + + return true; + } + +} + + + + + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Edge.java b/src/classes/share/com/sun/j3d/utils/geometry/Edge.java new file mode 100644 index 0000000..6e56f47 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Edge.java @@ -0,0 +1,89 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +// Class created so that the two vertex indices that make up an +// edge can be hashed. +class Edge { + + public int v1; + public int v2; + private static final int HASHCONST = 0xEDCBA987; + + public int hashCode() + { + return ((v1 * HASHCONST) << 2) ^ (v2 * HASHCONST); + } // end of Edge.hashCode + + public boolean equals(Object x) + { + if (!(x instanceof Edge)) return false; + Edge e = (Edge)x; + return (v1 == e.v1) && (v2 == e.v2); + } // End of Edge.equals + + public String toString() + { + return "(" + v1 + ", " + v2 + ")"; + } // End of toString + + public Edge(int a, int b) + { + v1 = a; + v2 = b; + } + + public Edge(Edge e) + { + v1 = e.v1; + v2 = e.v2; + } + + public Edge() + { + } +} // end of class Edge + +// End of file Edge.java diff --git a/src/classes/share/com/sun/j3d/utils/geometry/EdgeTable.java b/src/classes/share/com/sun/j3d/utils/geometry/EdgeTable.java new file mode 100644 index 0000000..5a33da5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/EdgeTable.java @@ -0,0 +1,116 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; +import com.sun.j3d.utils.geometry.Edge; + +class EdgeTable { + + private HashMap edgeTable; + private static final int DEBUG = 0; + + + + Integer get(int a, int b) + { + return (Integer)edgeTable.get(new Edge(a, b)); + } // End of get() + + + Integer get(Edge e) + { + return (Integer)edgeTable.get(e); + } // End of get() + + + + // This function creates a table used to connect the triangles. + // Here's how it works. If a triangle is made of indices 12, + // 40, and 51, then edge(12, 40) gets 51, edge(40, 51) gets 12, + // and edge(51, 12) gets 40. This lets us quickly move from + // triangle to triangle without saving a lot of extra data. + EdgeTable(int triangleIndices[]) + { + // We'll have one edge for each vertex + edgeTable = new HashMap(triangleIndices.length * 2); + + // Fill in table + Edge e; + for (int t = 0 ; t < triangleIndices.length ; t += 3) { + // Put all 3 edges of triangle into table + for (int v = 0 ; v < 3 ; v++) { + e = new Edge(triangleIndices[t + v], + triangleIndices[t + ((v + 1) % 3)]); + + if (edgeTable.get(e) != null) { + if ((DEBUG & 1) != 0) { + System.out.println("EdgeTable Error: duplicate edge (" + + triangleIndices[t + v] + ", " + + triangleIndices[t + ((v + 1) % 3)] + ")."); + } + } else { + // Store index of 3rd vertex (across from edge) + edgeTable.put(e, new Integer(t + ((v + 2) % 3))); + } + } + } + + if ((DEBUG & 1) != 0) { + System.out.println("Edge Table:"); + Iterator list = edgeTable.keySet().iterator(); + while (list.hasNext()) { + Edge edge = (Edge)list.next(); + System.out.println(" (" + edge.v1 + ", " + edge.v2 + ") = " + + get(edge.v1, edge.v2)); + } + } + } // End of constructor EdgeTable + +} // End of class EdgeTable + +// End of file EdgeTable.java diff --git a/src/classes/share/com/sun/j3d/utils/geometry/GeomBuffer.java b/src/classes/share/com/sun/j3d/utils/geometry/GeomBuffer.java new file mode 100644 index 0000000..48dd0fb --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/GeomBuffer.java @@ -0,0 +1,557 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.math.*; + +/** + * GeomBuffer allows OpenGL-like input of geometry data. It outputs + * Java 3D geometry array objects. This utility is to simplify porting + * of OpenGL programs to Java 3D. + *

+ * Here is a sample code that use this utility to create some quads. + *

+ * 
+ *     GeomBuffer gbuf = new GeomBuffer(100);
+ *     gbuf.begin(GeomBuffer.QUADS);
+ *
+ *     for (int i = 0; i < 5; i++){
+ *       gbuf.normal3d(0.0, 1.0, 0.0);
+ *       gbuf.vertex3d(1.0, 1.0, 0.0);
+ * 
+ *       gbuf.normal3d(0.0, 1.0, 0.0);
+ *       gbuf.vertex3d(0.0, 1.0, 0.0);
+ * 
+ *       gbuf.normal3d(0.0, 1.0, 0.0);
+ *       gbuf.vertex3d(0.0, 0.0, 0.0);
+ * 
+ *       gbuf.normal3d(0.0, 1.0, 0.0);
+ *       gbuf.vertex3d(1.0, 0.0, 0.0);
+ *     }
+ *     gbuf.end();
+ *     Shape3D shape = new Shape3D(gbuf.getGeom(GeomBuffer.GENERATE_NORMALS));
+ * 
+ * Notice, that you only need to specify some upperbound on the number of + * points you'll use at the beginning (100 in this case). + *

+ * Currently, you are limited to one primitive type per geom buffer. Future + * versions will add support for mixed primitive types. + * + **/ + +class GeomBuffer extends Object{ + + //Supported Primitives + static final int QUAD_STRIP = 0x01; + static final int TRIANGLES = 0x02; + static final int QUADS = 0x04; + static final int TRIANGLE_FAN = 0x10; + static final int TRIANGLE_STRIP = 0x20; + + private int flags; + static final int GENERATE_NORMALS = 0x01; + static final int GENERATE_TEXTURE_COORDS = 0x02; + + Point3f[] pts = null; + Vector3f[] normals = null; + TexCoord2f[] tcoords = null; + int currVertCnt; + int currPrimCnt; + int[] currPrimType = null, + currPrimStartVertex = null, + currPrimEndVertex = null; + GeometryArray geometry; + int numVerts = 0; + int numTris = 0; + int numTexUnit = 1; + int texCoordSetMap[] = null; + + + static final int debug = 0; + + /** Creates a geometry buffer of given number of vertices + * @param numVerts total number of vertices to allocate by this buffer. + * This is an upper bound estimate. + */ + GeomBuffer(int numVerts, int numTexUnit) + { + this.numTexUnit = numTexUnit; + pts = new Point3f[numVerts]; + normals = new Vector3f[numVerts]; + tcoords = new TexCoord2f[numVerts]; + // max primitives is numV/3 + currPrimType = new int[numVerts/3]; + currPrimStartVertex = new int[numVerts/3]; + currPrimEndVertex = new int[numVerts/3]; + currVertCnt = 0; + currPrimCnt = 0; + + texCoordSetMap = new int[numTexUnit]; + for (int i = 0; i < numTexUnit; i++) + texCoordSetMap[i] = 0; + + } + + GeomBuffer(int numVerts) + { + this(numVerts, 1); + } + + /* + * Returns a Java 3D geometry array from the geometry buffer. You need to + * call begin, vertex3d, end, etc. before calling this, of course. + * + * @param format vertex format. + */ + + GeometryArray getGeom(int format) + { + GeometryArray obj = null; + flags = format; + + numTris = 0; + + //Switch based on first primitive. + switch (currPrimType[0]){ + case TRIANGLES: + obj = processTriangles(); + break; + case QUADS: + obj = processQuads(); + break; + case QUAD_STRIP: + case TRIANGLE_STRIP: + obj = processQuadStrips(); + break; + case TRIANGLE_FAN: + obj = processTriangleFan(); + break; + } + if ((obj != null) && ((flags & Primitive.ENABLE_GEOMETRY_PICKING) != 0)) { + obj.setCapability(Geometry.ALLOW_INTERSECT); + obj.setCapability(GeometryArray.ALLOW_FORMAT_READ); + obj.setCapability(GeometryArray.ALLOW_COUNT_READ); + obj.setCapability(GeometryArray.ALLOW_COORDINATE_READ); + } + return obj; + } + + + /** + * Begins a new primitive given the primitive type. + * + * @param prim the primitive type (listed above). + * + **/ + + void begin(int prim) + { + if (debug >= 1) System.out.println("quad"); + currPrimType[currPrimCnt] = prim; + currPrimStartVertex[currPrimCnt] = currVertCnt; + } + + + /** + * End of primitive. + * + * + **/ + void end() + { + if (debug >= 1) System.out.println("end"); + currPrimEndVertex[currPrimCnt] = currVertCnt; + currPrimCnt++; + } + + void vertex3d(double x, double y, double z) + { + + if (debug >= 2) System.out.println("v " + x + " " + + y + " " + + z); + pts[currVertCnt] = new Point3f((float)x, (float)y, (float)z); + currVertCnt++; + } + + void normal3d(double x, double y, double z) + { + if (debug >= 2) System.out.println("n " + x + " " + + y + " " + z); + double sum = x*x+y*y+z*z; + if (Math.abs(sum - 1.0) > 0.001){ + if (debug >= 2) System.out.println("normalizing"); + double root = Math.sqrt(sum); + if (root > 0.000001) { + x /= root; + y /= root; + z /= root; + } else { + y = z = 0.0; x = 1.0; + } + } + normals[currVertCnt] = new Vector3f((float)x, (float)y, (float)z); + } + + void texCoord2d(double s, double t) + { + if (debug >= 2) System.out.println("t " + + s + " " + + t); + tcoords[currVertCnt] = new TexCoord2f((float)s, (float)t); + } + + /** + * Returns the Java 3D geometry gotten from calling getGeom. + * + **/ + + GeometryArray getComputedGeometry() + { + return geometry; + } + + int getNumTris() + { + return numTris; + } + + int getNumVerts() + { + return numVerts; + } + + + private GeometryArray processQuadStrips() + { + GeometryArray obj = null; + int i; + int totalVerts = 0; + + // Calculate how many vertices needed to hold all of the individual quads + int stripCounts[] = new int[currPrimCnt]; + for (i = 0; i < currPrimCnt; i++){ + stripCounts[i] = currPrimEndVertex[i] - currPrimStartVertex[i]; + totalVerts += stripCounts[i]; + } + + if (debug >= 1) System.out.println("totalVerts " + totalVerts); + + int tsaFlags = TriangleStripArray.COORDINATES; + if ((flags & GENERATE_NORMALS) != 0) + tsaFlags |= TriangleStripArray.NORMALS; + if ((flags & GENERATE_TEXTURE_COORDS) != 0) + tsaFlags |= TriangleStripArray.TEXTURE_COORDINATE_2; + + // Create GeometryArray to pass back + obj = new TriangleStripArray(totalVerts, tsaFlags, + 1, texCoordSetMap, stripCounts); + + // Allocate space to store new vertex info + Point3f[] newpts = new Point3f[totalVerts]; + Vector3f[] newnormals = new Vector3f[totalVerts]; + TexCoord2f[] newtcoords = new TexCoord2f[totalVerts]; + int currVert = 0; + + // Repeat for each Quad Strip + for (i = 0; i < currPrimCnt; i++){ + // Output order for these quad arrays same as java 3d triangle strips + for (int j = currPrimStartVertex[i] ; j < currPrimEndVertex[i] ; j++){ + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j); + } + + } + + numVerts = currVert; + numTris += totalVerts - currPrimCnt * 2; + + obj.setCoordinates(0, newpts); + if ((flags & GENERATE_NORMALS) != 0) + obj.setNormals(0, newnormals); + if ((flags & GENERATE_TEXTURE_COORDS) != 0) + obj.setTextureCoordinates(0, 0, newtcoords); + + geometry = obj; + return obj; + } + + private GeometryArray processQuads() + { + GeometryArray obj = null; + int i; + int totalVerts = 0; + + for (i = 0; i < currPrimCnt; i++){ + totalVerts += currPrimEndVertex[i] - currPrimStartVertex[i]; + } + + if (debug >= 1) System.out.println("totalVerts " + totalVerts); + + if (((flags & GENERATE_NORMALS) != 0) && + ((flags & GENERATE_TEXTURE_COORDS) != 0)){ + obj = new QuadArray(totalVerts, + QuadArray.COORDINATES | + QuadArray.NORMALS | + QuadArray.TEXTURE_COORDINATE_2, + 1, texCoordSetMap); + } + else + if (((flags & GENERATE_NORMALS) == 0) && + ((flags & GENERATE_TEXTURE_COORDS) != 0)){ + obj = new QuadArray(totalVerts, + QuadArray.COORDINATES | + QuadArray.TEXTURE_COORDINATE_2, + 1, texCoordSetMap); + } + else + if (((flags & GENERATE_NORMALS) != 0) && + ((flags & GENERATE_TEXTURE_COORDS) == 0)){ + obj = new QuadArray(totalVerts, + QuadArray.COORDINATES | + QuadArray.NORMALS); + } + else { + obj = new QuadArray(totalVerts, + QuadArray.COORDINATES); + } + + Point3f[] newpts = new Point3f[totalVerts]; + Vector3f[] newnormals = new Vector3f[totalVerts]; + TexCoord2f[] newtcoords = new TexCoord2f[totalVerts]; + int currVert = 0; + + if (debug > 1) System.out.println("total prims " + currPrimCnt); + + for (i = 0; i < currPrimCnt; i++){ + if (debug > 1) System.out.println("start " + currPrimStartVertex[i] + + " end " + currPrimEndVertex[i]); + for (int j = currPrimStartVertex[i]; j < currPrimEndVertex[i] - 3;j+=4){ + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j); + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j + 1); + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j + 2); + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j + 3); + numTris += 2; + } + } + numVerts = currVert; + + obj.setCoordinates(0, newpts); + if ((flags & GENERATE_NORMALS) != 0) + obj.setNormals(0, newnormals); + if ((flags & GENERATE_TEXTURE_COORDS) != 0) + obj.setTextureCoordinates(0, 0, newtcoords); + + geometry = obj; + return obj; + } + + private GeometryArray processTriangles() + { + GeometryArray obj = null; + int i; + int totalVerts = 0; + + for (i = 0; i < currPrimCnt; i++){ + totalVerts += currPrimEndVertex[i] - currPrimStartVertex[i]; + } + + if (debug >= 1) System.out.println("totalVerts " + totalVerts); + + if (((flags & GENERATE_NORMALS) != 0) && + ((flags & GENERATE_TEXTURE_COORDS) != 0)){ + obj = new TriangleArray(totalVerts, + TriangleArray.COORDINATES | + TriangleArray.NORMALS | + TriangleArray.TEXTURE_COORDINATE_2, + 1, texCoordSetMap); + } + else + if (((flags & GENERATE_NORMALS) == 0) && + ((flags & GENERATE_TEXTURE_COORDS) != 0)){ + obj = new TriangleArray(totalVerts, + TriangleArray.COORDINATES | + TriangleArray.TEXTURE_COORDINATE_2, + 1, texCoordSetMap); + } + else + if (((flags & GENERATE_NORMALS) != 0) && + ((flags & GENERATE_TEXTURE_COORDS) == 0)){ + obj = new TriangleArray(totalVerts, + TriangleArray.COORDINATES | + TriangleArray.NORMALS); + } + else { + obj = new TriangleArray(totalVerts, + TriangleArray.COORDINATES); + } + + Point3f[] newpts = new Point3f[totalVerts]; + Vector3f[] newnormals = new Vector3f[totalVerts]; + TexCoord2f[] newtcoords = new TexCoord2f[totalVerts]; + int currVert = 0; + + for (i = 0; i < currPrimCnt; i++){ + for (int j = currPrimStartVertex[i]; j < currPrimEndVertex[i] - 2;j+=3){ + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j); + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j + 1); + outVertex(newpts, newnormals, newtcoords, currVert++, + pts, normals, tcoords, j + 2); + numTris += 1; + } + } + numVerts = currVert; + + obj.setCoordinates(0, newpts); + if ((flags & GENERATE_NORMALS) != 0) + obj.setNormals(0, newnormals); + if ((flags & GENERATE_TEXTURE_COORDS) != 0) + obj.setTextureCoordinates(0, 0, newtcoords); + + geometry = obj; + return obj; + } + + + private GeometryArray processTriangleFan() { + if (debug > 0) System.out.println("processTriangleFan"); + + GeometryArray obj = null; + int i; + int totalVerts = 0; + + int stripCounts[] = new int[currPrimCnt]; + + // figure out how many vertices we need to hold the individual fans + for (i = 0; i < currPrimCnt; i++) { + stripCounts[i] = currPrimEndVertex[i] - currPrimStartVertex[i]; + totalVerts += stripCounts[i]; + } + + // figure out what flags we need + int tfFlags = TriangleFanArray.COORDINATES; + if ((flags & GENERATE_NORMALS) != 0) { + tfFlags |= TriangleFanArray.NORMALS; + } + if ((flags & GENERATE_TEXTURE_COORDS) != 0) { + tfFlags |= TriangleFanArray.TEXTURE_COORDINATE_2; + } + + // create the TriangleFanArray + obj = new TriangleFanArray(totalVerts, tfFlags, 1, texCoordSetMap, + stripCounts); + + // allocate space for vertex info + Point3f[] newpts = new Point3f[totalVerts]; + Vector3f[] newnormals = new Vector3f[totalVerts]; + TexCoord2f[] newtcoords = new TexCoord2f[totalVerts]; + + int currVert = 0; + + // repeat for each fan + for (i = 0; i < currPrimCnt; i++) { + for (int j = currPrimStartVertex[i]; j < currPrimEndVertex[i]; j++) { + outVertex(newpts, newnormals, newtcoords, currVert++, pts, + normals, tcoords, j); + } + } + + for (i = 0; i < newpts.length; i++) { + if (debug > 1) System.out.println("i = " + i + " " + newpts[i]); + } + + numVerts = currVert; + numTris = totalVerts - currPrimCnt * 2; + + // set the coordinates on the GeometryArray + obj.setCoordinates(0, newpts); + + // set the normals and tex coords if necessary + if ((flags & GENERATE_NORMALS) != 0) { + obj.setNormals(0, newnormals); + } + if ((flags & GENERATE_TEXTURE_COORDS) != 0) { + obj.setTextureCoordinates(0, 0, newtcoords); + } + geometry = obj; + return obj; + } + + + + void outVertex(Point3f[] dpts, Vector3f[] dnormals, TexCoord2f[] dtcoords, + int dloc, + Point3f[] spts, Vector3f[] snormals, TexCoord2f[] stcoords, + int sloc) + { + if (debug >= 1) System.out.println("v " + spts[sloc].x + " " + + spts[sloc].y + " " + + spts[sloc].z); + + // PSP: Do we really need new points here? + + dpts[dloc] = new Point3f(spts[sloc]); + + if ((flags & GENERATE_NORMALS) != 0){ + dnormals[dloc] = new Vector3f(snormals[sloc]); + } + if ((flags & GENERATE_TEXTURE_COORDS) != 0){ + if (debug >= 2) System.out.println("final out tcoord"); + dtcoords[dloc] = new TexCoord2f(stcoords[sloc]); + } + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfo.java b/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfo.java new file mode 100644 index 0000000..69eeadb --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfo.java @@ -0,0 +1,2826 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.Triangulator; +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.internal.J3dUtilsI18N; +import java.util.HashMap; +import com.sun.j3d.utils.geometry.GeometryInfoGenerator; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; +import com.sun.j3d.internal.ByteOrderWrapper; +import javax.media.j3d.J3DBuffer; + +/** + * The GeometryInfo object holds data for processing by the Java3D geometry + * utility tools.

+ * + * The NormalGenerator adds normals to geometry without normals.

+ * + * The Stripifier combines adjacent triangles into triangle strips for + * more efficent rendering.

+ * + * Also, the GeometryCompressor can take a set of GeometryInfo objects in a + * CompressionSteam and generate a CompressedGeometry object from the + * geometry.

+ * Geometry is loaded into a GeometryInfo in a manner similar to the + * + * GeometryArray methods. The constructor for the GeometryInfo takes a flag + * that specifies the kind of data being loaded. The vertex data is + * specified using methods that are similar to the GeometryArray methods, but + * with fewer variations.

+ * The major difference between GeometryInfo and GeometryArray is + * that the number of vertices, vertex format, and other data are specified + * implictly, rather than as part of the constructor. The number of verticies + * comes from the number of coordinates passed to the setCoordinates() + * method. The format comes from the set of data components that are + * specified. For example, calling the setCoordinates(), setColors3() and + * setTextureCoordinatesParames(1, 2) methods implies a + * format of COORDINATES | COLOR_3 + * | TEXTURE_COORDINATE_2. Indexed representation is specified by calling + * the methods that specify the indices, for example + * setCoordinateIndices().

+ * Stripped primitives are loaded using the TRIANGLE_FAN_ARRAY or + * TRIANGLE_STRIP_ARRAY flags to the constructor. The setStripCounts() + * method specifies the length of each strip.

+ * A set of complex polygons is loaded using the POLYGON_ARRAY + * flag to the constructor. The setStripCounts() method specifies the length + * of each contour of the polygons. The setContourCounts() method specifies + * the number of countours in each polygon. For example, a triangle with a + * triangular hole would have strip counts [3, 3] (indicating two contours of + * three points) and contour counts [2] (indicating a single polygon with two + * contours).

+ * GeometryInfo itelf contains some simple utilities, such as + * calculating indices for non-indexed data ("indexifying") and getting rid + * of unused data in your indexed geometry ("compacting").

+ * The geometry utility tools modify the contents of the + * GeometryInfo. After processing, the resulting geometry can be extracted + * from the GeometryInfo by calling getGeometryArray(). If multiple tools + * are used, the order of processing should be: generate normals, then + * stripify. For example, to convert a general mesh of polygons without + * normals into an optimized mesh call: + *

+ * GeometryInfo gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); + * // initialize the geometry info here + * // generate normals + * NormalGenerator ng = new NormalGenerator(); + * ng.generateNormals(gi); + * // stripify + * Stripifier st = new Stripifier(); + * st.stripify(gi); + * GeometryArray result = gi.getGeometryArray(); + *
+ * + * @see NormalGenerator + * @see Stripifier + * @see com.sun.j3d.utils.compression.CompressionStream + * @see com.sun.j3d.utils.compression.GeometryCompressor + * @see javax.media.j3d.GeometryArray + */ + +public class GeometryInfo { + + /** + * Send to the constructor to inform that the data will be arranged so + * that each set of three vertices form an independent triangle + */ + public static final int TRIANGLE_ARRAY = 1; + + /** + * Send to the constructor to inform that the data will be arranged so + * that each set of four vertices form an independent quad + */ + public static final int QUAD_ARRAY = 2; + + /** + * Send to the constructor to inform that the data will be arranged so + * that the stripCounts array indicates how many vertices to use + * for each triangle fan. + */ + public static final int TRIANGLE_FAN_ARRAY = 3; + + /** + * Send to the constructor to inform that the data will be arranged so + * that the stripCounts array indicates how many vertices to use + * for each triangle strip. + */ + public static final int TRIANGLE_STRIP_ARRAY = 4; + + /** + * Send to the constructor to inform that the data is arranged as + * possibly multi-contour, possible non-planar polygons. + * The stripCounts array indicates how many vertices to use + * for each contour, and the contourCounts array indicates how many + * stripCounts entries to use for each polygon. The first + * contour is the bounding polygon, and subsequent contours are + * "holes." If contourCounts is left null, the default is + * one contour per polygon. + */ + public static final int POLYGON_ARRAY = 5; + + private int prim; + + // 1 Show indexification details + private static final int DEBUG = 0; + + private Point3f coordinates[] = null; + private Color3f colors3[] = null; + private Color4f colors4[] = null; + private Vector3f normals[] = null; + private Object texCoordSets[][] = null; + + private int coordinateIndices[] = null; + private int colorIndices[] = null; + private int normalIndices[] = null; + private int texCoordIndexSets[][] = null; + + private int[] texCoordSetMap = null; + private int texCoordSetCount = 0; + private int texCoordDim = 0; + + private int stripCounts[] = null; + private int contourCounts[] = null; + + private Triangulator tr = null; + private NormalGenerator ng = null; + + private int oldPrim = 0; + private int oldStripCounts[] = null; + + private boolean coordOnly = false; + + + + /** + * Constructor. + * Creates an empty GeometryInfo object. + * @param primitive Tells the GeometryInfo object the type of + * primitive data to be stored + * in it, so it will know the format of the data. It can be one of + * TRIANGLE_ARRAY, + * QUAD_ARRAY, TRIANGLE_FAN_ARRAY, TRIANGLE_STRIP_ARRAY, or POLYGON_ARRAY. + */ + public GeometryInfo(int primitive) + { + if ((primitive >= TRIANGLE_ARRAY) && (primitive <= POLYGON_ARRAY)) { + prim = primitive; + } else { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo0")); + } + } // End of GeometryInfo(int) + + + + /** + * Contructor. Populates the GeometryInfo with the geometry from + * the GeometryArray.

+ * If the GeometryArray uses the Initial and + * Valid GeometryArray methods ( + * setInitialVertexIndex() and setValidVertexCount() + * and their cousins) then only the needed geometry + * is copied into the GeometryInfo. + */ + public GeometryInfo(GeometryArray ga) + { + GeometryInfoGenerator.create(this, ga); + } // End of GeometryInfo(GeometryArray) + + + + /** + * Removes all data from the GeometryInfo and resets the primitive. + * After a call to reset(), the GeometryInfo object will be just like + * it was when it was newly constructed. + * @param primitive Either TRIANGLE_ARRAY, QUAD_ARRAY, + * TRIANGLE_FAN_ARRAY, TRIANGLE_STRIP_ARRAY, or POLYGON_ARRAY. + * Tells the GeometryInfo object the type of primitive data to be stored + * in it, so it will know the format of the data. + */ + public void reset(int primitive) + { + if ((primitive >= TRIANGLE_ARRAY) && (primitive <= POLYGON_ARRAY)) { + prim = primitive; + } else { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo0")); + } + + coordinates = null; + colors3 = null; + colors4 = null; + normals = null; + + coordinateIndices = null; + colorIndices = null; + normalIndices = null; + + stripCounts = null; + contourCounts = null; + + oldPrim = 0; + oldStripCounts = null; + + texCoordDim = 0; + texCoordSetCount = 0; + texCoordSets = null; + texCoordIndexSets = null; + texCoordSetMap = null; + + coordOnly = false; + + } // End of reset(int) + + + + /** + * Removes all data from this GeometryInfo and populates it with + * the geometry from the GeometryArray. + */ + public void reset(GeometryArray ga) + { + GeometryInfoGenerator.create(this, ga); + } // End of reset(GeometryArray) + + + + // This method takes an indexed quad array and expands it to + // a list of indexed triangles. It is used for the Coordinate + // indices as well as the color and texture indices. + private int[] expandQuad(int indices[]) + { + int triangles[] = new int[indices.length / 4 * 6]; + + for (int i = 0 ; i < indices.length / 4 ; i++ ) { + triangles[i * 6 + 0] = indices[i * 4]; + triangles[i * 6 + 1] = indices[i * 4 + 1]; + triangles[i * 6 + 2] = indices[i * 4 + 2]; + triangles[i * 6 + 3] = indices[i * 4]; + triangles[i * 6 + 4] = indices[i * 4 + 2]; + triangles[i * 6 + 5] = indices[i * 4 + 3]; + } + + return triangles; + } // End of expandQuad + + + + // This method takes an indexed triangle fan and expands it to + // a list of indexed triangles. It is used for the Coordinate + // indices as well as the color and texture indices. + private int[] expandTriFan(int numTris, int indices[]) + { + int triangles[] = new int[numTris * 3]; + int p = 0; + int base = 0; + for (int f = 0 ; f < stripCounts.length ; f++) { + for (int t = 0 ; t < stripCounts[f] - 2 ; t++) { + triangles[p++] = indices[base]; + triangles[p++] = indices[base + t + 1]; + triangles[p++] = indices[base + t + 2]; + } + base += stripCounts[f]; + } + return triangles; + } // End of expandTriFan + + + + // This method takes an indexed triangle strip and expands it to + // a list of indexed triangles. It is used for the Coordinate + // indices as well as the color and texture indices. + private int[] expandTriStrip(int numTris, int indices[]) + { + int triangles[] = new int[numTris * 3]; + + int p = 0; + int base = 0; + for (int s = 0 ; s < stripCounts.length ; s++) { + for (int t = 0 ; t < stripCounts[s] - 2 ; t++) { + + // Use a ping-ponging algorithm to reverse order on every other + // triangle to preserve winding + if (t % 2 == 0) { + triangles[p++] = indices[base + t + 0]; + triangles[p++] = indices[base + t + 1]; + triangles[p++] = indices[base + t + 2]; + } else { + triangles[p++] = indices[base + t + 0]; + triangles[p++] = indices[base + t + 2]; + triangles[p++] = indices[base + t + 1]; + } + } + base += stripCounts[s]; + } + + return triangles; + } // End of expandTriStrip + + + + // Used by the NormalGenerator utility. Informs the GeometryInfo object + // to remember its current primitive and stripCounts arrays so that + // they can be used to convert the object back to its original + // primitive + void rememberOldPrim() + { + oldPrim = prim; + oldStripCounts = stripCounts; + } // End of rememberOldPrim + + + + // The NormalGenerator needs to know the original primitive for + // facet normal generation for quads + int getOldPrim() + { + return oldPrim; + } // End of getOldPrim + + + + // Used by the Utility libraries other than the NormalGenerator. + // Informs the GeometryInfo object that the geometry need not + // be converted back to the original primitive before returning. + // For example, if a list of Fans is sent, converted to Triangles + // for normal generation, and then stripified by the Stripifyer, + // we want to make sure that GeometryInfo doesn't convert the + // geometry *back* to fans before creating the output GeometryArray. + void forgetOldPrim() + { + oldPrim = 0; + oldStripCounts = null; + } // End of forgetOldPrim + + + + // We have changed the user's data from their original primitive + // type to TRIANGLE_ARRAY. If this method is being called, it + // means we need to change it back (to try and hide from the user + // the fact that we've converted). This usually happens when + // the user has used GeometryInfo for generating normals, but + // they are not Stripifying or Triangulating. The function is + // called from getGeometryArray before creating the output data. + private void changeBackToOldPrim() + { + if (oldPrim != 0) { + convertToIndexedTriangles(); + if (ng == null) ng = new NormalGenerator(); + ng.convertBackToOldPrim(this, oldPrim, oldStripCounts); + oldPrim = 0; + oldStripCounts = null; + } + } // End of changeBackToOldPrim + + + + /** + * Convert the GeometryInfo object to have primitive type TRIANGLE_ARRAY + * and be indexed. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void convertToIndexedTriangles() + { + int triangles = 0; + + // This calls checkForBadData + indexify(); + + if (prim == TRIANGLE_ARRAY) return; + + switch(prim) { + + case QUAD_ARRAY: + + coordinateIndices = expandQuad(coordinateIndices); + if (colorIndices != null) colorIndices = expandQuad(colorIndices); + if (normalIndices != null) + normalIndices = expandQuad(normalIndices); + for (int i = 0 ; i < texCoordSetCount ; i++) + texCoordIndexSets[i] = expandQuad(texCoordIndexSets[i]); + break; + + case TRIANGLE_FAN_ARRAY: + // Count how many triangles are in the object + for (int i = 0 ; i < stripCounts.length ; i++) { + triangles += stripCounts[i] - 2; + } + + coordinateIndices = expandTriFan(triangles, coordinateIndices); + if (colorIndices != null) + colorIndices = expandTriFan(triangles, colorIndices); + if (normalIndices != null) + normalIndices = expandTriFan(triangles, normalIndices); + for (int i = 0 ; i < texCoordSetCount ; i++) + texCoordIndexSets[i] = expandTriFan(triangles, + texCoordIndexSets[i]); + break; + + case TRIANGLE_STRIP_ARRAY: + // Count how many triangles are in the object + for (int i = 0 ; i < stripCounts.length ; i++) { + triangles += stripCounts[i] - 2; + } + + coordinateIndices = expandTriStrip(triangles, coordinateIndices); + if (colorIndices != null) + colorIndices = expandTriStrip(triangles, colorIndices); + if (normalIndices != null) + normalIndices = expandTriStrip(triangles, normalIndices); + for (int i = 0 ; i < texCoordSetCount ; i++) + texCoordIndexSets[i] = expandTriStrip(triangles, + texCoordIndexSets[i]); + break; + + case POLYGON_ARRAY: + if (tr == null) tr = new Triangulator(); + tr.triangulate(this); + break; + } + + prim = TRIANGLE_ARRAY; + stripCounts = null; + } // End of convertToIndexedTriangles + + + + /** + * Get the current primitive. Some of the utilities may change the + * primitive type of the data stored in the GeometryInfo object + * (for example, the stripifyer will change it to TRIANGLE_STRIP_ARRAY). + */ + public int getPrimitive() + { + return prim; + } // End of getPrimitive() + + + + /** + * Set the current primitive. Some of the utilities may change the + * primitive type of the data stored in the GeometryInfo object + * (for example, the stripifyer will change it to TRIANGLE_STRIP_ARRAY). + * But the user can't change the primitive type - it is set in the + * constructor. Therefore, this method has package scope. + */ + void setPrimitive(int primitive) + { + if ((prim >= TRIANGLE_ARRAY) && (prim <= POLYGON_ARRAY)) { + prim = primitive; + } else { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo0")); + } + } // End of setPrimitive() + + + + /** + * Sets the coordinates array. + * No data copying is done because a reference to user data is used. + */ + public void setCoordinates(Point3f coordinates[]) + { + this.coordinates = coordinates; + } // End of setCoordinates + + + + /** + * Sets the coordinates array. + * The points are copied into the GeometryInfo object. + */ + public void setCoordinates(Point3d coordinates[]) + { + if (coordinates == null) this.coordinates = null; + else { + this.coordinates = new Point3f[coordinates.length]; + for (int i = 0 ; i < coordinates.length ; i++) { + this.coordinates[i] = new Point3f( + (float)(coordinates[i].x), + (float)(coordinates[i].y), + (float)(coordinates[i].z)); + } + } + } // End of setCoordinates + + + + /** + * Sets the coordinates array. + * The points are copied into the GeometryInfo object. + */ + public void setCoordinates(float coordinates[]) + { + if (coordinates == null) this.coordinates = null; + else { + this.coordinates = new Point3f[coordinates.length / 3]; + for (int i = 0 ; i < this.coordinates.length ; i++) { + this.coordinates[i] = new Point3f(coordinates[i * 3], + coordinates[i * 3 + 1], + coordinates[i * 3 + 2]); + } + } + } // End of setCoordinates + + + + /** + * Sets the coordinates array. + * The points are copied into the GeometryInfo object. + */ + public void setCoordinates(double coordinates[]) + { + if (coordinates == null) this.coordinates = null; + else { + this.coordinates = new Point3f[coordinates.length / 3]; + for (int i = 0 ; i < coordinates.length / 3 ; i++) { + this.coordinates[i] = new Point3f((float)coordinates[i * 3], + (float)coordinates[i * 3 + 1], + (float)coordinates[i * 3 + 2]); + } + } + } // End of setCoordinates + + + + /** + * Retrieves a reference to the coordinate array. + */ + public Point3f[] getCoordinates() + { + return coordinates; + } // End of getCoordinates + + + + /** + * Sets the colors array. + * No data copying is done because a reference to + * user data is used. + */ + public void setColors(Color3f colors[]) + { + colors3 = colors; + colors4 = null; + } // End of setColors + + + + /** + * Sets the colors array. + * No data copying is done because a reference to + * user data is used. + */ + public void setColors(Color4f colors[]) + { + colors3 = null; + colors4 = colors; + } // End of setColors + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object. + */ + public void setColors(Color3b colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = new Color3f[colors.length]; + colors4 = null; + for (int i = 0 ; i < colors.length ; i++) { + colors3[i] = new Color3f((float) (colors[i].x & 0xff) / 255.0f, + (float) (colors[i].y & 0xff) / 255.0f, + (float) (colors[i].z & 0xff) / 255.0f); + } + } + } // End of setColors + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object. + */ + public void setColors(Color4b colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = null; + colors4 = new Color4f[colors.length]; + for (int i = 0 ; i < colors.length ; i++) { + colors4[i] = new Color4f((float) (colors[i].x & 0xff) / 255.0f, + (float) (colors[i].y & 0xff) / 255.0f, + (float) (colors[i].z & 0xff) / 255.0f, + (float) (colors[i].w & 0xff) / 255.0f); + } + } + } // End of setColors + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object, assuming + * 3 components (R, G, and B) per vertex. + */ + public void setColors3(float colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = new Color3f[colors.length / 3]; + colors4 = null; + for (int i = 0 ; i < colors.length / 3 ; i++) { + colors3[i] = new Color3f(colors[i * 3], + colors[i * 3 + 1], + colors[i * 3 + 2]); + } + } + } // End of setColors3 + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object, assuming + * 4 components (R, G, B, and A) per vertex. + */ + public void setColors4(float colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = null; + colors4 = new Color4f[colors.length / 4]; + for (int i = 0 ; i < colors.length / 4 ; i++) { + colors4[i] = new Color4f(colors[i * 4], + colors[i * 4 + 1], + colors[i * 4 + 2], + colors[i * 4 + 3]); + } + } + } // End of setColors4 + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object, assuming + * 3 components (R, G, and B) per vertex. + */ + public void setColors3(byte colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = new Color3f[colors.length / 3]; + colors4 = null; + for (int i = 0 ; i < colors.length / 3 ; i++) { + colors3[i] = + new Color3f((float)(colors[i * 3] & 0xff) / 255.0f, + (float)(colors[i * 3 + 1] & 0xff) / 255.0f, + (float)(colors[i * 3 + 2] & 0xff) / 255.0f); + } + } + } // End of setColors3 + + + + /** + * Sets the colors array. + * The points are copied into the GeometryInfo object, assuming + * 4 components (R, G, B, and A) per vertex. + */ + public void setColors4(byte colors[]) + { + if (colors == null) { + colors3 = null; + colors4 = null; + } else { + colors3 = null; + colors4 = new Color4f[colors.length / 4]; + for (int i = 0 ; i < colors.length / 4 ; i++) { + colors4[i] = + new Color4f((float)(colors[i * 4] & 0xff) / 255.0f, + (float)(colors[i * 4 + 1] & 0xff) / 255.0f, + (float)(colors[i * 4 + 2] & 0xff) / 255.0f, + (float)(colors[i * 4 + 3] & 0xff) / 255.0f); + } + } + } // End of setColors4 + + + + /** + * Retrieves a reference to the colors array. Will be either + * Color3f[] or Color4f[] depending on + * the type of the input data. Call + * getNumColorComponents() to find out which version is returned. + */ + public Object[] getColors() + { + if (colors3 != null) return colors3; + else return colors4; + } // End of getColors + + + + /** + * Returns the number of color data components stored per vertex + * in the current GeometryInfo object (3 for RGB or 4 for RGBA). + * If no colors are currently defined, 0 is returned. + */ + public int getNumColorComponents() + { + if (colors3 != null) return 3; + else if (colors4 != null) return 4; + else return 0; + } // End of getNumColorComponents + + + + /** + * Sets the normals array. + * No data copying is done because a reference to + * user data is used. + */ + public void setNormals(Vector3f normals[]) + { + this.normals = normals; + } // End of setNormals + + + + /** + * Sets the normals array. + * The points are copied into the GeometryInfo object. + */ + public void setNormals(float normals[]) + { + if (normals == null) this.normals = null; + else { + this.normals = new Vector3f[normals.length / 3]; + for (int i = 0 ; i < this.normals.length ; i++) { + this.normals[i] = new Vector3f(normals[i * 3], + normals[i * 3 + 1], + normals[i * 3 + 2]); + } + } + } // End of setNormals(float[]) + + + + /** + * Retrieves a reference to the normal array. + */ + public Vector3f[] getNormals() + { + return normals; + } // End of getNormals + + + + /** + * This method is used to specify the number of texture coordinate sets + * and the dimensionality of the texture coordinates. + * The number of texture coordinate sets must be specified to the GeometryInfo + * class before any of the sets are specified. The dimensionality of the + * texture coordinates may be 2, 3, or 4, corresponding to 2D, 3D, or 4D + * texture coordinates respectively.(All sets must have the same + * dimensionality.) The default is zero, 2D texture coordinate sets. + * This method should be called before any texture coordinate sets are + * specified because calling this method will delete all previously + * specified texture coordinate and texture coordinate index arrays + * associated with this GeometryInfo. For example: + *

+   *	geomInfo.setTextureCoordinateParams(2, 3);
+   *	geomInfo.setTextureCoordinates(0, tex0);
+   *	geomInfo.setTextureCoordinates(1, tex1);
+   *	geomInfo.setTextureCoordinateParams(1, 2);
+   *	geomInfo.getTexCoordSetCount();
+   * 
+ * The second call to setTextureCoordinateParams will erase all + * the texture coordinate arrays, so the subsequent call to + * getTexCoordSetCount will return 1. + * @param numSets The number of texture coordinate sets that will be + * specified for this GeometryInfo object. + * @param dim The dimensionality of the texture coordinates. Has to be 2, 3 + * or 4. + * @throws IllegalArgumentException if the dimensionality of the texture + * coordinates is not one of 2, 3 or 4. + */ + public void setTextureCoordinateParams(int numSets, int dim) + { + if (dim == 2) { + texCoordSets = new TexCoord2f[numSets][]; + } else if (dim == 3) { + texCoordSets = new TexCoord3f[numSets][]; + } else if (dim == 4) { + texCoordSets = new TexCoord4f[numSets][]; + } else { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo9")); + } + texCoordIndexSets = new int[numSets][]; + texCoordDim = dim; + texCoordSetCount = numSets; + } // End of setTextureCoordinateParams + + + + /** + * Returns the number of texture coordinate sets in this GeometryInfo. + * This value is set with setTextureCoordinateParams(). + * If setTextureCoordinateParams() + * has not been called, 0 is returned unless one of the deprecated + * texture coordinate methods has been called. Calling one of the + * deprecated texture coordinate methods sets the count to 1. + * The deprecated texture coordinate methods are those that don't + * take texCoordSet as the first parameter. + * @return the number of texture coordinate sets in this + * GeometryInfo. + */ + public int getTexCoordSetCount() { + return texCoordSetCount; + } + + + + /** + * Returns the number of texture coordinate components that are stored + * per vertex. Returns 2 for ST (2D), 3 for STR (3D), + * or 4 for STRQ (4D), aslo known as the "dimensionality" of the + * coordinates. This value is set with + * setTextureCoordinateParams(). If setTextureCoordinateParams() + * has not been called, 0 is returned unless one of the deprecated + * texture coordinate methods has been called. Calling one of the + * deprecated texture coordinate methods sets the dimensionality + * explicitly (if you called setTextureCoordinates(Point2f[]) then + * 2 is returned). + * The deprecated texture coordinate methods are those that don't + * take texCoordSet as the first parameter. + */ + public int getNumTexCoordComponents() + { + return texCoordDim; + } // End of getNumTexCoordComponents + + + + /** + * Sets the mapping between texture coordinate sets and texture units. + * See the + * + * GeometryArray constructor for further details. + *

Note: If the texCoordSetMap is not set, multi-texturing is + * turned off. Only the texture coordinate set at index 0 (if set) will be + * used. Any other sets specified by the GeometryInfo.setTextureCoordinate* + * methods will be ignored. + */ + public void setTexCoordSetMap(int map[]) { + texCoordSetMap = map; + } + + + + /** + * Returns a reference to the texture coordinate set map. + * See the + * + * GeometryArray constructor for further details. + */ + public int[] getTexCoordSetMap() { + return texCoordSetMap; + } + + + + /** + * Sets the 2D texture coordinates for the specified set. + * No data copying is done - a reference to user data is used. + * @param texCoordSet The texture coordinate set for which these + * coordinates are being specified. + * @param texCoords Array of 2D texture coordinates. + * @throws IllegalArgumentException if texCoordSet < 0 or + * texCoordSet >= texCoordSetCount, + * or the texture coordinate parameters were not previously set by + * calling setTextureCoordinateParams(texCoordSetCount, 2). + */ + public void setTextureCoordinates(int texCoordSet, TexCoord2f texCoords[]) + { + if (texCoordDim != 2) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo15")); + if ((texCoordSet >= texCoordSetCount) || (texCoordSet < 0)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo18")); + + texCoordSets[texCoordSet] = texCoords; + } // End of setTextureCoordinates(int, TexCoord3f[]) + + + + /** + * Sets the TextureCoordinates array by copying the data + * into the GeometryInfo object. + * This method sets the number of texture coordinate sets to 1, + * sets the dimensionality of the texture coordinates to 2, + * and sets the coordinates for texture coordinate set 0. + * @deprecated As of Java 3D 1.3 replaced by + * setTextureCoordinates(int texCoordSet, TexCoord2f coords[]) + */ + public void setTextureCoordinates(Point2f texCoords[]) + { + texCoordSetCount = 1; + texCoordDim = 2; + texCoordSets = new TexCoord2f[1][]; + if (texCoords != null) { + TexCoord2f[] tex = new TexCoord2f[texCoords.length]; + for (int i = 0 ; i < texCoords.length ; i++) + tex[i] = new TexCoord2f(texCoords[i]); + texCoordSets[0] = tex; + } + } // End of setTextureCoordinates(Point2f[]) + + + + /** + * Sets the texture coordinates array for the specified set. + * No data copying is done - a reference to user data is used. + * @param texCoordSet The texture coordinate set for which these coordinates + * are being specified. + * @param texCoords Array of 3D texture coordinates. + * @throws IllegalArgumentException if texCoordSet < 0 or + * texCoordSet >= texCoordSetCount, + * or the texture coordinate parameters were not previously set by + * calling setTextureCoordinateParams(texCoordSetCount, 3). + */ + public void setTextureCoordinates(int texCoordSet, TexCoord3f texCoords[]) + { + if (texCoordDim != 3) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo16")); + if ((texCoordSet >= texCoordSetCount) || (texCoordSet < 0)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo18")); + + texCoordSets[texCoordSet] = texCoords; + } // End of setTextureCoordinates(int, TexCoord3f[]) + + + + /** + * Sets the TextureCoordinates array by copying the data + * into the GeometryInfo object. + * This method sets the number of texture coordinate sets to 1, + * sets the dimensionality of the texture coordinates to 3, + * and sets the coordinates for texture coordinate set 0. + * @deprecated As of Java 3D 1.3 replaced by + * setTextureCoordinates(int texCoordSet, TexCoord3f coords[]) + */ + public void setTextureCoordinates(Point3f texCoords[]) + { + texCoordSetCount = 1; + texCoordDim = 3; + texCoordSets = new TexCoord3f[1][]; + if (texCoords != null) { + TexCoord3f[] tex = new TexCoord3f[texCoords.length]; + for (int i = 0 ; i < texCoords.length ; i++) + tex[i] = new TexCoord3f(texCoords[i]); + texCoordSets[0] = tex; + } + } // End of setTextureCoordinates(Point3f[]) + + + + /** + * Sets the texture coordinates array for the specified set. + * No data copying is done - a reference to user data is used. + * @param texCoordSet The texture coordinate set for which these coordinates + * are being specified. + * @param texCoords Array of 4D texture coordinates. + * @throws IllegalArgumentException if texCoordSet < 0 or + * texCoordSet >= texCoordSetCount, + * or the texture coordinate parameters were not previously set by + * calling setTextureCoordinateParams(texCoordSetCount, 4). + */ + public void setTextureCoordinates(int texCoordSet, TexCoord4f texCoords[]) { + if (texCoordDim != 4) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo17")); + if ((texCoordSet >= texCoordSetCount) || (texCoordSet < 0)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo18")); + + texCoordSets[texCoordSet] = texCoords; + } // End of setTextureCoordinates(int, TexCoord4f[]) + + + + /** + * Sets the texture coordinates array by copying the data into the + * GeometryInfo object. The number of sets and dimensionality of + * the sets must have been set previously with + * setTextureCoordinateParams(texCoordSetCount, dim). + * @param texCoordSet The texture coordinate set for which these coordinates + * are being specified. + * @param texCoords The float array of texture coordinates. For n texture + * coordinates with dimensionality d, there must be d*n floats in the array. + * @throws IllegalArgumentException if texCoordSet < 0 or + * texCoordSet >= texCoordSetCount, + * or the texture coordinate parameters were not previously set by + * calling setTextureCoordinateParams. + */ + public void setTextureCoordinates(int texCoordSet, float texCoords[]) + { + if ((texCoords.length % texCoordDim) != 0) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo2")); + + // Copy the texCoords into this GeometryInfo object + if (texCoordDim == 2) { + TexCoord2f tcoords[] = new TexCoord2f[texCoords.length / 2]; + for (int i = 0 ; i < tcoords.length ; i++) + tcoords[i] = new TexCoord2f(texCoords[i * 2], + texCoords[i * 2 + 1]); + setTextureCoordinates(texCoordSet, tcoords); + } else if (texCoordDim == 3) { + TexCoord3f tcoords[] = new TexCoord3f[texCoords.length / 3]; + for (int i = 0 ; i < tcoords.length ; i++) + tcoords[i] = new TexCoord3f(texCoords[i * 3], + texCoords[i * 3 + 1], + texCoords[i * 3 + 2]); + setTextureCoordinates(texCoordSet, tcoords); + } else if (texCoordDim == 4) { + TexCoord4f tcoords[] = new TexCoord4f[texCoords.length / 4]; + for (int i = 0 ; i < tcoords.length ; i++) + tcoords[i] = new TexCoord4f(texCoords[i * 4], + texCoords[i * 4 + 1], + texCoords[i * 4 + 2], + texCoords[i * 4 + 3]); + setTextureCoordinates(texCoordSet, tcoords); + } else { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo21")); + } + } // End of setTextureCoordinates(int, float[]) + + + + /** + * Sets the texture coordinates array by copying the data + * into the GeometryInfo object, assuming two numbers + * (S and T) per vertex. + * This method sets the number of texture coordinate sets to 1, + * sets the dimensionality of the texture coordinates to 2, + * and sets the coordinates for texture coordinate set 0. + * @deprecated As of Java 3D 1.3 replaced by + * setTextureCoordinates(int texCoordSet, float texCoords[]) + */ + public void setTextureCoordinates2(float texCoords[]) + { + texCoordSetCount = 1; + texCoordDim = 2; + texCoordSets = new TexCoord2f[1][]; + setTextureCoordinates(0, texCoords); + } // End of setTextureCoordinates2(float[]) + + + + /** + * Sets the TextureCoordinates array by copying the data + * into the GeometryInfo object, assuming three numbers + * (S, T, & R) per vertex. + * This method sets the number of texture coordinate sets to 1, + * sets the dimensionality of the texture coordinates to 3, + * and sets the coordinates for texture coordinate set 0. + * @deprecated As of Java 3D 1.3 replaced by + * setTextureCoordinates(int texCoordSet, float texCoords[]) + */ + public void setTextureCoordinates3(float texCoords[]) + { + texCoordSetCount = 1; + texCoordDim = 3; + texCoordSets = new TexCoord3f[1][]; + setTextureCoordinates(0, texCoords); + } // End of setTextureCoordinates3(float[]) + + + + /** + * Returns a reference to the indicated texture coordinate array. + * The return type will be TexCoord2f[], TexCoord3f[] + * , or TexCoord4f[] depending on the + * current dimensionality of the texture coordinates in the GeometryInfo + * object. Use getNumTexCoordComponents() to find out which + * version is returned. + * @param texCoordSet The index of the texture coordinate set to + * retrieve. + * @return An array of texture coordinates at the specified index + * @throws IllegalArgumentException If texCoordSet < 0 + * or texCoordSet >= texCoordSetCount + */ + public Object[] getTextureCoordinates(int texCoordSet) + { + if ((texCoordSet >= texCoordSetCount) || (texCoordSet < 0)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo18")); + return texCoordSets[texCoordSet]; + } // End of getTextureCoordinates(int) + + + + /** + * Retrieves a reference to texture coordinate set 0. + * The return type will be TexCoord2f[], TexCoord3f[] + * , or TexCoord4f[] depending on the + * current dimensionality of the texture coordinates in the GeometryInfo + * object. Use getNumTexCoordComponents() to find out which + * version is returned. Equivalent to getTextureCoordinates(0). + * @return An array of texture coordinates for set 0. + * @deprecated As of Java 3D 1.3 replaced by + * getTextureCoordinates(int texCoordSet) + */ + public Object[] getTextureCoordinates() + { + return texCoordSets[0]; + } // End of getTextureCoordinates() + + + + /** + * Sets the array of indices into the Coordinate array. + * No data copying is done - a reference to user data is used. + */ + public void setCoordinateIndices(int coordinateIndices[]) + { + this.coordinateIndices = coordinateIndices; + } // End of setCoordinateIndices + + + + /** + * Retrieves a reference to the array of indices into the + * coordinate array.

+ * + * This method should be considered for advanced users only. + * Novice users should just use getGeometryArray() to retrieve + * their data so that the internal format of GeometryInfo is + * of no concern.

+ * + * Depending on which of the utility routines you've called + * on your GeometryInfo object, the results may not be what you + * expect. If you've called the Stripifier, your GeometryInfo + * object's Primitive has been changed to indexed TRIANGLE_STRIP_ARRAY + * and your data will be formatted accordingly. Similarly, if + * you've called the Triangulator, your data is in indexed + * TRIANGLE_ARRAY format. Generating normals with the NormalGenerator + * utility will convert your data to indexed TRIANGLE_ARRAY also, + * but if you call getGeometryArray without calling the Stripifier or + * Triangulator, your data will be converted back to the original + * primitive type when creating the GeometryArray object to pass + * back. However, if your creaseAngle was not Math.PI (no creases - + * smooth shading), then the introduction of + * creases into your model may have split primitives, lengthening + * the StripCounts and index arrays from your original data. + */ + public int[] getCoordinateIndices() + { + return coordinateIndices; + } // End of getCoordinateIndices + + + + /** + * Sets the array of indices into the Color array. + * No data copying is done - a reference to user data is used. + */ + public void setColorIndices(int colorIndices[]) + { + this.colorIndices = colorIndices; + } // End of setColorIndices + + + + /** + * Retrieves a reference to the array of indices into the + * color array.

+ * + * This method should be considered for advanced users only. + * Novice users should just use getGeometryArray() to retrieve + * their data so that the internal format of GeometryInfo is + * of no concern.

+ * + * Depending on which of the utility routines you've called + * on your GeometryInfo object, the results may not be what you + * expect. If you've called the Stripifier, your GeometryInfo + * object's Primitive has been changed to indexed TRIANGLE_STRIP_ARRAY + * and your data will be formatted accordingly. Similarly, if + * you've called the Triangulator, your data is in indexed + * TRIANGLE_ARRAY format. Generating normals with the NormalGenerator + * utility will convert your data to indexed TRIANGLE_ARRAY also, + * but if you call getGeometryArray without calling the Stripifier or + * Triangulator, your data will be converted back to the original + * primitive type when creating the GeometryArray object to pass + * back. However, if your creaseAngle was not Math.PI (no creases - + * smooth shading), then the introduction of + * creases into your model may have split primitives, lengthening + * the StripCounts and index arrays from your original data. + */ + public int[] getColorIndices() + { + return colorIndices; + } // End of getColorIndices + + + + /** + * Sets the array of indices into the Normal array. + * No data copying is done - a reference to user data is used. + */ + public void setNormalIndices(int normalIndices[]) + { + this.normalIndices = normalIndices; + + } // End of setNormalIndices + + + + /** + * Retrieves a reference to the array of indices into the + * Normal array.

+ * + * This method should be considered for advanced users only. + * Novice users should just use getGeometryArray() to retrieve + * their data so that the internal format of GeometryInfo is + * of no concern.

+ * + * Depending on which of the utility routines you've called + * on your GeometryInfo object, the results may not be what you + * expect. If you've called the Stripifier, your GeometryInfo + * object's Primitive has been changed to indexed TRIANGLE_STRIP_ARRAY + * and your data will be formatted accordingly. Similarly, if + * you've called the Triangulator, your data is in indexed + * TRIANGLE_ARRAY format. Generating normals with the NormalGenerator + * utility will convert your data to indexed TRIANGLE_ARRAY also, + * but if you call getGeometryArray without calling the Stripifier or + * Triangulator, your data will be converted back to the original + * primitive type when creating the GeometryArray object to pass + * back. However, if your creaseAngle was not Math.PI (no creases - + * smooth shading), then the introduction of + * creases into your model may have split primitives, lengthening + * the StripCounts and index arrays from your original data. + */ + public int[] getNormalIndices() + { + return normalIndices; + } // End of getNormalIndices + + + + /** + * Sets one of the texture coordinate index arrays. + * No data copying is done - a reference to user data is used. + * @param texCoordSet The texture coordinate set for which these coordinate + * indices are being specified. + * @param texIndices The integer array of indices into the specified texture + * coordinate set + * @throws IllegalArgumentException If texCoordSet < 0 or + * texCoordSet >= texCoordSetCount. + */ + public void setTextureCoordinateIndices(int texCoordSet, int texIndices[]) + { + if ((texCoordSet >= texCoordSetCount) || (texCoordSet < 0)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo18")); + + // Texture coordinates are indexed + texCoordIndexSets[texCoordSet] = texIndices; + } // End of setTextureCoordinateIndices(int, int[]) + + + + /** + * Sets the array of indices into texture coordinate set 0. Do not + * call this method if you are using more than one set of texture + * coordinates. + * No data is copied - a reference to the user data is used. + * @deprecated As of Java 3D 1.3 replaced by + * setTextureCoordinateIndices(int texCoordSet, int indices[]) + * @throws IllegalArgumentException If texCoordSetCount > 1. + */ + public void setTextureCoordinateIndices(int texIndices[]) + { + if (texCoordSetCount > 1) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo1")); + texCoordIndexSets = new int[1][]; + texCoordIndexSets[0] = texIndices; + } // End of setTextureCoordinateIndices(int[]) + + + + /** + * Retrieves a reference to the specified array of texture + * coordinate indices.

+ * + * This method should be considered for advanced users only. + * Novice users should just use getGeometryArray() to retrieve + * their data so that the internal format of GeometryInfo is + * of no concern.

+ * + * Depending on which of the utility routines you've called + * on your GeometryInfo object, the results may not be what you + * expect. If you've called the Stripifier, your GeometryInfo + * object's Primitive has been changed to indexed TRIANGLE_STRIP_ARRAY + * and your data will be formatted accordingly. Similarly, if + * you've called the Triangulator, your data is in indexed + * TRIANGLE_ARRAY format. Generating normals with the NormalGenerator + * utility will convert your data to indexed TRIANGLE_ARRAY also, + * but if you call getGeometryArray without calling the Stripifier or + * Triangulator, your data will be converted back to the original + * primitive type when creating the GeometryArray object to pass + * back. However, if your creaseAngle was not Math.PI (no creases - + * smooth shading), then the introduction of + * creases into your model may have split primitives, lengthening + * the StripCounts and index arrays from your original data. + * @param texCoordSet The texture coordinate index set to be + * retrieved. + * @return Integer array of the texture coordinate indices for the specified + * set. + */ + public int[] getTextureCoordinateIndices(int texCoordSet) { + return texCoordIndexSets[texCoordSet]; + } + + + /** + * Returns a reference to texture coordinate index set 0. + * Equivalent to + * getTextureCoordinateIndices(0). + * @deprecated As of Java 3D 1.3 replaced by + * int[] getTextureCoordinateIndices(int texCoordSet) + * @return Integer array of the texture coordinate indices for set 0 + */ + public int[] getTextureCoordinateIndices() + { + if (texCoordIndexSets == null) return null; + return texCoordIndexSets[0]; + } // End of getTextureCoordinateIndices() + + + + /** + * Sets the array of strip counts. If index lists have been set for + * this GeomteryInfo object then the data is indexed and the stripCounts + * are like stripIndexCounts. If no index lists have been set then + * the data is non-indexed and the stripCounts are like + * stripVertexCounts. + * @see GeometryStripArray#GeometryStripArray(int, int, + * int[] stripVertexCounts) + * @see IndexedGeometryStripArray#IndexedGeometryStripArray(int, int, int, + * int[] stripIndexCounts) + */ + public void setStripCounts(int stripCounts[]) + { + this.stripCounts = stripCounts; + } // End of setStripCounts + + + + /** + * Retrieves a reference to the array of stripCounts.

+ * + * This method should be considered for advanced users only. + * Novice users should just use getGeometryArray() to retrieve + * their data so that the internal format of GeometryInfo is + * of no concern.

+ * + * Depending on which of the utility routines you've called + * on your GeometryInfo object, the results may not be what you + * expect. If you've called the Stripifier, your GeometryInfo + * object's Primitive has been changed to indexed TRIANGLE_STRIP_ARRAY + * and your data will be formatted accordingly. Similarly, if + * you've called the Triangulator, your data is in indexed + * TRIANGLE_ARRAY format. Generating normals with the NormalGenerator + * utility will convert your data to indexed TRIANGLE_ARRAY also, + * but if you call getGeometryArray without calling the Stripifier or + * Triangulator, your data will be converted back to the original + * primitive type when creating the GeometryArray object to pass + * back. However, if your creaseAngle was not Math.PI (no creases - + * smooth shading), then the introduction of + * creases into your model may have split primitives, lengthening + * the StripCounts and index arrays from your original data. + */ + public int[] getStripCounts() + { + return stripCounts; + } // End of getStripCounts + + + + /** + * Sets the list of contour counts. Only used with the POLYGON_ARRAY + * primitive. Polygons can be made of several vertex lists + * called contours. The first list is the polygon, and + * subsequent lists are "holes" that are removed from the + * polygon. All of the holes must be contained entirely + * within the polygon. + */ + public void setContourCounts(int contourCounts[]) + { + this.contourCounts = contourCounts; + } // End of setContourCounts + + + + /** + * Retrieves a reference to the array of contourCounts. + */ + public int[] getContourCounts() + { + return contourCounts; + } // End of getContourCounts + + + + /* + * This routine will return an index list for any array of objects. + */ + int[] getListIndices(Object list[]) + { + // Create list of indices to return + int indices[] = new int[list.length]; + + // Create hash table with initial capacity equal to the number + // of components (assuming about half will be duplicates) + HashMap table = new HashMap(list.length); + + Integer idx; + for (int i = 0 ; i < list.length ; i++) { + + // Find index associated with this object + idx = (Integer)table.get(list[i]); + + if (idx == null) { + // We haven't seen this object before + indices[i] = i; + + // Put into hash table and remember the index + table.put(list[i], new Integer(i)); + + } else { + // We've seen this object + indices[i] = idx.intValue(); + } + } + + return indices; + } // End of getListIndices + + + + // Class to hash 'size' integers + private class IndexRow { + int[] val; + int size; + private static final int HASHCONST = 0xBABEFACE; + + public int hashCode() + { + int bits = 0; + for (int i = 0 ; i < size ; i++) { + bits ^= (bits * HASHCONST) << 2; + } + return bits; + } // End of IndexRow.hashCode + + public boolean equals(Object obj) + { + for (int i = 0 ; i < size ; i++) { + if (((IndexRow)obj).get(i) != val[i]) return false; + } + return true; + } // End of IndexRow.equals() + + public int get(int index) + { + return val[index]; + } // End of IndexRow.get + + public void set(int index, int value) + { + val[index] = value; + } // End of IndexRow.set + + IndexRow(int numColumns) + { + size = numColumns; + val = new int[size]; + } // End of IndexRow constructor + } // End of class IndexRow + + + + /** + * Create index lists for all data lists. + * Identical data entries are guaranteed to + * use the same index value. Does not remove unused data values + * from the object - call compact() to do this. + * @param useCoordIndexOnly Reformat the data into the + * GeometryArray.USE_COORD_INDEX_ONLY format where there is only + * one index list. If the data is already in the USE_COORD_INDEX_ONLY + * format, sending false (or calling indexify()) will change + * it to the normal indexed format. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void indexify(boolean useCoordIndexOnly) + { + checkForBadData(); + + if (useCoordIndexOnly) { + // Return if already in this format + if (coordOnly) return; + + // Start from normal indexed format + indexify(false); + + // Reformat data to USE_COORD_INDEX_ONLY format + // Need to make an index into the index lists using each + // row of indexes as one value + + // First, find out how many index lists there are; + int numLists = 1; // Always have coordinates + if (colorIndices != null) numLists++; + if (normalIndices != null) numLists++; + numLists += texCoordSetCount; + + // Make single array containing all indices + int n = coordinateIndices.length; + IndexRow[] ir = new IndexRow[n]; + int j; + for (int i = 0 ; i < n ; i++) { + ir[i] = new IndexRow(numLists); + j = 0; + ir[i].set(j++, coordinateIndices[i]); + if (colorIndices != null) ir[i].set(j++, colorIndices[i]); + if (normalIndices != null) ir[i].set(j++, normalIndices[i]); + for (int k = 0 ; k < texCoordSetCount ; k++) { + ir[i].set(j++, texCoordIndexSets[k][i]); + } + } + + // Get index into that array + int[] coordOnlyIndices = getListIndices(ir); + + // Get rid of duplicate rows + int newInd[] = new int[coordOnlyIndices.length]; + ir = (IndexRow[])compactData(coordOnlyIndices, ir, newInd); + coordOnlyIndices = newInd; + + // Reformat data lists to correspond to new index + + // Allocate arrays to hold reformatted data + Point3f[] newCoords = new Point3f[ir.length]; + Color3f[] newColors3 = null; + Color4f[] newColors4 = null; + Vector3f[] newNormals = null; + Object newTexCoordSets[][] = null; + if (colors3 != null) newColors3 = new Color3f[ir.length]; + else if (colors4 != null) newColors4 = new Color4f[ir.length]; + if (normals != null) newNormals = new Vector3f[ir.length]; + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordDim == 2) { + if (i == 0) newTexCoordSets = new TexCoord2f[texCoordSetCount][]; + newTexCoordSets[i] = new TexCoord2f[ir.length]; + } else if (texCoordDim == 3) { + if (i == 0) newTexCoordSets = new TexCoord3f[texCoordSetCount][]; + newTexCoordSets[i] = new TexCoord3f[ir.length]; + } else if (texCoordDim == 4) { + if (i == 0) newTexCoordSets = new TexCoord4f[texCoordSetCount][]; + newTexCoordSets[i] = new TexCoord4f[ir.length]; + } + } + + // Copy data into new arrays + n = ir.length; + for (int i = 0 ; i < n ; i++) { + j = 0; + newCoords[i] = coordinates[(ir[i]).get(j++)]; + if (colors3 != null) { + newColors3[i] = colors3[(ir[i]).get(j++)]; + } else if (colors4 != null) { + newColors4[i] = colors4[(ir[i]).get(j++)]; + } + if (normals != null) newNormals[i] = normals[(ir[i]).get(j++)]; + for (int k = 0 ; k < texCoordSetCount ; k++) { + newTexCoordSets[k][i] = texCoordSets[k][(ir[i]).get(j++)]; + } + } + + // Replace old arrays with new arrays + coordinates = newCoords; + colors3 = newColors3; + colors4 = newColors4; + normals = newNormals; + texCoordSets = newTexCoordSets; + coordinateIndices = coordOnlyIndices; + colorIndices = null; + normalIndices = null; + texCoordIndexSets = new int[texCoordSetCount][]; + + coordOnly = true; + } else if (coordOnly) { + // Need to change from useCoordIndexOnly format to normal + // indexed format. Should make a more efficient implementation + // later. + + int n = coordinateIndices.length; + if ((colors3 != null) || (colors4 != null)) { + colorIndices = new int[n]; + for (int i = 0 ; i < n ; i++) colorIndices[i] = coordinateIndices[i]; + } + if (normals != null) { + normalIndices = new int[n]; + for (int i = 0 ; i < n ; i++) normalIndices[i] = coordinateIndices[i]; + } + texCoordIndexSets = new int[texCoordSetCount][]; + for (int i = 0 ; i < texCoordSetCount ; i++) { + texCoordIndexSets[i] = new int[n]; + for (int j = 0 ; j < n ; j++) { + texCoordIndexSets[i][j] = coordinateIndices[j]; + } + } + coordOnly = false; + } else { + + // No need to indexify if already indexed + if (coordinateIndices != null) return; + + coordinateIndices = getListIndices(coordinates); + + if (colors3 != null) colorIndices = getListIndices(colors3); + else if (colors4 != null) colorIndices = getListIndices(colors4); + + if (normals != null) normalIndices = getListIndices(normals); + + texCoordIndexSets = new int[texCoordSetCount][]; + for(int i = 0 ; i < texCoordSetCount ; i++) { + texCoordIndexSets[i] = getListIndices(texCoordSets[i]); + } + + coordOnly = false; + } + + if ((DEBUG & 1) == 1) { + System.out.println("Coordinate Array:"); + for (int i = 0 ; i < coordinates.length ; i++) { + System.out.println(" " + i + " " + coordinates[i] + + " " + coordinates[i].hashCode()); + } + System.out.println("Index array:"); + for (int i = 0 ; i < coordinateIndices.length ; i++) { + System.out.println(" " + i + " " + coordinateIndices[i]); + } + } + + } // End of indexify + + + + public void indexify() + { + indexify(false); + } // End of indexify() + + + + /** + * Allocates an array of the same type as the input type. This allows us to + * use a generic compactData method. + * + * @param data Array of coordinate, color, normal or texture coordinate data + * The data can be in one of the following formats - Point3f, Color3f, + * Color4f, TexCoord2f, TexCoord3f, TexCoord4f. + * + * @param num The size of the array to be allocated + * + * @return An array of size num of the same type as the input type + * + * @exception IllegalArgumentException if the input array is not one of the + * types listed above. + */ + Object[] allocateArray(Object data[], int num) { + Object newData[] = null; + if (data instanceof javax.vecmath.Point3f[]) { + newData = new Point3f[num]; + } else if (data instanceof javax.vecmath.Vector3f[]) { + newData = new Vector3f[num]; + } else if (data instanceof javax.vecmath.Color3f[]) { + newData = new Color3f[num]; + } else if (data instanceof javax.vecmath.Color4f[]) { + newData = new Color4f[num]; + } else if (data instanceof javax.vecmath.TexCoord2f[]) { + newData = new TexCoord2f[num]; + } else if (data instanceof javax.vecmath.TexCoord3f[]) { + newData = new TexCoord3f[num]; + } else if (data instanceof javax.vecmath.TexCoord4f[]) { + newData = new TexCoord4f[num]; + } else if (data instanceof IndexRow[]) { + // Hack so we can use compactData for coordIndexOnly + newData = new IndexRow[num]; + } else throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo9")); + return newData; + } // End of allocateArray + + + + /** + * Generic method that compacts (ie removes unreferenced/duplicate data) + * any type of indexed data. + * Used to compact coordinate, color, normal and texture coordinate data. + * @param indices Array of indices + * @param data Array of coordinate, color, normal or texture coordinate data + * The data can be in one of the following formats - Point3f, Color3f, + * Color4f, TexCoord2f, TexCoord3f, TexCoord4f. + * @param newInd The new array of indexes after the data has been compacted. + * This must be allocated by the calling method. On return, this array will + * contain the new index data. The size of this array must be equal to + * indices.length + * @return Array of the data with unreferenced and duplicate entries removed. + * The return type will be the same as the type that was passed in data. + */ + // TODO: Remove duplicate entries in data lists. + private Object[] compactData(int indices[], Object data[], int newInd[]) { + Object newData[] = null; + /* + * This is a three step process. + * First, find out how many unique indexes are used. This + * will be the size of the new data array. + */ + int numUnique = 0; + int translationTable[] = new int[data.length]; + for (int i = 0 ; i < indices.length ; i++) { + if (translationTable[indices[i]] == 0) { + + numUnique++; + translationTable[indices[i]] = 1; + } + } + /* + * Second, build the new data list. Remember the new indexes so + * we can use the table to translate the old indexes to the new + */ + newData = allocateArray(data, numUnique); + int newIdx = 0; + for (int i = 0 ; i < translationTable.length ; i++) { + if (translationTable[i] != 0) { + newData[newIdx] = data[i]; + translationTable[i] = newIdx++; + } + } + /* + * Third, make the new index list + */ + for (int i = 0 ; i < indices.length ; i++) { + newInd[i] = translationTable[indices[i]]; + } + return newData; + } // End of compactData + + + + /** + * Remove unused data from an indexed dataset. + * Indexed data may contain data entries that are never referenced by + * the dataset. This routine will remove those entries where + * appropriate and renumber the indices to match the new values. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void compact() + { + checkForBadData(); + + // Only usable on indexed data + if (coordinateIndices == null) return; + + // USE_COORD_INDEX_ONLY never has unused data + if (coordOnly) return; + + int newInd[] = new int[coordinateIndices.length]; + coordinates = + (Point3f[])compactData(coordinateIndices, coordinates, newInd); + coordinateIndices = newInd; + + if (colorIndices != null) { + newInd = new int[colorIndices.length]; + if (colors3 != null) + colors3 = (Color3f[])compactData(colorIndices, colors3, newInd); + else if (colors4 != null) + colors4 = (Color4f[])compactData(colorIndices, colors4, newInd); + colorIndices = newInd; + } + + if (normalIndices != null) { + newInd = new int[normalIndices.length]; + normals = (Vector3f[])compactData(normalIndices, normals, newInd); + normalIndices = newInd; + } + + for (int i = 0 ; i < texCoordSetCount ; i++) { + newInd = new int[texCoordIndexSets[i].length]; + texCoordSets[i] = compactData(texCoordIndexSets[i], + texCoordSets[i], newInd); + texCoordIndexSets[i] = newInd; + } + } // End of compact + + + + /** + * Check the data to make sure everything's consistent. + */ + private void checkForBadData() { + boolean badData = false; + + // + // Coordinates are required + // + if (coordinates == null) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo3")); + } + + // + // Check for indices with no data + // + if ((colors3 == null) && (colors4 == null) && (colorIndices != null)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo4")); + if ((normals == null) && (normalIndices != null)) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo11")); + + // + // Make sure all TextureCoordinate data is set (indices or not) + // + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordSets[i] == null) + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo10")); + } + + // + // Check for Missing Index lists + // + boolean texInds = false; // Indicates whether we have texcoord indices + if (texCoordIndexSets != null) { + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordIndexSets[i] != null) texInds = true; + } + } + if ((coordinateIndices != null) || + (colorIndices != null) || + (normalIndices != null) || + texInds) { + // At least one index list is present, so they all must be + // present (unless coordOnly) + if (coordinateIndices == null) badData = true; + else if (coordOnly) { + if ((colorIndices != null) || + (normalIndices != null) || + (texInds == true)) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo20")); + } + } else if (((colors3 != null) || (colors4 != null)) && + (colorIndices == null)) badData = true; + else if ((normals != null) && (normalIndices == null)) badData = true; + else if ((texCoordSetCount > 0) && !texInds) badData = true; + if (badData) throw new + IllegalArgumentException(J3dUtilsI18N.getString("GeometryInfo19")); + } + + // + // Make sure index lists are all the same length + // + if ((coordinateIndices != null) && (!coordOnly)) { + if (((colors3 != null) || (colors4 != null)) && + (colorIndices.length != coordinateIndices.length)) + badData = true; + else if ((normals != null) && + (normalIndices.length != coordinateIndices.length)) + badData = true; + else { + //Check all texCoord indices have the same length + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordIndexSets[i].length != coordinateIndices.length) { + badData = true; + break; + } + } + } + if (badData) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo5")); + } + } + + // + // For stripped primitives, make sure we have strip counts + // + if ((prim == TRIANGLE_STRIP_ARRAY) || + (prim == TRIANGLE_FAN_ARRAY) || + (prim == POLYGON_ARRAY)) { + if (stripCounts == null) badData = true; + } else if (stripCounts != null) badData = true; + if (badData) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo6")); + } + + // Find out how much data we have + int count; + if (coordinateIndices == null) count = coordinates.length; + else count = coordinateIndices.length; + + // + // Make sure sum of strip counts equals indexCount (or vertexCount) + // and check to make sure triangles and quads have the right number + // of vertices + // + if ((prim == TRIANGLE_STRIP_ARRAY) || + (prim == TRIANGLE_FAN_ARRAY) || + (prim == POLYGON_ARRAY)) { + int sum = 0; + for (int i = 0 ; i < stripCounts.length ; i++) { + sum += stripCounts[i]; + } + if (sum != count) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo7")); + } + } else if (prim == TRIANGLE_ARRAY) { + if (count % 3 != 0) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo12")); + } + } else if (prim == QUAD_ARRAY) { + if (count % 4 != 0) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo13")); + } + } + + // + // For polygons, make sure the contours add up. + // + if (prim == POLYGON_ARRAY) { + if (contourCounts != null) { + int c = 0; + for (int i = 0 ; i < contourCounts.length ; i++) + c += contourCounts[i]; + if (c != stripCounts.length) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo8")); + } + } + } else { + if (contourCounts != null) { + throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfo14")); + } + } + } // End of checkForBadData + + + + /** + * Get rid of index lists by reorganizing data into an un-indexed + * format. Does nothing if no index lists are set. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void unindexify() { + checkForBadData(); + if (coordinateIndices != null) { + // Switch from USE_COORD_INDEX_ONLY format + if (coordOnly) indexify(false); + + coordinates = + (Point3f[])unindexifyData(coordinates, coordinateIndices); + coordinateIndices = null; + + if (colors3 != null) { + colors3 = (Color3f[])unindexifyData(colors3, colorIndices); + } else if (colors4 != null) { + colors4 = (Color4f[])unindexifyData(colors4, colorIndices); + } + colorIndices = null; + + if (normals != null) { + normals = (Vector3f[])unindexifyData(normals, normalIndices); + normalIndices = null; + } + + for (int i = 0 ; i < texCoordSetCount ; i++) + texCoordSets[i] = unindexifyData(texCoordSets[i], + texCoordIndexSets[i]); + texCoordIndexSets = new int[texCoordSetCount][]; + } + } // End of unindexify + + + + /** + * Generic unindexify method. Can unindex data in any of the following + * formats Point3f, Color3f, Color4f, Vector3f, TexCoord2f, TexCoord3f, + * TexCoord4f. + */ + private Object[] unindexifyData(Object data[], int index[]) + { + Object newData[] = allocateArray(data, index.length); + for (int i = 0 ; i < index.length ; i++) { + newData[i] = data[index[i]]; + } + return newData; + } // End of unindexifyData + + + + /** + * Calculate vertexFormat based on data. + */ + private int getVertexFormat() + { + int vertexFormat = GeometryArray.COORDINATES; + + if (colors3 != null) vertexFormat |= GeometryArray.COLOR_3; + else if (colors4 != null) vertexFormat |= GeometryArray.COLOR_4; + + if (normals != null) vertexFormat |= GeometryArray.NORMALS; + + if (texCoordDim == 2) + vertexFormat |= GeometryArray.TEXTURE_COORDINATE_2; + else if (texCoordDim == 3) + vertexFormat |= GeometryArray.TEXTURE_COORDINATE_3; + else if (texCoordDim == 4) + vertexFormat |= GeometryArray.TEXTURE_COORDINATE_4; + + return vertexFormat; + } // End of getVertexFormat + + + + /** + * Calculate vertexCount based on data + */ + private int getVertexCount() + { + int vertexCount = coordinates.length; + + if (colors3 != null) { + if (colors3.length > vertexCount) vertexCount = colors3.length; + } else if (colors4 != null) { + if (colors4.length > vertexCount) vertexCount = colors4.length; + } + + if (normals != null) { + if (normals.length > vertexCount) vertexCount = normals.length; + } + + // Find max length tex coord set + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordSets[i].length > vertexCount) + vertexCount = texCoordSets[i].length; + } + + return vertexCount; + } // End of getVertexCount + + + + /** + * Converts an array of Tuple2f, Tuple3f, or Tuple4f values into + * an array of floats. Assumes array is not null. Returns null + * if array is not Tuple2f, Tuple3f, or Tuple4f. Used by fillIn() + * for BY_REFERENCE not INTERLEAVED geometry. + */ + private float[] vecmathToFloat(Object[] ar) + { + if (ar[0] instanceof Tuple2f) { + float[] p = new float[ar.length * 2]; + Tuple2f[] a = (Tuple2f[])ar; + for (int i = 0 ; i < ar.length ; i++) { + p[i * 2] = a[i].x; + p[i * 2 + 1] = a[i].y; + } + return p; + } else if (ar[0] instanceof Tuple3f) { + float[] p = new float[ar.length * 3]; + Tuple3f[] a = (Tuple3f[])ar; + for (int i = 0 ; i < ar.length ; i++) { + p[i * 3] = a[i].x; + p[i * 3 + 1] = a[i].y; + p[i * 3 + 2] = a[i].z; + } + return p; + } else if (ar[0] instanceof Tuple4f) { + float[] p = new float[ar.length * 4]; + Tuple4f[] a = (Tuple4f[])ar; + for (int i = 0 ; i < ar.length ; i++) { + p[i * 4] = a[i].x; + p[i * 4 + 1] = a[i].y; + p[i * 4 + 2] = a[i].z; + p[i * 4 + 3] = a[i].w; + } + return p; + } + return null; + } // End of vecmathToFloat + + + + /** + * Fill in the GeometryArray object. Used by getGeometryArray and + * getIndexedGeometryArray. checkForBadData has already been called. + */ + private void fillIn(GeometryArray ga, boolean byRef, boolean interleaved, + boolean nio) + { + if (interleaved) { + // Calculate number of words per vertex + int wpv = 3; // Always have coordinate data + if (normals != null) wpv += 3; + if (colors3 != null) wpv += 3; + else if (colors4 != null) wpv += 4; + wpv += (texCoordSetCount * texCoordDim); + + // Build array of interleaved data + float[] d = new float[wpv * coordinates.length]; + + // Fill in the array + int offset = 0; + for (int i = 0 ; i < coordinates.length ; i++) { + if (texCoordDim == 2) { + for (int j = 0 ; j < texCoordSetCount ; j++) { + d[offset++] = ((TexCoord2f)texCoordSets[j][i]).x; + d[offset++] = ((TexCoord2f)texCoordSets[j][i]).y; + } + } else if (texCoordDim == 3) { + for (int j = 0 ; j < texCoordSetCount ; j++) { + d[offset++] = ((TexCoord3f)texCoordSets[j][i]).x; + d[offset++] = ((TexCoord3f)texCoordSets[j][i]).y; + d[offset++] = ((TexCoord3f)texCoordSets[j][i]).z; + } + } else if (texCoordDim == 4) { + for (int j = 0 ; j < texCoordSetCount ; j++) { + d[offset++] = ((TexCoord4f)texCoordSets[j][i]).x; + d[offset++] = ((TexCoord4f)texCoordSets[j][i]).y; + d[offset++] = ((TexCoord4f)texCoordSets[j][i]).z; + d[offset++] = ((TexCoord4f)texCoordSets[j][i]).w; + } + } + + if (colors3 != null) { + d[offset++] = colors3[i].x; + d[offset++] = colors3[i].y; + d[offset++] = colors3[i].z; + } else if (colors4 != null) { + d[offset++] = colors4[i].x; + d[offset++] = colors4[i].y; + d[offset++] = colors4[i].z; + d[offset++] = colors4[i].w; + } + + if (normals != null) { + d[offset++] = normals[i].x; + d[offset++] = normals[i].y; + d[offset++] = normals[i].z; + } + + d[offset++] = coordinates[i].x; + d[offset++] = coordinates[i].y; + d[offset++] = coordinates[i].z; + } + // Register reference to array of interleaved data + if (nio) { + ByteBufferWrapper b = ByteBufferWrapper.allocateDirect(d.length * 4); + FloatBufferWrapper f = + b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(d); + ga.setInterleavedVertexBuffer(f.getJ3DBuffer()); + } else ga.setInterleavedVertices(d); + } else if (nio) { + + ByteBufferWrapper b = + ByteBufferWrapper.allocateDirect(coordinates.length * 4 * 3); + FloatBufferWrapper f = + b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(vecmathToFloat(coordinates)); + ga.setCoordRefBuffer(f.getJ3DBuffer()); + + if (colors3 != null) { + b = ByteBufferWrapper.allocateDirect(colors3.length * 4 * 3); + f = b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(vecmathToFloat(colors3)); + ga.setColorRefBuffer(f.getJ3DBuffer()); + } else if (colors4 != null) { + b = ByteBufferWrapper.allocateDirect(colors4.length * 4 * 4); + f = b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(vecmathToFloat(colors4)); + ga.setColorRefBuffer(f.getJ3DBuffer()); + } + + if (normals != null) { + b = ByteBufferWrapper.allocateDirect(normals.length * 4 * 3); + f = b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(vecmathToFloat(normals)); + ga.setNormalRefBuffer(f.getJ3DBuffer()); + } + + for (int i = 0 ; i < texCoordSetCount ; i++) { + b = ByteBufferWrapper.allocateDirect( + texCoordSets[i].length * 4 * texCoordDim); + f = b.order( ByteOrderWrapper.nativeOrder() ).asFloatBuffer(); + f.put(vecmathToFloat(texCoordSets[i])); + ga.setTexCoordRefBuffer(i, f.getJ3DBuffer()); + } + } else if (byRef) { + // Need to copy the data into float arrays - GeometryArray + // prefers them over the vecmath types + ga.setCoordRefFloat(vecmathToFloat(coordinates)); + if (colors3 != null) ga.setColorRefFloat(vecmathToFloat(colors3)); + else if (colors4 != null) ga.setColorRefFloat(vecmathToFloat(colors4)); + if (normals != null) ga.setNormalRefFloat(vecmathToFloat(normals)); + for (int i = 0 ; i < texCoordSetCount ; i++) { + ga.setTexCoordRefFloat(i, vecmathToFloat(texCoordSets[i])); + } + } else { + ga.setCoordinates(0, coordinates); + if (colors3 != null) ga.setColors(0, colors3); + else if (colors4 != null) ga.setColors(0, colors4); + if (normals != null) ga.setNormals(0, normals); + for (int i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordDim == 2) { + ga.setTextureCoordinates(i, 0, (TexCoord2f[])texCoordSets[i]); + } else if (texCoordDim == 3) { + ga.setTextureCoordinates(i, 0, (TexCoord3f[])texCoordSets[i]); + } else if (texCoordDim == 4) { + ga.setTextureCoordinates(i, 0, (TexCoord4f[])texCoordSets[i]); + } + } + } + + if (coordinateIndices != null) { + IndexedGeometryArray iga = null; + iga = (IndexedGeometryArray)ga; + iga.setCoordinateIndices(0, coordinateIndices); + if (!coordOnly) { + if (colorIndices != null) iga.setColorIndices(0, colorIndices); + if (normalIndices != null) iga.setNormalIndices(0, normalIndices); + for (int i = 0 ; i < texCoordSetCount ; i++) + iga.setTextureCoordinateIndices(i, 0, texCoordIndexSets[i]); + } + } + } // End of fillIn + + + + /** + * Redo indexes to guarantee connection information. + * Use this routine if your original data is in indexed format, but + * you don't trust that the indexing is correct. After this + * routine it is guaranteed that two points with the same + * position will have the same coordinate index (for example). + * Try this if you see + * glitches in your normals or stripification, to rule out + * bad indexing as the source of the problem. Works with normal + * indexed format or USE_COORD_INDEX_ONLY format. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void recomputeIndices() + { + boolean remember = coordOnly; + + // Can make more efficient implementation later + unindexify(); + indexify(remember); + } // End of recomputeIndices + + + + /** + * Reverse the order of an array of ints (computer class homework + * problem). + */ + private void reverseList(int list[]) + { + int t; + + if (list == null) return; + + for (int i = 0 ; i < list.length / 2 ; i++) { + t = list[i]; + list[i] = list[list.length - i - 1]; + list[list.length - i - 1] = t; + } + } // End of reverseList + + + + /** + * Reverse the order of all lists. If your polygons are formatted with + * clockwise winding, you will always see the back and never the front. + * (Java 3D always wants vertices specified with a counter-clockwise + * winding.) + * This method will (in effect) reverse the winding of your data by + * inverting all of the index lists and the stripCounts + * and contourCounts lists. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public void reverse() + { + indexify(); + reverseList(stripCounts); + reverseList(oldStripCounts); + reverseList(contourCounts); + reverseList(coordinateIndices); + reverseList(colorIndices); + reverseList(normalIndices); + for (int i = 0 ; i < texCoordSetCount ; i++) + reverseList(texCoordIndexSets[i]); + } // End of reverse + + + + /** + * Returns true if the data in this GeometryInfo is currently + * formatted in the USE_COORD_INDEX_ONLY format where a single + * index list is used to index into all data lists. + * @see GeometryInfo#indexify(boolean) + * @see GeometryInfo#getIndexedGeometryArray(boolean, boolean, boolean, + * boolean, boolean) + */ + public boolean getUseCoordIndexOnly() + { + return coordOnly; + } // End of getUseCoordIndexOnly + + + + /** + * Tells the GeometryInfo that its data is formatted in the + * USE_COORD_INDEX_ONLY format with a single index list + * (the coordinate index list) that indexes into all data + * lists (coordinates, normals, colors, and texture + * coordinates). NOTE: this will not convert the data + * for you. This method is for when you are sending in + * data useng the setCoordinates, setNormals, setColors, + * and/or setTextureCoordinates methods, and you are only + * setting one index using setCoordinateIndices(). If + * you want GeometryInfo to convert your data to the + * USE_COORD_INDEX_ONLY format, use indexify(true) or + * getIndexedGeometryArray with the useCoordIndexOnly + * parameter set to true. + * @see GeometryInfo#indexify(boolean) + * @see GeometryInfo#getIndexedGeometryArray(boolean, boolean, boolean, + * boolean, boolean) + */ + public void setUseCoordIndexOnly(boolean useCoordIndexOnly) + { + coordOnly = useCoordIndexOnly; + } // End of setUseCoordIndexOnly + + + + /** + * Creates and returns a non-indexed Java 3D GeometryArray object + * based on the data in the GeometryInfo object. This object is + * suitable to be attached to a Shape3D node for rendering. + * @param byRef Use geometry BY_REFERENCE + * @param interleaved Use INTERLEAVED geometry. Implies byRef is + * true as well. + * @param nio Create GeometryArray using java.nio.Buffer for + * geometry arrays. Only usable on JDK 1.4 or higher. Implies + * byRef is true as well. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public GeometryArray getGeometryArray(boolean byRef, boolean interleaved, + boolean nio) + { + checkForBadData(); + + if (prim == POLYGON_ARRAY) { + if (tr == null) tr = new Triangulator(); + tr.triangulate(this); + } else changeBackToOldPrim(); + + unindexify(); + + int vertexFormat = getVertexFormat(); + if (nio) vertexFormat |= (GeometryArray.BY_REFERENCE | + GeometryArray.USE_NIO_BUFFER); + if (interleaved) vertexFormat |= (GeometryArray.BY_REFERENCE | + GeometryArray.INTERLEAVED); + if (byRef) vertexFormat |= GeometryArray.BY_REFERENCE; + + int vertexCount = coordinates.length; + + // If the texCoordSetMap hasn't been set, assume one set of + // texture coordinates only and one texture state unit + if ((texCoordSetCount > 0) && (texCoordSetMap == null)) { + texCoordSetCount = 1; + texCoordSetMap = new int[1]; + texCoordSetMap[0] = 0; + } + + // Create the GeometryArray object + GeometryArray ga = null; + switch (prim) { + case TRIANGLE_ARRAY: + TriangleArray ta = new TriangleArray(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + ga = (GeometryArray)ta; + break; + + case QUAD_ARRAY: + QuadArray qa = new QuadArray(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + ga = (GeometryArray)qa; + break; + + case TRIANGLE_STRIP_ARRAY: + TriangleStripArray tsa = new TriangleStripArray(vertexCount, + vertexFormat, texCoordSetCount, texCoordSetMap, + stripCounts); + ga = (GeometryArray)tsa; + break; + + case TRIANGLE_FAN_ARRAY: + TriangleFanArray tfa = new TriangleFanArray(vertexCount, + vertexFormat, texCoordSetCount, texCoordSetMap, + stripCounts); + ga = (GeometryArray)tfa; + break; + } + + fillIn(ga, byRef, interleaved, nio); + + return ga; + } // End of getGeometryArray(int, int) + + + + /** + * Creates and returns a non-indexed Java 3D GeometryArray object + * based on the data in the GeometryInfo object. This object is + * suitable to be attached to a Shape3D node for rendering. + * The geometry is not created using data BY_REFERENCE, + * INTERLEAVED, or USE_NIO_BUFFER. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public GeometryArray getGeometryArray() + { + return getGeometryArray(false, false, false); + } // End of getGeometryArray() + + + + /** + * Creates and returns a IndexedGeometryArray + * based on the data in the GeometryInfo object. This object is + * suitable to be attached to a Shape3D node for rendering. + * @param compact Remove Coordinates, Colors, Normals, and + * TextureCoordinates that aren't referenced by any indices. + * @param byRef Create the IndexedGeometryArray using geometry + * BY_REFERENCE. + * @param interleaved Use INTERLEAVED geometry. Implies byRef is + * true as well. + * @param nio Create GeometryArray using java.nio.Buffer for + * geometry arrays. Only usable on JDK 1.4 or higher. Implies + * byRef is true as well. + * @param useCoordIndexOnly Create the IndexedGeometryArray using + * USE_COORD_INDEX_ONLY. Values from the coordinate index array + * are used as a single set of indices into all vertex + * component arrays (coord, color, normal, and texCoord). + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public IndexedGeometryArray getIndexedGeometryArray(boolean compact, + boolean byRef, + boolean interleaved, + boolean useCoordIndexOnly, + boolean nio) + { + indexify(useCoordIndexOnly); + + if (compact) compact(); + + if (prim == POLYGON_ARRAY) { + if (tr == null) tr = new Triangulator(); + tr.triangulate(this); + } else changeBackToOldPrim(); + + if (useCoordIndexOnly && coordOnly == false) { + // Check to see if we can optimize for USE_COORD_INDEX_ONLY + int i, j; + boolean canUseCoordIndexOnly = true; + + if (coordinateIndices != null) { + // See if all the array lengths are the same + if (colorIndices != null && + colorIndices.length != coordinateIndices.length) { + canUseCoordIndexOnly = false; + } + if (normalIndices != null && + normalIndices.length != coordinateIndices.length) { + canUseCoordIndexOnly = false; + } + for (i = 0 ; i < texCoordSetCount ; i++) { + if (texCoordIndexSets[i] != null && + texCoordIndexSets[i].length != coordinateIndices.length) { + canUseCoordIndexOnly = false; + break; + } + } + if (canUseCoordIndexOnly && + ((colorIndices != null) || + (normalIndices != null) || + (texCoordSetCount > 0))) { + // All array lengths are the same. Check their contents + + for (i=0; i 0) && (texCoordSetMap == null)) { + texCoordSetCount = 1; + texCoordSetMap = new int[1]; + texCoordSetMap[0] = 0; + } + + // + // Create the IndexedGeometryArray object + // + + IndexedGeometryArray ga = null; + + switch (prim) { + case TRIANGLE_ARRAY: + IndexedTriangleArray ta = new IndexedTriangleArray(vertexCount, + vertexFormat, texCoordSetCount, texCoordSetMap, + coordinateIndices.length); + ga = (IndexedGeometryArray)ta; + break; + + case QUAD_ARRAY: + IndexedQuadArray qa = new IndexedQuadArray(vertexCount, + vertexFormat, texCoordSetCount, texCoordSetMap, + coordinateIndices.length); + ga = (IndexedGeometryArray)qa; + break; + case TRIANGLE_STRIP_ARRAY: + IndexedTriangleStripArray tsa = new IndexedTriangleStripArray( + vertexCount, vertexFormat, texCoordSetCount, + texCoordSetMap, coordinateIndices.length, stripCounts); + ga = (IndexedGeometryArray)tsa; + break; + + case TRIANGLE_FAN_ARRAY: + IndexedTriangleFanArray tfa = new IndexedTriangleFanArray( + vertexCount, vertexFormat, texCoordSetCount, + texCoordSetMap, coordinateIndices.length, stripCounts); + ga = (IndexedGeometryArray)tfa; + break; + } + + // Fill in the GeometryArray object + fillIn(ga, byRef, interleaved, nio); + + return ga; + } // End of getIndexedGeometryArray(bool, bool, bool, bool, bool) + + + + /** + * Creates and returns an IndexedGeometryArray + * based on the data in the GeometryInfo object. This object is + * suitable to be attached to a Shape3D node for rendering. + * Equivalent to getIndexedGeometryArray(compact, false, + * false, false, false). + * @param compact Remove Coordinates, Colors, Normals, and + * TextureCoordinates that aren't referenced by any indices. + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public IndexedGeometryArray getIndexedGeometryArray(boolean compact) + { + return getIndexedGeometryArray(compact, false, false, false, false); + } // End of getIndexedGeometryArray(boolean) + + + + /** + * Creates and returns an IndexedGeometryArray + * based on the data in the GeometryInfo object. This object is + * suitable to be attached to a Shape3D node for rendering. + * Equivalent to getIndexedGeometryArray(false, false, + * false, false, false). + * @throws IllegalArgumentException if coordinate data is missing, + * if the index lists aren't all the + * same length, if an index list is set and the corresponding data + * list isn't set, if a data list is set and the corresponding + * index list is unset (unless all index lists are unset or in + * USE_COORD_INDEX_ONLY format), + * if StripCounts or ContourCounts is inconsistent with the current + * primitive, if the sum of the contourCounts array doesn't equal + * the length of the StripCounts array, or if the number of vertices + * isn't a multiple of three (for triangles) or four (for quads). + */ + public IndexedGeometryArray getIndexedGeometryArray() + { + return getIndexedGeometryArray(false, false, false, false, false); + } // End of getIndexedGeometryArray() + +} // End of class GeometryInfo + +// End of file GeometryInfo.java diff --git a/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfoGenerator.java b/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfoGenerator.java new file mode 100644 index 0000000..dd8b599 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/GeometryInfoGenerator.java @@ -0,0 +1,857 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import javax.media.j3d.GeometryArray; +import javax.media.j3d.GeometryStripArray; +import javax.media.j3d.TriangleFanArray; +import javax.media.j3d.TriangleStripArray; +import javax.media.j3d.TriangleArray; +import javax.media.j3d.QuadArray; +import javax.media.j3d.IndexedGeometryArray; +import javax.media.j3d.IndexedGeometryStripArray; +import javax.media.j3d.IndexedQuadArray; +import javax.media.j3d.IndexedTriangleArray; +import javax.media.j3d.IndexedTriangleFanArray; +import javax.media.j3d.IndexedTriangleStripArray; +import javax.vecmath.*; +import com.sun.j3d.utils.geometry.GeometryInfo; +import com.sun.j3d.internal.J3dUtilsI18N; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; +import javax.media.j3d.J3DBuffer; + + + +/** + * Populate a GeometryInfo object from the Geometry provided. Used + * by GeometryInfo. + */ +class GeometryInfoGenerator extends Object { + + public static void create(GeometryInfo geomInfo, GeometryArray geomArray) + { + if (geomArray instanceof GeometryStripArray) + create(geomInfo, (GeometryStripArray)geomArray); + else if (geomArray instanceof TriangleArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_ARRAY); + processGeometryArray(geomInfo, geomArray); + } else if (geomArray instanceof QuadArray) { + geomInfo.reset(GeometryInfo.QUAD_ARRAY); + processGeometryArray(geomInfo, geomArray); + } else if (geomArray instanceof IndexedGeometryArray) + create(geomInfo, (IndexedGeometryArray)geomArray); + else throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfoGenerator0")); + } // End of create(GeometryInfo, GeometryArray) + + + + private static void create(GeometryInfo geomInfo, + GeometryStripArray geomArray) + { + if (geomArray instanceof TriangleFanArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_FAN_ARRAY); + } else if (geomArray instanceof TriangleStripArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_STRIP_ARRAY); + } else throw new IllegalArgumentException( + J3dUtilsI18N.getString("GeometryInfoGenerator0")); + + processGeometryArray(geomInfo, geomArray); + processStripArray(geomInfo, geomArray); + } // End of create(GeometryInfo, GeometryStripArray) + + + + private static void create(GeometryInfo geomInfo, + IndexedGeometryArray geomArray) + { + if (geomArray instanceof IndexedQuadArray) { + geomInfo.reset(GeometryInfo.QUAD_ARRAY); + } else if (geomArray instanceof IndexedTriangleArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_ARRAY); + } else if (geomArray instanceof IndexedTriangleFanArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_FAN_ARRAY); + processIndexStripArray(geomInfo, (IndexedGeometryStripArray)geomArray); + } else if (geomArray instanceof IndexedTriangleStripArray) { + geomInfo.reset(GeometryInfo.TRIANGLE_STRIP_ARRAY); + processIndexStripArray(geomInfo, (IndexedGeometryStripArray)geomArray); + } + + processGeometryArray(geomInfo, geomArray); + processIndexedArray(geomInfo, geomArray); + } // End of create(GeometryInfo, IndexedGeometryArray) + + + + private static void processGeometryArray(GeometryInfo geomInfo, + GeometryArray geomArray) + { + int i, j; + int vertexFormat = geomArray.getVertexFormat(); + int texSets = geomArray.getTexCoordSetCount(); + int valid; + + // Calculate validVertexCount + if (geomArray instanceof GeometryStripArray) { + // Does not include IndexedGeometryStripArray + GeometryStripArray gsa = (GeometryStripArray)geomArray; + int[] strips = new int[gsa.getNumStrips()]; + gsa.getStripVertexCounts(strips); + valid = 0; + for (i = 0 ; i < strips.length ; i++) { + valid += strips[i]; + } + } else if (geomArray instanceof IndexedGeometryArray) { + valid = geomArray.getVertexCount(); + } else valid = geomArray.getValidVertexCount(); + + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + + // Calculate words_per_vertex (wpv) + int wpv = 3; // Always have coordinate data + if ((vertexFormat & GeometryArray.NORMALS) != 0) wpv += 3; + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) + wpv += 4; + else if ((vertexFormat & GeometryArray.COLOR_3) != 0) wpv += 3; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) + wpv += 2 * texSets; + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) + wpv += 3 * texSets; + else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) + wpv += 4 * texSets; + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + + float[] d; + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + J3DBuffer b = geomArray.getInterleavedVertexBuffer(); + FloatBufferWrapper w = new FloatBufferWrapper(b); + d = new float[w.limit()]; + w.position( 0 ); + w.get(d); + } else d = geomArray.getInterleavedVertices(); + + int offset = 0; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + geomInfo.setTextureCoordinateParams(texSets, 2); + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + for (i = 0 ; i < texSets ; i++) { + TexCoord2f[] tex = new TexCoord2f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord2f(d[wpv * (j + initial) + offset], + d[wpv * (j + initial) + offset + 1]); + } + geomInfo.setTextureCoordinates(i, tex); + offset += 2; + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + geomInfo.setTextureCoordinateParams(texSets, 3); + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + for (i = 0 ; i < texSets ; i++) { + TexCoord3f[] tex = new TexCoord3f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord3f(d[wpv * (j + initial) + offset], + d[wpv * (j + initial) + offset + 1], + d[wpv * (j + initial) + offset + 2]); + } + geomInfo.setTextureCoordinates(i, tex); + offset += 3; + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + geomInfo.setTextureCoordinateParams(texSets, 4); + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + for (i = 0 ; i < texSets ; i++) { + TexCoord4f[] tex = new TexCoord4f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord4f(d[wpv * (j + initial) + offset], + d[wpv * (j + initial) + offset + 1], + d[wpv * (j + initial) + offset + 2], + d[wpv * (j + initial) + offset + 3]); + } + geomInfo.setTextureCoordinates(i, tex); + offset += 4; + } + } + + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + Color4f[] color = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + color[i] = new Color4f(d[wpv * (i + initial) + offset], + d[wpv * (i + initial) + offset + 1], + d[wpv * (i + initial) + offset + 2], + d[wpv * (i + initial) + offset + 3]); + } + geomInfo.setColors(color); + offset += 4; + } else if ((vertexFormat & GeometryArray.COLOR_3) != 0) { + Color3f[] color = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + color[i] = new Color3f(d[wpv * (i + initial) + offset], + d[wpv * (i + initial) + offset + 1], + d[wpv * (i + initial) + offset + 2]); + } + geomInfo.setColors(color); + offset += 3; + } + + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + Vector3f[] normals = new Vector3f[valid]; + for (i = 0 ; i < valid ; i++) { + normals[i] = new Vector3f(d[wpv * (i + initial) + offset], + d[wpv * (i + initial) + offset + 1], + d[wpv * (i + initial) + offset + 2]); + } + geomInfo.setNormals(normals); + offset += 3; + } + + Point3f[] coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f(d[wpv * (i + initial) + offset], + d[wpv * (i + initial) + offset + 1], + d[wpv * (i + initial) + offset + 2]); + } + geomInfo.setCoordinates(coords); + } else { + // Data is not INTERLEAVED + boolean byRef = ((vertexFormat & GeometryArray.BY_REFERENCE) != 0 ); + boolean nio = ((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 ); + + Point3f[] coords = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialCoordIndex(); + } else initial = 0; + + if ( nio ) { + J3DBuffer buf = geomArray.getCoordRefBuffer(); + + switch (BufferWrapper.getBufferType(buf)) { + + case BufferWrapper.TYPE_FLOAT: { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f(c[i * 3 + 0], + c[i * 3 + 1], + c[i * 3 + 2]); + } + } + break; + + case BufferWrapper.TYPE_DOUBLE: { + DoubleBufferWrapper bb = new DoubleBufferWrapper( buf ); + double[] c = new double[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f((float)(c[i * 3 + 0]), + (float)(c[i * 3 + 1]), + (float)(c[i * 3 + 2])); + } + } + break; + } + } else if (geomArray.getCoordRef3f() != null) { + if (initial != 0) { + Point3f[] c = geomArray.getCoordRef3f(); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f(c[i + initial]); + } + } else coords = geomArray.getCoordRef3f(); + } else if (geomArray.getCoordRef3d() != null) { + Point3d[] c = geomArray.getCoordRef3d(); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f(c[i + initial]); + } + } else if (geomArray.getCoordRefFloat() != null) { + float[] c = geomArray.getCoordRefFloat(); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f(c[(i + initial) * 3], + c[(i + initial) * 3 + 1], + c[(i + initial) * 3 + 2]); + } + } else if (geomArray.getCoordRefDouble() != null) { + double[] c = geomArray.getCoordRefDouble(); + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) { + coords[i] = new Point3f((float)(c[(i + initial) * 3]), + (float)(c[(i + initial) * 3 + 1]), + (float)(c[(i + initial) * 3 + 2])); + } + } + // No coordinate data - let GeometryInfo handle this. + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + coords = new Point3f[valid]; + for (i = 0 ; i < valid ; i++) coords[i] = new Point3f(); + geomArray.getCoordinates(initial, coords); + } + geomInfo.setCoordinates(coords); + + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + Vector3f[] normals = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialNormalIndex(); + } else initial = 0; + + if ( nio ) { + J3DBuffer buf = geomArray.getNormalRefBuffer(); + + if (BufferWrapper.getBufferType(buf) == BufferWrapper.TYPE_FLOAT) { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + normals = new Vector3f[valid]; + for (i = 0 ; i < valid ; i++) { + normals[i] = new Vector3f(c[i * 3 + 0], + c[i * 3 + 1], + c[i * 3 + 2]); + } + } + // Normals were set in vertexFormat but none were set - OK + } else if (geomArray.getNormalRef3f() != null) { + if (initial != 0) { + Vector3f[] n = geomArray.getNormalRef3f(); + normals = new Vector3f[valid]; + for (i = 0 ; i < valid ; i++) { + normals[i] = new Vector3f(n[i + initial]); + } + } else normals = geomArray.getNormalRef3f(); + } else if (geomArray.getNormalRefFloat() != null) { + float[] n = geomArray.getNormalRefFloat(); + normals = new Vector3f[valid]; + for (i = 0 ; i < valid ; i++) { + normals[i] = new Vector3f(n[(i + initial) * 3], + n[(i + initial) * 3 + 1], + n[(i + initial) * 3 + 2]); + } + } + // Normals were set in vertexFormat but none were set - OK + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + normals = new Vector3f[valid]; + for (i = 0 ; i < valid ; i++) normals[i] = new Vector3f(); + geomArray.getNormals(initial, normals); + } + geomInfo.setNormals(normals); + } + + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + Color4f[] colors = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialColorIndex(); + } else initial = 0; + + if ( nio ) { + J3DBuffer buf = geomArray.getColorRefBuffer(); + + switch (BufferWrapper.getBufferType(buf)) { + + case BufferWrapper.TYPE_FLOAT: { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 4]; + bb.position(initial * 4); + bb.get(c, 0, valid * 4); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f(c[i * 4 + 0], + c[i * 4 + 1], + c[i * 4 + 2], + c[i * 4 + 3]); + } + } + break; + + case BufferWrapper.TYPE_BYTE: { + ByteBufferWrapper bb = new ByteBufferWrapper(buf); + byte[] c = new byte[valid * 4]; + bb.position(initial * 4); + bb.get(c, 0, valid * 4); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f((float)(c[i * 4 + 0] & 0xff) / 255.0f, + (float)(c[i * 4 + 1] & 0xff) / 255.0f, + (float)(c[i * 4 + 2] & 0xff) / 255.0f, + (float)(c[i * 4 + 3] & 0xff) / 255.0f); + } + } + break; + } + } else if (geomArray.getColorRef4f() != null) { + if (initial != 0) { + Color4f[] c = geomArray.getColorRef4f(); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f(c[i + initial]); + } + } else colors = geomArray.getColorRef4f(); + } else if (geomArray.getColorRefFloat() != null) { + float[] c = geomArray.getColorRefFloat(); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f(c[(i + initial) * 4 + 0], + c[(i + initial) * 4 + 1], + c[(i + initial) * 4 + 2], + c[(i + initial) * 4 + 3]); + } + } else if (geomArray.getColorRefByte() != null) { + byte[] c = geomArray.getColorRefByte(); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f((float)(c[(i + initial) * 4 + 0] & 0xff) / 255.0f, + (float)(c[(i + initial) * 4 + 1] & 0xff) / 255.0f, + (float)(c[(i + initial) * 4 + 2] & 0xff) / 255.0f, + (float)(c[(i + initial) * 4 + 3] & 0xff) / 255.0f); + } + } else if (geomArray.getColorRef4b() != null) { + Color4b[] c = geomArray.getColorRef4b(); + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color4f((float)(c[i + initial].x & 0xff) / 255.0f, + (float)(c[i + initial].y & 0xff) / 255.0f, + (float)(c[i + initial].z & 0xff) / 255.0f, + (float)(c[i + initial].w & 0xff) / 255.0f); + } + } + // Colors4 were set in vertexFormat but none were set - OK + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + colors = new Color4f[valid]; + for (i = 0 ; i < valid ; i++) colors[i] = new Color4f(); + geomArray.getColors(initial, colors); + } + geomInfo.setColors(colors); + } else if ((vertexFormat & GeometryArray.COLOR_3) != 0) { + Color3f[] colors = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialColorIndex(); + } else initial = 0; + + if ( nio ) { + J3DBuffer buf = geomArray.getColorRefBuffer(); + + switch (BufferWrapper.getBufferType(buf)) { + + case BufferWrapper.TYPE_FLOAT: { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f(c[i * 3 + 0], + c[i * 3 + 1], + c[i * 3 + 2]); + } + } + break; + + case BufferWrapper.TYPE_BYTE: { + ByteBufferWrapper bb = new ByteBufferWrapper(buf); + byte[] c = new byte[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f((float)(c[i * 3 + 0] & 0xff) / 255.0f, + (float)(c[i * 3 + 1] & 0xff) / 255.0f, + (float)(c[i * 3 + 2] & 0xff) / 255.0f); + } + } + break; + } + } else if (geomArray.getColorRef3f() != null) { + if (initial != 0) { + Color3f[] c = geomArray.getColorRef3f(); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f(c[i + initial]); + } + } else colors = geomArray.getColorRef3f(); + } else if (geomArray.getColorRefFloat() != null) { + float[] c = geomArray.getColorRefFloat(); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f(c[(i + initial) * 3 + 0], + c[(i + initial) * 3 + 1], + c[(i + initial) * 3 + 2]); + } + } else if (geomArray.getColorRefByte() != null) { + byte[] c = geomArray.getColorRefByte(); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f((float)(c[(i + initial) * 3 + 0] & 0xff) / 255.0f, + (float)(c[(i + initial) * 3 + 1] & 0xff) / 255.0f, + (float)(c[(i + initial) * 3 + 2] & 0xff) / 255.0f); + } + } else if (geomArray.getColorRef3b() != null) { + Color3b[] c = geomArray.getColorRef3b(); + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) { + colors[i] = new Color3f((float)(c[i + initial].x & 0xff) / 255.0f, + (float)(c[i + initial].y & 0xff) / 255.0f, + (float)(c[i + initial].z & 0xff) / 255.0f); + } + } + // Colors3 were set in vertexFormat but none were set - OK + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + colors = new Color3f[valid]; + for (i = 0 ; i < valid ; i++) colors[i] = new Color3f(); + geomArray.getColors(initial, colors); + } + geomInfo.setColors(colors); + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + geomInfo.setTextureCoordinateParams(texSets, 4); + for (i = 0 ; i < texSets ; i++) { + TexCoord4f[] tex = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialTexCoordIndex(i); + } else initial = 0; + + if (nio) { + J3DBuffer buf = geomArray.getTexCoordRefBuffer(i); + + if (BufferWrapper.getBufferType(buf) == BufferWrapper.TYPE_FLOAT) { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 4]; + bb.position(initial * 4); + bb.get(c, 0, valid * 4); + tex = new TexCoord4f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord4f(c[j * 4 + 0], + c[j * 4 + 1], + c[j * 4 + 2], + c[j * 4 + 3]); + } + } + // TexCoords4 were set in vertexFormat but none were set - OK + } else { + // There if no TexCoordRef4f, so we know it's float + float[] t = geomArray.getTexCoordRefFloat(i); + tex = new TexCoord4f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord4f(t[(j + initial) * 4], + t[(j + initial) * 4 + 1], + t[(j + initial) * 4 + 2], + t[(j + initial) * 4 + 3]); + } + } + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + tex = new TexCoord4f[valid]; + for (j = 0 ; j < valid ; j++) tex[j] = new TexCoord4f(); + geomArray.getTextureCoordinates(i, initial, tex); + } + geomInfo.setTextureCoordinates(i, tex); + } + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + geomInfo.setTextureCoordinateParams(texSets, 3); + for (i = 0 ; i < texSets ; i++) { + TexCoord3f[] tex = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialTexCoordIndex(i); + } else initial = 0; + + if (nio) { + J3DBuffer buf = geomArray.getTexCoordRefBuffer(i); + + if (BufferWrapper.getBufferType(buf) == BufferWrapper.TYPE_FLOAT) { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 3]; + bb.position(initial * 3); + bb.get(c, 0, valid * 3); + tex = new TexCoord3f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord3f(c[j * 3 + 0], + c[j * 3 + 1], + c[j * 3 + 2]); + } + } + // TexCoords3 were set in vertexFormat but none were set - OK + } else if (geomArray.getTexCoordRef3f(i) != null) { + if (initial != 0) { + TexCoord3f[] t = geomArray.getTexCoordRef3f(i); + tex = new TexCoord3f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord3f(t[j + initial]); + } + } else tex = geomArray.getTexCoordRef3f(i); + } else if (geomArray.getTexCoordRefFloat(i) != null) { + float[] t = geomArray.getTexCoordRefFloat(i); + tex = new TexCoord3f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord3f(t[(j + initial) * 3], + t[(j + initial) * 3 + 1], + t[(j + initial) * 3 + 2]); + } + } + // TexCoords3 were set in vertexFormat but none were set - OK + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + tex = new TexCoord3f[valid]; + for (j = 0 ; j < valid ; j++) tex[j] = new TexCoord3f(); + geomArray.getTextureCoordinates(i, initial, tex); + } + geomInfo.setTextureCoordinates(i, tex); + } + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0 ) { + geomInfo.setTextureCoordinateParams(texSets, 2); + for (i = 0 ; i < texSets ; i++) { + TexCoord2f[] tex = null; + if (byRef) { + + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialTexCoordIndex(i); + } else initial = 0; + + if (nio) { + J3DBuffer buf = geomArray.getTexCoordRefBuffer(i); + + if (BufferWrapper.getBufferType(buf) == BufferWrapper.TYPE_FLOAT) { + FloatBufferWrapper bb = new FloatBufferWrapper(buf); + float[] c = new float[valid * 2]; + bb.position(initial * 2); + bb.get(c, 0, valid * 2); + tex = new TexCoord2f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord2f(c[j * 2 + 0], + c[j * 2 + 1]); + } + } + // TexCoords2 were set in vertexFormat but none were set - OK + } else if (geomArray.getTexCoordRefFloat(i) != null) { + float[] t = geomArray.getTexCoordRefFloat(i); + tex = new TexCoord2f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord2f(t[(j + initial) * 2 + 0], + t[(j + initial) * 2 + 1]); + } + } else if (geomArray.getTexCoordRef2f(i) != null) { + if (initial != 0) { + TexCoord2f[] t = geomArray.getTexCoordRef2f(i); + tex = new TexCoord2f[valid]; + for (j = 0 ; j < valid ; j++) { + tex[j] = new TexCoord2f(t[j + initial]); + } + } else tex = geomArray.getTexCoordRef2f(i); + } + // TexCoords2 were set in vertexFormat but none were set - OK + } else { + // Not BY_REFERENCE + int initial; + if (!(geomArray instanceof IndexedGeometryArray)) { + initial = geomArray.getInitialVertexIndex(); + } else initial = 0; + tex = new TexCoord2f[valid]; + for (j = 0 ; j < valid ; j++) tex[j] = new TexCoord2f(); + geomArray.getTextureCoordinates(i, initial, tex); + } + geomInfo.setTextureCoordinates(i, tex); + } + int[] map = new int[geomArray.getTexCoordSetMapLength()]; + geomArray.getTexCoordSetMap(map); + geomInfo.setTexCoordSetMap(map); + } + } + } // End of processGeometryArray + + + + private static void processIndexedArray(GeometryInfo geomInfo, + IndexedGeometryArray geomArray) + { + int initial = geomArray.getInitialIndexIndex(); + int vertexFormat = geomArray.getVertexFormat(); + int texSets = geomArray.getTexCoordSetCount(); + + int valid; + if (geomArray instanceof IndexedGeometryStripArray) { + IndexedGeometryStripArray igsa = (IndexedGeometryStripArray)geomArray; + int[] strips = new int[igsa.getNumStrips()]; + igsa.getStripIndexCounts(strips); + valid = 0; + for (int i = 0 ; i < strips.length ; i++) { + valid += strips[i]; + } + } else { + valid = geomArray.getValidIndexCount(); + } + + int[] coordI = new int[valid]; + geomArray.getCoordinateIndices(initial, coordI); + geomInfo.setCoordinateIndices(coordI); + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if ((vertexFormat & GeometryArray.NORMALS) != 0) + geomInfo.setNormalIndices(coordI); + if (((vertexFormat & GeometryArray.COLOR_3) != 0) || + ((vertexFormat & GeometryArray.COLOR_4) != 0)) + geomInfo.setColorIndices(coordI); + if (((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) || + ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) || + ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0)) { + for (int i = 0 ; i < texSets ; i++) { + geomInfo.setTextureCoordinateIndices(i, coordI); + } + } + } else { + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + int[] normalI = new int[valid]; + geomArray.getNormalIndices(initial, normalI); + geomInfo.setNormalIndices(normalI); + } + + if (((vertexFormat & GeometryArray.COLOR_3) != 0) || + ((vertexFormat & GeometryArray.COLOR_4) != 0)) { + int[] colorI = new int[valid]; + geomArray.getColorIndices(initial, colorI); + geomInfo.setColorIndices(colorI); + } + + if (((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) || + ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) || + ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0)) { + for (int i = 0 ; i < texSets ; i++) { + int[] texI = new int[valid]; + geomArray.getTextureCoordinateIndices(i, initial, texI); + geomInfo.setTextureCoordinateIndices(i, texI); + } + } + } + } // End of processIndexedArray + + + + private static void processStripArray(GeometryInfo geomInfo, + GeometryStripArray geomArray) + { + int[] strips = new int[geomArray.getNumStrips()]; + geomArray.getStripVertexCounts(strips); + geomInfo.setStripCounts(strips); + } // End of processStripArray + + + + private static void processIndexStripArray( + GeometryInfo geomInfo, IndexedGeometryStripArray geomArray) + { + int[] strips = new int[geomArray.getNumStrips()]; + geomArray.getStripIndexCounts(strips); + geomInfo.setStripCounts(strips); + } // End of processIndexStripArray + +} // End of class GeometryInfoGenerator diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Heap.java b/src/classes/share/com/sun/j3d/utils/geometry/Heap.java new file mode 100644 index 0000000..8ae7735 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Heap.java @@ -0,0 +1,212 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Heap { + + static void printHeapData(Triangulator triRef) { + int i; + System.out.println("\nHeap Data : numZero " + triRef.numZero + + " numHeap " + triRef.numHeap); + for(i=0; i< triRef.numHeap; i++) + System.out.println(i + " ratio " + triRef.heap[i].ratio + ", index " + + triRef.heap[i].index + ", prev " + + triRef.heap[i].prev + ", next " + + triRef.heap[i].next); + + System.out.println(" "); + + + } + + static void initHeap(Triangulator triRef) { + // Calculate the maximum bounds : N + (N -2)* 2. + // triRef.maxNumHeap = triRef.numPoints * 3; + triRef.maxNumHeap = triRef.numPoints; + triRef.heap = new HeapNode[triRef.maxNumHeap]; + + triRef.numHeap = 0; + triRef.numZero = 0; + + } + + static void storeHeapData(Triangulator triRef, int index, double ratio, + int ind, int prev, int next) { + triRef.heap[index] = new HeapNode(); + triRef.heap[index].ratio = ratio; + triRef.heap[index].index = ind; + triRef.heap[index].prev = prev; + triRef.heap[index].next = next; + } + + static void dumpOnHeap(Triangulator triRef, double ratio, + int ind, int prev, int next) { + int index; + + if (triRef.numHeap >= triRef.maxNumHeap) { + // System.out.println("Heap:dumpOnHeap.Expanding heap array ..."); + HeapNode old[] = triRef.heap; + triRef.maxNumHeap = triRef.maxNumHeap + triRef.numPoints; + triRef.heap = new HeapNode[triRef.maxNumHeap]; + System.arraycopy(old, 0, triRef.heap, 0, old.length); + } + if (ratio == 0.0) { + if (triRef.numZero < triRef.numHeap) + if(triRef.heap[triRef.numHeap] == null) + storeHeapData(triRef, triRef.numHeap, triRef.heap[triRef.numZero].ratio, + triRef.heap[triRef.numZero].index, + triRef.heap[triRef.numZero].prev, + triRef.heap[triRef.numZero].next); + else + triRef.heap[triRef.numHeap].copy(triRef.heap[triRef.numZero]); + /* + storeHeapData(triRef, triRef.numHeap, triRef.heap[triRef.numZero].ratio, + triRef.heap[triRef.numZero].index, + triRef.heap[triRef.numZero].prev, + triRef.heap[triRef.numZero].next); + */ + index = triRef.numZero; + ++triRef.numZero; + } + else { + index = triRef.numHeap; + } + + storeHeapData(triRef, index, ratio, ind, prev, next); + ++triRef.numHeap; + + } + + + static void insertIntoHeap(Triangulator triRef, double ratio, + int ind, int prev, int next) { + dumpOnHeap(triRef, ratio, ind, prev, next); + } + + + static boolean deleteFromHeap(Triangulator triRef, int[] ind, + int[] prev, int[] next) { + double rnd; + int rndInd; + + // earSorted is not implemented yet. + + if (triRef.numZero > 0) { + // assert(num_heap >= num_zero); + --triRef.numZero; + --triRef.numHeap; + + ind[0] = triRef.heap[triRef.numZero].index; + prev[0] = triRef.heap[triRef.numZero].prev; + next[0] = triRef.heap[triRef.numZero].next; + if (triRef.numZero < triRef.numHeap) + triRef.heap[triRef.numZero].copy(triRef.heap[triRef.numHeap]); + /* + storeHeapData( triRef, triRef.numZero, triRef.heap[triRef.numHeap].ratio, + triRef.heap[triRef.numHeap].index, + triRef.heap[triRef.numHeap].prev, + triRef.heap[triRef.numHeap].next); + */ + return true; + } + else if (triRef.earsRandom) { + if (triRef.numHeap <= 0) { + triRef.numHeap = 0; + return false; + } + rnd = triRef.randomGen.nextDouble(); + rndInd = (int)(rnd * triRef.numHeap); + --triRef.numHeap; + if (rndInd > triRef.numHeap) rndInd = triRef.numHeap; + + ind[0] = triRef.heap[rndInd].index; + prev[0] = triRef.heap[rndInd].prev; + next[0] = triRef.heap[rndInd].next; + if (rndInd < triRef.numHeap) + triRef.heap[rndInd].copy(triRef.heap[triRef.numHeap]); + /* + storeHeapData( triRef, rndInd, triRef.heap[triRef.numHeap].ratio, + triRef.heap[triRef.numHeap].index, + triRef.heap[triRef.numHeap].prev, + triRef.heap[triRef.numHeap].next); + */ + return true; + } + else { + if (triRef.numHeap <= 0) { + triRef.numHeap = 0; + return false; + } + --triRef.numHeap; + ind[0] = triRef.heap[triRef.numHeap].index; + prev[0] = triRef.heap[triRef.numHeap].prev; + next[0] = triRef.heap[triRef.numHeap].next; + + return true; + } + + // return false; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/HeapNode.java b/src/classes/share/com/sun/j3d/utils/geometry/HeapNode.java new file mode 100644 index 0000000..37f02c0 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/HeapNode.java @@ -0,0 +1,75 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +class HeapNode { + int index, prev, next; + double ratio; + + HeapNode() { + } + + void copy(HeapNode hNode) { + index = hNode.index; + prev = hNode.prev; + next = hNode.next; + ratio = hNode.ratio; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Left.java b/src/classes/share/com/sun/j3d/utils/geometry/Left.java new file mode 100644 index 0000000..72a66dd --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Left.java @@ -0,0 +1,74 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +class Left extends Object { + int ind; + int index; + + Left() { + } + + void copy(Left l) { + ind = l.ind; + index = l.index; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/ListNode.java b/src/classes/share/com/sun/j3d/utils/geometry/ListNode.java new file mode 100644 index 0000000..15b8983 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/ListNode.java @@ -0,0 +1,87 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +class ListNode { + int index; + int prev; + int next; + int convex; + int vcntIndex; // Vertex, Color, Normal, Texture Index + + + + ListNode(int ind) { + index = ind; + prev = -1; + next = -1; + convex = 0; + vcntIndex = -1; + } + + void setCommonIndex(int comIndex) { + vcntIndex = comIndex; + + } + + int getCommonIndex() { + return vcntIndex; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/NoHash.java b/src/classes/share/com/sun/j3d/utils/geometry/NoHash.java new file mode 100644 index 0000000..35e25b5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/NoHash.java @@ -0,0 +1,302 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class NoHash { + + static final int NIL = -1; + + + static void insertAfterVtx(Triangulator triRef, int iVtx) { + int size; + + if (triRef.vtxList == null) { + size = Math.max(triRef.numVtxList+1, 100); + triRef.vtxList = new PntNode[size]; + } else if (triRef.numVtxList >= triRef.vtxList.length) { + size = Math.max(triRef.numVtxList+1, + triRef.vtxList.length + 100); + PntNode old[] = triRef.vtxList; + triRef.vtxList = new PntNode[size]; + System.arraycopy(old, 0, triRef.vtxList, 0, old.length); + } + + triRef.vtxList[triRef.numVtxList] = new PntNode(); + triRef.vtxList[triRef.numVtxList].pnt = iVtx; + triRef.vtxList[triRef.numVtxList].next = triRef.reflexVertices; + triRef.reflexVertices = triRef.numVtxList; + ++triRef.numVtxList; + ++triRef.numReflex; + } + + static void deleteFromList(Triangulator triRef, int i) { + int indPnt, indPnt1; + int indVtx; + + if(triRef.numReflex == 0) { + // System.out.println("NoHash:deleteFromList. numReflex is 0."); + return; + + } + indPnt = triRef.reflexVertices; + if(inVtxList(triRef, indPnt)==false) + System.out.println("NoHash:deleteFromList. Problem :Not is InVtxList ..." + + indPnt); + + indVtx = triRef.vtxList[indPnt].pnt; + + if (indVtx == i) { + triRef.reflexVertices = triRef.vtxList[indPnt].next; + --triRef.numReflex; + } + else { + indPnt1 = triRef.vtxList[indPnt].next; + while (indPnt1 != NIL) { + if(inVtxList(triRef, indPnt1)==false) + System.out.println("NoHash:deleteFromList. Problem :Not is InVtxList ..."+ + indPnt1); + + indVtx = triRef.vtxList[indPnt1].pnt; + if (indVtx == i) { + triRef.vtxList[indPnt].next = triRef.vtxList[indPnt1].next; + indPnt1 = NIL; + --triRef.numReflex; + } + else { + indPnt = indPnt1; + indPnt1 = triRef.vtxList[indPnt].next; + } + } + } + } + + + static boolean inVtxList(Triangulator triRef, int vtx) { + return ((0 <= vtx) && (vtx < triRef.numVtxList)); + } + + + static void freeNoHash(Triangulator triRef) { + + triRef.noHashingEdges = false; + triRef.noHashingPnts = false; + + triRef.numVtxList = 0; + } + + + + static void prepareNoHashEdges(Triangulator triRef, + int currLoopMin, int currLoopMax) { + triRef.loopMin = currLoopMin; + triRef.loopMax = currLoopMax; + + triRef.noHashingEdges = true; + + return; + } + + + static void prepareNoHashPnts(Triangulator triRef, int currLoopMin) { + int ind, ind1; + int i1; + + triRef.numVtxList = 0; + triRef.reflexVertices = NIL; + + // insert the reflex vertices into a list + ind = triRef.loops[currLoopMin]; + ind1 = ind; + triRef.numReflex = 0; + i1 = triRef.fetchData(ind1); + + do { + if (triRef.getAngle(ind1) < 0) + insertAfterVtx(triRef, ind1); + + ind1 = triRef.fetchNextData(ind1); + i1 = triRef.fetchData(ind1); + } while (ind1 != ind); + + triRef.noHashingPnts = true; + + } + + + static boolean noHashIntersectionExists(Triangulator triRef, int i1, int ind1, + int i2, int i3, BBox bb) { + int indVtx, ind5; + int indPnt; + int i4, i5; + int type[] = new int[1]; + boolean flag; + double y; + + if(triRef.noHashingPnts==false) + System.out.println("NoHash:noHashIntersectionExists noHashingPnts is false"); + + // assert(InPointsList(i1)); + // assert(InPointsList(i2)); + // assert(InPointsList(i3)); + + if (triRef.numReflex <= 0) return false; + + // first, let's extend the BBox of the line segment i2, i3 to a BBox + // of the entire triangle. + if (i1 < bb.imin) bb.imin = i1; + else if (i1 > bb.imax) bb.imax = i1; + y = triRef.points[i1].y; + if (y < bb.ymin) bb.ymin = y; + else if (y > bb.ymax) bb.ymax = y; + + // check whether the triangle i1, i2, i3 contains any reflex vertex; we + // assume that i2, i3 is the new diagonal, and that the triangle is + // oriented CCW. + indPnt = triRef.reflexVertices; + flag = false; + do { + // assert(InVtxList(ind_pnt)); + indVtx = triRef.vtxList[indPnt].pnt; + // assert(InPolyList(ind_vtx)); + i4 = triRef.fetchData(indVtx); + + + if (bb.pntInBBox(triRef, i4)) { + // only if the reflex vertex lies inside the BBox of the triangle. + ind5 = triRef.fetchNextData(indVtx); + i5 = triRef.fetchData(ind5); + if ((indVtx != ind1) && (indVtx != ind5)) { + // only if this node isn't i1, and if it still belongs to the + // polygon + if (i4 == i1) { + if (Degenerate.handleDegeneracies(triRef, i1, ind1, i2, i3, i4, indVtx)) + return true; + } + else if ((i4 != i2) && (i4 != i3)) { + flag = Numerics.vtxInTriangle(triRef, i1, i2, i3, i4, type); + if (flag) return true; + } + } + } + indPnt = triRef.vtxList[indPnt].next; + + } while (indPnt != NIL); + + return false; + } + + + + + static void deleteReflexVertex(Triangulator triRef, int ind) { + // assert(InPolyList(ind)); + deleteFromList(triRef, ind); + } + + + + + static boolean noHashEdgeIntersectionExists(Triangulator triRef, BBox bb, int i1, + int i2, int ind5, int i5) { + int ind, ind2; + int i, i3, i4; + BBox bb1; + + if(triRef.noHashingEdges==false) + System.out.println("NoHash:noHashEdgeIntersectionExists noHashingEdges is false"); + + triRef.identCntr = 0; + + // check the boundary segments. + for (i = triRef.loopMin; i < triRef.loopMax; ++i) { + ind = triRef.loops[i]; + ind2 = ind; + i3 = triRef.fetchData(ind2); + + do { + ind2 = triRef.fetchNextData(ind2); + i4 = triRef.fetchData(ind2); + // check this segment. we first compute its bounding box. + bb1 = new BBox(triRef, i3, i4); + if (bb.BBoxOverlap(bb1)) { + if (Numerics.segIntersect(triRef, bb.imin, bb.imax, bb1.imin, bb1.imax, i5)) + return true; + } + i3 = i4; + } while (ind2 != ind); + } + + // oops! this segment shares one endpoint with at least four other + // boundary segments! oh well, yet another degenerate situation... + if (triRef.identCntr >= 4) { + if (BottleNeck.checkBottleNeck(triRef, i5, i1, i2, ind5)) + return true; + else + return false; + } + + return false; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/NormalGenerator.java b/src/classes/share/com/sun/j3d/utils/geometry/NormalGenerator.java new file mode 100644 index 0000000..803a5c5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/NormalGenerator.java @@ -0,0 +1,907 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.GeometryInfo; +import com.sun.j3d.utils.geometry.EdgeTable; +import java.util.ArrayList; +import javax.vecmath.Vector3f; +import javax.vecmath.Point3f; + +/** + * The NormalGenerator utility will calculate and fill in the normals + * of a GeometryInfo object. The calculated normals are estimated based + * on an analysis of the indexed coordinate information. If your data + * isn't indexed, index lists will be created.

+ *

+ * If two (or more) triangles in the model share the same coordinate + * index then the normal generator will attempt to generate one normal + * for the vertex, resulting in a "smooth" looking surface. If two + * coordinates don't have the same index then they will have two + * separate normals, even if they have the same position. This will + * result in a "crease" in your object. If you suspect that your + * data isn't properly indexed, call GeometryInfo.recomputeIndexes().

+ *

+ * Of course, sometimes your model *has* a crease in it. That's what + * creaseAngle is. If two triangles' normals differ by more than + * creaseAngle, then the vertex will get two separate normals, creating a + * discontinuous crease in the model. This is perfect for the edge + * of a table or the corner of a cube, for instance. + */ + +public class NormalGenerator { + + private double creaseAngle; + private Vector3f facetNorms[]; + private ArrayList tally; + private GeometryInfo gi; + private int coordInds[]; + private int normalInds[]; + private int colorInds[]; + private int texInds[][]; + private int stripCounts[]; + private static long t1=0, t2=0, t3=0, t4=0, t5=0, t6=0; + private Triangulator tr = null; + private int numTexSets; + + + // 0 - No debug info + // 1 - Facet Normals + // 2 - Connection info + // 4 - Normals + // 8 - StripCounts + // 16 - Timing + // 32 - Hard edges + // 64 - Coordinate and normal indices + // 128 - Vertex normal calculation info + private static final int DEBUG = 0; + + + + // Calculate the normal of each triangle in the list by finding + // the cross product + private void calculatefacetNorms() + { + Point3f coordinates[] = gi.getCoordinates(); + facetNorms = new Vector3f[coordInds.length / 3]; + Vector3f a = new Vector3f(); + Vector3f b = new Vector3f(); + if ((DEBUG & 1) != 0) System.out.println("Facet normals:"); + + if (gi.getOldPrim() != gi.QUAD_ARRAY) { + for (int t = 0 ; t < coordInds.length ; t += 3) { + a.sub(coordinates[coordInds[t + 2]], coordinates[coordInds[t + 1]]); + b.sub(coordinates[coordInds[t + 0]], coordinates[coordInds[t + 1]]); + facetNorms[t / 3] = new Vector3f(); + facetNorms[t / 3].cross(a, b); + facetNorms[t / 3].normalize(); + + if (Float.isNaN(facetNorms[t / 3].x)) { + // Normal isn't valid + facetNorms[t / 3].x = 1.0f; + facetNorms[t / 3].y = facetNorms[t / 3].z = 0.0f; + } + if ((DEBUG & 1) != 0) { + System.out.println(" " + (t/3) + " " + facetNorms[t / 3]); + } + } + } else { + // For quads, the facet normal of both triangles is the cross + // product of the two vectors that make an 'X' across the quad. + for (int t = 0 ; t < coordInds.length ; t += 6) { + a.sub(coordinates[coordInds[t + 2]], coordinates[coordInds[t + 0]]); + b.sub(coordinates[coordInds[t + 5]], coordinates[coordInds[t + 1]]); + facetNorms[t / 3] = new Vector3f(); + facetNorms[t / 3].cross(a, b); + facetNorms[t / 3].normalize(); + + if (Float.isNaN(facetNorms[t / 3].x)) { + // Normal isn't valid + facetNorms[t / 3].x = 1.0f; + facetNorms[t / 3].y = facetNorms[t / 3].z = 0.0f; + } + + // Second triangle of quad + facetNorms[t / 3 + 1] = new Vector3f(facetNorms[t / 3]); + + if ((DEBUG & 1) != 0) { + System.out.println(" " + (t/3) + "&" + (t/3 + 1) + " " + + facetNorms[t / 3]); + } + } + } + } // End of calculatefacetNorms + + + + // The vertex normals will be calculated by averaging the facet normals + // of groups of triangles sharing the vertex. At the end of this routine + // the groups of coordinate indexes will all be made, and the normal + // indices will point to these groups. + // + // The routine works by going through each vertex of each triangle. + // Starting at a triangle, we see if the vertex normal can be shared + // with the neighbor triangle (their facet normals differ by less than + // creaseAngle). If they can be shared, then we move from that triangle + // to the next and the next in a circle around the vertex. + // + // If we hit the edge of the model or a Hard Edge (crease) then we stop + // and then try going around the vertex in the other direction. + // + // Each time we step from one triangle to the next around the center + // vertex, the triangle is added to the group of triangles whose normals + // will be averaged to make the vertex normal. + // + // Returns the largest number of triangles that share a single normal. + // + private int createHardEdges() + { + EdgeTable et = new EdgeTable(coordInds); + tally = new ArrayList(); + int normalMap[] = new int[coordInds.length]; + int maxShare = 1; + float cosine; + boolean smooth; + float threshold = (float)Math.cos(creaseAngle); + boolean goingRight; + + // Set Normal Indices array values to a flag + for (int c = 0 ; c < coordInds.length ; c++) + normalMap[c] = Integer.MAX_VALUE; + + // Cycle through each vertex + for (int c = 0 ; c < coordInds.length ; c++) { + // See if this vertex's normal has already been done + if (normalMap[c] == Integer.MAX_VALUE) { + if ((DEBUG & 32) != 0) { + System.out.println( + "Coordinate Index " + c + ": vertex " + coordInds[c]); + } + // Create a list of vertices used for calculating this normal + ArrayList sharers = new ArrayList(); + tally.add(sharers); + // Put this coordinate in the list + sharers.add(new Integer(c)); + // Point this coordinate's index at its list + normalMap[c] = tally.size() - 1; + + // First do right edge + goingRight = true; + Edge edge = new Edge(coordInds[c], + coordInds[(c + 1) % 3 == 0 ? c - 2 : c + 1]); + if ((DEBUG & 32) != 0) + System.out.println( " Right edge: " + edge); + + // This is how we'll know we've gone all the way around + int endVertex = coordInds[c % 3 == 0 ? c + 2 : c - 1]; + + // Start at current triangle + int cur = c; + + // Proceed from one triangle to the next + do { + // Look up edge in Edge Table to find neighbor triangle + Integer tableVal = et.get(edge.v2, edge.v1); + if ((DEBUG & 32) != 0) { + System.out.println( + " Search Edge: " + (new Edge(edge.v2, edge.v1))); + } + + // See if there is no triangle on the other side of this edge + if (tableVal == null) { + smooth = false; + if ((DEBUG & 32) != 0) + System.out.println(" No neighboring triangle found."); + } else { + + int n = tableVal.intValue(); + if ((DEBUG & 32) != 0) { + System.out.println( + " Table lookup result: " + n + " (vertex " + coordInds[n] + + ")"); + System.out.print(" Triangles " + (cur/3) + " & " + (n/3) + + ": "); + } + + cosine = facetNorms[cur / 3].dot(facetNorms[n / 3]); + smooth = cosine > threshold; + if (smooth) { + // The center coordinate (c) shares the same normal in these + // two triangles. Find that coordinate and set its index + // normalMap[n] = normalMap[cur]; + int centerv = (((n + 1) % 3) == 0 ? n - 2 : n + 1); + if (coordInds[c] != coordInds[centerv]) { + centerv = ((n % 3) == 0 ? n + 2 : n - 1); + } + + if ((DEBUG & 32) != 0) + System.out.println("Smooth! Adding " + centerv); + + if (normalMap[centerv] != Integer.MAX_VALUE) { + smooth = false; + if ((DEBUG & 32) != 0) System.out.println( + " Error: Coordinate aleady has normal (bad data)."); + } else { + + normalMap[centerv] = tally.size() - 1; + + // Consider this triangle's facet normal when calculating the + // vertex's normal + sharers.add(new Integer(centerv)); + if (sharers.size() > maxShare) maxShare = sharers.size(); + + // Continue on around the vertex to the next triangle + cur = n; + if (goingRight) edge.v2 = coordInds[cur]; + else edge.v1 = coordInds[cur]; + } + } else if ((DEBUG & 32) != 0) System.out.println("Hard Edge!"); + } + + if (!smooth && goingRight) { + + // We've hit an impasse going right, so now try going left + // from the original triangle + goingRight = false; + smooth = true; // Trick do loop + cur = c; // Go back to original triangle + + edge = new Edge(coordInds[(c % 3) == 0 ? c + 2 : c - 1], + coordInds[c]); + if ((DEBUG & 32) != 0) System.out.println( " Left edge: " + edge); + + } + + } while (smooth && ((goingRight && (edge.v2 != endVertex)) || + !goingRight)); + + if (((DEBUG & 32) != 0) && goingRight && (edge.v2 == endVertex)) + System.out.println(" Went all the way around!"); + } + } + + if ((DEBUG & 32) != 0) { + System.out.println("Tally:"); + for (int i = 0 ; i < tally.size() ; i++) { + System.out.print(" " + i + ": "); + ArrayList sharers = (ArrayList)(tally.get(i)); + for (int j = 0 ; j < sharers.size() ; j++) { + System.out.print(" " + sharers.get(j)); + } + System.out.println(); + } + + System.out.println("Normal Indexes:"); + for (int i = 0 ; i < normalMap.length ; i++) { + System.out.println(" " + i + ": " + normalMap[i]); + } + } + + return maxShare; + } // End of createHardEdges + + + // Now take all of the triangles who share a vertex (who have + // been grouped by the hard edge process) and average their facet + // normals to get the vertex normal + // + // This routine has something of a hack in it. We found that our + // method of breaking up data into individual triangles before + // calculating normals was causing a bug. If a polygon was broken + // into two triangles at a particular vertex, then that facet's + // normal would get averaged into the vertex normal *twice*, + // skewing the normal toward the decomposed facet. So what we did + // was to check for duplicate facet normals as we're averaging, + // not allowing the same facet normal to be counted twice. + // + // What should be done is to put the facets' normals into a separate, + // indexed, table. That way, to tell if two triangles have the + // same normal, we just need to compare indexes. This would speed up + // the process of checking for duplicates. + private void calculateVertexNormals(int maxShare) + { + Vector3f normals[]; + ArrayList sharers; + int triangle; + Vector3f fn[]; // Normals of facets joined by this vertex + int fnsize; // Number of elements currently ised in fn + + if (creaseAngle != 0.0) { + fn = new Vector3f[maxShare]; + normals = new Vector3f[tally.size()]; + normalInds = new int[coordInds.length]; + for (int n = 0 ; n < tally.size() ; n++) { + sharers = (ArrayList)(tally.get(n)); + if ((DEBUG & 128) != 0) { + System.out.println(n + ": " + sharers.size() + + " triangles:"); + } + fnsize = 0; + normals[n] = new Vector3f(); + for (int t = 0 ; t < sharers.size() ; t++) { + int v = ((Integer)sharers.get(t)).intValue(); + // See if index removed by hard edge process + if (v != -1) { + triangle = v / 3; + if (!Float.isNaN(facetNorms[triangle].x)) { + + int f; + // Don't add the same facet normal twice + for (f = 0 ; f < fnsize ; f++) { + if (fn[f].equals(facetNorms[triangle])) break; + } + + normalInds[v] = n; + if (f == fnsize) { + // Didn't find this triangle's normal already in the list + normals[n].add(facetNorms[triangle]); + fn[fnsize++] = facetNorms[triangle]; + } else if ((DEBUG & 128) != 0) { + System.out.println(" triangle " + t + " ignored."); + } + } + } + } + normals[n].normalize(); + if (Float.isNaN(normals[n].x)) { + // Normal isn't valid + normals[n].x = 1.0f; normals[n].y = normals[n].z = 0.0f; + } + if ((DEBUG & 128) != 0) { + for (int t = 0 ; t < sharers.size() ; t++) { + int v = ((Integer)sharers.get(t)).intValue(); + if (v != -1) { + triangle = v / 3; + System.out.println(" " + facetNorms[triangle]); + } + } + System.out.println(" Result: " + normals[n]); + System.out.println(); + } + } + } else { + // This code renders the facet normals + normals = facetNorms; + + normalInds = new int[facetNorms.length * 3]; + for (int i = 0 ; i < facetNorms.length ; i++) { + normalInds[i * 3 + 0] = i; + normalInds[i * 3 + 1] = i; + normalInds[i * 3 + 2] = i; + } + } + gi.setNormals(normals); + + if ((DEBUG & 4) != 0) { + System.out.println("Normals:"); + for (int i = 0 ; i < normals.length ; i++) { + System.out.println(" " + i + " " + normals[i]); + } + System.out.println("Indices:"); + for (int i = 0 ; i < normalInds.length ; i++) { + System.out.println(" " + i + " " + normalInds[i]); + } + } + } // End of calculateVertexNormals + + + + // The original data was in quads and we converted it to triangles to + // calculate the normals. Now we are converting it back to quads. + // It's a very simple algorithm. + // Since both sub-triangles of a quad have the same facet normal, + // there should never be a hard edge down the middle of the quad. + // Therefore, the vertices of the shared edge of the two subtriangles + // should have the same normal index in both triangles. + private int[] triToQuadIndices(int oldList[]) + { + if (oldList == null) return null; + + int newList[] = new int[oldList.length / 6 * 4]; + // index list to pass back + // Cycle through each pair of triangles and put them together + for (int q = 0 ; q < oldList.length / 6 ; q++) { + newList[q * 4 + 0] = oldList[q * 6 + 0]; + newList[q * 4 + 1] = oldList[q * 6 + 1]; + newList[q * 4 + 2] = oldList[q * 6 + 2]; + newList[q * 4 + 3] = oldList[q * 6 + 5]; + } + + return newList; + } // End of triToQuadIndices + + + + // The original data was in quads. We converted it to triangles to + // calculate normals. Now we need to convert it back to quads. + private void convertTriToQuad(GeometryInfo geom) + { + // Create the new arrays + geom.setCoordinateIndices( + triToQuadIndices(geom.getCoordinateIndices())); + geom.setColorIndices(triToQuadIndices(geom.getColorIndices())); + geom.setNormalIndices(triToQuadIndices(geom.getNormalIndices())); + int num = geom.getTexCoordSetCount(); + for (int i = 0 ; i < num ; i++) { + geom.setTextureCoordinateIndices(i, + triToQuadIndices(geom.getTextureCoordinateIndices(i))); + } + geom.setPrimitive(gi.QUAD_ARRAY); + } // End of convertTriToQuad() + + + + // The original data was in fans and we converted it to triangles to + // calculate the normals. Now we are converting it back to fans. + // We have already calculated the new stripCounts, so now we need + // to change the index lists so they match up with the stripCounts. + // It's a very simple algorithm. The paramater oldList is the + // index list being compressed back into fans (could be coordinate, + // color, normal, or texCoord indices) and numVerts is the pre- + // calculated total of all entries of the stripCounts array. + private int[] triToFanIndices(int sc[], int oldList[], int numVerts) + { + if (oldList == null) return null; + + int newList[] = new int[numVerts]; // index list to pass back + int vert1 = 0; // 1st vertex of triangle + int n = 0; // index into newList + // Cycle through each fan in the new list + for (int f = 0 ; f < sc.length ; f++) { + // Copy entire first triangle into new array + newList[n++] = oldList[vert1++]; + newList[n++] = oldList[vert1++]; + newList[n++] = oldList[vert1++]; + // Each additional triangle in the fan only needs one vertex + for (int t = 3 ; t < sc[f] ; t++) { + newList[n++] = oldList[vert1 + 2]; + vert1 += 3; + } + } + return newList; + } // End of triToFanIndices + + + + // + // The original data was in fans. We converted it to triangles to + // calculate normals. Now we need to convert it back to fans. + // The hard part is that, if we found a hard edge in the middle of + // a fan, we need to split the fan into two. To tell if there's + // a hard edge there, we compare the normal indices of both + // vertices comprising the edge. + private void convertTriToFan(GeometryInfo geom, int oldStripCounts[]) + { + int ni[] = geom.getNormalIndices(); + + // + // Calculate new stripCounts array + // + int tri = 0; // Which triangle currently being converted + ArrayList newStripCounts; + newStripCounts = new ArrayList(oldStripCounts.length + 100); + + // Use the original stripCounts array + for (int f = 0 ; f < oldStripCounts.length ; f++) { + int stripCount = 3; + + // Cycle through each triangle in the fan, comparing to the + // next triangle in the fan. Compare the normal indices of + // both vertices of the edge to see if the two triangles + // can be mated + for (int t = 0 ; t < oldStripCounts[f] - 3 ; t++) { + // The first vertex of this triangle must match the first + // vertex of the next, AND the third vertex of this + // triangle must match the second vertex of the next. + if ((ni[tri * 3] == ni[(tri+1) * 3]) && + (ni[tri * 3 + 2] == ni[(tri+1) * 3 + 1])) { + // OK to extend fan + stripCount++; + } else { + // hard edge within fan + newStripCounts.add(new Integer(stripCount)); + stripCount = 3; + } + tri++; + } + tri++; + newStripCounts.add(new Integer(stripCount)); + } + + // Convert from ArrayList to int[] + int sc[] = new int[newStripCounts.size()]; + for (int i = 0 ; i < sc.length ; i++) + sc[i] = ((Integer)newStripCounts.get(i)).intValue(); + newStripCounts = null; + + // + // Change the index lists so they match up with the new stripCounts + // + + // See how many vertices we'll need + int c = 0; + for (int i = 0 ; i < sc.length ; i++) c += sc[i]; + + // Create the new arrays + geom.setCoordinateIndices( + triToFanIndices(sc, geom.getCoordinateIndices(), c)); + geom.setColorIndices(triToFanIndices(sc, geom.getColorIndices(), c)); + geom.setNormalIndices(triToFanIndices(sc, geom.getNormalIndices(), c)); + int num = geom.getTexCoordSetCount(); + for (int i = 0 ; i < num ; i++) { + geom.setTextureCoordinateIndices(i, + triToFanIndices(sc, geom.getTextureCoordinateIndices(i), c)); + } + + if ((DEBUG & 8) != 0) { + System.out.print("Old stripCounts:"); + for (int i = 0 ; i < oldStripCounts.length ; i++) { + System.out.print(" " + oldStripCounts[i]); + } + System.out.println(); + System.out.print("New stripCounts:"); + for (int i = 0 ; i < sc.length ; i++) { + System.out.print(" " + sc[i]); + } + System.out.println(); + } + + geom.setStripCounts(sc); + geom.setPrimitive(gi.TRIANGLE_FAN_ARRAY); + } // End of convertTriToFan() + + + + // The original data was in strips and we converted it to triangles to + // calculate the normals. Now we are converting it back to strips. + // We have already calculated the new stripCounts, so now we need + // to change the index lists so they match up with the stripCounts. + // It's a very simple algorithm. The paramater oldList is the + // index list being compressed back into strips (could be coordinate, + // color, normal, or texCoord indices) and numVerts is the pre- + // calculated total of all entries of the stripCounts array. + private int[] triToStripIndices(int sc[], int oldList[], int numVerts) + { + if (oldList == null) return null; + + int newList[] = new int[numVerts]; // index list to pass back + int vert1 = 0; // 1st vertex of triangle + int n = 0; // index into newList + // Cycle through each strip in the new list + for (int f = 0 ; f < sc.length ; f++) { + // Copy entire first triangle into new array + newList[n++] = oldList[vert1++]; + newList[n++] = oldList[vert1++]; + newList[n++] = oldList[vert1++]; + // Each additional triangle in the fan only needs one vertex + for (int t = 3 ; t < sc[f] ; t++) { + // Every other triangle has been reversed to preserve winding + newList[n++] = oldList[vert1 + 2 - (t % 2)]; + vert1 += 3; + } + } + return newList; + } // End of triToStripIndices + + + + private void convertTriToStrip(GeometryInfo geom, int oldStripCounts[]) + { + int ni[] = geom.getNormalIndices(); + + // + // Calculate new stripCounts array + // + int tri = 0; // Which triangle currently being converted + ArrayList newStripCounts; + newStripCounts = new ArrayList(oldStripCounts.length + 100); + + // Use the original stripCounts array + for (int f = 0 ; f < oldStripCounts.length ; f++) { + int stripCount = 3; + + // Cycle through each triangle in the strip, comparing to the + // next triangle in the strip. Compare the normal indices of + // both vertices of the edge to see if the two triangles + // can be mated. + for (int t = 0 ; t < oldStripCounts[f] - 3 ; t++) { + // Every other triangle has been reversed to preserve winding + if (t % 2 == 0) { + // The middle vertex of this triangle needs to match the + // first vertex of the next, AND the third vertices of + // the two triangles must match + if ((ni[tri * 3 + 1] == ni[(tri+1) * 3]) && + (ni[tri * 3 + 2] == ni[(tri+1) * 3 + 2])) { + // OK to extend strip + stripCount++; + } else { + // hard edge within strip + newStripCounts.add(new Integer(stripCount)); + stripCount = 3; + + // Can't start a new strip on an odd edge so output + // isolated triangle + if (t < oldStripCounts[f] - 4) { + newStripCounts.add(new Integer(3)); + t++; + } + } + } else { + // The middle vertex of this triangle must match the middle + // vertex of the next, AND the third vertex of this triangle + // must match the first vertex of the next + if ((ni[tri * 3 + 1] == ni[(tri+1) * 3 + 1]) && + (ni[tri * 3 + 2] == ni[(tri+1) * 3])) { + // OK to extend strip + stripCount++; + } else { + // hard edge within strip + newStripCounts.add(new Integer(stripCount)); + stripCount = 3; + } + } + tri++; + } + tri++; + newStripCounts.add(new Integer(stripCount)); + } + + // Convert from ArrayList to int[] + int sc[] = new int[newStripCounts.size()]; + for (int i = 0 ; i < sc.length ; i++) + sc[i] = ((Integer)newStripCounts.get(i)).intValue(); + newStripCounts = null; + + // + // Change the index lists so they match up with the new stripCounts + // + + // See how many vertices we'll need + int c = 0; + for (int i = 0 ; i < sc.length ; i++) c += sc[i]; + + // Create the new arrays + geom.setCoordinateIndices( + triToStripIndices(sc, geom.getCoordinateIndices(), c)); + geom.setColorIndices(triToStripIndices(sc, geom.getColorIndices(), c)); + geom.setNormalIndices(triToStripIndices(sc, geom.getNormalIndices(), c)); + int num = geom.getTexCoordSetCount(); + for (int i = 0 ; i < num ; i++) { + geom.setTextureCoordinateIndices(i, + triToStripIndices(sc, geom.getTextureCoordinateIndices(i), c)); + } + + if ((DEBUG & 8) != 0) { + System.out.print("Old stripCounts:"); + for (int i = 0 ; i < oldStripCounts.length ; i++) { + System.out.print(" " + oldStripCounts[i]); + } + System.out.println(); + System.out.print("New stripCounts:"); + for (int i = 0 ; i < sc.length ; i++) { + System.out.print(" " + sc[i]); + } + System.out.println(); + } + + geom.setStripCounts(sc); + geom.setPrimitive(gi.TRIANGLE_STRIP_ARRAY); + }// End of convertTriToStrip() + + + + /** + * Used when the user calls the NormalGenerator and not + * the Stripifier or Triangulator. We had to convert + * the user's data to indexed triangles before we could + * generate normals, so now we need to switch back to + * the original format. + */ + void convertBackToOldPrim(GeometryInfo geom, int oldPrim, + int oldStripCounts[]) + { + if (oldPrim == geom.TRIANGLE_ARRAY) return; + + switch (oldPrim) { + case GeometryInfo.QUAD_ARRAY: + convertTriToQuad(geom); + break; + case GeometryInfo.TRIANGLE_FAN_ARRAY: + convertTriToFan(geom, oldStripCounts); + break; + case GeometryInfo.TRIANGLE_STRIP_ARRAY: + convertTriToStrip(geom, oldStripCounts); + break; + } + + if ((DEBUG & 64) != 0) { + System.out.println("Coordinate and normal indices (original):"); + for (int i = 0 ; i < coordInds.length ; i++) { + System.out.println(i + " " + coordInds[i] + " " + normalInds[i]); + } + } + } // End of convertBackToOldPrim + + + + /** + * Generate normals for the GeometryInfo object. If the GeometryInfo + * object didn't previously contain indexed data, indexes are made + * by collapsing identical positions into a single index. Any + * normal information previously contained in the GeometryInfo + * object is lost. Strips and Fans are converted into individual + * triangles for Normal generation, but are stitched back together + * if GeometryInfo.getGeometryArray() (or getIndexedGeometryArray()) + * is called without stripifying first. + */ + public void generateNormals(GeometryInfo geom) + { + gi = geom; + gi.setNormals((Vector3f[])null); + gi.setNormalIndices(null); + + long time = 0L; + if ((DEBUG & 16) != 0) { + time = System.currentTimeMillis(); + } + + if (gi.getPrimitive() == gi.POLYGON_ARRAY) { + if (tr == null) tr = new Triangulator(); + tr.triangulate(gi); + } else { + // NOTE: We should calculate facet normals before converting to + // triangles. + gi.rememberOldPrim(); + gi.convertToIndexedTriangles(); + } + + // Cache some of the GeometryInfo fields + coordInds = gi.getCoordinateIndices(); + colorInds = gi.getColorIndices(); + normalInds = gi.getNormalIndices(); + numTexSets = gi.getTexCoordSetCount(); + texInds = new int[numTexSets][]; + for (int i = 0 ; i < numTexSets ; i++) { + texInds[i] = gi.getTextureCoordinateIndices(i); + } + stripCounts = gi.getStripCounts(); + + if ((DEBUG & 16) != 0) { + t1 += System.currentTimeMillis() - time; + System.out.println("Convert to triangles: " + t1 + " ms"); + time = System.currentTimeMillis(); + } + + calculatefacetNorms(); + if ((DEBUG & 16) != 0) { + t2 += System.currentTimeMillis() - time; + System.out.println("Calculate Facet Normals: " + t2 + " ms"); + time = System.currentTimeMillis(); + } + + int maxShare = createHardEdges(); + if ((DEBUG & 16) != 0) { + t3 += System.currentTimeMillis() - time; + System.out.println("Hard Edges: " + t3 + " ms"); + time = System.currentTimeMillis(); + } + + calculateVertexNormals(maxShare); + if ((DEBUG & 16) != 0) { + t5 += System.currentTimeMillis() - time; + System.out.println("Vertex Normals: " + t5 + " ms"); + time = System.currentTimeMillis(); + } + + if ((DEBUG & 64) != 0) { + System.out.println("Coordinate and normal indices (triangles):"); + for (int i = 0 ; i < coordInds.length ; i++) { + System.out.println(i + " " + coordInds[i] + " " + normalInds[i]); + } + } + + // We have been caching some info from the GeometryInfo, so we need + // to update it. + gi.setCoordinateIndices(coordInds); + gi.setColorIndices(colorInds); + gi.setNormalIndices(normalInds); + for (int i = 0 ; i < numTexSets ; i++) { + gi.setTextureCoordinateIndices(i, texInds[i]); + } + gi.setStripCounts(stripCounts); + } // End of generateNormals + + + + /** + * Set the crease angle. + * If two triangles' normals differ by more than + * creaseAngle, then the vertex will get two separate normals, creating a + * discontinuous crease in the model. This is perfect for the edge + * of a table or the corner of a cube, for instance. Clamped to + * 0 <= creaseAngle <= PI. Optimizations are made for creaseAngle == 0 + * (facet normals) and creaseAngle == PI (smooth shading). + */ + public void setCreaseAngle(double radians) + { + if (radians > Math.PI) radians = Math.PI; + if (radians < 0.0) radians = 0.0; + creaseAngle = radians; + } // End of setCreaseAngle + + + + /** + * Returns the current value of the crease angle, in radians. + */ + public double getCreaseAngle() + { + return creaseAngle; + } // End of getCreaseAngle + + + + /** + * Constructor. Construct a NormalGenerator object with creaseAngle + * set to the given value. + */ + public NormalGenerator(double radians) + { + creaseAngle = radians; + } // End of NormalGenerator(double) + + + + /** + * Constructor. Construct a NormalGenerator object with creaseAngle + * set to 44 degrees (0.767944871 radians). + */ + public NormalGenerator() + { + this(44.0 * Math.PI / 180.0); + } // End of NormalGenerator() +} // End of class NormalGenerator + +// End of file NormalGenerator.java diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Numerics.java b/src/classes/share/com/sun/j3d/utils/geometry/Numerics.java new file mode 100644 index 0000000..386123b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Numerics.java @@ -0,0 +1,644 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Numerics { + + static double max3(double a, double b, double c) + { + return (((a) > (b)) ? (((a) > (c)) ? (a) : (c)) + : (((b) > (c)) ? (b) : (c))); + } + + static double min3(double a, double b, double c) + { + return (((a) < (b)) ? (((a) < (c)) ? (a) : (c)) + : (((b) < (c)) ? (b) : (c))); + } + + static boolean lt(double a, double eps) + { + return ((a) < -eps); + } + + static boolean le(double a, double eps) + { + return (a <= eps); + } + + static boolean ge(double a, double eps) + { + return (!((a) <= -eps)); + } + + static boolean eq(double a, double eps) + { + return (((a) <= eps) && !((a) < -eps)); + } + + static boolean gt(double a, double eps) + { + return !((a) <= eps); + } + + static double baseLength(Tuple2f u, Tuple2f v) { + double x, y; + x = (v).x - (u).x; + y = (v).y - (u).y; + return Math.abs(x) + Math.abs(y); + } + + static double sideLength(Tuple2f u, Tuple2f v) { + double x, y; + x = (v).x - (u).x; + y = (v).y - (u).y; + return x * x + y * y; + } + + /** + * This checks whether i3, which is collinear with i1, i2, is + * between i1, i2. note that we rely on the lexicographic sorting of the + * points! + */ + static boolean inBetween(int i1, int i2, int i3) { + return ((i1 <= i3) && (i3 <= i2)); + } + + static boolean strictlyInBetween(int i1, int i2, int i3) { + return ((i1 < i3) && (i3 < i2)); + } + + /** + * this method computes the determinant det(points[i],points[j],points[k]) + * in a consistent way. + */ + static double stableDet2D(Triangulator triRef, int i, int j, int k) { + double det; + Point2f numericsHP, numericsHQ, numericsHR; + + // if((triRef.inPointsList(i)==false)||(triRef.inPointsList(j)==false)|| + // (triRef.inPointsList(k)==false)) + // System.out.println("Numerics.stableDet2D Not inPointsList " + i + " " + j + // + " " + k); + + if ((i == j) || (i == k) || (j == k)) { + det = 0.0; + } + else { + numericsHP = triRef.points[i]; + numericsHQ = triRef.points[j]; + numericsHR = triRef.points[k]; + + if (i < j) { + if (j < k) /* i < j < k */ + det = Basic.det2D(numericsHP, numericsHQ, numericsHR); + else if (i < k) /* i < k < j */ + det = -Basic.det2D(numericsHP, numericsHR, numericsHQ); + else /* k < i < j */ + det = Basic.det2D(numericsHR, numericsHP, numericsHQ); + } + else { + if (i < k) /* j < i < k */ + det = -Basic.det2D(numericsHQ, numericsHP, numericsHR); + else if (j < k) /* j < k < i */ + det = Basic.det2D(numericsHQ, numericsHR, numericsHP); + else /* k < j < i */ + det = -Basic.det2D(numericsHR, numericsHQ, numericsHP); + } + } + + return det; + } + + /** + * Returns the orientation of the triangle. + * @return +1 if the points i, j, k are given in CCW order; + * -1 if the points i, j, k are given in CW order; + * 0 if the points i, j, k are collinear. + */ + static int orientation(Triangulator triRef, int i, int j, int k) { + int ori; + double numericsHDet; + numericsHDet = stableDet2D(triRef, i, j, k); + // System.out.println("orientation : numericsHDet " + numericsHDet); + if (lt(numericsHDet, triRef.epsilon)) ori = -1; + else if (gt(numericsHDet, triRef.epsilon)) ori = 1; + else ori = 0; + return ori; + } + + /** + * This method checks whether l is in the cone defined by i, j and j, k + */ + static boolean isInCone(Triangulator triRef, int i, int j, int k, + int l, boolean convex) { + boolean flag; + int numericsHOri1, numericsHOri2; + + // if((triRef.inPointsList(i)==false)||(triRef.inPointsList(j)==false)|| + // (triRef.inPointsList(k)==false)||(triRef.inPointsList(l)==false)) + // System.out.println("Numerics.isInCone Not inPointsList " + i + " " + j + // + " " + k + " " + l); + + flag = true; + if (convex) { + if (i != j) { + numericsHOri1 = orientation(triRef, i, j, l); + // System.out.println("isInCone : i != j, numericsHOri1 = " + numericsHOri1); + if (numericsHOri1 < 0) flag = false; + else if (numericsHOri1 == 0) { + if (i < j) { + if (!inBetween(i, j, l)) flag = false; + } + else { + if (!inBetween(j, i, l)) flag = false; + } + } + } + if ((j != k) && (flag == true)) { + numericsHOri2 = orientation(triRef, j, k, l); + // System.out.println("isInCone : ((j != k) && (flag == true)), numericsHOri2 = " + + // numericsHOri2); + if (numericsHOri2 < 0) flag = false; + else if (numericsHOri2 == 0) { + if (j < k) { + if (!inBetween(j, k, l)) flag = false; + } + else { + if (!inBetween(k, j, l)) flag = false; + } + } + } + } + else { + numericsHOri1= orientation(triRef, i, j, l); + if (numericsHOri1 <= 0) { + numericsHOri2 = orientation(triRef, j, k, l); + if (numericsHOri2 < 0) flag = false; + } + } + return flag; + } + + + /** + * Returns convex angle flag. + * @return 0 ... if angle is 180 degrees
+ * 1 ... if angle between 0 and 180 degrees
+ * 2 ... if angle is 0 degrees
+ * -1 ... if angle between 180 and 360 degrees
+ * -2 ... if angle is 360 degrees
+ */ + static int isConvexAngle(Triangulator triRef, int i, int j, int k, int ind) { + int angle; + double numericsHDot; + int numericsHOri1; + Point2f numericsHP, numericsHQ; + + // if((triRef.inPointsList(i)==false)||(triRef.inPointsList(j)==false)|| + // (triRef.inPointsList(k)==false)) + // System.out.println("Numerics.isConvexAngle: Not inPointsList " + i + " " + j + // + " " + k); + + if (i == j) { + if (j == k) { + // all three vertices are identical; we set the angle to 1 in + // order to enable clipping of j. + return 1; + } + else { + // two of the three vertices are identical; we set the angle to 1 + // in order to enable clipping of j. + return 1; + } + } + else if (j == k) { + // two vertices are identical. we could either determine the angle + // by means of yet another lengthy analysis, or simply set the + // angle to -1. using -1 means to err on the safe side, as all the + // incarnations of this vertex will be clipped right at the start + // of the ear-clipping algorithm. thus, eventually there will be no + // other duplicates at this vertex position, and the regular + // classification of angles will yield the correct answer for j. + return -1; + } + else { + numericsHOri1 = orientation(triRef, i, j, k); + // System.out.println("i " + i + " j " + j + " k " + k + " ind " + ind + + // ". In IsConvexAngle numericsHOri1 is " + + // numericsHOri1); + if (numericsHOri1 > 0) { + angle = 1; + } + else if (numericsHOri1 < 0) { + angle = -1; + } + else { + // 0, 180, or 360 degrees. + numericsHP = new Point2f(); + numericsHQ = new Point2f(); + Basic.vectorSub2D(triRef.points[i], triRef.points[j], numericsHP); + Basic.vectorSub2D(triRef.points[k], triRef.points[j], numericsHQ); + numericsHDot = Basic.dotProduct2D(numericsHP, numericsHQ); + if (numericsHDot < 0.0) { + // 180 degrees. + angle = 0; + } + else { + // 0 or 360 degrees? this cannot be judged locally, and more + // work is needed. + + angle = spikeAngle(triRef, i, j, k, ind); + // System.out.println("SpikeAngle return is "+ angle); + } + } + } + return angle; + } + + + /** + * This method checks whether point i4 is inside of or on the boundary + * of the triangle i1, i2, i3. + */ + static boolean pntInTriangle(Triangulator triRef, int i1, int i2, int i3, int i4) { + boolean inside; + int numericsHOri1; + + inside = false; + numericsHOri1 = orientation(triRef, i2, i3, i4); + if (numericsHOri1 >= 0) { + numericsHOri1 = orientation(triRef, i1, i2, i4); + if (numericsHOri1 >= 0) { + numericsHOri1 = orientation(triRef, i3, i1, i4); + if (numericsHOri1 >= 0) inside = true; + } + } + return inside; + } + + + /** + * This method checks whether point i4 is inside of or on the boundary + * of the triangle i1, i2, i3. it also returns a classification if i4 is + * on the boundary of the triangle (except for the edge i2, i3). + */ + static boolean vtxInTriangle(Triangulator triRef, int i1, int i2, int i3, + int i4, int[] type) { + boolean inside; + int numericsHOri1; + + inside = false; + numericsHOri1 = orientation(triRef, i2, i3, i4); + if (numericsHOri1 >= 0) { + numericsHOri1 = orientation(triRef, i1, i2, i4); + if (numericsHOri1 > 0) { + numericsHOri1 = orientation(triRef, i3, i1, i4); + if (numericsHOri1 > 0) { + inside = true; + type[0] = 0; + } + else if (numericsHOri1 == 0) { + inside = true; + type[0] = 1; + } + } + else if (numericsHOri1 == 0) { + numericsHOri1 = orientation(triRef, i3, i1, i4); + if (numericsHOri1 > 0) { + inside = true; + type[0] = 2; + } + else if (numericsHOri1 == 0) { + inside = true; + type[0] = 3; + } + } + } + return inside; + } + + + /** + * Checks whether the line segments i1, i2 and i3, i4 intersect. no + * intersection is reported if they intersect at a common vertex. + * the function assumes that i1 <= i2 and i3 <= i4. if i3 or i4 lies + * on i1, i2 then an intersection is reported, but no intersection is + * reported if i1 or i2 lies on i3, i4. this function is not symmetric! + */ + static boolean segIntersect(Triangulator triRef, int i1, int i2, int i3, + int i4, int i5) { + int ori1, ori2, ori3, ori4; + + // if((triRef.inPointsList(i1)==false)||(triRef.inPointsList(i2)==false)|| + // (triRef.inPointsList(i3)==false)||(triRef.inPointsList(i4)==false)) + // System.out.println("Numerics.segIntersect Not inPointsList " + i1 + " " + i2 + // + " " + i3 + " " + i4); + // + // if((i1 > i2) || (i3 > i4)) + // System.out.println("Numerics.segIntersect i1>i2 or i3>i4 " + i1 + " " + i2 + // + " " + i3 + " " + i4); + + if ((i1 == i2) || (i3 == i4)) return false; + if ((i1 == i3) && (i2 == i4)) return true; + + if ((i3 == i5) || (i4 == i5)) ++(triRef.identCntr); + + ori3 = orientation(triRef, i1, i2, i3); + ori4 = orientation(triRef, i1, i2, i4); + if (((ori3 == 1) && (ori4 == 1)) || + ((ori3 == -1) && (ori4 == -1))) return false; + + if (ori3 == 0) { + if (strictlyInBetween(i1, i2, i3)) return true; + if (ori4 == 0) { + if (strictlyInBetween(i1, i2, i4)) return true; + } + else return false; + } + else if (ori4 == 0) { + if (strictlyInBetween(i1, i2, i4)) return true; + else return false; + } + + ori1 = orientation(triRef, i3, i4, i1); + ori2 = orientation(triRef, i3, i4, i2); + if (((ori1 <= 0) && (ori2 <= 0)) || + ((ori1 >= 0) && (ori2 >= 0))) return false; + + return true; + } + + + /** + * this function computes a quality measure of a triangle i, j, k. + * it returns the ratio `base / height', where base is the length of the + * longest side of the triangle, and height is the normal distance + * between the vertex opposite of the base side and the base side. (as + * usual, we again use the l1-norm for distances.) + */ + static double getRatio(Triangulator triRef, int i, int j, int k) { + double area, a, b, c, base, ratio; + Point2f p, q, r; + + // if((triRef.inPointsList(i)==false)||(triRef.inPointsList(j)==false)|| + // (triRef.inPointsList(k)==false)) + // System.out.println("Numerics.getRatio: Not inPointsList " + i + " " + j + // + " " + k); + + p = triRef.points[i]; + q = triRef.points[j]; + r = triRef.points[k]; + + + a = baseLength(p, q); + b = baseLength(p, r); + c = baseLength(r, q); + base = max3(a, b, c); + + if ((10.0 * a) < Math.min(b, c)) return 0.1; + + area = stableDet2D(triRef, i, j, k); + if (lt(area, triRef.epsilon)) { + area = -area; + } + else if (!gt(area, triRef.epsilon)) { + if (base > a) return 0.1; + else return Double.MAX_VALUE; + } + + ratio = base * base / area; + + if (ratio < 10.0) return ratio; + else { + if (a < base) return 0.1; + else return ratio; + } + } + + + static int spikeAngle(Triangulator triRef, int i, int j, int k, int ind) { + int ind1, ind2, ind3; + int i1, i2, i3; + + // if((triRef.inPointsList(i)==false)||(triRef.inPointsList(j)==false)|| + // (triRef.inPointsList(k)==false)) + // System.out.println("Numerics.spikeAngle: Not inPointsList " + i + " " + j + // + " " + k); + + ind2 = ind; + i2 = triRef.fetchData(ind2); + + // if(i2 != j) + // System.out.println("Numerics.spikeAngle: i2 != j " + i2 + " " + j ); + + ind1 = triRef.fetchPrevData(ind2); + i1 = triRef.fetchData(ind1); + + // if(i1 != i) + // System.out.println("Numerics.spikeAngle: i1 != i " + i1 + " " + i ); + + ind3 = triRef.fetchNextData(ind2); + i3 = triRef.fetchData(ind3); + + // if(i3 != k) + // System.out.println("Numerics.spikeAngle: i3 != k " + i3 + " " + k ); + + return recSpikeAngle(triRef, i, j, k, ind1, ind3); + } + + + + static int recSpikeAngle(Triangulator triRef, int i1, int i2, int i3, + int ind1, int ind3) { + int ori, ori1, ori2, i0, ii1, ii2; + Point2f pq, pr; + double dot; + + if (ind1 == ind3) { + // all points are collinear??? well, then it does not really matter + // which angle is returned. perhaps, -2 is the best bet as my code + // likely regards this contour as a hole. + return -2; + } + + if (i1 != i3) { + if (i1 < i2) { + ii1 = i1; + ii2 = i2; + } + else { + ii1 = i2; + ii2 = i1; + } + if (inBetween(ii1, ii2, i3)) { + i2 = i3; + ind3 = triRef.fetchNextData(ind3); + i3 = triRef.fetchData(ind3); + + if (ind1 == ind3) return 2; + ori = orientation(triRef, i1, i2, i3); + if (ori > 0) return 2; + else if (ori < 0) return -2; + else return recSpikeAngle(triRef, i1, i2, i3, ind1, ind3); + } + else { + i2 = i1; + ind1 = triRef.fetchPrevData(ind1); + i1 = triRef.fetchData(ind1); + if (ind1 == ind3) return 2; + ori = orientation(triRef, i1, i2, i3); + if (ori > 0) return 2; + else if (ori < 0) return -2; + else return recSpikeAngle(triRef, i1, i2, i3, ind1, ind3); + } + } + else { + i0 = i2; + i2 = i1; + ind1 = triRef.fetchPrevData(ind1); + i1 = triRef.fetchData(ind1); + + if (ind1 == ind3) return 2; + ind3 = triRef.fetchNextData(ind3); + i3 = triRef.fetchData(ind3); + if (ind1 == ind3) return 2; + ori = orientation(triRef, i1, i2, i3); + if (ori > 0) { + ori1 = orientation(triRef, i1, i2, i0); + if (ori1 > 0) { + ori2 = orientation(triRef, i2, i3, i0); + if (ori2 > 0) return -2; + } + return 2; + } + else if (ori < 0) { + ori1 = orientation(triRef, i2, i1, i0); + if (ori1 > 0) { + ori2 = orientation(triRef, i3, i2, i0); + if (ori2 > 0) return 2; + } + return -2; + } + else { + pq = new Point2f(); + Basic.vectorSub2D(triRef.points[i1], triRef.points[i2], pq); + pr = new Point2f(); + Basic.vectorSub2D(triRef.points[i3], triRef.points[i2], pr); + dot = Basic.dotProduct2D(pq, pr); + if (dot < 0.0) { + ori = orientation(triRef, i2, i1, i0); + if (ori > 0) return 2; + else return -2; + } + else { + return recSpikeAngle(triRef, i1, i2, i3, ind1, ind3); + } + } + } + } + + + /** + * computes the signed angle between p, p1 and p, p2. + * + * warning: this function does not handle a 180-degree angle correctly! + * (this is no issue in our application, as we will always compute + * the angle centered at the mid-point of a valid diagonal.) + */ + static double angle(Triangulator triRef, Point2f p, Point2f p1, Point2f p2) { + int sign; + double angle1, angle2, angle; + Point2f v1, v2; + + sign = Basic.signEps(Basic.det2D(p2, p, p1), triRef.epsilon); + + if (sign == 0) return 0.0; + + v1 = new Point2f(); + v2 = new Point2f(); + Basic.vectorSub2D(p1, p, v1); + Basic.vectorSub2D(p2, p, v2); + + angle1 = Math.atan2(v1.y, v1.x); + angle2 = Math.atan2(v2.y, v2.x); + + if (angle1 < 0.0) angle1 += 2.0*Math.PI; + if (angle2 < 0.0) angle2 += 2.0*Math.PI; + + angle = angle1 - angle2; + if (angle > Math.PI) angle = 2.0*Math.PI - angle; + else if (angle < -Math.PI) angle = 2.0*Math.PI + angle; + + if (sign == 1) { + if (angle < 0.0) return -angle; + else return angle; + } + else { + if (angle > 0.0) return -angle; + else return angle; + } + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Orientation.java b/src/classes/share/com/sun/j3d/utils/geometry/Orientation.java new file mode 100644 index 0000000..e790bdd --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Orientation.java @@ -0,0 +1,173 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + +class Orientation { + + /** + * determine the outer polygon and the orientation of the polygons; the + * default orientation is CCW for the outer-most polygon, and CW for the + * inner polygons. the polygonal loops are referenced by loops[i1,..,i2-1]. + */ + static void adjustOrientation(Triangulator triRef, int i1, int i2) { + + double area; + int i, outer; + int ind; + + if(i1 >= i2) + System.out.println("Orientation:adjustOrientation Problem i1>=i2 !!!"); + + if (triRef.numLoops >= triRef.maxNumPolyArea) { + // System.out.println("Orientation:adjustOrientation Expanding polyArea array ."); + triRef.maxNumPolyArea = triRef.numLoops; + double old[] = triRef.polyArea; + triRef.polyArea = new double[triRef.maxNumPolyArea]; + if(old != null) + System.arraycopy(old, 0, triRef.polyArea, 0, old.length); + } + + // for each contour, compute its signed area, i.e., its orientation. the + // contour with largest area is assumed to be the outer-most contour. + for (i = i1; i < i2; ++i) { + ind = triRef.loops[i]; + triRef.polyArea[i] = polygonArea(triRef, ind); + } + + // determine the outer-most contour + area = Math.abs(triRef.polyArea[i1]); + outer = i1; + for (i = i1 + 1; i < i2; ++i) { + if (area < Math.abs(triRef.polyArea[i])) { + area = Math.abs(triRef.polyArea[i]); + outer = i; + } + } + + // default: the outer contour is referenced by loops[i1] + if (outer != i1) { + ind = triRef.loops[i1]; + triRef.loops[i1] = triRef.loops[outer]; + triRef.loops[outer] = ind; + + area = triRef.polyArea[i1]; + triRef.polyArea[i1] = triRef.polyArea[outer]; + triRef.polyArea[outer] = area; + } + + // adjust the orientation + if (triRef.polyArea[i1] < 0.0) triRef.swapLinks(triRef.loops[i1]); + for (i = i1 + 1; i < i2; ++i) { + if (triRef.polyArea[i] > 0.0) triRef.swapLinks(triRef.loops[i]); + } + } + + /** + * This function computes twice the signed area of a simple closed polygon. + */ + static double polygonArea(Triangulator triRef, int ind) { + int hook = 0; + int ind1, ind2; + int i1, i2; + double area = 0.0, area1 = 0; + + ind1 = ind; + i1 = triRef.fetchData(ind1); + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + area = Numerics.stableDet2D(triRef, hook, i1, i2); + + ind1 = ind2; + i1 = i2; + while (ind1 != ind) { + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + area1 = Numerics.stableDet2D(triRef, hook, i1, i2); + area += area1; + ind1 = ind2; + i1 = i2; + } + + return area; + } + + + /** + * Determine the orientation of the polygon. The default orientation is CCW. + */ + static void determineOrientation(Triangulator triRef, int ind) { + double area; + + // compute the polygon's signed area, i.e., its orientation. + area = polygonArea(triRef, ind); + + // adjust the orientation (i.e., make it CCW) + if (area < 0.0) { + triRef.swapLinks(ind); + triRef.ccwLoop = false; + } + + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/PntNode.java b/src/classes/share/com/sun/j3d/utils/geometry/PntNode.java new file mode 100644 index 0000000..1f246ad --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/PntNode.java @@ -0,0 +1,70 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +// Placeholder list +class PntNode extends Object { + int pnt; + int next; + + PntNode() { + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Primitive.java b/src/classes/share/com/sun/j3d/utils/geometry/Primitive.java new file mode 100644 index 0000000..2c34b88 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Primitive.java @@ -0,0 +1,262 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.math.*; + +/** + * Base class for all Java 3D primitives. By default all primitives + * with the same parameters share their geometry (e.g., you can have 50 + * shperes in your scene, but the geometry is stored only once). A + * change to one primitive will effect all shared nodes. Another + * implication of this implementation is that the capabilities of the + * geometry are shared, and once one of the shared nodes is live, the + * capabilities cannot be set. Use the GEOMETRY_NOT_SHARED flag if + * you do not wish to share geometry among primitives with the same + * parameters. + */ + +public abstract class Primitive extends Group { + /** + * Specifies that normals are generated along with the positions. + */ + public static final int GENERATE_NORMALS = 0x01; + + /** + * Specifies that texture coordinates are generated along with the + * positions. + */ + public static final int GENERATE_TEXTURE_COORDS = 0x02; + + /** + * Specifies that normals are to be flipped along the surface. + */ + public static final int GENERATE_NORMALS_INWARD = 0x04; + + /** + * Specifies that the geometry being created will not be shared by + * another scene graph node. By default all primitives created with + * the same parameters share their geometry (e.g., you can have 50 + * spheres in your scene, but the geometry is stored only once). A + * change to one primitive will effect all shared nodes. You + * specify this flag if you do not wish to share any geometry among + * primitives of the same parameters. */ + public static final int GEOMETRY_NOT_SHARED = 0x10; + + /** + * Specifies that the ALLOW_INTERSECT + * capability bit should be set on the generated geometry. + * This allows the object + * to be picked using Geometry based picking. + */ + public static final int ENABLE_GEOMETRY_PICKING = 0x20; + + /** + * Specifies that the ALLOW_APPEARANCE_READ and + * ALLOW_APPEARANCE_WRITE bits are to be set on the generated + * geometry's Shape3D nodes. + */ + public static final int ENABLE_APPEARANCE_MODIFY = 0x40; + + static final int SPHERE = 0x01; + static final int CYLINDER = 0x02; + static final int CONE = 0x04; + static final int BOX = 0x08; + + // used for cached geometries of Cone and Cylinder + static final int TOP_DISK = 0x10; + static final int BOTTOM_DISK = 0x20; + static final int CONE_DIVISIONS = 0x40; + + int numTris = 0; + int numVerts = 0; + + /** + * Primitive flags. + */ + int flags; + + + /** + * Constructs a default primitive. + */ + public Primitive() + { + flags = 0; + setCapability(ENABLE_PICK_REPORTING); + setCapability(ALLOW_CHILDREN_READ); + } + + /** + * Returns the total number of triangles in this primitive. + * @return the total number of triangles in this primitive + */ + public int getNumTriangles() { + return numTris; + } + + /** + * @deprecated The number of triangles is an immutable attribute. + */ + public void setNumTriangles(int num) { + System.err.println("Warning: setNumTriangles has no effect"); + } + + /** + * Returns the total number of vertices in this primitive. + * @return the total number of vertices in this primitive + */ + public int getNumVertices() { + return numVerts; + } + + /** + * @deprecated The number of vertices is an immutable attribute. + */ + public void setNumVertices(int num) { + System.err.println("Warning: setNumVertices has no effect"); + } + + /** Returns the flags of primitive (generate normal, textures, caching, etc). + */ + public int getPrimitiveFlags() + { + return flags; + } + + /** + * @deprecated The primitive flags must be set at construction time + * via one of the subclass constructors. + */ + public void setPrimitiveFlags(int fl) { + System.err.println("Warning: setPrimitiveFlags has no effect"); + } + + /** Obtains a shape node of a subpart of the primitive. + * @param partid identifier for a given subpart of the primitive. + */ + public abstract Shape3D getShape(int partid); + + /** Gets the appearance of the primitive (defaults to first subpart). + */ + public Appearance getAppearance(){ + return getShape(0).getAppearance(); + } + + /** + * Gets the appearance of the specified part of the primitive. + * + * @param partId identifier for a given subpart of the primitive + * + * @return The appearance object associated with the partID. If an + * invalid partId is passed in, null is returned. + * + * @since Java 3D 1.2.1 + */ + public abstract Appearance getAppearance(int partId); + + /** Sets the appearance of a subpart given a partid. + */ + + public void setAppearance(int partid, Appearance ap) + { + getShape(partid).setAppearance(ap); + } + + /** Sets the main appearance of the primitive (all subparts) to + * same appearance. + */ + public abstract void setAppearance(Appearance ap); + + + /** Sets the main appearance of the primitive (all subparts) to + * a default white appearance. + */ + public void setAppearance(){ + + Color3f aColor = new Color3f(0.1f, 0.1f, 0.1f); + Color3f eColor = new Color3f(0.0f, 0.0f, 0.0f); + Color3f dColor = new Color3f(0.6f, 0.6f, 0.6f); + Color3f sColor = new Color3f(1.0f, 1.0f, 1.0f); + + Material m = new Material(aColor, eColor, dColor, sColor, 100.0f); + Appearance a = new Appearance(); + m.setLightingEnable(true); + a.setMaterial(m); + setAppearance(a); + } + + static Hashtable geomCache = new Hashtable(); + + String strfloat(float x) + { + return (new Float(x)).toString(); + } + + protected void cacheGeometry(int kind, float a, float b, + float c, int d, int e, int flags, + GeomBuffer geo) + { + String key = new String(kind+strfloat(a)+strfloat(b)+ + strfloat(c)+d+e+flags); + geomCache.put(key, geo); + } + + protected GeomBuffer getCachedGeometry(int kind, float a, float b, float c, + int d, int e, int flags) + { + String key = new String(kind+strfloat(a)+strfloat(b)+ + strfloat(c)+d+e+flags); + Object cache = geomCache.get(key); + + return((GeomBuffer) cache); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Project.java b/src/classes/share/com/sun/j3d/utils/geometry/Project.java new file mode 100644 index 0000000..e5489e4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Project.java @@ -0,0 +1,254 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import javax.vecmath.*; +import java.io.*; +import java.util.*; + +class Project { + + /** + * This function projects the vertices of the polygons referenced by + * loops[i1,..,i2-1] to an approximating plane. + */ + static void projectFace(Triangulator triRef, int loopMin, int loopMax) { + Vector3f normal, nr; + int i, j; + double d; + + normal = new Vector3f(); + nr = new Vector3f(); + + // determine the normal of the plane onto which the points get projected + determineNormal(triRef, triRef.loops[loopMin], normal); + j = loopMin + 1; + if (j < loopMax) { + for (i = j; i < loopMax; ++i) { + determineNormal(triRef, triRef.loops[i], nr); + if (Basic.dotProduct(normal, nr) < 0.0) { + Basic.invertVector(nr); + } + Basic.vectorAdd(normal, nr, normal); + } + d = Basic.lengthL2(normal); + if (Numerics.gt(d, Triangulator.ZERO)) { + Basic.divScalar(d, normal); + } + else { + // System.out.println("*** ProjectFace: zero-length normal vector!? ***\n"); + normal.x = normal.y = 0.0f; + normal.z = 1.0f; + } + } + + // project the points onto this plane. the projected points are stored in + // the array `points[0,..,numPoints]' + + // System.out.println("loopMin " + loopMin + " loopMax " + loopMax); + projectPoints(triRef, loopMin, loopMax, normal); + + } + + + /** + * This function computes the average of all normals defined by triples of + * successive vertices of the polygon. we'll see whether this is a good + * heuristic for finding a suitable plane normal... + */ + static void determineNormal(Triangulator triRef, int ind, Vector3f normal) { + Vector3f nr, pq, pr; + int ind0, ind1, ind2; + int i0, i1, i2; + double d; + + ind1 = ind; + i1 = triRef.fetchData(ind1); + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + pq = new Vector3f(); + Basic.vectorSub((Tuple3f) triRef.vertices[i0], (Tuple3f) triRef.vertices[i1], (Vector3f) pq); + pr = new Vector3f(); + Basic.vectorSub((Tuple3f) triRef.vertices[i2], (Tuple3f) triRef.vertices[i1], (Vector3f) pr); + nr = new Vector3f(); + Basic.vectorProduct(pq, pr, nr); + d = Basic.lengthL2(nr); + if (Numerics.gt(d, Triangulator.ZERO)) { + Basic.divScalar(d, nr); + normal.set(nr); + } + else { + normal.x = normal.y = normal.z = 0.0f; + } + + pq.set(pr); + ind1 = ind2; + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + while (ind1 != ind) { + Basic.vectorSub((Tuple3f) triRef.vertices[i2], (Tuple3f) triRef.vertices[i1], pr); + Basic.vectorProduct(pq, pr, nr); + d = Basic.lengthL2(nr); + if (Numerics.gt(d, Triangulator.ZERO)) { + Basic.divScalar(d, nr); + if (Basic.dotProduct(normal, nr) < 0.0) { + Basic.invertVector(nr); + } + Basic.vectorAdd(normal, nr, normal); + } + pq.set(pr); + ind1 = ind2; + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + } + + d = Basic.lengthL2(normal); + if (Numerics.gt(d, Triangulator.ZERO)) { + Basic.divScalar(d, normal); + } + else { + //System.out.println("*** DetermineNormal: zero-length normal vector!? ***\n"); + normal.x = normal.y = 0.0f; normal.z = 1.0f; + + } + } + + + /** + * This function maps the vertices of the polygon referenced by `ind' to the + * plane n3.x * x + n3.y * y + n3.z * z = 0. every mapped vertex (x,y,z) + * is then expressed in terms of (x',y',z'), where z'=0. this is + * achieved by transforming the original vertices into a coordinate system + * whose z-axis coincides with n3, and whose two other coordinate axes n1 + * and n2 are orthonormal on n3. note that n3 is supposed to be of unit + * length! + */ + static void projectPoints(Triangulator triRef, int i1, int i2, Vector3f n3) { + Matrix4f matrix = new Matrix4f(); + Point3f vtx = new Point3f(); + Vector3f n1, n2; + double d; + int ind, ind1; + int i, j1; + + + n1 = new Vector3f(); + n2 = new Vector3f(); + + // choose n1 and n2 appropriately + if ((Math.abs(n3.x) > 0.1) || (Math.abs(n3.y) > 0.1)) { + n1.x = -n3.y; + n1.y = n3.x; + n1.z = 0.0f; + } + else { + n1.x = n3.z; + n1.z = -n3.x; + n1.y = 0.0f; + } + d = Basic.lengthL2(n1); + Basic.divScalar(d, n1); + Basic.vectorProduct(n1, n3, n2); + d = Basic.lengthL2(n2); + Basic.divScalar(d, n2); + + // initialize the transformation matrix + matrix.m00 = n1.x; + matrix.m01 = n1.y; + matrix.m02 = n1.z; + matrix.m03 = 0.0f; // translation of the coordinate system + matrix.m10 = n2.x; + matrix.m11 = n2.y; + matrix.m12 = n2.z; + matrix.m13 = 0.0f; // translation of the coordinate system + matrix.m20 = n3.x; + matrix.m21 = n3.y; + matrix.m22 = n3.z; + matrix.m23 = 0.0f; // translation of the coordinate system + matrix.m30 = 0.0f; + matrix.m31 = 0.0f; + matrix.m32 = 0.0f; + matrix.m33 = 1.0f; + + // transform the vertices and store the transformed vertices in the array + // `points' + triRef.initPnts(20); + for (i = i1; i < i2; ++i) { + ind = triRef.loops[i]; + ind1 = ind; + j1 = triRef.fetchData(ind1); + matrix.transform((Point3f)triRef.vertices[j1], vtx); + j1 = triRef.storePoint(vtx.x, vtx.y); + triRef.updateIndex(ind1, j1); + ind1 = triRef.fetchNextData(ind1); + j1 = triRef.fetchData(ind1); + while (ind1 != ind) { + matrix.transform(triRef.vertices[j1], vtx); + j1 = triRef.storePoint(vtx.x, vtx.y); + triRef.updateIndex(ind1, j1); + ind1 = triRef.fetchNextData(ind1); + j1 = triRef.fetchData(ind1); + } + } + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Quadrics.java b/src/classes/share/com/sun/j3d/utils/geometry/Quadrics.java new file mode 100644 index 0000000..4af75d9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Quadrics.java @@ -0,0 +1,722 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.math.*; + +class Quadrics extends Object { + + Quadrics (){ } + + // do not use this to make a cone. It uses too many triangles. The top of + // cone can be a triangle fan array + + GeomBuffer cylinder(double lowr, double highr, + double height, int xdivisions, int ydivisions, + boolean outside) + { + double r, sign; + + /* + * Cylinder is created by extruding the unit circle along the Z+ + * axis. The number of layers is controlled by ydivisions (which is + * actually zdivisions). For each consecutive sample points along the + * unit circle, we also sample along the height of the cylinder. Quads + * are created along the Z direction. When the unit circle at the base is + * completed, we also obtain the layers of quads along the height of the + * cylinder. + * + * Texture coordinates are created in a straight forward cylindrical + * mapping of the texture map. The texture is wrapped from the back of + * the cylinder. + */ + + if (outside) + sign = 1.0; + else + sign = -1.0; + + //Compute the deltas + double dtheta = 2.0*Math.PI / xdivisions; + double dr = (highr - lowr) / ydivisions; + double dz = height / ydivisions; + double znormal = (lowr - highr) / height; + double du = 1.0 / xdivisions; + double dv = 1.0 / ydivisions; + + //Initialize geometry buffer. + GeomBuffer gbuf = new GeomBuffer(xdivisions*ydivisions*4); + + double s = 0.0, t = 0.0; + for (int i=0;i=0;i--) { + theta = i * dtheta; + // add 90 degrees to theta so lines up with the body + sinTheta = Math.sin(theta + Math.PI/2.0); + cosTheta = Math.cos(theta + Math.PI/2.0); + gbuf.normal3d( 0.0, 0.0, 1.0*sign ); + // if not outside, texture coordinates need to be upside down + // to conform with VRML spec +// gbuf.texCoord2d(0.5-cosTheta*0.5, 0.5+sinTheta*0.5); + gbuf.texCoord2d(0.5+cosTheta*0.5, 0.5-sinTheta*0.5); + gbuf.vertex3d( r*cosTheta, r*sinTheta, 0.0 ); + } + } + + gbuf.end(); + return gbuf; + } + + // use this to make the top of the cone. It uses a triangle fan so there + // aren't extra triangles + + GeomBuffer coneTop(double coneRadius, double coneHeight, int xdivisions, + int ydivisions, boolean outside) { + + double sign; + double radius = coneRadius/(double)ydivisions; + + double bottom = coneHeight/2.0 - coneHeight/(double)ydivisions; + double top = coneHeight/2.0; + + if (outside) sign = 1.0; + else sign = -1.0; + + // compute the deltas + double dtheta = 2.0 * Math.PI / (double)xdivisions; + double znormal = radius/(top-bottom); + double du = 1.0/(double)xdivisions; + + // initialize geometry buffer + GeomBuffer gbuf = new GeomBuffer(xdivisions + 2); + gbuf.begin(GeomBuffer.TRIANGLE_FAN); + // add the tip, which is the center of the fan + gbuf.normal3d(0.0, 0.0, znormal*sign); + gbuf.texCoord2d(.5, 1); + gbuf.vertex3d(0.0, 0.0, top); + + // go around the circle and add the rest of the fan + double s = 0.0; + double t = 1.0 - 1.0/(double)ydivisions; + double px, py; + if (outside) { + for (int i = 0; i <= xdivisions; i++) { + // we have to start at the back of the sphere, so add 90 degrees + px = Math.cos(i*dtheta + Math.PI/2.0); + py = Math.sin(i*dtheta + Math.PI/2.0); + gbuf.normal3d(px*sign, py*sign, znormal*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py*radius, bottom); + s += du; + } + } + else { + for (int i = xdivisions; i >= 0; i--) { + px = Math.cos(i*dtheta + Math.PI/2.0); + py = Math.sin(i*dtheta + Math.PI/2.0); + gbuf.normal3d(px*sign, py*sign, znormal*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py*radius, bottom); + s += du; + } + } + gbuf.end(); + return gbuf; + } + + // use this to make the body of the cone. similar to cylinder, only allows + // you to leave off the top. + + GeomBuffer coneBody(double coneRadius, double height, + int xdivisions, int ydivisions, + boolean outside) + { + double r, sign; + + double bottom = -height/2.0; + double topRadius = coneRadius/(double)ydivisions; + + /* + * The cone body is created by extruding the unit circle along the Z+ + * axis. The number of layers is controlled by ydivisions (which is + * actually zdivisions). For each consecutive sample points along the + * unit circle, we also sample along the height of the cone. Quads + * are created along the Z direction. When the unit circle at the base is + * completed, we also obtain the layers of quads along the height of the + * cylinder. + * + * Texture coordinates are created in a straight forward cylindrical + * mapping of the texture map. The texture is wrapped from the back of + * the cylinder. + */ + + if (outside) + sign = 1.0; + else + sign = -1.0; + + //Compute the deltas + double dtheta = 2.0*Math.PI / xdivisions; + double dr = -coneRadius / ydivisions; + double dz = height / ydivisions; + double znormal = coneRadius / height; + double du = 1.0 / xdivisions; + double dv = 1.0 / ydivisions; + + //Initialize geometry buffer. + GeomBuffer gbuf = new GeomBuffer(xdivisions*ydivisions*4); + + double s = 0.0, t = 0.0; + for (int i=0;i= 0; i--) { + theta = i * dtheta; + // add 90 degrees to theta so lines up with the body + sinTheta = Math.sin(theta - Math.PI/2.0); + cosTheta = Math.cos(theta - Math.PI/2.0); + gbuf.normal3d(0.0, 1.0*sign, 0.0); + gbuf.texCoord2d(0.5+cosTheta*0.5, 0.5-sinTheta*0.5); + gbuf.vertex3d(cosTheta*r, y, sinTheta*r); + } + } + + gbuf.end(); + return gbuf; + } + + + // new cylinder to remove transforms in the cylinder code and to optimize + // by using triangle strip + GeomBuffer cylinder(double height, double radius, + int xdiv, int ydiv, boolean outside) { + + double sign; + + if (outside) sign = 1.0; + else sign = -1.0; + + // compute the deltas + double dtheta = 2.0*Math.PI / (double)xdiv; + double dy = height / (double)ydiv; + double du = 1.0/(double)xdiv; + double dv = 1.0/(double)ydiv; + + GeomBuffer gbuf = new GeomBuffer(ydiv*2*(xdiv+1)); + + double s = 0.0, t = 0.0; + double px, pz, qx, qz; + double py = -height/2.0; + double qy; + +// int c; +// if (outside) c = ydiv*2*(xdiv+1) - 1; +// else c = 0; + + gbuf.begin(GeomBuffer.QUAD_STRIP); + + for (int i = 0; i < ydiv; i++) { + qy = py+dy; + if (outside) { + px = Math.cos(xdiv*dtheta - Math.PI/2.0); + pz = Math.sin(xdiv*dtheta - Math.PI/2.0); + qx = Math.cos((xdiv-1)*dtheta - Math.PI/2.0); + qz = Math.sin((xdiv-1)*dtheta - Math.PI/2.0); + + // vert 2 + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*radius, qy, pz*radius); + + // vert 1 + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py, pz*radius); + + // vert 4 + gbuf.normal3d(qx*sign, 0.0, qz*sign); + gbuf.texCoord2d(s+du, t+dv); + gbuf.vertex3d(qx*radius, qy, qz*radius); + + // vert 3 + gbuf.normal3d(qx*sign, 0.0, qz*sign); + gbuf.texCoord2d(s+du, t); + gbuf.vertex3d(qx*radius, py, qz*radius); + + s += (du*2.0); + + for (int j = xdiv-2; j >=0; j--) { + px = Math.cos(j*dtheta - Math.PI/2.0); + pz = Math.sin(j*dtheta - Math.PI/2.0); + + // vert 6 + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*radius, qy, pz*radius); + + // vert 5 + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py, pz*radius); + + s += du; + } + + } + else { +// c = 0; + px = Math.cos(-Math.PI/2.0); + pz = Math.sin(-Math.PI/2.0); + qx = Math.cos(dtheta - Math.PI/2.0); + qz = Math.sin(dtheta - Math.PI/2.0); + + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*radius, qy, pz*radius); + + // vert 1 + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py, pz*radius); + + gbuf.normal3d(qx*sign, 0.0, qz*sign); + gbuf.texCoord2d(s+du, t+dv); + gbuf.vertex3d(qx*radius, qy, qz*radius); + + gbuf.normal3d(qx*sign, 0.0, qz*sign); + gbuf.texCoord2d(s+du, t); + gbuf.vertex3d(qx*radius, py, qz*radius); + + s += (du*2.0); + + for (int j = 2; j <= xdiv; j++) { + px = Math.cos(j*dtheta - Math.PI/2.0); + pz = Math.sin(j*dtheta - Math.PI/2.0); + + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*radius, qy, pz*radius); + + gbuf.normal3d(px*sign, 0.0, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, py, pz*radius); + + s += du; + } + + } + s = 0.0; + t += dv; + py += dy; + } + + gbuf.end(); + + return gbuf; + } + + // new coneBody method to remove transform in the Cone primitive + // and to optimize by using triangle strip + GeomBuffer coneBody(double bottom, double top, double bottomR, double topR, + int xdiv, int ydiv, double dv, boolean outside) { + + double r, sign; + + if (outside) sign = 1.0; + else sign = -1.0; + + // compute the deltas + double dtheta = 2.0*Math.PI/(double)xdiv; + double dr = (topR-bottomR)/(double)ydiv; + double height = top-bottom; + double dy = height/(double)ydiv; + double ynormal = (bottomR-topR)/height; + double du = 1.0/(double)xdiv; +// double dv = 1.0/(double)(ydiv+1); + + GeomBuffer gbuf = new GeomBuffer(ydiv*2*(xdiv+1)); + + double s = 0.0, t = 0.0; + double px, pz, qx, qz; + double py = bottom; + double qy; + r = bottomR; + + gbuf.begin(GeomBuffer.QUAD_STRIP); + + for (int i = 0; i < ydiv; i++) { + qy = py+dy; + if (outside) { + px = Math.cos(xdiv*dtheta - Math.PI/2.0); + pz = Math.sin(xdiv*dtheta - Math.PI/2.0); + qx = Math.cos((xdiv-1)*dtheta - Math.PI/2.0); + qz = Math.sin((xdiv-1)*dtheta - Math.PI/2.0); + + // vert2 + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*(r+dr), qy, pz*(r+dr)); + + // vert1 + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*r, py, pz*r); + + // vert4 + gbuf.normal3d(qx*sign, ynormal*sign, qz*sign); + gbuf.texCoord2d(s+du, t+dv); + gbuf.vertex3d(qx*(r+dr), qy, qz*(r+dr)); + + // vert3 + gbuf.normal3d(qx*sign, ynormal*sign, qz*sign); + gbuf.texCoord2d(s+du, t); + gbuf.vertex3d(qx*r, py, qz*r); + + s += (du*2.0); + + for (int j = xdiv-2; j >= 0; j--) { + px = Math.cos(j*dtheta - Math.PI/2.0); + pz = Math.sin(j*dtheta - Math.PI/2.0); + + // vert 6 + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*(r+dr), qy, pz*(r+dr)); + + // vert 5 + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*r, py, pz*r); + + s += du; + } + } + else { + px = Math.cos(-Math.PI/2.0); + pz = Math.sin(-Math.PI/2.0); + qx = Math.cos(dtheta - Math.PI/2.0); + qz = Math.sin(dtheta - Math.PI/2.0); + + // vert1 + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*(r+dr), qy, pz*(r+dr)); + + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*r, py, pz*r); + + gbuf.normal3d(qx*sign, ynormal*sign, qz*sign); + gbuf.texCoord2d(s+du, t+dv); + gbuf.vertex3d(qx*(r+dr), qy, qz*(r+dr)); + + gbuf.normal3d(qx*sign, ynormal*sign, qz*sign); + gbuf.texCoord2d(s+du, t); + gbuf.vertex3d(qx*r, py, qz*r); + + s += (du*2.0); + + for (int j = 2; j <= xdiv; j++) { + px = Math.cos(j*dtheta - Math.PI/2.0); + pz = Math.sin(j*dtheta - Math.PI/2.0); + + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t+dv); + gbuf.vertex3d(px*(r+dr), qy, pz*(r+dr)); + + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*r, py, pz*r); + + s += du; + } + } + s = 0.0; + t += dv; + py += dy; + r += dr; + } + gbuf.end(); + + return gbuf; + } + + // new coneTop method to remove transforms in the cone code + GeomBuffer coneTop(double bottom, double radius, double height, + int xdiv,double t, boolean outside) { + + double sign; + + if (outside) sign = 1.0; + else sign = -1.0; + + // compute the deltas + double dtheta = 2.0*Math.PI/(double)xdiv; + double ynormal = radius/height; + double du = 1.0/(double)xdiv; + double top = bottom + height; + + // initialize the geometry buffer + GeomBuffer gbuf = new GeomBuffer(xdiv + 2); + gbuf.begin(GeomBuffer.TRIANGLE_FAN); + + // add the tip, which is the center of the fan + gbuf.normal3d(0.0, ynormal*sign, 0.0); + gbuf.texCoord2d(.5, 1.0); + gbuf.vertex3d(0.0, top, 0.0); + + // go around the circle and add the rest of the fan + double s = 0.0; + double px, pz; + if (outside) { +// for (int i = 0; i <= xdiv; i++) { + for (int i = xdiv; i >= 0; i--) { + px = Math.cos(i*dtheta - Math.PI/2.0); + pz = Math.sin(i*dtheta - Math.PI/2.0); + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, bottom, pz*radius); + + s += du; + } + } + else { +// for (int i = xdiv; i >= 0; i--) { + for (int i = 0; i <= xdiv; i++) { + px = Math.cos(i*dtheta - Math.PI/2.0); + pz = Math.sin(i*dtheta - Math.PI/2.0); + gbuf.normal3d(px*sign, ynormal*sign, pz*sign); + gbuf.texCoord2d(s, t); + gbuf.vertex3d(px*radius, bottom, pz*radius); + s += du; + } + } + gbuf.end(); + return gbuf; + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Simple.java b/src/classes/share/com/sun/j3d/utils/geometry/Simple.java new file mode 100644 index 0000000..c4abd56 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Simple.java @@ -0,0 +1,225 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.io.*; +import java.util.*; +import javax.vecmath.*; + + +class Simple { + + /** + * Handle a triangle or a quadrangle in a simple and efficient way. if the + * face is more complex than false is returned. + * + * warning: the correctness of this function depends upon the fact that + * `CleanPolyhedralFace' has not yet been executed; i.e., the + * vertex indices have not been changed since the execution of + * `CleanPolyhedron'! (otherwise, we would have to get the original + * indices via calls to `GetOriginal'...) + */ + static boolean simpleFace(Triangulator triRef, int ind1) { + int ind0, ind2, ind3, ind4; + int i1, i2, i3, i0, i4; + + Point3f pq, pr, nr; + + double x, y, z; + int ori2, ori4; + + ind0 = triRef.fetchPrevData(ind1); + i0 = triRef.fetchData(ind0); + + if (ind0 == ind1) { + // this polygon has only one vertex! nothing to triangulate... + System.out.println("***** polygon with only one vertex?! *****\n"); + return true; + } + + ind2 = triRef.fetchNextData(ind1); + i2 = triRef.fetchData(ind2); + if (ind0 == ind2) { + // this polygon has only two vertices! nothing to triangulate... + System.out.println("***** polygon with only two vertices?! *****\n"); + return true; + } + + ind3 = triRef.fetchNextData(ind2); + i3 = triRef.fetchData(ind3); + if (ind0 == ind3) { + // this polygon is a triangle! let's triangulate it! + i1 = triRef.fetchData(ind1); + // triRef.storeTriangle(i1, i2, i3); + triRef.storeTriangle(ind1, ind2, ind3); + return true; + } + + ind4 = triRef.fetchNextData(ind3); + i4 = triRef.fetchData(ind4); + if (ind0 == ind4) { + // this polygon is a quadrangle! not too hard to triangulate it... + // we project the corners of the quadrangle onto one of the coordinate + // planes + triRef.initPnts(5); + i1 = triRef.fetchData(ind1); + + pq = new Point3f(); + pr = new Point3f(); + nr = new Point3f(); + /* + System.out.println("ind0 " + ind0 + ", ind1 " + ind1 + ", ind2 " + + ind2 + ", ind3 " + ind3 + ", ind4 " + ind4); + System.out.println("i0 " + i0 +", i1 " + i1 + ", i2 " + i2 + + ", i3 " + i3 + ", i4 " + i4); + + System.out.println("vert[i1] " + triRef.vertices[i1] + + "vert[i2] " + triRef.vertices[i2] + + "vert[i3] " + triRef.vertices[i3]); + */ + + Basic.vectorSub(triRef.vertices[i1], triRef.vertices[i2], pq); + Basic.vectorSub(triRef.vertices[i3], triRef.vertices[i2], pr); + Basic.vectorProduct(pq, pr, nr); + + // System.out.println("pq " + pq + " pr " + pr + " nr " + nr); + x = Math.abs(nr.x); + y = Math.abs(nr.y); + z = Math.abs(nr.z); + if ((z >= x) && (z >= y)) { + // System.out.println("((z >= x) && (z >= y))"); + triRef.points[1].x = triRef.vertices[i1].x; + triRef.points[1].y = triRef.vertices[i1].y; + triRef.points[2].x = triRef.vertices[i2].x; + triRef.points[2].y = triRef.vertices[i2].y; + triRef.points[3].x = triRef.vertices[i3].x; + triRef.points[3].y = triRef.vertices[i3].y; + triRef.points[4].x = triRef.vertices[i4].x; + triRef.points[4].y = triRef.vertices[i4].y; + } + else if ((x >= y) && (x >= z)) { + // System.out.println("((x >= y) && (x >= z))"); + triRef.points[1].x = triRef.vertices[i1].z; + triRef.points[1].y = triRef.vertices[i1].y; + triRef.points[2].x = triRef.vertices[i2].z; + triRef.points[2].y = triRef.vertices[i2].y; + triRef.points[3].x = triRef.vertices[i3].z; + triRef.points[3].y = triRef.vertices[i3].y; + triRef.points[4].x = triRef.vertices[i4].z; + triRef.points[4].y = triRef.vertices[i4].y; + } + else { + triRef.points[1].x = triRef.vertices[i1].x; + triRef.points[1].y = triRef.vertices[i1].z; + triRef.points[2].x = triRef.vertices[i2].x; + triRef.points[2].y = triRef.vertices[i2].z; + triRef.points[3].x = triRef.vertices[i3].x; + triRef.points[3].y = triRef.vertices[i3].z; + triRef.points[4].x = triRef.vertices[i4].x; + triRef.points[4].y = triRef.vertices[i4].z; + } + triRef.numPoints = 5; + + // find a valid diagonal + ori2 = Numerics.orientation(triRef, 1, 2, 3); + ori4 = Numerics.orientation(triRef, 1, 3, 4); + + /* + for(int i=0; i<5; i++) + System.out.println("point " + i + ", " + triRef.points[i]); + System.out.println("ori2 : " + ori2 + " ori4 : " + ori4); + */ + + if (((ori2 > 0) && (ori4 > 0)) || + ((ori2 < 0) && (ori4 < 0))) { + + // i1, i3 is a valid diagonal; + // + // encode as a 2-triangle strip: the triangles are (2, 3, 1) + // and (1, 3, 4). + + // triRef.storeTriangle(i1, i2, i3); + // triRef.storeTriangle(i1, i3, i4); + triRef.storeTriangle(ind1, ind2, ind3); + triRef.storeTriangle(ind1, ind3, ind4); + } + else { + // i2, i4 has to be a valid diagonal. (if this is no valid + // diagonal then the corners of the quad form a figure of eight; + // shall we apply any heuristics in order to guess which diagonal + // is more likely to be the better choice? alternatively, we could + // return false and subject it to the standard triangulation + // algorithm. well, let's see how this brute-force solution works.) + + // encode as a 2-triangle strip: the triangles are (1, 2, 4) + // and (4, 2, 3). + + // triRef.storeTriangle(i2, i3, i4); + // triRef.storeTriangle(i2, i4, i1); + triRef.storeTriangle(ind2, ind3, ind4); + triRef.storeTriangle(ind2, ind4, ind1); + } + return true; + } + + return false; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Sphere.java b/src/classes/share/com/sun/j3d/utils/geometry/Sphere.java new file mode 100644 index 0000000..8d5442e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Sphere.java @@ -0,0 +1,502 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.*; +import java.io.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.math.*; + +/** + * Sphere is a geometry primitive created with a given radius and resolution. + * It is centered at the origin. + *

+ * When a texture is applied to a Sphere, it is mapped CCW from the back + * of the sphere. + *

+ * By default all primitives with the same parameters share their + * geometry (e.g., you can have 50 shperes in your scene, but the + * geometry is stored only once). A change to one primitive will + * effect all shared nodes. Another implication of this + * implementation is that the capabilities of the geometry are shared, + * and once one of the shared nodes is live, the capabilities cannot + * be set. Use the GEOMETRY_NOT_SHARED flag if you do not wish to + * share geometry among primitives with the same parameters. + */ + +public class Sphere extends Primitive { + + /** + * Sphere shape identifier, used by getShape. + * + * @see Sphere#getShape + */ + public static final int BODY = 0; + + static final int MID_REZ_DIV = 16; + float radius; + int divisions; + + /** + * Constructs a Sphere of a given radius. Normals are generated + * by default, texture coordinates are not. The resolution defaults to + * 15 divisions along sphere's axes. Appearance defaults to white. + * @param radius Radius + */ + public Sphere (float radius) { + this(radius, GENERATE_NORMALS, MID_REZ_DIV); + } + + /** + * Constructs a default Sphere of radius of 1.0. Normals are generated + * by default, texture coordinates are not. + * Resolution defaults to 15 divisions. Appearance defaults to white. + */ + public Sphere() { + this(1.0f, GENERATE_NORMALS, MID_REZ_DIV); + } + + /** + * Constructs a Sphere of a given radius and appearance. + * Normals are generated by default, texture coordinates are not. + * @param radius Radius + * @param ap Appearance + */ + + public Sphere (float radius, Appearance ap) { + this(radius, GENERATE_NORMALS, MID_REZ_DIV, ap); + } + + /** + * Constructs a Sphere of a given radius and appearance with + * additional parameters specified by the Primitive flags. + * @param radius Radius + * @param primflags + * @param ap appearance + */ + public Sphere(float radius, int primflags, Appearance ap) { + this(radius, primflags, MID_REZ_DIV, ap); + } + + /** + * Constructs a Sphere of a given radius and number of divisions + * with additional parameters specified by the Primitive flags. + * Appearance defaults to white. + * @param radius Radius + * @param divisions Divisions + * @param primflags Primflags + */ + public Sphere(float radius, int primflags, int divisions) { + this(radius, primflags, divisions, null); + } + + + /** + * Obtains Sphere's shape node that contains the geometry. + * This allows users to modify the appearance or geometry. + * @param partId The part to return (must be BODY for Spheres) + * @return The Shape3D object associated with the partId. If an + * invalid partId is passed in, null is returned. + */ + public Shape3D getShape(int partId) { + if (partId != BODY) return null; +// return (Shape3D)((Group)getChild(0)).getChild(BODY); + return (Shape3D)getChild(BODY); + } + + /** Obtains Sphere's shape node that contains the geometry. + */ + public Shape3D getShape() { +// return (Shape3D)((Group)getChild(0)).getChild(BODY); + return (Shape3D)getChild(BODY); + } + + /** Sets appearance of the Sphere. + */ + public void setAppearance(Appearance ap) { +// ((Shape3D)((Group)getChild(0)).getChild(BODY)).setAppearance(ap); + ((Shape3D)getChild(BODY)).setAppearance(ap); + } + + /** + * Gets the appearance of the specified part of the sphere. + * + * @param partId identifier for a given subpart of the sphere + * + * @return The appearance object associated with the partID. If an + * invalid partId is passed in, null is returned. + * + * @since Java 3D 1.2.1 + */ + public Appearance getAppearance(int partId) { + if (partId != BODY) return null; + return getShape(partId).getAppearance(); + } + + + /** + * Constructs a customized Sphere of a given radius, + * number of divisions, and appearance, with additional parameters + * specified by the Primitive flags. The resolution is defined in + * terms of number of subdivisions along the sphere's axes. More + * divisions lead to more finely tesselated objects. + *

+ * If the appearance is null, the sphere defaults to a white appearance. + */ + public Sphere(float radius, int primflags, int divisions, Appearance ap) { + super(); + + int sign; + int n, nstep; + + this.radius = radius; + this.divisions = divisions; + + /* + * The sphere algorithm evaluates spherical angles along regular + * units. For each spherical coordinate, (theta, rho), a (x,y,z) is + * evaluated (along with the normals and texture coordinates). + * + * The spherical angles theta varies from 0 to 2pi and rho from 0 + * to pi. Sample points depends on the number of divisions. + */ + + flags = primflags; + + //Depending on whether normal inward bit is set. + if ((flags & GENERATE_NORMALS_INWARD) != 0) { + sign = -1; + } else { + sign = 1; + } + + if (divisions < 4) { + nstep = 1; + n = 4; + } else { + int mod = divisions % 4; + if (mod == 0) { + n = divisions; + } else { + n = divisions + (4 - mod); + } + nstep = n/4; + } + + + GeomBuffer cache = getCachedGeometry(Primitive.SPHERE, + radius, 0.0f, 0.0f, + divisions, 0, primflags); + + Shape3D shape; + + if (cache != null) { + shape = new Shape3D(cache.getComputedGeometry()); + numVerts += cache.getNumVerts(); + numTris += cache.getNumTris(); + } else { + // buffer size = 8*(1 + 2E{i} + (nstep+1)) + // where E{i} = sum of i = 2 ... nstep + GeomBuffer gbuf = new GeomBuffer(8*nstep*(nstep+2)); + + for (int i=0; i < 4; i++) { + buildQuadrant(gbuf, i*Math.PI/2, (i+1)*Math.PI/2, sign, nstep, n, true); + buildQuadrant(gbuf, i*Math.PI/2, (i+1)*Math.PI/2, sign, nstep, n, false); + } + + shape = new Shape3D(gbuf.getGeom(flags)); + numVerts = gbuf.getNumVerts(); + numTris = gbuf.getNumTris(); + if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) { + cacheGeometry(Primitive.SPHERE, + radius, 0.0f, 0.0f, + divisions, 0, primflags, gbuf); + } + } + + if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) { + shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ); + shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE); + } + + if ((flags & ENABLE_GEOMETRY_PICKING) != 0) { + shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); + } + + this.addChild(shape); + + if (ap == null) { + setAppearance(); + } + else setAppearance(ap); + } + + /** + * Used to create a new instance of the node. This routine is called + * by cloneTree to duplicate the current node. + * cloneNode should be overridden by any user subclassed + * objects. All subclasses must have their cloneNode + * method consist of the following lines: + *

+     *     public Node cloneNode(boolean forceDuplicate) {
+     *         UserSubClass usc = new UserSubClass();
+     *         usc.duplicateNode(this, forceDuplicate);
+     *         return usc;
+     *     }
+     * 
+ * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Sphere s = new Sphere(radius, flags, divisions, getAppearance()); + s.duplicateNode(this, forceDuplicate); + + return s; + } + + /** + * Copies all node information from originalNode into + * the current node. This method is called from the + * cloneNode method which is, in turn, called by the + * cloneTree method. + *

+ * For any NodeComponent objects + * contained by the object being duplicated, each NodeComponent + * object's duplicateOnCloneTree value is used to determine + * whether the NodeComponent should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * forceDuplicate parameter in the cloneTree + * method to true. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to true, causes the + * duplicateOnCloneTree flag to be ignored. When + * false, the value of each node's + * duplicateOnCloneTree variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + super.duplicateNode(originalNode, forceDuplicate); + } + + /** + * Returns the radius of the sphere + * + * @since Java 3D 1.2.1 + */ + public float getRadius() { + return radius; + } + + /** + * Returns the number of divisions + * + * @since Java 3D 1.2.1 + */ + public int getDivisions() { + return divisions; + } + + void buildQuadrant(GeomBuffer gbuf, double startDelta, double endDelta, + int sign, int nstep, int n, boolean upperSphere) + { + + double ds, dt, theta, delta; + int i, j, index, i2; + double h, r, vx, vz; + Point3f pt; + Vector3f norm; + TexCoord2f texCoord; + double starth; + double t; + boolean leftToRight; + + + if (upperSphere) { + dt = Math.PI/(2*nstep); + theta = dt; + starth = 1; + leftToRight = (sign > 0); + } else { + dt = -Math.PI/(2*nstep); + theta = Math.PI + dt; + starth = -1; + leftToRight = (sign < 0); + } + + + for (i = 1; i <= nstep; i++) { + h = Math.cos(theta); + r = Math.sin(theta); + if (sign > 0) { + t = 1 - theta/Math.PI; + } else { + t = theta/Math.PI; + } + + i2 = i << 1; + // subdivision decreases towards the pole + ds = (endDelta - startDelta) / i; + + gbuf.begin(GeomBuffer.TRIANGLE_STRIP); + + if (leftToRight) { + // Build triangle strips from left to right + delta = startDelta; + + for (j=0; j < i; j++) { + vx = r*Math.cos(delta); + vz = r*Math.sin(delta); + + gbuf.normal3d( vx*sign, h*sign, vz*sign ); + gbuf.texCoord2d(0.75 - delta/(2*Math.PI), t); + gbuf.vertex3d( vx*radius, h*radius, vz*radius ); + if (i > 1) { + // get previous vertex from buffer + index = gbuf.currVertCnt - i2; + pt = gbuf.pts[index]; + norm = gbuf.normals[index]; + texCoord = gbuf.tcoords[index]; + // connect with correspondent vertices from previous row + gbuf.normal3d(norm.x, norm.y, norm.z); + gbuf.texCoord2d(texCoord.x, texCoord.y); + gbuf.vertex3d(pt.x, pt.y, pt.z); + } else { + gbuf.normal3d(0, sign*starth, 0); + if (sign > 0) { + gbuf.texCoord2d(0.75 - (startDelta + endDelta)/(4*Math.PI), + 1.0 - (theta - dt)/Math.PI); + } else { + gbuf.texCoord2d(0.75 - (startDelta + endDelta)/(4*Math.PI), + (theta - dt)/Math.PI); + } + gbuf.vertex3d( 0, starth*radius, 0); + + } + delta += ds; + } + + // Put the last vertex in that row, + // for numerical accuracy we don't use delta + // compute from above. + delta = endDelta; + vx = r*Math.cos(delta); + vz = r*Math.sin(delta); + gbuf.normal3d( vx*sign, h*sign, vz*sign ); + gbuf.texCoord2d(0.75 - delta/(2*Math.PI), t); + gbuf.vertex3d( vx*radius, h*radius, vz*radius); + } else { + delta = endDelta; + // Build triangle strips from right to left + for (j=i; j > 0; j--) { + vx = r*Math.cos(delta); + vz = r*Math.sin(delta); + + gbuf.normal3d( vx*sign, h*sign, vz*sign ); + // Convert texture coordinate back to one + // set in previous version + gbuf.texCoord2d(0.75 - delta/(2*Math.PI), t); + gbuf.vertex3d( vx*radius, h*radius, vz*radius ); + if (i > 1) { + // get previous vertex from buffer + index = gbuf.currVertCnt - i2; + pt = gbuf.pts[index]; + norm = gbuf.normals[index]; + texCoord = gbuf.tcoords[index]; + gbuf.normal3d(norm.x, norm.y, norm.z); + gbuf.texCoord2d(texCoord.x, texCoord.y); + gbuf.vertex3d(pt.x, pt.y, pt.z); + } else { + gbuf.normal3d(0, sign*starth, 0); + if (sign > 0) { + gbuf.texCoord2d(0.75 - (startDelta + endDelta)/(4*Math.PI), + 1.0 - (theta - dt)/Math.PI); + } else { + gbuf.texCoord2d(0.75 - (startDelta + endDelta)/(4*Math.PI), + (theta - dt)/Math.PI); + } + gbuf.vertex3d( 0, starth*radius, 0); + + } + delta -= ds; + } + + // Put the last vertex in that row, + // for numerical accuracy we don't use delta + // compute from above. + delta = startDelta; + vx = r*Math.cos(delta); + vz = r*Math.sin(delta); + gbuf.normal3d( vx*sign, h*sign, vz*sign ); + gbuf.texCoord2d(0.75 - delta/(2*Math.PI), t); + gbuf.vertex3d( vx*radius, h*radius, vz*radius ); + + } + + gbuf.end(); + + if (i < nstep) { + theta += dt; + } else { // take care of numerical imprecision + theta = Math.PI/2; + } + } + + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Stripifier.java b/src/classes/share/com/sun/j3d/utils/geometry/Stripifier.java new file mode 100644 index 0000000..48f5709 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Stripifier.java @@ -0,0 +1,2535 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import com.sun.j3d.utils.geometry.GeometryInfo; +import java.util.LinkedList; +import java.util.ArrayList; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * The Stripifier utility will change the primitive of the GeometryInfo + * object to Triangle Strips. The strips are made by analyzing the + * triangles in the original data and connecting them together.

+ *

+ * Normal Generation should be performed on the GeometryInfo object + * before Stripification, for best results. Example:

+ *

+ *

+ *   GeometryInfo gi = new GeometryInfo(TRIANGLE_ARRAY);
+ *   gi.setCoordinates(coordinateData);
+ *
+ *   NormalGenerator ng = new NormalGenerator();
+ *   ng.generateNormals(gi);
+ *
+ *   Stripifier st = new Stripifier()
+ *   st.stripify(gi);
+ *
+ *   Shape3D part = new Shape3D();
+ *   part.setAppearance(appearance);
+ *   part.setGeometry(gi.getGeometryArray());
+ *   
+ */ +public class Stripifier { + + final boolean DEBUG = false; + final boolean CHECK_ORIENT = false; + + static final int EMPTY = -1; + + boolean hasNormals = false; + boolean hasTextures = false; + int texSetCount = 0; + boolean hasColors = false; + boolean colorStrips = false; + + StripifierStats stats; + + int[] numNhbrs; + + /** + * Indicates to the stripifier to collect statistics on the data + */ + public static final int COLLECT_STATS = 0x01; + + /** + * Creates the Stripifier object. + */ + public Stripifier() { + } + + /** + * Creates the Stripifier object. + * @param flags Flags + * @since Java 3D 1.2.1 + */ + public Stripifier(int flags) { + if ((flags & COLLECT_STATS) != 0) { + stats = new StripifierStats(); + } + } + + /** + * Converts the geometry contained in the GeometryInfo object into an + * array of triangle strips. + */ + public void stripify(GeometryInfo gi) { + // System.out.println("stripify"); + long time = System.currentTimeMillis(); + // setup + gi.convertToIndexedTriangles(); + gi.forgetOldPrim(); + + // write out the gi object + // System.out.println("write out the object"); + // gi.writeObj(); + + Face[] faces = createFaceArray(gi); + Edge[] edges = createEdgeArray(faces); + buildAdjacencies(edges, faces); + + // print out the adjacency information + if (DEBUG) { + for (int i = 0; i < faces.length; i++) { + faces[i].printVertices(); + } + System.out.println(""); + for (int i = 0; i < faces.length; i++) { + faces[i].printAdjacency(); + } + System.out.println(""); + } + + Node[] faceNodes = new Node[faces.length]; + // Node[] queue = hybridSearch(faces, faceNodes); + Node[] queue = dfSearch(faces, faceNodes); + + // print out the queue + if (DEBUG) { + for (int i = 0; i < queue.length; i++) { + queue[i].print(); + } + System.out.println(""); + } + + // int "pointers" for the numbers of strips and patches from + // hamiliton + int[] ns = new int[1]; + int[] np = new int[1]; + ArrayList hamiltons = hamilton(queue, ns, np); + int numStrips = ns[0]; + int numPatches = np[0]; + + // print out the hamiltonians + if (DEBUG) { + for (int i = 0; i < hamiltons.size(); i++) { + System.out.println("Hamiltonian: " + i); + ArrayList list = (ArrayList)hamiltons.get(i); + for (int j = 0; j < list.size(); j++) { + Face face = (Face)list.get(j); + face.printVertices(); + } + System.out.println(""); + } + } + + // now make strips out of the hamiltonians + ArrayList strips = stripe(hamiltons); + + // print out the strips + if (DEBUG) { + for (int i = 0; i < strips.size(); i++) { + System.out.println("Strip: " + i); + Istream istream = (Istream)strips.get(i); + for (int j = 0; j < istream.length; j++) { + System.out.println("vertex: " + istream.istream[j].index); + } + System.out.println(""); + } + } + + // concatenate the strips + concatenate(strips, faces); + + // print out the new strips + if (DEBUG) { + System.out.println(""); + System.out.println("concatenated strips: (" + + (strips.size()) + ")"); + System.out.println(""); + for (int i = 0; i < strips.size(); i++) { + System.out.println("Strip: " + i); + Istream istream = (Istream)strips.get(i); + for (int j = 0; j < istream.length; j++) { + System.out.println("vertex: " + istream.istream[j].index); + } + System.out.println(""); + } + } + + // put the stripified data into the GeometryInfo object + putBackData(gi, strips); + + // System.out.println("time: " + (System.currentTimeMillis()-time)); + // System.out.println(""); + + // add to stats + if (stats != null) { + stats.updateInfo(System.currentTimeMillis()-time, strips, + faces.length); + } + + // Stat.printInfo(); + + // print out strip count info + // System.out.println("numStrips = " + strips.size()); + // System.out.println("stripCounts:"); + // int avg = 0; + // for (int i = 0; i < strips.size(); i++) { + // System.out.print(((Istream)strips.get(i)).length + " "); + // avg += ((Istream)strips.get(i)).length; + // } + // System.out.println("Avg: " + ((double)avg/(double)strips.size())); + } + + /** + * Prints out statistical information for the stripifier: the number of + * original triangles, the number of original vertices, the number of + * strips created, the number of vertices, the total number of triangles, + * the minimum strip length (in # of tris) the maximum strip length + * (in number of tris), the average strip length (in # of tris), the + * average number of vertices per triangle, the total time it took to + * stripify, and the strip length (how many strips of a given length. + * The data is cumulative over all the times the stripifier is called + * until the stats are printed, and then they are reset. + */ + // public static void printStats() { + // // stats.toString(); + // } + + /** + * Returns the stripifier stats object. + * @exception IllegalStateException if the Stripfier has not + * been constructed + * with the COLLECT_STATS flag + * @since Java 3D 1.2.1 + */ + public StripifierStats getStripifierStats() { + if (stats == null) { + throw new IllegalStateException(J3dUtilsI18N.getString("Stripifier0")); + } + return stats; + } + + /** + * Creates an array of faces from the geometry in the GeometryInfo object. + */ + Face[] createFaceArray(GeometryInfo gi) { + int[] vertices = gi.getCoordinateIndices(); + int[] normals = gi.getNormalIndices(); + + int[][] textures = null; + int[] t1 = null; + int[] t2 = null; + int[] t3 = null; + texSetCount = gi.getTexCoordSetCount(); + if (texSetCount > 0) { + hasTextures = true; + textures = new int[texSetCount][]; + for (int i = 0; i < texSetCount; i++) { + textures[i] = gi.getTextureCoordinateIndices(i); + } + t1 = new int[texSetCount]; + t2 = new int[texSetCount]; + t3 = new int[texSetCount]; + } else hasTextures = false; + + int[] colors = gi.getColorIndices(); + Face[] faces = new Face[vertices.length/3]; + int n1, n2, n3, c1, c2, c3; + Vertex v1, v2, v3; + int count = 0; + for (int i = 0; i < vertices.length;) { + if (normals != null) { + // System.out.println("hasNormals"); + hasNormals = true; + n1 = normals[i]; + n2 = normals[i+1]; + n3 = normals[i+2]; + } + else { + // System.out.println("doesn't have normals"); + hasNormals = false; + n1 = EMPTY; + n2 = EMPTY; + n3 = EMPTY; + } + if (hasTextures) { + for (int j = 0; j < texSetCount; j++) { + t1[j] = textures[j][i]; + t2[j] = textures[j][(i+1)]; + t3[j] = textures[j][(i+2)]; + } + } + if (colors != null) { + hasColors = true; + c1 = colors[i]; + c2 = colors[i+1]; + c3 = colors[i+2]; + } + else { + hasColors = false; + c1 = EMPTY; + c2 = EMPTY; + c3 = EMPTY; + } + v1 = new Vertex(vertices[i], n1, texSetCount, t1, c1); + v2 = new Vertex(vertices[i+1], n2, texSetCount, t2, c2); + v3 = new Vertex(vertices[i+2], n3, texSetCount, t3, c3); + if (!v1.equals(v2) && !v2.equals(v3) && !v3.equals(v1)) { + faces[count] = new Face(count, v1, v2, v3); + count++; + } + i+=3; + } + + if (faces.length > count) { + Face[] temp = faces; + faces = new Face[count]; + System.arraycopy(temp, 0, faces, 0, count); + } + return faces; + } + + /** + * Creates an array of edges from the Face array. + */ + Edge[] createEdgeArray(Face[] faces) { + Edge[] edges = new Edge[faces.length*3]; + Face face; + for (int i = 0; i < faces.length; i++) { + face = faces[i]; + edges[i*3] = new Edge(face.verts[0], face.verts[1], face.key); + edges[i*3+1] = new Edge(face.verts[1], face.verts[2], face.key); + edges[i*3+2] = new Edge(face.verts[2], face.verts[0], face.key); + } + return edges; + } + + /** + * Builds the adjacency graph by finding the neighbors of the edges + */ + void buildAdjacencies(Edge[] edges, Face[] faces) { + // sortEdges(edges); + quickSortEdges(edges, 0, edges.length-1); + // int i = 1; + + // set up the edge list of each face + Edge edge; + Face face; + Vertex[] verts; + boolean flag; + int k; + for (int i = 0; i < edges.length; i++) { + // edges are kept in order s.t. the ith edge is the opposite + // edge of the ith vertex + edge = edges[i]; + face = faces[edge.face]; + verts = face.verts; + + flag = true; + if ((!verts[0].equals(edge.v1)) && (!verts[0].equals(edge.v2))) { + face.edges[0] = edge; + face.numNhbrs--; + flag = false; + } + else if ((!verts[1].equals(edge.v1)) && + (!verts[1].equals(edge.v2))) { + face.edges[1] = edge; + face.numNhbrs--; + flag = false; + } + else if ((!verts[2].equals(edge.v1)) && + (!verts[2].equals(edge.v2))) { + face.edges[2] = edge; + face.numNhbrs--; + flag = false; + } + else { + if (DEBUG) System.out.println("error!!! Stripifier.buildAdj"); + } + + // handle degenerencies + if (flag) { + Vertex i1; + // triangle degenerated to a point + if ((edge.v1).equals(edge.v2)) { + face.edges[--face.numNhbrs] = edge; + } + // triangle degenerated to an edge + else { + if (verts[0].equals(verts[1])) { + i1 = verts[1]; + } + else { + i1 = verts[2]; + } + if (verts[0].equals(i1) && face.edges[0] == null) { + face.edges[0] = edge; + face.numNhbrs--; + } + else if (verts[1].equals(i1) && face.edges[1] == null) { + face.edges[1] = edge; + face.numNhbrs--; + } + else { + face.edges[2] = edge; + face.numNhbrs--; + } + } + } + } + + // build the adjacency information by pairing up every two triangles + // that share the same edge + int i = 0; int j = 0; + int j1, j2; + while (i < (edges.length-1)) { + j = i+1; + if (edges[i].equals(edges[j])) { + // determine the orientations of the common edge in the two + // adjacent triangles. Only set them to be adjacent if they + // are opposite + j1 = edges[i].face; + j2 = edges[j].face; + if (j1 != j2) { // set up the two faces as neighbors + edge = edges[i]; + face = faces[j1]; + k = face.getEdgeIndex(edge); + if ((edge.v1.equals(face.verts[(k+1)%3])) && + (edge.v2.equals(face.verts[(k+2)%3]))) { + flag = false; + } + else flag = true; + + edge = edges[j]; + face = faces[j2]; + k = face.getEdgeIndex(edge); + if ((edge.v1.equals(face.verts[(k+1)%3])) && + (edge.v2.equals(face.verts[(k+2)%3]))) { + flag = flag; + } + else flag = (!flag); + + if (flag) { + edges[i].face = j2; + edges[j].face = j1; + (faces[j1].numNhbrs)++; + (faces[j2].numNhbrs)++; + j++; + } + else edges[i].face = EMPTY; + } + else edges[i].face = EMPTY; + } + else edges[i].face = EMPTY; + i=j; + } + if (i <= (edges.length-1)) edges[i].face = EMPTY; + + // check, for each face, if it is duplicated. For a face that + // neighbors its duplicate in the adjacency graph, it's possible + // that two or more of its neighbors are the same (the duplicate). + // This will be corrected to avoid introducing redundant faces + // later on + + for (i = 0; i < faces.length; i++) { + face = faces[i]; + if (face.numNhbrs == 3) { + if ((j1 = face.edges[1].face) == face.edges[0].face) { + face.edges[1].face = EMPTY; + face.numNhbrs--; + faces[j1].counterEdgeDel(face.edges[1]); + } + if ((j2 = face.edges[2].face) == face.edges[0].face) { + face.edges[2].face = EMPTY; + face.numNhbrs--; + faces[j2].counterEdgeDel(face.edges[2]); + } + if ((face.edges[1].face != EMPTY) && (j1 == j2)) { + face.edges[2].face = EMPTY; + face.numNhbrs--; + faces[j1].counterEdgeDel(face.edges[2]); + } + } + } + } + + /** + * Sorts the edges using BubbleSort + */ + void sortEdges(Edge[] edges) { + int i = edges.length; + boolean sorted = false; + Edge temp = null; + while ((i > 1) && !sorted) { + sorted = true; + for (int j = 1; j < i; j++) { + if (edges[j].lessThan(edges[j-1])) { + temp = edges[j-1]; + edges[j-1] = edges[j]; + edges[j] = temp; + sorted = false; + } + } + i--; + } + } + + /** + * uses quicksort to sort the edges + */ + void quickSortEdges(Edge[] edges, int l, int r) { + if (edges.length > 0) { + int i = l; + int j = r; + Edge k = edges[(l+r) / 2]; + + do { + while (edges[i].lessThan(k)) i++; + while (k.lessThan(edges[j])) j--; + if (i <= j) { + Edge tmp = edges[i]; + edges[i] = edges[j]; + edges[j] = tmp; + i++; + j--; + } + } while (i <= j); + + if (l < j) quickSortEdges(edges, l, j); + if (l < r) quickSortEdges(edges, i, r); + } + } + + /** + * Takes a list of faces as input and performs a hybrid search, a + * variated depth first search that returns to the highest level node + * not yet fully explored. Returns an array of pointers to the faces + * found in order from the search. The faceNodes parameter is an + * array of the Nodes created for the faces. + */ + Node[] hybridSearch(Face[] faces, Node[] faceNodes) { + + int numFaces = faces.length; + int i = 0, j = 0, k = 0, ind = 0; + + // keep # of faces with certain # of neighbors + int[] count = {0, 0, 0, 0}; + + // faces sorted by number of neighbors + int[] index = new int[numFaces]; + // the index of a certain face in the sorted array + int[] rindex = new int[numFaces]; + + // Control list pop up operation + boolean popFlag = false; + + // queue of pointers to faces found in search + Node[] queue = new Node[numFaces]; + // root of depth first tree + Node source; + // for the next node + Node nnode; + // a face + Face face; + // starting position for insertion into the list + int start = 0; + // list for search + SortedList dlist; + + // count how many faces have a certain # of neighbors and + // create a Node for each face + for (i = 0; i < numFaces; i++) { + j = faces[i].numNhbrs; + count[j]++; + faceNodes[i] = new Node(faces[i]); + } + + // to help with sorting + for (i = 1; i < 4; i++) { + count[i] += count[i-1]; + } + + // decreasing i to make sorting stable + for (i = numFaces - 1; i >= 0; i--) { + j = faces[i].numNhbrs; + count[j]--; + index[count[j]] = i; + rindex[i] = count[j]; + } + + // start the hybrid search + for (i = 0; i < numFaces; i++) { + if (index[i] != EMPTY) { + dlist = new SortedList(); + source = faceNodes[index[i]]; + source.setRoot(); + queue[ind] = source; + ind++; + index[i] = EMPTY; + + while (source != null) { + nnode = null; + // use the first eligible for continuing search + face = source.face; + for (j = 0; j < 3; j++) { + k = face.getNeighbor(j); + if ((k != EMPTY) && + (faceNodes[k].notAccessed())) { + nnode = faceNodes[k]; + break; + } + } + + if (nnode != null) { + // insert the new node + nnode.insert(source); + if (!popFlag) { + start = dlist.sortedInsert(source, start); + } + else popFlag = false; + source = nnode; + queue[ind] = source; + ind++; + index[rindex[k]] = EMPTY; + } + else { + source.processed(); + source = dlist.pop(); + popFlag = true; + start = 0; + } + } // while -- does popFlag need to be set to false here? + } + } + return queue; + } + + Node[] dfSearch(Face[] faces, Node[] faceNodes) { + int numFaces = faces.length; + int i = 0, j = 0, k = 0, ind = 0; + + // keep certain # of faces with certain # of neighbors + int[] count = {0, 0, 0, 0}; + + // faces sorted by # of neighbors + int[] index = new int[numFaces]; + // index of a certain face in the sorted array + int[] rindex = new int[numFaces]; + + // queue of pointers to faces found in the search + Node[] queue = new Node[numFaces]; + // root of the depth first tree + Node source; + // the current node + Node node; + // for the next Node + Node nnode; + // a face + Face face; + + // count how many faces have a certain # of neighbors and create + // a Node for each face + for (i = 0; i < numFaces; i++) { + j = faces[i].numNhbrs; + count[j]++; + faceNodes[i] = new Node(faces[i]); + } + + // to help with sorting + for (i = 1; i < 4; i++) count[i] += count[i-1]; + + // dec i to make sorting stable + for (i = numFaces-1; i >= 0; i--) { + j = faces[i].numNhbrs; + count[j]--; + index[count[j]] = i; + rindex[i] = count[j]; + } + + setNumNhbrs(faces); + // start the dfs + for (i = 0; i < numFaces; i++) { + if (index[i] != EMPTY) { + source = faceNodes[index[i]]; + source.setRoot(); + queue[ind] = source; + ind++; + index[i] = EMPTY; + node = source; + + do { + // if source has been done, stop + if ((node == source) && (node.right != null)) break; + + nnode = null; + face = node.face; + + // for (j = 0; j < 3; j++) { + // if (((k = face.getNeighbor(j)) != EMPTY) && + // (faceNodes[k].notAccessed())) { + // nnode = faceNodes[k]; + // break; + // } + // } + + k = findNext(node, faceNodes, faces); + if (k != EMPTY) nnode = faceNodes[k]; + if (nnode != null) updateNumNhbrs(nnode); + + if (nnode != null) { + // insert new node + nnode.insert(node); + node = nnode; + queue[ind] = node; + ind++; + index[rindex[k]] = EMPTY; + } + else { + node.processed(); + node = node.parent; + } + } while (node != source.parent); + } + } + freeNhbrTable(); + return queue; + } + + int findNext(Node node, Node[] faceNodes, Face[] faces) { + Face face = node.face; + // this face has no neighbors so return + if (face.numNhbrs == 0) return EMPTY; + + int i, j, count; + int[] n = new int[3]; // num neighbors of neighboring face + int[] ind = {-1, -1, -1}; // neighboring faces + + // find the number of neighbors for each neighbor + count = 0; + for (i = 0; i < 3; i++) { + if (((j = face.getNeighbor(i)) != EMPTY) && + (faceNodes[j].notAccessed())) { + ind[count] = j; + n[count] = numNhbrs[j]; + count++; + } + } + + // this face has no not accessed faces + if (count == 0) return EMPTY; + + // this face has only one neighbor + if (count == 1) return ind[0]; + + if (count == 2) { + // if the number of neighbors are the same, try reseting + if ((n[0] == n[1]) && (n[0] != 0)) { + n[0] = resetNhbr(ind[0], faces, faceNodes); + n[1] = resetNhbr(ind[1], faces, faceNodes); + } + // if one neighbor has fewer neighbors, return that neighbor + if (n[0] < n[1]) return ind[0]; + if (n[1] < n[0]) return ind[1]; + // neighbors tie. pick the sequential one + Node pnode, ppnode; + Face pface, ppface; + if ((pnode = node.parent) != null) { + pface = pnode.face; + i = pface.findSharedEdge(face.key); + if ((ppnode = pnode.parent) != null) { + ppface = ppnode.face; + if (pface.getNeighbor((i+1)%3) == ppface.key) { + j = pface.verts[(i+2)%3].index; + } + else { + j = pface.verts[(i+1)%3].index; + } + } + else { + j = pface.verts[(i+1)%3].index; + } + i = face.findSharedEdge(ind[0]); + if (face.verts[i].index == j) return ind[0]; + else return ind[1]; + } + else return ind[0]; + } + // three neighbors + else { + if ((n[0] < n[1]) && (n[0] < n[2])) return ind[0]; + else if ((n[1] < n[0]) && (n[1] < n[2])) return ind[1]; + else if ((n[2] < n[0]) && (n[2] < n[1])) return ind[2]; + else if ((n[0] == n[1]) && (n[0] < n[2])) { + if (n[0] != 0) { + n[0] = resetNhbr(ind[0], faces, faceNodes); + n[1] = resetNhbr(ind[1], faces, faceNodes); + } + if (n[0] <= n[1]) return ind[0]; + else return ind[1]; + } + else if ((n[1] == n[2]) && n[1] < n[0]) { + if (n[1] != 0) { + n[1] = resetNhbr(ind[1], faces, faceNodes); + n[2] = resetNhbr(ind[2], faces, faceNodes); + } + if (n[1] <= n[2]) return ind[1]; + else return ind[2]; + } + else if ((n[2] == n[0]) && (n[2] < n[1])) { + if (n[0] != 0) { + n[0] = resetNhbr(ind[0], faces, faceNodes); + n[2] = resetNhbr(ind[2], faces, faceNodes); + } + if (n[0] <= n[2]) return ind[0]; + else return ind[2]; + } + else { + if (n[0] != 0) { + n[0] = resetNhbr(ind[0], faces, faceNodes); + n[1] = resetNhbr(ind[1], faces, faceNodes); + n[2] = resetNhbr(ind[2], faces, faceNodes); + } + if ((n[0] <= n[1]) && (n[0] <= n[2])) return ind[0]; + else if (n[1] <= n[2]) return ind[1]; + else return ind[2]; + } + } + } + + void setNumNhbrs(Face[] faces) { + int numFaces = faces.length; + numNhbrs = new int[numFaces]; + for (int i = 0; i < numFaces; i++) { + numNhbrs[i] = faces[i].numNhbrs; + } + } + + void freeNhbrTable() { + numNhbrs = null; + } + + void updateNumNhbrs(Node node) { + Face face = node.face; + int i; + if ((i = face.getNeighbor(0)) != EMPTY) numNhbrs[i]--; + if ((i = face.getNeighbor(1)) != EMPTY) numNhbrs[i]--; + if ((i = face.getNeighbor(2)) != EMPTY) numNhbrs[i]--; + } + + int resetNhbr(int y, Face[] faces, Node[] faceNodes) { + int x = EMPTY; + Face nface = faces[y]; + int i; + for (int j = 0; j < 3; j++) { + if (((i = nface.getNeighbor(j)) != EMPTY) && + (faceNodes[i].notAccessed())) { + if ((x == EMPTY) || (x > numNhbrs[i])) x = numNhbrs[i]; + } + } + return x; + } + + /** + * generates hamiltonian strips from the derived binary spanning tree + * using the path peeling algorithm to peel off any node wiht double + * children in a bottom up fashion. Returns a Vector of strips. Also + * return the number of strips and patches in the numStrips and + * numPatches "pointers" + */ + ArrayList hamilton(Node[] sTree, int[] numStrips, int[] numPatches) { + // the number of nodes in the tree + int numNodes = sTree.length; + // number of strips + int ns = 0; + // number of patches + int np = 0; + // some tree node variables + Node node, pnode, cnode; + // the Vector of strips + ArrayList strips = new ArrayList(); + // the current strip + ArrayList currStrip; + + // the tree nodes are visited in such a bottom-up fashion that + // any node is visited prior to its parent + for (int i = numNodes - 1; i >= 0; i--) { + cnode = sTree[i]; + + // if cnode is the root of a tree create a strip + if (cnode.isRoot()) { + // each patch is a single tree + np++; + // create a new strip + currStrip = new ArrayList(); + // insert the current node into the list + currStrip.add(0, cnode.face); + + // add the left "wing" of the parent node to the strip + node = cnode.left; + while (node != null) { + currStrip.add(0, node.face); + node = node.left; + } + + // add the right "wing" of the parent node to the strip + node = cnode.right; + while (node != null) { + currStrip.add(currStrip.size(), node.face); + node = node.left; + } + + // increase the number of strips + ns++; + // add the strip to the Vector + strips.add(currStrip); + } + + // if the number of children of this node is 2, create a strip + else if (cnode.numChildren == 2) { + // if the root has a single child with double children, it + // could be left over as a singleton. However, the following + // rearrangement reduces the chances + pnode = cnode.parent; + if (pnode.isRoot() && (pnode.numChildren == 1)) { + pnode = cnode.right; + if (pnode.left != null) cnode = pnode; + else cnode = cnode.left; + } + + // handle non-root case + + // remove the node + cnode.remove(); + + // create a new strip + currStrip = new ArrayList(); + // insert the current node into the list + currStrip.add(0, cnode.face); + + // add the left "wing" of cnode to the list + node = cnode.left; + while (node != null) { + currStrip.add(0, node.face); + node = node.left; + } + + // add the right "wing" of cnode to the list + node = cnode.right; + while (node != null) { + currStrip.add(currStrip.size(), node.face); + node = node.left; + } + + // increase the number of strips + ns++; + // add the strip to the Vector + strips.add(currStrip); + } + } + + // put the ns and np in the "pointers to return + numStrips[0] = ns; + numPatches[0] = np; + + // return the strips + return strips; + } + + /** + * creates the triangle strips + */ + ArrayList stripe(ArrayList strips) { + int numStrips = strips.size(); // the number of strips + int count; // where we are in the hamiltonian + Face face; // the face we are adding to the stream + Face prev; // the previous face added to the stream + boolean done; // whether we are done with the current strip + boolean cont; // whether we should continue the current stream + ArrayList currStrip; // the current hamiltonian + Istream currStream; // the stream we are building + ArrayList istreams = new ArrayList(); // the istreams to return + boolean ccw = true;; // counter-clockwise + int share; // the shared edge + Vertex[] buf = new Vertex[4]; // a vertex array to start the stream + + // create streams for each hamiltonian + for (int i = 0; i < numStrips; i++) { + currStrip = (ArrayList)strips.get(i); + count = 0; + done = false; + face = getNextFace(currStrip, count++); + + // while we are not done with the current hamiltonian + while (!done) { + cont = true; + + // if the current face is the only one left in the current + // hamiltonian + if (stripDone(currStrip, count)) { + // create a new istream with the current face + currStream = new Istream(face.verts, 3, false); + // set the head of the strip to this face + currStream.head = face.key; + done = true; + // since we are done with the strip, set the tail to this + // face + currStream.tail = face.key; + } + + else { + prev = face; + face = getNextFace(currStrip, count++); + + // put the prev vertices in the correct order + // to add the next tri on + share = prev.findSharedEdge(face.key); + buf[0] = prev.verts[share]; + buf[1] = prev.verts[(share+1)%3]; + buf[2] = prev.verts[(share+2)%3]; + + // find the fourth vertex + if (CHECK_ORIENT) { + // check for clockwise orientation + if (checkOrientCWSeq(buf[2], buf[1], face)) { + share = face.findSharedEdge(prev.key); + buf[3] = face.verts[share]; + currStream = new Istream(buf, 4, false); + // set the head of this strip to the prev face + currStream.head = prev.key; + // if this was the last tri in the strip, then + // we are done + if (stripDone(currStrip, count)) { + done = true; + // set the tail for the strip to current face + currStream.tail = face.key; + } + } + else { + cont = false; + currStream = new Istream(buf, 3, false); + // set the head to the prev face + currStream.head = prev.key; + // since we are not continuing, set + // the tail to prev also + currStream.tail = prev.key; + } + + // orientation starts counter-clockwise for 3rd face + ccw = true; + } + else { + share = face.findSharedEdge(prev.key); + buf[3] = face.verts[share]; + currStream = new Istream(buf, 4, false); + // set the head of this strip to the prev face + currStream.head = prev.key; + // if this was the last tri in the strip, then + // we are done + if (stripDone(currStrip, count)) { + done = true; + // set the tail for the strip to current face + currStream.tail = face.key; + } + } + + // while continue and the strip isn't finished + // add more faces to the stream + while (cont && !stripDone(currStrip, count)) { + prev = face; + face = getNextFace(currStrip, count++); + share = face.findSharedEdge(prev.key); + + // if we can add the face without adding any + // zero area triangles + if (seq(currStream, face, share)) { + if (CHECK_ORIENT) { + // if we can add the next face with the correct + // orientation + if (orientSeq(ccw, currStream, face)) { + // append the vertex opposite the + //shared edge + currStream.append(face.verts[share]); + // next face must have opposite orientation + ccw = (!ccw); + // if this was the last tri in the + //strip, then we are done + if (stripDone(currStrip, count)) { + done = true; + // since we are done with this strip, + // set the tail to the current face + currStream.tail = face.key; + } + } + // if we cannot add the face with the correct + // orientation, do not continue with this + // stream + else { + cont = false; + // since we cannot continue with this strip + // set the tail to prev + currStream.tail = prev.key; + } + } + else { + // append the vertex opposite the + //shared edge + currStream.append(face.verts[share]); + // if this was the last tri in the + //strip, then we are done + if (stripDone(currStrip, count)) { + done = true; + // since we are done with this strip, + // set the tail to the current face + currStream.tail = face.key; + } + } + } + + // need zero area tris to add continue the strip + else { + if (CHECK_ORIENT) { + // check the orientation for adding a zero + // area tri and this face + if (orientZAT(ccw, currStream, face)) { + // swap the end of the current stream to + // add a zero area triangle + currStream.swapEnd(); + // append the vertex opposite the + // shared edge + currStream.append(face.verts[share]); + // if this was the last tri in the + // strip then we are done + if (stripDone(currStrip, count)) { + done = true; + // set the tail because we are done + currStream.tail = face.key; + } + } + // if we cannot add the face with the correct + // orientation, do not continue with this + // stream + else { + cont = false; + // since we cannot continue with this face, + // set the tail to the prev face + currStream.tail = prev.key; + } + } + else { + // swap the end of the current stream to + // add a zero area triangle + currStream.swapEnd(); + // append the vertex opposite the + // shared edge + currStream.append(face.verts[share]); + // if this was the last tri in the + // strip then we are done + if (stripDone(currStrip, count)) { + done = true; + // set the tail because we are done + currStream.tail = face.key; + } + } + } + } // while (cont && !stripDone) + } // else + + // add the current strip to the strips to be returned + istreams.add(currStream); + } // while !done + } // for each hamiltonian + return istreams; + } // stripe + + boolean stripDone(ArrayList strip, int count) { + if (count < strip.size()) { + return false; + } + else return true; + } + + boolean seq(Istream stream, Face face, int share) { + int length = stream.length; + Vertex v1 = face.edges[share].v1; + Vertex v2 = face.edges[share].v2; + Vertex last = stream.istream[length-1]; + Vertex prev = stream.istream[length-2]; + if (((v1.equals(prev)) && (v2.equals(last))) || + ((v1.equals(last)) && (v2.equals(prev)))) { + return true; + } + else return false; + } + + boolean orientSeq(boolean ccw, Istream stream, Face face) { + int length = stream.length; + Vertex last = stream.istream[length-1]; + Vertex prev = stream.istream[length-2]; + if ((ccw && checkOrientCCWSeq(last, prev, face)) || + ((!ccw) && checkOrientCWSeq(last, prev, face))) { + return true; + } + else return false; + } + + boolean orientZAT(boolean ccw, Istream stream, Face face) { + int length = stream.length; + Vertex last = stream.istream[length-1]; + Vertex swap = stream.istream[length-3]; + if ((ccw && checkOrientCWSeq(last, swap, face)) || + ((!ccw) && checkOrientCCWSeq(last, swap, face))) { + return true; + } + else return false; + } + + boolean checkOrientCWSeq(Vertex last, Vertex prev, Face face) { + System.out.println("checkOrientCWSeq"); + System.out.println("last = " + last.index); + System.out.println("prev = " + prev.index); + System.out.print("face = "); + face.printVertices(); + if (last.equals(face.verts[0])) { + if (!prev.equals(face.verts[1])) { + if (DEBUG) System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + else if (last.equals(face.verts[1])) { + if (!prev.equals(face.verts[2])) { + if (DEBUG) System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + else if (last.equals(face.verts[2])) { + if (!prev.equals(face.verts[0])) { + if (DEBUG) System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + return true; + } + + boolean checkOrientCCWSeq(Vertex last, Vertex prev, Face face) { + System.out.println("checkOrientCCWSeq"); + System.out.println("last = " + last.index); + System.out.println("prev = " + prev.index); + System.out.print("face = "); + face.printVertices(); + if (prev.equals(face.verts[0])) { + if (!last.equals(face.verts[1])) { + System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + else if (prev.equals(face.verts[1])) { + if (!last.equals(face.verts[2])) { + System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + else if (prev.equals(face.verts[2])) { + if (!last.equals(face.verts[0])) { + System.out.println("ORIENTATION PROBLEM!"); + return false; + } + } + return true; + } + + Face getNextFace(ArrayList currStrip, int index) { + if (currStrip.size() > index) return (Face)currStrip.get(index); + else return null; + } + + /** + * joins tristrips if their end triangles neighbor each other. The + * priority is performed in three stages: strips are concatenated to + * save 2, 1, or no vertices + */ + void concatenate(ArrayList strips, Face[] faces) { + int numFaces = faces.length; + int[] faceTable = new int[numFaces]; + Istream strm; + + // initialize the face table to empty + for (int i = 0; i < numFaces; i++) { + faceTable[i] = EMPTY; + } + + // set up the faceTable so that a face index relates to a strip + // that owns the face as one of its end faces + for (int i = 0; i < strips.size(); i++) { + strm = (Istream)strips.get(i); + faceTable[strm.head] = i; + faceTable[strm.tail] = i; + } + + if (DEBUG) { + System.out.println(""); + System.out.println("faceTable:"); + for (int i = 0; i < faceTable.length; i++) { + System.out.println(faceTable[i]); + } + System.out.println(""); + } + + reduceCostByTwo(strips, faces, faceTable); + reduceCostByOne(strips, faces, faceTable); + reduceCostByZero(strips, faces, faceTable); + } + + /** + * find all the links that reduce the cost by 2 + */ + void reduceCostByTwo(ArrayList strips, Face[] faces, int[] faceTable) { + // System.out.println("reduceCostByTwo"); + // number of faces in the face array + int numFaces = faces.length; + // possible adjacent strips + int id, id1, id2; + // Istreams + Istream strm, strm1; + // the length of the Istrem + int len, len1; + // vertex sequences for tristrips + Vertex[] seq, seq1; + // a face + Face face; + // the list of vertices for the face + Vertex[] verts; + // used to syncronize the orientation + boolean sync, sync1; + // a swap variable + Vertex swap; + + for (int i = 0; i < numFaces; i++) { + id = faceTable[i]; + if (id != EMPTY) { + sync = false; sync1 = false; + strm = (Istream)strips.get(id); + len = strm.length; + seq = strm.istream; + face = faces[i]; + verts = face.verts; + + // sequential strips + if (!strm.fan) { + + // a singleton strip + if (len == 3) { + + // check all three neighbors + for (int j = 0; j < 3; j++) { + int k = face.getNeighbor(j); + if ((k != EMPTY) && + ((id1 = faceTable[k]) != EMPTY) && + (id1 != id)) { + // reassign the sequence + seq[0] = verts[j]; + seq[1] = verts[(j+1)%3]; + seq[2] = verts[(j+2)%3]; + + // the neighboring stream + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + if (k != strm1.head) { + strm1.invert(); + // if the length is odd set sync1 to true + if ((len1 % 2) != 0) sync1 = true; + } + seq1 = strm1.istream; + + // append a singleton strip + if (len1 == 3) { + // System.out.println("reduce2"); + int m = faces[k].findSharedEdge(i); + strm.append(faces[k].verts[m]); + strm1.length = 0; + strm1.istream = null; + strm.tail = k; + faceTable[k] = id; + i--; + break; + } + + // append a strip of length over 2 + else { + if ((len1 == 4) && + (seq[1].index == seq1[0].index) && + (seq[2].index == seq1[2].index)) { + + // swap seq1[1] and seq1[2] so that + // seq[1] == seq1[0] and + // seq[1] == seq1[1] + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + // see if we can join the strips + if ((seq[1].index == seq1[0].index) && + (seq[2].index == seq1[1].index)) { + // System.out.println("reduce2"); + // add the stream in + strm.addStream(strm1); + faceTable[k] = EMPTY; + faceTable[strm.tail] = id; + + i--; + break; + } + else if (sync1) { + strm1.invert(); + sync1 = false; + } + } + } + } + } + + // not a singleton strip + + // can append a stream where the current face is the tail + // or is an even length so we can invert it + else if ((i == strm.tail) || ((len % 2) == 0)) { + // if the current face isn't the tail, then + // have to invert the strip + if (i != strm.tail) { + strm.invert(); + seq = strm.istream; + } + + // System.out.println("seq.length = " + seq.length); + // System.out.println("len = " + len); + // System.out.print("seq = "); + // for (int l = 0; l < seq.length; l++) { + // if (seq[l] == null) System.out.print(" null"); + // else System.out.print(" " + seq[l].index); + // } + // System.out.println(""); + + swap = seq[len - 3]; + + // find the neighboring strip + int m = EMPTY; + if (verts[0].index == swap.index) m = 0; + else if (verts[1].index == swap.index) m = 1; + else if (verts[2].index == swap.index) m = 2; + if (m == EMPTY) { + if (DEBUG) System.out.println("problem finding neighbor strip"); + } + int j = face.getNeighbor(m); + if (j == EMPTY) id1 = j; + else id1 = faceTable[j]; + if ((id1 != EMPTY) && + (((Istream)strips.get(id1)).fan != + strm.fan)) { + id1 = EMPTY; + } + + if ((id1 != EMPTY) && (id1 != id)) { + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + + // if the shared face isn't the head, invert + // the stream + if (j != strm1.head) { + strm1.invert(); + // set the sync var if the length is odd + if ((len1 % 2) != 0) sync1 = true; + } + seq1 = strm1.istream; + + // append a singleton strip + if (len1 == 3) { + // System.out.println("reduce2"); + m = faces[j].findSharedEdge(i); + strm.append(faces[j].verts[m]); + strm1.length = 0; + strm1.istream = null; + strm.tail = j; + faceTable[i] = EMPTY; + faceTable[j] = id; + } + + // append a non-singleton strip + else { + if ((len1 == 4) && + (seq[len-2].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index)) { + + // swap seq1[1] and seq1[2] so that + // seq[len-2] == seq1[0] and + // seq[len-1] == seq1[1] + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + // see if we can append the strip + if ((seq[len-2].index == seq1[0].index) && + (seq[len-1].index == seq1[1].index)) { + // System.out.println("reduce2"); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[j] = EMPTY; + } + else if (sync1) strm1.invert(); + } + } + } + } + } + } + } + + /** + * find all links that reduce cost by 1 + */ + void reduceCostByOne(ArrayList strips, Face[] faces, int[] faceTable) { + // System.out.println("reduceCostByOne"); + // number of faces in the face array + int numFaces = faces.length; + // possible adjacent strips + int id, id1, id2; + // Istreams + Istream strm, strm1; + // the length of the Istream + int len, len1; + // vertex sequences for tristrips + Vertex[] seq, seq1; + // a face + Face face; + // the list of vertices for the face + Vertex[] verts; + // used to synchronize the orientation + boolean sync, sync1; + // a swap variable + Vertex swap; + + for (int i = 0; i < numFaces; i++) { + id = faceTable[i]; + if ((id != EMPTY) && !((Istream)strips.get(id)).fan) { + sync = false; + strm = (Istream)strips.get(id); + seq = strm.istream; + face = faces[i]; + verts = face.verts; + len = strm.length; + + // a singleton strip + if (len == 3) { + + // consider the three neighboring triangles + for (int j = 0; j < 3; j++) { + int k = face.getNeighbor(j); + if ((k != EMPTY) && + ((id1 = faceTable[k]) != EMPTY) && + (id1 != id) && + (!((Istream)strips.get(id1)).fan)) { + + // reassign the sequence + seq[0] = verts[j]; + seq[1] = verts[(j+1)%3]; + seq[2] = verts[(j+2)%3]; + + // the neighboring stream + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + if (k != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + // see if we can join the strips + + if ((len1 == 4) && + (((seq[1].index == seq1[2].index) && + (seq[2].index == seq1[0].index)) || + ((seq[1].index == seq1[0].index) && + (seq[2].index == seq1[2].index)))) { + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + if ((seq[1].index == seq1[0].index) && + (seq[2].index == seq1[1].index)) { + // System.out.println("reduce1"); + strm.addStream(strm1); + faceTable[k] = EMPTY; + faceTable[strm.tail] = id; + i--; + break; + } + + if ((seq[1].index == seq1[1].index) && + (seq[2].index == seq1[0].index)) { + // System.out.println("reduce1"); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[k] = EMPTY; + faceTable[strm.tail] = id; + i--; + break; + } + + if ((seq[1].index == seq1[0].index) && + (seq[2].index == seq1[2].index)) { + // System.out.println("reduce1"); + seq1[0] = seq1[2]; + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[k] = EMPTY; + faceTable[strm.tail] = id; + i--; + break; + } + + if (sync) { + strm1.invert(); + sync = false; + } + } + } + } + + // non-singleton strip + else if ((i == strm.tail) || ((len % 2) == 0)) { + + // make sure the face i ends the id-th strip + if (i != strm.tail) { + strm.invert(); + seq = strm.istream; + } + + swap = seq[len-3]; + + // find the neighboring strip + int m = EMPTY; + if (verts[0].index == swap.index) m = 0; + else if (verts[1].index == swap.index) m = 1; + else if (verts[2].index == swap.index) m = 2; + if (m == EMPTY) { + if (DEBUG) System.out.println("problem finding neighbor strip"); + } + int j = face.getNeighbor(m); + if (j == EMPTY) id1 = j; + else id1 = faceTable[j]; + if ((id1 != EMPTY) && + (((Istream)strips.get(id1)).fan != strm.fan)) { + id1 = EMPTY; + } + + // find another neighboring strip + swap = seq[len-2]; + m = EMPTY; + if (verts[0].index == swap.index) m = 0; + else if (verts[1].index == swap.index) m = 1; + else if (verts[2].index == swap.index) m = 2; + if (m == EMPTY) { + if (DEBUG) System.out.println("problem finding neighbor strip."); + } + int k = face.getNeighbor(m); + if (k == EMPTY) id2 = k; + else id2 = faceTable[k]; + if ((id2 != EMPTY) && + (((Istream)strips.get(id2)).fan != strm.fan)) { + id2 = EMPTY; + } + + // consider strip id1 + boolean success = false; + if ((id1 != EMPTY) && (id1 != id)) { + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + if (j != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + if ((len1 == 4) && + (((seq[len-2].index == seq1[2].index) && + (seq[len-1].index == seq1[0].index)) || + (seq[len-2].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index))) { + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + // find matches + if ((seq[len-2].index == seq1[0].index) && + (seq[len-1].index == seq1[1].index)) { + // System.out.println("reduce1"); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[j] = EMPTY; + success = true; + } + + else if ((seq[len-2].index == seq1[1].index) && + (seq[len-1].index == seq1[0].index)) { + // System.out.println("reduce1"); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[j] = EMPTY; + success = true; + } + + else if ((seq[len-2].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index)) { + // System.out.println("reduce1"); + seq1[0] = seq1[2]; + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[j] = EMPTY; + success = true; + } + else if (sync) { + strm1.invert(); + sync = false; + } + } + + // now consider strip id2 + if (!success && + (id2 != EMPTY) && (id2 != id)) { + strm1 = (Istream)strips.get(id2); + len1 = strm1.length; + if (k != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + if ((len1 == 4) && + (seq[len-3].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index)) { + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + // find matches + + if ((seq[len-3].index == seq1[0].index) && + (seq[len-1].index == seq1[1].index)) { + // System.out.println("reduce1"); + strm.swapEnd(); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[k] = EMPTY; + success = true; + } + if (!success && sync) strm1.invert(); + } + } + } + } + } + + /** + * find all the links that reduce the cost by 0 + */ + void reduceCostByZero(ArrayList strips, Face[] faces, int[] faceTable) { + // System.out.println("reduceCostByZero"); + // number of faces in the face array + int numFaces = faces.length; + // possible adjacent strips + int id, id1, id2; + // Istreams + Istream strm, strm1; + // the length of the Istream + int len, len1; + // vertex sequences for tristrips + Vertex[] seq, seq1; + // a face + Face face; + // the list of vertices for the face + Vertex[] verts; + // used to synchronize the orientation + boolean sync, sync1; + // a swap variable + Vertex swap; + + for (int i = 0; i < numFaces; i++) { + id = faceTable[i]; + if ((id != EMPTY) && !((Istream)strips.get(id)).fan) { + sync = false; + strm = (Istream)strips.get(id); + seq = strm.istream; + len = strm.length; + face = faces[i]; + verts = face.verts; + + if (len == 3) { + for (int j = 0; j < 3; j++) { + int k = face.getNeighbor(j); + if ((k != EMPTY) && ((id1 = faceTable[k]) != EMPTY) && + (id1 != id) && + !((Istream)strips.get(id1)).fan) { + // reassign the sequence + seq[0] = verts[j]; + seq[1] = verts[(j+1)%3]; + seq[2] = verts[(j+2)%3]; + + // the neighboring stream + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + if (k != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + // see if we can join the strips + if ((seq[1].index == seq1[2].index) && + (seq[2].index == seq1[0].index)) { + // System.out.println("reduce0"); + seq1[0] = seq1[2]; + strm.append(seq1[0]); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[k] = EMPTY; + faceTable[strm.tail] = id; + i--; + break; + } + else if (sync) { + strm1.invert(); + sync = false; + } + } + } + } + else if ((i == strm.tail) || ((len % 2) == 0)) { + if (i != strm.tail) { + strm.invert(); + seq = strm.istream; + } + + swap = seq[len-3]; + + // find neighboring strip + int m = EMPTY; + if (verts[0].index == swap.index) m = 0; + else if (verts[1].index == swap.index) m = 1; + else if (verts[2].index == swap.index) m = 2; + if (m == EMPTY) { + if (DEBUG) System.out.println("problem finding neighbor strip"); + } + int j = face.getNeighbor(m); + if (j == EMPTY) id1 = j; + else id1 = faceTable[j]; + if ((id1 != EMPTY) && + (((Istream)strips.get(id1)).fan != strm.fan)) { + id1 = EMPTY; + } + + // find another neighboring strip + swap = seq[len-2]; + m = EMPTY; + if (verts[0].index == swap.index) m = 0; + else if (verts[1].index == swap.index) m = 1; + else if (verts[2].index == swap.index) m = 2; + if (m == EMPTY) { + if (DEBUG) System.out.println("problem finding neighbor strip."); + } + int k = face.getNeighbor(m); + if (k == EMPTY) id2 = k; + else id2 = faceTable[k]; + if ((id2 != EMPTY) && + (((Istream)strips.get(id2)).fan != strm.fan)) { + id2 = EMPTY; + } + + // consider strip id1 + boolean success = false; + if ((id1 != EMPTY) && (id1 != id)) { + strm1 = (Istream)strips.get(id1); + len1 = strm1.length; + if (j != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + // find matches + if ((seq[len-2].index == seq1[2].index) && + (seq[len-1].index == seq1[0].index)) { + // System.out.println("reduce0"); + seq1[0] = seq1[2]; + strm.append(seq1[0]); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[j] = EMPTY; + success = true; + } + else if (sync) { + strm1.invert(); + sync = false; + } + } + + // consider strip id2 + if (!success && (id2 != EMPTY) && (id2 != id)) { + strm1 = (Istream)strips.get(id2); + len1 = strm1.length; + if (k != strm1.head) { + strm1.invert(); + if ((len1 % 2) != 0) sync = true; + } + seq1 = strm1.istream; + + if ((len1 == 4) && + (((seq[len-3].index == seq1[2].index) && + (seq[len-1].index == seq1[0].index)) || + ((seq[len-3].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index)))) { + + swap = seq1[1]; + seq1[1] = seq1[2]; + seq1[2] = swap; + } + + // find matches + if ((seq[len-3].index == seq1[1].index) && + (seq[len-1].index == seq1[0].index)) { + // System.out.println("reduce0"); + strm.swapEnd(); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[k] = EMPTY; + } + else if ((seq[len-3].index == seq1[0].index) && + (seq[len-1].index == seq1[2].index)) { + // System.out.println("reduce0"); + seq1[0] = seq1[2]; + strm.swapEnd(); + strm.append(seq1[1]); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[k] = EMPTY; + } + else if ((seq[len-3].index == seq1[0].index) && + (seq[len-1].index == seq1[1].index)) { + // System.out.println("reduce0"); + strm.swapEnd(); + strm.addStream(strm1); + faceTable[i] = EMPTY; + faceTable[strm.tail] = id; + faceTable[k] = EMPTY; + } + else if (sync) strm1.invert(); + } + } + } + } + } + + /** + * puts the stripified data back into the GeometryInfo object + */ + void putBackData(GeometryInfo gi, ArrayList strips) { + int[] tempStripCounts = new int[strips.size()]; + int ciSize = 0; + int stripLength; + for (int i = 0; i < strips.size();) { + stripLength = ((Istream)strips.get(i)).length; + if (stripLength != 0) { + tempStripCounts[i] = stripLength; + ciSize += stripLength; + i++; + } + else { + strips.remove(i); + } + } + if (ciSize > 3) { + gi.setPrimitive(gi.TRIANGLE_STRIP_ARRAY); + int[] stripCounts = new int[strips.size()]; + System.arraycopy(tempStripCounts, 0, stripCounts, 0, strips.size()); + gi.setStripCounts(stripCounts); + + // create one array with all the strips + int[] coords = new int[ciSize]; + + // create arrays for normals, textures and colors if necessary + int[] normals = null; + int[][] textures = null; + int[] colors = null; + javax.vecmath.Color3b[] stripColors = null; + if (hasNormals) normals = new int[ciSize]; + if (hasTextures) { + textures = new int[texSetCount][ciSize]; + } + if (hasColors) colors = new int[ciSize]; + if (colorStrips) { + stripColors = new javax.vecmath.Color3b[ciSize]; + colors = new int[ciSize]; + } + int count = 0; + Istream currStrip; + for (int i = 0; i < strips.size(); i++) { + currStrip = (Istream)strips.get(i); + + if (currStrip.length < 3) { + throw new RuntimeException("currStrip.length = " + + currStrip.length); + } + + java.awt.Color stripColor = null; + if (colorStrips) { + int r = ((int)(Math.random()*1000))%255; + int g = ((int)(Math.random()*1000))%255; + int b = ((int)(Math.random()*1000))%255; + stripColor = new java.awt.Color(r, g, b); + } + + for (int j = 0; j < currStrip.length; j++) { + coords[count] = currStrip.istream[j].index; + if (hasNormals) normals[count] = currStrip.istream[j].normal; + if (hasTextures) { + for (int k = 0; k < texSetCount; k++) { + textures[k][count] = + currStrip.istream[j].texture[k]; + } + } + if (hasColors) colors[count] = currStrip.istream[j].color; + if (colorStrips) stripColors[count] = + new javax.vecmath.Color3b(stripColor); + count++; + } + } + gi.setCoordinateIndices(coords); + if (hasNormals) gi.setNormalIndices(normals); + if (hasTextures) { + for (int i = 0; i < texSetCount; i++) { + gi.setTextureCoordinateIndices(i, textures[i]); + } + } + if (hasColors) gi.setColorIndices(colors); + if (colorStrips) { + gi.setColors(stripColors); + colors = gi.getListIndices(stripColors); + gi.setColorIndices(colors); + } + } + } + + /** + * Stores the infomration about a vertex + */ + class Vertex { + + int index; + int normal = EMPTY; + int numTexSets = 0; + int[] texture = null; + int color = EMPTY; + + Vertex(int vertIndex) { + this(vertIndex, EMPTY, 0, null, EMPTY); + } + + Vertex(int vertIndex, int vertNormal, + int vertNumTexSets, int[] vertTexture, int vertColor) { + index = vertIndex; + normal = vertNormal; + numTexSets = vertNumTexSets; + if (numTexSets > 0) { + texture = new int[numTexSets]; + System.arraycopy(vertTexture, 0, texture, 0, numTexSets); + } + color = vertColor; + } + + boolean equals(Vertex v) { + for (int i = 0; i < numTexSets; i++) { + if (texture[i] != v.texture[i]) { + return false; + } + } + return ((v.index == index) && + (v.normal == normal) && + (v.color == color)); + } + + // will this yield same results as c code ??? + boolean lessThan(Vertex v) { + if (index < v.index) return true; + if (index > v.index) return false; + if (normal < v.normal) return true; + if (normal > v.normal) return false; + for (int i = 0; i < numTexSets; i++) { + if (texture[i] < v.texture[i]) return true; + if (texture[i] > v.texture[i]) return false; + } + if (color < v.color) return true; + if (color > v.color) return false; + return false; + } + } + + /** + * Stores the information about an edge of a triangle + */ + class Edge { + + Vertex v1, v2; + int face; + + Edge(Vertex vertex1, Vertex vertex2, int faceIndex) { + face = faceIndex; + + // this could be causing wrapping problem + if (vertex1.lessThan(vertex2)) { + v1 = vertex1; + v2 = vertex2; + } else { + v1 = vertex2; + v2 = vertex1; + } + } + + /** + * Determine whether the edges have the same vertices + */ + boolean equals(Edge edge) { + return ((v1.equals(edge.v1)) && (v2.equals(edge.v2))); + + } + + /** + * Used to sort the edges. If this is less than the edge parameter, + * return true. First check if vertex1 is less than vertex1 of the + * edge provided. If so, return true. If the first vertices are equal + * then check vertex2. + */ + boolean lessThan(Edge edge) { + if (v1.lessThan(edge.v1)) return true; + else if (v1.equals(edge.v1)) return (v2.lessThan(edge.v2)); + else return false; + } + } + + /** + * Stores the information about the face of a triangle + */ + class Face { + int key; + int numNhbrs = 0; + Vertex[] verts = null; + // edges are kept in order s.t. the ith edge is the opposite + // edge of the ith vertex + Edge[] edges = null; + + /** + * Creates a new Face with the three given vertices + */ + Face(int index, Vertex v1, Vertex v2, Vertex v3) { + key = index; + + verts = new Vertex[3]; + verts[0] = v1; + verts[1] = v2; + verts[2] = v3; + + edges = new Edge[3]; + edges[0] = null; + edges[1] = null; + edges[2] = null; + numNhbrs = 3; + } + + /** + * returns the index of the face that neighbors the edge supplied + * by the parameter + */ + int getNeighbor(int edge) { + return edges[edge].face; + } + + /** + * returns the index of the edge that is shared by the triangle + * specified by the key parameter + */ + int findSharedEdge(int key) { + if (edges[0].face == key) return 0; + else if (edges[1].face == key) return 1; + else if (edges[2].face == key) return 2; + else return -1; /* error */ + } + + int getEdgeIndex(Edge edge) { + if (edges[0].equals(edge)) return 0; + else if (edges[1].equals(edge)) return 1; + else return 2; + } + + void counterEdgeDel(Edge edge) { + if (DEBUG) { + System.out.println("counterEdgeDel"); + } + if ((edges[0]).equals(edge)) { + edges[0].face = EMPTY; + numNhbrs--; + } + else if ((edges[1]).equals(edge)) { + edges[1].face = EMPTY; + numNhbrs--; + } + else if ((edges[2]).equals(edge)) { + edges[2].face = EMPTY; + numNhbrs--; + } + else { + if (DEBUG) { + System.out.println("error in counterEdgeDel"); + } + } + } + + void printAdjacency() { + System.out.println("Face " + key + ": "); + System.out.println("\t numNhbrs = " + numNhbrs); + System.out.println("\t edge 0: Face " + edges[0].face); + System.out.println("\t edge 1: Face " + edges[1].face); + System.out.println("\t edge 2: Face " + edges[2].face); + } + + void printVertices() { + System.out.println("Face " + key + ": (" + verts[0].index + ", " + + verts[1].index + ", " + verts[2].index + ")"); + } + } + + /** + * stores the information for a face node + */ + class Node { + Face face; // the data: the face + Node parent; // the parent node + Node left; // the left child + Node right; // the right child + int depth; // the topological distance of the node from the root + int numChildren; // the number of children + int attrib; // characteristic of the node eg. color + + // the attributes - 3 states for the Node + static final int WHITE = 0; // not being accessed yet + static final int GREY = 1; // being accessed but not done yet + static final int BLACK = 2; // done + + Node(Face f) { + face = f; + } + + /** + * inserts this node below the parent supplied. + */ + void insert(Node p) { + parent = p; + depth = p.depth + 1; + attrib = GREY; + + if (parent.left == null) parent.left = this; + else parent.right = this; + (parent.numChildren)++; + } + + /** + * remove this node from its parent + */ + void remove() { + if (parent != null) { + if (parent.left == this) { + parent.left = parent.right; + parent.right = null; + } + else { + parent.right = null; + } + (parent.numChildren)--; + } + } + + + /** + * sets the depth to 0 and the attrib to GREY + */ + void setRoot() { + depth = 0; + attrib = GREY; + } + + /** + * returns true if the attrib is WHITE + */ + boolean notAccessed() { + return (attrib == WHITE); + } + + /** + * sets the color to BLACK + */ + void processed() { + attrib = BLACK; + } + + /** + * a node is the root if it doesn't have a parent + */ + boolean isRoot() { + return (parent == null); + } + + /** + * prints the information in this Node + */ + void print() { + System.out.println(this); + System.out.println("Node depth: " + depth); + face.printVertices(); + System.out.print("parent: "); + if (parent != null) parent.face.printVertices(); + else System.out.println("null"); + System.out.print("left: "); + if (left != null) left.face.printVertices(); + else System.out.println("null"); + System.out.print("right: "); + if (right != null) right.face.printVertices(); + else System.out.println("null"); + System.out.println("attrib: " + attrib); + System.out.println(""); + } + } + + /** + * sorts the Nodes by depth + */ + class SortedList { + + ArrayList list; + + /** + * create a new SortedList + */ + SortedList() { + list = new ArrayList(); + } + + /** + * insert into the list sorted by depth. start looking at start + * to save some time. Returns the index of the next item of the + * inserted element + */ + int sortedInsert(Node data, int start) { + // adjust start to where insert sorted + while ((start < list.size()) && + (((Node)list.get(start)).depth <= data.depth)) { + start++; + } + + // insert at start index + list.add(start, data); + + // return start+1 -- the index of the next element + return (start+1); + } + + /** + * remove and return 1st element + */ + Node pop() { + if (!list.isEmpty()) return (Node)list.remove(0); + else return null; + } + } + + class Istream { + + // fan encoding + boolean fan = false; + // length of the strip + int length = 0; + // array that specifies triangle strip + Vertex[] istream; + // indices of the head and tail vertices + int head, tail; + + /** + * creates a new Istream to store the triangle strip + */ + Istream(Vertex[] list, int size, boolean isFan) { + if (size == 0) throw new RuntimeException("size is 0"); + fan = isFan; + length = size; + istream = new Vertex[length]; + int i; + System.arraycopy(list, 0, istream, 0, length); + } + + /** + * adds a new vertex to the end of the stream + * makes the int array bigger, if necessary + */ + void append(Vertex vertex) { + growArray(); + // add in new vertex + istream[length] = vertex; + length++; + } + + /** + * turns the encoding (..., -3, -2, -1) into (.... -3, -2, -3, -1) + * so that zero-area triangle (-3, -2. -3) is added + */ + void swapEnd() { + growArray(); + istream[length] = istream[length-1]; + istream[length-1] = istream[length-3]; + length++; + } + + /** + * makes the array bigger, if necessary + */ + void growArray() { + if (length >= istream.length) { + Vertex[] old = istream; + // for now add enough space to add three more vertices + // may change this later + istream = new Vertex[length + 3]; + System.arraycopy(old, 0, istream, 0, length); + } + } + + /** + * inverts the istream + */ + void invert() { + Vertex[] tmp = new Vertex[istream.length]; + // reverse the stream + for (int i = 0; i < length; i++) { + tmp[i] = istream[length - i - 1]; + } + // copy it back + System.arraycopy(tmp, 0, istream, 0, istream.length); + tmp = null; + // swap the head and the tail + int swap = head; + head = tail; + tail = swap; + } + + /** + * concats two streams into one big stream + */ + void addStream(Istream strm) { + // System.out.println("addStream"); + int strmLen = strm.length; + int size = strmLen + length - 2; + + // make the istream bigger + if (size >= istream.length) { + Vertex[] old = istream; + istream = new Vertex[size]; + System.arraycopy(old, 0, istream, 0, length); + } + + // add the strm to istream + System.arraycopy(strm.istream, 2, istream, length, strmLen-2); + + tail = strm.tail; + length = size; + strm.length = 0; + strm.istream = null; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/StripifierStats.java b/src/classes/share/com/sun/j3d/utils/geometry/StripifierStats.java new file mode 100644 index 0000000..423ee18 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/StripifierStats.java @@ -0,0 +1,345 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import java.util.ArrayList; + +/** + * This class collects statistics on the Stripifier. The statistics + * are cumulative over all calls to stripify() until clearData() is called. + * + * @since Java 3D 1.2.1 + */ + +public class StripifierStats { + + int numStrips = 0; + int numVerts = 0; + int minStripLen = 10000; + int maxStripLen = 0; + int totalTris = 0; + int numFaces = 0; + long time = 0; + int[] counts = new int[14]; + + boolean noData = true; + + /** + * Returns the number of triangles in the original, un-stripified data. + * @since Java 3D 1.2.1 + */ + public int getNumOrigTris() { + return numFaces; + } + + /** + * Returns the number of vertices in the original, un-stripified data + * @since Java 3D 1.2.1 + */ + public int getNumOrigVerts() { + return (numFaces * 3); + } + + /** + * Returns the number of strips created by the stripifier. + * @since Java 3D 1.2.1 + */ + public int getNumStrips() { + return numStrips; + } + + /** + * Returns the number of vertices in the stripified data. + * @since Java 3D 1.2.1 + */ + public int getNumVerts() { + return numVerts; + } + + /** + * Returns the number of triangles in the stripified data. + * @since Java 3D 1.2.1 + */ + public int getTotalTris() { + return totalTris; + } + + /** + * Returns the length in triangles of the shortest strip + * created by the stripifier. + * @since Java 3D 1.2.1 + */ + public int getMinStripLength() { + return minStripLen; + } + + /** + * Returns the length in triangles of the longest strip + * created by the stripifier. + * @since Java 3D 1.2.1 + */ + public int getMaxStripLength() { + return maxStripLen; + } + + /** + * Return the average length of the strips created by the stripifier + * @since Java 3D 1.2.1 + */ + public double getAvgStripLength() { + return ((double)totalTris/(double)numStrips); + } + + /** + * Returns the average number of vertices per triangle in the stripified + * data + * @since Java 3D 1.2.1 + */ + public double getAvgNumVertsPerTri() { + return ((double)numVerts/(double)totalTris); + } + + /** + * Returns the total time spent in the stripify() method + * @since Java 3D 1.2.1 + */ + public long getTotalTime() { + return time; + } + + /** + * Returns an array of length 14 that contains the number of strips of + * a given length created by the stripifier. Spots 0-8 of the array + * represent lengths 1-9, 9 is lengths 10-19, 10 is lengths 20-49, + * 11 is lengths 50-99, 12 is lengths 100-999 and 13 is lengths 1000 + * or more. + * @since Java 3D 1.2.1 + */ + public int[] getStripLengthCounts() { + return counts; + } + + /** + * Returns a formated String that can be used to print out + * the Stripifier stats. + * @since Java 3D 1.2.1 + */ + + public String toString() { + StringBuffer str = new StringBuffer( + "num orig tris: " + numFaces + "\n" + + "num orig vertices: " + (numFaces*3) + "\n" + + "number of strips: " + numStrips + "\n" + + "number of vertices: " + numVerts + "\n" + + "total tris: " + totalTris + "\n" + + "min strip length: " + minStripLen + "\n" + + "max strip length: " + maxStripLen + "\n" + + "avg strip length: " + ((double)totalTris/ + (double)numStrips) + "\n" + + "avg num verts/tri: " + ((double)numVerts/ + (double)totalTris) + "\n" + + "total time: " + time + "\n" + + "strip length distribution:\n"); + for (int i = 0; i < 9; i++){ + str.append(" " + (i+1) + "=" + counts[i]); + } + str.append(" 10-19=" + counts[9]); + str.append(" 20-49=" + counts[10]); + str.append(" 50-99=" + counts[11]); + str.append(" 100-999=" + counts[12]); + str.append(" 1000 or more=" + counts[13] + "\n"); + + return str.toString(); + } + + /** + * Clears the statistical data + */ + public void clearData() { + noData = true; + + numStrips = 0; + numVerts = 0; + minStripLen = 10000; + maxStripLen = 0; + totalTris = 0; + numFaces = 0; + time = 0; + counts = new int[14]; + } + + void updateInfo(long ntime, ArrayList strips, + int nNumFaces) { + noData = false; + + time += ntime; + numStrips += strips.size(); + int nv = 0; + int mnsl = 10000; + int mxsl = 0; + int tt = 0; + for (int i = 0; i < strips.size(); i++) { + Stripifier.Istream strm = (Stripifier.Istream)strips.get(i); + int len = strm.length; + int trilen = (len-2); + nv += len; + if (trilen < mnsl) mnsl = trilen; + if (trilen > mxsl) mxsl = trilen; + tt += trilen; + + // add to counts + // how many strips are length 1-9 + if (trilen <= 9) counts[trilen-1] += 1; + // how many strips are length 10-19 + else if (trilen < 20) counts[9] += 1; + // how many strips are length 20-49 + else if (trilen < 50) counts[10] += 1; + // how many strips are length 50-99 + else if (trilen < 100) counts[11] += 1; + // how many strips are length 100-1000 + else if (trilen < 1000) counts[12] += 1; + // how many strips are length > 1000 + else counts[13] += 1; + } + numVerts += nv; + if (mnsl < minStripLen) minStripLen = mnsl; + if (mxsl > maxStripLen) maxStripLen = mxsl; + totalTris += tt; + numFaces += nNumFaces; + } + + void updateInfo(long ntime, int scLen, int sc[], + int nNumFaces) { + + noData = false; + + time += ntime; + numStrips += scLen; + int nv = 0; + int mnsl = 10000; + int mxsl = 0; + int tt = 0; + for (int i = 0; i < scLen; i++) { + int len = sc[i]; + int trilen = (len-2); + numVerts += len; + if (trilen < mnsl) mnsl = trilen; + if (trilen > mxsl) mxsl = trilen; + totalTris += trilen; + + // add to counts + // how many strips are length 1-9 + if (trilen <= 9) counts[trilen-1] += 1; + // how many strips are length 10-19 + else if (trilen < 20) counts[9] += 1; + // how many strips are length 20-49 + else if (trilen < 50) counts[10] += 1; + // how many strips are length 50-99 + else if (trilen < 100) counts[11] += 1; + // how many strips are length 100-1000 + else if (trilen < 1000) counts[12] += 1; + // how many strips are length > 1000 + else counts[13] += 1; + } + numVerts += nv; + if (mnsl < minStripLen) minStripLen = mnsl; + if (mxsl > maxStripLen) maxStripLen = mxsl; + totalTris += tt; + numFaces += nNumFaces; + } + + // void printInfo() { + // System.out.println("num orig tris: " + numFaces); + // System.out.println("num orig vertices: " + (numFaces*3)); + // System.out.println("number of strips: " + numStrips); + // System.out.println("number of vertices: " + numVerts); + // System.out.println("total tris: " + totalTris); + // System.out.println("min strip length: " + minStripLen); + // System.out.println("max strip length: " + maxStripLen); + // System.out.println("avg strip length: " + ((double)totalTris/ + // (double)numStrips)); + // System.out.println("avg num verts/tri: " + ((double)numVerts/ + // (double)totalTris)); + // System.out.println("total time: " + time); + // System.out.println("strip length distribution:"); + // for (int i = 0; i < 9; i++){ + // System.out.print(" " + (i+1) + "=" + counts[i]); + // } + // System.out.print(" 10-19=" + counts[9]); + // System.out.print(" 20-49=" + counts[10]); + // System.out.print(" 50-99=" + counts[11]); + // System.out.print(" 100-999=" + counts[12]); + // System.out.println(" 1000 or more=" + counts[13]); + + // // reset info after printing data + // numStrips = 0; + // numVerts = 0; + // minStripLen = 10000; + // maxStripLen = 0; + // totalTris = 0; + // numFaces = 0; + // time = 0; + // counts = new int[14]; + // } + + StripifierStats() { + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Text2D.java b/src/classes/share/com/sun/j3d/utils/geometry/Text2D.java new file mode 100644 index 0000000..291c757 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Text2D.java @@ -0,0 +1,382 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.geometry; + +import java.awt.image.BufferedImage; +import java.awt.image.WritableRaster; +import java.awt.image.DataBufferInt; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Toolkit; +import java.util.Hashtable; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * A Text2D object is a representation of a string as a texture mapped + * rectangle. The texture for the rectangle shows the string as rendered in + * the specified color with a transparent background. The appearance of the + * characters is specified using the font indicated by the font name, size + * and style (see java.awt.Font). The approximate height of the rendered + * string will be the font size times the rectangle scale factor, which has a + * default value of 1/256. For example, a 12 point font will produce + * characters that are about 12/256 = 0.047 meters tall. The lower left + * corner of the rectangle is located at (0,0,0) with the height + * extending along the positive y-axis and the width extending along the + * positive x-axis. + */ +public class Text2D extends Shape3D { + + // This table caches FontMetrics objects to avoid the huge cost + // of re-retrieving metrics for a font we've already seen. + private static Hashtable metricsTable = new Hashtable(); + float rectangleScaleFactor = 1f/256f; + + Color3f color = new Color3f(); + String fontName; + int fontSize, fontStyle; + String text; + + + /** + * Creates a Shape3D object which holds a + * rectangle that is texture-mapped with an image that has + * the specified text written with the specified font + * parameters. + * + * @param text The string to be written into the texture map. + * @param color The color of the text string. + * @param fontName The name of the Java font to be used for + * the text string. + * @param fontSize The size of the Java font to be used. + * @param fontStyle The style of the Java font to be used. + */ + public Text2D(String text, Color3f color, String fontName, + int fontSize, int fontStyle) { + + this.color.set(color); + this.fontName = fontName; + this.fontSize = fontSize; + this.fontStyle = fontStyle; + this.text = text; + + updateText2D(text, color, fontName, fontSize, fontStyle); + } + + /* + * Changes text of this Text2D to 'text'. All other + * parameters (color, fontName, fontSize, fontStyle + * remain the same. + * @param text The string to be set. + */ + public void setString(String text){ + this.text = text; + + Texture tex = getAppearance().getTexture(); + int width = tex.getWidth(); + int height = tex.getHeight(); + + ImageComponent imageComponent = setupImage(text, color, fontName, + fontSize, fontStyle); + if ((imageComponent.getWidth() == width) && + (imageComponent.getHeight() == height)) { + tex.setImage(0, imageComponent); + } else { + Texture2D newTex = setupTexture(imageComponent); + // Copy texture attributes except those related to + // mipmap since Texture only set base imageComponent. + + newTex.setBoundaryModeS(tex.getBoundaryModeS()); + newTex.setBoundaryModeT(tex.getBoundaryModeT()); + newTex.setMinFilter(tex.getMinFilter()); + newTex.setMagFilter(tex.getMagFilter()); + newTex.setEnable(tex.getEnable()); + newTex.setAnisotropicFilterMode(tex.getAnisotropicFilterMode()); + newTex.setAnisotropicFilterDegree(tex.getAnisotropicFilterDegree()); + int pcount = tex.getFilter4FuncPointsCount(); + if (pcount > 0) { + float weights[] = new float[pcount]; + tex.getFilter4Func(weights); + newTex.setFilter4Func(weights); + } + Color4f c = new Color4f(); + tex.getBoundaryColor(c); + newTex.setBoundaryColor(c); + newTex.setUserData(tex.getUserData()); + getAppearance().setTexture(newTex); + } + } + + private void updateText2D(String text, Color3f color, String fontName, + int fontSize, int fontStyle) { + ImageComponent imageComponent = setupImage(text, color, fontName, + fontSize, fontStyle); + + Texture2D t2d = setupTexture(imageComponent); + + QuadArray rect = setupGeometry(imageComponent.getWidth(), + imageComponent.getHeight()); + setGeometry(rect); + + Appearance appearance = setupAppearance(t2d); + setAppearance(appearance); + } + + + /** + * Sets the scale factor used in converting the image width/height + * to width/height values in 3D. + * + * @param newScaleFactor The new scale factor. + */ + public void setRectangleScaleFactor(float newScaleFactor) { + rectangleScaleFactor = newScaleFactor; + updateText2D(text, color, fontName, fontSize, fontStyle); + } + + /** + * Gets the current scale factor being used in converting the image + * width/height to width/height values in 3D. + * + * @return The current scale factor. + */ + public float getRectangleScaleFactor() { + return rectangleScaleFactor; + } + + /** + * Create the ImageComponent and Texture object. + */ + private Texture2D setupTexture(ImageComponent imageComponent) { + Texture2D t2d = new Texture2D(Texture2D.BASE_LEVEL, + Texture.RGBA, + imageComponent.getWidth(), + imageComponent.getHeight()); + t2d.setMinFilter(t2d.BASE_LEVEL_LINEAR); + t2d.setMagFilter(t2d.BASE_LEVEL_LINEAR); + t2d.setImage(0, imageComponent); + t2d.setEnable(true); + t2d.setCapability(Texture.ALLOW_IMAGE_WRITE); + t2d.setCapability(Texture.ALLOW_SIZE_READ); + t2d.setCapability(Texture.ALLOW_ENABLE_READ); + t2d.setCapability(Texture.ALLOW_BOUNDARY_MODE_READ); + t2d.setCapability(Texture.ALLOW_FILTER_READ); + t2d.setCapability(Texture.ALLOW_BOUNDARY_COLOR_READ); + t2d.setCapability(Texture.ALLOW_ANISOTROPIC_FILTER_READ); + t2d.setCapability(Texture.ALLOW_FILTER4_READ); + return t2d; + } + + /** + * Creates a ImageComponent2D of the correct dimensions for the + * given font attributes. Draw the given text into the image in + * the given color. The background of the image is transparent + * (alpha = 0). + */ + private ImageComponent setupImage(String text, Color3f color, + String fontName, + int fontSize, int fontStyle) { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + Font font = new java.awt.Font(fontName, fontStyle, fontSize); + + FontMetrics metrics; + if ((metrics = (FontMetrics)metricsTable.get(font)) == null) { + metrics = toolkit.getFontMetrics(font); + metricsTable.put(font, metrics); + } + int width = metrics.stringWidth(text); + int descent = metrics.getMaxDescent(); + int ascent = metrics.getMaxAscent(); + int leading = metrics.getLeading(); + int height = descent + ascent; + + // Need to make width/height powers of 2 because of Java3d texture + // size restrictions + int pow = 1; + for (int i = 1; i < 32; ++i) { + pow *= 2; + if (width <= pow) + break; + } + width = Math.max (width, pow); + pow = 1; + for (int i = 1; i < 32; ++i) { + pow *= 2; + if (height <= pow) + break; + } + height = Math.max (height, pow); + + // For now, jdk 1.2 only handles ARGB format, not the RGBA we want + BufferedImage bImage = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + Graphics offscreenGraphics = bImage.createGraphics(); + + // First, erase the background to the text panel - set alpha to 0 + Color myFill = new Color(0f, 0f, 0f, 0f); + offscreenGraphics.setColor(myFill); + offscreenGraphics.fillRect(0, 0, width, height); + + // Next, set desired text properties (font, color) and draw String + offscreenGraphics.setFont(font); + Color myTextColor = new Color(color.x, color.y, color.z, 1f); + offscreenGraphics.setColor(myTextColor); + offscreenGraphics.drawString(text, 0, height - descent); + + ImageComponent imageComponent = + new ImageComponent2D(ImageComponent.FORMAT_RGBA, + bImage); + + imageComponent.setCapability(ImageComponent.ALLOW_SIZE_READ); + + return imageComponent; + } + + /** + * Creates a rectangle of the given width and height and sets up + * texture coordinates to map the text image onto the whole surface + * of the rectangle (the rectangle is the same size as the text image) + */ + private QuadArray setupGeometry(int width, int height) { + float zPosition = 0f; + float rectWidth = (float)width * rectangleScaleFactor; + float rectHeight = (float)height * rectangleScaleFactor; + float[] verts1 = { + rectWidth, 0f, zPosition, + rectWidth, rectHeight, zPosition, + 0f, rectHeight, zPosition, + 0f, 0f, zPosition + }; + float[] texCoords = { + 0f, -1f, + 0f, 0f, + (-1f), 0f, + (-1f), -1f + }; + + QuadArray rect = new QuadArray(4, QuadArray.COORDINATES | + QuadArray.TEXTURE_COORDINATE_2); + rect.setCoordinates(0, verts1); + rect.setTextureCoordinates(0, 0, texCoords); + + return rect; + } + + /** + * Creates Appearance for this Shape3D. This sets transparency + * for the object (we want the text to be "floating" in space, + * so only the text itself should be non-transparent. Also, the + * appearance disables lighting for the object; the text will + * simply be colored, not lit. + */ + private Appearance setupAppearance(Texture2D t2d) { + TransparencyAttributes transp = new TransparencyAttributes(); + transp.setTransparencyMode(TransparencyAttributes.BLENDED); + transp.setTransparency(0f); + Appearance appearance = new Appearance(); + appearance.setTransparencyAttributes(transp); + appearance.setTexture(t2d); + + Material m = new Material(); + m.setLightingEnable(false); + appearance.setMaterial(m); + + return appearance; + } + + /** + * Returns the text string + * + * @since Java 3D 1.2.1 + */ + public String getString() { + return text; + } + + /** + * Returns the color of the text + * + * @since Java 3D 1.2.1 + */ + public Color3f getColor() { + return color; + } + + /** + * Returns the font + * + * @since Java 3D 1.2.1 + */ + public String getFontName() { + return fontName; + } + + /** + * Returns the font size + * + * @since Java 3D 1.2.1 + */ + public int getFontSize() { + return fontSize; + } + + /** + * Returns the font style + * + * @since Java 3D 1.2.1 + */ + public int getFontStyle() { + return fontStyle; + } + +} + + + + + diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Triangle.java b/src/classes/share/com/sun/j3d/utils/geometry/Triangle.java new file mode 100644 index 0000000..b543f6d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Triangle.java @@ -0,0 +1,69 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +class Triangle extends Object { + int v1, v2, v3; // This store the index into the list array. + // Not the index into vertex pool yet! + + Triangle(int a, int b, int c) { + v1=a; v2=b; v3=c; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/geometry/Triangulator.java b/src/classes/share/com/sun/j3d/utils/geometry/Triangulator.java new file mode 100644 index 0000000..c8c272b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/geometry/Triangulator.java @@ -0,0 +1,1049 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +// ---------------------------------------------------------------------- +// +// The reference to Fast Industrial Strength Triangulation (FIST) code +// in this release by Sun Microsystems is related to Sun's rewrite of +// an early version of FIST. FIST was originally created by Martin +// Held and Joseph Mitchell at Stony Brook University and is +// incorporated by Sun under an agreement with The Research Foundation +// of SUNY (RFSUNY). The current version of FIST is available for +// commercial use under a license agreement with RFSUNY on behalf of +// the authors and Stony Brook University. Please contact the Office +// of Technology Licensing at Stony Brook, phone 631-632-9009, for +// licensing information. +// +// ---------------------------------------------------------------------- + +package com.sun.j3d.utils.geometry; + +import javax.vecmath.*; +import java.util.*; +import com.sun.j3d.utils.geometry.GeometryInfo; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * Triangulator is a utility for turning arbitrary polygons into triangles + * so they can be rendered by Java 3D. + * Polygons can be concave, nonplanar, and can contain holes. + * @see GeometryInfo + */ +public class Triangulator extends Object { + + GeometryInfo gInfo = null; + + int faces[] = null; + int loops[] = null; + int chains[] = null; + Point2f points[] = null; + Triangle triangles[] = null; + ListNode list[] = null; + + Random randomGen = null; + + int numPoints = 0; + int maxNumPoints = 0; + int numList = 0; + int maxNumList = 0; + int numLoops = 0; + int maxNumLoops = 0; + int numTriangles = 0; + int maxNumTriangles = 0; + + int numFaces = 0; + int numTexSets = 0; + // int maxNumFaces = 0; + + int firstNode = 0; + + int numChains = 0; + int maxNumChains = 0; + + // For Clean class. + Point2f[] pUnsorted = null; + int maxNumPUnsorted = 0; + + // For NoHash class. + boolean noHashingEdges = false; + boolean noHashingPnts = false; + int loopMin, loopMax; + PntNode vtxList[] = null; + int numVtxList = 0; + int numReflex = 0; + int reflexVertices; + + // For Bridge class. + Distance distances[] = null; + int maxNumDist = 0; + Left leftMost[] = null; + int maxNumLeftMost = 0; + + // For Heap class. + HeapNode heap[] = null; + int numHeap = 0; + int maxNumHeap = 0; + int numZero = 0; + + // For Orientation class. + int maxNumPolyArea = 0; + double polyArea[] = null; + + int stripCounts[] = null; + int vertexIndices[] = null; + Point3f vertices[] = null; + Object colors[] = null; + Vector3f normals[] = null; + + boolean ccwLoop = true; + + boolean earsRandom = true; + boolean earsSorted = true; + + int identCntr; // Not sure what is this for. (Ask Martin) + + // double epsilon = 1.0e-12; + double epsilon = 1.0e-12; + + static final double ZERO = 1.0e-8; + static final int EARS_SEQUENCE = 0; + static final int EARS_RANDOM = 1; + static final int EARS_SORTED = 2; + + + static final int INC_LIST_BK = 100; + static final int INC_LOOP_BK = 20; + static final int INC_TRI_BK = 50; + static final int INC_POINT_BK = 100; + static final int INC_DIST_BK = 50; + + private static final int DEBUG = 0; + + /** + * Creates a new instance of the Triangulator. + * @deprecated This class is created automatically when needed in + * GeometryInfo and never needs to be used directly. Putting data + * into a GeometryInfo with primitive POLYGON_ARRAY automatically + * causes the triangulator to be created and used. + */ + public Triangulator() { + earsRandom = false; + earsSorted = false; + } + + /** + * Creates a new instance of a Triangulator. + * @deprecated This class is created automatically when needed in + * GeometryInfo and never needs to be used directly. Putting data + * into a GeometryInfo with primitive POLYGON_ARRAY automatically + * causes the triangulator to be created and used. + */ + public Triangulator(int earOrder) { + switch(earOrder) { + case EARS_SEQUENCE: + earsRandom = false; + earsSorted = false; + break; + case EARS_RANDOM: + randomGen = new Random(); + earsRandom = true; + earsSorted = false; + break; + case EARS_SORTED: + earsRandom = false; + earsSorted = true; + break; + default: + earsRandom = false; + earsSorted = false; + } + + } + + /** + * This routine converts the GeometryInfo object from primitive type + * POLYGON_ARRAY to primitive type TRIANGLE_ARRAY using polygon + * decomposition techniques. + *

+ *

+     * Example of usage:
+     *   Triangulator tr = new Triangulator();
+     *   tr.triangulate(ginfo); // ginfo contains the geometry.
+     *   shape.setGeometry(ginfo.getGeometryArray()); // shape is a Shape3D.
+     *

+ * @param gi Geometry to be triangulated + **/ + public void triangulate(GeometryInfo gi) { + int i, j, k; + int sIndex = 0, index, currLoop, lastInd, ind; + boolean proceed; + boolean reset = false, troubles = false; + + boolean done[] = new boolean[1]; + boolean gotIt[] = new boolean[1]; + + if (gi.getPrimitive() != GeometryInfo.POLYGON_ARRAY){ + throw new IllegalArgumentException(J3dUtilsI18N.getString("Triangulator0")); + } + + gi.indexify(); + + vertices = gi.getCoordinates(); + if(vertices != null) + vertexIndices = gi.getCoordinateIndices(); + else + vertexIndices = null; + + colors = gi.getColors(); + normals = gi.getNormals(); + this.gInfo= gi; + + + stripCounts = gi.getStripCounts(); + + faces = gi.getContourCounts(); + if(faces == null) { + if(stripCounts == null) + System.out.println("StripCounts is null! Don't know what to do."); + + faces = new int[stripCounts.length]; + for(i=0; i 1) { + proceed = true; + } + else if(Simple.simpleFace(this, loops[i1])) + proceed = false; + else + proceed = true; + + if (proceed) { + + + // Do some preprocessing here. + + // System.out.println("faces["+j+"] "+faces[j]); + for(int lpIndex = 0; lpIndex 1) { + NoHash.prepareNoHashEdges(this, i1, i2); + } + else { + noHashingEdges = false; + noHashingPnts = false; + } + + + // mark those vertices whose interior angle is convex + for (i = i1; i < i2; ++i) { + EarClip.classifyAngles(this, loops[i]); + } + + /* + System.out.println("After classifyAngles ..."); + printListData(); + */ + + + // link the holes with the outer boundary by means of "bridges" + if (faces[j] > 1) Bridge.constructBridges(this, i1, i2); + + // put all ears into a circular linked list + resetPolyList(loops[i1]); + NoHash.prepareNoHashPnts(this, i1); + EarClip.classifyEars(this, loops[i1]); + done[0] = false; + + /* + System.out.println("Before clipEar (List)..."); + printListData(); + System.out.println("Before clipEar (vtxList)..."); + printVtxList(); + + int counter = 0; + */ + + // triangulate the polygon + while (!done[0]) { + if (!EarClip.clipEar(this, done)) { + /* + System.out.println(" (False case) clipEar (vtxList)..."); + printListData(); + printVtxList(); + */ + + if (reset) { + // For debugging. + + // System.out.println("***** no further ear to clip! ***** \n"); + // System.out.println("***** not a simple polygon, isn't it? *****\n"); + + + ind = getNode(); + resetPolyList(ind); + + loops[i1] = ind; + if (Desperate.desperate(this, ind, i1, done)) { + // System.out.println("***** let's hope for the best *****\n"); + if (!Desperate.letsHope(this, ind)) { + /* + System.out.println("***** sorry, I can't do it! ***** \n"); + System.out.println("***** ask a triangulation wizard, or "); + System.out.println("clean-up your polyhedron! ***** \n"); + */ + return; + } + } + else { + reset = false; + } + } + else { + // try again from scratch + troubles = true; + // System.out.println("\n***** re-classifying the ears! ***** \n"); + ind = getNode(); + resetPolyList(ind); + + // System.out.println("Before classifyEars(" + ind + ")"); + // printListData(); + + EarClip.classifyEars(this, ind); + reset = true; + } + } + else { + reset = false; + /* + System.out.println(" (True case) clipEar (vtxList)..."); + + printVtxList(); + */ + + } + + if (done[0]) { + // System.out.println("In done[0] is true"); + ind = getNextChain(gotIt); + if (gotIt[0]) { + // at some point of the triangulation, we could not find + // any ear and the polygon was split into two parts. now + // we have to handle (one of) the remaining parts. + resetPolyList(ind); + loops[i1] = ind; + noHashingPnts = false; + NoHash.prepareNoHashPnts(this, i1); + EarClip.classifyEars(this, ind); + reset = false; + done[0] = false; + } + } + } + } + + i1 = i2; + + } + + /* + if (troubles) + System.out.println("\n\nTriangulation completed!\n"); + else + System.out.println("\n\nTriangulation successfully completed!\n"); + */ + // System.out.println("\n...writing the output data: "); + + // Output triangles here. + writeTriangleToGeomInfo(); + + } + + void printVtxList() { + int i; + System.out.println("numReflex " + numReflex + " reflexVertices " + + reflexVertices); + for(i= 0; i= 0) && (ind < numList) && (numList <= maxNumList)); + } + + + + void updateIndex(int ind, int index) { + // assert(InPolyList(ind)); + list[ind].index = index; + } + + int getAngle(int ind) { + return list[ind].convex; + } + + void setAngle(int ind, int convex) { + list[ind].convex = convex; + } + + + void resetPolyList(int ind) { + // assert(InPolyList(ind)); + firstNode = ind; + } + + int getNode() { + // assert(InPolyList(first_node)); + return firstNode; + } + + boolean inLoopList(int loop) { + return ((loop >= 0) && (loop < numLoops) && (numLoops <= maxNumLoops)); + } + + + void deleteHook(int currLoop) { + int ind1, ind2; + + if(inLoopList(currLoop)==false) + System.out.println("Triangulator:deleteHook : Loop access out of range."); + + ind1 = loops[currLoop]; + ind2 = list[ind1].next; + if((inPolyList(ind1))&&(inPolyList(ind2))) { + + deleteLinks(ind1); + loops[currLoop] = ind2; + + } + else + System.out.println("Triangulator:deleteHook : List access out of range."); + } + + /** + * Deletes node ind from list (with destroying its data fields) + */ + void deleteLinks(int ind) { + + if((inPolyList(ind))&&(inPolyList(list[ind].prev))&& + (inPolyList(list[ind].next))) { + + if (firstNode == ind) + firstNode = list[ind].next; + + list[list[ind].next].prev = list[ind].prev; + list[list[ind].prev].next = list[ind].next; + list[ind].prev = list[ind].next = ind; + + } + else + System.out.println("Triangulator:deleteLinks : Access out of range."); + + } + + void rotateLinks(int ind1, int ind2) { + int ind; + int ind0, ind3; + + // assert(InPolyList(ind1)); + // assert(InPolyList(ind2)); + ind0 = list[ind1].next; + ind3 = list[ind2].next; + // assert(InPolyList(ind0)); + // assert(InPolyList(ind3)); + + // Swap. + ind = list[ind1].next; + list[ind1].next = list[ind2].next; + list[ind2].next = ind; + + list[ind0].prev = ind2; + list[ind3].prev = ind1; + + } + + + void storeChain(int ind) { + if (numChains >= maxNumChains) { + // System.out.println("Triangulator:storeChain Expanding chain array ..."); + maxNumChains += 20; + int old[] = chains; + chains = new int[maxNumChains]; + if(old != null) + System.arraycopy(old, 0, chains, 0, old.length); + } + chains[numChains] = ind; + ++numChains; + + } + + int getNextChain(boolean[] done) { + if (numChains > 0) { + done[0] = true; + --numChains; + return chains[numChains]; + } + else { + done[0] = false; + numChains = 0; + return 0; + } + } + + void splitSplice(int ind1, int ind2, int ind3, int ind4) { + list[ind1].next = ind4; + list[ind4].prev = ind1; + list[ind2].prev = ind3; + list[ind3].next = ind2; + + } + + /** + * Allocates storage for a dummy list node; pointers are set to itself. + * @return pointer to node + */ + int makeHook() { + int ind; + + ind = numList; + if (numList >= maxNumList) { + maxNumList += INC_LIST_BK; + // System.out.println("Triangulator: Expanding list array ...."); + ListNode old[] = list; + list = new ListNode[maxNumList]; + System.arraycopy(old, 0, list, 0, old.length); + } + + list[numList] = new ListNode(-1); + list[numList].prev = ind; + list[numList].next = ind; + list[numList].index = -1; + ++numList; + + return ind; + } + + int makeLoopHeader() { + int i; + int ind; + + ind = makeHook(); + if(numLoops >= maxNumLoops) { + maxNumLoops += INC_LOOP_BK; + // System.out.println("Triangulator: Expanding loops array ...."); + int old[] = loops; + loops = new int[maxNumLoops]; + System.arraycopy(old, 0, loops, 0, old.length); + } + + loops[numLoops] = ind; + i = numLoops; + ++numLoops; + + return i; + } + + + /** + * Allocates storage for a new list node, and stores the index of the point + * at this node. Pointers are set to -1. + * @return pointer to node + */ + int makeNode(int index) { + int ind; + + if (numList >= maxNumList) { + maxNumList += INC_LIST_BK; + //System.out.println("Triangulator: Expanding list array ...."); + ListNode old[] = list; + list = new ListNode[maxNumList]; + System.arraycopy(old, 0, list, 0, old.length); + } + + list[numList] = new ListNode(index); + + ind = numList; + list[numList].index = index; + list[numList].prev = -1; + list[numList].next = -1; + ++numList; + + return ind; + } + + + /** + * Inserts node ind2 after node ind1. + */ + void insertAfter(int ind1, int ind2) { + int ind3; + + if((inPolyList(ind1))&&(inPolyList(ind2))) { + + list[ind2].next = list[ind1].next; + list[ind2].prev = ind1; + list[ind1].next = ind2; + ind3 = list[ind2].next; + + if(inPolyList(ind3)) + list[ind3].prev = ind2; + else + System.out.println("Triangulator:deleteHook : List access out of range."); + + return; + } + else + System.out.println("Triangulator:deleteHook : List access out of range."); + + } + + /** + * Returns pointer to the successor of ind1. + */ + int fetchNextData(int ind1) { + return list[ind1].next; + } + + /** + * obtains the data store at ind1 + */ + int fetchData(int ind1) { + return list[ind1].index; + } + + /** + * returns pointer to the successor of ind1. + */ + int fetchPrevData(int ind1) { + return list[ind1].prev; + } + + /** + * swap the list pointers in order to change the orientation. + */ + void swapLinks(int ind1) { + int ind2, ind3; + + ind2 = list[ind1].next; + list[ind1].next = list[ind1].prev; + list[ind1].prev = ind2; + ind3 = ind2; + while (ind2 != ind1) { + ind3 = list[ind2].next; + list[ind2].next = list[ind2].prev; + list[ind2].prev = ind3; + ind2 = ind3; + } + } + + // Methods for handling Triangle. + + void storeTriangle(int i, int j, int k) { + /* + if (ccwLoop) + triangles.add(new Triangle(i,j,k)); + else + triangles.add(new Triangle(j,i,k)); + */ + + if(numTriangles >= maxNumTriangles) { + // System.out.println("Triangulator:storeTriangle Expanding triangle array.."); + maxNumTriangles += INC_TRI_BK; + Triangle old[] = triangles; + triangles = new Triangle[maxNumTriangles]; + if(old != null) + System.arraycopy(old, 0, triangles, 0, old.length); + } + + if (ccwLoop) + triangles[numTriangles] = new Triangle(i,j,k); + else + triangles[numTriangles] = new Triangle(j,i,k); + numTriangles++; + + } + + // Methods for handling Point. + + void initPnts(int number) { + if (maxNumPoints < number) { + maxNumPoints = number; + points = new Point2f[maxNumPoints]; + } + + for(int i = 0; i= 0) && (index < numPoints) && + (numPoints <= maxNumPoints)); + } + + int storePoint(double x, double y) { + int i; + + if (numPoints >= maxNumPoints) { + // System.out.println("Triangulator:storePoint Expanding points array ..."); + maxNumPoints += INC_POINT_BK; + Point2f old[] = points; + points = new Point2f[maxNumPoints]; + if(old != null) + System.arraycopy(old, 0, points, 0, old.length); + } + + points[numPoints] = new Point2f((float)x, (float)y); + // points[numPoints].x = (float)x; + // points[numPoints].y = (float)y; + i = numPoints; + ++numPoints; + + return i; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java b/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java new file mode 100644 index 0000000..80ac782 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/image/TextureLoader.java @@ -0,0 +1,779 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.image; + +import javax.media.j3d.*; +import java.awt.Toolkit; +import java.awt.Image; +import java.awt.Component; +import java.awt.image.*; +import java.net.URL; +import java.lang.reflect.Method; +import java.awt.color.ColorSpace; +import java.awt.Transparency; + +import java.awt.geom.AffineTransform; +// TODO: remove the following for jdk1.2Beta4 +import java.awt.image.AffineTransformOp; +// TODO: add the following for jdk1.2Beta4 +//import java.awt.image.AffineTransformOp; + +/** + * This class is used for loading a texture from an Image or BufferedImage. + * If Java Advanced Imaging is installed then it is used to load the + * images, otherwise AWT toolkit it used. JAI is capable of loading + * a larger set of image formats including .tiff and .bmp + * + * Methods are provided to retrieve the Texture object and the associated + * ImageComponent object or a scaled version of the ImageComponent object. + * + * Default format is RGBA. Other legal formats are: RGBA, RGBA4, RGB5_A1, + * RGB, RGB4, RGB5, R3_G3_B2, LUM8_ALPHA8, LUM4_ALPHA4, LUMINANCE and ALPHA + */ + +public class TextureLoader extends Object { + + /** + * Optional flag - specifies that mipmaps are generated for all levels + **/ + public static final int GENERATE_MIPMAP = 0x01; + + /** + * Optional flag - specifies that the ImageComponent2D will + * access the image data by reference + * + * @since Java 3D 1.2 + **/ + public static final int BY_REFERENCE = 0x02; + + /** + * Optional flag - specifies that the ImageComponent2D will + * have a y-orientation of y up, meaning the orgin of the image is the + * lower left + * + * @since Java 3D 1.2 + **/ + public static final int Y_UP = 0x04; + + /** + * Private declaration for BufferedImage allocation + */ + private static ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + private static int[] nBits = {8, 8, 8, 8}; + private static int[] bandOffset = { 0, 1, 2, 3}; + private static ComponentColorModel colorModel = new ComponentColorModel(cs, nBits, true, false, Transparency.TRANSLUCENT, 0); + + private Texture2D tex = null; + private BufferedImage bufferedImage = null; + private ImageComponent2D imageComponent = null; + private int textureFormat = Texture.RGBA; + private int imageComponentFormat = ImageComponent.FORMAT_RGBA; + private int flags; + private boolean byRef; + private boolean yUp; + + private boolean useJAI = false; + + // JAI Methods + // Use introspection to get the methods so that JAI + // is not required to compile the Java3D API. + private Method jaiGetWidth; + private Method jaiGetHeight; + private Method jaiGetAsBufferedImage; + private Method jaiGetRendering; + private Method jaiCreate; + private boolean doneJAICheck = false; + private boolean jaiInstalled = false; + + /** + * Contructs a TextureLoader object using the specified BufferedImage + * and default format RGBA + * @param bImage The BufferedImage used for loading the texture + */ + public TextureLoader(BufferedImage bImage) { + this(bImage, new String("RGBA"), 0); + } + + /** + * Contructs a TextureLoader object using the specified BufferedImage + * and format + * @param bImage The BufferedImage used for loading the texture + * @param format The format specifies which channels to use + */ + public TextureLoader(BufferedImage bImage, String format) { + this(bImage, format, 0); + } + + /** + * Contructs a TextureLoader object using the specified BufferedImage, + * option flags and default format RGBA + * @param bImage The BufferedImage used for loading the texture + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + */ + public TextureLoader(BufferedImage bImage, int flags) { + this(bImage, new String("RGBA"), flags); + } + + /** + * Contructs a TextureLoader object using the specified BufferedImage, + * format and option flags + * @param bImage The BufferedImage used for loading the texture + * @param format The format specifies which channels to use + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + */ + public TextureLoader(BufferedImage bImage, String format, int flags) { + parseFormat(format); + this.flags = flags; + bufferedImage = bImage; + + if ((flags & BY_REFERENCE) != 0) { + byRef = true; + } + if ((flags & Y_UP) != 0) { + yUp = true; + } + } + + + /** + * Contructs a TextureLoader object using the specified Image + * and default format RGBA + * @param image The Image used for loading the texture + * @param observer The associated image observer + */ + public TextureLoader(Image image, Component observer) { + this(image, new String("RGBA"), 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified Image + * and format + * @param image The Image used for loading the texture + * @param format The format specifies which channels to use + * @param observer The associated image observer + */ + public TextureLoader(Image image, String format, Component observer) { + this(image, format, 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified Image + * flags and default format RGBA + * @param image The Image used for loading the texture + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(Image image, int flags, Component observer) { + this(image, new String("RGBA"), flags, observer); + } + + /** + * Contructs a TextureLoader object using the specified Image + * format and option flags + * @param image The Image used for loading the texture + * @param format The format specifies which channels to use + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(Image image, String format, int flags, + Component observer) { + + if (observer == null) { + observer = new java.awt.Container(); + } + + parseFormat(format); + this.flags = flags; + bufferedImage = createBufferedImage(image, observer); + + if ((flags & BY_REFERENCE) != 0) { + byRef = true; + } + if ((flags & Y_UP) != 0) { + yUp = true; + } + } + + + /** + * Contructs a TextureLoader object using the specified file + * and default format RGBA + * @param fname The file that specifies an Image to load the texture with + * @param observer The associated image observer + */ + public TextureLoader(String fname, Component observer) { + this(fname, new String("RGBA"), 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified file, + * and format + * @param fname The file that specifies an Image to load the texture with + * @param format The format specifies which channels to use + * @param observer The associated image observer + */ + public TextureLoader(String fname, String format, Component observer) { + this(fname, format, 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified file, + * option flags and default format RGBA + * @param fname The file that specifies an Image to load the texture with + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(String fname, int flags, Component observer) { + this(fname, new String("RGBA"), flags, observer); + } + + /** + * Contructs a TextureLoader object using the specified file, + * format and option flags + * @param fname The file that specifies an Image to load the texture with + * @param format The format specifies which channels to use + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(String fname, String format, int flags, + Component observer) { + + if (observer == null) { + observer = new java.awt.Container(); + } + + final Toolkit toolkit = Toolkit.getDefaultToolkit(); + final Image image[] = new Image[1]; + final String fn = fname; + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + image[0] = toolkit.createImage(fn); + return null; + } + } + ); + parseFormat(format); + this.flags = flags; + bufferedImage = createBufferedImage(image[0], observer); + + if (bufferedImage==null && JAIInstalled() ) { + parseFormat(format); + bufferedImage = JAIgetImage( fname, observer ); + } + + if (bufferedImage==null) + System.err.println("Error loading Image "+fname ); + + if ((flags & BY_REFERENCE) != 0) { + byRef = true; + } + if ((flags & Y_UP) != 0) { + yUp = true; + } + } + + /** + * Load the image using the JAI API + */ + private BufferedImage JAIgetImage( String filename, Component observer ) { + final String fn = filename; + BufferedImage bImage = null; + + try { + Object source = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + try { + Object renderedOp; + renderedOp = jaiCreate.invoke( null, new Object[] { "fileload", fn } ); + // Force JAI to actually load the image + // within this privileged action + jaiGetRendering.invoke( renderedOp, new Object[] {} ); + + return renderedOp; + } catch( Exception e ) { + e.printStackTrace(); + return null; + } + } + } + ); + + int width = ((Integer)jaiGetWidth.invoke( source, new Object[] {} )).intValue(); + int height = ((Integer)jaiGetHeight.invoke( source, new Object[] {} )).intValue(); + + + WritableRaster wr = java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height , width * 4, 4, bandOffset, null);; + bImage = new BufferedImage(colorModel, wr, false, null); + + java.awt.Graphics g = bImage.getGraphics(); + BufferedImage im = (BufferedImage)jaiGetAsBufferedImage.invoke( source, new Object[] {} ); + g.drawImage(im, 0, 0, observer ); + } catch( Exception e ) { + return null; + //System.out.println("Unable to load Texture "+filename); + } + + + return bImage; + } + + /** + * Load the image using the JAI API + */ + private BufferedImage JAIgetImage( URL url, Component observer ) { + final URL local_url = url; + BufferedImage bImage=null; + try { + Object source = java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + try { + Object renderedOp; + renderedOp = jaiCreate.invoke( null, new Object[] { "URL", local_url } ); + // Force JAI to actually load the image + // within this privileged action + jaiGetRendering.invoke( renderedOp, new Object[] {} ); + + return renderedOp; + } catch( Exception e ) { + e.printStackTrace(); + return null; + } + } + } + ); + + + int width = ((Integer)jaiGetWidth.invoke( source, new Object[] {} )).intValue(); + int height = ((Integer)jaiGetHeight.invoke( source, new Object[] {} )).intValue(); + + + WritableRaster wr = java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height , width * 4, 4, bandOffset, null);; + bImage = new BufferedImage(colorModel, wr, false, null); + + java.awt.Graphics g = bImage.getGraphics(); + BufferedImage im = (BufferedImage)jaiGetAsBufferedImage.invoke( source, new Object[] {} ); + g.drawImage(im, 0, 0, observer ); + } catch( Exception e ) { + return null; + //System.out.println("Unable to load Texture "+local_url.toString()); + } + + return bImage; + } + + /** + * Check if JAI is installed + * If it is installed created Methods for each method we need + * to call + */ + private boolean JAIInstalled() { + if (!doneJAICheck) { + try { + Class cl = Class.forName("javax.media.jai.PlanarImage"); + jaiGetWidth = cl.getDeclaredMethod( "getWidth", new Class[] {} ); + jaiGetHeight = cl.getDeclaredMethod( "getHeight", new Class[] {} ); + jaiGetAsBufferedImage = cl.getDeclaredMethod( "getAsBufferedImage", new Class[] {} ); + Class cl2 = Class.forName("javax.media.jai.JAI"); + jaiCreate = cl2.getDeclaredMethod( "create", new Class[] { String.class, Object.class } ); + Class cl3 = Class.forName("javax.media.jai.RenderedOp"); + jaiGetRendering = cl3.getDeclaredMethod("getRendering", new Class[] {} ); + + jaiInstalled = true; + } catch( ClassNotFoundException e ) { + jaiInstalled = false; + } catch( NoSuchMethodException ex ) { + ex.printStackTrace(); + jaiInstalled = false; + } + + doneJAICheck = true; + } + + return jaiInstalled; + } + + + /** + * Contructs a TextureLoader object using the specified URL + * and default format RGBA + * @param url The URL that specifies an Image to load the texture with + * @param observer The associated image observer + */ + public TextureLoader(URL url, Component observer) { + this(url, new String("RGBA"), 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified URL, + * and format + * @param url The URL that specifies an Image to load the texture with + * @param format The format specifies which channels to use + * @param observer The associated image observer + */ + public TextureLoader(URL url, String format, Component observer) { + this(url, format, 0, observer); + } + + /** + * Contructs a TextureLoader object using the specified URL, + * option flags and default format RGBA + * @param url The URL that specifies an Image to load the texture with + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(URL url, int flags, Component observer) { + this(url, new String("RGBA"), flags, observer); + } + /** + * Contructs a TextureLoader object using the specified URL, + * format and option flags + * @param url The url that specifies an Image to load the texture with + * @param format The format specifies which channels to use + * @param flags The flags specify what options to use in texture loading (generate mipmap etc) + * @param observer The associated image observer + */ + public TextureLoader(URL url, String format, int flags, + Component observer) { + + if (observer == null) { + observer = new java.awt.Container(); + } + + final Toolkit toolkit = Toolkit.getDefaultToolkit(); + final Image image[] = new Image[1]; + final URL Url = url; + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + image[0] = toolkit.getImage(Url); + return null; + } + } + ); + parseFormat(format); + this.flags = flags; + bufferedImage = createBufferedImage(image[0], observer); + + if (bufferedImage==null && JAIInstalled() ) { + bufferedImage = JAIgetImage( url, observer ); + } + + if (bufferedImage==null) + System.err.println("Error loading Image "+url.toString() ); + + if ((flags & BY_REFERENCE) != 0) { + byRef = true; + } + if ((flags & Y_UP) != 0) { + yUp = true; + } + } + + + /** + * Returns the associated ImageComponent2D object + * + * @return The associated ImageComponent2D object + */ + public ImageComponent2D getImage() { + + if (imageComponent == null) + imageComponent = new ImageComponent2D(imageComponentFormat, + bufferedImage, byRef, yUp); + return imageComponent; + } + + /** + * Returns the scaled ImageComponent2D object + * + * @param xScale The X scaling factor + * @param yScale The Y scaling factor + * + * @return The scaled ImageComponent2D object + */ + public ImageComponent2D getScaledImage(float xScale, float yScale) { + + if (xScale == 1.0f && yScale == 1.0f) + return getImage(); + else + return(new ImageComponent2D(imageComponentFormat, + getScaledImage(bufferedImage, + xScale, yScale), + byRef, yUp)); + } + + /** + * Returns the scaled ImageComponent2D object + * + * @param width The desired width + * @param height The desired height + * + * @return The scaled ImageComponent2D object + */ + public ImageComponent2D getScaledImage(int width, int height) { + + if (bufferedImage.getWidth() == width && + bufferedImage.getHeight() == height) + return getImage(); + else + return(new ImageComponent2D(imageComponentFormat, + getScaledImage(bufferedImage, + width, height), + byRef, yUp)); + } + + /** + * Returns the associated Texture object + * or null if the image failed to load + * + * @return The associated Texture object + */ + public Texture getTexture() { + + ImageComponent2D[] scaledImageComponents = null; + BufferedImage[] scaledBufferedImages = null; + if (tex == null) { + if (bufferedImage==null) return null; + + int width = getClosestPowerOf2(bufferedImage.getWidth()); + int height = getClosestPowerOf2(bufferedImage.getHeight()); + + if ((flags & GENERATE_MIPMAP) != 0) { + + BufferedImage origImage = bufferedImage; + int newW = width; + int newH = height; + int level = Math.max(computeLog(width), computeLog(height)) + 1; + scaledImageComponents = new ImageComponent2D[level]; + scaledBufferedImages = new BufferedImage[level]; + tex = new Texture2D(tex.MULTI_LEVEL_MIPMAP, textureFormat, + width, height); + + for (int i = 0; i < level; i++) { + scaledBufferedImages[i] = getScaledImage(origImage, newW, newH); + scaledImageComponents[i] = new ImageComponent2D( + imageComponentFormat, scaledBufferedImages[i], + byRef, yUp); + + tex.setImage(i, scaledImageComponents[i]); + if (newW > 1) newW >>= 1; + if (newH > 1) newH >>= 1; + origImage = scaledBufferedImages[i]; + } + + } else { + scaledImageComponents = new ImageComponent2D[1]; + scaledBufferedImages = new BufferedImage[1]; + + // Create texture from image + scaledBufferedImages[0] = getScaledImage(bufferedImage, + width, height); + scaledImageComponents[0] = new ImageComponent2D( + imageComponentFormat, scaledBufferedImages[0], + byRef, yUp); + + tex = new Texture2D(tex.BASE_LEVEL, textureFormat, width, height); + + tex.setImage(0, scaledImageComponents[0]); + } + tex.setMinFilter(tex.BASE_LEVEL_LINEAR); + tex.setMagFilter(tex.BASE_LEVEL_LINEAR); + } + + return tex; + } + + // create a BufferedImage from an Image object + private BufferedImage createBufferedImage(Image image, Component observer) { + + int status; + + observer.prepareImage(image, null); + while(true) { + status = observer.checkImage(image, null); + if ((status & ImageObserver.ERROR) != 0) { + return null; + } else if ((status & ImageObserver.ALLBITS) != 0) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) {} + } + + int width = image.getWidth(observer); + int height = image.getHeight(observer); + + WritableRaster wr = java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, height , width * 4, 4, bandOffset, null);; + BufferedImage bImage = new BufferedImage(colorModel, wr, false, null); + + java.awt.Graphics g = bImage.getGraphics(); + g.drawImage(image, 0, 0, observer); + + return bImage; + } + + // initialize appropriate format for ImageComponent and Texture + private void parseFormat(String format) { + if (format.equals("RGBA")) { + imageComponentFormat = ImageComponent.FORMAT_RGBA; + textureFormat = Texture.RGBA; + + } else if (format.equals("RGBA4")) { + imageComponentFormat = ImageComponent.FORMAT_RGBA4; + textureFormat = Texture.RGBA; + + } else if (format.equals("RGB5_A1")) { + imageComponentFormat = ImageComponent.FORMAT_RGB5_A1; + textureFormat = Texture.RGBA; + + } else if (format.equals("RGB")) { + imageComponentFormat = ImageComponent.FORMAT_RGB; + textureFormat = Texture.RGB; + + } else if (format.equals("RGB4")) { + imageComponentFormat = ImageComponent.FORMAT_RGB4; + textureFormat = Texture.RGB; + + } else if (format.equals("RGB5")) { + imageComponentFormat = ImageComponent.FORMAT_RGB5; + textureFormat = Texture.RGB; + + } else if (format.equals("R3_G3_B2")) { + imageComponentFormat = ImageComponent.FORMAT_R3_G3_B2; + textureFormat = Texture.RGB; + + } else if (format.equals("LUM8_ALPHA8")) { + imageComponentFormat = ImageComponent.FORMAT_LUM8_ALPHA8; + textureFormat = Texture.LUMINANCE_ALPHA; + + } else if (format.equals("LUM4_ALPHA4")) { + imageComponentFormat = ImageComponent.FORMAT_LUM4_ALPHA4; + textureFormat = Texture.LUMINANCE_ALPHA; + + } else if (format.equals("LUMINANCE")) { + imageComponentFormat = ImageComponent.FORMAT_CHANNEL8; + textureFormat = Texture.LUMINANCE; + + } else if (format.equals("ALPHA")) { + imageComponentFormat = ImageComponent.FORMAT_CHANNEL8; + textureFormat = Texture.ALPHA; + } + } + + // return a scaled image of given width and height + private BufferedImage getScaledImage(BufferedImage origImage, int width, + int height){ + + int origW = origImage.getWidth(); + int origH = origImage.getHeight(); + float xScale = (float)width/(float)origW; + float yScale = (float)height/(float)origH; + + return (getScaledImage(origImage, xScale, yScale)); + } + + // return a scaled image of given x and y scale + private BufferedImage getScaledImage(BufferedImage origImage, float xScale, + float yScale){ + + // If the image is already the requested size, no need to scale + if (xScale == 1.0f && yScale == 1.0f) + return origImage; + else { + + int scaleW = (int)(origImage.getWidth() * xScale + 0.5); + int scaleH = (int)(origImage.getHeight() * yScale + 0.5); + WritableRaster wr = java.awt.image.Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, scaleW, scaleH , scaleW * 4, 4, bandOffset, null);; + BufferedImage scaledImage = new BufferedImage(colorModel, wr, false, null); + + java.awt.Graphics2D g2 = scaledImage.createGraphics(); + AffineTransform at = AffineTransform.getScaleInstance(xScale, + yScale); + g2.transform(at); + g2.drawImage(origImage, 0, 0, null); + + return scaledImage; + } + } + + + private int computeLog(int value) { + int i = 0; + + if (value == 0) return -1; + for (;;) { + if (value == 1) + return i; + value >>= 1; + i++; + } + } + + private int getClosestPowerOf2(int value) { + + if (value < 1) + return value; + + int powerValue = 1; + for (;;) { + powerValue *= 2; + if (value < powerValue) { + // Found max bound of power, determine which is closest + int minBound = powerValue/2; + if ((powerValue - value) > + (value - minBound)) + return minBound; + else + return powerValue; + } + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/picking/PickCanvas.java b/src/classes/share/com/sun/j3d/utils/picking/PickCanvas.java new file mode 100644 index 0000000..9ec1183 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/PickCanvas.java @@ -0,0 +1,248 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking; + +import java.awt.event.*; +import javax.vecmath.*; +import javax.media.j3d.*; +import com.sun.j3d.utils.geometry.*; // Cone, Cylinder + +/** + * A subclass of PickTool, simplifies picking using mouse events from a canvas. + * This class allows picking using canvas x,y locations by generating the + * appropriate pick shape. + *

+ * The pick tolerance specifies the distance from the + * pick center to include in the pick shape. A tolerance of 0.0 may speedup + * picking slightly, but also make it very difficult to pick points and lines. + *

+ * The pick canvas can be used to make a series of picks. For example, to + * initialize the pick canvas: + *

+ *     PickCanvas pickCanvas = new PickCanvas(canvas, scene);
+ *     pickCanvas.setMode(PickTool.GEOMETRY_INTERSECT_INFO); 
+ *     pickCanvas.setTolerance(4.0f);
+ * 
+ *

+ * Then for each mouse event: + *

+ *     pickCanvas.setShapeLocation(mouseEvent);
+ *     PickResult[] results = pickCanvas.pickAll();
+ * 
+ *

+ * NOTE: For the pickAllSorted or pickClosest methods, the picks will be sorted + * by the distance from the ViewPlatform to the intersection point. + * @see PickTool + */ +public class PickCanvas extends PickTool { + + /* OPEN ISSUES: + -- Should restrict the pick shape to the front/back clip plane + */ + + + /** The canvas we are picking into */ + Canvas3D canvas; + + /* the pick tolerance, default to 2.0 */ + float tolerance = 2.0f; + int save_xpos; + int save_ypos; + + /** Constructor with Canvas3D for mouse events and BranchGroup to be picked. + */ + public PickCanvas (Canvas3D c, BranchGroup b) { + super (b); + canvas = c; + } + + /** Constructor with Canvas3D for mouse events and Locale to be picked. + */ + public PickCanvas (Canvas3D c, Locale l) { + super (l); + canvas = c; + } + + /** Inquire the canvas to be used for picking operations. + @return the canvas. + */ + public Canvas3D getCanvas() { + return canvas; + } + + /** Set the picking tolerance. Objects within this distance + * (in pixels) + * to the mouse x,y location will be picked. The default tolerance is 2.0. + * @param t The tolerance + * @exception IllegalArgumentException if the tolerance is less than 0. + */ + public void setTolerance(float t) { + if (t < 0.0f) { + throw new IllegalArgumentException(); + } + tolerance = t; + + if ((pickShape != null) && (!userDefineShape)) { + // reset pickShape + pickShape = null; + setShapeLocation(save_xpos, save_ypos); + } + } + + /** Get the pick tolerance. + */ + public float getTolerance() { + return tolerance; + } + + /** Set the pick location. Defines the location on the canvas where the + pick is to be performed. + @param mevent The MouseEvent for the picking point + */ + public void setShapeLocation(MouseEvent mevent) { + setShapeLocation(mevent.getX(), mevent.getY()); + } + /** Set the pick location. Defines the location on the canvas where the + pick is to be performed (upper left corner of canvas is 0,0). + @param xpos the X position of the picking point + @param ypos the Y position of the picking point + */ + public void setShapeLocation (int xpos, int ypos) { + Transform3D motion = new Transform3D(); + Point3d eyePosn = new Point3d(); + Point3d mousePosn = new Point3d(); + Vector3d mouseVec = new Vector3d(); + boolean isParallel = false; + double radius = 0.0; + double spreadAngle = 0.0; + + this.save_xpos = xpos; + this.save_ypos = ypos; + canvas.getCenterEyeInImagePlate(eyePosn); + canvas.getPixelLocationInImagePlate(xpos,ypos,mousePosn); + + if ((canvas.getView() != null) && + (canvas.getView().getProjectionPolicy() == + View.PARALLEL_PROJECTION)) { + // Correct for the parallel projection: keep the eye's z + // coordinate, but make x,y be the same as the mouse, this + // simulates the eye being at "infinity" + eyePosn.x = mousePosn.x; + eyePosn.y = mousePosn.y; + isParallel = true; + } + + // Calculate radius for PickCylinderRay and spread angle for PickConeRay + Vector3d eyeToCanvas = new Vector3d(); + eyeToCanvas.sub (mousePosn, eyePosn); + double distanceEyeToCanvas = eyeToCanvas.length(); + + Point3d deltaImgPlate = new Point3d(); + canvas.getPixelLocationInImagePlate (xpos+1, ypos, deltaImgPlate); + + Vector3d ptToDelta = new Vector3d(); + ptToDelta.sub (mousePosn, deltaImgPlate); + double distancePtToDelta = ptToDelta.length(); + distancePtToDelta *= tolerance; + + canvas.getImagePlateToVworld(motion); + + /* + System.out.println("mouse position " + xpos + " " + ypos); + System.out.println("before, mouse " + mousePosn + " eye " + eyePosn); + */ + + motion.transform(eyePosn); + start = new Point3d (eyePosn); // store the eye position + motion.transform(mousePosn); + mouseVec.sub(mousePosn, eyePosn); + mouseVec.normalize(); + + /* + System.out.println(motion + "\n"); + System.out.println("after, mouse " + mousePosn + " eye " + eyePosn + + " mouseVec " + mouseVec); + */ + + if (tolerance == 0.0) { + if ((pickShape != null) && (pickShape instanceof PickRay)) { + ((PickRay)pickShape).set (eyePosn, mouseVec); + } else { + pickShape = (PickShape) new PickRay (eyePosn, mouseVec); + } + // pickShape = (PickShape) new PickConeRay (eyePosn, + // mouseVec,1.0*Math.PI/180.0); + } else { + if (isParallel) { + // Parallel projection, use a PickCylinderRay + distancePtToDelta *= motion.getScale(); + if ((pickShape != null) && + (pickShape instanceof PickCylinderRay)) { + ((PickCylinderRay)pickShape).set (eyePosn, mouseVec, + distancePtToDelta); + } else { + pickShape = (PickShape) new PickCylinderRay (eyePosn, + mouseVec, distancePtToDelta); + } + } else { + // Perspective projection, use a PickConeRay + + // Calculate spread angle + spreadAngle = Math.atan (distancePtToDelta/distanceEyeToCanvas); + + if ((pickShape != null) && + (pickShape instanceof PickConeRay)) { + ((PickConeRay)pickShape).set (eyePosn, mouseVec, + spreadAngle); + } else { + pickShape = (PickShape) new PickConeRay (eyePosn, mouseVec, + spreadAngle); + } + } + } + } +} // PickCanvas + + diff --git a/src/classes/share/com/sun/j3d/utils/picking/PickIntersection.java b/src/classes/share/com/sun/j3d/utils/picking/PickIntersection.java new file mode 100644 index 0000000..5aaa229 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/PickIntersection.java @@ -0,0 +1,1570 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking; + +import javax.vecmath.*; +import javax.media.j3d.*; +import com.sun.j3d.utils.geometry.Primitive; + +/** + * Holds information about an intersection of a PickShape with a Node + * as part of a PickResult. Information about + * the intersected geometry, intersected primitive, intersection point, and + * closest vertex can be inquired. + *

+ * The intersected geometry is indicated by an index into the list of + * geometry arrays on the PickResult. It can also be inquired from this + * object. + *

+ * The intersected primitive indicates which primitive out of the GeometryArray + * was intersected (where the primitive is a point, line, triangle or quad, + * not a + * com.sun.j3d.utils.geometry.Primitive). + * For example, the intersection would indicate which triangle out of a + * triangle strip was intersected. + * The methods which return primitive data will have one value if the primitive + * is + * a point, two values if the primitive is a line, three values if the primitive + * is a triangle and four values if the primitive is quad. + *

+ * The primitive's VWorld coordinates are saved when then intersection is + * calculated. The local coordinates, normal, color and texture coordinates + * for the primitive can also be inquired if they are present and readable. + *

+ * The intersection point is the location on the primitive which intersects the + * pick shape closest to the center of the pick shape. The intersection point's + * location in VWorld coordinates is saved when the intersection is calculated. + * The local coordinates, normal, color and texture coordiantes of at the + * intersection can be interpolated if they are present and readable. + *

+ * The closest vertex is the vertex of the primitive closest to the intersection + * point. The vertex index, VWorld coordinates and local coordinates of the + * closest vertex can be inquired. The normal, color and texture coordinate + * of the closest vertex can be inquired from the geometry array: + *

+ *      Vector3f getNormal(PickIntersection pi, int vertexIndex) {
+ *          int index;
+ *          Vector3d normal = new Vector3f();
+ *          GeometryArray ga = pickIntersection.getGeometryArray();
+ *          if (pickIntersection.geometryIsIndexed()) {
+ *              index = ga.getNormalIndex(vertexIndex);
+ *          } else {
+ *              index = vertexIndex;
+ *          }
+ *          ga.getNormal(index, normal);
+ *          return normal;
+ *      }
+ * 
+ *

+ * The color, normal + * and texture coordinate information for the intersected primitive and the + * intersection point + * can be inquired + * the geometry includes them and the corresponding READ capibility bits are + * set. + * + * PickTool.setCapabilties(Node, int) + * can be used to set the capability bits + * to allow this data to be inquired. + */ +public class PickIntersection { + + /* OPEN ISSUES: + -- Tex coordinates always use texCoordSet == 0. + */ + + /* =================== ATTRIBUTES ======================= */ + + + // init by constructor: + + /** PickResult for intersection is part of */ + PickResult pickResult = null; + + // init by intersection: + /** Distance between start point and intersection point (see comment above)*/ + double distance = -1; + + /** index of GeometryArray in PickResult */ + int geomIndex = 0; + + /** Indices of the intersected primitive */ + int[] primitiveVertexIndices = null; + + /** VWorld coordinates of intersected primitive */ + Point3d[] primitiveCoordinatesVW = null; + + /** VWorld Coordinates of the intersection point */ + Point3d pointCoordinatesVW = null; + + // Derived data + + // Geometry + GeometryArray geom = null; + IndexedGeometryArray iGeom = null; + boolean hasNormals = false; + boolean hasColors = false; + boolean hasTexCoords = false; + + // Primitive + /* indices for the different data types */ + int[] primitiveCoordinateIndices; + int[] primitiveNormalIndices; + int[] primitiveColorIndices; + int[] primitiveTexCoordIndices; + + + /* Local coordinates of the intersected primitive */ + Point3d[] primitiveCoordinates = null; + + /* Normals of the intersected primitive */ + Vector3f[] primitiveNormals = null; + + /* Colors of the intersected primitive */ + Color4f[] primitiveColors = null; + + /* TextureCoordinates of the intersected primitive */ + TexCoord3f[] primitiveTexCoords = null; + + // Intersection point + + /** Local Coordinates of the intersection point */ + Point3d pointCoordinates = null; + + /** Normal at the intersection point */ + Vector3f pointNormal = null; + + /** Color at the intersection point */ + Color4f pointColor = null; + + /** TexCoord at the intersection point */ + TexCoord3f pointTexCoord = null; + + // Closest Vertex + /** Index of the closest vertex */ + int closestVertexIndex = -1; + + /** Coordinates of the closest vertex */ + Point3d closestVertexCoordinates = null; + + /** Coordinates of the closest vertex (World coordinates) */ + Point3d closestVertexCoordinatesVW = null; + + /** Weight factors for interpolation, values correspond to vertex indices, + * sum == 1 + */ + double[] interpWeights; + + static final boolean debug = false; + + // Axis constants + static final int X_AXIS = 1; + static final int Y_AXIS = 2; + static final int Z_AXIS = 3; + + // Tolerance for numerical stability + static final double TOL = 1.0e-5; + + /* =================== METHODS ======================= */ + + /** Constructor + @param pickResult The pickResult this intersection is part of. + */ + PickIntersection (PickResult pr, GeometryArray geomArr) { + + // pr can't be null. + pickResult = pr; + geom = geomArr; + if (geom == null) { + GeometryArray[] ga = pickResult.getGeometryArrays(); + geom = ga[geomIndex]; + + } + + if (geom instanceof IndexedGeometryArray) { + iGeom = (IndexedGeometryArray)geom; + } + int vertexFormat = geom.getVertexFormat(); + hasColors = (0 != (vertexFormat & + (GeometryArray.COLOR_3 | GeometryArray.COLOR_4))); + hasNormals = (0 != (vertexFormat & GeometryArray.NORMALS)); + hasTexCoords = (0 != (vertexFormat & + (GeometryArray.TEXTURE_COORDINATE_2 | + GeometryArray.TEXTURE_COORDINATE_3))); + } + + /** + String representation of this object + */ + public String toString () { + String rt = new String ("PickIntersection: "); + rt += " pickResult = "+pickResult + "\n"; + rt += " geomIndex = "+geomIndex + "\n"; + if (distance != -1) rt += " dist:"+distance + "\n"; + if (pointCoordinates != null) rt += " pt:" + pointCoordinates + "\n"; + if (pointCoordinatesVW != null) rt += " ptVW:" + pointCoordinatesVW + "\n"; + + if (primitiveCoordinateIndices != null) { + rt += " prim coordinate ind:" + "\n"; + for (int i=0;i2, etc*/ + factor[0] = + getInterpFactorForBase(intPt, coords[index1], coords[index2], mainAxis); + factor[1] = + getInterpFactorForBase(intPt, coords[index2], coords[index0], mainAxis); + factor[2] = + getInterpFactorForBase(intPt, coords[index0], coords[index1], mainAxis); + + if (debug) { + System.out.println("intPt = " + intPt); + switch(mainAxis) { + case X_AXIS: + System.out.println("mainAxis = X_AXIS"); + break; + case Y_AXIS: + System.out.println("mainAxis = Y_AXIS"); + break; + case Z_AXIS: + System.out.println("mainAxis = Z_AXIS"); + break; + } + System.out.println("factor[0] = " + factor[0]); + System.out.println("factor[1] = " + factor[1]); + System.out.println("factor[2] = " + factor[2]); + } + + /* Find the factor that is out of range, it will tell us which + * vertex to use for base + */ + int base, left, right; + double leftFactor, rightFactor; + if ((factor[0] < 0.0) || (factor[0] > 1.0)) { + base = index0; + right = index1; + left = index2; + rightFactor = factor[2]; + leftFactor = 1.0 - factor[1]; + if (debug) { + System.out.println("base 0, rightFactor = " + rightFactor + + " leftFactor = " + leftFactor); + } + } else if ((factor[1] < 0.0) || (factor[1] > 1.0)) { + base = index1; + right = index2; + left = index0; + rightFactor = factor[0]; + leftFactor = 1.0 - factor[2]; + if (debug) { + System.out.println("base 1, rightFactor = " + rightFactor + + " leftFactor = " + leftFactor); + } + } else { + base = index2; + right = index0; + left = index1; + rightFactor = factor[1]; + leftFactor = 1.0 - factor[0]; + if (debug) { + System.out.println("base 2, rightFactor = " + rightFactor + + " leftFactor = " + leftFactor); + } + } + if (debug) { + System.out.println("base = " + coords[base]); + System.out.println("left = " + coords[left]); + System.out.println("right = " + coords[right]); + } + /* find iLeft and iRight */ + Point3d iLeft = new Point3d(leftFactor * coords[left].x + + (1.0-leftFactor)*coords[base].x, + leftFactor * coords[left].y + + (1.0-leftFactor)*coords[base].y, + leftFactor * coords[left].z + + (1.0-leftFactor)*coords[base].z); + + Point3d iRight = new Point3d(rightFactor * coords[right].x + + (1.0-rightFactor)*coords[base].x, + rightFactor * coords[right].y + + (1.0-rightFactor)*coords[base].y, + rightFactor * coords[right].z + + (1.0-rightFactor)*coords[base].z); + + if (debug) { + System.out.println("iLeft = " + iLeft); + System.out.println("iRight = " + iRight); + } + + /* now find an axis and solve for midFactor */ + delta0.sub(iLeft, iRight); + int midAxis = maxAxis(delta0); + double midFactor = getInterpFactor(intPt, iRight, iLeft, midAxis); + + if (debug) { + switch(midAxis) { + case X_AXIS: + System.out.println("midAxis = X_AXIS"); + break; + case Y_AXIS: + System.out.println("midAxis = Y_AXIS"); + break; + case Z_AXIS: + System.out.println("midAxis = Z_AXIS"); + break; + } + System.out.println("midFactor = " + midFactor); + } + + if (midFactor < 0.0) { + // System.out.println("midFactor = " + midFactor); + if ((midFactor + TOL) >= 0.0) { + // System.out.println("In Tol case : midFactor = " + midFactor); + midFactor = 0.0; + } + else { + /* int point is outside triangle */ + return false; + } + } + else if (midFactor > 1.0) { + // System.out.println("midFactor = " + midFactor); + if ((midFactor-TOL) <= 1.0) { + // System.out.println("In Tol case : midFactor = " + midFactor); + midFactor = 1.0; + } + else { + /* int point is outside triangle */ + return false; + } + } + + // Assign the weights + interpWeights[base] = 1.0 - midFactor * leftFactor - + rightFactor + midFactor * rightFactor; + interpWeights[left] = midFactor * leftFactor; + interpWeights[right] = rightFactor - midFactor * rightFactor; + return true; + + } + + /* Get the interpolation weights for each of the verticies of the + * primitive. + */ + double[] getInterpWeights() { + + Point3d pt = getPointCoordinatesVW(); + Point3d[] coordinates = getPrimitiveCoordinatesVW(); + double factor; + int axis; + + if (interpWeights != null) { + return interpWeights; + } + + interpWeights = new double[coordinates.length]; + + // Interpolate + switch (coordinates.length) { + case 1: + // Nothing to interpolate + interpWeights[0] = 1.0; + break; + case 2: // edge + Vector3d delta = new Vector3d(); + delta.sub (coordinates[1], coordinates[0]); + axis = maxAxis(delta); + factor = getInterpFactor (pt, coordinates[1], coordinates[0], axis); + interpWeights[0] = factor; + interpWeights[1] = 1.0 - factor; + break; + case 3: // triangle + if (!interpTriangle(0, 1, 2, coordinates, pt)) { + throw new RuntimeException ("Interp point outside triangle"); + } + break; + case 4: // quad + if (!interpTriangle(0, 1, 2, coordinates, pt)) { + if (!interpTriangle(0, 2, 3, coordinates, pt)) { + throw new RuntimeException ("Interp point outside quad"); + } + } + break; + default: + throw new RuntimeException ("Unexpected number of points."); + } + return interpWeights; + } + + /** + Calculate the interpolation factor for point p by projecting it along + an axis (x,y,z) onto the edge between p1 and p2. If the result is + in the 0->1 range, point is between p1 and p2 (0 = point is at p1, + 1 => point is at p2). + */ + private static float getInterpFactor (Point3d p, Point3d p1, Point3d p2, + int axis) { + float t; + switch (axis) { + case X_AXIS: + if (p1.x == p2.x) + //t = Float.MAX_VALUE; // TODO: should be 0? + t = 0.0f; + else + t = (float) ((p1.x - p.x) / (p1.x - p2.x)); + break; + case Y_AXIS: + if (p1.y == p2.y) + // t = Float.MAX_VALUE; + t = 0.0f; + else + t = (float) ((p1.y - p.y) / (p1.y - p2.y)); + break; + case Z_AXIS: + if (p1.z == p2.z) + // t = Float.MAX_VALUE; + t = 0.0f; + else + t = (float)((p1.z - p.z) / (p1.z - p2.z)); + break; + default: + throw new RuntimeException ("invalid axis parameter "+axis+" (must be 0-2)"); + } + return t; + } + + /** + Calculate the interpolation factor for point p by projecting it along + an axis (x,y,z) onto the edge between p1 and p2. If the result is + in the 0->1 range, point is between p1 and p2 (0 = point is at p1, + 1 => point is at p2). + return MAX_VALUE if component of vertices are the same. + */ + private static float getInterpFactorForBase (Point3d p, Point3d p1, Point3d p2, + int axis) { + float t; + switch (axis) { + case X_AXIS: + if (p1.x == p2.x) + t = Float.MAX_VALUE; + else + t = (float) ((p1.x - p.x) / (p1.x - p2.x)); + break; + case Y_AXIS: + if (p1.y == p2.y) + t = Float.MAX_VALUE; + else + t = (float) ((p1.y - p.y) / (p1.y - p2.y)); + break; + case Z_AXIS: + if (p1.z == p2.z) + t = Float.MAX_VALUE; + else + t = (float)((p1.z - p.z) / (p1.z - p2.z)); + break; + default: + throw new RuntimeException ("invalid axis parameter "+axis+" (must be 0-2)"); + } + return t; + } + + +} // PickIntersection + + + + + + + + + diff --git a/src/classes/share/com/sun/j3d/utils/picking/PickResult.java b/src/classes/share/com/sun/j3d/utils/picking/PickResult.java new file mode 100644 index 0000000..e9f3adf --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/PickResult.java @@ -0,0 +1,3344 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking; + +import javax.vecmath.*; +import javax.media.j3d.*; +import java.util.ArrayList; +import com.sun.j3d.utils.geometry.Primitive; +import com.sun.j3d.internal.*; + +/** + * Stores information about a pick hit. + * Detailed information about the pick and each intersection of the PickShape + * with the picked Node can be inquired. The PickResult is constructed with + * basic information and more detailed information is generated as needed. The + * additional information is only available if capability bits on the scene + * graph Nodes are set properly; + * + * PickTool.setCapabilties(Node, int) + * can + * be used to ensure correct capabilites are set. Inquiring data which is not + * available due to capabilties not being set will generate a + * CapabilityNotSet exception. + *

+ * A PickResult can be used to calculate intersections on Node which is not part + * of a live scene graph using the constructor which takes a local to VWorld + * transformation for the Node. + *

+ * Pick hits on TriangleStrip primitives will store the triangle points in the + * PickIntersection with + * the verticies in counter-clockwise order. For triangles which start with + * an odd numbered vertex this will be the the opposite of the + * order of the points in the TriangleStrip. + * This way the triangle in + * the PickIntersection will display the same was as the triangle in the + * strip. + *

+ * If the Shape3D being picked has multiple geometry arrays, the arrays are + * stored in the PickResult and referred to by a geometry index. + *

+ * If the Shape3D refers to a CompressedGeometry, the geometry is decompressed + * into an array of Shape3D nodes which can be inquired. The geometry + * NodeComponents for the Shape3D nodes are stored and used as if the Shape3D + * had multiple geometries. If there are multiple CompressedGeometries on the + * Shape3D, the decompressed Shape3Ds and GeometryArrays will be stored + * sequentially. + *

+ * The intersection point for Morph nodes cannot be calculated using the + * displayed geometry + * due to limitations in the current Java3D core API (the current + * geometry of the the Morph cannot be inquired). Instead + * the geometry at index 0 in the Morph is used. This limitation may + * be eliminated in a future release of Java3D. + */ +public class PickResult { + + /* OPEN ISSUES: + -- getInterpolatedTextureCoordinates uses the depricated API faor + getTextureCoordinate(), need to update. + -- Bounds tests don't fill in any picking info. + -- Can't do any intersections with the PickPoint shape. + */ + + + // Externally used constants + + /** + * Flag to pass to + * getNode(int) + * to return a + * Shape3D node from + * the SceneGraphPath. + */ + public static final int SHAPE3D = 0x1; + + /** + * Flag to pass to + * getNode(int) + * to return a + * Morph node from + * the SceneGraphPath. + */ + public static final int MORPH = 0x2; + + /** + * Flag to pass to + * getNode(int) + + * to return a + * Primitive node from + * the SceneGraphPath. + */ + public static final int PRIMITIVE = 0x4; + + /** + * Flag to pass to + * getNode(int) + * to return a + * Link node from + * the SceneGraphPath. + */ + public static final int LINK = 0x8; + + /** + * Flag to pass to + * getNode(int) + * to return a + * Group node from + * the SceneGraphPath. + */ + public static final int GROUP = 0x10; + + /** + * Flag to pass to + * getNode(int) + * to return a + * TransformGroup node from + * the SceneGraphPath. + */ + public static final int TRANSFORM_GROUP = 0x20; + + /** + * Flag to pass to + * getNode(int) + * to return a + * BranchGroup node from + * the SceneGraphPath. + */ + public static final int BRANCH_GROUP = 0x40; + + /** + * Flag to pass to + * getNode(int) + * to return a + * Switch node from + * the SceneGraphPath. + */ + public static final int SWITCH = 0x80; + + + + /* =================== ATTRIBUTES ======================= */ + static boolean debug = false; + + /** if true, find only the first intersection */ + boolean firstIntersectOnly = false; + + /** Stored SceneGraphPath */ + SceneGraphPath pickedSceneGraphPath = null; + + /** Picked node: shape3d, text3d, etc. */ + Node pickedNode = null; + + /** GeometryArray(s) of the picked node */ + GeometryArray[] geometryArrays = null; + + /** Shape3Ds from CompressedGeometry on the picked node */ + Shape3D[] compressGeomShape3Ds = null; + + /** Transform to World Coordinates */ + Transform3D localToVWorld = null; + + /** the pick shape to use for intersections */ + PickShape pickShape = null; + /* data derived from the pick shape */ + int pickShapeType = -1; + Vector3d pickShapeDir = null; + Point3d pickShapeStart = null; + Point3d pickShapeEnd = null; + Bounds pickShapeBounds = null; + + static final Point3d zeroPnt = new Point3d(); + + /** ArrayList to store intersection results */ + ArrayList intersections = null; + + // internal constants used for intersections + static final double FUZZ = 1E-6; /* fuzziness factor used to determine + if two lines are parallel */ + static final int PICK_SHAPE_RAY = 1; + static final int PICK_SHAPE_SEGMENT = 2; + static final int PICK_SHAPE_POINT = 3; + static final int PICK_SHAPE_BOUNDING_BOX = 4; + static final int PICK_SHAPE_BOUNDING_SPHERE = 5; + static final int PICK_SHAPE_BOUNDING_POLYTOPE = 6; + static final int PICK_SHAPE_CYLINDER = 7; + static final int PICK_SHAPE_CONE = 8; + + static final double EPS = 1.0e-13; + + + /* =================== METHODS ======================= */ + + /** Default constructor. */ + PickResult () { } + + /** Construct a PickResult using a SceneGraphPath + @param sgp SceneGraphPath associated with this PickResult + @param ps The pickShape to intersect against + */ + public PickResult (SceneGraphPath sgp, PickShape ps) { + pickedSceneGraphPath = sgp; + pickedNode = sgp.getObject(); + localToVWorld = sgp.getTransform(); + pickShape = ps; + initPickShape(); + } + + + /** Construct a PickResult using the Node and localToVWorld transform + @param pn The picked node. + @param l2vw The local to VWorld transformation for the node + @param ps The PickShape to intersect against + @throws IllegalArgumentException If the node is not a Morph or Shape3D. + */ + public PickResult (Node pn, Transform3D l2vw, PickShape ps) { + if ((pn instanceof Shape3D) || (pn instanceof Morph)) { + pickedNode = pn; + localToVWorld = l2vw; + pickShape = ps; + initPickShape(); + } else { + throw new IllegalArgumentException(); + } + } + + // similar to the constructor, resets the data in PickResult so it can be + // reused via a freelist + void reset(SceneGraphPath sgp, PickShape ps) { + firstIntersectOnly = false; + geometryArrays = null; + compressGeomShape3Ds = null; + pickShapeBounds = null; + intersections = null; + pickedSceneGraphPath = sgp; + pickedNode = sgp.getObject(); + localToVWorld = sgp.getTransform(); + pickShape = ps; + initPickShape(); + } + + // similar to the constructor, resets the data in PickResult so + // it can be reused via a freelist + void reset(Node pn, Transform3D l2vw, PickShape ps) { + if ((pn instanceof Shape3D) || (pn instanceof Morph)) { + firstIntersectOnly = false; + geometryArrays = null; + compressGeomShape3Ds = null; + pickShapeBounds = null; + intersections = null; + pickedSceneGraphPath = null; + pickedNode = pn; + localToVWorld = l2vw; + pickShape = ps; + initPickShape(); + } + else { + throw new IllegalArgumentException(); + } + } + + void initPickShape() { + if(pickShape instanceof PickRay) { + if (pickShapeStart == null) pickShapeStart = new Point3d(); + if (pickShapeDir == null) pickShapeDir = new Vector3d(); + ((PickRay) pickShape).get (pickShapeStart, pickShapeDir); + pickShapeType = PICK_SHAPE_RAY; + } else if (pickShape instanceof PickSegment) { + if (pickShapeStart == null) pickShapeStart = new Point3d(); + if (pickShapeEnd == null) pickShapeEnd = new Point3d(); + if (pickShapeDir == null) pickShapeDir = new Vector3d(); + ((PickSegment)pickShape).get(pickShapeStart, pickShapeEnd); + pickShapeDir.set (pickShapeEnd.x - pickShapeStart.x, + pickShapeEnd.y - pickShapeStart.y, + pickShapeEnd.z - pickShapeStart.z); + pickShapeType = PICK_SHAPE_SEGMENT; + } else if (pickShape instanceof PickBounds) { + pickShapeBounds = ((PickBounds) pickShape).get(); + if ( pickShapeBounds instanceof BoundingBox ) + pickShapeType = PICK_SHAPE_BOUNDING_BOX; + else if( pickShapeBounds instanceof BoundingSphere ) + pickShapeType = PICK_SHAPE_BOUNDING_SPHERE; + else if( pickShapeBounds instanceof BoundingPolytope ) + pickShapeType = PICK_SHAPE_BOUNDING_POLYTOPE; + } else if(pickShape instanceof PickPoint) { + throw new RuntimeException ("PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance."); + } else if (pickShape instanceof PickCylinder) { + pickShapeType = PICK_SHAPE_CYLINDER; + } else if (pickShape instanceof PickCone) { + pickShapeType = PICK_SHAPE_CONE; + } else { + throw new + RuntimeException("PickShape not supported for intersection"); + } + } + + /** Get the SceneGraphPath. This will be null if the non SceneGraphPath + * constructor was used. + */ + public SceneGraphPath getSceneGraphPath() { + /* Q: should this return a copy */ + return pickedSceneGraphPath; + } + + + /** Get the localToVworld transform for the Node + */ + public Transform3D getLocalToVworld() { + return localToVWorld; + } + + /** Get the GeometryArray at index 0 for the picked node + */ + public GeometryArray getGeometryArray() { + if (geometryArrays == null) { + storeGeometry(); + } + return geometryArrays[0]; + } + + /** Get the array of GeometryArrays for the picked node + */ + public GeometryArray[] getGeometryArrays() { + if (geometryArrays == null) { + storeGeometry(); + } + return geometryArrays; + } + + /** Get the number of GeometryArrays for the picked node + */ + public int numGeometryArrays() { + if (geometryArrays == null) { + storeGeometry(); + } + return geometryArrays.length; + } + + /** Get the number of Shape3Ds that came from decompressing a + CompressedGeometry on the picked node. + */ + public int numCompressedGeometryShape3Ds() { + if (geometryArrays == null) { + storeGeometry(); + } + if (compressGeomShape3Ds == null) { + return 0; + } else { + return compressGeomShape3Ds.length; + } + } + + /** Get the array of Shape3Ds that came from decompressing a + CompressedGeometry on the picked node. + */ + public Shape3D[] getCompressedGeometryShape3Ds() { + if (geometryArrays == null) { + storeGeometry(); + } + if (compressGeomShape3Ds == null) { + return null; + } else { + return compressGeomShape3Ds; + } + } + + /** Get the PickShape used for intersections + */ + public PickShape getPickShape() { + return pickShape; + } + + /** Set the PickResult to find only the first intersection of the PickShape + * with the Node. The default is false (all intersections are + * found) + */ + public void setFirstIntersectOnly(boolean flag) { + firstIntersectOnly = flag; + } + + /** Return the "first intersection only" value. */ + public boolean getFirstPickEnable() { + return firstIntersectOnly; + } + + /** Returns the number of PickIntersections in the PickResult. + @return the number of intersections + */ + public int numIntersections () { + if (intersections == null) { + generateIntersections(); + } + return intersections.size(); + } + + /** Returns a specific PickIntersection object + @param index the index number + @return the PickIntersection referenced by the index number + */ + public PickIntersection getIntersection (int index) { + if (intersections == null) { + generateIntersections(); + } + return (PickIntersection) intersections.get (index); + } + + /** Gets the PickIntersection in this PickResult that is closest to a point + @param pt the point to use for distance calculations + @return the closest PickIntersection object + */ + public PickIntersection getClosestIntersection (Point3d pt) { + PickIntersection pi = null; + PickIntersection curPi = null; + Point3d curPt = null; + double minDist = Double.MAX_VALUE; + double curDist = 0.0; + + if (pt == null) return null; + + if (intersections == null) { + generateIntersections(); + } + + for (int i=0;i 0) { + for (int i=0;i=0; j--){ + Node pNode = pickedSceneGraphPath.getNode(j); + if (debug) System.out.println("looking at node " + pNode); + + if ((pNode instanceof Primitive) && + ((flags & PRIMITIVE) != 0)){ + if (debug) System.out.println("Primitive found"); + return pNode; + } + else if ((pNode instanceof Link) && ((flags & LINK) != 0)){ + if (debug) System.out.println("Link found"); + return pNode; + } + else if ((pNode instanceof Switch) && ((flags & SWITCH) != 0)){ + if (debug) System.out.println("Switch found"); + return pNode; + } + else if ((pNode instanceof TransformGroup) && + ((flags & TRANSFORM_GROUP) != 0)){ + if (debug) System.out.println("xform group found"); + return pNode; + } + else if ((pNode instanceof BranchGroup) && + ((flags & BRANCH_GROUP) != 0)){ + if (debug) System.out.println("Branch group found"); + return pNode; + } + else if ((pNode instanceof Group) && ((flags & GROUP) != 0)){ + if (debug) System.out.println("Group found"); + return pNode; + } + } + } + return null; // should not be reached + } + + /** Extract the picked node from the SceneGraphPath */ + void storeNode () { + if (pickedSceneGraphPath == null) { + throw new RuntimeException ("SceneGraphPath missing"); + } + pickedNode = pickedSceneGraphPath.getObject(); + } + + /** Fill in the intersections of the Node with the PickShape */ + boolean generateIntersections() { + if (geometryArrays == null) { + storeGeometry(); + } + intersections = new ArrayList(); + int hits = 0; + + for (int i = 0; i < geometryArrays.length; i++) { + if (intersect(i, firstIntersectOnly)) { + if (firstIntersectOnly) { + return true; + } else { + hits++; + } + } + } + return (hits > 0); + } + + + + /* Takes a GeometryArray object, determines what actual type + * it is (RTTI) and casts it to call the appropriate intersect method. + */ + final boolean intersect(int geomIndex, boolean firstpick) { + int offset; + GeometryArray geom = geometryArrays[geomIndex]; + int numPts = geom.getVertexCount(); + double[] doubleData = null; + float[] floatData = null; + Point3d[] p3dData = null; + Point3f[] p3fData = null; + int vformat = geom.getVertexFormat(); + int stride; + boolean retFlag = false; + + if ((vformat & GeometryArray.BY_REFERENCE) == 0) { + doubleData = new double [numPts * 3]; + geom.getCoordinates (0, doubleData); + } + else { + if ((vformat & GeometryArray.INTERLEAVED) == 0) { + doubleData = geom.getCoordRefDouble(); + // If data was set as float then .. + if (doubleData == null) { + floatData = geom.getCoordRefFloat(); + if (floatData == null) { + p3fData = geom.getCoordRef3f(); + if (p3fData == null) { + p3dData = geom.getCoordRef3d(); + } + } + } + } + else { + floatData = geom.getInterleavedVertices(); + } + } + + + Point3d[] pnts = new Point3d[numPts]; + + /* + System.out.println("geomIndex : " + geomIndex); + System.out.println("numPts : " + numPts); + System.out.println("firstpick : " + firstpick); + System.out.println("localToVWorld : "); + System.out.println(localToVWorld); + */ + + if (debug) { + System.out.println("localToVWorld = " + localToVWorld); + } + if ((vformat & GeometryArray.INTERLEAVED) == 0) { + if (doubleData != null) { + offset = 0; + for (int i=0; i < numPts; i++) { + + // Need to transform each pnt by localToVWorld. + pnts[i] = getPoint3d(); + pnts[i].x = doubleData[offset++]; + pnts[i].y = doubleData[offset++]; + pnts[i].z = doubleData[offset++]; + + localToVWorld.transform(pnts[i]); + } + } + else if (floatData != null) { // by reference and float data is defined .. + offset = 0; + for (int i=0; i < numPts; i++) { + + // Need to transform each pnt by localToVWorld. + pnts[i] = getPoint3d(); + pnts[i].x = floatData[offset++]; + pnts[i].y = floatData[offset++]; + pnts[i].z = floatData[offset++]; + + localToVWorld.transform(pnts[i]); + } + } + else if (p3fData != null) { + for (int i=0; i < numPts; i++) { + + // Need to transform each pnt by localToVWorld. + pnts[i] = getPoint3d(); + pnts[i].set(p3fData[i]); + localToVWorld.transform(pnts[i]); + } + } + else { // p3dData + for (int i=0; i < numPts; i++) { + + // Need to transform each pnt by localToVWorld. + pnts[i] = getPoint3d(); + pnts[i].set(p3dData[i]); + localToVWorld.transform(pnts[i]); + } + } + } + // Its an interleaved type .. + else { + offset = 0; + if ((vformat & GeometryArray.COLOR_3) == GeometryArray.COLOR_3) { + offset += 3; + } + else if ((vformat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + offset += 4; + } + if ((vformat & GeometryArray.NORMALS) != 0) + offset += 3; + if ((vformat & GeometryArray.TEXTURE_COORDINATE_2) == GeometryArray.TEXTURE_COORDINATE_2) { + offset += 2 * geom.getTexCoordSetCount(); + } + else if ((vformat & GeometryArray.TEXTURE_COORDINATE_3) == GeometryArray.TEXTURE_COORDINATE_3) { + offset += 3 * geom.getTexCoordSetCount(); + } + stride = offset + 3; // for the vertices . + for (int i=0; i < numPts; i++) { + + // Need to transform each pnt by localToVWorld. + pnts[i] = getPoint3d(); + pnts[i].x = floatData[offset]; + pnts[i].y = floatData[offset+1]; + pnts[i].z = floatData[offset+2]; + + localToVWorld.transform(pnts[i]); + offset += stride; + } + } + + PickIntersection pi = new PickIntersection(this, geom); + + if (geom instanceof PointArray) { + retFlag = intersectPA ((PointArray)geom, geomIndex, pnts, firstpick, pi); + } else if (geom instanceof IndexedPointArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectIPA ((IndexedPointArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof LineArray) { + retFlag = intersectLA ((LineArray)geom, geomIndex, pnts, firstpick, pi); + } else if (geom instanceof LineStripArray) { + retFlag = intersectLSA ((LineStripArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof IndexedLineArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectILA ((IndexedLineArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof IndexedLineStripArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectILSA ((IndexedLineStripArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof TriangleArray) { + retFlag = intersectTA ((TriangleArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof TriangleStripArray) { + retFlag = intersectTSA ((TriangleStripArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof TriangleFanArray) { + retFlag = intersectTFA ((TriangleFanArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof IndexedTriangleArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectITA ((IndexedTriangleArray)geom, geomIndex, pnts, + firstpick, pi); + } else if (geom instanceof IndexedTriangleStripArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectITSA ((IndexedTriangleStripArray)geom, geomIndex, + pnts, firstpick, pi); + } else if (geom instanceof IndexedTriangleFanArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectITFA ((IndexedTriangleFanArray)geom, geomIndex, + pnts, firstpick, pi); + } else if (geom instanceof QuadArray) { + retFlag = intersectQA ((QuadArray)geom, geomIndex, pnts, firstpick, pi); + } else if (geom instanceof IndexedQuadArray) { + pi.iGeom = (IndexedGeometryArray) geom; + retFlag = intersectIQA ((IndexedQuadArray)geom, geomIndex, pnts, + firstpick, pi); + } else { + throw new RuntimeException ("incorrect class type"); + } + + if(retFlag == false) { + for (int i = 0; i < numPts; i++) { + freePoint3d(pnts[i]); + } + } + + return retFlag; + + } + + + + /* ==================================================================== */ + /* INTERSECT METHODS BY PRIMITIVE TYPE */ + /* ==================================================================== */ + + boolean intersectPoint(int[] vertidx, int[] coordidx, int geomIndex, + Point3d[] pnts, PickIntersection pi) { + // PickIntersection pi = new PickIntersection(this); + + Point3d[] point = new Point3d[1]; + point[0] = pnts[coordidx[0]]; + + if (debug) { + System.out.println("intersect point, point = " + point[0]); + } + + boolean intersect = false; + switch(pickShapeType) { + case PICK_SHAPE_RAY: + intersect = intersectPntAndRay(point[0], pickShapeStart, + pickShapeDir, pi); + break; + case PICK_SHAPE_SEGMENT: + if (intersectPntAndRay(point[0], pickShapeStart, pickShapeDir, pi)){ + if(pi.getDistance() <= 1.0) { // TODO: why 1.0? + intersect = true; + } + } + break; + /* case PICK_SHAPE_POINT: + intersect = intersectPntAndPnt(point[0], + ((PickPoint) pickShape).location ); + break; + */ + case PICK_SHAPE_BOUNDING_BOX: + intersect = ((BoundingBox)pickShapeBounds).intersect(point[0]); + pi.setPointCoordinatesVW(point[0]); + break; + case PICK_SHAPE_BOUNDING_SPHERE: + intersect = ((BoundingSphere)pickShapeBounds).intersect(point[0]); + pi.setPointCoordinatesVW(point[0]); + break; + case PICK_SHAPE_BOUNDING_POLYTOPE: + intersect = ((BoundingPolytope)pickShapeBounds).intersect(point[0]); + pi.setPointCoordinatesVW(point[0]); + break; + case PICK_SHAPE_CYLINDER: + intersect = intersectCylinder(point[0], (PickCylinder)pickShape,pi); + break; + case PICK_SHAPE_CONE: + intersect = intersectCone (point[0], (PickCone)pickShape, pi); + break; + } + if (intersect) { + PickIntersection newpi = new PickIntersection(this, pi.geom); + newpi.iGeom = pi.iGeom; + newpi.setDistance(pi.distance); + newpi.setPointCoordinatesVW(pi.getPointCoordinatesVW()); + + // Set PickIntersection parameters + newpi.setGeomIndex(geomIndex); + newpi.setVertexIndices (vertidx); + newpi.setPrimitiveCoordinatesVW(point); + intersections.add (newpi); + return true; + } + return false; + } + + boolean intersectLine(int[] vertidx, int[] coordidx, int geomIndex, + Point3d[] pnts, PickIntersection pi) { + + Point3d[] linePts = new Point3d[2]; + linePts[0] = pnts[coordidx[0]]; + linePts[1] = pnts[coordidx[1]]; + + boolean intersect = false; + switch(pickShapeType) { + case PICK_SHAPE_RAY: + intersect = intersectLineAndRay(linePts[0], linePts[1], + pickShapeStart, pickShapeDir, pi); + break; + case PICK_SHAPE_SEGMENT: + if (intersectLineAndRay(linePts[0], linePts[1], pickShapeStart, + pickShapeDir, pi)) { + if (pi.getDistance() <= 1.0) { + intersect = true; + } + } + break; + /* case PICK_SHAPE_POINT: + dir.x = linePts[1].x - linePts[0].x; + dir.y = linePts[1].y - linePts[0].y; + dir.z = linePts[1].z - linePts[0].z; + if (intersectPntAndRay(((PickPoint)pickShape).location, + pnts[0], dir, dist)) { + if(dist[0] <= 1.0) { + intersect = true; + } + } + break; + */ + case PICK_SHAPE_BOUNDING_BOX: + intersect = intersectBoundingBox(linePts, + (BoundingBox)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_SPHERE: + intersect = intersectBoundingSphere(linePts, + (BoundingSphere)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_POLYTOPE: + intersect = intersectBoundingPolytope(linePts, + (BoundingPolytope)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_CYLINDER: + intersect = intersectCylinder (linePts, (PickCylinder)pickShape,pi); + break; + case PICK_SHAPE_CONE: + intersect = intersectCone (linePts, (PickCone) pickShape, pi); + break; + } + if (intersect) { + PickIntersection newpi = new PickIntersection(this, pi.geom); + newpi.iGeom = pi.iGeom; + newpi.setDistance(pi.distance); + newpi.setPointCoordinatesVW(pi.getPointCoordinatesVW()); + + // Set PickIntersection parameters + newpi.setGeomIndex(geomIndex); + newpi.setVertexIndices (vertidx); + newpi.setPrimitiveCoordinatesVW(linePts); + intersections.add (newpi); + return true; + } + return false; + } + + boolean intersectTri(int[] vertidx, int[] coordidx, int geomIndex, + Point3d[] pnts, PickIntersection pi) { + + Point3d[] triPts = new Point3d[3]; + + triPts[0] = pnts[coordidx[0]]; + triPts[1] = pnts[coordidx[1]]; + triPts[2] = pnts[coordidx[2]]; + + + boolean intersect = false; + switch(pickShapeType) { + case PICK_SHAPE_RAY: + intersect = intersectRay(triPts, (PickRay) pickShape, pi); + break; + case PICK_SHAPE_SEGMENT: + intersect = intersectSegment(triPts, (PickSegment) pickShape, pi); + break; + /* case PICK_SHAPE_POINT: + if(inside(triPts, (PickPoint) pickShape, ccw)==false) + return false; + break; + */ + case PICK_SHAPE_BOUNDING_BOX: + intersect = intersectBoundingBox (triPts, + (BoundingBox)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_SPHERE: + intersect = intersectBoundingSphere (triPts, + (BoundingSphere)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_POLYTOPE: + intersect = intersectBoundingPolytope (triPts, + (BoundingPolytope)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_CYLINDER: + intersect = intersectCylinder (triPts, (PickCylinder) pickShape,pi); + break; + case PICK_SHAPE_CONE: + intersect = intersectCone (triPts, (PickCone)pickShape, pi); + break; + } + if (intersect) { + PickIntersection newpi = new PickIntersection(this, pi.geom); + newpi.iGeom = pi.iGeom; + newpi.setDistance(pi.distance); + newpi.setPointCoordinatesVW(pi.getPointCoordinatesVW()); + + // Set PickIntersection parameters + newpi.setGeomIndex(geomIndex); + newpi.setVertexIndices (vertidx); + + newpi.setPrimitiveCoordinatesVW(triPts); + intersections.add (newpi); + return true; + } + return false; + } + + boolean intersectQuad(int[] vertidx, int[] coordidx, int geomIndex, + Point3d[] pnts, PickIntersection pi) { + + Point3d[] quadPts = new Point3d[4]; + + quadPts[0] = pnts[coordidx[0]]; + quadPts[1] = pnts[coordidx[1]]; + quadPts[2] = pnts[coordidx[2]]; + quadPts[3] = pnts[coordidx[3]]; + + // PickIntersection pi = new PickIntersection(this); + + boolean intersect = false; + switch(pickShapeType) { + case PICK_SHAPE_RAY: + intersect = intersectRay(quadPts, (PickRay) pickShape, pi); + break; + case PICK_SHAPE_SEGMENT: + intersect = intersectSegment(quadPts, (PickSegment) pickShape, pi); + break; + /* case PICK_SHAPE_POINT: + if(inside(quadPts, (PickPoint) pickShape, ccw)==false) + return false; + break; + */ + case PICK_SHAPE_BOUNDING_BOX: + intersect = intersectBoundingBox (quadPts, + (BoundingBox)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_SPHERE: + intersect = intersectBoundingSphere (quadPts, + (BoundingSphere)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_BOUNDING_POLYTOPE: + intersect = intersectBoundingPolytope (quadPts, + (BoundingPolytope)pickShapeBounds); + pi.setPointCoordinatesVW(zeroPnt); + break; + case PICK_SHAPE_CYLINDER: + intersect = intersectCylinder (quadPts, (PickCylinder)pickShape,pi); + break; + case PICK_SHAPE_CONE: + intersect = intersectCone (quadPts, (PickCone)pickShape, pi); + break; + } + if (intersect) { + PickIntersection newpi = new PickIntersection(this, pi.geom); + newpi.iGeom = pi.iGeom; + newpi.setDistance(pi.distance); + newpi.setPointCoordinatesVW(pi.getPointCoordinatesVW()); + + // Set PickIntersection parameters + newpi.setGeomIndex(geomIndex); + newpi.setVertexIndices (vertidx); + newpi.setPrimitiveCoordinatesVW(quadPts); + intersections.add (newpi); + return true; + } + return false; + } + + /* ==================================================================== */ + /* INTERSECT METHODS BY GEOMETRY TYPE */ + /* ==================================================================== */ + + /** + Intersect method for PointArray + */ + boolean intersectPA (PointArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println ("intersect: PointArray"); + + int[] pntVertIdx = new int[1]; + int numint = 0; + + for (int i = 0; i < pnts.length; i++) { + pntVertIdx[0] = i; + if (intersectPoint(pntVertIdx, pntVertIdx, geomIndex, pnts, pi)) { + numint++; + if (firstpick) return true; + } + } + if (numint > 0) return true; + return false; + } + + /** + Intersect method for IndexedPointArray + */ + boolean intersectIPA (IndexedPointArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println ("intersect: IndexedPointArray"); + + int[] pntVertIdx = new int[1]; + int[] pntCoordIdx = new int[1]; + + int numint = 0; + int indexCount = geom.getIndexCount(); + + for (int i=0; i< indexCount; i++) { + pntVertIdx[0] = i; + pntCoordIdx[0] = geom.getCoordinateIndex(i); + if (intersectPoint(pntVertIdx, pntCoordIdx, geomIndex, pnts, pi)) { + numint++; + if (firstpick) return true; + } + } + if (numint > 0) return true; + return false; + } + + + /** + Intersect method for LineArray + */ + /** + Intersect method for LineArray + */ + boolean intersectLA (LineArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println ("intersect: LineArray"); + + int[] lineVertIdx = new int[2]; + + int numint = 0; + + for (int i=0; i< pnts.length;) { + /* set up the parameters for the current line */ + lineVertIdx[0] = i++; + lineVertIdx[1] = i++; + if (intersectLine(lineVertIdx, lineVertIdx, geomIndex, pnts, pi)) { + numint++; + if (firstpick) return true; + } + } + if (numint > 0) return true; + return false; + } + + /** + Intersect method for LineStripArray + */ + boolean intersectLSA (LineStripArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + int numint = 0; + + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripVertexCounts (stripVertexCounts); + int stripStart = 0; + + if (debug) System.out.println ("intersect: LineStripArray"); + + int[] lineVertIdx = new int[2]; + + for (int i=0; i < stripVertexCounts.length; i++) { + lineVertIdx[0] = stripStart; + int end = stripStart + stripVertexCounts[i]; + + for (int j=stripStart+1; j 0) return true; + return false; + } + + /** + Intersect method for IndexedLineArray + */ + boolean intersectILA (IndexedLineArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + int numint = 0; + int indexCount = geom.getIndexCount(); + if (debug) System.out.println ("intersect: IndexedLineArray"); + + int[] lineVertIdx = new int[2]; + int[] lineCoordIdx = new int[2]; + + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for IndexedLineStripArray + */ + boolean intersectILSA (IndexedLineStripArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, PickIntersection pi) { + if (debug) System.out.println ("intersect: IndexedLineStripArray"); + + int[] lineVertIdx = new int[2]; + int[] lineCoordIdx = new int[2]; + + int numint = 0; + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripIndexCounts (stripVertexCounts); + int stripStart = 0; + + for (int i=0; i < stripVertexCounts.length; i++) { + + lineVertIdx[0] = stripStart; + lineCoordIdx[0] = geom.getCoordinateIndex(stripStart); + int end = stripStart + stripVertexCounts[i]; + for (int j=stripStart+1; j 0) return true; + return false; + } + + /** + Intersect method for TriangleArray + */ + boolean intersectTA (TriangleArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) + System.out.println ("intersect: TriangleArray"); + + int[] triVertIdx = new int[3]; + + int numint = 0; + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for IndexedTriangleArray + */ + boolean intersectITA (IndexedTriangleArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, PickIntersection pi) { + + if (debug) + System.out.println ("intersect: IndexedTriangleArray"); + + int[] triVertIdx = new int[3]; + int[] triCoordIdx = new int[3]; + + int numint = 0; + int indexCount = geom.getIndexCount(); + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for TriangleStripArray + */ + boolean intersectTSA (TriangleStripArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, PickIntersection pi) { + if (debug) + System.out.println ("intersect: TriangleStripArray"); + + boolean ccw; + int numint = 0; + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripVertexCounts (stripVertexCounts); + int stripStart = 0; + int start; + int[] triVertIdx = new int[3]; + + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for IndexedTriangleStripArray + */ + boolean intersectITSA (IndexedTriangleStripArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, PickIntersection pi) { + + if (debug) + System.out.println ("intersect: IndexedTriangleStripArray"); + int numint = 0; + boolean ccw; + + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripIndexCounts (stripVertexCounts); + int stripStart = 0; + int start; + int[] triVertIdx = new int[3]; + int[] triCoordIdx = new int[3]; + + for (int i=0; i 0) return true; + return false; + + } + + /** + Intersect method for TriangleFanArray + */ + boolean intersectTFA (TriangleFanArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println("intersect: TriangleFanArray"); + + int numint = 0; + + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripVertexCounts (stripVertexCounts); + int fanStart = 0; + int start; + int[] triVertIdx = new int[3]; + + // System.out.println("stripVertexCounts.length " + stripVertexCounts.length); + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for IndexedTriangleFanArray + */ + boolean intersectITFA (IndexedTriangleFanArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println ("intersect: IndexedTriangleFanArray"); + + int numint = 0; + int[] stripVertexCounts = new int [geom.getNumStrips()]; + geom.getStripIndexCounts (stripVertexCounts); + int fanStart = 0; + int start; + int[] triVertIdx = new int[3]; + int[] triCoordIdx = new int[3]; + + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for QuadArray + */ + boolean intersectQA (QuadArray geom, int geomIndex, Point3d[] pnts, + boolean firstpick, PickIntersection pi) { + + if (debug) System.out.println ("intersect: QuadArray"); + + int[] quadVertIdx = new int[4]; + + int numint = 0; + for (int i=0; i 0) return true; + return false; + } + + /** + Intersect method for IndexedQuadArray + */ + final boolean intersectIQA (IndexedQuadArray geom, int geomIndex, + Point3d[] pnts, boolean firstpick, + PickIntersection pi) { + + if (debug) System.out.println ("intersect: IndexedQuadArray"); + + int[] quadVertIdx = new int[4]; + int[] quadCoordIdx = new int[4]; + + int numint = 0; + int indexCount = geom.getIndexCount(); + // System.out.println ("intersect: IndexedQuadArray : indexCount " + indexCount); + for (int i=0; i 0) return true; + return false; + + } + + /* ==================================================================== */ + /* GENERAL INTERSECT METHODS */ + /* ==================================================================== */ + static boolean intersectBoundingBox (Point3d coordinates[], + BoundingBox box) { + int i, j; + int out[] = new int[6]; + + Point3d lower = new Point3d(); + Point3d upper = new Point3d(); + box.getLower (lower); + box.getUpper (upper); + + //Do trivial vertex test. + for (i=0; i<6; i++) out[i] = 0; + for (i=0; i= lower.x) && + (coordinates[i].x <= upper.x) && + (coordinates[i].y >= lower.y) && + (coordinates[i].y <= upper.y) && + (coordinates[i].z >= lower.z) && + (coordinates[i].z <= upper.z)) { + // We're done! It's inside the boundingbox. + return true; + } else { + if (coordinates[i].x < lower.x) out[0]++; // left + if (coordinates[i].y < lower.y) out[1]++; // bottom + if (coordinates[i].z < lower.z) out[2]++; // back + if (coordinates[i].x > upper.x) out[3]++; // right + if (coordinates[i].y > upper.y) out[4]++; // top + if (coordinates[i].z > upper.z) out[5]++; // front + } + } + + if ((out[0] == coordinates.length) || (out[1] == coordinates.length) || + (out[2] == coordinates.length) || (out[3] == coordinates.length) || + (out[4] == coordinates.length) || (out[5] == coordinates.length)){ + // we're done. primitive is outside of boundingbox. + return false; + } + // Setup bounding planes. + Point3d pCoor[] = new Point3d[4]; + for (i=0; i<4; i++) pCoor[i] = new Point3d(); + + // left plane. + pCoor[0].set(lower.x, lower.y, lower.z); + pCoor[1].set(lower.x, lower.y, upper.z); + pCoor[2].set(lower.x, upper.y, upper.z); + pCoor[3].set(lower.x, upper.y, lower.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + // right plane. + pCoor[0].set(upper.x, lower.y, lower.z); + pCoor[1].set(upper.x, upper.y, lower.z); + pCoor[2].set(upper.x, upper.y, upper.z); + pCoor[3].set(upper.x, lower.y, upper.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + // bottom plane. + pCoor[0].set(upper.x, lower.y, upper.z); + pCoor[1].set(lower.x, lower.y, upper.z); + pCoor[2].set(lower.x, lower.y, lower.z); + pCoor[3].set(upper.x, lower.y, lower.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + // top plane. + pCoor[0].set(upper.x, upper.y, upper.z); + pCoor[1].set(upper.x, upper.y, lower.z); + pCoor[2].set(lower.x, upper.y, lower.z); + pCoor[3].set(lower.x, upper.y, upper.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + // front plane. + pCoor[0].set(upper.x, upper.y, upper.z); + pCoor[1].set(lower.x, upper.y, upper.z); + pCoor[2].set(lower.x, lower.y, upper.z); + pCoor[3].set(upper.x, lower.y, upper.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + // back plane. + pCoor[0].set(upper.x, upper.y, lower.z); + pCoor[1].set(upper.x, lower.y, lower.z); + pCoor[2].set(lower.x, lower.y, lower.z); + pCoor[3].set(lower.x, upper.y, lower.z); + if (intersectPolygon(pCoor, coordinates, false) == true) return true; + + return false; + } + + static boolean intersectBoundingSphere (Point3d coordinates[], + BoundingSphere sphere) { + + + int i, j; + Vector3d tempV3D = new Vector3d(); + boolean esFlag; + Point3d center = new Point3d(); + sphere.getCenter (center); + double radius = sphere.getRadius (); + //Do trivial vertex test. + + for (i=0; i 0.0) break; + } + + for (j=i; j 0.0) break; + } + + if (j == (coordinates.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + for (i=0; i radius) + return false; + + tq = pNrmDotPa / nLenSq; + + q.x = center.x + tq * pNrm.x; + q.y = center.y + tq * pNrm.y; + q.z = center.z + tq * pNrm.z; + + // PolyPnt2D Test. + return pointIntersectPolygon2D( pNrm, coordinates, q); + } + + static boolean intersectBoundingPolytope (Point3d coordinates[], + BoundingPolytope polytope) { + + boolean debug = false; + + // this is a multiplier to the halfplane distance coefficients + double distanceSign = -1.0; + // Variable needed for intersection. + Point4d tP4d = new Point4d(); + + Vector4d[] planes = new Vector4d [polytope.getNumPlanes()]; + polytope.getPlanes (planes); + + if (coordinates.length == 2) { + // we'll handle line separately. + throw new java.lang.RuntimeException ("TODO: must make polytope.intersect(coordinates[0], coordinates[1], tP4d) public!"); + // TODO: must make this public !!! + // return polytope.intersect(coordinates[0], coordinates[1], tP4d ); + } + + // It is a triangle or a quad. + + // first test to see if any of the coordinates are all inside of the + // intersection polytope's half planes + // essentially do a matrix multiply of the constraintMatrix K*3 with + // the input coordinates 3*1 = K*1 vector + + if (debug) { + System.out.println("The value of the input vertices are: "); + for (int i=0; i < coordinates.length; i++) { + System.out.println("The " +i+ " th vertex is: " + coordinates[i]); + } + + System.out.println("The value of the input bounding Polytope's planes ="); + for (int i=0; i < planes.length; i++) { + System.out.println("The " +i+ " th plane is: " + planes[i]); + } + + } + + // the direction for the intersection cost function + double centers[] = new double[4]; + centers[0] = 0.8; centers[1] = 0.9; centers[2] = 1.1; centers[3] = 1.2; + + boolean intersection = true; + boolean PreTest = false; + + if (PreTest) { + // for each coordinate, test it with each half plane + for (int i=0; i < coordinates.length; i++) { + for (int j=0; j < planes.length; j++) { + if ((planes[j].x * coordinates[i].x + + planes[j].y * coordinates[i].y + + planes[j].z*coordinates[i].z) <= + (distanceSign)*planes[j].w){ + // the point satisfies this particular hyperplane + intersection = true; + } else { + // the point fails this hyper plane try with a new hyper plane + intersection = false; + break; + } + } + if (intersection) { + // a point was found to be completely inside the bounding hull + return true; + } + } + } // end of pretest + + // at this point all points are outside of the bounding hull + // build the problem tableau for the linear program + + int numberCols = planes.length + 2 + coordinates.length + 1; + int numberRows = 1 + coordinates.length; + + double problemTableau[][] = new double[numberRows][numberCols]; + + // compute -Mtrans = -A*P + for ( int i = 0; i < planes.length; i++) { + for ( int j=0; j < coordinates.length; j++) { + problemTableau[j][i] = (-1.0)* (planes[i].x*coordinates[j].x+ + planes[i].y*coordinates[j].y+ + planes[i].z*coordinates[j].z); + } + } + + // add the other rows + for (int i = 0; i < coordinates.length; i++) { + problemTableau[i][planes.length] = -1.0; + problemTableau[i][planes.length + 1] = 1.0; + + for (int j=0; j < coordinates.length; j++) { + if ( i==j ) { + problemTableau[i][j + planes.length + 2] = 1.0; + } else { + problemTableau[i][j + planes.length + 2] = 0.0; + } + + // place the last column elements the Ci's + problemTableau[i][numberCols - 1] = centers[i]; + } + } + + // place the final rows value + for (int j = 0; j < planes.length; j++) { + problemTableau[numberRows - 1][j] = + (distanceSign)*planes[j].w; + } + problemTableau[numberRows - 1][planes.length] = 1.0; + problemTableau[numberRows - 1][planes.length+1] = -1.0; + for (int j = 0; j < coordinates.length; j++) { + problemTableau[numberRows - 1][planes.length+2+j] = 0.0; + } + + if (debug) { + System.out.println("The value of the problem tableau is: " ); + for (int i=0; i < problemTableau.length; i++) { + for (int j=0; j < problemTableau[0].length; j++) { + System.out.print(problemTableau[i][j] + " "); + } + System.out.println(); + } + } + + double distance = generalStandardSimplexSolver(problemTableau, + Float.NEGATIVE_INFINITY); + if (debug) { + System.out.println("The value returned by the general standard simplex = " + + distance); + } + if (distance == Float.POSITIVE_INFINITY) { + return false; + } + return true; + } + + + // optimized version using arrays of doubles, but using the standard simplex + // method to solve the LP tableau. This version has not been optimized to + // work with a particular size input tableau and is much slower than some + // of the other variants...supposedly + static double generalStandardSimplexSolver(double problemTableau[][], + double stopingValue) { + boolean debug = false; + int numRow = problemTableau.length; + int numCol = problemTableau[0].length; + boolean optimal = false; + int i, pivotRowIndex, pivotColIndex; + double maxElement, element, endElement, ratio, prevRatio; + int count = 0; + double multiplier; + + if (debug) { + System.out.println("The number of rows is : " + numRow); + System.out.println("The number of columns is : " + numCol); + } + + // until the optimal solution is found continue to do + // iterations of the simplex method + while(!optimal) { + + if (debug) { + System.out.println("input problem tableau is:"); + for (int k=0; k < numRow; k++) { + for (int j=0; j < numCol; j++) { + System.out.println("kth, jth value is:" +k+" "+j+" : " + + problemTableau[k][j]); + } + } + } + + // test to see if the current solution is optimal + // check all bottom row elements except the right most one and + // if all positive or zero its optimal + for (i = 0, maxElement = 0, pivotColIndex = -1; i < numCol - 1; i++) { + // a bottom row element + element = problemTableau[numRow - 1][i]; + if ( element < maxElement) { + maxElement = element; + pivotColIndex = i; + } + } + + // if there is no negative non-zero element then we + // have found an optimal solution (the last row of the tableau) + if (pivotColIndex == -1) { + // found an optimal solution + //System.out.println("Found an optimal solution"); + optimal = true; + } + + //System.out.println("The value of maxElement is:" + maxElement); + + if (!optimal) { + // Case when the solution is not optimal but not known to be + // either unbounded or infeasable + + // from the above we have found the maximum negative element in + // bottom row, we have also found the column for this value + // the pivotColIndex represents this + + // initialize the values for the algorithm, -1 for pivotRowIndex + // indicates no solution + + prevRatio = Float.POSITIVE_INFINITY; + ratio = 0.0; + pivotRowIndex = -1; + + // note if all of the elements in the pivot column are zero or + // negative the problem is unbounded. + for (i = 0; i < numRow - 1; i++) { + element = problemTableau[i][pivotColIndex]; // r value + endElement = problemTableau[i][numCol-1]; // s value + + // pivot according to the rule that we want to choose the row + // with smallest s/r ratio see third case + // currently we ignore valuse of r==0 (case 1) and cases where the + // ratio is negative, i.e. either r or s are negative (case 2) + if (element == 0) { + if (debug) { + System.out.println("Division by zero has occurred"); + System.out.println("Within the linear program solver"); + System.out.println("Ignoring the zero as a potential pivot"); + } + } else if ( (element < 0.0) || (endElement < 0.0) ){ + if (debug) { + System.out.println("Ignoring cases where element is negative"); + System.out.println("The value of element is: " + element); + System.out.println("The value of end Element is: " + endElement); + } + } else { + ratio = endElement/element; // should be s/r + if (debug) { + System.out.println("The value of element is: " + element); + System.out.println("The value of endElement is: " + endElement); + System.out.println("The value of ratio is: " + ratio); + System.out.println("The value of prevRatio is: " + prevRatio); + System.out.println("Value of ratio <= prevRatio is :" + + (ratio <= prevRatio)); + } + if (ratio <= prevRatio) { + if (debug) { + System.out.println("updating prevRatio with ratio"); + } + prevRatio = ratio; + pivotRowIndex = i; + } + } + } + + // if the pivotRowIndex is still -1 then we know the pivotColumn + // has no viable pivot points and the solution is unbounded or + // infeasable (all pivot elements were either zero or negative or + // the right most value was negative (the later shouldn't happen?) + if (pivotRowIndex == -1) { + if (debug) { + System.out.println("UNABLE TO FIND SOLUTION"); + System.out.println("The system is infeasable or unbounded"); + } + return(Float.POSITIVE_INFINITY); + } + + // we now have the pivot row and col all that remains is + // to divide through by this value and subtract the appropriate + // multiple of the pivot row from all other rows to obtain + // a tableau which has a column of all zeros and one 1 in the + // intersection of pivot row and col + + // divide through by the pivot value + double pivotValue = problemTableau[pivotRowIndex][pivotColIndex]; + + if (debug) { + System.out.println("The value of row index is: " + pivotRowIndex); + System.out.println("The value of col index is: " + pivotColIndex); + System.out.println("The value of pivotValue is: " + pivotValue); + } + // divide through by s on the pivot row to obtain a 1 in pivot col + for (i = 0; i < numCol; i++) { + problemTableau[pivotRowIndex][i] = + problemTableau[pivotRowIndex][i] / pivotValue; + } + + // subtract appropriate multiple of pivot row from all other rows + // to zero out all rows except the final row and the pivot row + for (i = 0; i < numRow; i++) { + if (i != pivotRowIndex) { + multiplier = problemTableau[i][pivotColIndex]; + for (int j=0; j < numCol; j++) { + problemTableau[i][j] = problemTableau[i][j] - + multiplier * problemTableau[pivotRowIndex][j]; + } + } + } + } + // case when the element is optimal + } + return(problemTableau[numRow - 1][numCol - 1]); + } + + static boolean edgeIntersectSphere (BoundingSphere sphere, Point3d start, + Point3d end) { + + double abLenSq, acLenSq, apLenSq, abDotAp, radiusSq; + Vector3d ab = new Vector3d(); + Vector3d ap = new Vector3d(); + + Point3d center = new Point3d(); + sphere.getCenter (center); + double radius = sphere.getRadius (); + + ab.x = end.x - start.x; + ab.y = end.y - start.y; + ab.z = end.z - start.z; + + ap.x = center.x - start.x; + ap.y = center.y - start.y; + ap.z = center.z - start.z; + + abDotAp = ab.dot(ap); + + if (abDotAp < 0.0) + return false; // line segment points away from sphere. + + abLenSq = ab.lengthSquared(); + acLenSq = abDotAp * abDotAp / abLenSq; + + if (acLenSq < abLenSq) + return false; // C doesn't lies between end points of edge. + + radiusSq = radius * radius; + apLenSq = ap.lengthSquared(); + + if ((apLenSq - acLenSq) <= radiusSq) + return true; + + return false; + } + + + static double det2D(Point2d a, Point2d b, Point2d p) { + return (((p).x - (a).x) * ((a).y - (b).y) + + ((a).y - (p).y) * ((a).x - (b).x)); + } + + // Assume coord is CCW. + static boolean pointIntersectPolygon2D(Vector3d normal, Point3d[] coord, + Point3d point) { + + double absNrmX, absNrmY, absNrmZ; + Point2d coord2D[] = new Point2d[coord.length]; + Point2d pnt = new Point2d(); + + int i, j, axis; + + // Project 3d points onto 2d plane. + // Note : Area of polygon is not preserve in this projection, but + // it doesn't matter here. + + // Find the axis of projection. + absNrmX = Math.abs(normal.x); + absNrmY = Math.abs(normal.y); + absNrmZ = Math.abs(normal.z); + + if (absNrmX > absNrmY) + axis = 0; + else + axis = 1; + + if (axis == 0) { + if (absNrmX < absNrmZ) + axis = 2; + } + else if (axis == 1) { + if (absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + normal + " axis " + axis ); + + for (i=0; i0.0) + ; + else + return false; + else + if (det2D(coord2D[j], coord2D[0], pnt)>0.0) + ; + else + return false; + } + return true; + } + + + static boolean edgeIntersectPlane(Vector3d normal, Point3d pnt, + Point3d start, Point3d end, Point3d iPnt){ + + Vector3d tempV3d = new Vector3d(); + Vector3d direction = new Vector3d(); + double pD, pNrmDotrDir, tr; + + // Compute plane D. + tempV3d.set((Tuple3d) pnt); + pD = normal.dot(tempV3d); + + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + pNrmDotrDir = normal.dot(direction); + + // edge is parallel to plane. + if (pNrmDotrDir== 0.0) { + // System.out.println("Edge is parallel to plane."); + return false; + } + + tempV3d.set((Tuple3d) start); + + tr = (pD - normal.dot(tempV3d))/ pNrmDotrDir; + + // Edge intersects the plane behind the edge's start. + // or exceed the edge's length. + if ((tr < 0.0 ) || (tr > 1.0 )) { + // System.out.println("Edge intersects the plane behind the start or exceed end."); + return false; + } + + iPnt.x = start.x + tr * direction.x; + iPnt.y = start.y + tr * direction.y; + iPnt.z = start.z + tr * direction.z; + + return true; + } + + // Assume coord is CCW. + static boolean edgeIntersectPolygon2D(Vector3d normal, Point3d[] coord, + Point3d[] seg) { + + double absNrmX, absNrmY, absNrmZ; + Point2d coord2D[] = new Point2d[coord.length]; + Point2d seg2D[] = new Point2d[2]; + + int i, j, axis; + + // Project 3d points onto 2d plane. + // Note : Area of polygon is not preserve in this projection, but + // it doesn't matter here. + + // Find the axis of projection. + absNrmX = Math.abs(normal.x); + absNrmY = Math.abs(normal.y); + absNrmZ = Math.abs(normal.z); + + if (absNrmX > absNrmY) + axis = 0; + else + axis = 1; + + if (axis == 0) { + if (absNrmX < absNrmZ) + axis = 2; + } + else if (axis == 1) { + if (absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + normal + " axis " + axis ); + + for (i=0; i 0.0) + break; + } + + for (j=i; j 0.0) + break; + } + + if (j == (coord1.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + for (i=0; i1) + break; + } + } + + if (j==0) + return false; + + if (coord2.length < 3) + return pointIntersectPolygon2D(pNrm, coord1, seg[0]); + + return edgeIntersectPolygon2D(pNrm, coord1, seg); + } + + + static final boolean isNonZero(double v) { + return ((v > EPS) || (v < -EPS)); + + } + + + static boolean intersectRay(Point3d coordinates[], + PickRay ray, PickIntersection pi) { + Point3d origin = getPoint3d(); + Vector3d direction = getVector3d(); + boolean result; + ray.get (origin, direction); + result = intersectRayOrSegment(coordinates, direction, origin, pi, false); + freeVector3d(direction); + freePoint3d(origin); + return result; + } + + /** + * Return true if triangle or quad intersects with ray and the distance is + * stored in pr. + * */ + static boolean intersectRayOrSegment(Point3d coordinates[], + Vector3d direction, Point3d origin, + PickIntersection pi, boolean isSegment) { + Vector3d vec0, vec1, pNrm, tempV3d; + Point3d iPnt; + + vec0 = getVector3d(); + vec1 = getVector3d(); + pNrm = getVector3d(); + + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + double pNrmDotrDir = 0.0; + + boolean isIntersect = false; + int i, j, k=0, l = 0; + + // Compute plane normal. + for (i=0; i 0.0) { + break; + } + } + + + for (j=l; j 0.0) { + break; + } + } + + pNrm.cross(vec0,vec1); + + if ((vec1.length() == 0) || (pNrm.length() == 0)) { + // degenerated to line if vec0.length() == 0 + // or vec0.length > 0 and vec0 parallel to vec1 + k = (l == 0 ? coordinates.length-1: l-1); + isIntersect = intersectLineAndRay(coordinates[l], + coordinates[k], + origin, + direction, + pi); + + // put the Vectors on the freelist + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return isIntersect; + } + + // It is possible that Quad is degenerate to Triangle + // at this point + + pNrmDotrDir = pNrm.dot(direction); + + // Ray is parallel to plane. + if (pNrmDotrDir == 0.0) { + // Ray is parallel to plane + // Check line/triangle intersection on plane. + for (i=0; i < coordinates.length ;i++) { + if (i != coordinates.length-1) { + k = i+1; + } else { + k = 0; + } + if (intersectLineAndRay(coordinates[i], + coordinates[k], + origin, + direction, + pi)) { + isIntersect = true; + break; + } + } + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return isIntersect; + } + + // Plane equation: (p - p0)*pNrm = 0 or p*pNrm = pD; + tempV3d = getVector3d(); + tempV3d.set((Tuple3d) coordinates[0]); + pD = pNrm.dot(tempV3d); + tempV3d.set((Tuple3d) origin); + + // Substitute Ray equation: + // p = origin + pi.distance*direction + // into the above Plane equation + + double dist = (pD - pNrm.dot(tempV3d))/ pNrmDotrDir; + + // Ray intersects the plane behind the ray's origin. + if ((dist < -EPS ) || + (isSegment && (dist > 1.0+EPS))) { + // Ray intersects the plane behind the ray's origin + // or intersect point not fall in Segment + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(tempV3d); + return false; + } + + // Now, one thing for sure the ray intersect the plane. + // Find the intersection point. + iPnt = getPoint3d(); + iPnt.x = origin.x + direction.x * dist; + iPnt.y = origin.y + direction.y * dist; + iPnt.z = origin.z + direction.z * dist; + + // Project 3d points onto 2d plane. + // Find the axis so that area of projection is maximize. + absNrmX = Math.abs(pNrm.x); + absNrmY = Math.abs(pNrm.y); + absNrmZ = Math.abs(pNrm.z); + + // Check out + // http://astronomy.swin.edu.au/~pbourke/geometry/insidepoly/ + // Solution 3: + // All sign of (y - y0) (x1 - x0) - (x - x0) (y1 - y0) + // must agree. + double sign, t, lastSign = 0; + Point3d p0 = coordinates[coordinates.length-1]; + Point3d p1 = coordinates[0]; + + isIntersect = true; + + if (absNrmX > absNrmY) { + if (absNrmX < absNrmZ) { + for (i=0; i < coordinates.length; i++) { + p0 = coordinates[i]; + p1 = (i != coordinates.length-1 ? coordinates[i+1]: coordinates[0]); + sign = (iPnt.y - p0.y)*(p1.x - p0.x) - + (iPnt.x - p0.x)*(p1.y - p0.y); + if (isNonZero(sign)) { + if (sign*lastSign < 0) { + isIntersect = false; + break; + } + lastSign = sign; + } else { // point on line, check inside interval + t = p1.y - p0.y; + if (isNonZero(t)) { + t = (iPnt.y - p0.y)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.x - p0.x; + if (isNonZero(t)) { + t = (iPnt.x - p0.x)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } else { + for (i=0; i -EPS) && (t < 1+EPS)); + break; + + } else { + t = p1.z - p0.z; + if (isNonZero(t)) { + t = (iPnt.z - p0.z)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } + } else { + if (absNrmY < absNrmZ) { + for (i=0; i -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.x - p0.x; + if (isNonZero(t)) { + t = (iPnt.x - p0.x)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } else { + for (i=0; i -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.z - p0.z; + if (isNonZero(t)) { + t = (iPnt.z - p0.z)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } + } + + if (isIntersect) { + pi.setDistance(dist*direction.length()); + pi.setPointCoordinatesVW(iPnt); + } + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(tempV3d); + freePoint3d(iPnt); + return isIntersect; + } + + + /** + Return true if triangle or quad intersects with segment and the distance is + stored in dist. + */ + static boolean intersectSegment (Point3d coordinates[], PickSegment segment, + PickIntersection pi) { + Point3d start = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + boolean result; + segment.get(start, end); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + result = intersectRayOrSegment(coordinates, direction, start, pi, true); + freeVector3d(direction); + freePoint3d(start); + freePoint3d(end); + return result; + } + + + /** + Return true if point is on the inside of halfspace test. The halfspace is + partition by the plane of triangle or quad. + */ + + static boolean inside (Point3d coordinates[], PickPoint point, int ccw) { + + Vector3d vec0 = new Vector3d(); //Edge vector from point 0 to point 1; + Vector3d vec1 = new Vector3d(); //Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = new Vector3d(); + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + Vector3d tempV3d = new Vector3d(); + double pNrmDotrDir = 0.0; + + double tempD; + + int i, j; + + Point3d location = new Point3d (); + point.get (location); + + // Compute plane normal. + for (i=0; i 0.0) + break; + } + + for (j=i; j 0.0) + break; + } + + if (j == (coordinates.length-1)) { + // System.out.println("(1) Degenerated polygon."); + return false; // Degenerated polygon. + } + + /* + System.out.println("Ray orgin : " + origin + " dir " + direction); + System.out.println("Triangle/Quad :"); + for (i=0; i 0.0 ) { + // System.out.println("point is on the outside of plane."); + return false; + } + else + return true; + } + + static boolean intersectPntAndPnt (Point3d pnt1, Point3d pnt2, + PickIntersection pi) { + + if ((pnt1.x == pnt2.x) && (pnt1.y == pnt2.y) && (pnt1.z == pnt2.z)) { + pi.setPointCoordinatesVW (pnt1); + pi.setDistance (0.0); + return true; + } + else + return false; + } + + static boolean intersectPntAndRay (Point3d pnt, Point3d ori, Vector3d dir, + PickIntersection pi) { + int flag = 0; + double temp; + double dist; + + if (dir.x != 0.0) { + flag = 0; + dist = (pnt.x - ori.x)/dir.x; + } + else if (dir.y != 0.0) { + if (pnt.x != ori.x) + return false; + flag = 1; + dist = (pnt.y - ori.y)/dir.y; + } + else if (dir.z != 0.0) { + if ((pnt.x != ori.x)||(pnt.y != ori.y)) + return false; + flag = 2; + dist = (pnt.z - ori.z)/dir.z; + + } + else + return false; + + if (dist < 0.0) + return false; + + if (flag == 0) { + temp = ori.y + dist * dir.y; + if ((pnt.y < (temp - Double.MIN_VALUE)) || (pnt.y > (temp + Double.MIN_VALUE))) + return false; + } + + if (flag < 2) { + temp = ori.z + dist * dir.z; + if ((pnt.z < (temp - Double.MIN_VALUE)) || (pnt.z > (temp + Double.MIN_VALUE))) + return false; + } + + pi.setPointCoordinatesVW (pnt); + pi.setDistance (dist); + + return true; + + } + + static boolean intersectLineAndRay(Point3d start, Point3d end, + Point3d ori, Vector3d dir, + PickIntersection pi) { + + double m00, m01, m10, m11; + double mInv00, mInv01, mInv10, mInv11; + double dmt, t, s, tmp1, tmp2; + Vector3d lDir; + double dist; + + // System.out.println("Intersect : intersectLineAndRay"); + // System.out.println("start " + start + " end " + end ); + // System.out.println("ori " + ori + " dir " + dir); + + lDir = new Vector3d(end.x - start.x, end.y - start.y, + end.z - start.z); + + m00 = lDir.x; + m01 = -dir.x; + m10 = lDir.y; + m11 = -dir.y; + + // Get the determinant. + dmt = (m00 * m11) - (m10 * m01); + + if (dmt==0.0) { // No solution, hence no intersect. + // System.out.println("dmt is zero"); + boolean isIntersect = false; + if ((lDir.x == 0) && (lDir.y == 0) && (lDir.z == 0)) { + isIntersect = intersectPntAndRay(start, ori, dir, pi); + if (isIntersect) { + pi.setPointCoordinatesVW(start); + pi.setDistance(0); + } + } + freeVector3d(lDir); + return isIntersect; + } + // Find the inverse. + tmp1 = 1/dmt; + + mInv00 = tmp1 * m11; + mInv01 = tmp1 * (-m01); + mInv10 = tmp1 * (-m10); + mInv11 = tmp1 * m00; + + tmp1 = ori.x - start.x; + tmp2 = ori.y - start.y; + + t = mInv00 * tmp1 + mInv01 * tmp2; + s = mInv10 * tmp1 + mInv11 * tmp2; + + if (s<0.0) { // Before the origin of ray. + // System.out.println("Before the origin of ray " + s); + return false; + } + if ((t<0)||(t>1.0)) {// Before or after the end points of line. + // System.out.println("Before or after the end points of line. " + t); + return false; + } + + tmp1 = ori.z + s * dir.z; + tmp2 = start.z + t * lDir.z; + + if ((tmp1 < (tmp2 - Double.MIN_VALUE)) || + (tmp1 > (tmp2 + Double.MIN_VALUE))) { + // System.out.println("No intersection : tmp1 " + tmp1 + " tmp2 " + tmp2); + return false; + } + dist = s; + + pi.setDistance (dist); + Point3d iPnt = new Point3d (); + iPnt.scaleAdd (s, dir, ori); + pi.setPointCoordinatesVW (iPnt); + + // System.out.println("Intersected : tmp1 " + tmp1 + " tmp2 " + tmp2); + return true; + } + + /** + Return true if triangle or quad intersects with cylinder and the + distance is stored in pr. + */ + static boolean intersectCylinder (Point3d coordinates[], + PickCylinder cyl, PickIntersection pi) { + + Point3d origin = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + Point3d iPnt1 = getPoint3d(); + Point3d iPnt2 = getPoint3d(); + Vector3d originToIpnt = getVector3d(); + + // Get cylinder information + cyl.getOrigin (origin); + cyl.getDirection (direction); + double radius = cyl.getRadius (); + + if (cyl instanceof PickCylinderSegment) { + ((PickCylinderSegment)cyl).getEnd (end); + } + + // If the ray intersects, we're good (do not do this if we only have + // a segment + if (coordinates.length > 2) { + if (cyl instanceof PickCylinderRay) { + if (intersectRay (coordinates, new PickRay (origin, direction), pi)) { + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freePoint3d(iPnt1); + freePoint3d(iPnt2); + freeVector3d(originToIpnt); + + return true; + } + } + else { + if (intersectSegment (coordinates, new PickSegment (origin, end), pi)) { + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freePoint3d(iPnt1); + freePoint3d(iPnt2); + freeVector3d(originToIpnt); + + return true; + } + } + } + + // Ray doesn't intersect, check distance to edges + double sqDistToEdge; + for (int i=0; i 2) { + if (cone instanceof PickConeRay) { + if (intersectRay (coordinates, new PickRay (origin, direction), pi)) { + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freePoint3d(iPnt2); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return true; + } + } + else { + if (intersectSegment (coordinates, new PickSegment (origin, end), + pi)) { + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freePoint3d(iPnt2); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return true; + } + } + } + + // Ray doesn't intersect, check distance to edges + double sqDistToEdge; + for (int i=0; i + * The pick mode specifies the detail level of picking before the PickResult + * is returned: + *

+ *

    + *
  • PickTool.BOUNDS - Pick using the bounds of the pickable nodes. The + * PickResult returned will contain the SceneGraphPath to the picked Node. + *
  • + *
  • PickTool.GEOMETRY will pick using the geometry of the pickable nodes. + * The PickResult returned will contain the SceneGraphPath to the picked Node. + * Geometry nodes in the scene must have the ALLOW_INTERSECT capability set for + * this mode.
  • + *
  • PickTool.GEOMETRY_INTERSECT_INFO -is the same as GEOMETRY, but the + * the PickResult will also include information on each intersection + * of the pick shape with the geometry. The intersection information includes + * the sub-primitive picked (that is, the point, line, triangle or quad), + * the closest vertex to the center of the pick shape, and + * the intersection's coordinate, normal, color and texture coordinates. + * To allow this information to be generated, Shape3D and Morph nodes must have + * the ALLOW_GEOMETRY_READ capability set and GeometryArrays must have the + * ALLOW_FORMAT_READ, + * ALLOW_COUNT_READ, and ALLOW_COORDINATE_READ capabilities set, plus the + * ALLOW_COORDINATE_INDEX_READ capability for indexed geometry. + * To inquire + * the intersection color, normal or texture coordinates + * the corresponding READ capability bits must be set on the GeometryArray. + *
  • + *
+ *

The utility method + * + * PickTool.setCapabilities(Node, int) + * can be used before the scene graph is + * made live to set the + * capabilities of Shape3D, Morph or Geometry + * nodes to allow picking. + *

+ * A PickResult from a lower level of detail pick can be used to + * inquire more detailed information if the capibility bits are set. + * This can be used to filter the PickResults + * before the more computationally intensive intersection processing. + * For example, + * the application can do a BOUNDS pick and then selectively inquire + * intersections on some of the PickResults. This will save the effort of + * doing intersection computation on the other PickResults. + * However, inquiring the intersections from a GEOMETRY pick will make + * the intersection computation happen twice, use GEOMETRY_INTERSECT_INFO + * if you want to inquire the intersection information on all the PickResults. + *

+ * When using pickAllSorted or pickClosest methods, the picks + * will be sorted by the distance from the start point of the pick shape to + * the intersection point. + *

+ * Morph nodes cannot be picked using the displayed geometry in + * GEOMETRY_INTERSECT_INFO mode due to limitations in the current Java3D core + * API (the current + * geometry of the the Morph cannot be inquired). Instead they are picked + * using + * the geometry at index 0 in the Morph, this limitation may be eliminated in a + * future release of Java3D. + *

+ * If the pick shape is a PickBounds, the pick result will contain only the + * scene graph path, even if the mode is GEOMETRY_INTERSECT_INFO. + */ +public class PickTool { + + /* OPEN ISSUES: + -- pickClosest() and pickAllSorted() using GEOMETRY and a non-PickRay + shape => unsorted picking. + -- Need to implement Morph geometry index 0 picking. + */ + + private final boolean debug = true; + protected boolean userDefineShape = false; + + PickShape pickShape; + + /** Used to store the BranchGroup used for picking */ + BranchGroup pickRootBG = null; + /** Used to store the Locale used for picking */ + Locale pickRootL = null; + + /** Used to store a reference point used in determining how "close" points + are. + */ + Point3d start = null; + + /* pick mode, one of BOUNDS, GEOMETRY, etc. */ + int mode = BOUNDS; + + /** Use this mode to pick by bounds and get basic information + on the pick. + */ + public static final int BOUNDS = 0x200; + + /** Use this mode to pick by geometry and get basic + information on the pick. + */ + public static final int GEOMETRY = 0x100; + + /** Use this mode to pick by geometry and save + information about the intersections (intersected primitive, + intersection point and closest vertex). + */ + public static final int GEOMETRY_INTERSECT_INFO = 0x400; + + + // Flags for the setCapabilities() method + /** + * Flag to pass to setCapabilities(Node, int) to set + * the Node's capabilities to allow intersection tests, but not + * inquire information about the intersections (use for GEOMETRY mode). + * @see PickTool#setCapabilities + */ + public static final int INTERSECT_TEST = 0x1001; + + /** + * Flag to pass to setCapabilities(Node, int) to set + * the Node's capabilities to allow inquiry of the intersection + * coordinate information. + * @see PickTool#setCapabilities + */ + public static final int INTERSECT_COORD = 0x1002; + + /** + * Flag to pass to setCapabilities(Node, int) to set + * the Node's capabilities to allow inquiry of all intersection + * information. + * @see PickTool#setCapabilities + */ + public static final int INTERSECT_FULL = 0x1004; + + /* ============================ METHODS ============================ */ + + /** + * Constructor with BranchGroup to be picked. + */ + public PickTool (BranchGroup b) { + pickRootBG = b; + } + + /** Returns the BranchGroup to be picked if the tool was initialized + with a BranchGroup, null otherwise. + */ + public BranchGroup getBranchGroup() { + return pickRootBG; + } + + /** + * Constructor with the Locale to be picked. + */ + public PickTool (Locale l) { + pickRootL = l; + } + + /** + * Returns the Locale to be picked if the tool was initialized with + * a Locale, null otherwise. + */ + public Locale setBranchGroup (Locale l) { + return pickRootL; + } + + /** + * Sets the capabilities on the Node and it's components to allow + * picking at the specified detail level. + *

+ * Note that by default all com.sun.j3d.utils.geometry.Primitive + * objects with the same parameters share their geometry (e.g., + * you can have 50 spheres in your scene, but the geometry is + * stored only once). Therefore the capabilities of the geometry + * are also shared, and once a shared node is live, the + * capabilities cannot be changed. To assign capabilities to + * Primitives with the same parameters, either set the + * capabilities before the primitive is set live, or specify the + * Primitive.GEOMETRY_NOT_SHARED constructor parameter when + * creating the primitive. + * @param node The node to modify + * @param level The capability level, must be one of INTERSECT_TEST, + * INTERSECT_COORD or INTERSECT_FULL + * @throws IllegalArgumentException if Node is not a Shape3D or Morph or + * if the flag value is not valid. + * @throws javax.media.j3d.RestrictedAccessException if the node is part + * of a live or compiled scene graph. */ + static public void setCapabilities(Node node, int level) { + if (node instanceof Morph) { + Morph morph = (Morph) node; + switch (level) { + case INTERSECT_FULL: + /* intentional fallthrough */ + case INTERSECT_COORD: + morph.setCapability(Morph.ALLOW_GEOMETRY_ARRAY_READ); + /* intentional fallthrough */ + case INTERSECT_TEST: + break; + default: + throw new IllegalArgumentException("Improper level"); + } + double[] weights = morph.getWeights(); + for (int i = 0; i < weights.length; i++) { + GeometryArray ga = morph.getGeometryArray(i); + setCapabilities(ga, level); + } + } else if (node instanceof Shape3D) { + Shape3D shape = (Shape3D) node; + switch (level) { + case INTERSECT_FULL: + /* intentional fallthrough */ + case INTERSECT_COORD: + shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); + /* intentional fallthrough */ + case INTERSECT_TEST: + break; + default: + throw new IllegalArgumentException("Improper level"); + } + for (int i = 0; i < shape.numGeometries(); i++) { + Geometry geo = shape.getGeometry(i); + if (geo instanceof GeometryArray) { + setCapabilities((GeometryArray)geo, level); + } else if (geo instanceof CompressedGeometry) { + setCapabilities((CompressedGeometry)geo, level); + } + } + } else { + throw new IllegalArgumentException("Improper node type"); + } + } + + static private void setCapabilities(GeometryArray ga, int level) { + switch (level) { + case INTERSECT_FULL: + ga.setCapability(GeometryArray.ALLOW_COLOR_READ); + ga.setCapability(GeometryArray.ALLOW_NORMAL_READ); + ga.setCapability(GeometryArray.ALLOW_TEXCOORD_READ); + /* intential fallthrough */ + case INTERSECT_COORD: + ga.setCapability(GeometryArray.ALLOW_COUNT_READ); + ga.setCapability(GeometryArray.ALLOW_FORMAT_READ); + ga.setCapability(GeometryArray.ALLOW_COORDINATE_READ); + /* intential fallthrough */ + case INTERSECT_TEST: + ga.setCapability(GeometryArray.ALLOW_INTERSECT); + break; + } + if (ga instanceof IndexedGeometryArray) { + setCapabilities((IndexedGeometryArray)ga, level); + } + } + + static private void setCapabilities(IndexedGeometryArray iga, int level) { + switch (level) { + case INTERSECT_FULL: + iga.setCapability(IndexedGeometryArray.ALLOW_COLOR_INDEX_READ); + iga.setCapability(IndexedGeometryArray.ALLOW_NORMAL_INDEX_READ); + iga.setCapability(IndexedGeometryArray.ALLOW_TEXCOORD_INDEX_READ); + /* intential fallthrough */ + case INTERSECT_COORD: + iga.setCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_READ); + /* intential fallthrough */ + case INTERSECT_TEST: + break; + } + } + + static private void setCapabilities(CompressedGeometry cg, int level) { + switch (level) { + case INTERSECT_FULL: + /* intential fallthrough */ + case INTERSECT_COORD: + cg.setCapability(CompressedGeometry.ALLOW_GEOMETRY_READ); + /* intential fallthrough */ + case INTERSECT_TEST: + cg.setCapability(CompressedGeometry.ALLOW_INTERSECT); + break; + } + } + + // Methods used to define the pick shape + + /** Sets the pick shape to a user-provided PickShape object + * @param ps The pick shape to pick against. + * @param startPt The start point to use for distance calculations + */ + public void setShape (PickShape ps, Point3d startPt) { + this.pickShape = ps; + this.start = startPt; + userDefineShape = (ps != null); + } + + /** Sets the pick shape to use a user-provided Bounds object + * @param bounds The bounds to pick against. + * @param startPt The start point to use for distance calculations + */ + public void setShapeBounds (Bounds bounds, Point3d startPt) { + this.pickShape = (PickShape) new PickBounds (bounds); + this.start = startPt; + userDefineShape = true; + } + + /** Sets the picking detail mode. The default is BOUNDS. + * @param mode One of BOUNDS, GEOMETRY, GEOMETRY_INTERSECT_INFO, or + * @exception IllegalArgumentException if mode is not a legal value + */ + public void setMode (int mode) { + if ((mode != BOUNDS) && (mode != GEOMETRY) && + (mode != GEOMETRY_INTERSECT_INFO)) { + throw new java.lang.IllegalArgumentException(); + } + this.mode = mode; + } + + /** Gets the picking detail mode. + */ + public int getMode () { + return mode; + } + + /** Sets the pick shape to a PickRay. + * @param start The start of the ray + * @param dir The direction of the ray + */ + public void setShapeRay (Point3d start, Vector3d dir) { + this.pickShape = (PickShape) new PickRay (start, dir); + this.start = start; + userDefineShape = true; + } + + /** Sets the pick shape to a PickSegment. + @param start The start of the segment +p @param end The end of the segment + */ + public void setShapeSegment (Point3d start, Point3d end) { + this.pickShape = (PickShape) new PickSegment (start, end); + this.start = start; + userDefineShape = true; + } + + /** Sets the pick shape to a capped PickCylinder + * @param start The start of axis of the cylinder + * @param end The end of the axis of the cylinder + * @param radius The radius of the cylinder + */ + public void setShapeCylinderSegment (Point3d start, Point3d end, + double radius) { + this.pickShape = (PickShape) + new PickCylinderSegment (start, end, radius); + this.start = start; + userDefineShape = true; + } + + /** Sets the pick shape to an infinite PickCylinder. + * @param start The start of axis of the cylinder + * @param dir The direction of the axis of the cylinder + * @param radius The radius of the cylinder + */ + public void setShapeCylinderRay (Point3d start, Vector3d dir, + double radius) { + this.pickShape = (PickShape) new PickCylinderRay (start, dir, radius); + this.start = start; + userDefineShape = true; + } + + /** Sets the pick shape to a capped PickCone + * @param start The start of axis of the cone + * @param end The end of the axis of the cone + * @param angle The angle of the cone + */ + public void setShapeConeSegment (Point3d start, Point3d end, + double angle) { + this.pickShape = (PickShape) new PickConeSegment (start, end, angle); + this.start = start; + userDefineShape = true; + } + + /** Sets the pick shape to an infinite PickCone. + * @param start The start of axis of the cone + * @param dir The direction of the axis of the cone + * @param angle The angle of the cone + */ + public void setShapeConeRay (Point3d start, Vector3d dir, + double angle) { + this.pickShape = (PickShape) new PickConeRay (start, dir, angle); + this.start = start; + userDefineShape = true; + } + + /** Returns the PickShape for this object. */ + public PickShape getPickShape () { + return pickShape; + } + + /** Returns the start postion used for distance measurement. */ + public Point3d getStartPosition () { + return start; + } + + /** Selects all the nodes that intersect the PickShape. + @return An array of PickResult objects which will contain + information about the picked instances. null if nothing was + picked. + */ + public PickResult[] pickAll () { + PickResult[] retval = null; + switch (mode) { + case BOUNDS: + retval = pickAll(pickShape); + break; + case GEOMETRY: + retval = pickGeomAll(pickShape); + break; + case GEOMETRY_INTERSECT_INFO: + retval = pickGeomAllIntersect(pickShape); + break; + default: + throw new java.lang.InternalError("Invalid pick mode"); + } + return retval; + } + + /** Select one of the nodes that intersect the PickShape + @return An array of PickResult objects which will contain + information about the picked instances. null if nothing + was picked. + */ + public PickResult pickAny () { + PickResult retval = null; + switch (mode) { + case BOUNDS: + retval = pickAny(pickShape); + break; + case GEOMETRY: + retval = pickGeomAny(pickShape); + break; + case GEOMETRY_INTERSECT_INFO: + retval = pickGeomAnyIntersect(pickShape); + break; + default: + throw new java.lang.InternalError("Invalid pick mode"); + } + return retval; + } + + /** Select all the nodes that intersect the + PickShape, returned sorted. The "closest" object will be returned first. + See note above to see how "closest" is determined. +

+ @return An array of PickResult objects which will contain + information + about the picked instances. null if nothing was picked. + */ + public PickResult[] pickAllSorted () { + PickResult[] retval = null; + + // System.out.println ("PickTool.pickAllSorted."); + + switch (mode) { + case BOUNDS: + // System.out.println ("PickTool.pickAllSorted : Bounds"); + retval = pickAllSorted(pickShape); + break; + case GEOMETRY: + // System.out.println ("PickTool.pickAllSorted : Geometry"); + // TODO - BugId 4351050. + // pickGeomAllSorted is broken for PickCone and PickCylinder : + // The current Shape3D.intersect() API doesn't return the distance for + // PickCone and PickCylinder. + // 2) TODO - BugId 4351579. + // pickGeomClosest is broken for multi-geometry Shape3D node : + // The current Shape3D.intersect() API does't return the closest intersected + // geometry. + retval = pickGeomAllSorted(pickShape); + + break; + case GEOMETRY_INTERSECT_INFO: + // System.out.println ("PickShape " + pickShape); + // System.out.println ("PickTool.pickAllSorted : GEOMETRY_INTERSECT_INFO"); + retval = pickGeomAllSortedIntersect(pickShape); + break; + default: + throw new java.lang.InternalError("Invalid pick mode"); + } + return retval; + } + + /** Select the closest node that + intersects the PickShape. See note above to see how "closest" is + determined. +

+ @return An array of PickResult objects which will contain + information about the picked instances. null if nothing + was picked. + */ + public PickResult pickClosest () { + PickResult retval = null; + switch (mode) { + case BOUNDS: + retval = pickClosest(pickShape); + break; + case GEOMETRY: + // System.out.println("pickCloset -- Geometry based picking"); + // 1) TODO - BugId 4351050. + // pickGeomClosest is broken for PickCone and PickCylinder : + // The current Shape3D.intersect() API doesn't return the distance for + // PickCone and PickCylinder. + // 2) TODO - BugId 4351579. + // pickGeomClosest is broken for multi-geometry Shape3D node : + // The current Shape3D.intersect() API does't return the closest intersected + // geometry. + retval = pickGeomClosest(pickShape); + + break; + case GEOMETRY_INTERSECT_INFO: + // System.out.println ("PickShape " + pickShape); + // System.out.println ("PickTool.pickClosest : GEOMETRY_INTERSECT_INFO"); + retval = pickGeomClosestIntersect(pickShape); + break; + default: + throw new java.lang.InternalError("Invalid pick mode"); + } + return retval; + } + + private PickResult[] pickAll (PickShape pickShape) { + PickResult[] pr = null; + SceneGraphPath[] sgp = null; + + if (pickRootBG != null) { + sgp = pickRootBG.pickAll (pickShape); + } else if (pickRootL != null) { + sgp = pickRootL.pickAll (pickShape); + } + if (sgp == null) return null; // no match + + // Create PickResult array + pr = new PickResult [sgp.length]; + for (int i=0;i 1) { + return sortPickResults (npr, distance); + } else { // Don't have to sort if only one item + return npr; + } + } + + private PickResult pickGeomAny (PickShape pickShape) { + Node obj = null; + int i; + SceneGraphPath[] sgpa = null; + + if (pickRootBG != null) { + sgpa = pickRootBG.pickAll(pickShape); + } else if (pickRootL != null) { + sgpa = pickRootL.pickAll(pickShape); + } + + if (sgpa == null) return null; // no match + + for(i=0; i 0) { + found[i] = true; + cnt++; + } + } + + if (cnt == 0) return null; // no match + + PickResult[] newpr = new PickResult[cnt]; + cnt = 0; // reset for reuse. + for(i=0; i 0) { + // System.out.println ("numIntersection " + numIntersection); + found[i] = true; + double minDist; + double tempDist; + + int minIndex; + boolean needToSwap = false; + minDist = pr[i].getIntersection(0).getDistance(); + minIndex = 0; + for(int j=1; j tempDist) { + minDist = tempDist; + minIndex = j; + needToSwap = true; + } + } + + //Swap if necc. + if(needToSwap) { + // System.out.println ("Swap is needed"); + PickIntersection pi0 = pr[i].getIntersection(0); + PickIntersection piMin = pr[i].getIntersection(minIndex); + pr[i].intersections.set(0, piMin); + pr[i].intersections.set(minIndex, pi0); + } + + distArr[i] = pr[i].getIntersection(0).getDistance(); + cnt++; + } + } + + + // System.out.println ("PickTool.pickGeomAllSortedIntersect: geometry intersect check " + // + " cnt " + cnt); + + + if (cnt == 0) return null; // no match + + PickResult[] npr = new PickResult[cnt]; + double[] distance = new double[cnt]; + cnt = 0; // reset for reuse. + for(i=0; i 1) { + return sortPickResults (npr, distance); + } else { // Don't have to sort if only one item + return npr; + } + } + + private PickResult pickGeomClosestIntersect(PickShape pickShape) { + PickResult[] pr = pickGeomAllSortedIntersect(pickShape); + /* + System.out.println ("PickTool.pickGeomClosestIntersect: pr.length " + + pr.length); + for(int i=0;i 0) { + return pr; + } + } + + return null; + } + + // ================================================================ + // Sort Methods + // ================================================================ + private PickResult[] sortPickResults (PickResult[] pr, double[] dist) { + int[] pos = new int [pr.length]; + PickResult[] prsorted = new PickResult [pr.length]; + + // Initialize position array + for (int i=0; i + * 1. Create your scene graph. + *

+ * 2. Create this behavior with root and canvas. + *

+ *

+ *	PickRotateBehavior behavior = new PickRotateBehavior(canvas, root, bounds);
+ *      root.addChild(behavior);
+ * 
+ *

+ * The above behavior will monitor for any picking events on + * the scene graph (below root node) and handle mouse drags on pick hits. + * Note the root node can also be a subgraph node of the scene graph (rather + * than the topmost). + */ + +public class PickRotateBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseRotate drag; +// int pickMode = PickTool.BOUNDS; + private PickingCallback callback=null; + private TransformGroup currentTG; + + /** + * Creates a pick/rotate behavior that waits for user mouse events for + * the scene graph. This method has its pickMode set to BOUNDS picking. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickRotateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + drag = new MouseRotate(MouseRotate.MANUAL_WAKEUP); + drag.setTransformGroup(currGrp); + currGrp.addChild(drag); + drag.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/rotate behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickTool.BOUNDS, PickTool.GEOMETRY or + * PickTool.GEOMETRY_INTERSECT_INFO. + * @see PickTool#setMode + **/ + + public PickRotateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds, + int pickMode){ + super(canvas, root, bounds); + drag = new MouseRotate(MouseRotate.MANUAL_WAKEUP); + drag.setTransformGroup(currGrp); + currGrp.addChild(drag); + drag.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.setMode(pickMode); + } + + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (!mevent.isMetaDown() && !mevent.isAltDown()){ + + pickCanvas.setShapeLocation(xpos, ypos); + PickResult pr = pickCanvas.pickClosest(); + if ((pr != null) && + ((tg = (TransformGroup)pr.getNode(PickResult.TRANSFORM_GROUP)) + != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + drag.setTransformGroup(tg); + drag.wakeup(); + currentTG = tg; + // free the PickResult + freePickResult(pr); + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + } + + /** + * Callback method from MouseRotate + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.ROTATE, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + drag.setupCallback( null ); + else + drag.setupCallback( this ); + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java new file mode 100644 index 0000000..8f7d716 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickTranslateBehavior.java @@ -0,0 +1,157 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking.behaviors; + +import com.sun.j3d.utils.picking.*; +import com.sun.j3d.utils.behaviors.mouse.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * A mouse behavior that allows user to pick and translate scene graph objects. + * Common usage: 1. Create your scene graph. 2. Create this behavior with + * the root and canvas. See PickRotateBehavior for more details. + */ + +public class PickTranslateBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseTranslate translate; + private PickingCallback callback = null; + private TransformGroup currentTG; + + /** + * Creates a pick/translate behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickTranslateBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + translate = new MouseTranslate(MouseBehavior.MANUAL_WAKEUP); + translate.setTransformGroup(currGrp); + currGrp.addChild(translate); + translate.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/translate behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickTool.BOUNDS, PickTool.GEOMETRY or + * PickTool.GEOMETRY_INTERSECT_INFO. + * @see PickTool#setMode + **/ + public PickTranslateBehavior(BranchGroup root, Canvas3D canvas, + Bounds bounds, int pickMode) { + super(canvas, root, bounds); + translate = new MouseTranslate(MouseBehavior.MANUAL_WAKEUP); + translate.setTransformGroup(currGrp); + currGrp.addChild(translate); + translate.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.setMode(pickMode); + } + + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (!mevent.isAltDown() && mevent.isMetaDown()){ + + pickCanvas.setShapeLocation(xpos, ypos); + PickResult pr = pickCanvas.pickClosest(); + if ((pr != null) && + ((tg = (TransformGroup)pr.getNode(PickResult.TRANSFORM_GROUP)) + != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + + translate.setTransformGroup(tg); + translate.wakeup(); + currentTG = tg; + freePickResult(pr); + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + + } + + /** + * Callback method from MouseTranslate + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.TRANSLATE, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + translate.setupCallback( null ); + else + translate.setupCallback( this ); + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java new file mode 100644 index 0000000..1db2b31 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickZoomBehavior.java @@ -0,0 +1,155 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking.behaviors; + +import com.sun.j3d.utils.picking.*; +import com.sun.j3d.utils.behaviors.mouse.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.media.j3d.*; +import javax.vecmath.*; + + +/** + * A mouse behavior that allows user to pick and zoom scene graph objects. + * Common usage: 1. Create your scene graph. 2. Create this behavior with + * the root and canvas. See PickRotateBehavior for more details. + */ + +public class PickZoomBehavior extends PickMouseBehavior implements MouseBehaviorCallback { + MouseZoom zoom; + private PickingCallback callback = null; + private TransformGroup currentTG; + + /** + * Creates a pick/zoom behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + **/ + + public PickZoomBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds){ + super(canvas, root, bounds); + zoom = new MouseZoom(MouseBehavior.MANUAL_WAKEUP); + zoom.setTransformGroup(currGrp); + currGrp.addChild(zoom); + zoom.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + } + + /** + * Creates a pick/zoom behavior that waits for user mouse events for + * the scene graph. + * @param root Root of your scene graph. + * @param canvas Java 3D drawing canvas. + * @param bounds Bounds of your scene. + * @param pickMode specifys PickTool.BOUNDS, PickTool.GEOMETRY or + * PickTool.GEOMETRY_INTERSECT_INFO. + * @see PickTool#setMode + */ + public PickZoomBehavior(BranchGroup root, Canvas3D canvas, Bounds bounds, + int pickMode) { + super(canvas, root, bounds); + zoom = new MouseZoom(MouseBehavior.MANUAL_WAKEUP); + zoom.setTransformGroup(currGrp); + currGrp.addChild(zoom); + zoom.setSchedulingBounds(bounds); + this.setSchedulingBounds(bounds); + this.setMode(pickMode); + } + + /** + * Update the scene to manipulate any nodes. This is not meant to be + * called by users. Behavior automatically calls this. You can call + * this only if you know what you are doing. + * + * @param xpos Current mouse X pos. + * @param ypos Current mouse Y pos. + **/ + + public void updateScene(int xpos, int ypos){ + TransformGroup tg = null; + + if (mevent.isAltDown() && !mevent.isMetaDown()){ + + pickCanvas.setShapeLocation(xpos, ypos); + PickResult pr = pickCanvas.pickClosest(); + if ((pr != null) && + ((tg = (TransformGroup)pr.getNode(PickResult.TRANSFORM_GROUP)) + != null) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_READ)) && + (tg.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE))){ + zoom.setTransformGroup(tg); + zoom.wakeup(); + currentTG = tg; + freePickResult(pr); + } else if (callback!=null) + callback.transformChanged( PickingCallback.NO_PICK, null ); + } + } + + /** + * Callback method from MouseZoom + * This is used when the Picking callback is enabled + */ + public void transformChanged( int type, Transform3D transform ) { + callback.transformChanged( PickingCallback.ZOOM, currentTG ); + } + + /** + * Register the class @param callback to be called each + * time the picked object moves + */ + public void setupCallback( PickingCallback callback ) { + this.callback = callback; + if (callback==null) + zoom.setupCallback( null ); + else + zoom.setupCallback( this ); + } +} + diff --git a/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickingCallback.java b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickingCallback.java new file mode 100644 index 0000000..fdb268b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/picking/behaviors/PickingCallback.java @@ -0,0 +1,76 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.picking.behaviors; + +import javax.media.j3d.TransformGroup; + +/** + * The PickingCallback interface allows a class to be notified when a + * picked object is moved. The class that is interested in object + * movement implements this interface, and the object created with + * that class is registered with the desired subclass of + * PickMouseBehavior using the setupCallback method. + * When the picked object moves, the registered object's + * transformChanged method is invoked. + */ + +public interface PickingCallback { + + public final static int ROTATE=0; + public final static int TRANSLATE=1; + public final static int ZOOM=2; + + /** + * The user made a selection but nothing was + * actually picked + */ + public final static int NO_PICK=3; + + /** + * Called by the Pick Behavior with which this callback + * is registered each time the Picked object is moved + */ + public void transformChanged( int type, TransformGroup tg ); +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/NamedObjectException.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/NamedObjectException.java new file mode 100644 index 0000000..73ed757 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/NamedObjectException.java @@ -0,0 +1,68 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * An error has occurred while processing a named object + */ +public class NamedObjectException extends java.lang.Exception { + + /** + * Creates new NoSuchNameException without detail message. + */ + public NamedObjectException() { + } + + + /** + * Constructs an NoSuchNameException with the specified detail message. + * @param msg the detail message. + */ + public NamedObjectException(String msg) { + super(msg); + } +} + + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/ObjectNotLoadedException.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/ObjectNotLoadedException.java new file mode 100644 index 0000000..ffe83c4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/ObjectNotLoadedException.java @@ -0,0 +1,68 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * The named object has not been loaded so it's instance can not be returned + */ +public class ObjectNotLoadedException extends java.lang.Exception { + + /** + * Creates new ObjectNotLoadedException without detail message. + */ + public ObjectNotLoadedException() { + } + + + /** + * Constructs an ObjectNotLoadedException with the specified detail message. + * @param msg the detail message. + */ + public ObjectNotLoadedException(String msg) { + super(msg); + } +} + + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileReader.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileReader.java new file mode 100644 index 0000000..5f9b182 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileReader.java @@ -0,0 +1,224 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +import java.io.File; +import java.io.IOException; + +import javax.media.j3d.VirtualUniverse; +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.Canvas3D; +import com.sun.j3d.utils.universe.ConfiguredUniverse; + +import com.sun.j3d.utils.scenegraph.io.retained.RandomAccessFileControl; + +/** + * Read Java3D BranchGraphs and/or Universe from a file. Individual branchgraphs or an + * entire Universe can be read and references (shared nodes and components) + * between the graphs are handled correctly. + */ +public class SceneGraphFileReader extends java.lang.Object { + + private RandomAccessFileControl fileControl; + + /** + * Creates new SceneGraphFileReader. + */ + public SceneGraphFileReader( java.io.File file ) throws IOException { + fileControl = new RandomAccessFileControl(); + fileControl.openFile( file ); + } + + /** + * Create and return a ConfiguredUniverse with the PlatformGeometry, ViewerAvatar, + * and Locales saved in the file. The MultiTransformGroup between the ViewingPlatform + * and the View is also restored. Universe configuration information is retrieved + * via ConfiguredUniverse.getConfigURL().

+ * If the file does not contain universe information, null is returned.

+ * + * @param attachBranchGraphs load and attach all the branchgraphs + * to the universe. + * @see ConfiguredUniverse#getConfigURL + */ + public ConfiguredUniverse readUniverse(boolean attachBranchGraphs ) throws IOException { + return fileControl.readUniverse( attachBranchGraphs, null ); + } + + /** + * Set the ClassLoader used to load the scene graph objects and + * deserialize user data + */ + public void setClassLoader( ClassLoader classLoader ) { + fileControl.setClassLoader( classLoader ); + } + + /** + * Get the ClassLoader used to load the scene graph objects and + * deserialize user data + */ + public ClassLoader getClassLoader() { + return fileControl.getClassLoader(); + } + + /** + * Create and return a ConfiguredUniverse with the PlatformGeometry, ViewerAvatar, + * and Locales saved in the file. The MultiTransformGroup between the ViewingPlatform + * and the View is also restored.

+ * If the file does not contain universe information, null is returned.

+ * + * @param attachBranchGraphs load and attach all the branchgraphs + * to the universe. + * @param canvas The canvas to be associated with the Universe. + */ + public ConfiguredUniverse readUniverse(boolean attachBranchGraphs, + Canvas3D canvas) throws IOException { + return fileControl.readUniverse( attachBranchGraphs, canvas ); + } + + /** + * Get the UserData in the File header + */ + public Object readUserData() throws IOException { + return fileControl.getUserData(); + } + + /** + * Get the Description of this file's contents + */ + public String readDescription() throws IOException { + return fileControl.readFileDescription(); + } + + /** + * Return the number of BranchGraphs in the file + */ + public int getBranchGraphCount() { + return fileControl.getBranchGraphCount(); + } + + /** + * Read the BranchGraph at index in the file. If the graph + * contains references to nodes in other BranchGraphs that have not already been + * loaded, they will also be loaded and returned.

+ * + * The requested graph will always be the first element in the array.

+ * + * The file index of all the Graphs can be discovered using getBranchGraphPosition.

+ * + * @param index The index of the Graph in the file. First graph is at index 0 + * + * @see #getBranchGraphPosition( BranchGroup graph ) + * + */ + public BranchGroup[] readBranchGraph(int index) throws IOException { + return fileControl.readBranchGraph( index ); + } + + /** + * Read and return all the branchgraphs in the file + */ + public BranchGroup[] readAllBranchGraphs() throws IOException { + return fileControl.readAllBranchGraphs(); + } + + /** + * Remove the IO system's reference to this branchgraph and all its nodes.

+ * + * References to all loaded graphs are maintained by the IO system in + * order to facilitate node and component sharing between the graphs.

+ * + * This call removes the references to graph index

+ * + * NOT CURRENTLY IMPLEMENTED + */ + public void dereferenceBranchGraph( BranchGroup graph ) { + throw new RuntimeException("Not implemented"); + } + + /** + * Given a BranchGraph that has been loaded return the index of the + * graph in the file. The the Branchgroup isn't found, -1 is returned. + */ + public int getBranchGraphPosition( BranchGroup graph ) { + return fileControl.getBranchGraphPosition( graph ); + } + + /** + * Read the userdata for the branchgraph at 'index' in the file + * + * @param index the index of the graph in the file + */ + public Object readBranchGraphUserData( int index ) throws IOException { + return fileControl.readBranchGraphUserData( index ); + } + + /** + * Return the names of all the named objects + */ + public String[] getNames() { + return fileControl.getNames(); + } + + /** + * Return the named object. + * + * @param name The name of the object + * + * @exception NamedObjectException is thrown if the name is not known to the system + * @exception ObjectNotLoadedException is thrown if the named object has not been loaded yet + */ + public SceneGraphObject getNamedObject( String name ) throws NamedObjectException, ObjectNotLoadedException { + return fileControl.getNamedObject( name ); + } + + /** + * Close the file and cleanup internal data structures + */ + public void close() throws IOException { + fileControl.close(); + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileWriter.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileWriter.java new file mode 100644 index 0000000..9c6907e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphFileWriter.java @@ -0,0 +1,157 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Iterator; + +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.CapabilityNotSetException; +import com.sun.j3d.utils.scenegraph.io.retained.RandomAccessFileControl; +import com.sun.j3d.utils.universe.SimpleUniverse; + +/** + * Write a (set) of Java3D BranchGraphs and/or Universe to a file. The BranchGraphs + * are stored in the order in which they are written, they can be read in any order + * using SceneGraphFileReader. + * + * The API handles Nodes and NodeComponents that are shared between seperate + * graphs. It will handle all Java3D 1.3 core classes and any user + * subclass of a Node or NodeComponent that implements the SceneGraphIO + * interface. + */ +public class SceneGraphFileWriter extends java.lang.Object { + + private RandomAccessFileControl fileControl; + private File file; + + /** Creates new SceneGraphFileWriter and opens the file for writing. + * + *

Writes the + * Java3D Universe structure to the file. This includes the number and position of + * the Locales, PlatformGeometry, ViewerAvatar, and the MultitransformGroup between + * the ViewingPlatform and the View. However this + * call does not write the content of the branch graphs unless writeUniverseContent is true. + * universe may be null. + * This call will overwrite any existing universe, fileDescription and + * userData in the file.

+ * + *

close() MUST be called when IO is complete. If close() is not called + * the file contents will be undefined.

+ * + * @param file The file to write the data to + * @param universe The SimpleUniverse to write + * @param writeUniverseContent If true, the content of the Locales will be written. + * Otherwise just the universe configuration data will be written. + * @param fileDescription A description of the file's content + * @param fileUserData User defined object + * + * @exception IOException Thrown if there are any IO errors + * @exception UnsupportedUniverseException Thrown if universe is not + * a supported universe class. Currently SimpleUniverse and ConfiguredUniverse + * are supported. + */ + public SceneGraphFileWriter( java.io.File file, + SimpleUniverse universe, + boolean writeUniverseContent, + String fileDescription, + java.io.Serializable fileUserData) throws IOException, UnsupportedUniverseException { + fileControl = new RandomAccessFileControl(); + this.file = file; + file.createNewFile(); + + if (!file.canWrite()) + throw new IOException( "Can not Write to File" ); + + fileControl.createFile( file, universe, writeUniverseContent, fileDescription, fileUserData ); + } + + /** + * Write the graph to the end of the file. + * + * close() MUST be called when IO is complete. If close() is not called + * the file contents will be undefined. + */ + public void writeBranchGraph( BranchGroup graph ) throws IOException { + writeBranchGraph( graph, null ); + } + + /** + * Write a branch graph and some user associated data to the + * end of the file. + * + * close() MUST be called when IO is complete. If close() is not called + * the file contents will be undefined. + */ + public void writeBranchGraph( BranchGroup graph, + java.io.Serializable data ) throws IOException { + fileControl.writeBranchGraph( graph, data ); + } + + /** + * Add a named reference to a SceneGraphObject in the file. + * + * object must have been written to the file before this method is + * called. If the object is not in the file a NamedObjectException will be thrown. + * + * Adding duplicate names will result in the old name being overwritten. + * Different names can reference the same object + */ + public void addObjectName( String name, SceneGraphObject object ) throws NamedObjectException { + fileControl.addNamedObject( name, object ); + } + + /** + * Close the file and cleanup internal data structures. + */ + public void close() throws IOException { + fileControl.close(); + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphIO.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphIO.java new file mode 100644 index 0000000..598c37c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphIO.java @@ -0,0 +1,112 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * Implement this interface in any classes that subclass a Java3D SceneGraphObject + * in order to have your class handled correctly by scenegraph.io. + * + * More information and example code is provided here. + * + * Classes that implement this interface MUST have a no-arg constructor + */ +public interface SceneGraphIO { + + /** + * The method is called before writeSGObject and gives the user the chance + * to create references to other Nodes and NodeComponents. + * + * References take the form of a nodeID, of type integer. Every SceneGraphObject + * is assigned a unique ID. + * + * The user must save the reference information in writeSGObject + * + * @param ref provides methods to create references to a SceneGraphObject + */ + public void createSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ); + + /** + * Within this method the user should restore references to the SceneGraphObjects + * whose nodeID's were created with createSceneGraphObjectReferences + * This method is called once the all objects in the scenegraph have been loaded. + * + * + * @param ref provides methods to resolve references to a SceneGraphObject + */ + public void restoreSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ); + + /** + * This method should store all the local state of the object and any references + * to other SceneGraphObjects into out. + * + * This is called after data for the parent SceneGraphObject has been written to + * the out. + * + * @param out the output stream + */ + public void writeSceneGraphObject( java.io.DataOutput out ) throws java.io.IOException; + + /** + * This is called after the object has been constructed and the superclass SceneGraphObject + * data has been read from in. + * + * The user should restore all state infomation written in writeSGObject + * + * @param in the input stream + */ + public void readSceneGraphObject( java.io.DataInput in ) throws java.io.IOException; + + /** + * Flag indicating for children of this object should be saved + * + * This method only has an effect if this is a subclass of Group. + * + * If this returns true then all children of this Group will be saved. + * + * If it returns false then the children are not saved. + */ + public boolean saveChildren(); +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphObjectReferenceControl.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphObjectReferenceControl.java new file mode 100644 index 0000000..34708be --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphObjectReferenceControl.java @@ -0,0 +1,69 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * Provides and resolves references to SceneGraphObjects to enable + * persistant references in user defined SceneGraphObjects implementing + * the SceneGraphIO interface. + */ +public interface SceneGraphObjectReferenceControl { + + /** + * Add a reference to the scenegraph object specified and return + * the nodeID for the object + * + * Use only during the save cycle + */ + public int addReference( javax.media.j3d.SceneGraphObject object ); + + /** + * Given a nodeID return the corresponding scene graph object. + * + * Use only during the load cycle + */ + public javax.media.j3d.SceneGraphObject resolveReference( int nodeID ); +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamReader.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamReader.java new file mode 100644 index 0000000..6b2c64a --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamReader.java @@ -0,0 +1,117 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +import java.io.InputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashMap; + +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.Canvas3D; +import com.sun.j3d.utils.scenegraph.io.retained.StreamControl; +import com.sun.j3d.utils.universe.ConfiguredUniverse; + +/** + * Read and create a (set) of Java3D BranchGraphs or Universe from a Java Stream. + */ +public class SceneGraphStreamReader extends java.lang.Object { + + private StreamControl control; + private DataInputStream in; + + /** Creates new SceneGraphStreamReader and reads the file header information */ + public SceneGraphStreamReader( InputStream stream ) throws IOException { + in = new DataInputStream( stream ); + control = new StreamControl( in ); + control.readStreamHeader(); + } + + /** + * Read and create the universe. If the BranchGraphs were written then + * they will be added to the universe before it is returned. + */ + public ConfiguredUniverse readUniverse() throws IOException { + return control.readUniverse(in, true, null); + } + + /** + * Read and create the universe. If the BranchGraphs were written then + * they will be added to the universe before it is returned. + * @param canvas The Canvas3D to associate with the universe. + */ + public ConfiguredUniverse readUniverse(Canvas3D canvas) throws IOException { + return control.readUniverse(in, true, canvas); + } + + /** + * Read and return the graph from the stream. + * namedObjects map will be updated with any objects that + * were named during the write process + */ + public BranchGroup readBranchGraph( HashMap namedObjects ) throws IOException { + return control.readBranchGraph( namedObjects ); + } + + /** + * Set the ClassLoader used to load the scene graph objects and + * deserialize user data + */ + public void setClassLoader( ClassLoader classLoader ) { + control.setClassLoader( classLoader ); + } + + /** + * Get the ClassLoader used to load the scene graph objects and + * deserialize user data + */ + public ClassLoader getClassLoader() { + return control.getClassLoader(); + } + + +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamWriter.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamWriter.java new file mode 100644 index 0000000..81eb89b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/SceneGraphStreamWriter.java @@ -0,0 +1,127 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +import java.io.File; +import java.io.IOException; +import java.io.DataOutputStream; +import java.util.HashMap; + +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.CapabilityNotSetException; +import javax.media.j3d.DanglingReferenceException; +import com.sun.j3d.utils.scenegraph.io.retained.StreamControl; +import com.sun.j3d.utils.universe.SimpleUniverse; + +/** + * Writes a Java3D SceneGraph to a Java OutputStream.

+ * Using this class to write to a FileOutputStream is not recommended. Use + * SceneGraphFileWriter instead to achieve maximum performance and flexibility. + */ +public class SceneGraphStreamWriter extends java.lang.Object { + + private StreamControl control; + private DataOutputStream out; + + /** Creates new SceneGraphStreamWriter that will write to the supplied stream */ + public SceneGraphStreamWriter(java.io.OutputStream outputStream ) throws IOException { + this.out = new java.io.DataOutputStream( outputStream ); + control = new StreamControl( out ); + control.writeStreamHeader(); + } + + + /** + * Write universe to the Stream.

+ * + * If writeContent is true then all BranchGraphs attached to the + * universe will be saved. If it is false then only the universe + * data structures will be output (PlatformGeometry, ViewerAvatar, Locales, + * and the MultiTransformGroup between the ViewingPlatform and the View).

+ * + * If writeContent is true then all the BranchGraphs + * attached to the Locales of the universe must have the + * ALLOW_DETACH capability set. If they do not, a CapabilityNotSetException + * will be thrown

+ * + * @param universe The universe to write + * @param writeContent Flag enabling the BranchGraphs to be written + * + * @exception IOException + * @exception UnsupportedUniverseException Thrown if the universe class is not + * supported by this implementation + */ + public void writeUniverse( SimpleUniverse universe, boolean writeContent ) throws IOException, UnsupportedUniverseException { + control.writeUniverse( out, universe, writeContent ); + } + + /** + * Write the entire graph to the stream.

+ * + * The API will correctly handle NodeComponents that are shared + * between seperate graphs. However Nodes cannot be referenced + * in other Graphs.

+ * + * If a reference to a Node in another graph is encountered a + * DanglingReferenceException will be thrown. + * + * namedObjects can contain a mapping between a key and a SceneGraphObject + * in the graph. During the read process this can be used to locate nodes + * in the graph. + */ + public void writeBranchGraph( BranchGroup graph, HashMap namedObjects ) throws IOException, DanglingReferenceException, NamedObjectException { + // TODO Add namedObjects to SymbolTable + control.addNamedObjects( namedObjects ); + control.writeBranchGraph( graph, null ); + } + + /** + * Close the SceneGraphStreamWriter, but does not close the Stream + */ + public void close() throws IOException { + control.close(); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnresolvedBehavior.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnresolvedBehavior.java new file mode 100644 index 0000000..6d152f2 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnresolvedBehavior.java @@ -0,0 +1,64 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * This Behavior is used in place of any behaviors which can not + * be instantiated when a scene graph is read. This behavior is + * always disabled when it initalizes. It just provides an indicator + * in the scene graph that a Behavior is missing. + * + * This normally means the Behavior is not present in the classpath. + */ +public class UnresolvedBehavior extends javax.media.j3d.Behavior { + + public void initialize() { + setEnable(false); + } + + public void processStimulus(java.util.Enumeration enumeration) { + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnsupportedUniverseException.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnsupportedUniverseException.java new file mode 100644 index 0000000..72ba99e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/UnsupportedUniverseException.java @@ -0,0 +1,71 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io; + +/** + * Thrown if the VirtualUniverse subclass is not supported + * by the writeUniverse calls. + * + * Currently only com.sun.j3d.utils.universe.SimpleUniverse is supported + */ +public class UnsupportedUniverseException extends java.lang.Exception { + + /** + * Creates new UnsupportedUniverseException without detail message. + */ + public UnsupportedUniverseException() { + } + + + /** + * Constructs an UnsupportedUniverseException with the specified detail message. + * @param msg the detail message. + */ + public UnsupportedUniverseException(String msg) { + super(msg); + } +} + + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/doc-files/extensibility.html b/src/classes/share/com/sun/j3d/utils/scenegraph/io/doc-files/extensibility.html new file mode 100644 index 0000000..f92b1d9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/doc-files/extensibility.html @@ -0,0 +1,189 @@ + + + + + + + + Java 3D scenegraph.io Extensibility + + +

+Using your own Classes with scenegraph.io +

+

The scenegraph.io APIs will handle the IO for all the core Java3D +SceneGraphObjects. However, if you create a subclass of one of these +objects and add it to your Scene Graph, the IO system, by default, +will not store any state information specific to your class.

+

The default behavior when an unrecognized SceneGraphObject class +is encountered is to traverse up the superclasses of the object until +a recognized Java3D class is located. The data structures for this +class are then used for IO. The system does store the class name of +the original object. +

For example: +


+public class MyBranchGroup extends javax.media.j3d.BranchGroup {
+    private int myData;
+    ....
+}
+
+

When the Scene Graph is written to a file and this node is +encountered, the superclass javax.media.j3d.BranchGroup will be used +to store all the state for the object so any children of +MyBranchGroup, the capabilities, etc. will be stored, but myData will +be lost. When the scene graph is loaded, MyBranchGroup will be +instantiated and will be populated with all the state from +BranchGroup but myData will have been lost.

+

To overcome this, the scenegraph.io API provides an interface for +you to implement in your own classes that provides the opportunity +for you to save the state of your classes during the IO processes. +This is the SceneGraphIO interface.

+

When the scenegraph is saved, the methods of SceneGraphIO are +called in this order +

+
    +
  1. createSceneGraphObjectReferences

    +
  2. saveChildren

    +
  3. writeSceneGraphObject

    +
+

During the load cycle the method call order is

+
    +
  1. Instantiate Object using default constructor

    +
  2. Populate object with state from superclasses

    +
  3. readSceneGraphObject

    +
  4. restoreSceneGraphObjectReferences

    +
+

Within each method you need to perform the following actions: +

    +
  • createSceneGraphObjectReferences If your object has + references to other SceneGraphObjects then you need to obtain an + object reference (int) for each reference using the + SceneGraphReferenceControl object passed as a parameter to this + method. If you don't have references to other SceneGraphObjects then + no action is required.

    +
  • saveChildren If your object is a subclass of Group and you + want the scenegraph.io package to save the children then this must + return true. If it returns false, the object will be saved but not + its children.

    +
  • writeSceneGraphObject In this method you must write all the + state information for your class, including the object references + obtained in createSceneGraphObjectReferences, to the DataOutput + stream passed into this method.

    +
  • readSceneGraphObject By the time this method is called your + class has been instantiated and the state information in the Java3D + superclass will have been loaded. You should load all the state + information you saved for your class.

    +
  • restoreSceneGraphObjectReferences is called once all the + SceneGraph objects have been loaded and allows you to restore the + references to the other SceneGraph objects.

    +
+

Here are some examples. Only the parts of the source pertaining to +IO are show....

+

Behavior Example

+
+public class BehaviorIO extends javax.media.j3d.Behavior implements SceneGraphIO + private TransformGroup target; // The TG on which this behavior acts + private int targetRef; // Object Reference for target + + public void createSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ) { + targetRef = ref.addReference( target ); + } + + public void restoreSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ) { + target = (TransformGroup)ref.resolveReference( targetRef ); + } + + public void writeSceneGraphObject( java.io.DataOutput out ) throws IOException { + out.writeInt( targetRef ); + } + + public void readSceneGraphObject( java.io.DataInput in ) throws IOException { + targetRef = in.readInt(); + } + + // This has no effect as this is not a subclass of Group + public boolean saveChildren() { + return true; + } +
+

+`BlackBox' Group Example +

+This example is a Group node that creates its subgraph during +its instantiation. An example where you might use this is to +represent some geometry that is loaded from an external file format +such a OpenFLT. +

+public class House extends Group implements SceneGraphIO {
+    public House() {
+        super();
+        this.addChild( OpenFlightLoader.load( "/dir/house.flt" );
+    }
+
+    public void createSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ) {
+        // No references
+    }
+
+    public void restoreSceneGraphObjectReferences( SceneGraphObjectReferenceControl ref ) {
+        // No references
+    }
+
+    public void writeSceneGraphObject( java.io.DataOutput out ) throws IOException {
+        // No local state
+    }
+
+    public void readSceneGraphObject( java.io.DataInput in ) throws IOException {
+        // No local state
+    }
+
+    public boolean saveChildren() {
+        // Don't save the children as they will be restored by the openflightloader
+        return false;
+    }
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/package.html b/src/classes/share/com/sun/j3d/utils/scenegraph/io/package.html new file mode 100644 index 0000000..2cbc2ac --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/package.html @@ -0,0 +1,105 @@ + + + + + + + + + + +

This package provides a Java3D SceneGraph IO capability. +The API supports IO of a scenegraph to and from a Java Stream and/or +RandomAccessFile. The features offered for these two io systems are +somewhat different.

+

The SceneGraphFileReader and SceneGraphFileWriter classes provide +IO to and from a RandomAccessFile. They allow a universe and/or +multiple BranchGraphs to be written to the file with Node's and +NodeComponent's shared between the separate graphs. The graphs can be +read in any order.

+

SceneGraphStreamReader and SceneGraphStreamWriter classes provide +IO to and from a Stream. These classes allow a universe and/or +multiple BranchGraphs to be passed over stream. In contrast to the +FileReader/Writer sharing of Node's is NOT supported between graphs +by the API. Sharing of node components is supported. If your +application requires references to Nodes in other graphs (such as +SharedGroups) the application must handle the references using the +namedObjects constructs.

+

Note : If you use SceneGraphStreamWriter class to write to a +FileOutputStream the resulting file cannot be read using the +SceneGraphFileReader, the converse is also true, you can not use a +FileInputStream to load a file written by SceneGraphFileWriter. +

+The package supports the IO of all the Java3D 1.3 core classes +and many of the utilities. It also includes interfaces which can be +implemented to allow user defined subclasses of SceneGraphObjects to +be stored. Information on the extensibility can be found +here +

+The package has a number of properties which can be used to control the IO +behavior

+ +

+j3d.io.UseSuperClassIfNoChildClass when this property is present the load +operation will attempt to avoid failure if Scene Graph nodes are not present +in the classpath. For example if a developer has subclassed BranchGroup with a +class called MyBG but has not +implemented the SceneGraphIO interface when the API saves the graph the +superclass (ie BranchGroup) data will be stored. When the scene is loaded +normally MyBG must be in the classpath otherwise the load will fail. If this +property is set then the superclass node (ie BranchGroup) will be instantiated +when MyBG is missing. Obviously, if MyBG contained any state information +then this will be lost.

+ +j3d.io.ImageCompression this can be set to None, GZIP, JPEG and tells the +IO system to compress images in the .j3f file using the prescribed technique. In +the future this will be extended to support all the formats available in +javax.imageio in JDK 1.4. +

+

+ + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/Controller.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/Controller.java new file mode 100644 index 0000000..196ec41 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/Controller.java @@ -0,0 +1,943 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.retained; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.io.DataOutput; +import java.io.DataInput; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.IOException; +import java.util.ListIterator; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Iterator; + +import javax.media.j3d.SceneGraphObject; +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SharedGroup; +import javax.media.j3d.Bounds; +import javax.media.j3d.CapabilityNotSetException; +import javax.media.j3d.BoundingBox; +import javax.media.j3d.BoundingSphere; +import javax.media.j3d.BoundingPolytope; +import javax.media.j3d.Transform3D; +import javax.media.j3d.Canvas3D; +import javax.vecmath.*; +import com.sun.j3d.utils.universe.SimpleUniverse; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.SceneGraphObjectState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.NullSceneGraphObjectState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.*; +import com.sun.j3d.utils.scenegraph.io.state.com.sun.j3d.utils.universe.SimpleUniverseState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.ImageComponentState; +import com.sun.j3d.utils.scenegraph.io.UnsupportedUniverseException; +import com.sun.j3d.utils.scenegraph.io.NamedObjectException; +import com.sun.j3d.utils.scenegraph.io.ObjectNotLoadedException; +import com.sun.j3d.utils.universe.SimpleUniverse; +import com.sun.j3d.utils.universe.ConfiguredUniverse; + +/** + * Provides code to control the reading and writing of Java3D objects to and + * from any Java IO mechanism. + */ +public abstract class Controller extends java.lang.Object { + + + protected static final long SYMBOL_TABLE_PTR = 30; // long - 8 bytes + protected static final long BG_DIR_PTR = 38; // long - 8 bytes + protected static final long NAMES_OBJECTS_TABLE_PTR = 46; // long - 8 bytes + protected static final long NODE_TYPES_PTR = 52; // long - 8 bytes + protected static final long UNIVERSE_CONFIG_PTR = 60; // long - 8 bytes + protected static final long BRANCH_GRAPH_COUNT = 68; // int - 4 bytes + protected static final long FILE_DESCRIPTION = 72; // UTF - n bytes + + protected SymbolTable symbolTable; + protected NullSceneGraphObjectState nullObject = new NullSceneGraphObjectState( null, this ); + + /** + * The currentFileVersion being read + */ + protected int currentFileVersion; + + /** + * The File version which will be written + * + * 1 = Java3D 1.3 beta 1 + * 2 = Java3D 1.3 FCS, 1) fix to allow skipping user data written via + SceneGraphIO interface + 2) Add missing duplicateOnCloneTree flag + (bug 4690159) + */ + protected int outputFileVersion = 2; + + protected ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + + /** + * If true when loading a scenegraph that contains nodes who's classes + * are not in the classpath then use then first Java3D core superclass + * to instantiate the node. + * + * If false a SGIORuntimeException will be thrown when classes cannot be + * located + */ + private boolean useSuperClass = false; + + private int imageCompression = ImageComponentState.JPEG_COMPRESSION; + + /** Creates new Controller */ + public Controller() { + try { + if ( System.getProperty("j3d.io.UseSuperClassIfNoChildClass")!=null) + useSuperClass = true; + + String imageC = System.getProperty("j3d.io.ImageCompression"); + if (imageC!=null) { + if (imageC.equalsIgnoreCase("None")) + imageCompression = ImageComponentState.NO_COMPRESSION; + else if (imageC.equalsIgnoreCase("GZIP")) + imageCompression = ImageComponentState.GZIP_COMPRESSION; + else if (imageC.equalsIgnoreCase("JPEG")) + imageCompression = ImageComponentState.JPEG_COMPRESSION; + } + } catch( Exception e ) {} + + } + + public final SymbolTable getSymbolTable() { + return symbolTable; + } + + /** + * Get the file version that we should write + */ + public int getOutputFileVersion() { + return outputFileVersion; + } + + /** + * Get the file version of the file we are reading + */ + public int getCurrentFileVersion() { + return currentFileVersion; + } + + /** + * Create a new state object and check for a pre-existing symbol table + * entry + */ + public SceneGraphObjectState createState( SceneGraphObject obj ) { + return createState( obj, symbolTable.getSymbol( obj ) ); + } + + /** + * Given a scene graph object instantiate the correct State class + * for that object. If the symbol already exists (is not null) then + * increment the reference count, otherwise create a new symbol. + */ + public SceneGraphObjectState createState( SceneGraphObject obj, SymbolTableData symbol ) { + if (obj==null) return nullObject; + + if (symbol!=null) { + symbol.incrementReferenceCount(); + symbolTable.setBranchGraphID( symbol ); + if (symbol.getNodeState()!=null) + return symbol.getNodeState(); + } else + symbol = symbolTable.createSymbol( obj ); + + return createState( symbol ); + } + + /** + * Return the state class for the SceneGraphObject, creating one if it does + * not already exist + */ + public SceneGraphObjectState createState( SymbolTableData symbol ) { + SceneGraphObject obj = symbol.getJ3dNode(); + if (obj==null) return nullObject; + + String name = obj.getClass().getName(); + SceneGraphObjectState ret; + + try { + Class state = Class.forName( "com.sun.j3d.utils.scenegraph.io.state."+name+"State", true, classLoader ); + ret = constructStateObj( symbol, state, obj.getClass() ); + } catch(ClassNotFoundException e) { + ret = checkSuperClasses( symbol ); + if (!(obj instanceof com.sun.j3d.utils.scenegraph.io.SceneGraphIO)) + System.out.println("Could not find "+"com.sun.j3d.utils.scenegraph.io.state."+name+"State, using superclass "+ret.getClass().getName() ); + if (ret==null) + throw new SGIORuntimeException( "No State class for "+ + obj.getClass().getName() ); + } + + symbol.nodeState = ret; + + return ret; + } + private SceneGraphObjectState constructStateObj( SymbolTableData symbol, + Class state, + Class objClass ) { + + SceneGraphObjectState ret = null; + + try { + Constructor construct = state.getConstructor( + new Class[] { com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData.class, + com.sun.j3d.utils.scenegraph.io.retained.Controller.class + } ); + ret = (SceneGraphObjectState)construct.newInstance( + new Object[]{ symbol, this } ); + + } catch( NoSuchMethodException ex ) { + System.out.println("Looking for Constructor ("+symbol.j3dNode.getClass().getName()+", Controller )"); + throw new SGIORuntimeException( "1 Broken State class for "+ + state.getName() ); + } catch( InvocationTargetException exc ) { + exc.printStackTrace(); + throw new SGIORuntimeException( "2 Broken State class for "+ + state.getName() ); + } catch( IllegalAccessException exce ) { + throw new SGIORuntimeException( "3 Broken State class for "+ + state.getName() ); + } catch( InstantiationException excep ) { + throw new SGIORuntimeException( "4 Broken State class for "+ + state.getName() ); + } + + return ret; + } + + /** + * Check to see if any of the superclasses of obj are + * known to the Java3D IO package + */ + private SceneGraphObjectState checkSuperClasses( SymbolTableData symbol ) { + + Class cl = symbol.j3dNode.getClass().getSuperclass(); + Class state = null; + boolean finished = false; + + + while( cl != null & !finished ) { + String name = cl.getName(); + //System.out.println("Got superclass "+name); + try { + state = Class.forName( "com.sun.j3d.utils.scenegraph.io.state."+name+"State", true, classLoader ); + } catch(ClassNotFoundException e) { + state = null; + } + + if (state!=null) + finished = true; + else + cl = cl.getSuperclass(); + } + + if (cl==null) + throw new SGIORuntimeException( "Unsupported class "+symbol.j3dNode.getClass().getName() ); + + return constructStateObj( symbol, state, cl ); + } + + + public void writeObject( DataOutput out, SceneGraphObjectState obj ) throws IOException { + + int classID = getStateID( obj ); + + out.writeInt( classID ); // Node class id + + if (classID==0) { + out.writeUTF( obj.getClass().getName() ); + } + + obj.writeObject( out ); + } + + public SceneGraphObjectState readObject( DataInput in ) throws IOException { + int classID = in.readInt(); + + SceneGraphObjectState state = null; + + if (classID==-1) + return nullObject; + else if (classID==0) { + String stateClassName = in.readUTF(); + + try { + Class cl = Class.forName( stateClassName, true, classLoader ); + // System.out.println("Got class "+cl ); + Constructor construct = cl.getConstructor( + new Class[] { + com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData.class, + com.sun.j3d.utils.scenegraph.io.retained.Controller.class} ); + + // System.out.println("Got constructor "+construct ); + state = (SceneGraphObjectState)construct.newInstance( + new Object[]{ null, this } ); + + // System.out.println("Got state instance "+state); + } catch(ClassNotFoundException e) { + throw new java.io.IOException( "Error Loading State Class "+stateClassName+" "+e.getMessage() ); + } catch( NoSuchMethodException ex ) { + throw new java.io.IOException( "1 Broken State class for "+ + stateClassName+" "+ex.getMessage() ); + } catch( InvocationTargetException exc ) { + exc.printStackTrace(); + throw new java.io.IOException( "2 Broken State class for "+ + stateClassName ); + } catch( IllegalAccessException exce ) { + throw new java.io.IOException( "3 Broken State class for "+ + stateClassName ); + } catch( InstantiationException excep ) { + throw new java.io.IOException( "4 Broken State class for "+ + stateClassName ); + } + } else { + state = createCoreState( classID ); + } + + state.readObject( in ); + + return state; + } + + /** + * Set the class loader used to load the Scene Graph Objects and + * the serialized user data. The default is + * ClassLoader.getSystemClassLoader() + */ + public void setClassLoader( ClassLoader classLoader ) { + this.classLoader = classLoader; + } + + + /** + * Get the class loader used to load the Scene Graph Objects and + * the serialized user data. The default is + * ClassLoader.getSystemClassLoader() + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Write all the unsaved NodeComponents and SharedGroups to DataOutput. + * Mark all the NodeComponents as saved. + */ + protected void writeNodeComponents( DataOutput out ) throws IOException { + // This method is overridden by RandomAccessFileControl + // The RandomAccessFileControl version sets the pointer to + // the next NodeComponent correclty + + ListIterator list = symbolTable.getUnsavedNodeComponents(); + out.writeInt( symbolTable.getUnsavedNodeComponentsSize() ); + while( list.hasNext() ) { + SymbolTableData symbol = (SymbolTableData)list.next(); + + out.writeInt( symbol.nodeID ); + out.writeLong( 0L ); // Pointer to next NodeComponent + + writeObject( out, symbol.getNodeState() ); + } + } + + /** + * Read in all the node components in this block + */ + protected void readNodeComponents( DataInput in ) throws IOException { + int count = in.readInt(); + + for(int i=0; iposition) + throw new SGIORuntimeException( "Seeking Backward "+pos +" "+position ); + else + stream.skip( (int)(position-pos) ); + + pos = position; + } + + public long getFilePointer() { + return pos; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/PositionOutputStream.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/PositionOutputStream.java new file mode 100644 index 0000000..db55bf1 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/PositionOutputStream.java @@ -0,0 +1,91 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.retained; + +import java.io.DataOutputStream; +import java.io.IOException; + +class PositionOutputStream extends java.io.OutputStream { + + private long pos = 0; + private java.io.OutputStream stream; + + public PositionOutputStream( java.io.OutputStream stream ) { + this.stream = stream; + } + + public void write(int p1) throws IOException { + pos++; + stream.write(p1); + } + + public void write( byte[] b ) throws IOException { + pos+= b.length; + stream.write( b ); + } + + public void write( byte[] b, int off, int len ) throws IOException { + pos+= len; + stream.write( b, off, len ); + } + + /** + * Move the file pointer to the specified position. + * The position MUST be greater or equal to the current position + */ + public void seekForward( long position ) throws IOException { + if (pos>position) + throw new SGIORuntimeException( "Seeking Backward "+pos +" "+position ); + else + for(int i=0; i< (int)(position-pos); i++) + stream.write(0); + + pos = position; + } + + public long getFilePointer() { + return pos; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/RandomAccessFileControl.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/RandomAccessFileControl.java new file mode 100644 index 0000000..003a74e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/RandomAccessFileControl.java @@ -0,0 +1,500 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.retained; + +import java.io.RandomAccessFile; +import java.io.IOException; +import java.io.DataOutput; +import java.io.DataInput; + +import javax.media.j3d.BranchGroup; +import javax.media.j3d.CapabilityNotSetException; +import javax.media.j3d.Canvas3D; + +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.SceneGraphObjectState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.NodeComponentState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.BranchGroupState; +import com.sun.j3d.utils.scenegraph.io.UnsupportedUniverseException; +import com.sun.j3d.utils.universe.SimpleUniverse; +import com.sun.j3d.utils.universe.ConfiguredUniverse; + +public class RandomAccessFileControl extends Controller { + + protected String FILE_IDENT = new String( "j3dff" ); + + private long user_data; + private long universe_config; + + private long symbol_table; + + private RandomAccessFile raf; + + private int branchGraphCount=0; + + private boolean writeMode = false; + private Object userData; + + /** Creates new RandomAccessFileControl */ + public RandomAccessFileControl() { + super(); + symbolTable = new SymbolTable(this); + } + + /** + * Create the file and write the inital header information + */ + public void createFile( java.io.File file, + SimpleUniverse universe, + boolean writeUniverseContent, + String description, + java.io.Serializable userData ) throws IOException, + UnsupportedUniverseException, + CapabilityNotSetException { + + raf = new RandomAccessFile( file, "rw" ); + writeMode = true; + + raf.seek(0); + raf.writeUTF( FILE_IDENT ); + + raf.seek(20); + raf.writeInt( outputFileVersion ); + + raf.seek( BRANCH_GRAPH_COUNT ); + raf.writeInt( 0 ); // Place holder to branch graph count + + raf.seek( FILE_DESCRIPTION ); + + if (description==null) + description=""; + raf.writeUTF( description ); + + try { + writeSerializedData( raf, userData ); + + universe_config = raf.getFilePointer(); + writeUniverse( raf, universe, writeUniverseContent ); + } catch( SGIORuntimeException e ) { + throw new IOException( e.getMessage() ); + } + } + + /** + * Open the file for reading + */ + public void openFile( java.io.File file ) throws IOException { + raf = new RandomAccessFile( file, "r" ); + writeMode = false; + + raf.seek(0); + String ident = raf.readUTF(); + + if ( ident.equals("demo_j3f") ) + throw new IOException( + "Use Java 3D Fly Through I/O instead of Java 3D Scenegraph I/O" ); + + if ( !ident.equals("j3dff") ) + throw new IOException( + "This is a Stream - use SceneGraphStreamReader instead"); + + raf.seek(20); + currentFileVersion = raf.readInt(); + + if ( currentFileVersion > outputFileVersion ) { + throw new IOException("Unsupported file version. This file was written using a new version of the SceneGraph IO API, please update your installtion to the latest version"); + } + + // readFileDescription sets user_data + String description = readFileDescription(); + + raf.seek( BRANCH_GRAPH_COUNT ); + branchGraphCount = raf.readInt(); + //System.out.println("BranchGraph count : "+branchGraphCount ); + + raf.seek( UNIVERSE_CONFIG_PTR ); + universe_config = raf.readLong(); + + raf.seek( SYMBOL_TABLE_PTR ); + symbol_table = raf.readLong(); + + ConfiguredUniverse universe; + + raf.seek( symbol_table ); + symbolTable.readTable( raf, false ); + raf.seek(user_data); + + userData = readSerializedData(raf); + } + + public ConfiguredUniverse readUniverse( boolean attachBranchGraphs, + Canvas3D canvas) throws IOException { + raf.seek( universe_config ); + return readUniverse( raf, attachBranchGraphs, canvas ); + } + + public Object getUserData() { + return userData; + } + + /** + * Read the set of branchgraps. + * + * Used by readUniverse + * + * RandomAccessFileControl will read the graphs in the array, + * StreamControl will read all graphs in the stream + */ + protected void readBranchGraphs( int[] graphs ) throws IOException { + for(int i=0; iSGIORuntimeException
+ * without a detail message. + */ + public SGIORuntimeException() { + } + + + /** + * Constructs an instance of SGIORuntimeException + * with the specified detail message. + * + * @param msg the detail message. + */ + public SGIORuntimeException(String msg) { + super(msg); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/StreamControl.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/StreamControl.java new file mode 100644 index 0000000..a61226b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/retained/StreamControl.java @@ -0,0 +1,205 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.retained; + +import java.io.RandomAccessFile; +import java.io.IOException; +import java.io.DataOutput; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.util.HashMap; +import java.util.Iterator; + +import javax.media.j3d.VirtualUniverse; +import javax.media.j3d.BranchGroup; +import javax.media.j3d.SceneGraphObject; + +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.SceneGraphObjectState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.NodeComponentState; +import com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d.BranchGroupState; +import com.sun.j3d.utils.scenegraph.io.UnsupportedUniverseException; + +/** + * Provides the infrastructure for ScenGraphStream Reader and Writer + */ +public class StreamControl extends Controller { + + protected String FILE_IDENT = new String( "j3dsf" ); + + private DataInputStream inputStream; + private DataOutputStream outputStream; + + public StreamControl( DataOutputStream out ) { + super(); + outputStream = out; + symbolTable = new SymbolTable( this ); + } + + public StreamControl( DataInputStream in ) { + super(); + inputStream = in; + symbolTable = new SymbolTable( this ); + } + + /** + * Prepare the Stream for writing, by sending header information + */ + public void writeStreamHeader() throws IOException { + outputStream.writeUTF( FILE_IDENT ); + outputStream.writeInt( outputFileVersion ); + } + + public void readStreamHeader() throws IOException { + String ident = inputStream.readUTF(); + if ( ident.equals("demo_j3s") ) + throw new IOException( "Use Java 3D Fly Through I/O instead of Java 3D Scenegraph I/O" ); + + if ( !ident.equals("j3dsf") ) + throw new IOException( + "This is a File - use SceneGraphFileReader instead"); + + currentFileVersion = inputStream.readInt(); + + if (currentFileVersion > outputFileVersion ) { + throw new IOException("Unsupported file version. This file was written using a new version of the SceneGraph IO API, please update your installtion to the latest version"); + } + } + + /** + * Add the named objects to the symbol table + */ + public void addNamedObjects( HashMap namedObjects ) { + symbolTable.addNamedObjects( namedObjects ); + } + + /** + * The BranchGraph userData is not supported in a stream and will be + * ignored. + * + * However the data in the userData field of the BranchGroup will be + * stored in the stream + */ + public void writeBranchGraph( BranchGroup bg, java.io.Serializable userData ) throws IOException { + try { + SymbolTableData symbol = symbolTable.getSymbol( bg ); + + if (symbol==null) { + symbol = symbolTable.createSymbol( bg ); + symbol.branchGraphID = -1; // This is a new BranchGraph so set the ID to -1 + } // which will cause setBranchGraphRoot to assign a new ID. + + symbolTable.setBranchGraphRoot( symbol, 0 ); + symbolTable.startUnsavedNodeComponentFrame(); + SceneGraphObjectState state = createState( bg, symbol ); + writeObject( outputStream, state ); + writeNodeComponents( outputStream ); + symbolTable.endUnsavedNodeComponentFrame(); + + if (symbolTable.branchGraphHasDependencies( symbol.branchGraphID )) + throw new javax.media.j3d.DanglingReferenceException(); + + symbolTable.clearUnshared(); + symbolTable.writeTable( outputStream ); + } catch( SGIORuntimeException e ) { + throw new IOException( e.getMessage() ); + } + } + + public BranchGroup readBranchGraph( HashMap namedObjects ) throws IOException { + try { + SceneGraphObjectState state = readObject( inputStream ); + readNodeComponents( inputStream ); + symbolTable.readTable( inputStream, true ); + + symbolTable.setBranchGraphRoot( state.getSymbol(), 0 ); + + state.buildGraph(); + + if (namedObjects!=null) + symbolTable.getNamedObjectMap( namedObjects ); + + return (BranchGroup)state.getNode(); + } catch( SGIORuntimeException e ) { + throw new IOException( e.getMessage() ); + } + } + + /** + * Read the set of branchgraps. + * + * Used by readUniverse + * + * RandomAccessFileControl will read the graphs in the array, + * StreamControl expects the graphs to follow the universe in the + * stream so it will read graphs.length branchgraphs. + */ + protected void readBranchGraphs( int[] graphs ) throws IOException { + for(int i=0; i1) + sharedNodes.add( symbol ); + nodeIDIndex.set( symbol.nodeID, symbol ); + } + + branchGraphs.set( j, symbol ); + } + + + for(int i=0; isymbol
+ * + * Only nodes (not nodeComponents) affect intergraph dependencies + */ + private void addInterGraphDependency( SymbolTableData symbol ) { + HashSet set = (HashSet)branchGraphDependencies.get( currentBranchGraphID ); + if (set==null) { + set = new HashSet(); + branchGraphDependencies.set( currentBranchGraphID, set ); + } + + set.add(symbol); + } + + /** + * Update the reference count for the node component. + * + * Called during NodeComponentState.addSubReference() + */ + public void incNodeComponentRefCount( int nodeID ) { + if (nodeID==0) return; + + SymbolTableData symbol = getSymbol( nodeID ); + + ((NodeComponentState)symbol.nodeState).addSubReference(); + + if (symbol.referenceCount==1) + sharedNodes.add( symbol ); + symbol.referenceCount++; + } + + /** + * Add a refernce to the specified node + * Also returns the nodes id + */ + public int addReference( SceneGraphObject node ) { + if (node==null) return 0; + + SymbolTableData symbol = getSymbol( node ); + + if (symbol==null) { + if (node instanceof javax.media.j3d.Node) { + symbol = createDanglingSymbol( node ); + if (symbol.branchGraphID != currentBranchGraphID ) { + //System.out.println("------------- Adding Reference "+symbol.nodeID+" "+node ); // TODO - remove + addInterGraphDependency( symbol ); + sharedNodes.add( symbol ); + } + } else { + symbol = createNodeComponentSymbol( node ); + } + return symbol.nodeID; + } else { + return addReference( symbol ); + } + } + + /** + * Add a refernce to the specified node + * Also returns the nodes id + */ + public int addReference( SymbolTableData symbol ) { + + if (symbol!=null) { + if (symbol.referenceCount==1) + sharedNodes.add( symbol ); + symbol.referenceCount++; + + if (symbol.j3dNode instanceof javax.media.j3d.NodeComponent && symbol.referenceCount>1) { + ((NodeComponentState)symbol.nodeState).addSubReference(); + } + + if (symbol.branchGraphID != currentBranchGraphID && + symbol.j3dNode instanceof javax.media.j3d.Node ) { + // System.out.println("------------- Adding Reference "+symbol.nodeID+" "+symbol.j3dNode ); // TODO - remove + addInterGraphDependency( symbol ); + } + } else { + throw new SGIORuntimeException("Null Symbol"); + } + + return symbol.nodeID; + } + + /** + * Add a refernce to the BranchGraph root + * Also returns the nodes id + * + * Used to associate graphs with a locale without storing the graph at the + * current time. + */ + public int addBranchGraphReference( SceneGraphObject node, int branchGraphID ) { + if (node==null) return 0; + + SymbolTableData symbol = getSymbol( node ); + + if (symbol!=null) { + if (symbol.referenceCount==1) + sharedNodes.add( symbol ); + symbol.referenceCount++; + } else { + symbol = new SymbolTableData( nodeID++, node, null, -3 ); + j3dNodeIndex.put( node, symbol ); + nodeIDIndex.add( symbol ); + danglingReferences.put( node, symbol ); + } + + symbol.branchGraphID = branchGraphID; + for(int i=branchGraphs.size(); inodeIDIndex.size() ) + return null; + else + return (SymbolTableData)nodeIDIndex.get( nodeID ); + } + + /** Get the symbol for the shared group + * If the sharedgroup has not been loaded then load it before + * returning (if we are using RandomAccessFileControl + */ + public SymbolTableData getSharedGroup( int nodeID ) { + SymbolTableData symbol = getSymbol( nodeID ); + + if (symbol.nodeState==null && control instanceof RandomAccessFileControl) { + try { + ((RandomAccessFileControl)control).loadSharedGroup( symbol ); + } catch( java.io.IOException e ) { + e.printStackTrace(); + throw new SGIORuntimeException("Internal error in getSharedGroup"); + } + } + + return symbol; + } + + /** + * Set the position of the object referenced by state + */ + public void setFilePosition( long ptr, SceneGraphObjectState state ) { + if (state instanceof NullSceneGraphObjectState) return; + + SymbolTableData symbol = getSymbol( state.getNodeID() ); + + symbol.filePosition = ptr; + } + /** + * Associate the name with the scene graph object + */ + public void addNamedObject( String name, SceneGraphObject object ) { + namedObjects.put( name, object ); + } + + /** + * Add all the named objects in map + */ + public void addNamedObjects( HashMap map ) { + namedObjects.putAll( map ); + } + + /** + * Return the SceneGraphObject associated with the name + */ + public SceneGraphObject getNamedObject( String name ) throws NamedObjectException, ObjectNotLoadedException { + Object obj = namedObjects.get( name ); + if (obj==null) + throw new NamedObjectException( "Unknown name :"+name ); + + if (obj instanceof SceneGraphObject) + return (SceneGraphObject)obj; + else { + SymbolTableData symbol = getSymbol( ((Integer)obj).intValue() ); + if (symbol==null || symbol.j3dNode==null) + throw new ObjectNotLoadedException( ((Integer)obj).toString() ); + return symbol.j3dNode; + } + } + + /** + * Get all the names of the named objects + */ + public String[] getNames() { + return (String[])namedObjects.keySet().toArray( new String[] {} ); + } + + /** + * Add the namedObject mappings to map + */ + public void getNamedObjectMap( HashMap map ) { + map.putAll( namedObjects ); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + + for(int i=0; i0) // TODO - remove if, workaround for bug in daily + app.setTextureUnitState( texUnitState ); + + app.setTransparencyAttributes( (TransparencyAttributes)control.getSymbolTable().getJ3dNode(transparencyAttributes) ); + + super.buildGraph(); // Must be last call in method + } + + protected javax.media.j3d.SceneGraphObject createNode() { + return new Appearance(); + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AuralAttributesState.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AuralAttributesState.java new file mode 100644 index 0000000..ff7fcb0 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/AuralAttributesState.java @@ -0,0 +1,125 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d; + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; +import javax.media.j3d.AuralAttributes; +import javax.media.j3d.SceneGraphObject; +import javax.vecmath.Vector3f; +import com.sun.j3d.utils.scenegraph.io.retained.Controller; +import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData; + +public class AuralAttributesState extends NodeComponentState { + + public AuralAttributesState(SymbolTableData symbol,Controller control) { + super( symbol, control ); + + } + + public void writeObject( DataOutput out ) throws IOException { + super.writeObject( out ); + + out.writeFloat( ((AuralAttributes)node).getAttributeGain() ); + + float[] distance = new float[ ((AuralAttributes)node).getDistanceFilterLength() ]; + float[] cutoff = new float[ distance.length ]; + + ((AuralAttributes)node).getDistanceFilter( distance, cutoff ); + out.writeInt( distance.length ); + for(int i=0; i1) + ((NodeComponent)this.node).setDuplicateOnCloneTree(in.readBoolean()); + + } + + public void writeObject( DataOutput out ) throws IOException { + super.writeObject(out); + out.writeBoolean(((NodeComponent)this.node).getDuplicateOnCloneTree()); + } + + /** + * Called when this component reference count is incremented. + * Allows this component to update the reference count of any components + * that it references. + */ + public void addSubReference() { + } + +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NodeState.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NodeState.java new file mode 100644 index 0000000..c6fb735 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NodeState.java @@ -0,0 +1,88 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d; + +import java.io.*; +import javax.media.j3d.Node; +import javax.media.j3d.NodeComponent; +import javax.media.j3d.SceneGraphObject; + +import com.sun.j3d.utils.scenegraph.io.retained.Controller; +import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData; + +public class NodeState extends SceneGraphObjectState { + + public NodeState( SymbolTableData symbol, Controller control ) { + super(symbol, control); + } + + public void writeObject( DataOutput out ) throws + IOException { + + super.writeObject( out ); + + control.writeBounds( out, ((Node)node).getBounds() ); + + out.writeBoolean( ((Node)node).getPickable() ); + out.writeBoolean( ((Node)node).getCollidable() ); + out.writeBoolean( ((Node)node).getBoundsAutoCompute() ); + + } + + public void readObject( DataInput in ) throws + IOException { + super.readObject(in); + + ((Node)node).setBounds( control.readBounds(in) ); + + ((Node)node).setPickable( in.readBoolean() ); + ((Node)node).setCollidable( in.readBoolean() ); + ((Node)node).setBoundsAutoCompute( in.readBoolean() ); + } + + + +} + diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NullSceneGraphObjectState.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NullSceneGraphObjectState.java new file mode 100644 index 0000000..17aa275 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/NullSceneGraphObjectState.java @@ -0,0 +1,105 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Enumeration; +import javax.media.j3d.SceneGraphObject; +import javax.vecmath.Color3f; +import javax.vecmath.Point3d; +import javax.vecmath.Vector4d; +import javax.vecmath.Tuple3d; +import javax.vecmath.Tuple4d; +import com.sun.j3d.utils.scenegraph.io.retained.Controller; +import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData; + +public class NullSceneGraphObjectState extends SceneGraphObjectState { + + SymbolTableData symbolTableData; + + /** + * Dummy class to represent a null object in the scene graph + * + */ + public NullSceneGraphObjectState(SymbolTableData symbol,Controller control) { + super( null, control ); + symbolTableData = new SymbolTableData( -1, null, this, -1 ); + } + + /** + * DO NOT call symbolTable.addReference in writeObject as this (may) + * result in a concurrentModificationException. + * + * All references should be created in the constructor + */ + public void writeObject( DataOutput out ) throws IOException { + } + + public void readObject( DataInput in ) throws IOException { + } + + public SceneGraphObject getNode() { + return null; + } + + public int getNodeID() { + return -1; + } + + public SymbolTableData getSymbol() { + return symbolTableData; + } + + + protected javax.media.j3d.SceneGraphObject createNode() { + return null; + } + + +} diff --git a/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/OrderedGroupState.java b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/OrderedGroupState.java new file mode 100644 index 0000000..8a4c515 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/scenegraph/io/state/javax/media/j3d/OrderedGroupState.java @@ -0,0 +1,85 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.scenegraph.io.state.javax.media.j3d; + +import javax.media.j3d.OrderedGroup; +import com.sun.j3d.utils.scenegraph.io.retained.Controller; +import com.sun.j3d.utils.scenegraph.io.retained.SymbolTableData; +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +public class OrderedGroupState extends GroupState { + + /** Creates new BranchGroupState */ + public OrderedGroupState(SymbolTableData symbol,Controller control) { + super( symbol, control ); + } + + public void writeObject( DataOutput out ) throws IOException { + super.writeObject( out ); + + int[] childIndexOrder = ((OrderedGroup)node).getChildIndexOrder(); + out.writeInt( childIndexOrder.length ); + for ( int i=0;i0 ) { + float[] lod = new float[ points ]; + float[] pts = new float[ points ]; + attr.getSharpenTextureFunc( lod, pts ); + for (int i = 0 ; i < points ; i++) { + out.writeFloat( lod[i] ); + out.writeFloat( pts[i] ); + } + } + + points = attr.getFilter4FuncPointsCount(); + out.writeInt( points ); + if ( points>=4 ) { + float[] weights = new float[ points ]; + attr.getFilter4Func( weights ); + for (int i = 0 ; i < points ; i++) { + out.writeFloat( weights[i] ); + } + } + } + + public void readObject( DataInput in ) throws IOException { + super.readObject( in ); + Texture attr = (Texture)node; + attr.setBoundaryColor( control.readColor4f( in )); + attr.setBoundaryModeS( in.readInt() ); + attr.setBoundaryModeT( in.readInt() ); + attr.setEnable( in.readBoolean() ); + + imageComponents = new int[ in.readInt() ]; + for(int i=0; i0 ) { + float[] lod = new float[ points ]; + float[] pts = new float[ points ]; + for (int i = 0 ; i < points ; i++) { + lod[i] = in.readFloat(); + pts[i] = in.readFloat(); + } + attr.setSharpenTextureFunc( lod, pts ); + } + + points = in.readInt(); + if ( points >= 4 ) { + float[] weights = new float[ points ]; + for (int i = 0 ; i < points ; i++) { + weights[i] = in.readFloat(); + } + attr.setFilter4Func( weights ); + } + } + + public void addSubReference() { + if ( !(node instanceof TextureCubeMap) ) { + for( int i=0; i + + + + + + + com.sun.j3d.utils.timer + + +

A High Resolution operating system dependent interval timer.

+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigCommand.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigCommand.java new file mode 100644 index 0000000..cb0e412 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigCommand.java @@ -0,0 +1,417 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.text.DecimalFormat ; +import java.text.FieldPosition ; +import java.util.Collection ; +import javax.vecmath.Matrix3d ; +import javax.vecmath.Matrix4d ; + +/** + * Contains the elements which compose a configuration file command, + * including the command name, type, and arguments. + */ +class ConfigCommand { + /** + * Specifies that this command creates a new ConfigObject. + */ + static final int CREATE = 0 ; + + /** + * Specifies that this command sets an attribute for a class known + * to ConfiguredUniverse. As of Java 3D 1.3.1, these commands are + * handled the same as property commands (see PROPERTY below) and + * this constant is no longer used. + */ + static final int ATTRIBUTE = 1 ; + + /** + * Specifies that this command sets a Java system property or a + * property for a class unknown to ConfiguredUniverse. Properties + * for such a class are set by using the reflection API to invoke a + * method whose name is specified in the command's argument list. + * Such a method must accept an array of Object as its sole + * parameter, where that array contains all the command elements + * which appear after the method name.

+ * + * As of Java 3D 1.3.1, this is handled the same as an attribute. + * The individual setProperty() method implementations of + * ConfigObject determine whether the method to set the property can + * be invoked directly or through introspection. If through + * introspection, then the evaluation of the property must be + * delayed until the target object is instantiated. + */ + static final int PROPERTY = 2 ; + + /** + * Specifies that this command creates an alias for a ConfigObject of the + * same base name. + */ + static final int ALIAS = 3 ; + + /** + * Specifies that this command is a deferred built-in command that can't + * be immediately evaluated by the parser. Its evaluation is delayed + * until all config objects are instantiated and their properties can be + * evaluated. + */ + static final int BUILTIN = 4 ; + + /** + * Specifies that this command is an include file directive. + */ + static final int INCLUDE = 5 ; + + /** + * Specifes that this command is entirely processed by the + * constructor and should be ignored by subsequent recipients. + */ + static final int IGNORE = 6 ; + + /** + * The type of this command, either CREATE, PROPERTY, ALIAS, + * BUILTIN, INCLUDE, or IGNORE. + */ + int type = -1 ; + + /** + * The number of arguments in this command, including the command + * name. + */ + int argc = 0 ; + + /** + * An array containing all of this command's arguments, including + * the command name. + */ + Object[] argv = null ; + + /** + * The name of the command being invoked, which is always the first + * argument of the command. + */ + String commandName = null ; + + /** + * The base name of this command, from which the name of the ConfigObject + * subclass that processes it is derived. This is constructed by + * stripping off the leading "New" prefix or the trailing "Attribute", + * "Property", or "Alias" suffix of the command name. The name of the + * ConfigObject subclass which handles the command is derived by adding + * "Config" as a prefix to the base name. + */ + String baseName = null ; + + /** + * The instance name of the ConfigObject subclass which processes this + * command. Together with the base name this provides the handle by which + * a ConfigObject can be referenced by other commands in the configuration + * file. + */ + String instanceName = null ; + + /** + * The file from which this command was read. + */ + String fileName = null ; + + /** + * The line number from which this command was read. + */ + int lineNumber = 0 ; + + /** + * Constructs a ConfigCommand from configuration file command arguments. + * + * @param elements arguments to this command, including the command name + * @param fileName name of the file from where the command was read + * @param lineNumber line number where the command is found in the file + */ + ConfigCommand(Collection elements, String fileName, int lineNumber) { + this.fileName = fileName ; + this.lineNumber = lineNumber ; + + argc = elements.size() ; + argv = elements.toArray(new Object[0]) ; + + if (! (argc > 0 && (argv[0] instanceof String))) + throw new IllegalArgumentException("malformed command") ; + + commandName = (String)argv[0] ; + + if (commandName.startsWith("New")) { + type = CREATE ; + baseName = commandName.substring(3) ; + instanceName = checkName(argv[1]) ; + } + else if (commandName.endsWith("Property")) { + baseName = commandName.substring(0, commandName.length()-8) ; + if (baseName.equals("Java")) { + type = IGNORE ; + processJavaProperty(argc, argv) ; + } + else { + type = PROPERTY ; + instanceName = checkName(argv[1]) ; + } + } + else if (commandName.endsWith("Attribute")) { + // Backward compatibility. + type = PROPERTY ; + baseName = commandName.substring(0, commandName.length()-9) ; + instanceName = checkName(argv[1]) ; + } + else if (commandName.endsWith("Alias")) { + type = ALIAS ; + baseName = commandName.substring(0, commandName.length()-5) ; + instanceName = checkName(argv[1]) ; + } + else if (commandName.equals("Include")) { + type = INCLUDE ; + } + else { + type = BUILTIN ; + } + + // We allow "Window" as an equivalent to "Screen". + if (baseName != null && baseName.equals("Window")) + baseName = "Screen" ; + } + + /** + * Sets the Java property specified in the command. If the command + * has 3 arguments then it's an unconditional assignment. If the + * 3rd argument is "Default", then the property is set to the value + * of the 4th argument only if the specified property has no + * existing value. + * + * @param argc the number of arguments in the command + * @param argv command arguments as an array of Objects; the 1st is + * the command name (ignored), the 2nd is the name of the Java + * property, the 3rd is the value to be set or the keyword + * "Default", and the 4th is thevalue to be set if the Java + * property doesn't already exist + */ + private static void processJavaProperty(int argc, Object[] argv) { + for (int i = 1 ; i < argc ; i++) { + // Check args. + if (argv[i] instanceof Boolean) { + argv[i] = ((Boolean)argv[i]).toString() ; + } + else if (! (argv[i] instanceof String)) { + throw new IllegalArgumentException + ("JavaProperty arguments must be Strings or Booleans") ; + } + } + if (argc == 3) { + // Unconditional assignment. + setJavaProperty((String)argv[1], (String)argv[2]) ; + } + else if (argc != 4) { + // Conditional assignment must have 4 args. + throw new IllegalArgumentException + ("JavaProperty must have either 2 or 3 arguments") ; + } + else if (! ((String)argv[2]).equals("Default")) { + // Penultimate arg must be "Default" keyword. + throw new IllegalArgumentException + ("JavaProperty 2nd argument must be \"Default\"") ; + } + else if (evaluateJavaProperty((String)argv[1]) == null) { + // Assignment only if no existing value. + setJavaProperty((String)argv[1], (String)argv[3]) ; + } + } + + /** + * Sets the given Java system property if allowed by the security manager. + * + * @param key property name + * @param value property value + * @return previous property value if any + */ + static String setJavaProperty(final String key, final String value) { + return (String)java.security.AccessController.doPrivileged + (new java.security.PrivilegedAction() { + public Object run() { + return System.setProperty(key, value) ; + } + }) ; + } + + /** + * Evaluates the specified Java property string if allowed by the security + * manager. + * + * @param key string containing a Java property name + * @return string containing the Java property valaue + */ + static String evaluateJavaProperty(final String key) { + return (String)java.security.AccessController.doPrivileged + (new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty(key) ; + } + }) ; + } + + /** + * Checks if the given object is an instance of String. + * + * @param o the object to be checked + * @return the object cast to a String + * @exception IllegalArgumentException if the object is not a String + */ + private final String checkName(Object o) { + if (! (o instanceof String)) + throw new IllegalArgumentException + ("second argument to \"" + commandName + "\" must be a name") ; + + return (String)o ; + } + + /** + * Calls formatMatrixRows(3, 3, m), where m is a + * an array of doubles retrieved from the given Matrix3d. + * + * @param m3 matrix to be formatted + * @return matrix rows formatted into strings + */ + static String[] formatMatrixRows(Matrix3d m3) { + double[] m = new double[9] ; + m[0] = m3.m00 ; m[1] = m3.m01 ; m[2] = m3.m02 ; + m[3] = m3.m10 ; m[4] = m3.m11 ; m[5] = m3.m12 ; + m[6] = m3.m20 ; m[7] = m3.m21 ; m[8] = m3.m22 ; + + return formatMatrixRows(3, 3, m) ; + } + + /** + * Calls formatMatrixRows(4, 4, m), where m is a + * an array of doubles retrieved from the given Matrix4d. + * + * @param m4 matrix to be formatted + * @return matrix rows formatted into strings + */ + static String[] formatMatrixRows(Matrix4d m4) { + double[] m = new double[16] ; + m[0] = m4.m00 ; m[1] = m4.m01 ; m[2] = m4.m02 ; m[3] = m4.m03 ; + m[4] = m4.m10 ; m[5] = m4.m11 ; m[6] = m4.m12 ; m[7] = m4.m13 ; + m[8] = m4.m20 ; m[9] = m4.m21 ; m[10] = m4.m22 ; m[11] = m4.m23 ; + m[12] = m4.m30 ; m[13] = m4.m31 ; m[14] = m4.m32 ; m[15] = m4.m33 ; + + return formatMatrixRows(4, 4, m) ; + } + + /** + * Formats a matrix with fixed fractional digits and integer padding to + * align the decimal points in columns. Non-negative numbers print up to + * 7 integer digits, while negative numbers print up to 6 integer digits + * to account for the negative sign. 6 fractional digits are printed. + * + * @param rowCount number of rows in the matrix + * @param colCount number of columns in the matrix + * @param m matrix to be formatted + * @return matrix rows formatted into strings + */ + static String[] formatMatrixRows(int rowCount, int colCount, double[] m) { + DecimalFormat df = new DecimalFormat("0.000000") ; + FieldPosition fp = new FieldPosition(DecimalFormat.INTEGER_FIELD) ; + StringBuffer sb0 = new StringBuffer() ; + StringBuffer sb1 = new StringBuffer() ; + String[] rows = new String[rowCount] ; + + for (int i = 0 ; i < rowCount ; i++) { + sb0.setLength(0) ; + for (int j = 0 ; j < colCount ; j++) { + sb1.setLength(0) ; + df.format(m[i*colCount+j], sb1, fp) ; + int pad = 8 - fp.getEndIndex() ; + for (int k = 0 ; k < pad ; k++) { + sb1.insert(0, " ") ; + } + sb0.append(sb1) ; + } + rows[i] = sb0.toString() ; + } + return rows ; + } + + /** + * Returns the String representation of this command. + * + * @return string representing this command + */ + public String toString() { + String[] lines = null ; + StringBuffer sb = new StringBuffer("(") ; + + for (int i = 0 ; i < argc ; i++) { + if (argv[i] instanceof Matrix3d) { + lines = formatMatrixRows((Matrix3d)argv[i]) ; + sb.append("\n ((" + lines[0] + ")\n") ; + sb.append(" (" + lines[1] + ")\n") ; + sb.append(" (" + lines[2] + "))") ; + if (i != (argc - 1)) sb.append("\n") ; + } + else if (argv[i] instanceof Matrix4d) { + lines = formatMatrixRows((Matrix4d)argv[i]) ; + sb.append("\n ((" + lines[0] + ")\n") ; + sb.append(" (" + lines[1] + ")\n") ; + sb.append(" (" + lines[2] + ")\n") ; + sb.append(" (" + lines[3] + "))") ; + if (i != (argc - 1)) sb.append("\n") ; + } + else { + if (i > 0) sb.append(" ") ; + sb.append(argv[i].toString()) ; + } + } + + sb.append(")") ; + return sb.toString() ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigContainer.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigContainer.java new file mode 100644 index 0000000..acbc984 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigContainer.java @@ -0,0 +1,1479 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.io.* ; +import java.util.* ; +import java.net.URL ; +import java.net.MalformedURLException ; +import javax.media.j3d.* ; +import com.sun.j3d.utils.behaviors.vp.ViewPlatformBehavior ; + +/** + * Loads a Java 3D configuration file and creates a container of named objects + * that will effect the viewing configuration specified in the file. These + * can include Viewers, ViewingPlatforms, ViewPlatformBehaviors, InputDevices, + * Sensors, and other objects.

+ * + * Clients can construct the view side of a scene graph by retrieving these + * objects using the accessor methods provided by this class. This could + * involve as little as just attaching ViewingPlatforms to a Locale, depending + * upon how completely the viewing configuration is specified in the file. + * The ConfiguredUniverse class is an example of a ConfigContainer client and + * how it can be used.

+ * + * ConfigContainer can be useful for clients other than ConfiguredUniverse. + * InputDevice and ViewPlatformBehavior configuration is fully supported, so a + * given Java 3D installation can provide configuration files to an + * application that will allow it to fully utilize whatever site-specific + * devices and behaviors are available. The configuration mechanism can be + * extended for any target object through the use of the + * NewObject and ObjectProperty configuration + * commands. + * + * @see ConfiguredUniverse + * @see + * The Java 3D Configuration File + * @see + * Example Configuration Files + * + * @since Java 3D 1.3.1 + */ +public class ConfigContainer { + // + // The configuration object database is implemented with a HashMap which + // maps their class names to ArrayList objects which contain the actual + // instances. The latter are used since the instances of a given class + // must be evaluated in the order in which they were created. + // LinkedHashMap is available in JDK 1.4 but currently this code must run + // under JDK 1.3.1 as well. + // + private Map baseNameMap = new HashMap() ; + + // Map containing named canvases for each view. + private Map viewCanvasMap = new HashMap() ; + + // Read-only Maps for the public interface to the configuration database. + private ReadOnlyMap bodyMap = null ; + private ReadOnlyMap environmentMap = null ; + private ReadOnlyMap viewerMap = null ; + private ReadOnlyMap deviceMap = null ; + private ReadOnlyMap sensorMap = null ; + private ReadOnlyMap behaviorMap = null ; + private ReadOnlyMap platformMap = null ; + private ReadOnlyMap genericObjectMap = null ; + + // Read-only Sets for the public interface to the configuration database. + private ReadOnlySet bodies = null ; + private ReadOnlySet environments = null ; + private ReadOnlySet viewers = null ; + private ReadOnlySet devices = null ; + private ReadOnlySet sensors = null ; + private ReadOnlySet behaviors = null ; + private ReadOnlySet platforms = null ; + private ReadOnlySet genericObjects = null ; + + // The number of TransformGroups to include in ViewingPlatforms. + private int transformCount = 1 ; + + // The visibility status of Viewer AWT components. + private boolean setVisible = false ; + + /** + * The name of the file this ConfigContainer is currently loading. + */ + String currentFileName = null ; + + /** + * Creates a new ConfigContainer and loads the configuration file at the + * specified URL. All ViewingPlatform instances are created with a single + * TransformGroup and all Viewer components are initially invisible. + * + * @param userConfig URL of the configuration file to load + */ + public ConfigContainer(URL userConfig) { + this(userConfig, false, 1, true) ; + } + + /** + * Creates a new ConfigContainer and loads the configuration file at the + * specified URL. Any ViewingPlatform instantiated by the configuration + * file will be created with the specified number of transforms. Viewer + * components may be set initially visible or invisible with the + * setVisible flag. + * + * @param userConfig URL of the configuration file to load + * @param setVisible if true, setVisible(true) is called on + * all Viewers + * @param transformCount number of transforms to be included in any + * ViewingPlatform created; must be greater than 0 + */ + public ConfigContainer(URL userConfig, + boolean setVisible, int transformCount) { + + this(userConfig, setVisible, transformCount, true) ; + } + + /** + * Package-scoped constructor for ConfigContainer. This provides an + * additional flag, attachBehaviors, which indicates whether + * or not ViewPlatformBehaviors should be attached to the ViewingPlatforms + * specified for them.

+ * + * Normally the flag should be true. However, when instantiated by + * ConfiguredUniverse, this flag is set false so that ConfiguredUniverse + * can set a reference to itself in the ViewingPlatform before attaching + * the behavior. This provides backwards compatibility to behaviors that + * access the ConfiguredUniverse instance from a call to + * setViewingPlatform in order to look up the actual Sensor, + * Viewer, Behavior, etc., instances associated with the names provided + * them from the configuration file.

+ * + * The preferred methods to retrieve instances of specific objects defined + * in the configuration file are to either 1) get the ConfiguredUniverse + * instance when the behavior's initialize method is called, + * or to 2) define properties that accept object instances directly, and + * then use the newer Device, Sensor, ViewPlatform, etc., built-in + * commands in the configuration file. These built-ins will return an + * object instance from a name. + * + * @param userConfig URL of the configuration file to load + * @param setVisible if true, setVisible(true) is called on + * all Viewers + * @param transformCount number of transforms to be included in any + * ViewingPlatform created; must be greater than 0 + * @param attachBehaviors if true, attach ViewPlatformBehaviors to the + * appropriate ViewingPlatforms + */ + ConfigContainer(URL userConfig, boolean setVisible, + int transformCount, boolean attachBehaviors) { + + if (transformCount < 1) + throw new IllegalArgumentException + ("transformCount must be greater than 0") ; + + loadConfig(userConfig) ; + processConfig(setVisible, transformCount, attachBehaviors) ; + } + + /** + * Open, parse, and load the contents of a configuration file. + * + * @param userConfig location of the configuration file + */ + private void loadConfig(URL userConfig) { + InputStream inputStream = null ; + StreamTokenizer streamTokenizer = null ; + String lastFileName = currentFileName ; + + currentFileName = userConfig.toString() ; + try { + inputStream = userConfig.openStream() ; + Reader r = new BufferedReader(new InputStreamReader(inputStream)) ; + streamTokenizer = new StreamTokenizer(r) ; + } + catch (IOException e) { + throw new IllegalArgumentException + ("\n" + e + "\nUnable to open " + currentFileName) ; + } + + // + // Set up syntax tables for the tokenizer. + // + // It would be nice to allow '/' as a word constituent for URL strings + // and Unix paths, but then the scanner won't ignore "//" and "/* */" + // comment style syntax. Treating '/' as an ordinary character will + // allow comments to work, but then '/' becomes a single token which + // has to be concatenated with subsequent tokens to reconstruct the + // original word string. + // + // It is cleaner to just require quoting for forward slashes. '/' + // should still be treated as an ordinary character however, so that a + // non-quoted URL string or Unix path will be treated as a syntax + // error instead of a comment. + // + streamTokenizer.ordinaryChar('/') ; + streamTokenizer.wordChars('_', '_') ; + streamTokenizer.wordChars('$', '$') ; // for ${...} Java property + streamTokenizer.wordChars('{', '}') ; // substitution in word tokens + streamTokenizer.slashSlashComments(true) ; + streamTokenizer.slashStarComments(true) ; + + // Create an s-expression parser to use for all top-level (0) commands. + ConfigSexpression sexp = new ConfigSexpression() ; + + // Loop through all top-level commands. Boolean.FALSE is returned + // after the last one is evaluated. + while (sexp.parseAndEval(this, streamTokenizer, 0) != Boolean.FALSE) ; + + // Close the input stream. + try { + inputStream.close() ; + } + catch (IOException e) { + throw new IllegalArgumentException + ("\n" + e + "\nUnable to close " + currentFileName) ; + } + + // Restore current file name. + currentFileName = lastFileName ; + } + + /** + * This method gets called from the s-expression parser to process a + * configuration command. + * + * @param elements tokenized list of sexp elements + * @param lineNumber command line number + */ + void evaluateCommand(ArrayList elements, int lineNumber) { + ConfigObject co ; + ConfigCommand cmd ; + + // Create a command object. + cmd = new ConfigCommand(elements, currentFileName, lineNumber) ; + + // Process the command according to its type. + switch (cmd.type) { + case ConfigCommand.CREATE: + co = createConfigObject(cmd) ; + addConfigObject(co) ; + break ; + case ConfigCommand.ALIAS: + co = createConfigAlias(cmd) ; + addConfigObject(co) ; + break ; + case ConfigCommand.PROPERTY: + co = findConfigObject(cmd.baseName, cmd.instanceName) ; + co.setProperty(cmd) ; + break ; + case ConfigCommand.INCLUDE: + if (! (cmd.argv[1] instanceof String)) { + throw new IllegalArgumentException + ("Include file must be a URL string") ; + } + URL url = null ; + String urlString = (String)cmd.argv[1] ; + try { + url = new URL(urlString) ; + } + catch (MalformedURLException e) { + throw new IllegalArgumentException(e.toString()) ; + } + loadConfig(url) ; + break ; + case ConfigCommand.IGNORE: + break ; + default: + throw new IllegalArgumentException + ("Unknown command \"" + cmd.commandName + "\"") ; + } + } + + /** + * Instantiates and initializes an object that extends the ConfigObject + * base class. The class name of the object is derived from the + * command, which is of the following form:

+ * + * (New{baseName} {instanceName} ... [Alias {aliasName}])

+ * + * The first two command elements and the optional trailing Alias syntax + * are processed here, at which point the subclass implementation of + * initialize() is called. Subclasses must override initialize() if they + * need to process more than what is processed by default here. + * + * @param cmd configuration command that creates a new ConfigObject + */ + private ConfigObject createConfigObject(ConfigCommand cmd) { + Class objectClass = null ; + ConfigObject configObject = null ; + + // Instantatiate the ConfigObject if possible. This is not the target + // object, but an object that will gather configuration properties, + // instantiate the target object, and then apply the configuration + // properties to it. + try { + objectClass = Class.forName("com.sun.j3d.utils.universe.Config" + + cmd.baseName) ; + } + catch (ClassNotFoundException e) { + throw new IllegalArgumentException + ("\"" + cmd.baseName + "\"" + + " is not a configurable object; ignoring command") ; + } + try { + configObject = (ConfigObject)(objectClass.newInstance()) ; + } + catch (IllegalAccessException e) { + System.out.println(e) ; + throw new IllegalArgumentException("Ignoring command") ; + } + catch (InstantiationException e) { + System.out.println(e) ; + throw new IllegalArgumentException("Ignoring command") ; + } + + // Process an Alias keyword if present. This option is available for + // all New commands so it is processed here. The Alias keyword must + // be the penultimate command element, followed by a String. + for (int i = 2 ; i < cmd.argc ; i++) { + if (cmd.argv[i] instanceof String && + ((String)cmd.argv[i]).equals("Alias")) { + if (i == (cmd.argc - 2) && cmd.argv[i+1] instanceof String) { + addConfigObject(new ConfigAlias(cmd.baseName, + (String)cmd.argv[i+1], + configObject)) ; + cmd.argc -= 2 ; + } + else { + throw new IllegalArgumentException + ("The alias name must be a string and " + + "must be the last command argument") ; + } + } + } + + // Initialize common fields. + configObject.baseName = cmd.baseName ; + configObject.instanceName = cmd.instanceName ; + configObject.creatingCommand = cmd ; + configObject.configContainer = this ; + + // Initialize specific fields and return the ConfigObject. + configObject.initialize(cmd) ; + return configObject ; + } + + /** + * Instantiate and initialize a ConfigObject base class containing alias + * information. The command is of the form:

+ * + * ({baseName}Alias {aliasName} {originalName}) + * + * @param cmd configuration command that creates a new alias + * @return the new ConfigObject with alias information + */ + private ConfigObject createConfigAlias(ConfigCommand cmd) { + ConfigObject original ; + + if (cmd.argc != 3 || ! (cmd.argv[2] instanceof String)) + throw new IllegalArgumentException + ("Command \"" + cmd.commandName + + "\" requires an instance name as second argument") ; + + original = findConfigObject(cmd.baseName, (String)cmd.argv[2]) ; + return new ConfigAlias(cmd.baseName, cmd.instanceName, original) ; + } + + /** + * A class that does nothing but reference another ConfigObject. Once + * created, the alias name can be used in all commands that would accept + * the original name. A lookup of the alias name will always return the + * original instance. + */ + private static class ConfigAlias extends ConfigObject { + ConfigAlias(String baseName, String instanceName, ConfigObject targ) { + this.baseName = baseName ; + this.instanceName = instanceName ; + this.isAlias = true ; + this.original = targ ; + targ.aliases.add(instanceName) ; + } + } + + /** + * Adds the specified ConfigObject instance into this container using the + * given ConfigCommand's base name and instance name. + * + * @param object the ConfigObject instance to add into the database + */ + private void addConfigObject(ConfigObject object) { + ArrayList instances ; + + instances = (ArrayList)baseNameMap.get(object.baseName) ; + if (instances == null) { + instances = new ArrayList() ; + baseNameMap.put(object.baseName, instances) ; + } + + // Disallow duplicate instance names. + for (int i = 0 ; i < instances.size() ; i++) { + ConfigObject co = (ConfigObject)instances.get(i) ; + if (co.instanceName.equals(object.instanceName)) { + // Don't confuse anybody using Window. + String base = object.baseName ; + if (base.equals("Screen")) base = "Screen or Window" ; + throw new IllegalArgumentException + ("Duplicate " + base + " instance name \"" + + object.instanceName + "\" ignored") ; + } + } + + instances.add(object) ; + } + + /** + * Finds a config object matching the given base name and the instance + * name. If an alias is found, then its original is returned. If the + * object is not found, an IllegalArgumentException is thrown.

+ * + * @param basename base name of the config object + * @param instanceName name associated with this config object instance + * @return the found ConfigObject + */ + ConfigObject findConfigObject(String baseName, String instanceName) { + ArrayList instances ; + ConfigObject configObject ; + + instances = (ArrayList)baseNameMap.get(baseName) ; + if (instances != null) { + for (int i = 0 ; i < instances.size() ; i++) { + configObject = (ConfigObject)instances.get(i) ; + + if (configObject.instanceName.equals(instanceName)) { + if (configObject.isAlias) + return configObject.original ; + else + return configObject ; + } + } + } + + // Throw an error, but don't confuse anybody using Window. + if (baseName.equals("Screen")) baseName = "Screen or Window" ; + throw new IllegalArgumentException + (baseName + " \"" + instanceName + "\" not found") ; + } + + /** + * Find instances of config objects with the given base name. + * This is the same as findConfigObjects(baseName, true). + * Aliases are filtered out so that all returned instances are unique. + * + * @param baseName base name of desired config object class + * @return ArrayList of config object instances of the desired base + * class, or null if instances of the base class don't exist + */ + Collection findConfigObjects(String baseName) { + return findConfigObjects(baseName, true) ; + } + + + /** + * Find instances of config objects with the given base name. + * + * @param baseName base name of desired config object class + * @param filterAlias if true, aliases are filtered out so that all + * returned instances are unique + * @return ArrayList of config object instances of the desired base + * class, or null if instances of the base class don't exist + */ + Collection findConfigObjects(String baseName, boolean filterAlias) { + ArrayList instances ; + + instances = (ArrayList)baseNameMap.get(baseName) ; + if (instances == null || instances.size() == 0) { + return null ; // This is not an error. + } + + if (filterAlias) { + ArrayList output = new ArrayList() ; + for (int i = 0 ; i < instances.size() ; i++) { + ConfigObject configObject = (ConfigObject)instances.get(i) ; + + if (! configObject.isAlias) { + output.add(configObject) ; + } + } + return output ; + } + else { + return instances ; + } + } + + /** + * Returns the ConfigObject associated with the name in the given + * ConfigCommand. This is used for evaluating retained built-in commands + * after the config file has already been parsed. The parser won't catch + * any of the exceptions generated by this method, so the error messages + * are wrapped accordingly. + * + * @param basename base name of the config object + * @param cmd command containing the name in argv[1] + * @return the found ConfigObject + */ + private ConfigObject findConfigObject(String baseName, ConfigCommand cmd) { + if (cmd.argc != 2 || !(cmd.argv[1] instanceof String)) + throw new IllegalArgumentException + (ConfigObject.errorMessage + (cmd, "Parameter must be a single string")) ; + try { + return findConfigObject(baseName, (String)cmd.argv[1]) ; + } + catch (IllegalArgumentException e) { + throw new IllegalArgumentException + (ConfigObject.errorMessage(cmd, e.getMessage())) ; + } + } + + /** + * This method gets called from a ConfigObject to evaluate a retained + * built-in command nested within a property command. These are commands + * that can't be evaluated until the entire config file is parsed. + * + * @param cmd the built-in command + * @return object representing result of evaluation + */ + Object evaluateBuiltIn(ConfigCommand cmd) { + int argc = cmd.argc ; + Object[] argv = cmd.argv ; + + if (cmd.commandName.equals("ConfigContainer")) { + // return a reference to this ConfigContainer + return this ; + } + else if (cmd.commandName.equals("Canvas3D")) { + // Look for canvases in the screen database. + return ((ConfigScreen)findConfigObject("Screen", cmd)).j3dCanvas ; + } + else if (baseNameMap.get(cmd.commandName) != null) { + // Handle commands of the form ({objectType} name) that return the + // object associated with the name. + return findConfigObject(cmd.commandName, cmd).targetObject ; + } + else { + // So far no other retained built-in commands. + throw new IllegalArgumentException + (ConfigObject.errorMessage(cmd, "Unknown built-in command \"" + + cmd.commandName + "\"")) ; + } + } + + /** + * Process the configuration after parsing the configuration file. + * Note: the processing order of the various config objects is + * significant. + * + * @param setVisible true if Viewer components should be visible + * @param transformCount number of TransformGroups with which + * ViewingPlatforms should be created + * @param attachBehaviors true if behaviors should be attached to + * ViewingPlatforms + */ + private void processConfig(boolean setVisible, + int transformCount, boolean attachBehaviors) { + + Collection c, s, pe, vp ; + this.setVisible = setVisible ; + this.transformCount = transformCount ; + + c = findConfigObjects("PhysicalBody") ; + if (c != null) { + processPhysicalBodies(c) ; + } + + pe = findConfigObjects("PhysicalEnvironment") ; + if (pe != null) { + processPhysicalEnvironments(pe) ; + } + + c = findConfigObjects("View") ; + if (c != null) { + processViews(c, setVisible) ; + } + + c = findConfigObjects("Device") ; + s = findConfigObjects("Sensor") ; + if (c != null) { + processDevices(c, s, pe) ; + } + + vp = findConfigObjects("ViewPlatform") ; + if (vp != null) { + processViewPlatforms(vp, transformCount) ; + } + + c = findConfigObjects("ViewPlatformBehavior") ; + if (c != null) { + processViewPlatformBehaviors(c, vp, attachBehaviors) ; + } + + c = findConfigObjects("Object") ; + if (c != null) { + processGenericObjects(c) ; + } + } + + // Process config physical environments into Java 3D physical + // environments. + private void processPhysicalEnvironments(Collection c) { + Iterator i = c.iterator() ; + while (i.hasNext()) { + ConfigPhysicalEnvironment e = (ConfigPhysicalEnvironment)i.next() ; + e.targetObject = e.createJ3dPhysicalEnvironment() ; + } + } + + // Process config physical bodys into Java 3D physical bodies. + private void processPhysicalBodies(Collection c) { + Iterator i = c.iterator() ; + while (i.hasNext()) { + ConfigPhysicalBody b = (ConfigPhysicalBody)i.next() ; + b.targetObject = b.createJ3dPhysicalBody() ; + } + } + + // Process config views into Java 3D Views and then create Viewer objects + // for them. This should only be called after all physical bodies and + // physical environments have been processed. + private void processViews(Collection c, boolean setVisible) { + Iterator i = c.iterator() ; + while (i.hasNext()) { + ConfigView v = (ConfigView)i.next() ; + v.targetObject = v.createViewer(setVisible) ; + } + } + + // Process config devices into Java 3D input devices. This should be done + // only after all views have been processed, as some InputDevice + // implementations require the AWT components associated with a view. + private void processDevices(Collection c, Collection s, Collection p) { + ConfigDevice cd = null ; + Iterator i = c.iterator() ; + while (i.hasNext()) { + cd = (ConfigDevice)i.next() ; + cd.targetObject = cd.createInputDevice() ; + } + + // Process device properties only after all InputDevices have been + // instantiated. Some InputDevice properties require references + // to other InputDevice implementations. + i = c.iterator() ; + while (i.hasNext()) ((ConfigDevice)i.next()).processProperties() ; + + // Initialize the devices only after all have been instantiated, as + // some InputDevices implementations are slaved to the first one + // created and will not initialize otherwise (e.g. LogitechTracker). + i = c.iterator() ; + while (i.hasNext()) { + cd = (ConfigDevice)i.next() ; + if (! cd.j3dInputDevice.initialize()) + throw new RuntimeException + (cd.errorMessage(cd.creatingCommand, + "could not initialize device \"" + + cd.instanceName + "\"")) ; + } + + // An InputDevice implementation will have created all its Sensors by + // the time initialize() returns. Retrieve and configure them here. + if (s != null) { + i = s.iterator() ; + while (i.hasNext()) { + ConfigSensor cs = (ConfigSensor)i.next() ; + cs.configureSensor() ; + cs.targetObject = cs.j3dSensor ; + } + } + + // Iterate through the PhysicalEnvironments and process the devices. + if (p != null) { + i = p.iterator() ; + while (i.hasNext()) + ((ConfigPhysicalEnvironment)i.next()).processDevices() ; + } + } + + // Process config view platforms into Java 3D viewing platforms. + private void processViewPlatforms(Collection c, int numTransforms) { + Iterator i = c.iterator() ; + while (i.hasNext()) { + ConfigViewPlatform cvp = (ConfigViewPlatform)i.next() ; + cvp.targetObject = cvp.createViewingPlatform(numTransforms) ; + } + } + + // Process the configured view platform behaviors. + private void processViewPlatformBehaviors(Collection behaviors, + Collection viewPlatforms, + boolean attach) { + Iterator i = behaviors.iterator() ; + while (i.hasNext()) { + ConfigViewPlatformBehavior b = + (ConfigViewPlatformBehavior)i.next() ; + b.targetObject = b.createViewPlatformBehavior() ; + } + + // Process properties only after all behaviors are instantiated. + i = behaviors.iterator() ; + while (i.hasNext()) + ((ConfigViewPlatformBehavior)i.next()).processProperties() ; + + // Attach behaviors to platforms after properties processed. + if (attach && viewPlatforms != null) { + i = viewPlatforms.iterator() ; + while (i.hasNext()) + ((ConfigViewPlatform)i.next()).processBehavior() ; + } + } + + // Process generic objects. + private void processGenericObjects(Collection objects) { + Iterator i = objects.iterator() ; + while (i.hasNext()) { + ConfigObject o = (ConfigObject)i.next() ; + o.targetObject = o.createTargetObject() ; + } + + // Process properties only after all target objects are instantiated. + i = objects.iterator() ; + while (i.hasNext()) ((ConfigObject)i.next()).processProperties() ; + } + + // Returns a read-only Set containing all unique Java 3D objects of the + // specified base class. + private ReadOnlySet createSet(String baseName) { + Collection c = findConfigObjects(baseName, true) ; + if (c == null || c.size() == 0) + return null ; + + Iterator i = c.iterator() ; + ArrayList l = new ArrayList() ; + while (i.hasNext()) l.add(((ConfigObject)i.next()).targetObject) ; + + return new ReadOnlySet(l) ; + } + + // Returns a read-only Map that maps all names in the specified base + // class, including aliases, to their corresponding Java 3D objects. + private ReadOnlyMap createMap(String baseName) { + Collection c = findConfigObjects(baseName, false) ; + if (c == null || c.size() == 0) + return null ; + + Iterator i = c.iterator() ; + HashMap m = new HashMap() ; + while (i.hasNext()) { + ConfigObject co = (ConfigObject)i.next() ; + if (co.isAlias) + m.put(co.instanceName, co.original.targetObject) ; + else + m.put(co.instanceName, co.targetObject) ; + } + + return new ReadOnlyMap(m) ; + } + + /** + * Returns a read-only Set of all configured PhysicalBody instances in the + * order they were defined in the configuration file. + * + * PhysicalBody instances are created with the following command:

+ *

+ * (NewPhysicalBody <instance name> + * [Alias <alias name>]) + *
+ * + * The PhysicalBody is configured through the following command:

+ *

+ * (PhysicalBodyProperty <instance name> + * <property name> <property value>) + *
+ * + * @return read-only Set of all unique instances, or null + */ + public Set getPhysicalBodies() { + if (bodies != null) return bodies ; + bodies = createSet("PhysicalBody") ; + return bodies ; + } + + /** + * Returns a read-only Map that maps PhysicalBody names to instances. + * Names may be aliases and if so will map to the original instances. + * + * @return read-only Map from names to PhysicalBody instances, or null if + * no instances + */ + public Map getNamedPhysicalBodies() { + if (bodyMap != null) return bodyMap ; + bodyMap = createMap("PhysicalBody") ; + return bodyMap ; + } + + /** + * Returns a read-only Set of all configured PhysicalEnvironment instances + * in the order they were defined in the configuration file.

+ * + * PhysicalEnvironment instances are created with the following command:

+ *

+ * (NewPhysicalEnvironment <instance name> + * [Alias <alias name>]) + *
+ * + * The PhysicalEnvironment is configured through the following command:

+ *

+ * (PhysicalEnvironmentProperty <instance name> + * <property name> <property value>) + *
+ * + * @return read-only Set of all unique instances, or null + */ + public Set getPhysicalEnvironments() { + if (environments != null) return environments ; + environments = createSet("PhysicalEnvironment") ; + return environments ; + } + + /** + * Returns a read-only Map that maps PhysicalEnvironment names to + * instances. Names may be aliases and if so will map to the original + * instances. + * + * @return read-only Map from names to PhysicalEnvironment instances, or + * null if no instances + */ + public Map getNamedPhysicalEnvironments() { + if (environmentMap != null) return environmentMap ; + environmentMap = createMap("PhysicalEnvironment") ; + return environmentMap ; + } + + /** + * Returns a read-only Set of all configured Viewer instances in the order + * they were defined in the configuration file. The Viewers will have + * incorporated any PhysicalEnvironment and PhysicalBody objects specfied + * for them in the configuration file, and will be attached to any + * ViewingPlatforms specified for them.

+ * + * Viewer instances are created with the following command:

+ *

+ * (NewView <instance name> [Alias <alias name>]) + *
+ * + * The Viewer is configured through the following command:

+ *

+ * (ViewProperty <instance name> + * <property name> <property value>) + *
+ * + * @return read-only Set of all unique instances, or null + */ + public Set getViewers() { + if (viewers != null) return viewers ; + viewers = createSet("View") ; + return viewers ; + } + + /** + * Returns a read-only Map that maps Viewer names to instances. + * Names may be aliases and if so will map to the original instances. + * The Viewers will have incorporated any PhysicalEnvironment and + * PhysicalBody objects specfied for them in the configuration file, and + * will be attached to any ViewingPlatforms specified for them.

+ * + * @return read-only Map from names to Viewer instances, or + * null if no instances + */ + public Map getNamedViewers() { + if (viewerMap != null) return viewerMap ; + viewerMap = createMap("View") ; + return viewerMap ; + } + + /** + * Returns a read-only Set of all configured InputDevice instances in the + * order they were defined in the configuration file. All InputDevice + * instances in the set are initialized and registered with any + * PhysicalEnvironments that reference them.

+ * + * InputDevice instances are created with the following command:

+ *

+ * (NewDevice <instanceName> <className> + * [Alias <alias name>]) + *
+ * + * className must be the fully-qualified name of a class that + * implements the InputDevice interface. The implementation + * must provide a parameterless constructor.

+ * + * The InputDevice is configured through the DeviceProperty command:

+ *

+ * (DeviceProperty <instanceName> <propertyName> + * <arg0> ... <argn>) + *
+ * + * propertyName must be the name of a input device method that + * takes an array of Objects as its only parameter; the array is populated + * with the values of arg0 through argn when the method is + * invoked to set the property. These additional requirements for + * configurable input devices can usually be fulfilled by extending or + * wrapping available InputDevice implementations. + * + * @return read-only Set of all unique instances, or null + */ + public Set getInputDevices() { + if (devices != null) return devices ; + devices = createSet("Device") ; + return devices ; + } + + /** + * Returns a read-only Map that maps InputDevice names to instances. + * Names may be aliases and if so will map to the original instances. All + * InputDevice instances in the map are initialized and registered with + * any PhysicalEnvironments that reference them. + * + * @return read-only Map from names to InputDevice instances, or + * null if no instances + * @see #getInputDevices + */ + public Map getNamedInputDevices() { + if (deviceMap != null) return deviceMap ; + deviceMap = createMap("Device") ; + return deviceMap ; + } + + /** + * Returns a read-only Set of all configured Sensor instances in the order + * they were defined in the configuration file. The associated + * InputDevices are all initialized and registered with any + * PhysicalEnvironments that reference them.

+ * + * Sensor instances are named with the following command:

+ *

+ * (NewSensor <instance name> <device name> + * <sensor index> [Alias <alias name>]) + *
+ * + * device name is the instance name of a previously defined + * InputDevice, and sensor index is the index of the Sensor to be + * bound to instance name. The InputDevice implementation is + * responsible for creating its own Sensor objects, so this command does + * not create any new instances.

+ * + * The Sensor is configured through the SensorProperty command:

+ *

+ * (SensorProperty <instance name> <property name> + * <property value>) + *
+ * + * With the sole exception of the Sensor assigned to the head tracker, + * none of the Sensors defined in the configuration file are placed into + * the Sensor array maintained by a PhysicalEnvironment. + * + * @return read-only Set of all unique instances, or null + */ + public Set getSensors() { + if (sensors != null) return sensors ; + sensors = createSet("Sensor") ; + return sensors ; + } + + /** + * Returns a read-only Map that maps Sensor names to instances. Names may + * be aliases and if so will map to the original instances. The + * associated InputDevices are all initialized and registered with any + * PhysicalEnvironments that reference them.

+ * + * With the sole exception of the Sensor assigned to the head tracker, + * none of the Sensors defined in the configuration file are placed into + * the Sensor array maintained by a PhysicalEnvironment. + * + * @return read-only Map from names to Sensor instances, or + * null if no instances + */ + public Map getNamedSensors() { + if (sensorMap != null) return sensorMap ; + sensorMap = createMap("Sensor") ; + return sensorMap ; + } + + /** + * Returns a read-only Set of all configured ViewingPlatform instances in + * the order they were defined in the configuration file. The + * ConfigContainer class itself does not attach the ViewingPlatform + * instances to any scengraph components or universe Locales; they are not + * "live" until made so by a separate client such as ConfiguredUniverse. + * + * ViewingPlatform instances are created with the following command:

+ *

+ * (NewViewPlatform <instance name> + * [Alias <alias name>]) + *
+ * + * The ViewingPlatform is configured through the following command:

+ *

+ * (ViewPlatformProperty <instance name> <property name> + * <property value>) + *
+ * + * @return read-only Set of all unique instances, or null + */ + public Set getViewingPlatforms() { + if (platforms != null) return platforms ; + platforms = createSet("ViewPlatform") ; + return platforms ; + } + + /** + * Returns a read-only Map that maps ViewingPlatform names to instances. + * Names may be aliases and if so will map to the original instances. The + * ConfigContainer class itself does not attach the ViewingPlatform + * instances to any scengraph components or universe Locales; they are not + * "live" until made so by a separate client such as ConfiguredUniverse. + * + * @return read-only Map from names to ViewingPlatform instances, or + * null if no instances + */ + public Map getNamedViewingPlatforms() { + if (platformMap != null) return platformMap ; + platformMap = createMap("ViewPlatform") ; + return platformMap ; + } + + /** + * Returns a read-only Set of all configured ViewPlatformBehavior + * instances in the order they were defined in the configuration file.

+ * + * The behaviors are attached to any ViewingPlatforms that specified them; + * that is, the setViewPlatformBehavior and + * setViewingPlatform methods of ViewingPlatform and + * ViewPlatformBehavior have been called if appropriate. However, a + * behavior's initialize method is not called until the + * ViewingPlatform to which it is attached is made live.

+ * + * ViewPlatformBehavior instances are created by the following command:

+ *

+ * (NewViewPlatformBehavior <instanceName> <className>) + *
+ * + * className must be the fully qualified name of a concrete class + * that extends the abstract ViewPlatformBehavior class. The + * implementation must provide a parameterless constructor.

+ * + * The behavior is configured using ViewPlatformBehaviorProperty:

+ *

+ * (ViewPlatformBehaviorProperty <instanceName> + * <propertyName> <arg0> ... <argn>) + *
+ * + * ViewPlatformBehavior subclasses inherit a number of pre-defined + * properties that can be directly specified with the propertyName + * string; see the configuration file documentation for details.

+ * + * Concrete ViewPlatformBehavior instances can also define their own + * unique properties. In those cases, propertyName must be the + * name of a behavior method that takes an array of Objects as its only + * parameter; the array is populated with the values of arg0 + * through argn when the method is invoked to set the property. + * These additional requirements for configurable behaviors can usually be + * fulfilled by extending or wrapping available ViewPlatformBehavior + * subclasses. + * + * @return read-only Set of all unique instances, or null + */ + public Set getViewPlatformBehaviors() { + if (behaviors != null) return behaviors ; + behaviors = createSet("ViewPlatformBehavior") ; + return behaviors ; + } + + /** + * Returns a read-only Map that maps ViewPlatformBehavior names to + * instances. Names may be aliases and if so will map to the original + * instances.

+ * + * The behaviors are attached to any ViewingPlatforms that specified them; + * that is, the setViewPlatformBehavior and + * setViewingPlatform methods of ViewingPlatform and + * ViewPlatformBehavior have been called if appropriate. However, a + * behavior's initialize method is not called until the + * ViewingPlatform to which it is attached is made live.

+ * + * @return read-only Map from names to ViewPlatformBehavior instances, or + * null if no instances + * @see #getViewPlatformBehaviors + */ + public Map getNamedViewPlatformBehaviors() { + if (behaviorMap != null) return behaviorMap ; + behaviorMap = createMap("ViewPlatformBehavior") ; + return behaviorMap ; + } + + /** + * Returns a read-only Map containing the named Canvas3D instances used by + * the specified Viewer. Names may be aliases and if so will map to the + * original instances. The set of unique Canvas3D instances used by a + * Viewer may be obtained by calling the Viewer's accessor methods + * directly.

+ * + * A named Canvas3D is created and added to a Viewer whenever any of the + * following configuration commands are used:

+ *

+ * (ViewProperty <view> Screen <screenName>)
+ * (ViewProperty <view> Window <windowName>) + *
+ * + * view is the name of a Viewer created with the NewView command. + * The screenName and windowName parameters of the above + * commands are the keys to use when looking up the associated Canvas3D + * instances in the Map returned by this method. Note: the + * NewScreen and NewWindow commands do not create Canvas3D + * instances themselves; they are created only by the above configuration + * commands. + * + * @param viewName the name of the Viewer + * @return read-only Map containing the Viewer's named Canvas3D instances + */ + public Map getNamedCanvases(String viewName) { + Map m = (Map)viewCanvasMap.get(viewName) ; + if (m != null) return m ; + + m = new HashMap() ; + ConfigView cv = (ConfigView)findConfigObject("View", viewName) ; + Iterator i = cv.screens.iterator() ; + while (i.hasNext()) { + ConfigScreen cs = (ConfigScreen)i.next() ; + m.put(cs.instanceName, cs.j3dCanvas) ; + + // The aliases list contains all alias strings for the canvas. + Iterator j = cs.aliases.iterator() ; + while (j.hasNext()) m.put(j.next(), cs.j3dCanvas) ; + } + m = new ReadOnlyMap(m) ; + viewCanvasMap.put(viewName, m) ; + return m ; + } + + /** + * Returns a read-only Set of all generic configuration object + * instances in the order they were defined in the configuration file.

+ * + * Generic object instances are created with the following command:

+ *

+ * (NewObject <instanceName> <className>) + *
+ * + * className must be the fully-qualified name of a class that + * provides a parameterless constructor.

+ * + * The object is configured through the ObjectProperty command:

+ *

+ * (ObjectProperty <instanceName> <propertyName> + * <arg0> ... <argn>) + *
+ * + * propertyName must be the name of a method provided by object + * instanceName. It must take an array of Objects as its only + * parameter; the array is populated with the values of arg0 + * through argn when the method is invoked to set the property. + * These additional requirements for configurable objects can usually be + * fulfilled by extending or wrapping available object classes. + * + * @return read-only Set of all unique instances, or null + */ + public Set getGenericObjects() { + if (genericObjects != null) return genericObjects ; + genericObjects = createSet("Object") ; + return genericObjects ; + } + + /** + * Returns a read-only Map that maps generic object names to + * instances. Names may be aliases and if so will map to the original + * instances. + * + * @return read-only Map from names to generic object instances, or + * null if no instances + * @see #getGenericObjects + */ + public Map getNamedGenericObjects() { + if (genericObjectMap != null) return genericObjectMap ; + genericObjectMap = createMap("Object") ; + return genericObjectMap ; + } + + /** + * Returns the number of TransformGroups with which ViewingPlatforms + * should be created. This is useful for clients that wish to provide a + * default ViewingPlatform if the configuration file doesn't specify one. + * + * @return the number of TransformGroups + */ + public int getViewPlatformTransformCount() { + return transformCount ; + } + + /** + * Returns whether Viewers should be created with their AWT components + * initially visible or invisible. This is useful for clients that wish + * to provide a default Viewer if the configuration file doesn't specify + * one. + * + * @return true if Viewer components should be initially visible; false + * otherwise + */ + public boolean getViewerVisibility() { + return setVisible ; + } + + /** + * Release memory references used by this ConfigContainer. All Sets and + * Maps obtained from this ConfigContainer are cleared. + */ + public void clear() { + // Clear baseNameList. + Iterator i = baseNameMap.values().iterator() ; + while (i.hasNext()) ((Collection)i.next()).clear() ; + baseNameMap.clear() ; + + // Clear viewCanvasMap. + i = viewCanvasMap.values().iterator() ; + while (i.hasNext()) ((ReadOnlyMap)i.next()).map.clear() ; + viewCanvasMap.clear() ; + + // Release reference to file name. + currentFileName = null ; + + // Clear and release sets. + if (bodies != null) { + bodies.collection.clear() ; + bodies = null ; + } + if (environments != null) { + environments.collection.clear() ; + environments = null ; + } + if (devices != null) { + devices.collection.clear() ; + devices = null ; + } + if (sensors != null) { + sensors.collection.clear() ; + sensors = null ; + } + if (behaviors != null) { + behaviors.collection.clear() ; + behaviors = null ; + } + if (platforms != null) { + platforms.collection.clear() ; + platforms = null ; + } + if (viewers != null) { + viewers.collection.clear() ; + viewers = null ; + } + if (genericObjects != null) { + genericObjects.collection.clear() ; + genericObjects = null ; + } + + // Clear and release maps. + if (bodyMap != null) { + bodyMap.map.clear() ; + bodyMap = null ; + } + if (environmentMap != null) { + environmentMap.map.clear() ; + environmentMap = null ; + } + if (deviceMap != null) { + deviceMap.map.clear() ; + deviceMap = null ; + } + if (sensorMap != null) { + sensorMap.map.clear() ; + sensorMap = null ; + } + if (behaviorMap != null) { + behaviorMap.map.clear() ; + behaviorMap = null ; + } + if (platformMap != null) { + platformMap.map.clear() ; + platformMap = null ; + } + if (viewerMap != null) { + viewerMap.map.clear() ; + viewerMap = null ; + } + if (genericObjectMap != null) { + genericObjectMap.map.clear() ; + genericObjectMap = null ; + } + + } + + /** + * Returns the config file URL based on system properties. The current + * implementation of this method parses the j3d.configURL property as a + * URL string. For example, the following command line would specify that + * the config file is taken from the file "j3dconfig" in the current + * directory: + *
    + * java -Dj3d.configURL=file:j3dconfig ... + *
+ * + * @return the URL of the config file; null is returned if no valid + * URL is defined by the system properties + */ + public static URL getConfigURL() { + return getConfigURL(null) ; + } + + /** + * Returns the config file URL based on system properties. The current + * implementation of this method parses the j3d.configURL property as a + * URL string. For example, the following command line would specify that + * the config file is taken from the file "j3dconfig" in the current + * directory: + *
    + * java -Dj3d.configURL=file:j3dconfig ... + *
+ * + * @param defaultURLString the default string used to construct + * the URL if the appropriate system properties are not defined + * @return the URL of the config file; null is returned if no + * valid URL is defined either by the system properties or the + * default URL string + */ + public static URL getConfigURL(String defaultURLString) { + URL url = null ; + String urlString = null ; + final String defProp = defaultURLString ; + + urlString = (String)java.security.AccessController.doPrivileged + (new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty("j3d.configURL", defProp) ; + } + }) ; + + if (urlString == null) { + return null ; + } + try { + url = new URL(urlString) ; + } + catch(MalformedURLException e) { + System.out.println(e) ; + return null ; + } + return url ; + } + + // A general purpose read-only Map backed by a HashMap. + private static class ReadOnlyMap extends AbstractMap { + HashMap map ; + private Set entrySet = null ; + + ReadOnlyMap(Map map) { + this.map = new HashMap(map) ; + } + + // overridden for efficiency + public Object get(Object key) { + return map.get(key) ; + } + + // overridden for efficiency + public boolean containsKey(Object key) { + return map.containsKey(key) ; + } + + // overridden for efficiency + public boolean containsValue(Object value) { + return map.containsValue(value) ; + } + + public Set entrySet() { + if (entrySet == null) + entrySet = new ReadOnlySet(map.entrySet()) ; + + return entrySet ; + } + } + + // A general purpose read-only Set backed by a Collection containing + // unique objects. + private static class ReadOnlySet extends AbstractSet { + Collection collection = null ; + + ReadOnlySet(Collection c) { + this.collection = c ; + } + + public int size() { + return collection.size() ; + } + + public Iterator iterator() { + return new ReadOnlyIterator(collection.iterator()) ; + } + } + + // A general purpose read-only Iterator backed by another Iterator. + private static class ReadOnlyIterator implements Iterator { + private Iterator i ; + + ReadOnlyIterator(Iterator i) { + this.i = i ; + } + + public boolean hasNext() { + return i.hasNext() ; + } + + public Object next() { + return i.next() ; + } + + public void remove() { + throw new UnsupportedOperationException() ; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigDevice.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigDevice.java new file mode 100644 index 0000000..35b53cd --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigDevice.java @@ -0,0 +1,66 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; +import javax.media.j3d.InputDevice ; + +/** + * Mostly empty now; ConfigObject provides all required methods. + */ +class ConfigDevice extends ConfigObject { + /** + * The corresponding Java 3D core InputDevice instance. + */ + InputDevice j3dInputDevice ; + + /** + * Instantiate an InputDevice of the given class name. + * + * @return the InputDevice, or null if error + */ + InputDevice createInputDevice() { + j3dInputDevice = (InputDevice)createTargetObject() ; + return j3dInputDevice ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigObject.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigObject.java new file mode 100644 index 0000000..6783253 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigObject.java @@ -0,0 +1,371 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.lang.reflect.* ; +import java.util.List ; +import java.util.ArrayList ; + +/** + * Base class for all configuration objects. A ConfigObject processes + * configuration parameters for a target object, which is instantiated after + * the configuration file is parsed. The ConfigObject then applies its + * configuration properties to the target object.

+ * + * Generic base implementations are provided for the initialize(), + * setProperty(), and processProperties() methods. These implementations + * assume target objects that are unknown and thus instantiated via + * introspection. Property names are assumed to be method names that take an + * array of Objects as a parameter; they are invoked through introspection as + * well.

+ * + * Most ConfigObjects target concrete Java 3D core classes, so these + * implementations are usually overridden to instantiate those objects and + * call their methods directly. + */ +class ConfigObject { + /** + * The base name of this object, derived from the configuration command + * which created it. This is constructed by stripping off the leading + * "New" prefix or the trailing "Attribute", "Property", or "Alias" suffix + * of the command name. The name of the ConfigObject subclass which + * handles the command is derived by adding "Config" as a prefix to the + * base name. + */ + String baseName = null ; + + /** + * The instance name of this object, as specified in the configuration + * file. + */ + String instanceName = null ; + + /** + * The corresponding target object which this ConfigObject is configuring. + */ + Object targetObject = null ; + + /** + * The name of the target class this object is configuring. + */ + String targetClassName = null ; + + /** + * The Class object for the target. + */ + Class targetClass = null ; + + /** + * Configurable properties gathered by this object, represented by the + * ConfigCommands that set them. + */ + List properties = new ArrayList() ; + + /** + * The ConfigContainer in which this ConfigObject is contained. + */ + ConfigContainer configContainer = null ; + + /** + * The command that created this class. + */ + ConfigCommand creatingCommand = null ; + + /** + * If true, this object is an alias to another. + */ + boolean isAlias = false ; + + /** + * If isAlias is true, this references the original object. + */ + ConfigObject original = null ; + + /** + * List of alias Strings for this object if it's not an alias itself. + */ + List aliases = new ArrayList() ; + + /** + * The base initialize() implementation. This takes a ConfigCommand with + * three arguments: the command name, the instance name, and the name of + * the target class this ConfigObject is configuring. The command in the + * configuration file should have the form:

+ * + * (New{configType} {instanceName} {className})

+ * + * For example, (NewDevice tracker com.sun.j3d.input.LogitechTracker) will + * first cause ConfigDevice to be instantiated, which will then be + * initialized with this method. After all the properties are collected, + * ConfigDevice will instantiate com.sun.j3d.input.LogitechTracker, + * evaluate its properties, and allow references to it in the + * configuration file by the name "tracker".

+ * + * It's assumed the target class will be instantiated through + * introspection and its properties set through introspection as well. + * Most config objects (ConfigScreen, ConfigView, ConfigViewPlatform, + * ConfigPhysicalBody, and ConfigPhysicalEnvironment) target a concrete + * core Java 3D class and will instantiate them directly, so they override + * this method. + * + * @param c the command that created this ConfigObject + */ + protected void initialize(ConfigCommand c) { + if (c.argc != 3) { + syntaxError("Wrong number of arguments to " + c.commandName) ; + } + + if (!isName(c.argv[1])) { + syntaxError("The first argument to " + c.commandName + + " must be the instance name") ; + } + + if (!isName(c.argv[2])) { + syntaxError("The second argument to " + c.commandName + + " must be the class name") ; + } + + targetClassName = (String)c.argv[2] ; + } + + /** + * The base setProperty() implementation. This implementation assumes the + * property needs to be set by introspection on the property name as a + * method that accepts an array of Objects. That is, the command in the + * configuration file is of the form:

+ * + * ({type}Property {instance name} {method name} {arg0} ... {argn})

+ * + * For example, (DeviceProperty tracker SerialPort "/dev/ttya") will + * invoke the method named "SerialPort" in the object referenced by + * "tracker" with an array of 1 Object containing the String + * "/dev/ttya".

+ * + * The property is stored as the original ConfigCommand and is evaluated + * after the configuration file has been parsed. It is overridden by + * subclasses that instantiate concrete core Java 3D classes with known + * method names. + * + * @param c the command that invoked this method + */ + protected void setProperty(ConfigCommand c) { + if (c.argc < 4) { + syntaxError("Wrong number of arguments to " + c.commandName) ; + } + + if (!isName(c.argv[1])) { + syntaxError("The first argument to " + c.commandName + + " must be the instance name") ; + } + + if (!isName(c.argv[2])) { + syntaxError("The second argument to " + c.commandName + + " must be the property name") ; + } + + properties.add(c) ; + } + + /** + * Instantiates the target object. + */ + protected Object createTargetObject() { + if (targetClassName == null) + return null ; + + targetClass = getClassForName(creatingCommand, targetClassName) ; + targetObject = getNewInstance(creatingCommand, targetClass) ; + + return targetObject ; + } + + /** + * Return the class for the specified class name string. + * + * @param className the name of the class + * @return the object representing the class + */ + protected Class getClassForName(ConfigCommand cmd, String className) { + try { + // Use the system class loader. If the Java 3D jar files are + // installed directly in the JVM's lib/ext directory, then the + // default class loader won't find user classes outside ext. + return Class.forName(className, true, + ClassLoader.getSystemClassLoader()) ; + } + catch (ClassNotFoundException e) { + throw new IllegalArgumentException + (errorMessage(cmd, "Class \"" + className + "\" not found")) ; + } + } + + /** + * Return an instance of the class specified by the given class object. + * + * @param objectClass the object representing the class + * @return a new instance of the class + */ + protected Object getNewInstance(ConfigCommand cmd, Class objectClass) { + try { + return objectClass.newInstance() ; + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException + (errorMessage(cmd, "Illegal access to object class")) ; + } + catch (InstantiationException e) { + throw new IllegalArgumentException + (errorMessage(cmd, "Instantiation error for object class")) ; + } + } + + /** + * Evaluate properties for the the given class instance. The property + * names are used as the names of methods to be invoked by the instance. + * Each such method takes an array of Objects as its only parameter. The + * array will contain Objects corresponding to the property values. + */ + protected void processProperties() { + evaluateProperties(this.targetClass, + this.targetObject, this.properties) ; + + // Potentially holds a lot of references, and not needed anymore. + this.properties.clear() ; + } + + /** + * Evaluate properties for the the given class instance. + * + * @param objectClass the class object representing the given class + * @param objectInstance the class instance whose methods will be invoked + * @param properties list of property setting commands + */ + protected void evaluateProperties(Class objectClass, + Object objectInstance, + List properties) { + + // Holds the single parameter passed to the class instance methods. + Object[] parameters = new Object[1] ; + + // Specifies the class of the single method parameter. + Class[] parameterTypes = new Class[1] ; + + // All property methods use Object[] as their single parameter, which + // happens to be the same type as the parameters variable above. + parameterTypes[0] = parameters.getClass() ; + + // Loop through all property commands and invoke the appropriate + // method for each one. Property commands are of the form: + // ({configClass}Property {instanceName} {methodName} {arg} ...) + for (int i = 0 ; i < properties.size() ; i++) { + ConfigCommand cmd = (ConfigCommand)properties.get(i) ; + String methodName = (String)cmd.argv[2] ; + Object[] argv = new Object[cmd.argc - 3] ; + + for (int a = 0 ; a < argv.length ; a++) { + argv[a] = cmd.argv[a + 3] ; + if (argv[a] instanceof ConfigCommand) { + // Evaluate a delayed built-in command. + ConfigCommand bcmd = (ConfigCommand)argv[a] ; + argv[a] = configContainer.evaluateBuiltIn(bcmd) ; + } + } + + parameters[0] = argv ; + + try { + Method objectMethod = + objectClass.getMethod(methodName, parameterTypes) ; + + objectMethod.invoke(objectInstance, parameters) ; + } + catch (NoSuchMethodException e) { + throw new IllegalArgumentException + (errorMessage + (cmd, "Unknown property \"" + methodName + "\"")) ; + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException + (errorMessage + (cmd, "Illegal access to \"" + methodName + "\"")) ; + } + catch (InvocationTargetException e) { + throw new IllegalArgumentException + (errorMessage(cmd, e.getTargetException().getMessage())) ; + } + } + } + + /** + * Throws an IllegalArgumentException with the specified description. + * This is caught by the parser which prints out error diagnostics and + * continues parsing if it can. + * + * @param s string describing the syntax error + */ + protected void syntaxError(String s) { + throw new IllegalArgumentException(s) ; + } + + /** + * Constructs an error message from the given string and file information + * from the given command. + */ + static String errorMessage(ConfigCommand cmd, String s) { + return + "\n" + s + "\nat line " + cmd.lineNumber + + " in " + cmd.fileName + "\n" + cmd + "\n" ; + } + + /** + * Check if the argument is a name string. + * + * @param o the object to be checked + * @return true if the object is an instance of String + */ + protected boolean isName(Object o) { + return (o instanceof String) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalBody.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalBody.java new file mode 100644 index 0000000..f735060 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalBody.java @@ -0,0 +1,178 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import java.awt.event.*; +import java.lang.Integer; +import java.io.*; +import javax.media.j3d.*; +import javax.vecmath.*; +import java.util.*; + +class ConfigPhysicalBody extends ConfigObject { + + Point3d leftEyePosition = new Point3d(-0.033, 0.0, 0.0); + Point3d rightEyePosition = new Point3d(0.033, 0.0, 0.0); + double stereoEyeSeparation = Double.MAX_VALUE; + + Point3d leftEarPosition = new Point3d(-0.080, -0.030, 0.09); + Point3d rightEarPosition = new Point3d(0.080, -0.030, 0.09); + + double nominalEyeHeightFromGround = 1.68; + double nominalEyeOffsetFromNominalScreen = 0.4572; + + Matrix4d headToHeadTracker = new Matrix4d( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + + PhysicalBody j3dPhysicalBody; + + // Overridden to do nothing. + protected void initialize(ConfigCommand command) { + } + + protected void setProperty(ConfigCommand command) { + int argc = command.argc; + Object[] argv = command.argv; + String prop; + Object val; + + // Check that arg[1] and arg[2] are strings + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName); + } + + if (!isName(argv[1])) { + syntaxError("The first argument to " + command.commandName + + " must be a name"); + } + + if (!isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be an property/attribute name"); + } + + prop = (String) argv[2]; + val = argv[3]; + + if (prop.equals("StereoEyeSeparation")) { + if (!(val instanceof Double)) { + syntaxError("StereoEyeSeparation must be a number"); + } + stereoEyeSeparation = ((Double) val).doubleValue(); + } + else if (prop.equals("LeftEyePosition")) { + if (!(val instanceof Point3d)) { + syntaxError("LeftEyePosition must be a point"); + } + leftEyePosition = (Point3d) val; + } + else if (prop.equals("RightEyePosition")) { + if (!(val instanceof Point3d)) { + syntaxError("RightEyePosition must be a point"); + } + rightEyePosition = (Point3d) val; + } + else if (prop.equals("LeftEarPosition")) { + if (!(val instanceof Point3d)) { + syntaxError("LeftEarPosition must be a point"); + } + leftEarPosition = (Point3d) val; + } + else if (prop.equals("RightEarPosition")) { + if (!(val instanceof Point3d)) { + syntaxError("RightEarPosition must be a point"); + } + leftEarPosition = (Point3d) val; + } + else if (prop.equals("NominalEyeHeightFromGround")) { + if (!(val instanceof Double)) { + syntaxError("NominalEyeHeightFromGround must be a number"); + } + nominalEyeHeightFromGround = ((Double) val).doubleValue(); + } + else if (prop.equals("NominalEyeOffsetFromNominalScreen")) { + if (!(val instanceof Double)) { + syntaxError("NominalEyeOffsetFromNominalScreen " + + "must be a number"); + } + nominalEyeOffsetFromNominalScreen = ((Double) val).doubleValue(); + } + else if (prop.equals("HeadToHeadTracker")) { + if (!(val instanceof Matrix4d)) { + syntaxError("HeadToHeadTracker must be a matrix"); + } + headToHeadTracker = (Matrix4d) val; + } + else { + syntaxError("Unknown " + command.commandName + + " \"" + prop + "\"") ; + } + } + + PhysicalBody createJ3dPhysicalBody() { + // Transfer all the information from the config version + if (stereoEyeSeparation < Double.MAX_VALUE) { + leftEyePosition.set(-stereoEyeSeparation / 2.0, 0.0, 0.0); + rightEyePosition.set(stereoEyeSeparation / 2.0, 0.0, 0.0); + } + + j3dPhysicalBody = new PhysicalBody(leftEyePosition, rightEyePosition, + leftEarPosition, rightEarPosition); + + j3dPhysicalBody.setHeadToHeadTracker( + new Transform3D(headToHeadTracker)); + + j3dPhysicalBody.setNominalEyeHeightFromGround( + nominalEyeHeightFromGround); + j3dPhysicalBody.setNominalEyeOffsetFromNominalScreen( + nominalEyeOffsetFromNominalScreen); + + return j3dPhysicalBody ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalEnvironment.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalEnvironment.java new file mode 100644 index 0000000..d228773 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigPhysicalEnvironment.java @@ -0,0 +1,179 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import javax.media.j3d.* ; +import javax.vecmath.* ; +import java.util.* ; + +class ConfigPhysicalEnvironment extends ConfigObject { + + /** + * The corresponding J3D core PhysicalEnvironment instance. + */ + PhysicalEnvironment j3dPhysicalEnvironment = null ; + + /** + * The coexistence to tracker base matrix. + */ + Matrix4d coexistenceToTrackerBase = null ; + + // All other configurable attributes. + private ConfigSensor headTracker = null ; + private ArrayList inputDevices = new ArrayList() ; + private int coexistenceCenterInPworldPolicy = View.NOMINAL_SCREEN ; + + /** + * Overrides initialize() to do nothing. + */ + protected void initialize(ConfigCommand command) { + } + + /** + * Handles the commands + * (PhysicalEnvironmentAttribute {instance} {attrName} {attrValue}) and + * (PhysicalEnvironmentProperty {instance} {attrName} {attrValue}). + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand command) { + Object val ; + Object[] argv = command.argv ; + int argc = command.argc ; + String sval, prop ; + + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!isName(argv[1])) { + syntaxError("The first argument to " + command.commandName + + " must be a name") ; + } + + if (!isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be a property name") ; + } + + prop = (String)argv[2] ; + val = argv[3] ; + + if (prop.equals("CoexistenceCenterInPworldPolicy")) { + if (!(val instanceof String)) + syntaxError("CoexistenceCenterInPworldPolicy must be string") ; + + sval = (String)val ; + if (sval.equals("NOMINAL_HEAD")) + coexistenceCenterInPworldPolicy = View.NOMINAL_HEAD ; + else if (sval.equals("NOMINAL_SCREEN")) + coexistenceCenterInPworldPolicy = View.NOMINAL_SCREEN ; + else if (sval.equals("NOMINAL_FEET")) + coexistenceCenterInPworldPolicy = View.NOMINAL_FEET ; + else + syntaxError("Illegal value " + sval + + " for CoexistenceCenterInPworldPolicy") ; + } + else if (prop.equals("CoexistenceToTrackerBase")) { + if (val instanceof Matrix4d) + coexistenceToTrackerBase = (Matrix4d)val ; + else + syntaxError("CoexistenceToTrackerBase must be a Matrix4d") ; + } + else if (prop.equals("InputDevice")) { + if (!(val instanceof String)) + syntaxError("InputDevice must be a name") ; + + sval = (String)val ; + inputDevices.add(configContainer.findConfigObject("Device", sval)); + } + else if (prop.equals("HeadTracker")) { + if (!(val instanceof String)) + syntaxError("HeadTracker must be a Sensor name") ; + + sval = (String)val ; + headTracker = + (ConfigSensor)configContainer.findConfigObject("Sensor", sval); + } + else { + syntaxError("Unknown " + command.commandName + + " \"" + prop + "\"") ; + } + } + + /** + * Create a core Java 3D PhysicalEnvironment instance using the attributes + * gathered by this object. + */ + PhysicalEnvironment createJ3dPhysicalEnvironment() { + j3dPhysicalEnvironment = new PhysicalEnvironment() ; + + j3dPhysicalEnvironment.setCoexistenceCenterInPworldPolicy + (coexistenceCenterInPworldPolicy) ; + + if (coexistenceToTrackerBase != null) + j3dPhysicalEnvironment.setCoexistenceToTrackerBase + (new Transform3D(coexistenceToTrackerBase)) ; + + return j3dPhysicalEnvironment ; + } + + /** + * Process the devices associated with the PhysicalEnvironment. + */ + void processDevices() { + for (int j = 0; j < inputDevices.size(); j++) { + ConfigDevice configDevice = (ConfigDevice)inputDevices.get(j) ; + InputDevice device = configDevice.j3dInputDevice ; + j3dPhysicalEnvironment.addInputDevice(device) ; + } + + if (headTracker != null) { + j3dPhysicalEnvironment.setHeadIndex(0) ; + j3dPhysicalEnvironment.setSensor(0, headTracker.j3dSensor) ; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigScreen.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigScreen.java new file mode 100644 index 0000000..f17a0ce --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigScreen.java @@ -0,0 +1,307 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.awt.Window ; +import javax.media.j3d.Canvas3D ; +import javax.media.j3d.View ; +import javax.swing.JFrame ; +import javax.swing.JPanel ; +import javax.vecmath.Matrix4d ; +import javax.vecmath.Point2d ; + +class ConfigScreen extends ConfigObject { + + /** + * The index of this screen in the GraphicsDevice array returned by + * GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(). + */ + int frameBufferNumber ; + + /** + * The physical width in meters of the screen area of the GraphicsDevice + * associated with this ConfigScreen. The default is based on a screen + * resolution of 90 pixels/inch. + */ + double physicalScreenWidth = 0.0 ; + + /** + * The physical height in meters of the screen area of the GraphicsDevice + * associated with this ConfigScreen. The default is based on a screen + * resolution of 90 pixels/inch. + */ + double physicalScreenHeight = 0.0 ; + + /** + * The trackerBaseToImagePlate transform of this ConfigScreen. + * The default is the identity transform. + */ + Matrix4d trackerBaseToImagePlate = null ; + + /** + * The headTrackerToLeftImagePlate transform of this ConfigScreen if HMD + * mode is in effect. The default is the identity transform. + */ + Matrix4d headTrackerToLeftImagePlate = null ; + + /** + * The headTrackerToRightImagePlate transform of this ConfigScreen if HMD + * mode is in effect. The default is the identity transform. + */ + Matrix4d headTrackerToRightImagePlate = null ; + + /** + * The monoscopicViewPolicy for this ConfigScreen. The default is + * View.CYCLOPEAN_EYE_VIEW. + */ + int monoscopicViewPolicy = View.CYCLOPEAN_EYE_VIEW ; + + /** + * Boolean indicating whether a full-screen window should be created for + * this ConfigScreen. The default is false. + */ + boolean fullScreen = false ; + + /** + * Boolean indicating whether a full-screen window with no borders should + * be created for this ConfigScreen. The default is false. + */ + boolean noBorderFullScreen = false ; + + /** + * The width in pixels for the window to be created for this ConfigScreen + * if a full screen window is not specified. The default is 512. + */ + int windowWidthInPixels = 512 ; + + /** + * The height in pixels for the window to be created for this ConfigScreen + * if a full screen window is not specified. The default is 512. + */ + int windowHeightInPixels = 512 ; + + /** + * The X pixel position of the top-left corner of the window, relative to + * the physical screen. The default is 0. + */ + int windowX = 0 ; + + /** + * The Y pixel position of the top-left corner of the window, relative to + * the physical screen. The default is 0. + */ + int windowY = 0 ; + + /** + * The JFrame created for this ConfigScreen. When running under JDK 1.4 + * or newer, the JFrame always contains a JPanel which contains the + * Canvas3D. When running under JDK 1.3.1 and using a borderless full + * screen the JFrame will instead contain a JWindow which will contain the + * JPanel and Canvas3D. + */ + JFrame j3dJFrame ; + + /** + * The Window created for this ConfigScreen. Under JDK 1.4 or higher this + * is the same reference as j3dJFrame. If a borderless full screen is + * specified while running under JDK 1.3.1 then this is a JWindow with the + * j3dJFrame as its parent. + */ + Window j3dWindow ; + + /** + * The JPanel created for this ConfigScreen to hold the Canvas3D. + */ + JPanel j3dJPanel ; + + /** + * The Canvas3D created for this ConfigScreen. + */ + Canvas3D j3dCanvas ; + + /** + * Processes attributes for this object. Handles commands of the form:

+ * (ScreenAttribute {instanceName} {attrName} {attrValue}) + * (ScreenProperty {instanceName} {attrName} {attrValue}) + * (DisplayAttribute {instanceName} {attrName} {attrValue}) + * (DisplayProperty {instanceName} {attrName} {attrValue}) + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand command) { + + String attr = null ; + Object val = null ; + String sval = null ; + + if (command.argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!isName(command.argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be a property name") ; + } + + attr = (String)command.argv[2] ; + val = command.argv[3] ; + + if (attr.equals("PhysicalScreenWidth")) { + if (!(val instanceof Double)) { + syntaxError("Value for PhysicalScreenWidth " + + "must be a number") ; + } + physicalScreenWidth = ((Double)val).doubleValue() ; + } + else if (attr.equals("PhysicalScreenHeight")) { + if (!(val instanceof Double)) { + syntaxError("Value for PhysicalScreenHeight " + + "must be a number") ; + } + physicalScreenHeight = ((Double)val).doubleValue() ; + } + else if (attr.equals("TrackerBaseToImagePlate")) { + if (!(val instanceof Matrix4d)) { + syntaxError("Value for TrackerBaseToImagePlate " + + "must be a 4x3 or 4x4 matrix") ; + } + trackerBaseToImagePlate = (Matrix4d)val ; + } + else if (attr.equals("HeadTrackerToLeftImagePlate")) { + if (!(val instanceof Matrix4d)) { + syntaxError("Value for HeadTrackerToLeftImagePlate " + + "must be a 4x3 or 4x4 matrix") ; + } + headTrackerToLeftImagePlate = (Matrix4d)val ; + } + else if (attr.equals("HeadTrackerToRightImagePlate")) { + if (!(val instanceof Matrix4d)) { + syntaxError("Value for HeadTrackerToRightImagePlate " + + "must be a 4x3 or 4x4 matrix") ; + } + headTrackerToRightImagePlate = (Matrix4d)val ; + } + else if (attr.equals("MonoscopicViewPolicy")) { + if (!(val instanceof String)) { + syntaxError("Value for MonoscopicViewPolicy " + + "must be a name") ; + } + sval = (String)val ; + if (sval.equals("LEFT_EYE_VIEW")) + monoscopicViewPolicy = View.LEFT_EYE_VIEW ; + else if (sval.equals("RIGHT_EYE_VIEW")) + monoscopicViewPolicy = View.RIGHT_EYE_VIEW ; + else if (sval.equals("CYCLOPEAN_EYE_VIEW")) + monoscopicViewPolicy = View.CYCLOPEAN_EYE_VIEW ; + else + syntaxError("Invalid value for MonoscopicViewPolicy " + + "\"" + sval + "\"") ; + } + else if (attr.equals("WindowPosition")) { + if (! (val instanceof Point2d)) { + syntaxError("WindowPosition must be a Point2d") ; + } + Point2d p2d = (Point2d)val ; + windowX = (int)p2d.x ; + windowY = (int)p2d.y ; + } + else if (attr.equals("WindowSize")) { + if (val instanceof Point2d) { + fullScreen = false ; + noBorderFullScreen = false ; + + Point2d p2d = (Point2d)val ; + windowWidthInPixels = (int)p2d.x ; + windowHeightInPixels = (int)p2d.y ; + } + else if (val instanceof String) { + String s = (String)val ; + + if (s.equals("FullScreen")) { + fullScreen = true ; + noBorderFullScreen = false ; + } else if (s.equals("NoBorderFullScreen")) { + fullScreen = false ; + noBorderFullScreen = true ; + } else { + syntaxError("Value for WindowSize " + + "must be one of\n" + "\"FullScreen\" " + + "\"NoBorderFullScreen\" or Point2d") ; + } + } + else { + syntaxError("Invalid WindowSize value: " + val + + "\nValue for WindowSize " + + "must be one of\n" + "\"FullScreen\" " + + "\"NoBorderFullScreen\" or Point2d") ; + } + } + else { + syntaxError("Unknown " + command.commandName + + " \"" + attr + "\"") ; + } + } + + /** + * Initializes this object. Handles commands of the form:

+ * (NewScreen {instanceName} {FrameBufferNumber}). + * + * @param command the command that invoked this method + */ + protected void initialize(ConfigCommand command) { + if (command.argc != 3) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!(command.argv[2] instanceof Double)) { + syntaxError("The second argument to " + command.commandName + + " must be a GraphicsDevice index") ; + } + + frameBufferNumber = ((Double)command.argv[2]).intValue() ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigSensor.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigSensor.java new file mode 100644 index 0000000..38aa779 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigSensor.java @@ -0,0 +1,211 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import javax.vecmath.Point3d ; +import javax.media.j3d.Sensor ; + +class ConfigSensor extends ConfigObject { + + // The index of this sensor in the associated InputDevice. + private int sensorIndex ; + + // The ConfigDevice which creates the associated InputDevice. + private ConfigDevice configDevice ; + + // All configurable attributes. + private Point3d hotspot = null ; + private int predictor = -1 ; + private int predictionPolicy = -1 ; + private int sensorReadCount = -1 ; + + /** + * The corresponding Java 3D core Sensor instance. This is created by the + * associated InputDevice. + */ + Sensor j3dSensor ; + + /** + * Handles the command + * (NewSensor {instanceName} {inputDeviceName} {indexInInputDevice}) + * + * @param command the command that invoked this method + */ + protected void initialize(ConfigCommand command) { + + int argc = command.argc ; + Object[] argv = command.argv ; + + // Check that arg[1] and arg[2] are strings, arg[3] a number + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be the device name") ; + } + + if (!(argv[3] instanceof Double)) { + syntaxError("The third argument to " + command.commandName + + " must be a sensor index") ; + } + + sensorIndex = ((Double)argv[3]).intValue() ; + configDevice = (ConfigDevice)configContainer.findConfigObject + ("Device", (String)argv[2]) ; + } + + /** + * Handles the commands + * (SensorAttribute {instanceName} {attributeName} {attributeValue}) and + * (SensorProperty {instanceName} {attributeName} {attributeValue}). + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand command) { + + int argc = command.argc ; + Object[] argv = command.argv ; + String attribute ; + + // Check that arg[1] and arg[2] are strings + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (! isName(argv[1])) { + syntaxError("The first argument to " + command.commandName + + " must be the instance name") ; + } + + if (! isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be a property name") ; + } + + attribute = (String)argv[2] ; + if (attribute.equals("Hotspot")) { + if (! (argv[3] instanceof Point3d)) { + syntaxError("Hotspot must be a 3D point") ; + } + hotspot = (Point3d)argv[3] ; + } + /* + * Questionable attributes. Commented out for now. + else if (attribute.equals("Predictor")) { + if (! isName(argv[3])) { + syntaxError("Predictor must be a name") ; + } + + String predictorName = (String)argv[3] ; + if (predictorName.equals("PREDICT_NONE")) { + predictor = Sensor.PREDICT_NONE ; + } + else if (predictorName.equals("PREDICT_NEXT_FRAME_TIME")) { + predictor = Sensor.PREDICT_NEXT_FRAME_TIME ; + } + else { + syntaxError("Predictor must be either PREDICT_NONE " + + "or PREDICT_NEXT_FRAME_TIME") ; + } + } + else if (attribute.equals("PredictionPolicy")) { + if (! isName(argv[3])) { + syntaxError("PredictionPolicy must be a name") ; + } + + String predictionPolicyName = (String)argv[3] ; + if (predictionPolicyName.equals("NO_PREDICTOR")) { + predictionPolicy = Sensor.NO_PREDICTOR ; + } + else if (predictionPolicyName.equals("HEAD_PREDICTOR")) { + predictionPolicy = Sensor.HEAD_PREDICTOR ; + } + else if (predictionPolicyName.equals("HAND_PREDICTOR")) { + predictionPolicy = Sensor.HAND_PREDICTOR ; + } + else { + syntaxError("PredictionPolicy must be either NO_PREDICTOR, " + + "HEAD_PREDICTOR, or HAND_PREDICTOR") ; + } + } + else if (attribute.equals("SensorReadCount")) { + if (! (argv[3] instanceof Double)) { + syntaxError("SensorReadCount must be a number") ; + } + sensorReadCount = ((Double)argv[3]).intValue() ; + } + */ + else { + syntaxError("Unknown " + command.commandName + + " \"" + attribute + "\"") ; + } + } + + /** + * This method is called after all InputDevice implementations have been + * instantiated and initialized. All the specified attributes for this + * sensor are set in the corresponding Java3D core Sensor instantiated by + * the associated InputDevice. + */ + void configureSensor() { + j3dSensor = configDevice.j3dInputDevice.getSensor(sensorIndex) ; + + if (hotspot != null) + j3dSensor.setHotspot(hotspot) ; + + if (predictor != -1) + j3dSensor.setPredictor(predictor) ; + + if (predictionPolicy != -1) + j3dSensor.setPredictionPolicy(predictionPolicy) ; + + if (sensorReadCount != -1) + j3dSensor.setSensorReadCount(sensorReadCount) ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigSexpression.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigSexpression.java new file mode 100644 index 0000000..075e63c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigSexpression.java @@ -0,0 +1,569 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.awt.event.* ; +import java.io.* ; +import java.lang.Integer ; +import java.lang.Boolean ; +import java.util.* ; +import javax.vecmath.* ; +import javax.media.j3d.* ; + +class ConfigSexpression { + + private ArrayList elements = new ArrayList() ; + + private void syntaxError(StreamTokenizer st, String file, String s) { + System.out.println(s + ":\nat line " + st.lineno() + " in " + file) ; + print() ; System.out.print("\n\n") ; + } + + private int myNextToken(StreamTokenizer st, String file) { + int tok = 0 ; + try { + tok = st.nextToken() ; + } + catch (IOException e) { + throw new RuntimeException("\n" + e + "\nwhile reading " + file) ; + } + return tok ; + } + + + Object parseAndEval(ConfigContainer configContainer, + StreamTokenizer st, int level) { + int tok ; + String s ; + String file = configContainer.currentFileName ; + + // + // First tokenize the character stream and add the tokens to the + // elements array. + // + elements.clear() ; + + // Look for an open paren to start this sexp. + while (true) { + tok = myNextToken(st, file) ; + + if (tok == StreamTokenizer.TT_EOF) + return Boolean.FALSE ; + + if (tok == ')') + syntaxError(st, file, "Premature closing parenthesis") ; + + if (tok == '(') + break ; + } + + // Add elements until a close paren for this sexp is found. + for (int i = 0 ; true ; i++) { + tok = myNextToken(st, file) ; + + if (tok == StreamTokenizer.TT_EOF) { + syntaxError(st, file, "Missing closing parenthesis") ; + break ; + } + + // An open paren starts a new embedded sexp. Put the paren back, + // evaluate the sexp, and add the result to the elements list. + if (tok == '(') { + st.pushBack() ; + ConfigSexpression cs = new ConfigSexpression() ; + elements.add(cs.parseAndEval(configContainer, st, level+1)) ; + continue ; + } + + // A close paren finishes the scan. + if (tok == ')') + break ; + + // Check for too many arguments. + if (i >= 20) + syntaxError(st, file, "Too many arguments") ; + + // Check for numeric argument. + if (tok == StreamTokenizer.TT_NUMBER) { + elements.add(new Double(st.nval)) ; + continue ; + } + + // Anything other than a word or a quoted string is an error. + if (tok != StreamTokenizer.TT_WORD && tok != '"' && tok != '\'') { + String badToken = String.valueOf((char)tok) ; + elements.add(badToken) ; // so bad token prints out + syntaxError(st, file, "Invalid token \"" + badToken + + "\" must be enclosed in quotes") ; + continue ; + } + + // Scan the token for Java property substitution syntax ${...}. + s = scanJavaProperties(st, file, st.sval) ; + if (s == null) continue ; + + if (s.equalsIgnoreCase("true")) + // Replace "true" or "True" with the Boolean equivalent. + elements.add(new Boolean(true)) ; + + else if (s.equalsIgnoreCase("false")) + // Replace "false" or "False" with the Boolean equivalent. + elements.add(new Boolean(false)) ; + + else + // Add the token as a string element. + elements.add(s) ; + } + + + // + // Now evaluate elements. + // + if (elements.size() == 0) + syntaxError(st, file, "Null command") ; + + // If the first argument is a string, then this sexp must be + // a top-level command or a built-in, and needs to be evaluated. + if (elements.get(0) instanceof String) { + try { + if (level == 0) { + configContainer.evaluateCommand(elements, st.lineno()) ; + + // Continue parsing top-level commands. + return Boolean.TRUE ; + } + else { + // Evaluate built-in and return result to next level up. + return evaluateBuiltIn + (configContainer, elements, st.lineno()) ; + } + } + catch (IllegalArgumentException e) { + syntaxError(st, file, e.getMessage()) ; + if (level == 0) + // Command ignored: continue parsing. + return Boolean.TRUE ; + else + // Function ignored: return sexp to next level up so error + // processing can print it out in context of command. + return this ; + } + } + + // If the first argument isn't a string, and we are at level 0, + // this is a syntax error. + if (level == 0) + syntaxError(st, file, "Malformed top-level command name") ; + + // If the first argument is a number, then we must have + // either a 2D, 3D, or 4D numeric vector. + if (elements.get(0) instanceof Double) { + if (elements.size() == 1) + syntaxError(st, file, "Can't have single-element vector") ; + + // Point2D + if (elements.size() == 2) { + if (!(elements.get(1) instanceof Double)) + syntaxError(st, file, "Both elements must be numbers") ; + + return new Point2d(((Double)elements.get(0)).doubleValue(), + ((Double)elements.get(1)).doubleValue()) ; + } + + // Point3d + if (elements.size() == 3) { + if (!(elements.get(1) instanceof Double) || + !(elements.get(2) instanceof Double)) + syntaxError(st, file, "All elements must be numbers") ; + + return new Point3d(((Double)elements.get(0)).doubleValue(), + ((Double)elements.get(1)).doubleValue(), + ((Double)elements.get(2)).doubleValue()) ; + } + + // Point4D + if (elements.size() == 4) { + if (!(elements.get(1) instanceof Double) || + !(elements.get(2) instanceof Double) || + !(elements.get(3) instanceof Double)) + syntaxError(st, file, "All elements must be numbers") ; + + return new Point4d(((Double)elements.get(0)).doubleValue(), + ((Double)elements.get(1)).doubleValue(), + ((Double)elements.get(2)).doubleValue(), + ((Double)elements.get(3)).doubleValue()) ; + } + + // Anything else is an error. + syntaxError(st, file, "Too many vector elements") ; + } + + // If the first argument is a Point3d, then we should be a Matrix3d. + if (elements.get(0) instanceof Point3d) { + if (elements.size() != 3) + syntaxError(st, file, "Matrix must have three rows") ; + + if (!(elements.get(1) instanceof Point3d) || + !(elements.get(2) instanceof Point3d)) + syntaxError(st, file, "All rows must have three elements") ; + + return new Matrix3d(((Point3d)elements.get(0)).x, + ((Point3d)elements.get(0)).y, + ((Point3d)elements.get(0)).z, + ((Point3d)elements.get(1)).x, + ((Point3d)elements.get(1)).y, + ((Point3d)elements.get(1)).z, + ((Point3d)elements.get(2)).x, + ((Point3d)elements.get(2)).y, + ((Point3d)elements.get(2)).z) ; + } + + // If the first argument is a Point4d, then we should be a Matrix4d. + if (elements.get(0) instanceof Point4d) { + if (elements.size() == 3) { + if (!(elements.get(1) instanceof Point4d) || + !(elements.get(2) instanceof Point4d)) + syntaxError(st, file, "All rows must have four elements") ; + + return new Matrix4d(((Point4d)elements.get(0)).x, + ((Point4d)elements.get(0)).y, + ((Point4d)elements.get(0)).z, + ((Point4d)elements.get(0)).w, + ((Point4d)elements.get(1)).x, + ((Point4d)elements.get(1)).y, + ((Point4d)elements.get(1)).z, + ((Point4d)elements.get(1)).w, + ((Point4d)elements.get(2)).x, + ((Point4d)elements.get(2)).y, + ((Point4d)elements.get(2)).z, + ((Point4d)elements.get(2)).w, + 0.0, 0.0, 0.0, 1.0) ; + } + else if (elements.size() != 4) + syntaxError(st, file, "Matrix must have three or four rows") ; + + if (!(elements.get(1) instanceof Point4d) || + !(elements.get(2) instanceof Point4d) || + !(elements.get(3) instanceof Point4d)) + syntaxError(st, file, "All rows must have four elements") ; + + return new Matrix4d(((Point4d)elements.get(0)).x, + ((Point4d)elements.get(0)).y, + ((Point4d)elements.get(0)).z, + ((Point4d)elements.get(0)).w, + ((Point4d)elements.get(1)).x, + ((Point4d)elements.get(1)).y, + ((Point4d)elements.get(1)).z, + ((Point4d)elements.get(1)).w, + ((Point4d)elements.get(2)).x, + ((Point4d)elements.get(2)).y, + ((Point4d)elements.get(2)).z, + ((Point4d)elements.get(2)).w, + ((Point4d)elements.get(3)).x, + ((Point4d)elements.get(3)).y, + ((Point4d)elements.get(3)).z, + ((Point4d)elements.get(3)).w) ; + } + + // Anything else is an error. + syntaxError(st, file, "Syntax error") ; + return null ; + } + + /** + * Scan for Java properties in the specified string. Nested properties are + * not supported. + * + * @param st stream tokenizer in use + * @param f current file name + * @param s string containing non-nested Java properties possibly + * interspersed with arbitrary text. + * @return scanned string with Java properties replaced with values + */ + private String scanJavaProperties(StreamTokenizer st, String f, String s) { + int open = s.indexOf("${") ; + if (open == -1) return s ; + + int close = 0 ; + StringBuffer buf = new StringBuffer() ; + while (open != -1) { + buf.append(s.substring(close, open)) ; + close = s.indexOf('}', open) ; + if (close == -1) { + elements.add(s) ; // so that the bad element prints out + syntaxError(st, f, "Java property substitution syntax error") ; + return null ; + } + + String property = s.substring(open + 2, close) ; + String value = ConfigCommand.evaluateJavaProperty(property) ; + if (value == null) { + elements.add(s) ; // so that the bad element prints out + syntaxError(st, f, "Java property \"" + property + + "\" has a null value") ; + return null ; + } + + buf.append(value) ; + open = s.indexOf("${", close) ; + close++ ; + } + + buf.append(s.substring(close)) ; + return buf.toString() ; + } + + /** + * This method gets called from the s-expression parser to evaluate a + * built-in command. + * + * @param elements tokenized list of sexp elements + * @return object representing result of evaluation + */ + private Object evaluateBuiltIn(ConfigContainer configContainer, + ArrayList elements, int lineNumber) { + int argc ; + String functionName ; + + argc = elements.size() ; + functionName = (String)elements.get(0) ; + + if (functionName.equals("Rotate")) { + return makeRotate(elements) ; + } + else if (functionName.equals("Translate")) { + return makeTranslate(elements) ; + } + else if (functionName.equals("RotateTranslate") || + functionName.equals("TranslateRotate") || + functionName.equals("Concatenate")) { + + return concatenate(elements) ; + } + else if (functionName.equals("BoundingSphere")) { + return makeBoundingSphere(elements) ; + } + else { + // This built-in can't be evaluated immediately or contains an + // unknown command. Create a ConfigCommand for later evaluation. + return new ConfigCommand + (elements, configContainer.currentFileName, lineNumber) ; + } + } + + /** + * Processes the built-in command (Translate x y z). + * + * @param elements ArrayList containing Doubles wrapping x, y, and z + * translation components at indices 1, 2, and 3 respectively + * + * @return matrix that translates by the given x, y, and z components + */ + private Matrix4d makeTranslate(ArrayList elements) { + if (elements.size() != 4) { + throw new IllegalArgumentException + ("Incorrect number of arguments to Translate") ; + } + + if (!(elements.get(1) instanceof Double) || + !(elements.get(2) instanceof Double) || + !(elements.get(3) instanceof Double)) { + throw new IllegalArgumentException + ("All arguments to Translate must be numbers") ; + } + + Matrix4d m4d = new Matrix4d() ; + m4d.set(new Vector3d(((Double)elements.get(1)).doubleValue(), + ((Double)elements.get(2)).doubleValue(), + ((Double)elements.get(3)).doubleValue())) ; + + return m4d ; + } + + /** + * Processes the (Rotate x y z) built-in command. + * + * @param elements ArrayList containing Doubles wrapping x, y, and z Euler + * angles at indices 1, 2, and 3 respectively + * + * @return matrix that rotates by the given Euler angles around static X, + * Y, and Z basis vectors: first about X, then Y, and then Z + * + * @see Transform3D#setEuler() + */ + private Matrix4d makeRotate(ArrayList elements) { + if (elements.size() != 4) { + throw new IllegalArgumentException + ("Incorrect number of arguments to Rotate") ; + } + + if (!(elements.get(1) instanceof Double) || + !(elements.get(2) instanceof Double) || + !(elements.get(3) instanceof Double)) { + throw new IllegalArgumentException + ("All arguments to Rotate must be numbers") ; + } + + double x = Math.toRadians(((Double)elements.get(1)).doubleValue()) ; + double y = Math.toRadians(((Double)elements.get(2)).doubleValue()) ; + double z = Math.toRadians(((Double)elements.get(3)).doubleValue()) ; + + Transform3D t3d = new Transform3D() ; + t3d.setEuler(new Vector3d(x, y, z)) ; + + Matrix4d m4d = new Matrix4d() ; + t3d.get(m4d) ; + + return m4d ; + } + + /** + * Processes the (RotateTranslate m1 m2), (TranslateRotate m1 m2), and + * (Concatenate m1 m2) built-in commands. Although these do exactly the + * same thing, using the appropriate command is recommended in order to + * explicitly describe the sequence of transforms and their intent. + * + * @param elements ArrayList containing Matrix4d objects m1 and m2 at + * indices 1 and 2 respectively + * + * @return matrix that concatenates m1 and m2 in that order: if a point is + * transformed by the resulting matrix, then in effect the points are + * first transformed by m1 and then m2 + */ + private Matrix4d concatenate(ArrayList elements) { + String functionName = (String)elements.get(0) ; + + if (elements.size() != 3) { + throw new IllegalArgumentException + ("Incorrect number of arguments to " + functionName) ; + } + + if (!(elements.get(1) instanceof Matrix4d) || + !(elements.get(2) instanceof Matrix4d)) { + throw new IllegalArgumentException + ("Both arguments to " + functionName + " must be Matrix4d") ; + } + + // Multiply the matrices in the order such that the result, when + // transforming a 3D point, will apply the transform represented by + // the 1st matrix and then apply the transform represented by the 2nd + // matrix. + Matrix4d m4d = new Matrix4d((Matrix4d)elements.get(2)) ; + m4d.mul((Matrix4d)elements.get(1)) ; + + return m4d ; + } + + /** + * Processes the built-in command (BoundingSphere center radius). + * This is used when configuring behaviors. + * + * @param elements ArrayList containing Point3d at index 1 for the sphere + * center and Double at index 2 wrapping the sphere radius, or the String + * "infinite" at index 2. + * + * @return BoundingSphere with the given center and radius + */ + private BoundingSphere makeBoundingSphere(ArrayList elements) { + if (elements.size() != 3) { + throw new IllegalArgumentException + ("Incorrect number of arguments to BoundingSphere") ; + } + + if (! (elements.get(1) instanceof Point3d) || + ! (elements.get(2) instanceof Double || + elements.get(2) instanceof String)) + throw new IllegalArgumentException + ("BoundingSphere needs a Point3d center " + + "followed by a Double radius or the String \"infinite\"") ; + + double r ; + if (elements.get(2) instanceof Double) + r = ((Double)elements.get(2)).doubleValue() ; + else + r = Double.POSITIVE_INFINITY ; + + return new BoundingSphere((Point3d)elements.get(1), r) ; + } + + void print() { + System.out.print("(") ; + int argc = elements.size() ; + for (int i = 0 ; i < argc ; i++) { + if (elements.get(i) instanceof Matrix3d) { + String[] rows = ConfigCommand.formatMatrixRows + ((Matrix3d)elements.get(i)) ; + System.out.println("\n ((" + rows[0] + ")") ; + System.out.println(" (" + rows[1] + ")") ; + System.out.print(" (" + rows[2] + "))") ; + if (i != (argc - 1)) System.out.println() ; + } + else if (elements.get(i) instanceof Matrix4d) { + String[] rows = ConfigCommand.formatMatrixRows + ((Matrix4d)elements.get(i)) ; + System.out.println("\n ((" + rows[0] + ")") ; + System.out.println(" (" + rows[1] + ")") ; + System.out.println(" (" + rows[2] + ")") ; + System.out.print(" (" + rows[3] + "))") ; + if (i != (argc - 1)) System.out.println() ; + } + else if (elements.get(i) instanceof ConfigSexpression) { + if (i > 0) System.out.print(" ") ; + ((ConfigSexpression)elements.get(i)).print() ; + if (i != (argc - 1)) System.out.println() ; + } + else if (elements.get(i) instanceof ConfigCommand) { + if (i > 0) System.out.print(" ") ; + System.out.print(elements.get(i).toString()) ; + if (i != (argc - 1)) System.out.println() ; + } + else { + if (i > 0) System.out.print(" ") ; + System.out.print(elements.get(i).toString()) ; + } + } + System.out.print(")") ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigView.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigView.java new file mode 100644 index 0000000..7a3813c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigView.java @@ -0,0 +1,469 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.util.* ; +import javax.media.j3d.* ; +import javax.vecmath.* ; + +class ConfigView extends ConfigObject { + /** + * The corresponding View and Viewer instances. These are set when + * createJ3dView() is called after parsing the configuration file. + */ + View j3dView = null ; + Viewer j3dViewer = null ; + + /** + * Set of ConfigScreen instances added to this view. + */ + Set screens = new HashSet() ; + + /** + * Indicates whether or not stereo viewing should be enabled for this + * ConfigView. This is set during parsing of the configuration file. + */ + boolean stereoEnable = false ; + + /** + * Indicates whether or not antialiasing is enabled for this ConfigView. + * This is set during parsing of the configuration file. + */ + boolean antialiasingEnable = false; + + /** + * Reference to the PhysicalBody associated with this ConfigView. This is + * set when createJ3dView() is called after parsing the configuration + * file. + */ + PhysicalBody physicalBody = null ; + + /** + * Reference to the PhysicalEnvironment associated with this ConfigView. + * This is set when createJ3dView() is called after parsing the + * configuration file. + */ + PhysicalEnvironment physicalEnvironment = null ; + + // All other configurable attributes. + private double fieldOfView = Math.PI/4.0 ; + private int backClipPolicy = View.PHYSICAL_EYE ; + private int frontClipPolicy = View.PHYSICAL_EYE ; + private double backClipDistance = 10.0 ; + private double frontClipDistance = 0.1 ; + private int screenScalePolicy = View.SCALE_SCREEN_SIZE ; + private double screenScale = 1.0 ; + private boolean trackingEnable = false ; + private int viewPolicy = View.SCREEN_VIEW ; + private int windowEyepointPolicy = -1 ; + private int windowMovementPolicy = -1 ; + private int windowResizePolicy = -1 ; + private boolean coeCenteringEnableSet = false ; + private boolean coeCenteringEnable = false ; + private Point3d centerEyeInCoexistence = null ; + + private ConfigPhysicalBody configBody = null ; + private ConfigPhysicalEnvironment configEnv = null ; + private ConfigViewPlatform configViewPlatform = null ; + + /** + * Overrides initialize() to do nothing. + */ + protected void initialize(ConfigCommand command) { + } + + /** + * Processes properties for this object. Handles commands of the form:

+ * (ViewAttribute {instanceName} {attrName} {attrValue}) + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand command) { + + int argc = command.argc ; + Object[] argv = command.argv ; + String attr = null ; + Object val = null ; + String sval = null ; + ConfigScreen cs = null ; + + // Check that arg[1] and arg[2] are strings + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!isName(argv[1])) { + syntaxError("The first argument to " + command.commandName + + " must be the instance name") ; + } + + if (!isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be a property name") ; + } + + attr = (String) argv[2] ; + val = argv[3] ; + + if (attr.equals("Screen") || attr.equals("Window")) { + if (!(val instanceof String)) { + syntaxError("Value for " + attr + " must be a name") ; + } + cs = (ConfigScreen) + configContainer.findConfigObject("Screen", (String)val) ; + + if (!screens.add(cs)) { + syntaxError(attr + " \"" + ((String)val) + + "\" has already been added to " + instanceName) ; + } + } + else if (attr.equals("ViewPlatform")) { + if (!(val instanceof String)) { + syntaxError("value for ViewPlatform " + + " must be an instance name") ; + } + configViewPlatform = + (ConfigViewPlatform)configContainer.findConfigObject + ("ViewPlatform", (String)val) ; + + configViewPlatform.addConfigView(this) ; + } + else if (attr.equals("PhysicalEnvironment")) { + if (!(val instanceof String)) { + syntaxError("value for PhysicalEnvironment " + + "must be an instance name") ; + } + configEnv = + (ConfigPhysicalEnvironment)configContainer.findConfigObject + ("PhysicalEnvironment", (String)val) ; + } + else if (attr.equals("PhysicalBody")) { + if (!(val instanceof String)) { + syntaxError("value for PhysicalBody " + + "must be an instance name") ; + } + configBody = (ConfigPhysicalBody) + configContainer.findConfigObject("PhysicalBody", (String)val) ; + } + else if (attr.equals("BackClipPolicy")) { + if (!(val instanceof String)) { + syntaxError("value for BackClipPolicy must be a string") ; + } + sval = (String) val ; + if (sval.equals("PHYSICAL_EYE")) + backClipPolicy = View.PHYSICAL_EYE ; + else if (sval.equals("PHYSICAL_SCREEN")) + backClipPolicy = View.PHYSICAL_SCREEN ; + else if (sval.equals("VIRTUAL_EYE")) + backClipPolicy = View.VIRTUAL_EYE ; + else if (sval.equals("VIRTUAL_SCREEN")) + backClipPolicy = View.VIRTUAL_SCREEN ; + else + syntaxError("Invalid value for BackClipPolicy " + sval) ; + } + else if (attr.equals("FrontClipPolicy")) { + if (!(val instanceof String)) { + syntaxError("value for FrontClipPolicy must be a string") ; + } + sval = (String) val ; + if (sval.equals("PHYSICAL_EYE")) + frontClipPolicy = View.PHYSICAL_EYE ; + else if (sval.equals("PHYSICAL_SCREEN")) + frontClipPolicy = View.PHYSICAL_SCREEN ; + else if (sval.equals("VIRTUAL_EYE")) + frontClipPolicy = View.VIRTUAL_EYE ; + else if (sval.equals("VIRTUAL_SCREEN")) + frontClipPolicy = View.VIRTUAL_SCREEN ; + else + syntaxError("Invalid value for FrontClipPolicy " + sval) ; + } + else if (attr.equals("ScreenScalePolicy")) { + if (!(val instanceof String)) { + syntaxError("value for ScreenScalePolicy must be a string") ; + } + sval = (String) val ; + if (sval.equals("SCALE_SCREEN_SIZE")) + screenScalePolicy = View.SCALE_SCREEN_SIZE ; + else if (sval.equals("SCALE_EXPLICIT")) + screenScalePolicy = View.SCALE_EXPLICIT ; + else + syntaxError("Invalid value for ScreenScalePolicy " + sval) ; + } + else if (attr.equals("FieldOfView")) { + if (!(val instanceof Double)) { + syntaxError("value for FieldOfView must be a number") ; + } + fieldOfView = ((Double)val).doubleValue() ; + } + else if (attr.equals("BackClipDistance")) { + if (!(val instanceof Double)) { + syntaxError("value for BackClipDistance must be a number") ; + } + backClipDistance = ((Double)val).doubleValue() ; + } + else if (attr.equals("FrontClipDistance")) { + if (!(val instanceof Double)) { + syntaxError("value for FrontClipDistance must be a number") ; + } + frontClipDistance = ((Double)val).doubleValue() ; + } + else if (attr.equals("ScreenScale")) { + if (!(val instanceof Double)) { + syntaxError("value for ScreenScale must be a number") ; + } + screenScale = ((Double)val).doubleValue() ; + } + else if (attr.equals("TrackingEnable")) { + if (!(val instanceof Boolean)) { + syntaxError("value for TrackingEnable must be a boolean") ; + } + trackingEnable = ((Boolean)val).booleanValue() ; + } + else if (attr.equals("CoexistenceCenteringEnable")) { + if (!(val instanceof Boolean)) { + syntaxError("value for CoexistenceCenteringEnable " + + "must be a boolean") ; + } + coeCenteringEnable = ((Boolean)val).booleanValue() ; + coeCenteringEnableSet = true ; + } + else if (attr.equals("ViewPolicy")) { + if (!(val instanceof String)) { + syntaxError("value for ViewPolicy must be a string") ; + } + sval = (String) val ; + if (sval.equals("SCREEN_VIEW")) + viewPolicy = View.SCREEN_VIEW ; + else if (sval.equals("HMD_VIEW")) + viewPolicy = View.HMD_VIEW ; + else + syntaxError("Invalid value for ViewPolicy " + sval) ; + } + else if (attr.equals("WindowEyepointPolicy")) { + if (!(val instanceof String)) { + syntaxError("value for WindowEyepointPolicy " + + "must be a string") ; + } + sval = (String) val ; + if (sval.equals("RELATIVE_TO_SCREEN")) + windowEyepointPolicy = View.RELATIVE_TO_SCREEN ; + else if (sval.equals("RELATIVE_TO_COEXISTENCE")) + windowEyepointPolicy = View.RELATIVE_TO_COEXISTENCE ; + else if (sval.equals("RELATIVE_TO_WINDOW")) + windowEyepointPolicy = View.RELATIVE_TO_WINDOW ; + else if (sval.equals("RELATIVE_TO_FIELD_OF_VIEW")) + windowEyepointPolicy = View.RELATIVE_TO_FIELD_OF_VIEW ; + else + syntaxError("Invalid value for WindowEyepointPolicy " + sval) ; + } + else if (attr.equals("WindowMovementPolicy")) { + if (!(val instanceof String)) { + syntaxError("value for WindowEyeMovementPolicy " + + "must be a string") ; + } + sval = (String) val ; + if (sval.equals("VIRTUAL_WORLD")) + windowMovementPolicy = View.VIRTUAL_WORLD ; + else if (sval.equals("PHYSICAL_WORLD")) + windowMovementPolicy = View.PHYSICAL_WORLD ; + else + syntaxError("Invalid value for WindowMovementPolicy " + sval) ; + } + else if (attr.equals("WindowResizePolicy")) { + if (!(val instanceof String)) { + syntaxError("value for WindowResizePolicy " + + "must be a string") ; + } + sval = (String) val ; + if (sval.equals("VIRTUAL_WORLD")) + windowResizePolicy = View.VIRTUAL_WORLD ; + else if (sval.equals("PHYSICAL_WORLD")) + windowResizePolicy = View.PHYSICAL_WORLD ; + else + syntaxError("Invalid value for WindowResizePolicy " + sval) ; + } + else if (attr.equals("CenterEyeInCoexistence")) { + if (val instanceof Point3d) + centerEyeInCoexistence = (Point3d)val ; + else + syntaxError("value for CenterEyeInCoexistence " + + "must be a Point3d") ; + } + else if (attr.equals("StereoEnable")) { + if (!(val instanceof Boolean)) { + syntaxError("value for StereoEnable must be a boolean") ; + } + stereoEnable = ((Boolean)val).booleanValue() ; + } + else if (attr.equals("AntialiasingEnable")) { + if (!(val instanceof Boolean)) { + syntaxError("value for AntialiasingEnable must be a boolean") ; + } + antialiasingEnable = ((Boolean)val).booleanValue() ; + } + else { + syntaxError("Unknown " + command.commandName + + " \"" + attr + "\"") ; + } + } + + /** + * Create a core Java 3D View instance and a utility Viewer instance using + * the attributes gathered by this object. + */ + protected Viewer createViewer(boolean setVisible) { + Point3d leftEyeCoe, rightEyeCoe ; + + j3dView = new View() ; + j3dView.setViewPolicy(viewPolicy) ; + + if (configBody == null) + physicalBody = new PhysicalBody() ; + else + physicalBody = configBody.j3dPhysicalBody ; + + if (configEnv == null) + physicalEnvironment = new PhysicalEnvironment() ; + else + physicalEnvironment = configEnv.j3dPhysicalEnvironment ; + + j3dView.setPhysicalBody(physicalBody) ; + j3dView.setPhysicalEnvironment(physicalEnvironment) ; + + boolean standardDefaults = true ; + if (coeCenteringEnableSet && !coeCenteringEnable) { + standardDefaults = false ; + } + if (configEnv != null && configEnv.coexistenceToTrackerBase != null) { + standardDefaults = false ; + } + else { + Iterator i = screens.iterator() ; + while (i.hasNext()) { + ConfigScreen s = (ConfigScreen)i.next() ; + if (s.trackerBaseToImagePlate != null) { + standardDefaults = false ; + break ; + } + } + } + + if (standardDefaults) { + // Coexistence centering has not been explicitly set false, and + // the tracker base to image plate and coexistence to tracker base + // transforms are unset, so use the standard Java 3D defaults. + if (windowEyepointPolicy == -1) + windowEyepointPolicy = View.RELATIVE_TO_FIELD_OF_VIEW ; + if (windowMovementPolicy == -1) + windowMovementPolicy = View.PHYSICAL_WORLD ; + if (windowResizePolicy == -1) + windowResizePolicy = View.PHYSICAL_WORLD ; + if (!coeCenteringEnableSet) + coeCenteringEnable = true ; + } + else { + // Use multi-screen or calibrated coexistence defaults. + if (windowEyepointPolicy == -1) + windowEyepointPolicy = View.RELATIVE_TO_COEXISTENCE ; + if (windowMovementPolicy == -1) + windowMovementPolicy = View.VIRTUAL_WORLD ; + if (windowResizePolicy == -1) + windowResizePolicy = View.VIRTUAL_WORLD ; + if (!coeCenteringEnableSet) + coeCenteringEnable = false ; + } + + j3dView.setWindowEyepointPolicy(windowEyepointPolicy) ; + j3dView.setWindowMovementPolicy(windowMovementPolicy) ; + j3dView.setWindowResizePolicy(windowResizePolicy) ; + j3dView.setCoexistenceCenteringEnable(coeCenteringEnable) ; + + if (centerEyeInCoexistence == null) { + centerEyeInCoexistence = new Point3d(0.0, 0.0, 0.4572) ; + } + + leftEyeCoe = new Point3d(centerEyeInCoexistence) ; + rightEyeCoe = new Point3d(centerEyeInCoexistence) ; + + if (stereoEnable) { + Point3d leftEyeBody = new Point3d() ; + Point3d rightEyeBody = new Point3d() ; + + physicalBody.getLeftEyePosition(leftEyeBody) ; + physicalBody.getRightEyePosition(rightEyeBody) ; + + leftEyeCoe.add(leftEyeBody) ; + rightEyeCoe.add(rightEyeBody) ; + } + + j3dView.setLeftManualEyeInCoexistence(leftEyeCoe) ; + j3dView.setRightManualEyeInCoexistence(rightEyeCoe) ; + + j3dView.setBackClipPolicy(backClipPolicy) ; + j3dView.setFrontClipPolicy(frontClipPolicy) ; + j3dView.setBackClipDistance(backClipDistance) ; + j3dView.setFrontClipDistance(frontClipDistance) ; + + j3dView.setScreenScalePolicy(screenScalePolicy) ; + j3dView.setScreenScale(screenScale) ; + + j3dView.setFieldOfView(fieldOfView) ; + j3dView.setTrackingEnable(trackingEnable) ; + j3dView.setSceneAntialiasingEnable(antialiasingEnable) ; + + if (screens.size() == 0) { + throw new IllegalStateException + (errorMessage(creatingCommand, "View \"" + instanceName + + "\" has no canvases or screens")) ; + } + + ConfigScreen[] cs = new ConfigScreen[screens.size()] ; + screens.toArray(cs) ; + + j3dViewer = new Viewer(cs, this, setVisible) ; + return j3dViewer ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatform.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatform.java new file mode 100644 index 0000000..2ce5070 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatform.java @@ -0,0 +1,255 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.util.ArrayList ; +import javax.media.j3d.Node ; +import javax.media.j3d.View ; +import javax.media.j3d.ViewPlatform ; +import javax.media.j3d.Transform3D ; +import javax.media.j3d.TransformGroup ; +import javax.vecmath.Matrix4d ; +import com.sun.j3d.utils.behaviors.vp.ViewPlatformBehavior ; + +class ConfigViewPlatform extends ConfigObject { + + private boolean allowPolicyRead = false ; + private boolean allowLocalToVworldRead = false ; + private boolean nominalViewingTransform = false ; + private Transform3D initialViewingTransform = null ; + private ArrayList configViews = new ArrayList() ; + private Viewer[] viewers = null ; + + /** + * The corresponding ViewingPlatform instance. + */ + ViewingPlatform viewingPlatform = null ; + + /** + * Indicates the view attach policy specified in the configuration file. + * If none is set, it remains -1 even though a default may be in effect. + */ + int viewAttachPolicy = -1 ; + + /** + * The associated ConfigViewPlatformBehavior, if any. + */ + ConfigViewPlatformBehavior configBehavior = null ; + + /** + * Overrides initialize() to do nothing. + */ + protected void initialize(ConfigCommand command) { + } + + /** + * Processes attributes for this object. Handles commands of the form:

+ * (ViewPlatformAttribute {instanceName} {attrName} {attrValue})
+ * (ViewPlatformProperty {instanceName} {attrName} {attrValue}) + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand command) { + + int argc = command.argc ; + Object[] argv = command.argv ; + String attribute ; + Object value ; + + if (argc != 4) { + syntaxError("Incorrect number of arguments to " + + command.commandName) ; + } + + if (!isName(argv[2])) { + syntaxError("The second argument to " + command.commandName + + " must be a property name"); + } + + attribute = (String)argv[2] ; + value = argv[3] ; + + if (attribute.equals("NominalViewingTransform")) { + if (! (value instanceof Boolean)) { + syntaxError("NominalViewingTransform must be a boolean") ; + } + nominalViewingTransform = ((Boolean)value).booleanValue() ; + } + else if (attribute.equals("InitialViewingTransform")) { + if (! (value instanceof Matrix4d)) { + syntaxError("InitialViewingTransform must be a Matrix4d") ; + } + initialViewingTransform = new Transform3D((Matrix4d)value) ; + } + else if (attribute.equals("ViewAttachPolicy")) { + if (! (value instanceof String)) { + syntaxError("ViewAttachPolicy must be a string") ; + } + + String svalue = (String)value ; + + if (svalue.equals("NOMINAL_HEAD")) + viewAttachPolicy = View.NOMINAL_HEAD ; + else if (svalue.equals("NOMINAL_SCREEN")) + viewAttachPolicy = View.NOMINAL_SCREEN ; + else if (svalue.equals("NOMINAL_FEET")) + viewAttachPolicy = View.NOMINAL_FEET ; + else + syntaxError("Illegal value " + + svalue + " for ViewAttachPolicy") ; + } + else if (attribute.equals("ViewPlatformBehavior")) { + if (! (value instanceof String)) { + syntaxError("ViewPlatformBehavior must be a name") ; + } + configBehavior = + (ConfigViewPlatformBehavior)configContainer.findConfigObject + ("ViewPlatformBehavior", (String)value) ; + } + else if (attribute.equals("AllowPolicyRead")) { + if (!(value instanceof Boolean)) { + syntaxError("value for AllowPolicyRead " + + "must be a boolean") ; + } + allowPolicyRead = ((Boolean)value).booleanValue() ; + } + else if (attribute.equals("AllowLocalToVworldRead")) { + if (!(value instanceof Boolean)) { + syntaxError("value for AllowLocalToVworldRead " + + "must be a boolean") ; + } + allowLocalToVworldRead = ((Boolean)value).booleanValue() ; + } + else { + syntaxError("Unknown " + command.commandName + + " \"" + attribute + "\"") ; + } + } + + /** + * Add a ConfigView to this ConfigViewPlatform. + */ + void addConfigView(ConfigView cv) { + configViews.add(cv) ; + } + + /** + * Creates a ViewingPlatform from attributes gathered by this object. + * + * @param transformCount the number of TransformGroups to attach to the + * ViewingPlatform + * @return the new ViewingPlatform + */ + ViewingPlatform createViewingPlatform(int transformCount) { + + // Get the Viewers attached to this ViewingPlatform. + // All ConfigViews must be processed at this point. + if (configViews.size() == 0) { + viewers = new Viewer[0] ; + } + else { + viewers = new Viewer[configViews.size()] ; + for (int i = 0 ; i < viewers.length ; i++) + viewers[i] = ((ConfigView)configViews.get(i)).j3dViewer ; + } + + // Create the viewing platform and get its ViewPlatform instance. + viewingPlatform = new ViewingPlatform(transformCount) ; + ViewPlatform vp = viewingPlatform.getViewPlatform() ; + + // Set defined policies. + if (allowPolicyRead) + vp.setCapability(ViewPlatform.ALLOW_POLICY_READ) ; + + if (allowLocalToVworldRead) + vp.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ) ; + + if (viewAttachPolicy == -1) { + // Apply a default based on the eyepoint policy. + boolean nominalHead = true ; + for (int i = 0 ; i < viewers.length ; i++) { + if (viewers[i].getView().getWindowEyepointPolicy() != + View.RELATIVE_TO_FIELD_OF_VIEW) { + nominalHead = false ; + break ; + } + } + if (nominalHead) + vp.setViewAttachPolicy(View.NOMINAL_HEAD) ; + else + vp.setViewAttachPolicy(View.NOMINAL_SCREEN) ; + } + else { + vp.setViewAttachPolicy(viewAttachPolicy) ; + } + + // Assign the viewing platform to all viewers. + for (int i = 0 ; i < viewers.length ; i++) { + viewers[i].setViewingPlatform(viewingPlatform) ; + } + + // Apply initial viewing transforms if defined. + if (nominalViewingTransform) { + viewingPlatform.setNominalViewingTransform() ; + } + + if (initialViewingTransform != null) { + TransformGroup tg = viewingPlatform.getViewPlatformTransform() ; + tg.setTransform(initialViewingTransform) ; + } + + return viewingPlatform ; + } + + /** + * Attach any ViewPlatformBehavior specified for this platform. + */ + void processBehavior() { + if (configBehavior != null) { + viewingPlatform.setViewPlatformBehavior + (configBehavior.viewPlatformBehavior) ; + } + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatformBehavior.java b/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatformBehavior.java new file mode 100644 index 0000000..944e15f --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfigViewPlatformBehavior.java @@ -0,0 +1,139 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.lang.reflect.* ; +import java.util.ArrayList ; +import javax.vecmath.Matrix4d ; +import javax.media.j3d.Bounds ; +import javax.media.j3d.Canvas3D ; +import javax.media.j3d.Sensor ; +import javax.media.j3d.Transform3D ; +import com.sun.j3d.utils.behaviors.vp.ViewPlatformBehavior ; + +class ConfigViewPlatformBehavior extends ConfigObject { + + // All known configurable properties. + private Transform3D homeTransform = null ; + private Bounds schedulingBounds = null ; + private int schedulingInterval = -1 ; + + /** + * The corresponding ViewPlatformBehavior instance. + */ + ViewPlatformBehavior viewPlatformBehavior ; + + /** + * Processes properties for this object. Handles commands of the form:

+ * (ViewPlatformBehaviorProperty {instanceName} {attrName} {arg} ...) + * + * @param command the command that invoked this method + */ + protected void setProperty(ConfigCommand cmd) { + + int argc = cmd.argc ; + Object[] argv = cmd.argv ; + + if (argc < 4) { + syntaxError("Wrong number of arguments to " + cmd.commandName) ; + } + + if (! isName(argv[2])) { + syntaxError("The second argument to " + cmd.commandName + + " must be a property name"); + } + + String attribute = (String)argv[2] ; + if (attribute.equals("HomeTransform")) { + if (! (argv[3] instanceof Matrix4d)) { + syntaxError("HomeTransform must be a Matrix4d") ; + } + homeTransform = new Transform3D((Matrix4d)argv[3]) ; + } + else if (attribute.equals("SchedulingBounds")) { + if (! (argv[3] instanceof Bounds)) { + syntaxError("SchedulingBounds must be an instance of Bounds") ; + } + schedulingBounds = (Bounds)argv[3] ; + } + else if (attribute.equals("SchedulingInterval")) { + if (! (argv[3] instanceof Double)) { + syntaxError("SchedulingInterval must be a priority (number)") ; + } + schedulingInterval = ((Double)argv[3]).intValue() ; + } + else { + // It's not any of the pre-defined attributes. Add it to the + // properties list for the behavior instance itself to evaluate. + properties.add(cmd) ; + } + } + + /** + * Instantiate a ViewPlatformBehavior of the given class name.

+ * + * NOTE: All ConfigView and ConfigSensor objects must be processed before + * calling this method. + * + * @return the configured ViewPlatformBehavior, or null if error + */ + ViewPlatformBehavior createViewPlatformBehavior() { + + viewPlatformBehavior = (ViewPlatformBehavior)createTargetObject() ; + + // Set known attributes. + if (homeTransform != null) + viewPlatformBehavior.setHomeTransform(homeTransform) ; + + if (schedulingBounds != null) + viewPlatformBehavior.setSchedulingBounds(schedulingBounds) ; + + if (schedulingInterval != -1) + viewPlatformBehavior.setSchedulingInterval(schedulingInterval) ; + + // Unknown properties in the concrete instance are evaluated later. + return viewPlatformBehavior ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ConfiguredUniverse.java b/src/classes/share/com/sun/j3d/utils/universe/ConfiguredUniverse.java new file mode 100644 index 0000000..52d9c65 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ConfiguredUniverse.java @@ -0,0 +1,768 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import java.net.URL; +import java.net.MalformedURLException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import javax.media.j3d.*; + +/** + * This utility class creates all the necessary objects on the view side of + * the scene graph. Specifically, it creates a Locale, one or more + * ViewingPlatforms, and at least one Viewer object.

+ * + * ConfiguredUniverse can set up a viewing environment based upon the contents + * of a configuration file. This allows an application to run without change + * across a broad range of viewing configurations, such as windows on + * conventional desktops, stereo-enabled views, full screen immersive displays + * on single or multiple screens, or virtual reality installations including + * cave and head-mounted displays incorporating 6 degree of freedom sensor + * devices.

+ * + * A configuration file may create InputDevice, Sensor, and + * ViewPlatformBehavior instances as well as Viewers and ViewingPlatforms. At + * least one Viewer must be provided by the configuration. If a + * ViewingPlatform is not provided, a default one will be created and the + * Viewer will be attached to it.

+ * + * A configuration file may be specified directly by passing a URL to a + * ConfiguredUniverse constructor. Alternatively, a ConfigContainer may be + * created from a configuration file first, and then passed to an appropriate + * ConfiguredUniverse constructor. The latter technique allows Java system + * properties that affect Java 3D to be specified in the configuration file, + * as long as no references to a VirtualUniverse are made before creating the + * container.

+ * + * If a configuration file or container is not provided, then + * ConfiguredUniverse creates a default viewing environment in the same way as + * SimpleUniverse. If one or more Canvas3D objects are provided, it will use + * them instead of creating new ones. All of the constructors provided by + * SimpleUniverse are also available here.

+ * + * The syntax and description of the configuration file may be found + * here. Example config files can + * be found here. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see ConfigContainer + * @see + * The Java 3D Configuration File + * @see + * Example Configuration Files + * + * @since Java 3D 1.3 + */ +public class ConfiguredUniverse extends SimpleUniverse { + + /** + * The configuration instance for this universe. + */ + private ConfigContainer configContainer = null; + + /** + * Equivalent to SimpleUniverse(). Creates a + * Locale, a single ViewingPlatform, and a Viewer object. + * + * @see SimpleUniverse#SimpleUniverse() + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse() { + super(); + } + + /** + * Equivalent to SimpleUniverse(int). + * Creates a Locale, a single ViewingPlatform with the specified number of + * transforms, and a Viewer object. + * + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * + * @see SimpleUniverse#SimpleUniverse(int) + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(int transformCount) { + super(transformCount); + } + + /** + * Equivalent to SimpleUniverse(Canvas3D). + * Creates a Locale, a single ViewingPlatform, and a Viewer object using + * the given Canvas3D instance. + * + * @param canvas the canvas to associate with the Viewer object; + * passing in null will cause this parameter to be ignored and a canvas + * to be created by the utility + * + * @see SimpleUniverse#SimpleUniverse(Canvas3D) + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse(Canvas3D canvas) { + super(canvas); + } + + /** + * Equivalent to SimpleUniverse(Canvas3D, int). + * Creates a Locale, a single ViewingPlatform with the specified number of + * transforms, and a Viewer object with the given Canvas3D. + * + * @param canvas the canvas to associate with the Viewer object; + * passing in null will cause this parameter to be ignored and a canvas + * to be created by the utility + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * + * @see SimpleUniverse#SimpleUniverse(Canvas3D, int) + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(Canvas3D canvas, int transformCount) { + super(canvas, transformCount); + } + + /** + * Equivalent to SimpleUniverse(ViewingPlatform, Viewer). + * Creates the view side of the scene graph with the given ViewingPlatform + * and Viewer. + * + * @param viewingPlatform the viewingPlatform to use to create + * the view side of the scene graph + * @param viewer the viewer object to use to create + * the view side of the scene graph + * + * @see SimpleUniverse#SimpleUniverse(ViewingPlatform, Viewer) + * @see ViewingPlatform + * @see Viewer + */ + public ConfiguredUniverse(ViewingPlatform viewingPlatform, Viewer viewer) { + super(viewingPlatform, viewer, null); + } + + /** + * Equivalent to SimpleUniverse(ViewingPlatform, Viewer, + * LocalFactory). Creates the view side of the scene graph with + * the given ViewingPlatform, Viewer, and Locale created by the specified + * LocaleFactory. + * + * @param viewingPlatform the viewingPlatform to use to create + * the view side of the scene graph + * @param viewer the viewer object to use to create + * the view side of the scene graph + * @param localeFactory the factory object used to create the Locale + * + * @see SimpleUniverse#SimpleUniverse(ViewingPlatform, Viewer, + * LocaleFactory) + * @see ViewingPlatform + * @see Viewer + * @see LocaleFactory + */ + public ConfiguredUniverse(ViewingPlatform viewingPlatform, Viewer viewer, + LocaleFactory localeFactory ) { + super(viewingPlatform, viewer, localeFactory); + } + + /** + * Creates a Locale, a single ViewingPlatform, and a Viewer object from + * the given array of Canvas3D instances. + * + * @param canvases the canvases to associate with the Viewer object; + * passing in null will cause this parameter to be ignored and a canvas + * to be created by the utility + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse(Canvas3D[] canvases) { + this(1, canvases, null, null, null, true); + } + + /** + * Creates a Locale, a single ViewingPlatform with the specified number of + * transforms, and a Viewer object using the given array of Canvas3D + * instances. + * + * @param canvases the canvases to associate with the Viewer object; + * passing in null will cause this parameter to be ignored and a canvas + * to be created by the utility + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(Canvas3D[] canvases, int transformCount) { + this(transformCount, canvases, null, null, null, true); + } + + /** + * Reads the configuration specified by the given URL to create a Locale, + * one or more ViewingPlatforms, and at least one Viewer object. The + * configuration file may also create InputDevice, Sensor, and + * ViewPlatformBehavior instances. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer and ViewingPlatform + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse(URL userConfig) { + this(1, null, userConfig, null, null, true); + } + + /** + * Reads the configuration specified by the given URL to create a Locale, + * one or more ViewingPlatforms with the specified number of transforms, + * and at least one Viewer object. The configuration file may also create + * InputDevice, Sensor, and ViewPlatformBehavior instances. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer and ViewingPlatform with the specified + * number of transforms + * @param transformCount the number of transforms in the + * MultiTransformGroup objects to be created + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(URL userConfig, int transformCount) { + this(transformCount, null, userConfig, null, null, true); + } + + /** + * Reads the configuration specified by the given URL to create a Locale, + * one or more ViewingPlatforms with the specified number of transforms, + * and at least one Viewer object with optional visibility. AWT + * components used by the Viewers will remain invisible unless the + * setVisible flag is true. The configuration file may also + * create InputDevice, Sensor, and ViewPlatformBehavior instances. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer with the specified visibility and a + * ViewingPlatform with the specified number of transforms + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * @param setVisible if true, calls setVisible(true) on all + * created window components; otherwise, they remain invisible + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(URL userConfig, + int transformCount, boolean setVisible) { + this(transformCount, null, userConfig, null, null, setVisible); + } + + /** + * Reads the configuration specified by the given URL to create a Locale + * using the given LocaleFactory, one or more ViewingPlatforms, and at + * least one Viewer object. The configuration file may also create + * InputDevice, Sensor, and ViewPlatformBehavior instances. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer and ViewingPlatform with the specified + * number of transforms + * @param localeFactory the factory object used to create the Locale + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse(URL userConfig, LocaleFactory localeFactory) { + this(1, null, userConfig, localeFactory, null, true); + } + + /** + * Reads the configuration specified by the given URL to create a Locale + * using the given LocaleFactory, one or more ViewingPlatforms, and at + * least one Viewer object with optional visibility. The configuration + * file may also create InputDevice, Sensor, and ViewPlatformBehavior + * instances. Window components used by the Viewers will remain invisible + * unless the setVisible flag is true. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer with the specified visibility and a + * default ViewingPlatform + * @param localeFactory the factory object used to create the Locale + * @param setVisible if true, calls setVisible(true) on all + * created window components; otherwise, they remain invisible + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public ConfiguredUniverse(URL userConfig, + LocaleFactory localeFactory, + boolean setVisible) { + this(1, null, userConfig, localeFactory, null, setVisible); + } + + /** + * Reads the configuration specified by the given URL to create a Locale + * using the specified LocaleFactory with the given origin, one or more + * ViewingPlatforms with the specified number of transforms, and at least + * one Viewer object with optional visibility. Window components used by + * the Viewers will remain invisible unless the setVisible + * flag is true. The configuration file may also create InputDevice, + * Sensor, and ViewPlatformBehavior instances. + * + * @param userConfig the URL to the user's configuration file; passing in + * null creates a default Viewer with the specified visibility and a + * ViewingPlatform with the specified number of transforms + * @param localeFactory the factory object used to create the Locale + * @param origin the origin used to set the origin of the Locale object; + * if this object is null, then 0.0 is used + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * @param setVisible if true, calls setVisible(true) on all + * created window components; otherwise, they remain invisible + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + public ConfiguredUniverse(URL userConfig, LocaleFactory localeFactory, + HiResCoord origin, int transformCount, + boolean setVisible) { + + this(transformCount, null, userConfig, + localeFactory, origin, setVisible); + } + + /** + * Retrieves view-side scenegraph components from the given container to + * create a universe with one Locale, one or more ViewingPlatforms, and at + * least one Viewer object. Equivalent to + * ConfiguredUniverse(ConfigContainer, null, null). + * + * @param userConfig container holding viewing configuration components; + * must not be null + * + * @see #ConfiguredUniverse(ConfigContainer, LocaleFactory, HiResCoord) + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @since Java 3D 1.3.1 + */ + public ConfiguredUniverse(ConfigContainer userConfig) { + this(userConfig, null, null); + } + + /** + * Retrieves view-side scenegraph components from the given container to + * create a universe with one Locale created from the specified + * LocaleFactory and origin, one or more ViewingPlatforms, and at least + * one Viewer object. The container may also provide InputDevice, Sensor, + * and ViewPlatformBehavior instances which will be incorporated into the + * universe if they are referenced by any of the Viewer or ViewingPlatform + * instances.

+ * + * This constructor and ConfiguredUniverse(ConfigContainer) + * both accept ConfigContainer references directly and are the preferred + * interfaces for constructing universes from configuration files. They + * differ from the constructors that accept URL objects in the + * following ways:

+ *

    + *
  • A Viewer will be attached to a default ViewingPlatform only if + * no ViewingPlatforms are provided in the ConfigContainer. If one + * or more ViewingPlatforms are provided by the ConfigContainer, then + * Viewers must be attached to them explicitly in the configuration.

    + *

  • + *
  • ViewPlatformBehaviors will be attached to their specified + * ViewingPlatforms before ConfiguredUniverse can set a reference to + * itself in the ViewingPlatform. This means that a behavior can't + * get a reference to the universe at the time its + * setViewingPlatform method is called; it must wait + * until its initialize method is called.

    + *

  • + *
  • All Java properties used by Java 3D may be set in the beginning of + * the configuration file as long as there is no reference to a + * VirtualUniverse prior to creating the ConfigContainer. Note + * however, that some Java 3D utilities and objects such as + * Transform3D can cause static references to VirtualUniverse and + * trigger the evaluation of Java properties before they are set by + * ConfigContainer.

    + *

  • + *
+ * @param userConfig container holding viewing configuration components; + * must not be null + * @param localeFactory the factory object used to create the Locale, or + * null + * @param origin the origin used to set the origin of the Locale object; + * if this object is null, then 0.0 is used + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @since Java 3D 1.3.1 + */ + public ConfiguredUniverse(ConfigContainer userConfig, + LocaleFactory localeFactory, + HiResCoord origin) { + + super(origin, localeFactory); + configContainer = userConfig; + + Collection c = configContainer.getViewers(); + if (c == null || c.size() == 0) + throw new IllegalArgumentException + ("\nno views defined in configuration file\n"); + + viewer = (Viewer[])c.toArray(new Viewer[1]); + + c = configContainer.getViewingPlatforms(); + if (c == null || c.size() == 0) { + createDefaultViewingPlatform + (configContainer.getViewPlatformTransformCount()); + } + else { + Iterator i = c.iterator(); + while (i.hasNext()) { + ViewingPlatform vp = (ViewingPlatform)i.next(); + vp.setUniverse(this); + locale.addBranchGraph(vp); + } + } + } + + /** + * Package-scope constructor that creates the view side of the + * scene graph. The passed in parameters override the default + * values where appropriate. Note that the userCanvases parameter + * is ignored when the userConfig is non-null. + * + * @param transformCount the number of transforms in the + * MultiTransformGroup object to be created + * @param canvases the canvases to associate with the Viewer object; + * passing in null will cause this parameter to be ignored and a canvas + * to be created by the utility + * @param userConfig the URL to the user's configuration file; passing in + * null causes the default values to be used. + * @param localeFactory the factory object used to create the Locale + * @param origin the origin used to set the origin of the Locale object; + * if this object is null, then 0.0 is used + * @param setVisible if true, calls setVisible(true) on all + * created window components; otherwise, they remain invisible + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + */ + ConfiguredUniverse(int transformCount, + Canvas3D[] canvases, + URL userConfig, + LocaleFactory localeFactory, + HiResCoord origin, + boolean setVisible) { + + super(origin, localeFactory); + + if (userConfig == null) { + viewer = new Viewer[1]; + viewer[0] = new Viewer(canvases, null, null, setVisible); + createDefaultViewingPlatform(transformCount); + } + else { + // Create a ConfigContainer without attaching behaviors. The + // package-scope constructor is used for backward compatibility. + configContainer = new ConfigContainer + (userConfig, setVisible, transformCount, false); + + Collection c = configContainer.getViewers(); + if (c == null || c.size() == 0) + throw new IllegalArgumentException + ("\nno views defined in configuration file\n"); + + viewer = (Viewer[])c.toArray(new Viewer[1]); + + // Get ViewingPlatforms from the ConfigContainer and add them to + // the locale. The package-scoped findConfigObjects() accesor is + // used so that backward compatibility can be maintained for older + // configuration files. + c = configContainer.findConfigObjects("ViewPlatform"); + if (c == null || c.size() == 0) { + createDefaultViewingPlatform(transformCount); + } + else { + Iterator i = c.iterator(); + while (i.hasNext()) { + ConfigViewPlatform cvp = (ConfigViewPlatform)i.next(); + ViewingPlatform vp = cvp.viewingPlatform; + + // For backward compatibility, handle the default + // attachment of one Viewer to one ViewingPlatform. If + // there are multiple Viewers and ViewingPlatforms then + // attachments must be made explicitly in the config file. + if (vp.getViewers() == null && + viewer.length == 1 && c.size() == 1) { + if (cvp.viewAttachPolicy == -1) { + setDerivedAttachPolicy(viewer[0], vp) ; + } + viewer[0].setViewingPlatform(vp); + } + vp.setUniverse(this); + locale.addBranchGraph(vp); + + // If there's a behavior associated with the platform, + // attach it now after the setting the universe reference. + cvp.processBehavior(); + } + } + } + } + + /** + * Creates a default ViewingPlatform, attaches the first Viewer, and then + * attaches the platform to the Locale. + * + * @param transformCount number of TransformGroups to create in the + * ViewingPlatform + */ + private void createDefaultViewingPlatform(int transformCount) { + ViewingPlatform vp = new ViewingPlatform(transformCount); + setDerivedAttachPolicy(viewer[0], vp); + viewer[0].setViewingPlatform(vp); + vp.setUniverse(this); + locale.addBranchGraph(vp); + } + + /** + * Sets a view attach policy appropriate for a window eyepoint policy. + * + * @param v Viewer to which the ViewingPlatform will be attached + * @param vp ViewingPlatform to which the Viewer will be attached + */ + private void setDerivedAttachPolicy(Viewer v, ViewingPlatform vp) { + if (v.getView().getWindowEyepointPolicy() != + View.RELATIVE_TO_FIELD_OF_VIEW) { + vp.getViewPlatform().setViewAttachPolicy(View.NOMINAL_SCREEN); + } + } + + + /** + * Returns the Viewer object specified by the given index. + * + * @param index The index of which Viewer object to return. + * + * @return The Viewer object specified by the given index. + */ + public Viewer getViewer(int index) { + return viewer[index]; + } + + /** + * Returns all of the Viewer objects associated with this scene graph. + * + * @return The Viewer objects associated with this scene graph. + */ + public Viewer[] getViewers() { + Viewer[] ret = new Viewer[viewer.length]; + for (int i = 0; i < viewer.length; i++) { + ret[i] = viewer[i]; + } + return ret; + } + + /** + * Call setVisible() on all AWT components created by this + * ConfiguredUniverse instance.

+ * + * @param visible boolean to be passed to the setVisible() + * calls on the window components created by this + * ConfiguredUniverse instance + */ + public void setVisible(boolean visible) { + for (int i = 0; i < viewer.length; i++) + if (viewer[i] != null) + viewer[i].setVisible(visible); + } + + /** + * Returns the config file URL based on system properties. This is + * equivalent to calling ConfigContainer.getConfigURL(). The + * current implementation of this method parses the j3d.configURL property + * as a URL string. For example, the following command line would specify + * that the config file is taken from the file "j3dconfig" in the current + * directory: + *

    + * java -Dj3d.configURL=file:j3dconfig ... + *
+ * + * @return the URL of the config file; null is returned if no valid + * URL is defined by the system properties + */ + public static URL getConfigURL() { + return ConfigContainer.getConfigURL(null); + } + + /** + * Returns the config file URL based on system properties. This is the + * same as calling ConfigContainer.getConfigURL(String). The + * current implementation of this method parses the j3d.configURL property + * as a URL string. For example, the following command line would specify + * that the config file is taken from the file "j3dconfig" in the current + * directory: + *
    + * java -Dj3d.configURL=file:j3dconfig ... + *
+ * + * @param defaultURLString the default string used to construct + * the URL if the appropriate system properties are not defined + * @return the URL of the config file; null is returned if no + * valid URL is defined either by the system properties or the + * default URL string + */ + public static URL getConfigURL(String defaultURLString) { + return ConfigContainer.getConfigURL(defaultURLString); + } + + /** + * Returns all named Sensors defined by the configuration file used to + * create the ConfiguredUniverse, if any. Equivalent to + * getConfigContainer().getNamedSensors().

+ * + * With the sole exception of the Sensor assigned to the head tracker, + * none of the Sensors defined in the configuration file are placed into + * the Sensor array maintained by PhysicalEnvironment. The head tracker + * Sensor is the only one read by the Java 3D core and must generate reads + * with a full 6 degrees of freedom (3D position and 3D orientation).

+ * + * Other Sensors need not generate reads with a full 6 degrees of freedom, + * although their reads must be expressed using Transform3D. Some + * joysticks may provide only 2D relative X and Y axis movement; dials, + * levers, and sliders are 1D devices, and some devices may combine dials + * and levers to generate 3D positional data.

+ * + * The index names to identify left / right / dominant / non-dominant hand + * Sensors in the PhysicalEnvironement Sensor array are not adequate to + * distinguish these differences, so this method allows applications to + * look up Sensors based on the names bound to them in the configuration + * file. There are no set rules on naming. Applications that use Sensors + * may set up conventions for generic devices such as "mouse6D" or + * "joystick2D" or specific product names.

+ * + * @return read-only Map which maps Sensor names to the associated Sensors, + * or null if no Sensors have been named + */ + public Map getNamedSensors() { + if (configContainer == null) + return null; + else + return configContainer.getNamedSensors(); + } + + /** + * Returns all named ViewPlatformBehaviors defined by the configuration + * file used to create the ConfiguredUniverse, if any. Equivalent + * to getConfigContainer().getNamedViewPlatformBehaviors().

+ * + * @return read-only Map which maps behavior names to the associated + * ViewPlatformBehavior instances, or null if none have been named. + * @since Java 3D 1.3.1 + */ + public Map getNamedBehaviors() { + if (configContainer == null) + return null; + else + return configContainer.getNamedViewPlatformBehaviors(); + } + + /** + * Returns a container holding all the objects defined by the + * configuration file used to create the ConfiguredUniverse. + * + * @return the container + * @since Java 3D 1.3.1 + */ + public ConfigContainer getConfigContainer() { + return configContainer; + } + + /** + * Cleanup memory references used by ConfiguredUniverse. + * @since Java 3D 1.3.1 + */ + public void cleanup() { + if (viewer != null) { + for (int i = 0 ; i < viewer.length ; i++) { + viewer[i].getView().removeAllCanvas3Ds(); + viewer[i].setViewingPlatform(null); + viewer[i] = null; + } + } + + locale = null; + removeAllLocales(); + Viewer.clearViewerMap(); + + configContainer.clear(); + configContainer = null; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/LocaleFactory.java b/src/classes/share/com/sun/j3d/utils/universe/LocaleFactory.java new file mode 100644 index 0000000..570d65b --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/LocaleFactory.java @@ -0,0 +1,82 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import javax.media.j3d.Locale; +import javax.media.j3d.HiResCoord; +import javax.media.j3d.VirtualUniverse; + +/** + * This interface defines a factory for creating Locale objects in a + * SimpleUniverse. Implementations of the createLocale methods in + * this interface should construct a new Locale object from the + * specified parameters. This class is used by the SimpleUniverse + * class to construct the default Locale used to hold the view and + * content branch graphs. + * + * @see Locale + * @see ConfiguredUniverse + * @see SimpleUniverse + * + * @since Java 3D 1.3 + */ +public interface LocaleFactory { + /** + * Creates a new Locale object at the specified high resolution + * coordinate in the specified universe. + * + * @param universe the VirtualUniverse in which to create the Locale + * @param hiRes the high resolution coordinate that defines the origin + * of the Locale + */ + public Locale createLocale(VirtualUniverse universe, HiResCoord hiRes); + + /** + * Creates a new Locale object at (0, 0, 0) in the specified universe. + * + * @param universe the VirtualUniverse in which to create the Locale + */ + public Locale createLocale(VirtualUniverse universe); +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/MultiTransformGroup.java b/src/classes/share/com/sun/j3d/utils/universe/MultiTransformGroup.java new file mode 100644 index 0000000..4c241a9 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/MultiTransformGroup.java @@ -0,0 +1,140 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * A convenience class that effectively creates a series of TransformGroup + * nodes connected one to another hierarchically. For most applications, + * creating a MultiTransformGroup containing one transform will suffice. + * More sophisticated applications that use a complex portal/head tracking + * viewing system may find that more transforms are needed. + *

+ * When more than one transform is needed, transform[0] is considered the + * "top most" transform with repsect to the scene graph, (attached to the + * ViewingPlatform node) and transform[numTransforms - 1] is the "bottom + * most" transform (the ViewPlatorm object is attached to this transform). + */ +public class MultiTransformGroup { + + // For now just have an array of TransformGroup nodes. + TransformGroup[] transforms; + + /** + * Creates a MultiTransformGroup node that contains a single transform. + * This is effectively equivalent to creating a single TransformGroup + * node. + */ + public MultiTransformGroup() { + this(1); + } + + /** + * Creates a MultiTransformGroup node that contains the specified + * number of transforms. + *

+ * When more than one transform is needed, transform[0] is considered the + * "top most" transform with repsect to the scene graph, (attached to the + * ViewingPlatform node) and transform[numTransforms - 1] is the "bottom + * most" transform (the ViewPlatorm object is attached to this transform). + * + * @param numTransforms The number of transforms for this node to + * contain. If this number is less than one, one is assumed. + */ + public MultiTransformGroup(int numTransforms) { + if (numTransforms < 1) + numTransforms = 1; + + transforms = new TransformGroup[numTransforms]; + + // there is always at least one TransformGroup + transforms[0] = new TransformGroup(); + transforms[0].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + transforms[0].setCapability(TransformGroup.ALLOW_TRANSFORM_READ); + transforms[0].setCapability(TransformGroup.ALLOW_CHILDREN_WRITE); + transforms[0].setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND); + + for (int i = 1; i < numTransforms; i++) { + transforms[i] = new TransformGroup(); + transforms[i].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + transforms[i].setCapability(TransformGroup.ALLOW_TRANSFORM_READ); + transforms[i].setCapability(TransformGroup.ALLOW_CHILDREN_WRITE); + transforms[i].setCapability(TransformGroup.ALLOW_CHILDREN_EXTEND); + transforms[i-1].addChild(transforms[i]); + } + } + + /** + * Returns the selected TransformGroup node. + * + * @param transform The index of the transform to return. The indices + * are in the range [0..(n - 1)] - where n was the number of transforms + * created. transform[0] is considered the + * "top most" transform with repsect to the scene graph, (attached to the + * ViewingPlatform node) and transform[numTransforms - 1] is the "bottom + * most" transform (the ViewPlatorm object is attached to this transform). + * + * @return The TransformGroup node at the designated index. If an out of + * range index is given, null is returned. + */ + public TransformGroup getTransformGroup(int transform) { + if (transform >= transforms.length || transform < 0) + return null; + + return transforms[transform]; + } + + /** + * Returns the number of transforms in this MultiTransformGroup object. + * + * @return The number of transforms in this object. + */ + public int getNumTransforms() { + return transforms.length; + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/PlatformGeometry.java b/src/classes/share/com/sun/j3d/utils/universe/PlatformGeometry.java new file mode 100644 index 0000000..990197d --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/PlatformGeometry.java @@ -0,0 +1,66 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * This class holds any geometry that should be associated with the + * ViewingPlatform object. To create a scene with a dashboard, for + * instance, a programmer would place the dashboard geometry under + * the PlatformGeometry node. + * + * @see ViewingPlatform + */ +public class PlatformGeometry extends BranchGroup { + + /** + * Constructs an instance of the PlatformGeometry node. + */ + public PlatformGeometry() { + setCapability(ALLOW_DETACH); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/SimpleUniverse.java b/src/classes/share/com/sun/j3d/utils/universe/SimpleUniverse.java new file mode 100644 index 0000000..1b9c343 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/SimpleUniverse.java @@ -0,0 +1,412 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import java.awt.GraphicsEnvironment; +import java.awt.GraphicsConfiguration; +import java.net.URL; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * This class sets up a minimal user environment to quickly and easily + * get a Java 3D program up and running. This utility class creates + * all the necessary objects on the "view" side of the scene graph. + * Specifically, this class creates a locale, a single ViewingPlatform, + * and a Viewer object (both with their default values). + * Many basic Java 3D applications + * will find that SimpleUniverse provides all necessary functionality + * needed by their applications. More sophisticated applications + * may find that they need more control in order to get extra functionality + * and will not be able to use this class. + * + * @see Viewer + * @see ViewingPlatform + */ +public class SimpleUniverse extends VirtualUniverse { + + /** + * Locale reference needed to create the "view" portion + * of the scene graph. + */ + protected Locale locale; + + /** + * Viewer reference needed to create the "view" portion + * of the scene graph. + */ + protected Viewer[] viewer = null; + + /** + * Creates a locale, a single ViewingPlatform, and + * and a Viewer object (both with their default values). + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public SimpleUniverse() { + // call main constructor with default values. + this(null, 1, null, null); + } + + /** + * Creates a locale, a single ViewingPlatform, and a Viewer object + * (with default values). The ViewingPlatform is created with the + * specified number of TransformGroups. + * + * @param numTransforms The number of transforms to be in the + * MultiTransformGroup object. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * + * @since Java 3D 1.2.1 + */ + public SimpleUniverse(int numTransforms) { + // call main constructor with default values except numTransforms + this(null, numTransforms, null, null); + } + + /** + * Creates a locale, a single ViewingPlatform (with default values), and + * and a Viewer object. The Viewer object uses default values for + * everything but the canvas. + * + * @param canvas The canvas to associate with the Viewer object. Passing + * in null will cause this parameter to be ignored and a canvas to be + * created by the utility. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + */ + public SimpleUniverse(Canvas3D canvas) { + // call main constructor with default values for everything but + // the canvas parameter. + this(null, 1, canvas, null); + } + + /** + * Creates a locale, a single ViewingPlatform, and a Viewer object + * The Viewer object uses default values for everything but the canvas. + * The ViewingPlatform is created with the specified number of + * TransformGroups. + * + * @param canvas The canvas to associate with the Viewer object. Passing + * in null will cause this parameter to be ignored and a canvas to be + * created by the utility. + * @param numTransforms The number of transforms to be in the + * MultiTransformGroup object. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + * + * @since Java 3D 1.2.1 + */ + public SimpleUniverse(Canvas3D canvas, int numTransforms) { + // call main constructor with default values except canvas + // and numTransforms + this(null, numTransforms, canvas, null); + } + + /** + * Creates the "view" side of the scene graph. The passed in parameters + * override the default values where appropriate. + * + * @param origin The origin used to set the origin of the Locale object. + * If this object is null, then 0.0 is used. + * @param numTransforms The number of transforms to be in the + * MultiTransformGroup object. + * @param canvas The canvas to draw into. If this is null, it is + * ignored and a canvas will be created by the utility. + * @param userConfig The URL to the user's configuration file, used + * by the Viewer object. This is never examined and default values are + * always taken. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + * @deprecated use ConfiguredUniverse constructors to read a + * configuration file + */ + public SimpleUniverse(HiResCoord origin, int numTransforms, + Canvas3D canvas, URL userConfig) { + this( origin, numTransforms, canvas, userConfig, null ); + } + + /** + * Creates the "view" side of the scene graph. The passed in parameters + * override the default values where appropriate. + * + * @param origin The origin used to set the origin of the Locale object. + * If this object is null, then 0.0 is used. + * @param numTransforms The number of transforms to be in the + * MultiTransformGroup object. + * @param canvas The canvas to draw into. If this is null, it is + * ignored and a canvas will be created by the utility. + * @param userConfig The URL to the user's configuration file, used + * by the Viewer object. This is never examined and default values are + * always taken. + * @param localeFactory The Locale Factory which will instantiate the + * locale(s) for this universe. + * + * @see Locale + * @see Viewer + * @see ViewingPlatform + * @see MultiTransformGroup + * @deprecated use ConfiguredUniverse constructors to read a + * configuration file + */ + public SimpleUniverse(HiResCoord origin, int numTransforms, + Canvas3D canvas, URL userConfig, LocaleFactory localeFactory ) { + ViewingPlatform vwp; + + createLocale( origin, localeFactory ); + + // Create the ViewingPlatform and Viewer objects, passing + // down the appropriate parameters. + vwp = new ViewingPlatform(numTransforms); + vwp.setUniverse( this ); + viewer = new Viewer[1]; + // viewer[0] = new Viewer(canvas, userConfig); + viewer[0] = new Viewer(canvas); + viewer[0].setViewingPlatform(vwp); + + // Add the ViewingPlatform to the locale - the scene + // graph is now "live". + locale.addBranchGraph(vwp); + } + + + /** + * Creates the "view" side of the scene graph. The passed in parameters + * override the default values where appropriate. + * + * @param viewingPlatform The viewingPlatform to use to create + * the "view" side of the scene graph. + * @param viewer The viewer object to use to create + * the "view" side of the scene graph. + */ + public SimpleUniverse(ViewingPlatform viewingPlatform, Viewer viewer) { + this( viewingPlatform, viewer, null ); + } + + /** + * Creates the "view" side of the scene graph. The passed in parameters + * override the default values where appropriate. + * + * @param viewingPlatform The viewingPlatform to use to create + * the "view" side of the scene graph. + * @param viewer The viewer object to use to create + * the "view" side of the scene graph. + * @param localeFactory The factory used to create the Locale Object + */ + public SimpleUniverse(ViewingPlatform viewingPlatform, Viewer viewer, + LocaleFactory localeFactory ) { + createLocale( null, localeFactory ); + viewingPlatform.setUniverse( this ); + + // Assign object references. + this.viewer = new Viewer[1]; + this.viewer[0] = viewer; + + // Add the ViewingPlatform to the Viewer object. + this.viewer[0].setViewingPlatform(viewingPlatform); + + // Add the ViewingPlatform to the locale - the scene + // graph is now "live". + locale.addBranchGraph(viewingPlatform); + } + + /** + * Constructor for use by Configured Universe + */ + SimpleUniverse( HiResCoord origin, LocaleFactory localeFactory ) { + createLocale( origin, localeFactory ); + } + + /** + * Create the Locale using the LocaleFactory and HiRes origin, + * if specified. + */ + private void createLocale( HiResCoord origin, + LocaleFactory localeFactory ) { + + if (localeFactory != null) { + if (origin != null) + locale = localeFactory.createLocale(this, origin); + else + locale = localeFactory.createLocale(this); + } + else { + if (origin != null) + locale = new Locale(this, origin); + else + locale = new Locale(this); + } + } + + /** + * Returns the Locale object associated with this scene graph. + * + * @return The Locale object used in the construction of this scene + * graph. + */ + public Locale getLocale() { + return locale; + } + + /** + * Returns the Viewer object associated with this scene graph. + * SimpleUniverse creates a single Viewer object for use in the + * scene graph. + * + * @return The Viewer object associated with this scene graph. + */ + public Viewer getViewer() { + return viewer[0]; + } + + /** + * Returns the ViewingPlatform object associated with this scene graph. + * + * @return The ViewingPlatform object of this scene graph. + */ + public ViewingPlatform getViewingPlatform() { + return viewer[0].getViewingPlatform(); + } + + /** + * Returns the Canvas3D object associated with this Java 3D Universe. + * + * @return A reference to the Canvas3D object associated with the + * Viewer object. This method is equivalent to calling getCanvas(0). + * + * @see Viewer + */ + public Canvas3D getCanvas() { + return getCanvas(0); + } + + /** + * Returns the Canvas3D object at the specified index associated with + * this Java 3D Universe. + * + * @param canvasNum The index of the Canvas3D object to retrieve. + * If there is no Canvas3D object for the given index, null is returned. + * + * @return A reference to the Canvas3D object associated with the + * Viewer object. + */ + public Canvas3D getCanvas(int canvasNum) { + return viewer[0].getCanvas3D(canvasNum); + } + + /** + * Used to add Nodes to the geometry side (as opposed to the view side) + * of the scene graph. This is a short cut to getting the Locale object + * and calling that object's addBranchGraph() method. + * + * @param bg The BranchGroup to attach to this Universe's Locale. + */ + public void addBranchGraph(BranchGroup bg) { + locale.addBranchGraph(bg); + } + + /** + * Finds the preferred GraphicsConfiguration object + * for the system. This object can then be used to create the + * Canvas3D objet for this system. + * + * @return The best GraphicsConfiguration object for + * the system. + */ + public static GraphicsConfiguration getPreferredConfiguration() { + GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D(); + String stereo; + + // Check if the user has set the Java 3D stereo option. + // Getting the system properties causes appletviewer to fail with a + // security exception without a try/catch. + + stereo = (String) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty("j3d.stereo"); + } + }); + + // update template based on properties. + if (stereo != null) { + if (stereo.equals("REQUIRED")) + template.setStereo(template.REQUIRED); + else if (stereo.equals("PREFERRED")) + template.setStereo(template.PREFERRED); + } + + // Return the GraphicsConfiguration that best fits our needs. + return GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getBestConfiguration(template); + } + + /** + * Cleanup memory use and reference by SimpleUniverse. + * Typically it should be invoked by the applet's destroy method. + */ + public void cleanup() { + viewer[0].getView().removeAllCanvas3Ds(); + viewer[0].setViewingPlatform(null); + removeAllLocales(); + // viewerMap cleanup here to prevent memory leak problem. + Viewer.clearViewerMap(); + + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java b/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java new file mode 100644 index 0000000..9f5c53c --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ViewInfo.java @@ -0,0 +1,3398 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe ; + +import java.awt.GraphicsConfiguration ; +import java.awt.Point ; +import java.awt.Rectangle ; +import java.text.DecimalFormat ; +import java.text.FieldPosition ; +import java.util.* ; +import javax.media.j3d.* ; +import javax.vecmath.* ; + +/** + * Provides methods to extract synchronized transform information from a View. + * These transforms are derived from application scene graph information, as + * opposed to similar core Java 3D methods that derive transforms from + * internally maintained data. This allows updates to the scene graph to be + * synchronized with the current view platform position.

+ * + * The architecture of the Java 3D 1.3 sample implementation introduces a + * frame latency between updates to the application scene graph structure and + * their effects on internal Java 3D state. getImagePlateToVworld + * and other methods in the core Java 3D classes use a transform from view + * platform coordinates to virtual world coordinates that can be out of date + * with respect to the state of the view platform as set by the application. + * When an application uses the transforms returned by those methods to update + * view dependent parts of the scene graph, those updates might not be + * synchronized with what the viewer actually sees.

+ * + * The methods in this class work around this problem at the expense of + * querying the application state of the scene graph to get the current + * transform from view platform to virtual world coordinates. This can + * involve a potential performance degradation, however, since the application + * scene graph state is not designed for high performance queries. The view + * platform must also have ALLOW_LOCAL_TO_VWORLD_READ capability + * set, which potentially inhibits internal scene graph optimization.

+ * + * On the other hand, application behaviors that create the view platform + * transformation directly will have access to it without the need to query it + * from the scene graph; in that case, the transforms from physical + * coordinates to view platform coordinates provided by this class are all + * that are needed. The ALLOW_LOCAL_TO_VWORLD_READ view platform + * capability doesn't need to be set for these applications.

+ * + * Other Synchronization Issues

+ * + * Scene graph updates are guaranteed to take effect in the same frame only + * if run from the processStimulus() method of a Behavior. Updates from + * multiple behaviors are only guaranteed to take effect in the same frame if + * they're responding to a WakeupOnElapsedFrames(0) condition. Use a single + * behavior to perform view dependent updates if possible; otherwise, use + * WakeupOnElapsedFrames(0) and set behavior scheduling intervals to ensure + * that behaviors that need the current view platform transform are run after + * it's set. Updating scene graph elements from anything other than the + * Behavior thread, such as an external input thread or a renderer callback + * in Canvas3D, will not necessarily be synchronized with rendering.

+ * + * Direct updates to geometry data have a different frame latency than + * updates to scene graph transforms and structure. In the Java 3D 1.3 + * architecture, updates to by-reference geometry arrays and texture data have + * a 1-frame latency, while updates to transforms and scene graph structure + * have a 2-frame latency. Because of bug 4799494, which is outstanding + * in Java 3D 1.3.1, updates to by-copy geometry arrays also have a 1-frame + * latency. It is therefore recommended that view dependent scene graph + * updates be limited to transforms and scene graph structure only.

+ * + * If it is not possible to avoid updating geometry directly, then these + * updates must be delayed by one frame in order to remain synchronized with + * the view platform. This can be accomplished by creating an additional + * behavior to actually update the geometry, separate from the behavior that + * computes the changes that need to be made based on current view state. If + * the update behavior is awakened by a behavior post from the computing + * behavior then the update will be delayed by a single frame.

+ * + * Implementation Notes

+ * + * This utility is essentially a rewrite of a few private Java 3D core + * classes, but designed for public use and source code availability. The + * source code may be helpful in understanding some of the more complex + * aspects of the view model, especially with regards to various interactions + * between attributes which are not adequately documented. None of the actual + * core Java 3D source code is used, but the code is designed to comply with + * the view model as defined by the Java 3D Specification, so it can be + * considered an alternative implementation. This class will produce the + * same results as the Java 3D core implementation except for:

    + * + *
  • The frame latency issue for virtual world transforms.
  • + * + *

  • Active clip node status. If a clip node is active in the scene graph, + * it should override the view's back clip plane. This class has no such + * information, so this can't be implemented.
  • + * + *

  • "Infinite" view transforms for background geometry. These are simply + * the rotation components of the normal view transforms with adjusted + * clip planes. Again, this function depends upon scene graph content + * inaccessible to this class.
  • + * + *

  • Small floating point precision differences resulting from the + * alternative computations.
  • + * + *

  • Bugs in this class and the Java 3D core.
  • + * + *

  • Tracked head position.

+ * + * The last item deserves some mention. Java 3D provides no way to directly + * query the tracked head position being used by the renderer. The View's + * getUserHeadToVworld method always incorporates a virtual world + * transform that is out of date with respect to the application scene graph + * state. ViewInfo reads data from the head tracking sensor directly, but + * since head trackers are continuous input devices, getting the same data + * that the renderer is using is unlikely. See the source code for the + * private method getHeadInfo in this class for more information + * and possible workarounds.

+ * + * Thread Safety

+ * + * All transforms are lazily evaluated. The updateScreen, + * updateCanvas, updateViewPlatform, + * updateView, and updateHead methods just set flags + * indicating that derived transforms need to be recomputed; they are safe to + * call from any thread. updateCanvas, for example, can safely + * be called from an AWT event listener.

+ * + * Screens and view platforms can be shared between separate views in the Java + * 3D view model. To remain accurate, ViewInfo also allows this sharing. + * Since it is likely that a multi-view application has separate threads + * managing each view, potential concurrent modification of data associated + * with a screen or a view platform is internally synchronized in this class. + * It is safe for each thread to use its own instance of a ViewInfo + * corresponding to the view it is managing.

+ * + * Otherwise, none of the other methods in this class are internally + * synchronized. Except for the update methods mentioned above, a single + * instance of ViewInfo should not be used by more than one concurrent thread + * without external synchronization.

+ * + * @since Java 3D 1.3.1 + */ +public class ViewInfo { + private final static boolean verbose = false ; + + /** + * Indicates that updates to a Screen3D associated with the View should + * be automatically checked with each call to a public method in this + * class. + */ + public final static int SCREEN_AUTO_UPDATE = 1 ; + + /** + * Indicates that updates to a Canvas3D associated with the View should + * be automatically checked with each call to a public method in this + * class. + */ + public final static int CANVAS_AUTO_UPDATE = 2 ; + + /** + * Indicates that updates to the View should be automatically checked + * with each call to a public method in this class. + */ + public final static int VIEW_AUTO_UPDATE = 4 ; + + /** + * Indicates that updates to the tracked head position should be + * automatically checked with each call to a public method in this class. + */ + public final static int HEAD_AUTO_UPDATE = 8 ; + + /** + * Indicates that updates to the ViewPlatform localToVworld + * transform should be automatically checked with each call to a public + * method in this class. The View must be attached to a ViewPlatform + * which is part of a live scene graph, and the ViewPlatform node must + * have its ALLOW_LOCAL_TO_VWORLD_READ capability set. + */ + public final static int PLATFORM_AUTO_UPDATE = 16 ; + + // + // Screen3D and ViewPlatform instances are shared across multiple Views in + // the Java 3D view model. Since ViewInfo is per-View and we want to + // cache screen and platform derived data, we maintain static references + // to the screens and platforms here. + // + // From a design standpoint our ViewInfo objects should probably be in the + // scope of an object that encloses these maps so they can be gc'ed + // properly. This is cumbersome with the current design constraints, so + // for now we provide an alternative constructor to override these static + // maps and a method to explicitly clear them. The alternative + // constructor can be used to wrap this class into a multi-view context + // that provides the maps. + // + private static Map staticVpMap = new HashMap() ; + private static Map staticSiMap = new HashMap() ; + + private Map screenMap = null ; + private Map viewPlatformMap = null ; + + // The target View and some derived data. + private View view = null ; + private Sensor headTracker = null ; + private boolean useTracking = false ; + private boolean clipVirtual = false ; + + // The current ViewPlatform and Canvas3D information used by this object. + private ViewPlatformInfo vpi = null ; + private int canvasCount = 0 ; + private Map canvasMap = new HashMap() ; + private CanvasInfo[] canvasInfo = new CanvasInfo[1] ; + + // This View's update flags. The other update flags are maintained by + // ScreenInfo, CanvasInfo, and ViewPlatformInfo. + private boolean updateView = true ; + private boolean updateHead = true ; + private boolean autoUpdate = false ; + private int autoUpdateFlags = 0 ; + + // Cached View policies. + private int viewPolicy = View.SCREEN_VIEW ; + private int resizePolicy = View.PHYSICAL_WORLD ; + private int movementPolicy = View.PHYSICAL_WORLD ; + private int eyePolicy = View.RELATIVE_TO_FIELD_OF_VIEW ; + private int projectionPolicy = View.PERSPECTIVE_PROJECTION ; + private int frontClipPolicy = View.PHYSICAL_EYE ; + private int backClipPolicy = View.PHYSICAL_EYE ; + private int scalePolicy = View.SCALE_SCREEN_SIZE ; + private boolean coeCentering = true ; + + // This View's cached transforms. See ScreenInfo, CanvasInfo, and + // ViewPlatformInfo for the rest of the cached transforms. + private Transform3D coeToTrackerBase = null ; + private Transform3D headToHeadTracker = null ; + + // These are from the head tracker read. + private Transform3D headTrackerToTrackerBase = null ; + private Transform3D trackerBaseToHeadTracker = null ; + + // These are derived from the head tracker read. + private Transform3D headToTrackerBase = null ; + private Transform3D coeToHeadTracker = null ; + + // Cached physical body and environment. + private PhysicalEnvironment env = null ; + private PhysicalBody body = null ; + private Point3d leftEyeInHead = new Point3d() ; + private Point3d rightEyeInHead = new Point3d() ; + + // Temporary variables. These could just be new'ed as needed, but we'll + // assume that ViewInfo instances are used much more than they're created. + private Vector3d v3d = new Vector3d() ; + private double[] m16d = new double[16] ; + private Point3d leftEye = new Point3d() ; + private Point3d rightEye = new Point3d() ; + private Map newMap = new HashMap() ; + private Set newSet = new HashSet() ; + + /** + * Creates a new ViewInfo for the specified View.

+ * + * Applications are responsible for informing this class of changes to the + * View, its Canvas3D and Screen3D components, the tracked head position, + * and the ViewPlatform's localToVworld transform. These + * notifications are performed with the updateView, + * updateCanvas, updateScreen, + * updateHead, and updateViewPlatform + * methods.

+ * + * The View must be attached to a ViewPlatform. If the ViewPlatform is + * attached to a live scene graph, then ALLOW_POLICY_READ + * capability must be set on the ViewPlatform node. + * + * @param view the View to use + * @see #updateView + * @see #updateCanvas updateCanvas(Canvas3D) + * @see #updateScreen updateScreen(Screen3D) + * @see #updateHead + * @see #updateViewPlatform + */ + public ViewInfo(View view) { + this(view, 0) ; + } + + /** + * Creates a new ViewInfo for the specified View. The View must be + * attached to a ViewPlatform. If the ViewPlatform is attached to a live + * scene graph, then ALLOW_POLICY_READ capability must be set + * on the ViewPlatform node. + * + * @param view the View to use

+ * @param autoUpdateFlags a logical OR of any of the + * VIEW_AUTO_UPDATE, CANVAS_AUTO_UPDATE, + * SCREEN_AUTO_UPDATE, HEAD_AUTO_UPDATE, or + * PLATFORM_AUTO_UPDATE flags to control whether changes to + * the View, its Canvas3D or Screen3D components, the tracked head + * position, or the ViewPlatform's localToVworld transform + * are checked automatically with each call to a public method of this + * class; if a flag is not set, then the application must inform this + * class of updates to the corresponding data + */ + public ViewInfo(View view, int autoUpdateFlags) { + this(view, autoUpdateFlags, staticSiMap, staticVpMap) ; + } + + /** + * Creates a new ViewInfo for the specified View. The View must be + * attached to a ViewPlatform. If the ViewPlatform is attached to a live + * scene graph, then ALLOW_POLICY_READ capability must be set + * on the ViewPlatform node.

+ * + * ViewInfo caches Screen3D and ViewPlatform data, but Screen3D and + * ViewPlatform instances are shared across multiple Views in the Java 3D + * view model. Since ViewInfo is per-View, all ViewInfo constructors + * except for this one use static references to manage the shared Screen3D + * and ViewPlatform objects. In this constructor, however, the caller + * supplies two Map instances to hold these references for all ViewInfo + * instances, so static references can be avoided; it can be used to wrap + * this class into a multi-view context that provides the required + * maps.

+ * + * Alternatively, the other constructors can be used by calling + * ViewInfo.clear when done with ViewInfo, or by simply + * retaining the static references until the JVM exits.

+ * + * @param view the View to use

+ * @param autoUpdateFlags a logical OR of any of the + * VIEW_AUTO_UPDATE, CANVAS_AUTO_UPDATE, + * SCREEN_AUTO_UPDATE, HEAD_AUTO_UPDATE, or + * PLATFORM_AUTO_UPDATE flags to control whether changes to + * the View, its Canvas3D or Screen3D components, the tracked head + * position, or the ViewPlatform's localToVworld transform + * are checked automatically with each call to a public method of this + * class; if a flag is not set, then the application must inform this + * class of updates to the corresponding data

+ * @param screenMap a writeable Map to hold Screen3D information + * @param viewPlatformMap a writeable Map to hold ViewPlatform information + */ + public ViewInfo(View view, int autoUpdateFlags, + Map screenMap, Map viewPlatformMap) { + + if (verbose) + System.err.println("ViewInfo: init " + hashCode()) ; + if (view == null) + throw new IllegalArgumentException("View is null") ; + if (screenMap == null) + throw new IllegalArgumentException("screenMap is null") ; + if (viewPlatformMap == null) + throw new IllegalArgumentException("viewPlatformMap is null") ; + + this.view = view ; + this.screenMap = screenMap ; + this.viewPlatformMap = viewPlatformMap ; + + if (autoUpdateFlags == 0) { + this.autoUpdate = false ; + } + else { + this.autoUpdate = true ; + this.autoUpdateFlags = autoUpdateFlags ; + } + + getViewInfo() ; + } + + /** + * Gets the current transforms from image plate coordinates to view + * platform coordinates and copies them into the given Transform3Ds.

+ * + * With a monoscopic canvas the image plate transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left image plate transform, and + * if the second argument is non-null it receives the right image plate + * transform. These transforms are always the same unless a head mounted + * display driven by a single stereo canvas is in use. + * + * @param c3d the Canvas3D associated with the image plate + * @param ip2vpl the Transform3D to receive the left transform + * @param ip2vpr the Transform3D to receive the right transform, or null + */ + public void getImagePlateToViewPlatform(Canvas3D c3d, + Transform3D ip2vpl, + Transform3D ip2vpr) { + + CanvasInfo ci = updateCache + (c3d, "getImagePlateToViewPlatform", false) ; + + getImagePlateToViewPlatform(ci) ; + ip2vpl.set(ci.plateToViewPlatform) ; + if (ci.useStereo && ip2vpr != null) + ip2vpr.set(ci.rightPlateToViewPlatform) ; + } + + private void getImagePlateToViewPlatform(CanvasInfo ci) { + if (ci.updatePlateToViewPlatform) { + if (verbose) System.err.println("updating PlateToViewPlatform") ; + if (ci.plateToViewPlatform == null) + ci.plateToViewPlatform = new Transform3D() ; + + getCoexistenceToImagePlate(ci) ; + getViewPlatformToCoexistence(ci) ; + + ci.plateToViewPlatform.mul(ci.coeToPlate, ci.viewPlatformToCoe) ; + ci.plateToViewPlatform.invert() ; + + if (ci.useStereo) { + if (ci.rightPlateToViewPlatform == null) + ci.rightPlateToViewPlatform = new Transform3D() ; + + ci.rightPlateToViewPlatform.mul(ci.coeToRightPlate, + ci.viewPlatformToCoe) ; + ci.rightPlateToViewPlatform.invert() ; + } + ci.updatePlateToViewPlatform = false ; + if (verbose) t3dPrint(ci.plateToViewPlatform, "plateToVp") ; + } + } + + /** + * Gets the current transforms from image plate coordinates to virtual + * world coordinates and copies them into the given Transform3Ds.

+ * + * With a monoscopic canvas the image plate transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left image plate transform, and + * if the second argument is non-null it receives the right image plate + * transform. These transforms are always the same unless a head mounted + * display driven by a single stereo canvas is in use.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set. + * + * @param c3d the Canvas3D associated with the image plate + * @param ip2vwl the Transform3D to receive the left transform + * @param ip2vwr the Transform3D to receive the right transform, or null + */ + public void getImagePlateToVworld(Canvas3D c3d, + Transform3D ip2vwl, Transform3D ip2vwr) { + + CanvasInfo ci = updateCache(c3d, "getImagePlateToVworld", true) ; + getImagePlateToVworld(ci) ; + ip2vwl.set(ci.plateToVworld) ; + if (ci.useStereo && ip2vwr != null) + ip2vwr.set(ci.rightPlateToVworld) ; + } + + private void getImagePlateToVworld(CanvasInfo ci) { + if (ci.updatePlateToVworld) { + if (verbose) System.err.println("updating PlateToVworld") ; + if (ci.plateToVworld == null) + ci.plateToVworld = new Transform3D() ; + + getImagePlateToViewPlatform(ci) ; + ci.plateToVworld.mul + (vpi.viewPlatformToVworld, ci.plateToViewPlatform) ; + + if (ci.useStereo) { + if (ci.rightPlateToVworld == null) + ci.rightPlateToVworld = new Transform3D() ; + + ci.rightPlateToVworld.mul + (vpi.viewPlatformToVworld, ci.rightPlateToViewPlatform) ; + } + ci.updatePlateToVworld = false ; + } + } + + /** + * Gets the current transforms from coexistence coordinates to image plate + * coordinates and copies them into the given Transform3Ds. The default + * coexistence centering enable and window movement policies are + * true and PHYSICAL_WORLD respectively, which + * will center coexistence coordinates to the middle of the canvas, + * aligned with the screen (image plate). A movement policy of + * VIRTUAL_WORLD centers coexistence coordinates to the + * middle of the screen.

+ * + * If coexistence centering is turned off, then canvases and screens can + * have arbitrary positions with respect to coexistence, set through the + * the Screen3D trackerBaseToImagePlate transform and the + * PhysicalEnvironment coexistenceToTrackerBase transform. + * These are calibration constants used for multiple fixed screen displays. + * For head mounted displays the transform is determined by the user head + * position along with calibration parameters found in Screen3D and + * PhysicalBody. (See the source code for the private method + * getEyesHMD for more information).

+ * + * With a monoscopic canvas the image plate transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left image plate transform, and + * if the second argument is non-null it receives the right image plate + * transform. These transforms are always the same unless a head mounted + * display driven by a single stereo canvas is in use.

+ * + * @param c3d the Canvas3D associated with the image plate + * @param coe2ipl the Transform3D to receive the left transform + * @param coe2ipr the Transform3D to receive the right transform, or null + */ + public void getCoexistenceToImagePlate(Canvas3D c3d, + Transform3D coe2ipl, + Transform3D coe2ipr) { + + CanvasInfo ci = updateCache(c3d, "getCoexistenceToImagePlate", false) ; + getCoexistenceToImagePlate(ci) ; + coe2ipl.set(ci.coeToPlate) ; + if (ci.useStereo && coe2ipr != null) + coe2ipr.set(ci.coeToRightPlate) ; + } + + private void getCoexistenceToImagePlate(CanvasInfo ci) { + // + // This method will always set coeToRightPlate even if stereo is not + // in use. This is necessary so that getEyeToImagePlate() can handle + // a monoscopic view policy of CYCLOPEAN_EYE_VIEW (which averages the + // left and right eye positions) when the eyepoints are expressed in + // coexistence coordinates or are derived from the tracked head. + // + if (ci.updateCoeToPlate) { + if (verbose) System.err.println("updating CoeToPlate") ; + if (ci.coeToPlate == null) { + ci.coeToPlate = new Transform3D() ; + ci.coeToRightPlate = new Transform3D() ; + } + if (viewPolicy == View.HMD_VIEW) { + // Head mounted displays have their image plates fixed with + // respect to the head, so get the head position in + // coexistence. + ci.coeToPlate.mul(ci.si.headTrackerToLeftPlate, + coeToHeadTracker) ; + if (ci.useStereo) + // This is the only case in the view model in which the + // right plate transform could be different from the left. + ci.coeToRightPlate.mul(ci.si.headTrackerToRightPlate, + coeToHeadTracker) ; + else + ci.coeToRightPlate.set(ci.coeToPlate) ; + } + else if (coeCentering) { + // The default, for fixed single screen displays with no + // motion tracking. The transform is just a translation. + if (movementPolicy == View.PHYSICAL_WORLD) + // The default. Coexistence is centered in the window. + v3d.set(ci.canvasX + (ci.canvasWidth / 2.0), + ci.canvasY + (ci.canvasHeight / 2.0), 0.0) ; + else + // Coexistence is centered in the screen. + v3d.set(ci.si.screenWidth / 2.0, + ci.si.screenHeight / 2.0, 0.0) ; + + ci.coeToPlate.set(v3d) ; + ci.coeToRightPlate.set(v3d) ; + } + else { + // Coexistence centering should be false for multiple fixed + // screens and/or motion tracking. trackerBaseToImagePlate + // and coexistenceToTrackerBase are used explicitly. + ci.coeToPlate.mul(ci.si.trackerBaseToPlate, coeToTrackerBase) ; + ci.coeToRightPlate.set(ci.coeToPlate) ; + } + ci.updateCoeToPlate = false ; + if (verbose) t3dPrint(ci.coeToPlate, "coeToPlate") ; + } + } + + /** + * Gets the current transform from view platform coordinates to + * coexistence coordinates and copies it into the given transform. View + * platform coordinates are always aligned with coexistence coordinates + * but may differ in scale and in Y and Z offset. The scale is derived + * from the window resize and screen scale policies, while the offset is + * derived from the view attach policy.

+ * + * Java 3D constructs a view from the physical position of the eyes + * relative to the physical positions of the image plates; it then uses a + * view platform to position that physical configuration into the virtual + * world and from there computes the correct projections of the virtual + * world onto the physical image plates. Coexistence coordinates are used + * to place the physical positions of the view platform, eyes, head, image + * plate, sensors, and tracker base in relation to each other. The view + * platform is positioned with respect to the virtual world through the + * scene graph, so the view platform to coexistence transform defines the + * space in which the virtual world and physical world coexist.

+ * + * This method requires a Canvas3D. A different transform may be returned + * for each canvas in the view if any of the following apply:

    + * + *
  • The window resize policy is PHYSICAL_WORLD, which + * alters the scale depending upon the width of the canvas.
  • + * + *

  • The screen scale policy is SCALE_SCREEN_SIZE, + * which alters the scale depending upon the width of the screen + * associated with the canvas.
  • + * + *

  • A window eyepoint policy of RELATIVE_TO_FIELD_OF_VIEW + * with a view attach policy of NOMINAL_HEAD in effect, + * which sets the view platform Z offset in coexistence coordinates + * based on the width of the canvas. These are the default policies. + * The offset also follows the width of the canvas when the + * NOMINAL_FEET view attach policy is used.
+ * + * @param c3d the Canvas3D to use + * @param vp2coe the Transform3D to receive the transform + */ + public void getViewPlatformToCoexistence(Canvas3D c3d, + Transform3D vp2coe) { + + CanvasInfo ci = updateCache + (c3d, "getViewPlatformToCoexistence", false) ; + + getViewPlatformToCoexistence(ci) ; + vp2coe.set(ci.viewPlatformToCoe) ; + } + + private void getViewPlatformToCoexistence(CanvasInfo ci) { + if (!ci.updateViewPlatformToCoe) return ; + if (verbose) System.err.println("updating ViewPlatformToCoe") ; + if (ci.viewPlatformToCoe == null) + ci.viewPlatformToCoe = new Transform3D() ; + // + // The scale from view platform coordinates to coexistence coordinates + // has two components -- the screen scale and the window scale. The + // window scale only applies if the resize policy is PHYSICAL_WORLD. + // + // This scale is not the same as the vworld to view platform scale. + // The latter is contained in the view platform's localToVworld + // transform as defined by the scene graph. The complete scale factor + // from virtual units to physical units is the product of the vworld + // to view platform scale and the view platform to coexistence scale. + // + getScreenScale(ci) ; + if (resizePolicy == View.PHYSICAL_WORLD) + ci.viewPlatformToCoe.setScale(ci.screenScale * ci.windowScale) ; + else + ci.viewPlatformToCoe.setScale(ci.screenScale) ; + + if (viewPolicy == View.HMD_VIEW) { + // In HMD mode view platform coordinates are the same as + // coexistence coordinates, except for scale. + ci.updateViewPlatformToCoe = false ; + return ; + } + + // + // Otherwise, get the offset of the origin of view platform + // coordinates relative to the origin of coexistence. This is is + // specified by two policies: the view platform's view attach policy + // and the physical environment's coexistence center in pworld policy. + // + double eyeOffset ; + double eyeHeight = body.getNominalEyeHeightFromGround() ; + int viewAttachPolicy = view.getViewPlatform().getViewAttachPolicy() ; + int pworldAttachPolicy = env.getCoexistenceCenterInPworldPolicy() ; + + if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) + // The view platform origin is the same as the eye position. + eyeOffset = ci.getFieldOfViewOffset() ; + else + // The view platform origin is independent of the eye position. + eyeOffset = body.getNominalEyeOffsetFromNominalScreen() ; + + if (pworldAttachPolicy == View.NOMINAL_SCREEN) { + // The default. The physical coexistence origin locates the + // nominal screen. This is rarely, if ever, set to anything + // else, and the intended effects of the other settings are + // not well documented. + if (viewAttachPolicy == View.NOMINAL_HEAD) { + // The default. The view platform origin is at the origin + // of the nominal head in coexistence coordinates, offset + // from the screen along +Z. If the window eyepoint + // policy is RELATIVE_TO_FIELD_OF_VIEW, then the eyepoint + // is the same as the view platform origin. + v3d.set(0.0, 0.0, eyeOffset) ; + } + else if (viewAttachPolicy == View.NOMINAL_SCREEN) { + // View platform and coexistence are the same except for + // scale. + v3d.set(0.0, 0.0, 0.0) ; + } + else { + // The view platform origin is at the ground beneath the + // head. + v3d.set(0.0, -eyeHeight, eyeOffset) ; + } + } + else if (pworldAttachPolicy == View.NOMINAL_HEAD) { + // The physical coexistence origin locates the nominal head. + if (viewAttachPolicy == View.NOMINAL_HEAD) { + // The view platform origin is set to the head; + // coexistence and view platform coordinates differ only + // in scale. + v3d.set(0.0, 0.0, 0.0) ; + } + else if (viewAttachPolicy == View.NOMINAL_SCREEN) { + // The view platform is set in front of the head, at the + // nominal screen location. + v3d.set(0.0, 0.0, -eyeOffset) ; + } + else { + // The view platform origin is at the ground beneath the + // head. + v3d.set(0.0, -eyeHeight, 0.0) ; + } + } + else { + // The physical coexistence origin locates the nominal feet. + if (viewAttachPolicy == View.NOMINAL_HEAD) { + v3d.set(0.0, eyeHeight, 0.0) ; + } + else if (viewAttachPolicy == View.NOMINAL_SCREEN) { + v3d.set(0.0, eyeHeight, -eyeOffset) ; + } + else { + v3d.set(0.0, 0.0, 0.0) ; + } + } + + ci.viewPlatformToCoe.setTranslation(v3d) ; + ci.updateViewPlatformToCoe = false ; + if (verbose) t3dPrint(ci.viewPlatformToCoe, "vpToCoe") ; + } + + /** + * Gets the current transform from coexistence coordinates to + * view platform coordinates and copies it into the given transform.

+ * + * This method requires a Canvas3D. The returned transform may differ + * across canvases for the same reasons as discussed in the description of + * getViewPlatformToCoexistence.

+ * + * @param c3d the Canvas3D to use + * @param coe2vp the Transform3D to receive the transform + * @see #getViewPlatformToCoexistence + * getViewPlatformToCoexistence(Canvas3D, Transform3D) + */ + public void getCoexistenceToViewPlatform(Canvas3D c3d, + Transform3D coe2vp) { + + CanvasInfo ci = updateCache + (c3d, "getCoexistenceToViewPlatform", false) ; + + getCoexistenceToViewPlatform(ci) ; + coe2vp.set(ci.coeToViewPlatform) ; + } + + private void getCoexistenceToViewPlatform(CanvasInfo ci) { + if (ci.updateCoeToViewPlatform) { + if (verbose) System.err.println("updating CoeToViewPlatform") ; + if (ci.coeToViewPlatform == null) + ci.coeToViewPlatform = new Transform3D() ; + + getViewPlatformToCoexistence(ci) ; + ci.coeToViewPlatform.invert(ci.viewPlatformToCoe) ; + + ci.updateCoeToViewPlatform = false ; + if (verbose) t3dPrint(ci.coeToViewPlatform, "coeToVp") ; + } + } + + /** + * Gets the current transform from coexistence coordinates to virtual + * world coordinates and copies it into the given transform.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set.

+ * + * This method requires a Canvas3D. The returned transform may differ + * across canvases for the same reasons as discussed in the description of + * getViewPlatformToCoexistence.

+ * + * @param c3d the Canvas3D to use + * @param coe2vw the Transform3D to receive the transform + * @see #getViewPlatformToCoexistence + * getViewPlatformToCoexistence(Canvas3D, Transform3D) + */ + public void getCoexistenceToVworld(Canvas3D c3d, + Transform3D coe2vw) { + + CanvasInfo ci = updateCache(c3d, "getCoexistenceToVworld", true) ; + getCoexistenceToVworld(ci) ; + coe2vw.set(ci.coeToVworld) ; + } + + private void getCoexistenceToVworld(CanvasInfo ci) { + if (ci.updateCoeToVworld) { + if (verbose) System.err.println("updating CoexistenceToVworld") ; + if (ci.coeToVworld == null) ci.coeToVworld = new Transform3D() ; + + getCoexistenceToViewPlatform(ci) ; + ci.coeToVworld.mul(vpi.viewPlatformToVworld, + ci.coeToViewPlatform) ; + + ci.updateCoeToVworld = false ; + } + } + + /** + * Gets the transforms from eye coordinates to image plate coordinates and + * copies them into the Transform3Ds specified.

+ * + * When head tracking is used the eye positions are taken from the head + * position and set in relation to the image plates with each Screen3D's + * trackerBaseToImagePlate transform. Otherwise the window + * eyepoint policy is used to derive the eyepoint relative to the image + * plate. When using a head mounted display the eye position is + * determined solely by calibration constants in Screen3D and + * PhysicalBody; see the source code for the private method + * getEyesHMD for more information.

+ * + * Eye coordinates are always aligned with image plate coordinates, so + * these transforms are always just translations. With a monoscopic + * canvas the eye transform is copied to the first argument and the second + * argument is not used. For a stereo canvas the first argument receives + * the left eye transform, and if the second argument is non-null it + * receives the right eye transform. + * + * @param c3d the Canvas3D associated with the image plate + * @param e2ipl the Transform3D to receive left transform + * @param e2ipr the Transform3D to receive right transform, or null + */ + public void getEyeToImagePlate(Canvas3D c3d, + Transform3D e2ipl, Transform3D e2ipr) { + + CanvasInfo ci = updateCache(c3d, "getEyeToImagePlate", false) ; + getEyeToImagePlate(ci) ; + e2ipl.set(ci.eyeToPlate) ; + if (ci.useStereo && e2ipr != null) + e2ipr.set(ci.rightEyeToPlate) ; + } + + private void getEyeToImagePlate(CanvasInfo ci) { + if (ci.updateEyeInPlate) { + if (verbose) System.err.println("updating EyeInPlate") ; + if (ci.eyeToPlate == null) + ci.eyeToPlate = new Transform3D() ; + + if (viewPolicy == View.HMD_VIEW) { + getEyesHMD(ci) ; + } + else if (useTracking) { + getEyesTracked(ci) ; + } + else { + getEyesFixedScreen(ci) ; + } + ci.updateEyeInPlate = false ; + if (verbose) System.err.println("eyeInPlate: " + ci.eyeInPlate) ; + } + } + + // + // Get physical eye positions for head mounted displays. These are + // determined solely by the headTrackerToImagePlate and headToHeadTracker + // calibration constants defined by Screen3D and the PhysicalBody. + // + // Note that headTrackerLeftToImagePlate and headTrackerToRightImagePlate + // should be set according to the *apparent* position and orientation of + // the image plates, relative to the head and head tracker, as viewed + // through the HMD optics. This is also true of the "physical" screen + // width and height specified by the Screen3D -- they should be the + // *apparent* width and height as viewed through the HMD optics. They + // must be set directly through the Screen3D methods; the default pixel + // metrics of 90 pixels/inch used by Java 3D aren't appropriate for HMD + // optics. + // + // Most HMDs have 100% overlap between the left and right displays; in + // that case, headTrackerToLeftImagePlate and headTrackerToRightImagePlate + // should be identical. The HMD manufacturer's specifications of the + // optics in terms of field of view, image overlap, and distance to the + // focal plane should be used to derive these parameters. + // + private void getEyesHMD(CanvasInfo ci) { + if (ci.useStereo) { + // This case is for head mounted displays driven by a single + // stereo canvas on a single screen. These use a field sequential + // stereo signal to split the left and right images. + leftEye.set(leftEyeInHead) ; + headToHeadTracker.transform(leftEye) ; + ci.si.headTrackerToLeftPlate.transform(leftEye, + ci.eyeInPlate) ; + rightEye.set(rightEyeInHead) ; + headToHeadTracker.transform(rightEye) ; + ci.si.headTrackerToRightPlate.transform(rightEye, + ci.rightEyeInPlate) ; + if (ci.rightEyeToPlate == null) + ci.rightEyeToPlate = new Transform3D() ; + + v3d.set(ci.rightEyeInPlate) ; + ci.rightEyeToPlate.set(v3d) ; + } + else { + // This case is for 2-channel head mounted displays driven by two + // monoscopic screens, one for each eye. + switch (ci.monoscopicPolicy) { + case View.LEFT_EYE_VIEW: + leftEye.set(leftEyeInHead) ; + headToHeadTracker.transform(leftEye) ; + ci.si.headTrackerToLeftPlate.transform(leftEye, + ci.eyeInPlate) ; + break ; + case View.RIGHT_EYE_VIEW: + rightEye.set(rightEyeInHead) ; + headToHeadTracker.transform(rightEye) ; + ci.si.headTrackerToRightPlate.transform(rightEye, + ci.eyeInPlate) ; + break ; + case View.CYCLOPEAN_EYE_VIEW: + default: + throw new IllegalStateException + ("Illegal monoscopic view policy for 2-channel HMD") ; + } + } + v3d.set(ci.eyeInPlate) ; + ci.eyeToPlate.set(v3d) ; + } + + private void getEyesTracked(CanvasInfo ci) { + leftEye.set(leftEyeInHead) ; + rightEye.set(rightEyeInHead) ; + headToTrackerBase.transform(leftEye) ; + headToTrackerBase.transform(rightEye) ; + if (coeCentering) { + // Coexistence and tracker base coordinates are the same. + // Centering is normally turned off for tracking. + getCoexistenceToImagePlate(ci) ; + ci.coeToPlate.transform(leftEye) ; + ci.coeToRightPlate.transform(rightEye) ; + } + else { + // The normal policy for head tracking. + ci.si.trackerBaseToPlate.transform(leftEye) ; + ci.si.trackerBaseToPlate.transform(rightEye) ; + } + setEyeScreenRelative(ci, leftEye, rightEye) ; + } + + private void getEyesFixedScreen(CanvasInfo ci) { + switch (eyePolicy) { + case View.RELATIVE_TO_FIELD_OF_VIEW: + double z = ci.getFieldOfViewOffset() ; + setEyeWindowRelative(ci, z, z) ; + break ; + case View.RELATIVE_TO_WINDOW: + setEyeWindowRelative(ci, + ci.leftManualEyeInPlate.z, + ci.rightManualEyeInPlate.z) ; + break ; + case View.RELATIVE_TO_SCREEN: + setEyeScreenRelative(ci, + ci.leftManualEyeInPlate, + ci.rightManualEyeInPlate) ; + break ; + case View.RELATIVE_TO_COEXISTENCE: + view.getLeftManualEyeInCoexistence(leftEye) ; + view.getRightManualEyeInCoexistence(rightEye) ; + + getCoexistenceToImagePlate(ci) ; + ci.coeToPlate.transform(leftEye) ; + ci.coeToRightPlate.transform(rightEye) ; + setEyeScreenRelative(ci, leftEye, rightEye) ; + break ; + } + } + + private void setEyeWindowRelative(CanvasInfo ci, + double leftZ, double rightZ) { + + // Eye position X is offset from the window center. + double centerX = (ci.canvasX + (ci.canvasWidth / 2.0)) ; + leftEye.x = centerX + leftEyeInHead.x ; + rightEye.x = centerX + rightEyeInHead.x ; + + // Eye position Y is always the canvas center. + leftEye.y = rightEye.y = ci.canvasY + (ci.canvasHeight / 2.0) ; + + // Eye positions Z are as given. + leftEye.z = leftZ ; + rightEye.z = rightZ ; + + setEyeScreenRelative(ci, leftEye, rightEye) ; + } + + private void setEyeScreenRelative(CanvasInfo ci, + Point3d leftEye, Point3d rightEye) { + if (ci.useStereo) { + ci.eyeInPlate.set(leftEye) ; + ci.rightEyeInPlate.set(rightEye) ; + + if (ci.rightEyeToPlate == null) + ci.rightEyeToPlate = new Transform3D() ; + + v3d.set(ci.rightEyeInPlate) ; + ci.rightEyeToPlate.set(v3d) ; + } + else { + switch (ci.monoscopicPolicy) { + case View.CYCLOPEAN_EYE_VIEW: + ci.eyeInPlate.set((leftEye.x + rightEye.x) / 2.0, + (leftEye.y + rightEye.y) / 2.0, + (leftEye.z + rightEye.z) / 2.0) ; + break ; + case View.LEFT_EYE_VIEW: + ci.eyeInPlate.set(leftEye) ; + break ; + case View.RIGHT_EYE_VIEW: + ci.eyeInPlate.set(rightEye) ; + break ; + } + } + v3d.set(ci.eyeInPlate) ; + ci.eyeToPlate.set(v3d) ; + } + + /** + * Gets the current transforms from eye coordinates to view platform + * coordinates and copies them into the given Transform3Ds.

+ * + * With a monoscopic canvas the eye transform is copied to the first + * argument and the second argument is not used. For a stereo canvas the + * first argument receives the left eye transform, and if the second + * argument is non-null it receives the right eye transform.

+ * + * This method requires a Canvas3D. When using a head mounted display, + * head tracking with fixed screens, or a window eyepoint policy of + * RELATIVE_TO_COEXISTENCE, then the transforms returned may + * be different for each canvas if stereo is not in use and they have + * different monoscopic view policies. They may additionally differ in + * scale across canvases with the PHYSICAL_WORLD window + * resize policy or the SCALE_SCREEN_SIZE screen scale + * policy, which alter the scale depending upon the width of the canvas or + * the width of the screen respectively.

+ * + * With window eyepoint policies of RELATIVE_TO_FIELD_OF_VIEW, + * RELATIVE_TO_SCREEN, or RELATIVE_TO_WINDOW, + * then the transforms returned may differ across canvases due to + * the following additional conditions:

    + * + *
  • The window eyepoint policy is RELATIVE_TO_WINDOW or + * RELATIVE_TO_SCREEN, in which case the manual eye + * position in image plate can be set differently for each + * canvas.
  • + * + *

  • The window eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW + * and the view attach policy is NOMINAL_SCREEN, which + * decouples the view platform's canvas Z offset from the eyepoint's + * canvas Z offset.
  • + * + *

  • The eyepoint X and Y coordinates are centered in the canvas with a + * window eyepoint policy of RELATIVE_TO_FIELD_OF_VIEW + * or RELATIVE_TO_WINDOW, and a window movement policy + * of VIRTUAL_WORLD centers the view platform's X and Y + * coordinates to the middle of the screen.
  • + * + *

  • Coexistence centering is set false, which allows each canvas and + * screen to have a different position with respect to coexistence + * coordinates.
+ * + * @param c3d the Canvas3D to use + * @param e2vpl the Transform3D to receive the left transform + * @param e2vpr the Transform3D to receive the right transform, or null + */ + public void getEyeToViewPlatform(Canvas3D c3d, + Transform3D e2vpl, Transform3D e2vpr) { + + CanvasInfo ci = updateCache(c3d, "getEyeToViewPlatform", false) ; + getEyeToViewPlatform(ci) ; + e2vpl.set(ci.eyeToViewPlatform) ; + if (ci.useStereo && e2vpr != null) + e2vpr.set(ci.rightEyeToViewPlatform) ; + } + + private void getEyeToViewPlatform(CanvasInfo ci) { + if (ci.updateEyeToViewPlatform) { + if (verbose) System.err.println("updating EyeToViewPlatform") ; + if (ci.eyeToViewPlatform == null) + ci.eyeToViewPlatform = new Transform3D() ; + + getEyeToImagePlate(ci) ; + getImagePlateToViewPlatform(ci) ; + ci.eyeToViewPlatform.mul(ci.plateToViewPlatform, ci.eyeToPlate) ; + + if (ci.useStereo) { + if (ci.rightEyeToViewPlatform == null) + ci.rightEyeToViewPlatform = new Transform3D() ; + + ci.rightEyeToViewPlatform.mul + (ci.rightPlateToViewPlatform, ci.rightEyeToPlate) ; + } + ci.updateEyeToViewPlatform = false ; + if (verbose) t3dPrint(ci.eyeToViewPlatform, "eyeToVp") ; + } + } + + /** + * Gets the current transforms from view platform coordinates to eye + * coordinates and copies them into the given Transform3Ds.

+ * + * With a monoscopic canvas the eye transform is copied to the first + * argument and the second argument is not used. For a stereo canvas the + * first argument receives the left eye transform, and if the second + * argument is non-null it receives the right eye transform.

+ * + * This method requires a Canvas3D. The transforms returned may differ + * across canvases for all the same reasons discussed in the description + * of getEyeToViewPlatform. + * + * @param c3d the Canvas3D to use + * @param vp2el the Transform3D to receive the left transform + * @param vp2er the Transform3D to receive the right transform, or null + * @see #getEyeToViewPlatform + * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D) + */ + public void getViewPlatformToEye(Canvas3D c3d, + Transform3D vp2el, Transform3D vp2er) { + + CanvasInfo ci = updateCache(c3d, "getViewPlatformToEye", false) ; + getViewPlatformToEye(ci) ; + vp2el.set(ci.viewPlatformToEye) ; + if (ci.useStereo && vp2er != null) + vp2er.set(ci.viewPlatformToRightEye) ; + } + + private void getViewPlatformToEye(CanvasInfo ci) { + if (ci.updateViewPlatformToEye) { + if (verbose) System.err.println("updating ViewPlatformToEye") ; + if (ci.viewPlatformToEye == null) + ci.viewPlatformToEye = new Transform3D() ; + + getEyeToViewPlatform(ci) ; + ci.viewPlatformToEye.invert(ci.eyeToViewPlatform) ; + + if (ci.useStereo) { + if (ci.viewPlatformToRightEye == null) + ci.viewPlatformToRightEye = new Transform3D() ; + + ci.viewPlatformToRightEye.invert(ci.rightEyeToViewPlatform) ; + } + ci.updateViewPlatformToEye = false ; + } + } + + /** + * Gets the current transforms from eye coordinates to virtual world + * coordinates and copies them into the given Transform3Ds.

+ * + * With a monoscopic canvas the eye transform is copied to the first + * argument and the second argument is not used. For a stereo canvas the + * first argument receives the left eye transform, and if the second + * argument is non-null it receives the right eye transform.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set.

+ * + * This method requires a Canvas3D. The transforms returned may differ + * across canvases for all the same reasons discussed in the description + * of getEyeToViewPlatform. + * + * @param c3d the Canvas3D to use + * @param e2vwl the Transform3D to receive the left transform + * @param e2vwr the Transform3D to receive the right transform, or null + * @see #getEyeToViewPlatform + * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D) + */ + public void getEyeToVworld(Canvas3D c3d, + Transform3D e2vwl, Transform3D e2vwr) { + + CanvasInfo ci = updateCache(c3d, "getEyeToVworld", true) ; + getEyeToVworld(ci) ; + e2vwl.set(ci.eyeToVworld) ; + if (ci.useStereo && e2vwr != null) + e2vwr.set(ci.rightEyeToVworld) ; + } + + private void getEyeToVworld(CanvasInfo ci) { + if (ci.updateEyeToVworld) { + if (verbose) System.err.println("updating EyeToVworld") ; + if (ci.eyeToVworld == null) + ci.eyeToVworld = new Transform3D() ; + + getEyeToViewPlatform(ci) ; + ci.eyeToVworld.mul + (vpi.viewPlatformToVworld, ci.eyeToViewPlatform) ; + + if (ci.useStereo) { + if (ci.rightEyeToVworld == null) + ci.rightEyeToVworld = new Transform3D() ; + + ci.rightEyeToVworld.mul + (vpi.viewPlatformToVworld, ci.rightEyeToViewPlatform) ; + } + ci.updateEyeToVworld = false ; + } + } + + /** + * Gets the transforms from eye coordinates to clipping coordinates + * and copies them into the given Transform3Ds. These transforms take + * a viewing volume bounded by the physical canvas edges and the + * physical front and back clip planes and project it into a range + * bound to [-1.0 .. +1.0] on each of the X, Y, and Z axes. If a + * perspective projection has been specified then the physical image + * plate eye location defines the apex of a viewing frustum; + * otherwise, the orientation of the image plate determines the + * direction of a parallel projection.

+ * + * With a monoscopic canvas the projection transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left projection transform, + * and if the second argument is non-null it receives the right + * projection transform.

+ * + * If either of the clip policies VIRTUAL_EYE or + * VIRTUAL_SCREEN are used, then the View should be attached + * to a ViewPlatform that is part of a live scene graph and that has its + * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a + * scale factor of 1.0 will be used for the scale factor from virtual + * world units to view platform units. + * + * @param c3d the Canvas3D to use + * @param e2ccl the Transform3D to receive left transform + * @param e2ccr the Transform3D to receive right transform, or null + */ + public void getProjection(Canvas3D c3d, + Transform3D e2ccl, Transform3D e2ccr) { + + CanvasInfo ci = updateCache(c3d, "getProjection", true) ; + getProjection(ci) ; + e2ccl.set(ci.projection) ; + if (ci.useStereo && e2ccr != null) + e2ccr.set(ci.rightProjection) ; + } + + private void getProjection(CanvasInfo ci) { + if (ci.updateProjection) { + if (verbose) System.err.println("updating Projection") ; + if (ci.projection == null) + ci.projection = new Transform3D() ; + + getEyeToImagePlate(ci) ; + getClipDistances(ci) ; + + // Note: core Java 3D code insists that the back clip plane + // relative to the image plate must be the same left back clip + // distance for both the left and right eye. Not sure why this + // should be, but the same is done here for compatibility. + double backClip = getBackClip(ci, ci.eyeInPlate) ; + computeProjection(ci, ci.eyeInPlate, + getFrontClip(ci, ci.eyeInPlate), + backClip, ci.projection) ; + + if (ci.useStereo) { + if (ci.rightProjection == null) + ci.rightProjection = new Transform3D() ; + + computeProjection(ci, ci.rightEyeInPlate, + getFrontClip(ci, ci.rightEyeInPlate), + backClip, ci.rightProjection) ; + } + ci.updateProjection = false ; + if (verbose) t3dPrint(ci.projection, "projection") ; + } + } + + /** + * Gets the transforms from clipping coordinates to eye coordinates + * and copies them into the given Transform3Ds. These transforms take + * the clip space volume bounded by the range [-1.0 .. + 1.0] on each + * of the X, Y, and Z and project it into eye coordinates.

+ * + * With a monoscopic canvas the projection transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left projection transform, and + * if the second argument is non-null it receives the right projection + * transform.

+ * + * If either of the clip policies VIRTUAL_EYE or + * VIRTUAL_SCREEN are used, then the View should be attached + * to a ViewPlatform that is part of a live scene graph and that has its + * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a + * scale factor of 1.0 will be used for the scale factor from virtual + * world units to view platform units. + * + * @param c3d the Canvas3D to use + * @param cc2el the Transform3D to receive left transform + * @param cc2er the Transform3D to receive right transform, or null + */ + public void getInverseProjection(Canvas3D c3d, + Transform3D cc2el, Transform3D cc2er) { + + CanvasInfo ci = updateCache(c3d, "getInverseProjection", true) ; + getInverseProjection(ci) ; + cc2el.set(ci.inverseProjection) ; + if (ci.useStereo && cc2er != null) + cc2er.set(ci.inverseRightProjection) ; + } + + private void getInverseProjection(CanvasInfo ci) { + if (ci.updateInverseProjection) { + if (verbose) System.err.println("updating InverseProjection") ; + if (ci.inverseProjection == null) + ci.inverseProjection = new Transform3D() ; + + getProjection(ci) ; + ci.inverseProjection.invert(ci.projection) ; + + if (ci.useStereo) { + if (ci.inverseRightProjection == null) + ci.inverseRightProjection = new Transform3D() ; + + ci.inverseRightProjection.invert(ci.rightProjection) ; + } + ci.updateInverseProjection = false ; + } + } + + /** + * Gets the transforms from clipping coordinates to view platform + * coordinates and copies them into the given Transform3Ds. These + * transforms take the clip space volume bounded by the range + * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into + * the view platform coordinate system.

+ * + * With a monoscopic canvas the projection transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left projection transform, and + * if the second argument is non-null it receives the right projection + * transform.

+ * + * If either of the clip policies VIRTUAL_EYE or + * VIRTUAL_SCREEN are used, then the View should be attached + * to a ViewPlatform that is part of a live scene graph and that has its + * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a + * scale factor of 1.0 will be used for the scale factor from virtual + * world units to view platform units. + * + * @param c3d the Canvas3D to use + * @param cc2vpl the Transform3D to receive left transform + * @param cc2vpr the Transform3D to receive right transform, or null + */ + public void getInverseViewPlatformProjection(Canvas3D c3d, + Transform3D cc2vpl, + Transform3D cc2vpr) { + + CanvasInfo ci = updateCache + (c3d, "getInverseViewPlatformProjection", true) ; + + getInverseViewPlatformProjection(ci) ; + cc2vpl.set(ci.inverseViewPlatformProjection) ; + if (ci.useStereo & cc2vpr != null) + cc2vpr.set(ci.inverseViewPlatformRightProjection) ; + } + + private void getInverseViewPlatformProjection(CanvasInfo ci) { + if (ci.updateInverseViewPlatformProjection) { + if (verbose) System.err.println("updating InverseVpProjection") ; + if (ci.inverseViewPlatformProjection == null) + ci.inverseViewPlatformProjection = new Transform3D() ; + + getInverseProjection(ci) ; + getEyeToViewPlatform(ci) ; + ci.inverseViewPlatformProjection.mul + (ci.eyeToViewPlatform, ci.inverseProjection) ; + + if (ci.useStereo) { + if (ci.inverseViewPlatformRightProjection == null) + ci.inverseViewPlatformRightProjection = new Transform3D() ; + + ci.inverseViewPlatformRightProjection.mul + (ci.rightEyeToViewPlatform, ci.inverseRightProjection) ; + } + ci.updateInverseVworldProjection = false ; + } + } + + /** + * Gets the transforms from clipping coordinates to virtual world + * coordinates and copies them into the given Transform3Ds. These + * transforms take the clip space volume bounded by the range + * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into + * the virtual world.

+ * + * With a monoscopic canvas the projection transform is copied to the + * first argument and the second argument is not used. For a stereo + * canvas the first argument receives the left projection transform, and + * if the second argument is non-null it receives the right projection + * transform.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set. + * + * @param c3d the Canvas3D to use + * @param cc2vwl the Transform3D to receive left transform + * @param cc2vwr the Transform3D to receive right transform, or null + */ + public void getInverseVworldProjection(Canvas3D c3d, + Transform3D cc2vwl, + Transform3D cc2vwr) { + + CanvasInfo ci = updateCache(c3d, "getInverseVworldProjection", true) ; + getInverseVworldProjection(ci) ; + cc2vwl.set(ci.inverseVworldProjection) ; + if (ci.useStereo & cc2vwr != null) + cc2vwr.set(ci.inverseVworldRightProjection) ; + } + + private void getInverseVworldProjection(CanvasInfo ci) { + if (ci.updateInverseVworldProjection) { + if (verbose) System.err.println("updating InverseVwProjection") ; + if (ci.inverseVworldProjection == null) + ci.inverseVworldProjection = new Transform3D() ; + + getInverseViewPlatformProjection(ci) ; + ci.inverseVworldProjection.mul + (vpi.viewPlatformToVworld, ci.inverseViewPlatformProjection) ; + + if (ci.useStereo) { + if (ci.inverseVworldRightProjection == null) + ci.inverseVworldRightProjection = new Transform3D() ; + + ci.inverseVworldRightProjection.mul + (vpi.viewPlatformToVworld, + ci.inverseViewPlatformRightProjection) ; + } + ci.updateInverseVworldProjection = false ; + } + } + + // + // Compute a projection matrix from the given eye position in image plate, + // the front and back clip Z positions in image plate, and the current + // canvas position in image plate. + // + private void computeProjection(CanvasInfo ci, Point3d eye, + double front, double back, Transform3D p) { + + // Convert everything to eye coordinates. + double lx = ci.canvasX - eye.x ; // left (low x) + double ly = ci.canvasY - eye.y ; // bottom (low y) + double hx = (ci.canvasX+ci.canvasWidth) - eye.x ; // right (high x) + double hy = (ci.canvasY+ci.canvasHeight) - eye.y ; // top (high y) + double nz = front - eye.z ; // front (near z) + double fz = back - eye.z ; // back (far z) + double iz = -eye.z ; // plate (image z) + + if (projectionPolicy == View.PERSPECTIVE_PROJECTION) + computePerspectiveProjection(lx, ly, hx, hy, iz, nz, fz, m16d) ; + else + computeParallelProjection(lx, ly, hx, hy, nz, fz, m16d) ; + + p.set(m16d) ; + } + + // + // Compute a perspective projection from the given eye-space bounds. + // + private void computePerspectiveProjection(double lx, double ly, + double hx, double hy, + double iz, double nz, + double fz, double[] m) { + // + // We first derive the X and Y projection components without regard + // for Z scaling. The Z scaling or perspective depth is handled by + // matrix elements expressed solely in terms of the near and far clip + // planes. + // + // Since the eye is at the origin, the projector for any point V in + // eye space is just V. Any point along this ray can be expressed in + // parametric form as P = tV. To find the projection onto the plane + // containing the canvas, find t such that P.z = iz; ie, t = iz/V.z. + // The projection P is thus [V.x*iz/V.z, V.y*iz/V.z, iz]. + // + // This projection can expressed as the following matrix equation: + // + // -iz 0 0 0 V.x + // 0 -iz 0 0 X V.y + // 0 0 -iz 0 V.z + // 0 0 -1 0 1 {matrix 1} + // + // where the matrix elements have been negated so that w is positive. + // This is mostly by convention, although some hardware won't handle + // clipping in the -w half-space. + // + // After the point has been projected to the image plate, the + // canvas bounds need to be mapped to the [-1..1] of Java 3D's + // clipping space. The scale factor for X is thus 2/(hx - lx); adding + // the translation results in (V.x - lx)(2/(hx - lx)) - 1, which after + // some algebra can be confirmed to the same as the following + // canonical scale/offset form: + // + // V.x*2/(hx - lx) - (hx + lx)/(hx - lx) + // + // Similarly for Y: + // + // V.y*2/(hy - ly) - (hy + ly)/(hy - ly) + // + // If we set idx = 1/(hx - lx) and idy = 1/(hy - ly), then we get: + // + // 2*V.x*idx - (hx + lx)idx + // 2*V.y*idy - (hy + ly)idy + // + // These scales and offsets are represented by the following matrix: + // + // 2*idx 0 0 -(hx + lx)*idx + // 0 2*idy 0 -(hy + ly)*idy + // 0 0 1 0 + // 0 0 0 1 {matrix 2} + // + // The result after concatenating the projection transform + // ({matrix 2} X {matrix 1}): + // + // -2*iz*idx 0 (hx + lx)*idx 0 + // 0 -2*iz*idy (hy + ly)*idy 0 + // 0 0 -iz {a} 0 {b} + // 0 0 -1 0 {matrix 3} + // + // The Z scaling is handled by m[10] ("a") and m[11] ("b"), which must + // map the range [front..back] to [1..-1] in clipping space. If ze is + // the Z coordinate in eye space, and zc is the Z coordinate in + // clipping space after division by w, then from {matrix 3}: + // + // zc = (a*ze + b)/-ze = -(a + b/ze) + // + // We want this to map to +1 when ze is at the near clip plane, and + // to -1 when ze is at the far clip plane: + // + // -(a + b/nz) = +1 + // -(a + b/fz) = -1 + // + // Solving results in: + // + // a = -(nz + fz)/(nz - fz) + // b = (2*nz*fz)/(nz - fz). + // + // NOTE: this produces a perspective transform that has matrix + // components with a different scale than the matrix computed by the + // Java 3D core. They do in fact effect the equivalent clipping in 4D + // homogeneous coordinates and project to the same 3D Euclidean + // coordinates. m[14] is always -1 in our derivation above. If the + // matrix components produced by Java 3D core are divided by its value + // of -m[14], then both matrices are the same. + // + double idx = 1.0 / (hx - lx) ; + double idy = 1.0 / (hy - ly) ; + double idz = 1.0 / (nz - fz) ; + + m[0] = -2.0 * iz * idx ; + m[5] = -2.0 * iz * idy ; + m[2] = (hx + lx) * idx ; + m[6] = (hy + ly) * idy ; + m[10] = -(nz + fz) * idz ; + m[11] = 2.0 * fz * nz * idz ; + m[14] = -1.0 ; + m[1] = m[3] = m[4] = m[7] = m[8] = m[9] = m[12] = m[13] = m[15] = 0.0 ; + } + + // + // Compute a parallel projection from the given eye-space bounds. + // + private void computeParallelProjection(double lx, double ly, + double hx, double hy, + double nz, double fz, double[] m) { + // + // A parallel projection in eye space just involves scales and offsets + // with no w division. We can use {matrix 2} for the X and Y scales + // and offsets and then use a linear mapping of the front and back + // clip distances to the [1..-1] Z clip range. + // + double idx = 1.0 / (hx - lx) ; + double idy = 1.0 / (hy - ly) ; + double idz = 1.0 / (nz - fz) ; + + m[0] = 2.0 * idx ; + m[5] = 2.0 * idy ; + m[10] = 2.0 * idz ; + m[3] = -(hx + lx) * idx ; + m[7] = -(hy + ly) * idy ; + m[11] = -(nz + fz) * idz ; + m[15] = 1.0 ; + m[1] = m[2] = m[4] = m[6] = m[8] = m[9] = m[12] = m[13] = m[14] = 0.0 ; + } + + // + // Get front clip plane Z coordinate in image plate space. + // + private double getFrontClip(CanvasInfo ci, Point3d eye) { + if (frontClipPolicy == View.PHYSICAL_EYE || + frontClipPolicy == View.VIRTUAL_EYE) { + return eye.z - ci.frontClipDistance ; + } + else { + return - ci.frontClipDistance ; + } + } + + // + // Get back clip plane Z coordinate in image plate space. + // + private double getBackClip(CanvasInfo ci, Point3d eye) { + // + // Note: Clip node status is unavailable here. If a clip node is + // active in the scene graph, it should override the view's back + // clip plane. + // + if (backClipPolicy == View.PHYSICAL_EYE || + backClipPolicy == View.VIRTUAL_EYE) { + return eye.z - ci.backClipDistance ; + } + else { + return -ci.backClipDistance ; + } + } + + // + // Compute clip distance scale. + // + private double getClipScale(CanvasInfo ci, int clipPolicy) { + if (clipPolicy == View.VIRTUAL_EYE || + clipPolicy == View.VIRTUAL_SCREEN) { + getScreenScale(ci) ; + if (resizePolicy == View.PHYSICAL_WORLD) + return vpi.vworldToViewPlatformScale * ci.screenScale * + ci.windowScale ; + else + return vpi.vworldToViewPlatformScale * ci.screenScale ; + } + else { + if (resizePolicy == View.PHYSICAL_WORLD) + return ci.windowScale ; // see below + else + return 1.0 ; + } + } + + /** + * Gets the front clip distance scaled to physical meters. This is useful + * for ensuring that objects positioned relative to a physical coordinate + * system (such as eye, image plate, or coexistence) will be within the + * viewable Z depth. This distance will be relative to either the eye or + * the image plate depending upon the front clip policy.

+ * + * Note that this is not necessarily the clip distance as set by + * setFrontClipDistance, even when the front clip policy + * is PHYSICAL_SCREEN or PHYSICAL_EYE. If + * the window resize policy is PHYSICAL_WORLD, then physical + * clip distances as specified are in fact scaled by the ratio of the + * window width to the screen width. The Java 3D view model does this + * to prevent the physical clip planes from moving with respect to the + * virtual world when the window is resized.

+ * + * If either of the clip policies VIRTUAL_EYE or + * VIRTUAL_SCREEN are used, then the View should be attached + * to a ViewPlatform that is part of a live scene graph and that has its + * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a + * scale factor of 1.0 will be used for the scale factor from virtual + * world units to view platform units. + * + * @param c3d the Canvas3D to use + * @return the physical front clip distance + */ + public double getPhysicalFrontClipDistance(Canvas3D c3d) { + CanvasInfo ci = updateCache + (c3d, "getPhysicalFrontClipDistance", true) ; + + getClipDistances(ci) ; + return ci.frontClipDistance ; + } + + /** + * Gets the back clip distance scaled to physical meters. This is useful + * for ensuring that objects positioned relative to a physical coordinate + * system (such as eye, image plate, or coexistence) will be within the + * viewable Z depth. This distance will be relative to either the eye or + * the image plate depending upon the back clip policy.

+ * + * Note that this is not necessarily the clip distance as set by + * setBackClipDistance, even when the back clip policy + * is PHYSICAL_SCREEN or PHYSICAL_EYE. If + * the window resize policy is PHYSICAL_WORLD, then physical + * clip distances as specified are in fact scaled by the ratio of the + * window width to the screen width. The Java 3D view model does this + * to prevent the physical clip planes from moving with respect to the + * virtual world when the window is resized.

+ * + * If either of the clip policies VIRTUAL_EYE or + * VIRTUAL_SCREEN are used, then the View should be attached + * to a ViewPlatform that is part of a live scene graph and that has its + * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a + * scale factor of 1.0 will be used for the scale factor from virtual + * world units to view platform units. + * + * @param c3d the Canvas3D to use + * @return the physical back clip distance + */ + public double getPhysicalBackClipDistance(Canvas3D c3d) { + CanvasInfo ci = updateCache(c3d, "getPhysicalBackClipDistance", true) ; + getClipDistances(ci) ; + return ci.backClipDistance ; + } + + private void getClipDistances(CanvasInfo ci) { + if (ci.updateClipDistances) { + if (verbose) System.err.println("updating clip distances") ; + + ci.frontClipDistance = view.getFrontClipDistance() * + getClipScale(ci, frontClipPolicy) ; + + ci.backClipDistance = view.getBackClipDistance() * + getClipScale(ci, backClipPolicy) ; + + ci.updateClipDistances = false ; + if (verbose) { + System.err.println + (" front clip distance " + ci.frontClipDistance) ; + System.err.println + (" back clip distance " + ci.backClipDistance) ; + } + } + } + + private void getScreenScale(CanvasInfo ci) { + if (ci.updateScreenScale) { + if (verbose) System.err.println("updating screen scale") ; + + if (scalePolicy == View.SCALE_SCREEN_SIZE) + ci.screenScale = ci.si.screenWidth / 2.0 ; + else + ci.screenScale = view.getScreenScale() ; + + ci.updateScreenScale = false ; + if (verbose) System.err.println("screen scale " + ci.screenScale) ; + } + } + + /** + * Gets the scale factor from physical meters to view platform units.

+ * + * This method requires a Canvas3D. A different scale may be returned + * for each canvas in the view if any of the following apply:

    + * + *
  • The window resize policy is PHYSICAL_WORLD, which + * alters the scale depending upon the width of the canvas.
  • + * + *

  • The screen scale policy is SCALE_SCREEN_SIZE, + * which alters the scale depending upon the width of the screen + * associated with the canvas.
+ * + * @param c3d the Canvas3D to use + * @return the physical to view platform scale + */ + public double getPhysicalToViewPlatformScale(Canvas3D c3d) { + CanvasInfo ci = updateCache + (c3d, "getPhysicalToViewPlatformScale", false) ; + + getPhysicalToViewPlatformScale(ci) ; + return ci.physicalToVpScale ; + } + + private void getPhysicalToViewPlatformScale(CanvasInfo ci) { + if (ci.updatePhysicalToVpScale) { + if (verbose) System.err.println("updating PhysicalToVp scale") ; + + getScreenScale(ci) ; + if (resizePolicy == View.PHYSICAL_WORLD) + ci.physicalToVpScale = 1.0/(ci.screenScale * ci.windowScale) ; + else + ci.physicalToVpScale = 1.0/ci.screenScale ; + + ci.updatePhysicalToVpScale = false ; + if (verbose) System.err.println("PhysicalToVp scale " + + ci.physicalToVpScale) ; + } + } + + /** + * Gets the scale factor from physical meters to virtual units.

+ * + * This method requires a Canvas3D. A different scale may be returned + * across canvases for the same reasons as discussed in the description of + * getPhysicalToViewPlatformScale.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set. + * + * @param c3d the Canvas3D to use + * @return the physical to virtual scale + * @see #getPhysicalToViewPlatformScale + * getPhysicalToViewPlatformScale(Canvas3D) + */ + public double getPhysicalToVirtualScale(Canvas3D c3d) { + CanvasInfo ci = updateCache(c3d, "getPhysicalToVirtualScale", true) ; + getPhysicalToVirtualScale(ci) ; + return ci.physicalToVirtualScale ; + } + + private void getPhysicalToVirtualScale(CanvasInfo ci) { + if (ci.updatePhysicalToVirtualScale) { + if (verbose) + System.err.println("updating PhysicalToVirtual scale") ; + + getPhysicalToViewPlatformScale(ci) ; + ci.physicalToVirtualScale = + ci.physicalToVpScale / vpi.vworldToViewPlatformScale ; + + ci.updatePhysicalToVirtualScale = false ; + if (verbose) System.err.println("PhysicalToVirtual scale " + + ci.physicalToVirtualScale) ; + } + } + + /** + * Gets the width of the specified canvas scaled to physical meters. This + * is derived from the physical screen width as reported by the Screen3D + * associated with the canvas. If the screen width is not explicitly set + * using the setPhysicalScreenWidth method of Screen3D, then + * Java 3D will derive the screen width based on a screen resolution of 90 + * pixels/inch. + * + * @param c3d the Canvas3D to use + * @return the width of the canvas scaled to physical meters + */ + public double getPhysicalWidth(Canvas3D c3d) { + CanvasInfo ci = updateCache(c3d, "getPhysicalWidth", false) ; + return ci.canvasWidth ; + } + + /** + * Gets the height of the specified canvas scaled to physical meters. This + * is derived from the physical screen height as reported by the Screen3D + * associated with the canvas. If the screen height is not explicitly set + * using the setPhysicalScreenHeight method of Screen3D, then + * Java 3D will derive the screen height based on a screen resolution of 90 + * pixels/inch. + * + * @param c3d the Canvas3D to use + * @return the height of the canvas scaled to physical meters + */ + public double getPhysicalHeight(Canvas3D c3d) { + CanvasInfo ci = updateCache(c3d, "getPhysicalHeight", false) ; + return ci.canvasHeight ; + } + + /** + * Gets the location of the specified canvas relative to the image plate + * origin. This is derived from the physical screen parameters as + * reported by the Screen3D associated with the canvas. If the screen + * width and height are not explicitly set in Screen3D, then Java 3D will + * derive those screen parameters based on a screen resolution of 90 + * pixels/inch. + * + * @param c3d the Canvas3D to use + * @param location the output position, in meters, of the lower-left + * corner of the canvas relative to the image plate lower-left corner; Z + * is always 0.0 + */ + public void getPhysicalLocation(Canvas3D c3d, Point3d location) { + CanvasInfo ci = updateCache(c3d, "getPhysicalLocation", false) ; + location.set(ci.canvasX, ci.canvasY, 0.0) ; + } + + /** + * Gets the location of the AWT pixel value and copies it into the + * specified Point3d. + * + * @param c3d the Canvas3D to use + * @param x the X coordinate of the pixel relative to the upper-left + * corner of the canvas + * @param y the Y coordinate of the pixel relative to the upper-left + * corner of the canvas + * @param location the output position, in meters, relative to the + * lower-left corner of the image plate; Z is always 0.0 + */ + public void getPixelLocationInImagePlate(Canvas3D c3d, int x, int y, + Point3d location) { + + CanvasInfo ci = updateCache + (c3d, "getPixelLocationInImagePlate", false) ; + + location.set(ci.canvasX + ((double)x * ci.si.metersPerPixelX), + ci.canvasY - ((double)y * ci.si.metersPerPixelY) + + ci.canvasHeight, 0.0) ; + } + + /** + * Gets a read from the specified sensor and transforms it to virtual + * world coordinates. The View must be attached to a ViewPlatform which + * is part of a live scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set.

+ * + * This method requires a Canvas3D. The returned transform may differ + * across canvases for the same reasons as discussed in the description of + * getViewPlatformToCoexistence. + * + * @param sensor the Sensor instance to read + * @param s2vw the output transform + * @see #getViewPlatformToCoexistence + * getViewPlatformToCoexistence(Canvas3D, Transform3D) + */ + public void getSensorToVworld(Canvas3D c3d, + Sensor sensor, Transform3D s2vw) { + + CanvasInfo ci = updateCache(c3d, "getSensorToVworld", true) ; + getTrackerBaseToVworld(ci) ; + sensor.getRead(s2vw) ; + s2vw.mul(ci.trackerBaseToVworld, s2vw) ; + } + + /** + * Gets the transform from tracker base coordinates to view platform + * coordinates and copies it into the specified Transform3D.

+ * + * This method requires a Canvas3D. The returned transform may differ + * across canvases for the same reasons as discussed in the description of + * getViewPlatformToCoexistence. + * + * @param c3d the Canvas3D to use + * @param tb2vp the output transform + * @see #getViewPlatformToCoexistence + * getViewPlatformToCoexistence(Canvas3D, Transform3D) + */ + public void getTrackerBaseToViewPlatform(Canvas3D c3d, Transform3D tb2vp) { + CanvasInfo ci = updateCache + (c3d, "getTrackerBaseToViewPlatform", false) ; + + getTrackerBaseToViewPlatform(ci) ; + tb2vp.set(ci.trackerBaseToViewPlatform) ; + } + + private void getTrackerBaseToViewPlatform(CanvasInfo ci) { + if (ci.updateTrackerBaseToViewPlatform) { + if (verbose) System.err.println("updating TrackerBaseToVp") ; + if (ci.trackerBaseToViewPlatform == null) + ci.trackerBaseToViewPlatform = new Transform3D() ; + + getViewPlatformToCoexistence(ci) ; + ci.trackerBaseToViewPlatform.mul(coeToTrackerBase, + ci.viewPlatformToCoe) ; + + ci.trackerBaseToViewPlatform.invert() ; + ci.updateTrackerBaseToViewPlatform = false ; + if (verbose) t3dPrint(ci.trackerBaseToViewPlatform, + "TrackerBaseToViewPlatform") ; + } + } + + /** + * Gets the transform from tracker base coordinates to virtual world + * coordinates and copies it into the specified Transform3D. The View + * must be attached to a ViewPlatform which is part of a live scene graph, + * and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set.

+ * + * This method requires a Canvas3D. The returned transform may differ + * across canvases for the same reasons as discussed in the description of + * getViewPlatformToCoexistence. + * + * @param c3d the Canvas3D to use + * @param tb2vw the output transform + * @see #getViewPlatformToCoexistence + * getViewPlatformToCoexistence(Canvas3D, Transform3D) + */ + public void getTrackerBaseToVworld(Canvas3D c3d, Transform3D tb2vw) { + CanvasInfo ci = updateCache(c3d, "getTrackerBaseToVworld", true) ; + getTrackerBaseToVworld(ci) ; + tb2vw.set(ci.trackerBaseToVworld) ; + } + + private void getTrackerBaseToVworld(CanvasInfo ci) { + if (ci.updateTrackerBaseToVworld) { + if (verbose) System.err.println("updating TrackerBaseToVworld") ; + if (ci.trackerBaseToVworld == null) + ci.trackerBaseToVworld = new Transform3D() ; + // + // We compute trackerBaseToViewPlatform and compose with + // viewPlatformToVworld instead of computing imagePlateToVworld + // and composing with trackerBaseToImagePlate. That way it works + // with HMD and avoids the issue of choosing the left image plate + // or right image plate transform. + // + getTrackerBaseToViewPlatform(ci) ; + ci.trackerBaseToVworld.mul(vpi.viewPlatformToVworld, + ci.trackerBaseToViewPlatform) ; + + ci.updateTrackerBaseToVworld = false ; + } + } + + /** + * Release all static memory references held by ViewInfo, if any. These + * are the Screen3D and ViewPlatform maps shared by all existing ViewInfo + * instances if they're not provided by a constructor. Releasing the + * screen references effectively releases all canvas references in all + * ViewInfo instances as well.

+ * + * It is safe to continue using existing ViewInfo instances after calling + * this method; the data in the released maps will be re-derived as + * needed. + */ + public static synchronized void clear() { + Iterator i = staticVpMap.values().iterator() ; + while (i.hasNext()) ((ViewPlatformInfo)i.next()).clear() ; + staticVpMap.clear() ; + + i = staticSiMap.values().iterator() ; + while (i.hasNext()) ((ScreenInfo)i.next()).clear() ; + staticSiMap.clear() ; + } + + /** + * Arrange for an update of cached screen parameters. If automatic update + * has not been enabled, then this method should be called if any of the + * attributes of the Screen3D have changed. This method should also be + * called if the screen changes pixel resolution. + * + * @param s3d the Screen3D to update + */ + public void updateScreen(Screen3D s3d) { + if (verbose) System.err.println("updateScreen") ; + ScreenInfo si = (ScreenInfo)screenMap.get(s3d) ; + if (si != null) si.updateScreen = true ; + } + + /** + * Arrange for an update of cached canvas parameters. If automatic update + * has not been enabled, then this method should be called if any of the + * attributes of the Canvas3D have changed. These attributes include the + * canvas position and size, but do not include the attributes of + * the associated Screen3D, which are cached separately. + * + * @param c3d the Canvas3D to update + */ + public void updateCanvas(Canvas3D c3d) { + if (verbose) System.err.println("updateCanvas") ; + CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; + if (ci != null) ci.updateCanvas = true ; + } + + /** + * Arrange for an update of cached view parameters. If automatic update + * has not been enabled for the View, then this method should be called if + * any of the attributes of the View associated with this object have + * changed.

+ * + * These do not include the attributes of the existing Canvas3D or + * Screen3D components of the View, but do include the attributes of all + * other components such as the PhysicalEnvironment and PhysicalBody, and + * all attributes of the attached ViewPlatform except for its + * localToVworld transform. The screen and canvas components + * as well as the ViewPlatform's localToVworld are cached + * separately.

+ * + * This method should also be called if the ViewPlatform is replaced with + * another using the View's attachViewPlatform method, or if + * any of the setCanvas3D, addCanvas3D, + * insertCanvas3D, removeCanvas3D, or + * removeAllCanvas3Ds methods of View are called to change + * the View's canvas list.

+ * + * Calling this method causes most transforms to be re-derived. It should + * be used only when necessary. + */ + public void updateView() { + if (verbose) System.err.println("updateView") ; + this.updateView = true ; + } + + /** + * Arrange for an update of the cached head position if head tracking is + * enabled. If automatic update has not enabled for the head position, + * then this method should be called anytime a new head position is to be + * read. + */ + public void updateHead() { + if (verbose) System.err.println("updateHead") ; + this.updateHead = true ; + } + + /** + * Arrange for an update of the cached localToVworld + * transform of the view platform. If automatic update has not been + * enabled for this transform, then this method should be called anytime + * the view platform has been repositioned in the virtual world and a + * transform involving virtual world coordinates is desired.

+ * + * The View must be attached to a ViewPlatform which is part of a live + * scene graph, and the ViewPlatform node must have its + * ALLOW_LOCAL_TO_VWORLD_READ capability set. + */ + public void updateViewPlatform() { + if (verbose) System.err.println("updateViewPlatform") ; + vpi.updateViewPlatformToVworld = true ; + } + + // + // Set cache update bits based on auto update flags. + // VIEW_AUTO_UPDATE is handled in updateCache(). + // + private void getAutoUpdate(CanvasInfo ci) { + if ((autoUpdateFlags & SCREEN_AUTO_UPDATE) != 0) + ci.si.updateScreen = true ; + + if ((autoUpdateFlags & CANVAS_AUTO_UPDATE) != 0) + ci.updateCanvas = true ; + + if ((autoUpdateFlags & PLATFORM_AUTO_UPDATE) != 0) + vpi.updateViewPlatformToVworld = true ; + + if ((autoUpdateFlags & HEAD_AUTO_UPDATE) != 0) + this.updateHead = true ; + } + + // + // Update any changed cached data. This takes a Canvas3D instance. The + // cache mechanism could have used a Canvas3D index into the View instead, + // but the direct reference is probably more convenient for applications. + // + private CanvasInfo updateCache(Canvas3D c3d, String name, boolean vworld) { + if (verbose) { + System.err.println("updateCache: " + name + " in " + hashCode()) ; + System.err.println(" canvas " + c3d.hashCode()) ; + } + + // The View may have had Canvas3D instances added or removed, or may + // have been attached to a different ViewPlatform, so update the view + // before anything else. + if (updateView || (autoUpdateFlags & VIEW_AUTO_UPDATE) != 0) + getViewInfo() ; + + // Now get the CanvasInfo to update. + CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; + if (ci == null) + throw new IllegalArgumentException + ("\nSpecified Canvas3D is not a component of the View") ; + + // Check rest of autoUpdateFlags. + if (autoUpdate) getAutoUpdate(ci) ; + + // Update the screen, canvas, view platform, and head caches. + if (ci.si.updateScreen) + ci.si.getScreenInfo() ; + + if (ci.updateCanvas) + ci.getCanvasInfo() ; + + if (vworld && vpi.updateViewPlatformToVworld) + vpi.getViewPlatformToVworld() ; + + if (useTracking && updateHead) + getHeadInfo() ; + + // Return the CanvasInfo instance. + return ci ; + } + + // + // Get physical view parameters and derived data. This is a fairly + // heavyweight method -- everything gets marked for update since we don't + // currently track changes in individual view attributes. Fortunately + // there shouldn't be a need to call it very often. + // + private void getViewInfo() { + if (verbose) System.err.println(" getViewInfo") ; + + // Check if an update of the Canvas3D collection is needed. + if (this.canvasCount != view.numCanvas3Ds()) { + this.canvasCount = view.numCanvas3Ds() ; + getCanvases() ; + } + else { + for (int i = 0 ; i < canvasCount ; i++) { + if (canvasMap.get(view.getCanvas3D(i)) != canvasInfo[i]) { + getCanvases() ; + break ; + } + } + } + + // Update the ViewPlatform. + getViewPlatform() ; + + // Update the PhysicalBody and PhysicalEnvironment. + this.body = view.getPhysicalBody() ; + this.env = view.getPhysicalEnvironment() ; + + // Use the result of the possibly overridden method useHeadTracking() + // to determine if head tracking is to be used within ViewInfo. + this.useTracking = useHeadTracking() ; + + // Get the head tracker only if really available. + if (view.getTrackingEnable() && env.getTrackingAvailable()) { + int headIndex = env.getHeadIndex() ; + this.headTracker = env.getSensor(headIndex) ; + } + + // Get the new policies and update data derived from them. + this.viewPolicy = view.getViewPolicy() ; + this.projectionPolicy = view.getProjectionPolicy() ; + this.resizePolicy = view.getWindowResizePolicy() ; + this.movementPolicy = view.getWindowMovementPolicy() ; + this.eyePolicy = view.getWindowEyepointPolicy() ; + this.scalePolicy = view.getScreenScalePolicy() ; + this.backClipPolicy = view.getBackClipPolicy() ; + this.frontClipPolicy = view.getFrontClipPolicy() ; + + if (useTracking || viewPolicy == View.HMD_VIEW) { + if (this.headToHeadTracker == null) + this.headToHeadTracker = new Transform3D() ; + if (this.headTrackerToTrackerBase == null) + this.headTrackerToTrackerBase = new Transform3D() ; + + if (viewPolicy == View.HMD_VIEW) { + if (this.trackerBaseToHeadTracker == null) + this.trackerBaseToHeadTracker = new Transform3D() ; + if (this.coeToHeadTracker == null) + this.coeToHeadTracker = new Transform3D() ; + } + else { + if (this.headToTrackerBase == null) + this.headToTrackerBase = new Transform3D() ; + } + + body.getLeftEyePosition(this.leftEyeInHead) ; + body.getRightEyePosition(this.rightEyeInHead) ; + body.getHeadToHeadTracker(this.headToHeadTracker) ; + + if (verbose) { + System.err.println(" leftEyeInHead " + leftEyeInHead) ; + System.err.println(" rightEyeInHead " + rightEyeInHead) ; + t3dPrint(headToHeadTracker, " headToHeadTracker") ; + } + } + + if (eyePolicy == View.RELATIVE_TO_WINDOW || + eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { + body.getLeftEyePosition(this.leftEyeInHead) ; + body.getRightEyePosition(this.rightEyeInHead) ; + if (verbose) { + System.err.println(" leftEyeInHead " + leftEyeInHead) ; + System.err.println(" rightEyeInHead " + rightEyeInHead) ; + } + } + + if ((env.getCoexistenceCenterInPworldPolicy() != + View.NOMINAL_SCREEN) || (viewPolicy == View.HMD_VIEW)) + this.coeCentering = false ; + else + this.coeCentering = view.getCoexistenceCenteringEnable() ; + + if (!coeCentering || useTracking) { + if (this.coeToTrackerBase == null) + this.coeToTrackerBase = new Transform3D() ; + + env.getCoexistenceToTrackerBase(this.coeToTrackerBase) ; + if (verbose) t3dPrint(coeToTrackerBase, " coeToTrackerBase") ; + } + + if (backClipPolicy == View.VIRTUAL_EYE || + backClipPolicy == View.VIRTUAL_SCREEN || + frontClipPolicy == View.VIRTUAL_EYE || + frontClipPolicy == View.VIRTUAL_SCREEN) { + this.clipVirtual = true ; + } + else { + this.clipVirtual = false ; + } + + // Propagate view updates to each canvas. + for (int i = 0 ; i < canvasCount ; i++) + this.canvasInfo[i].updateViewDependencies() ; + + this.updateView = false ; + if (verbose) { + System.err.println(" tracking " + useTracking) ; + System.err.println(" coeCentering " + coeCentering) ; + System.err.println(" clipVirtual " + clipVirtual) ; + } + } + + // + // Each view can have multiple canvases, each with an associated screen. + // Each canvas is associated with only one view. Each screen can have + // multiple canvases that are used across multiple views. We rebuild the + // canvas info instead of trying to figure out what canvases have been + // added or removed from the view. + // + private void getCanvases() { + if (this.canvasInfo.length < canvasCount) { + this.canvasInfo = new CanvasInfo[canvasCount] ; + } + + for (int i = 0 ; i < canvasCount ; i++) { + Canvas3D c3d = view.getCanvas3D(i) ; + Screen3D s3d = c3d.getScreen3D() ; + + // Check if we have a new screen. + ScreenInfo si = (ScreenInfo)screenMap.get(s3d) ; + if (si == null) { + si = new ScreenInfo(s3d, c3d.getGraphicsConfiguration()) ; + screenMap.put(s3d, si) ; + } + + // Check to see if we've encountered the screen so far in this + // loop over the view's canvases. If not, clear the screen's list + // of canvases for this ViewInfo. + if (newSet.add(si)) si.clear(this) ; + + // Check if this is a new canvas. + CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; + if (ci == null) ci = new CanvasInfo(c3d, si) ; + + // Add this canvas to the screen's list for this ViewInfo. + si.addCanvasInfo(this, ci) ; + + // Add this canvas to the new canvas map and canvas array. + this.newMap.put(c3d, ci) ; + this.canvasInfo[i] = ci ; + } + + // Null out old references if canvas count shrinks. + for (int i = canvasCount ; i < canvasInfo.length ; i++) + this.canvasInfo[i] = null ; + + // Update the CanvasInfo map. + Map tmp = canvasMap ; + this.canvasMap = newMap ; + this.newMap = tmp ; + + // Clear the temporary collections. + this.newMap.clear() ; + this.newSet.clear() ; + } + + // + // Force the creation of new CanvasInfo instances. This is called when a + // screen is removed from the screen map. + // + private void clearCanvases() { + this.canvasCount = 0 ; + this.canvasMap.clear() ; + this.updateView = true ; + } + + // + // Update the view platform. Each view can be attached to only one, but + // each view platform can have many views attached. + // + private void getViewPlatform() { + ViewPlatform vp = view.getViewPlatform() ; + if (vp == null) + throw new IllegalStateException + ("The View must be attached to a ViewPlatform") ; + + ViewPlatformInfo tmpVpi = + (ViewPlatformInfo)viewPlatformMap.get(vp) ; + + if (tmpVpi == null) { + // We haven't encountered this ViewPlatform before. + tmpVpi = new ViewPlatformInfo(vp) ; + viewPlatformMap.put(vp, tmpVpi) ; + } + + if (this.vpi != tmpVpi) { + // ViewPlatform has changed. Could set an update flag here if it + // would be used, but updating the view updates everything anyway. + if (this.vpi != null) { + // Remove this ViewInfo from the list of Views attached to the + // old ViewPlatform. + this.vpi.removeViewInfo(this) ; + } + this.vpi = tmpVpi ; + this.vpi.addViewInfo(this) ; + + // updateViewPlatformToVworld is initially set false since the + // capability to read the vworld transform may not be + // available. If it is, set it here. + if (vp.getCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ)) { + this.vpi.updateViewPlatformToVworld = true ; + if (verbose) System.err.println(" vworld read allowed") ; + } else + if (verbose) System.err.println(" vworld read disallowed") ; + } + } + + // + // Force the creation of a new ViewPlatformInfo when a view platform is + // removed from the view platform map. + // + private void clearViewPlatform() { + this.updateView = true ; + } + + // + // Update vworld dependencies for this ViewInfo -- called by + // ViewPlatformInfo.getViewPlatformToVworld(). + // + private void updateVworldDependencies() { + for (int i = 0 ; i < canvasCount ; i++) + this.canvasInfo[i].updateVworldDependencies() ; + } + + /** + * Returns a reference to a Transform3D containing the current transform + * from head tracker coordinates to tracker base coordinates. It is only + * called if useHeadTracking returns true and a head position + * update is specified with updateHead or the + * HEAD_AUTO_UPDATE constructor flag.

+ * + * The default implementation uses the head tracking sensor specified by + * the View's PhysicalEnvironment, and reads it by calling the sensor's + * getRead method directly. The result is a sensor reading + * that may have been taken at a slightly different time from the one used + * by the renderer. This method can be overridden to synchronize the two + * readings through an external mechanism. + * + * @return current head tracker to tracker base transform + * @see #useHeadTracking + * @see #updateHead + * @see #HEAD_AUTO_UPDATE + */ + protected Transform3D getHeadTrackerToTrackerBase() { + headTracker.getRead(this.headTrackerToTrackerBase) ; + return this.headTrackerToTrackerBase ; + } + + /** + * Returns true if head tracking should be used.

+ * + * The default implementation returns true if the View's + * getTrackingEnable method and the PhysicalEnvironment's + * getTrackingAvailable method both return true. + * These are the same conditions under which the Java 3D renderer uses + * head tracking. This method can be overridden if there is any need to + * decouple the head tracking status of ViewInfo from the renderer. + * + * @return true if ViewInfo should use head tracking + */ + protected boolean useHeadTracking() { + return view.getTrackingEnable() && env.getTrackingAvailable() ; + } + + // + // Cache the current tracked head position and derived data. + // + private void getHeadInfo() { + if (verbose) System.err.println(" getHeadInfo") ; + + this.headTrackerToTrackerBase = getHeadTrackerToTrackerBase() ; + if (viewPolicy == View.HMD_VIEW) { + this.trackerBaseToHeadTracker.invert(headTrackerToTrackerBase) ; + this.coeToHeadTracker.mul(trackerBaseToHeadTracker, + coeToTrackerBase) ; + } + else { + this.headToTrackerBase.mul(headTrackerToTrackerBase, + headToHeadTracker) ; + } + for (int i = 0 ; i < canvasCount ; i++) + this.canvasInfo[i].updateHeadDependencies() ; + + this.updateHead = false ; + // + // The head position used by the Java 3D renderer isn't accessible + // in the public API. A head tracker generates continuous data, so + // getting the same sensor read as the renderer is unlikely. + // + // Possible workaround: for fixed screens, get the Java 3D + // renderer's version of plateToVworld and headToVworld by calling + // Canvas3D.getImagePlateToVworld() and View.getUserHeadToVworld(). + // Although the vworld components will have frame latency, they can + // be cancelled out by inverting the former transform and + // multiplying by the latter, resulting in userHeadToImagePlate, + // which can then be transformed to tracker base coordinates. + // + // For head mounted displays, the head to image plate transforms are + // just calibration constants, so they're of no use. There are more + // involved workarounds possible, but one that may work for both fixed + // screens and HMD is to define a SensorInterposer class that extends + // Sensor. Take the View's head tracking sensor, use it to construct + // a SensorInterposer, and then replace the head tracking sensor with + // the SensorInterposer. SensorInterposer can then override the + // getRead() methods and thus control what the Java 3D renderer gets. + // getHeadTrackerToTrackerBase() is a protected method in ViewInfo + // which can be overridden to call a variant of getRead() so that + // calls from ViewInfo and from the renderer can be distinguished. + // + // Even if getting the same head position as used by the renderer is + // achieved, tracked eye space interactions with objects in the + // virtual world still can't be synchronized with rendering. This + // means that objects in the virtual world cannot be made to appear in + // a fixed position relative to the tracked head position without a + // frame lag between them. + // + // The reason for this is that the tracked head position used by the + // Java 3D renderer is updated asynchronously from scene graph + // updates. This is done to reduce latency between the user's + // position and the rendered image, which is directly related to the + // quality of the immersive virtual reality experience. So while an + // update to the scene graph may have a frame latency before it gets + // rendered, a change to the user's tracked position is always + // reflected in the current frame. + // + // This problem can't be fixed without eliminating the frame latency + // in the Java 3D internal state, although there are possible + // workarounds at the expense of increased user position latency. + // These involve disabling tracking, reading the head sensor directly, + // performing whatever eye space interactions are necessary with the + // virtual world (using the view platform's current localToVworld), + // and then propagating the head position change to the renderer + // manually through a behavior post mechanism that delays it by a + // frame. + // + // For example, with head tracking in a fixed screen environment (such + // as a CAVE), disable Java 3D head tracking and set the View's window + // eyepoint policy to RELATIVE_TO_COEXISTENCE. Read the sensor to get + // the head position relative to the tracker base, transform it to + // coexistence coordinates using the inverse of the value of the + // coexistenceToTrackerBase transform, and then set the eye positions + // manually with the View's set{Left,Right}ManualEyeInCoexistence + // methods. If these method calls are delayed through a behavior post + // mechanism, then they will be synchronized with the rendering of the + // scene graph updates. + // + // With a head mounted display the sensor can be read directly to get + // the head position relative to the tracker base. If Java 3D's head + // tracking is disabled, it uses identity for the current + // headTrackerToTrackerBase transform. It concatenates its inverse, + // trackerBaseToHeadTracker, with coexistenceToTrackerBase to get the + // image plate positions in coexistence; the former transform is + // inaccessible, but the latter can be set through the + // PhysicalEnvironment. So the workaround is to maintain a local copy + // with the real value of coexistenceToTrackerBase, but set the + // PhysicalEnvironment copy to the product of the real value and the + // trackerBaseToHeadTracker inverted from the sensor read. Like the + // CAVE example, this update to the View would have to be delayed in + // order to synchronize with scene graph updates. + // + // Another possibility is to put the Java 3D view model in + // compatibility mode, where it accepts vpcToEye and eyeToCc + // (projection) directly. The various view attributes can still be + // set and accessed, but will be ignored by the Java 3D view model. + // The ViewInfo methods can be used to compute the view and projection + // matrices, which can then be delayed to synchronize with the scene + // graph. + // + // Note that these workarounds could be used to make view-dependent + // scene graph updates consistent, but they still can't do anything + // about synchronizing the actual physical position of the user with + // the rendered images. That requires zero latency between position + // update and scene graph state. + // + // Still another possibility: extrapolate the position of the user + // into the next few frames from a sample of recently recorded + // positions. Unfortunately, that is also a very hard problem. The + // Java 3D Sensor API is designed to support prediction but it was + // never realized successfully in the sample implementation. + } + + // + // A per-screen cache, shared between ViewInfo instances. In the Java 3D + // view model a single screen can be associated with multiple canvas + // and view instances. + // + private static class ScreenInfo { + private Screen3D s3d = null ; + private GraphicsConfiguration graphicsConfiguration = null ; + private boolean updateScreen = true ; + + private Map viewInfoMap = new HashMap() ; + private List viewInfoList = new LinkedList() ; + private Transform3D t3d = new Transform3D() ; + + private double screenWidth = 0.0 ; + private double screenHeight = 0.0 ; + private boolean updateScreenSize = true ; + + private Rectangle screenBounds = null ; + private double metersPerPixelX = 0.0 ; + private double metersPerPixelY = 0.0 ; + private boolean updatePixelSize = true ; + + // These transforms are pre-allocated here since they are required by + // some view policies and we don't know what views this screen will be + // attached to. Their default identity values are used if not + // explicitly set. TODO: allocate if needed in getCanvasInfo(), where + // view information will be available. + private Transform3D trackerBaseToPlate = new Transform3D() ; + private Transform3D headTrackerToLeftPlate = new Transform3D() ; + private Transform3D headTrackerToRightPlate = new Transform3D() ; + private boolean updateTrackerBaseToPlate = false ; + private boolean updateHeadTrackerToPlate = false ; + + private ScreenInfo(Screen3D s3d, GraphicsConfiguration gc) { + this.s3d = s3d ; + this.graphicsConfiguration = gc ; + if (verbose) + System.err.println(" ScreenInfo: init " + s3d.hashCode()) ; + } + + private List getCanvasList(ViewInfo vi) { + List canvasList = (List)viewInfoMap.get(vi) ; + if (canvasList == null) { + canvasList = new LinkedList() ; + viewInfoMap.put(vi, canvasList) ; + viewInfoList.add(canvasList) ; + } + return canvasList ; + } + + private synchronized void clear(ViewInfo vi) { + getCanvasList(vi).clear() ; + } + + private synchronized void clear() { + Iterator i = viewInfoMap.keySet().iterator() ; + while (i.hasNext()) ((ViewInfo)i.next()).clearCanvases() ; + viewInfoMap.clear() ; + + i = viewInfoList.iterator() ; + while (i.hasNext()) ((List)i.next()).clear() ; + viewInfoList.clear() ; + } + + private synchronized void addCanvasInfo(ViewInfo vi, CanvasInfo ci) { + getCanvasList(vi).add(ci) ; + } + + // + // Get all relevant screen information, find out what changed, and + // flag derived data. With normal use it's unlikely that any of the + // Screen3D attributes will change after the first time this method is + // called. It's possible that the screen resolution changed or some + // sort of interactive screen calibration is in process. + // + private synchronized void getScreenInfo() { + if (verbose) + System.err.println(" getScreenInfo " + s3d.hashCode()); + + // This is used for positioning screens in relation to each other + // and must be accurate for good results with multi-screen + // displays. By default the coexistence to tracker base transform + // is identity so in that case this transform will also set the + // image plate in coexistence coordinates. + s3d.getTrackerBaseToImagePlate(t3d) ; + if (! t3d.equals(trackerBaseToPlate)) { + this.trackerBaseToPlate.set(t3d) ; + this.updateTrackerBaseToPlate = true ; + if (verbose) t3dPrint(trackerBaseToPlate, + " trackerBaseToPlate") ; + } + + // This transform and the following are used for head mounted + // displays. They should be based on the *apparent* position of + // the screens as viewed through the HMD optics. + s3d.getHeadTrackerToLeftImagePlate(t3d) ; + if (! t3d.equals(headTrackerToLeftPlate)) { + this.headTrackerToLeftPlate.set(t3d) ; + this.updateHeadTrackerToPlate = true ; + if (verbose) t3dPrint(headTrackerToLeftPlate, + " headTrackerToLeftPlate") ; + } + + s3d.getHeadTrackerToRightImagePlate(t3d) ; + if (! t3d.equals(headTrackerToRightPlate)) { + this.headTrackerToRightPlate.set(t3d) ; + this.updateHeadTrackerToPlate = true ; + if (verbose) t3dPrint(headTrackerToRightPlate, + " headTrackerToRightPlate") ; + } + + // If the screen width and height in meters are not explicitly set + // through the Screen3D, then the Screen3D will assume a pixel + // resolution of 90 pixels/inch and compute the dimensions from + // the screen resolution. These dimensions should be measured + // accurately for multi-screen displays. For HMD, these + // dimensions should be the *apparent* width and height as viewed + // through the HMD optics. + double w = s3d.getPhysicalScreenWidth() ; + double h = s3d.getPhysicalScreenHeight(); + if (w != screenWidth || h != screenHeight) { + this.screenWidth = w ; + this.screenHeight = h ; + this.updateScreenSize = true ; + if (verbose) { + System.err.println(" screen width " + screenWidth) ; + System.err.println(" screen height " + screenHeight) ; + } + } + + this.screenBounds = graphicsConfiguration.getBounds() ; + double mpx = screenWidth / (double)screenBounds.width ; + double mpy = screenHeight / (double)screenBounds.height ; + if ((mpx != metersPerPixelX) || (mpy != metersPerPixelY)) { + this.metersPerPixelX = mpx ; + this.metersPerPixelY = mpy ; + this.updatePixelSize = true ; + if (verbose) { + System.err.println(" screen bounds " + screenBounds) ; + System.err.println(" pixel size X " + metersPerPixelX) ; + System.err.println(" pixel size Y " + metersPerPixelY) ; + } + } + + // Propagate screen updates to each canvas in each ViewInfo. + Iterator vi = viewInfoList.iterator() ; + while (vi.hasNext()) { + Iterator ci = ((List)vi.next()).iterator() ; + while (ci.hasNext()) + ((CanvasInfo)ci.next()).updateScreenDependencies() ; + } + + this.updateTrackerBaseToPlate = false ; + this.updateHeadTrackerToPlate = false ; + this.updateScreenSize = false ; + this.updatePixelSize = false ; + this.updateScreen = false ; + } + } + + // + // A per-ViewPlatform cache, shared between ViewInfo instances. In the + // Java 3D view model, a view platform may have several views attached to + // it. The only view platform data cached here is its localToVworld, the + // inverse of its localToVworld, and the scale from vworld to view + // platform coordinates. The view platform to coexistence transform is + // cached by the CanvasInfo instances associated with the ViewInfo. + // + private static class ViewPlatformInfo { + private ViewPlatform vp = null ; + private List viewInfo = new LinkedList() ; + private double[] m = new double[16] ; + + // These transforms are pre-allocated since we don't know what views + // will be attached. Their default identity values are used if a + // vworld dependent computation is requested and no initial update of + // the view platform was performed; this occurs if the local to vworld + // read capability isn't set. TODO: rationalize this and allocate + // only if necessary. + private Transform3D viewPlatformToVworld = new Transform3D() ; + private Transform3D vworldToViewPlatform = new Transform3D() ; + private double vworldToViewPlatformScale = 1.0 ; + + // Set these update flags initially false since we might not have the + // capability to read the vworld transform. + private boolean updateViewPlatformToVworld = false ; + private boolean updateVworldScale = false ; + + private ViewPlatformInfo(ViewPlatform vp) { + this.vp = vp ; + if (verbose) System.err.println + (" ViewPlatformInfo: init " + vp.hashCode()) ; + } + + private synchronized void addViewInfo(ViewInfo vi) { + this.viewInfo.add(vi) ; + } + + private synchronized void removeViewInfo(ViewInfo vi) { + this.viewInfo.remove(vi) ; + } + + private synchronized void clear() { + Iterator i = viewInfo.iterator() ; + while (i.hasNext()) ((ViewInfo)i.next()).clearViewPlatform() ; + viewInfo.clear() ; + } + + // + // Get the view platform's current localToVworld and + // force the update of derived data. + // + private synchronized void getViewPlatformToVworld() { + if (verbose) System.err.println + (" getViewPlatformToVworld " + vp.hashCode()) ; + + vp.getLocalToVworld(this.viewPlatformToVworld) ; + this.vworldToViewPlatform.invert(viewPlatformToVworld) ; + + // Get the scale factor from the virtual world to view platform + // transform. Note that this is always a congruent transform. + vworldToViewPlatform.get(m) ; + double newScale = Math.sqrt(m[0]*m[0] + m[1]*m[1] + m[2]*m[2]) ; + + // May need to update clip plane distances if scale changed. We'll + // check with an epsilon commensurate with single precision float. + // It would be more efficient to check the square of the distance + // and then compute the square root only if different, but that + // makes choosing an epsilon difficult. + if ((newScale > vworldToViewPlatformScale + 0.0000001) || + (newScale < vworldToViewPlatformScale - 0.0000001)) { + this.vworldToViewPlatformScale = newScale ; + this.updateVworldScale = true ; + if (verbose) System.err.println(" vworld scale " + + vworldToViewPlatformScale) ; + } + + // All virtual world transforms must be updated. + Iterator i = viewInfo.iterator() ; + while (i.hasNext()) + ((ViewInfo)i.next()).updateVworldDependencies() ; + + this.updateVworldScale = false ; + this.updateViewPlatformToVworld = false ; + } + } + + // + // A per-canvas cache. + // + private class CanvasInfo { + private Canvas3D c3d = null ; + private ScreenInfo si = null ; + private boolean updateCanvas = true ; + + private double canvasX = 0.0 ; + private double canvasY = 0.0 ; + private boolean updatePosition = true ; + + private double canvasWidth = 0.0 ; + private double canvasHeight = 0.0 ; + private double windowScale = 0.0 ; + private boolean updateWindowScale = true ; + + private double screenScale = 0.0 ; + private boolean updateScreenScale = true ; + + private boolean useStereo = false ; + private boolean updateStereo = true ; + + // + // coeToPlate is the same for each Canvas3D in a Screen3D unless + // coexistence centering is enabled and the window movement policy is + // PHYSICAL_WORLD. + // + private Transform3D coeToPlate = null ; + private Transform3D coeToRightPlate = null ; + private boolean updateCoeToPlate = true ; + + // + // viewPlatformToCoe is the same for each Canvas3D in a View unless + // the window resize policy is PHYSICAL_WORLD, in which case the scale + // factor includes the window scale; or if the screen scale policy is + // SCALE_SCREEN_SIZE, in which case the scale factor depends upon the + // width of the screen associated with the canvas; or if the window + // eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW and the view attach + // policy is not NOMINAL_SCREEN, which will set the view platform + // origin in coexistence based on the width of the canvas. + // + private Transform3D viewPlatformToCoe = null ; + private Transform3D coeToViewPlatform = null ; + private boolean updateViewPlatformToCoe = true ; + private boolean updateCoeToViewPlatform = true ; + + // + // plateToViewPlatform is composed from viewPlatformToCoe and + // coeToPlate. + // + private Transform3D plateToViewPlatform = null ; + private Transform3D rightPlateToViewPlatform = null ; + private boolean updatePlateToViewPlatform = true ; + + // + // trackerBaseToViewPlatform is computed from viewPlatformToCoe and + // coeToTrackerBase. + // + private Transform3D trackerBaseToViewPlatform = null ; + private boolean updateTrackerBaseToViewPlatform = true ; + + // + // Eye position in image plate is always different for each Canvas3D + // in a View, unless two or more Canvas3D instances are the same + // position and size, or two or more Canvas3D instances are using a + // window eyepoint policy of RELATIVE_TO_SCREEN and have the same + // settings for the manual eye positions. + // + private Point3d eyeInPlate = new Point3d() ; + private Point3d rightEyeInPlate = new Point3d() ; + private Transform3D eyeToPlate = null ; + private Transform3D rightEyeToPlate = null ; + private boolean updateEyeInPlate = true ; + + private Point3d leftManualEyeInPlate = new Point3d() ; + private Point3d rightManualEyeInPlate = new Point3d() ; + private boolean updateManualEye = true ; + + private int monoscopicPolicy = -1 ; + private boolean updateMonoPolicy = true ; + + // + // eyeToViewPlatform is computed from eyeToPlate and + // plateToViewPlatform. + // + private Transform3D eyeToViewPlatform = null ; + private Transform3D rightEyeToViewPlatform = null ; + private boolean updateEyeToViewPlatform = true ; + + private Transform3D viewPlatformToEye = null ; + private Transform3D viewPlatformToRightEye = null ; + private boolean updateViewPlatformToEye = true ; + + // + // The projection transform depends upon eye position in image plate. + // + private Transform3D projection = null ; + private Transform3D rightProjection = null ; + private boolean updateProjection = true ; + + private Transform3D inverseProjection = null ; + private Transform3D inverseRightProjection = null ; + private boolean updateInverseProjection = true ; + + private Transform3D inverseViewPlatformProjection = null ; + private Transform3D inverseViewPlatformRightProjection = null ; + private boolean updateInverseViewPlatformProjection = true ; + + // + // The physical clip distances can be affected by the canvas width + // with the PHYSICAL_WORLD resize policy. + // + private double frontClipDistance = 0.0 ; + private double backClipDistance = 0.0 ; + private boolean updateClipDistances = true ; + + // + // The physical to view platform scale can be affected by the canvas + // width with the PHYSICAL_WORLD resize policy. + // + private double physicalToVpScale = 0.0 ; + private double physicalToVirtualScale = 0.0 ; + private boolean updatePhysicalToVpScale = true ; + private boolean updatePhysicalToVirtualScale = true ; + + // + // The vworld transforms require reading the ViewPlaform's + // localToVworld tranform. + // + private Transform3D plateToVworld = null ; + private Transform3D rightPlateToVworld = null ; + private boolean updatePlateToVworld = true ; + + private Transform3D coeToVworld = null ; + private boolean updateCoeToVworld = true ; + + private Transform3D eyeToVworld = null ; + private Transform3D rightEyeToVworld = null ; + private boolean updateEyeToVworld = true ; + + private Transform3D trackerBaseToVworld = null ; + private boolean updateTrackerBaseToVworld = true ; + + private Transform3D inverseVworldProjection = null ; + private Transform3D inverseVworldRightProjection = null ; + private boolean updateInverseVworldProjection = true ; + + private CanvasInfo(Canvas3D c3d, ScreenInfo si) { + this.si = si ; + this.c3d = c3d ; + if (verbose) System.err.println(" CanvasInfo: init " + + c3d.hashCode()) ; + } + + private void getCanvasInfo() { + if (verbose) System.err.println(" getCanvasInfo " + + c3d.hashCode()) ; + boolean newStereo = + c3d.getStereoEnable() && c3d.getStereoAvailable() ; + + if (useStereo != newStereo) { + this.useStereo = newStereo ; + this.updateStereo = true ; + if (verbose) System.err.println(" stereo " + useStereo) ; + } + + this.canvasWidth = c3d.getWidth() * si.metersPerPixelX ; + this.canvasHeight = c3d.getHeight() * si.metersPerPixelY ; + double newScale = canvasWidth / si.screenWidth ; + + if (windowScale != newScale) { + this.windowScale = newScale ; + this.updateWindowScale = true ; + if (verbose) { + System.err.println(" width " + canvasWidth) ; + System.err.println(" height " + canvasHeight) ; + System.err.println(" scale " + windowScale) ; + } + } + + // For multiple physical screens, AWT returns the canvas location + // relative to the origin of the aggregated virtual screen. We + // need the location relative to the physical screen origin. + Point awtLocation = c3d.getLocationOnScreen() ; + int x = awtLocation.x - si.screenBounds.x ; + int y = awtLocation.y - si.screenBounds.y ; + + double newCanvasX = si.metersPerPixelX * x ; + double newCanvasY = si.metersPerPixelY * + (si.screenBounds.height - (y + c3d.getHeight())) ; + + if (canvasX != newCanvasX || canvasY != newCanvasY) { + this.canvasX = newCanvasX ; + this.canvasY = newCanvasY ; + this.updatePosition = true ; + if (verbose) { + System.err.println(" lower left X " + canvasX) ; + System.err.println(" lower left Y " + canvasY) ; + } + } + + int newMonoPolicy = c3d.getMonoscopicViewPolicy() ; + if (monoscopicPolicy != newMonoPolicy) { + this.monoscopicPolicy = newMonoPolicy ; + this.updateMonoPolicy = true ; + + if (verbose && !useStereo) { + if (monoscopicPolicy == View.LEFT_EYE_VIEW) + System.err.println(" left eye view") ; + else if (monoscopicPolicy == View.RIGHT_EYE_VIEW) + System.err.println(" right eye view") ; + else + System.err.println(" cyclopean view") ; + } + } + + c3d.getLeftManualEyeInImagePlate(leftEye) ; + c3d.getRightManualEyeInImagePlate(rightEye) ; + + if (!leftEye.equals(leftManualEyeInPlate) || + !rightEye.equals(rightManualEyeInPlate)) { + + this.leftManualEyeInPlate.set(leftEye) ; + this.rightManualEyeInPlate.set(rightEye) ; + this.updateManualEye = true ; + + if (verbose && (eyePolicy == View.RELATIVE_TO_WINDOW || + eyePolicy == View.RELATIVE_TO_SCREEN)) { + System.err.println(" left manual eye in plate " + + leftManualEyeInPlate) ; + System.err.println(" right manual eye in plate " + + rightManualEyeInPlate) ; + } + } + + updateCanvasDependencies() ; + this.updateStereo = false ; + this.updateWindowScale = false ; + this.updatePosition = false ; + this.updateMonoPolicy = false ; + this.updateManualEye = false ; + this.updateCanvas = false ; + } + + private double getFieldOfViewOffset() { + return 0.5 * canvasWidth / Math.tan(0.5 * view.getFieldOfView()) ; + } + + private void updateScreenDependencies() { + if (si.updatePixelSize || si.updateScreenSize) { + if (eyePolicy == View.RELATIVE_TO_WINDOW || + eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { + // Physical location of the canvas might change without + // changing the pixel location. + updateEyeInPlate = true ; + } + if (resizePolicy == View.PHYSICAL_WORLD || + eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { + // Could change the window scale or view platform Z offset. + updateViewPlatformToCoe = true ; + } + if (resizePolicy == View.PHYSICAL_WORLD) { + // Window scale affects the clip distance and the physical + // to viewplatform scale. + updateClipDistances = true ; + updatePhysicalToVpScale = true ; + updatePhysicalToVirtualScale = true ; + } + // Upper right corner of canvas may have moved from eye. + updateProjection = true ; + } + if (si.updateScreenSize && scalePolicy == View.SCALE_SCREEN_SIZE) { + // Screen scale affects the clip distances and physical to + // view platform scale. The screen scale is also a component + // of viewPlatformToCoe. + updateScreenScale = true ; + updateClipDistances = true ; + updatePhysicalToVpScale = true ; + updatePhysicalToVirtualScale = true ; + updateViewPlatformToCoe = true ; + } + + if (viewPolicy == View.HMD_VIEW) { + if (si.updateHeadTrackerToPlate) { + // Plate moves with respect to the eye and coexistence. + updateEyeInPlate = true ; + updateCoeToPlate = true ; + } + } + else if (coeCentering) { + if (movementPolicy == View.PHYSICAL_WORLD) { + // Coexistence is centered on the canvas. + if (si.updatePixelSize || si.updateScreenSize) + // Physical location of the canvas might change + // without changing the pixel location. + updateCoeToPlate = true ; + } + else if (si.updateScreenSize) + // Coexistence is centered on the screen. + updateCoeToPlate = true ; + } + else if (si.updateTrackerBaseToPlate) { + // Image plate has possibly changed location. Could be + // offset by an update to coeToTrackerBase in the + // PhysicalEnvironment though. + updateCoeToPlate = true ; + } + + if (updateCoeToPlate && + eyePolicy == View.RELATIVE_TO_COEXISTENCE) { + // Coexistence has moved with respect to plate. + updateEyeInPlate = true ; + } + if (updateViewPlatformToCoe) { + // Derived transforms. trackerBaseToViewPlatform is composed + // from viewPlatformToCoe and coexistenceToTrackerBase. + updateCoeToViewPlatform = true ; + updateCoeToVworld = true ; + updateTrackerBaseToViewPlatform = true ; + updateTrackerBaseToVworld = true ; + } + if (updateCoeToPlate || updateViewPlatformToCoe) { + // The image plate to view platform transform is composed from + // the coexistence to image plate and view platform to + // coexistence transforms, so these need updates as well. + updatePlateToViewPlatform = true ; + updatePlateToVworld = true ; + } + updateEyeDependencies() ; + } + + private void updateEyeDependencies() { + if (updateEyeInPlate) { + updateEyeToVworld = true ; + updateProjection = true ; + } + if (updateProjection) { + updateInverseProjection = true ; + updateInverseViewPlatformProjection = true ; + updateInverseVworldProjection = true ; + } + if (updateEyeInPlate || updatePlateToViewPlatform) { + updateViewPlatformToEye = true ; + updateEyeToViewPlatform = true ; + } + } + + private void updateCanvasDependencies() { + if (updateStereo || updateMonoPolicy || + (updateManualEye && (eyePolicy == View.RELATIVE_TO_WINDOW || + eyePolicy == View.RELATIVE_TO_SCREEN))) { + updateEyeInPlate = true ; + } + if (updateWindowScale || updatePosition) { + if (coeCentering && movementPolicy == View.PHYSICAL_WORLD) { + // Coexistence is centered on the canvas. + updateCoeToPlate = true ; + if (eyePolicy == View.RELATIVE_TO_COEXISTENCE) + updateEyeInPlate = true ; + } + if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW || + eyePolicy == View.RELATIVE_TO_WINDOW) + // Eye depends on canvas position and size. + updateEyeInPlate = true ; + } + if (updateWindowScale) { + if (resizePolicy == View.PHYSICAL_WORLD || + eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { + // View platform scale and its origin Z offset changed. + // trackerBaseToViewPlatform needs viewPlatformToCoe. + updateViewPlatformToCoe = true ; + updateCoeToViewPlatform = true ; + updateCoeToVworld = true ; + updateTrackerBaseToViewPlatform = true ; + updateTrackerBaseToVworld = true ; + } + if (resizePolicy == View.PHYSICAL_WORLD) { + // Clip distance and physical to view platform scale are + // affected by the window size. + updateClipDistances = true ; + updateProjection = true ; + updatePhysicalToVpScale = true ; + updatePhysicalToVirtualScale = true ; + } + } + if (updateViewPlatformToCoe || updateCoeToPlate) { + // The image plate to view platform transform is composed from + // the coexistence to image plate and the view platform to + // coexistence transforms, so these need updates. + updatePlateToViewPlatform = true ; + updatePlateToVworld = true ; + } + if (coeCentering && !updateManualEye && !updateWindowScale && + (movementPolicy == View.PHYSICAL_WORLD) && + (eyePolicy != View.RELATIVE_TO_SCREEN)) { + // The canvas may have moved, but the eye, coexistence, and + // view platform moved with it. updateEyeDependencies() + // isn't called since it would unnecessarily update the + // projection and eyeToViewPlatform transforms. The tested + // policies are all true by default. + return ; + } + updateEyeDependencies() ; + } + + // + // TODO: A brave soul could refine cache updates here. There are a + // lot of attributes to monitor, so we just update everything for now. + // + private void updateViewDependencies() { + // View policy, physical body eye positions, head to head + // tracker, window eyepoint policy, field of view, coexistence + // centering, or coexistence to image plate may have changed. + updateEyeInPlate = true ; + + // If the eye position in image plate has changed, then the + // projection transform may need to be updated. The projection + // policy and clip plane distances and policies may have changed. + // The window resize policy and screen scale may have changed, + // which affects clip plane distance scaling. + updateProjection = true ; + updateClipDistances = true ; + updatePhysicalToVpScale = true ; + updatePhysicalToVirtualScale = true ; + + // View policy, coexistence to tracker base, coexistence centering + // enable, or window movement policy may have changed. + updateCoeToPlate = true ; + + // Screen scale, resize policy, view policy, view platform, + // physical body, physical environment, eyepoint policy, or field + // of view may have changed. + updateViewPlatformToCoe = true ; + updateCoeToViewPlatform = true ; + updateCoeToVworld = true ; + + // The image plate to view platform transform is composed from the + // coexistence to image plate and view platform to coexistence + // transforms, so these need updates. + updatePlateToViewPlatform = true ; + updatePlateToVworld = true ; + + // View platform to coexistence or coexistence to tracker base may + // have changed. + updateTrackerBaseToViewPlatform = true ; + updateTrackerBaseToVworld = true ; + + // Screen scale policy or explicit screen scale may have changed. + updateScreenScale = true ; + + // Update transforms derived from eye info. + updateEyeDependencies() ; + } + + private void updateHeadDependencies() { + if (viewPolicy == View.HMD_VIEW) { + // Image plates are fixed relative to the head, so their + // positions have changed with respect to coexistence, the + // view platform, and the virtual world. The eyes are fixed + // with respect to the image plates, so the projection doesn't + // change with respect to them. + updateCoeToPlate = true ; + updatePlateToViewPlatform = true ; + updatePlateToVworld = true ; + updateViewPlatformToEye = true ; + updateEyeToViewPlatform = true ; + updateEyeToVworld = true ; + updateInverseViewPlatformProjection = true ; + updateInverseVworldProjection = true ; + } + else { + // Eye positions have changed with respect to the fixed + // screens, so the projections must be updated as well as the + // positions. + updateEyeInPlate = true ; + updateEyeDependencies() ; + } + } + + private void updateVworldDependencies() { + updatePlateToVworld = true ; + updateCoeToVworld = true ; + updateEyeToVworld = true ; + updateTrackerBaseToVworld = true ; + updateInverseVworldProjection = true ; + + if (vpi.updateVworldScale) + updatePhysicalToVirtualScale = true ; + + if (vpi.updateVworldScale && clipVirtual) { + // vworldToViewPlatformScale changed and clip plane distances + // are in virtual units. + updateProjection = true ; + updateClipDistances = true ; + updateInverseProjection = true ; + updateInverseViewPlatformProjection = true ; + } + } + } + + /** + * Prints out the specified transform in a readable format. + * + * @param t3d transform to be printed + * @param name the name of the transform + */ + private static void t3dPrint(Transform3D t3d, String name) { + double[] m = new double[16] ; + t3d.get(m) ; + String[] sa = formatMatrixRows(4, 4, m) ; + System.err.println(name) ; + for (int i = 0 ; i < 4 ; i++) System.err.println(sa[i]) ; + } + + /** + * Formats a matrix with fixed fractional digits and integer padding to + * align the decimal points in columns. Non-negative numbers print up to + * 7 integer digits, while negative numbers print up to 6 integer digits + * to account for the negative sign. 6 fractional digits are printed. + * + * @param rowCount number of rows in the matrix + * @param colCount number of columns in the matrix + * @param m matrix to be formatted + * @return matrix rows formatted into strings + */ + private static String[] formatMatrixRows + (int rowCount, int colCount, double[] m) { + + DecimalFormat df = new DecimalFormat("0.000000") ; + FieldPosition fp = new FieldPosition(DecimalFormat.INTEGER_FIELD) ; + StringBuffer sb0 = new StringBuffer() ; + StringBuffer sb1 = new StringBuffer() ; + String[] rows = new String[rowCount] ; + + for (int i = 0 ; i < rowCount ; i++) { + sb0.setLength(0) ; + for (int j = 0 ; j < colCount ; j++) { + sb1.setLength(0) ; + df.format(m[i*colCount+j], sb1, fp) ; + int pad = 8 - fp.getEndIndex() ; + for (int k = 0 ; k < pad ; k++) { + sb1.insert(0, " ") ; + } + sb0.append(sb1) ; + } + rows[i] = sb0.toString() ; + } + return rows ; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/Viewer.java b/src/classes/share/com/sun/j3d/utils/universe/Viewer.java new file mode 100644 index 0000000..d1665f6 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/Viewer.java @@ -0,0 +1,1012 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import java.awt.event.*; +import java.awt.*; +import java.net.URL; +import java.util.*; +import javax.media.j3d.*; +import javax.swing.*; +import javax.vecmath.*; +import com.sun.j3d.audioengines.javasound.JavaSoundMixer; +import java.applet.*; + +/** + * The Viewer class holds all the information that describes the physical + * and virtual "presence" in the Java 3D universe. The Viewer object + * consists of: + *

    + *
  • Physical Objects
  • + *
      + *
    • Canvas3D's - used to render with.
    • + *
    • PhysicalEnvironment - holds characteristics of the hardware platform + * being used to render on.
    • + *
    • PhysicalBody - holds the physical characteristics and personal + * preferences of the person who will be viewing the Java 3D universe.
    • + *
    + *
  • Virtual Objects
  • + *
      + *
    • View - the Java 3D View object.
    • + *
    • ViewerAvatar - the geometry that is used by Java 3D to represent the + * person viewing the Java 3D universe.
    • + *
    + *
+ * If the Viewer object is created without any Canvas3D's, or indirectly + * through a configuration file, it will create the Canvas3D's as needed. + * The default Viewer creates one Canvas3D. If the Viewer object creates + * the Canvas3D's, it will also create a JPanel and JFrame for each Canvas3D. + * + * Dynamic video resize is a new feature in Java 3D 1.3.1. + * This feature provides a means for doing swap synchronous resizing + * of the area that is to be magnified (or passed through) to the + * output video resolution. This functionality allows an application + * to draw into a smaller viewport in the framebuffer in order to reduce + * the time spent doing pixel fill. The reduced size viewport is then + * magnified up to the video output resolution using the SUN_video_resize + * extension. This extension is only implemented in XVR-4000 and later + * hardware with back end video out resizing capability. + * + * If video size compensation is enable, the line widths, point sizes and pixel + * operations will be scaled internally with the resize factor to approximately + * compensate for video resizing. The location of the pixel ( x, y ) in the + * resized framebuffer = ( floor( x * factor + 0.5 ), floor( y * factor + 0.5 ) ) + * + *

+ * @see Canvas3D + * @see PhysicalEnvironment + * @see PhysicalBody + * @see View + * @see ViewerAvatar + */ +public class Viewer { + private static final boolean debug = false; + private static PhysicalBody physicalBody = null; + private static PhysicalEnvironment physicalEnvironment = null; + private View view = null; + private ViewerAvatar avatar = null; + private Canvas3D[] canvases = null; + private JFrame[] j3dJFrames = null; + private JPanel[] j3dJPanels = null; + private Window[] j3dWindows = null; + private ViewingPlatform viewingPlatform = null; + + + static HashMap viewerMap = new HashMap(5); + private float dvrFactor = 1.0f; + private boolean doDvr = false; + private boolean doDvrResizeCompensation = true; + + + /** + * Get the Viewer associated with the view object. + * + * @param view The View object for inquiry. + * @return The Viewer object associated with this View object. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public static Viewer getViewer(View view) { + Viewer viewer = null; + synchronized (viewerMap) { + //System.out.println("Viewer.getViewer viewerMap's size is " + viewerMap.size()); + viewer = (Viewer) (viewerMap.get(view)); + } + return viewer; + } + + + /** + * Removes the entry associated with the view object. + * + * @param view The View object to be removed. + * @return The Viewer object associated with this View object. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public static Viewer removeViewerMapEntry(View view) { + Viewer viewer = null; + synchronized (viewerMap) { + + viewer = (Viewer) (viewerMap.remove(view)); + } + // System.out.println("viewerMap.size() " + viewerMap.size()); + + return viewer; + } + + + /** + * Removes all Viewer mappings from the Viewer map. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public static void clearViewerMap() { + synchronized (viewerMap) { + viewerMap.clear(); + } + // System.out.println("clearViewerMap - viewerMap.size() " + viewerMap.size()); + + } + + + /** + * Returns a status flag indicating whether or not dynamic video size + * is enabled. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public boolean isDvrEnabled() { + return doDvr; + } + + /** + * Turns on or off dynamic video size. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @param dvr enables or disables dynamic video size. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public void setDvrEnable(boolean dvr) { + doDvr = dvr; + view.repaint(); + + } + + /** + * Retrieves the dynamic video resize factor of this + * viewer. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public float getDvrFactor() { + return dvrFactor; + } + + + /** + * Set the dynamic video resize factor for this viewer. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @param dvr set the dynamic video resize factor for this viewer. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public void setDvrFactor(float dvr) { + dvrFactor = dvr; + view.repaint(); + + } + + /** + * Turns on or off dynamic video resize compensation. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @param dvrRCE enables or disables dynamic video resize compensation. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public void setDvrResizeCompensationEnable(boolean dvrRCE) { + doDvrResizeCompensation = dvrRCE; + view.repaint(); + } + + /** + * Returns a status flag indicating whether or not dynamic video resize + * compensation is enabled. + * + * Note: This method is targeted for SUN framebuffer XVR-4000 and later + * hardware that support video size extension. + * + * @since Java 3D 1.3.1 + */ + // To support a back door for DVR support. + public boolean getDvrResizeCompensationEnable() { + return doDvrResizeCompensation; + } + + /** + * Creates a default viewer object. The default values are used to create + * the PhysicalBody and PhysicalEnvironment. A single RGB, double buffered + * and depth buffered Canvas3D object is created. The View is created + * with a front clip distance of 0.1f and a back clip distance of 10.0f. + */ + public Viewer() { + // Call main constructor with default values. + this(null, null, null, true); + } + + /** + * Creates a default viewer object. The default values are used to create + * the PhysicalBody and PhysicalEnvironment. The View is created + * with a front clip distance of 0.1f and a back clip distance of 10.0f. + * + * @param userCanvas the Canvas3D object to be used for rendering; + * if this is null then a single RGB, double buffered and depth buffered + * Canvas3D object is created + * @since Java3D 1.1 + */ + public Viewer(Canvas3D userCanvas) { + // Call main constructor. + this(userCanvas == null ? null : new Canvas3D[] {userCanvas}, + null, null, true); + } + + + /** + * Creates a default viewer object. The default values are used to create + * the PhysicalBody and PhysicalEnvironment. The View is created + * with a front clip distance of 0.1f and a back clip distance of 10.0f. + * + * @param userCanvases the Canvas3D objects to be used for rendering; + * if this is null then a single RGB, double buffered and depth buffered + * Canvas3D object is created + * @since Java3D 1.3 + */ + public Viewer(Canvas3D[] userCanvases) { + this(userCanvases, null, null, true); + } + + /** + * Creates a viewer object. The Canvas3D objects, PhysicalEnvironment, and + * PhysicalBody are taken from the arguments. + * + * @param userCanvases the Canvas3D objects to be used for rendering; + * if this is null then a single RGB, double buffered and depth buffered + * Canvas3D object is created + * @param userBody the PhysicalBody to use for this Viewer; if it is + * null, a default PhysicalBody object is created + * @param userEnvironment the PhysicalEnvironment to use for this Viewer; + * if it is null, a default PhysicalEnvironment object is created + * @param setVisible determines if the Frames should be set to visible once created + * @since Java3D 1.3 + */ + public Viewer(Canvas3D[] userCanvases, PhysicalBody userBody, + PhysicalEnvironment userEnvironment, boolean setVisible ) { + + if (userBody == null) { + physicalBody = new PhysicalBody(); + } else { + physicalBody = userBody; + } + + if (userEnvironment == null) { + physicalEnvironment = new PhysicalEnvironment(); + } else { + physicalEnvironment = userEnvironment; + } + + // Create Canvas3D object if none was passed in. + if (userCanvases == null) { + GraphicsConfiguration config = + ConfiguredUniverse.getPreferredConfiguration(); + + canvases = new Canvas3D[1]; + canvases[0] = new Canvas3D(config); + try { + canvases[0].setFocusable( true ); + } catch(NoSuchMethodError e) {} + createFramesAndPanels(setVisible); + } + else { + canvases = new Canvas3D[userCanvases.length]; + for (int i=0; i= devices.length) + throw new ArrayIndexOutOfBoundsException + (cs[i].errorMessage + (cs[i].creatingCommand, + "Screen " + cs[i].frameBufferNumber + " is invalid; " + + (devices.length-1) + " is the maximum local index.")); + + Rectangle bounds; + Container contentPane; + GraphicsConfiguration cfg = + devices[cs[i].frameBufferNumber].getBestConfiguration(tpl3D); + + if (cfg == null) + throw new RuntimeException + ("\nNo GraphicsConfiguration on screen " + + cs[i].frameBufferNumber + " conforms to template"); + + bounds = cfg.getBounds(); + cs[i].j3dJFrame = j3dJFrames[i] = + new JFrame(cs[i].instanceName, cfg); + + if (cs[i].noBorderFullScreen) { + try { + // Required by JDK 1.4 AWT for borderless full screen. + j3dJFrames[i].setUndecorated(true); + + cs[i].j3dWindow = j3dWindows[i] = j3dJFrames[i]; + contentPane = j3dJFrames[i].getContentPane(); + } + catch (NoSuchMethodError e) { + // Handle borderless full screen running under JDK 1.3.1. + JWindow jwin = new JWindow(j3dJFrames[i], cfg); + + cs[i].j3dWindow = j3dWindows[i] = jwin; + contentPane = jwin.getContentPane(); + } + + contentPane.setLayout(new BorderLayout()); + j3dWindows[i].setSize(bounds.width, bounds.height); + j3dWindows[i].setLocation(bounds.x, bounds.y); + } + else { + cs[i].j3dWindow = j3dWindows[i] = j3dJFrames[i]; + + contentPane = j3dJFrames[i].getContentPane(); + contentPane.setLayout(new BorderLayout()); + + if (cs[i].fullScreen) { + j3dWindows[i].setSize(bounds.width, bounds.height); + j3dWindows[i].setLocation(bounds.x, bounds.y); + } + else { + j3dWindows[i].setSize(cs[i].windowWidthInPixels, + cs[i].windowHeightInPixels); + j3dWindows[i].setLocation(bounds.x + cs[i].windowX, + bounds.y + cs[i].windowY) ; + } + } + + // Create a Canvas3D and set its attributes. + cs[i].j3dCanvas = canvases[i] = new Canvas3D(cfg); + canvases[i].setStereoEnable(cv.stereoEnable); + canvases[i].setMonoscopicViewPolicy(cs[i].monoscopicViewPolicy); + + // Get the Screen3D and set its attributes. + Screen3D screen = canvases[i].getScreen3D(); + + if (cs[i].physicalScreenWidth != 0.0) + screen.setPhysicalScreenWidth(cs[i].physicalScreenWidth); + + if (cs[i].physicalScreenHeight != 0.0) + screen.setPhysicalScreenHeight(cs[i].physicalScreenHeight); + + if (cs[i].trackerBaseToImagePlate != null) + screen.setTrackerBaseToImagePlate + (new Transform3D(cs[i].trackerBaseToImagePlate)); + + if (cs[i].headTrackerToLeftImagePlate != null) + screen.setHeadTrackerToLeftImagePlate + (new Transform3D(cs[i].headTrackerToLeftImagePlate)); + + if (cs[i].headTrackerToRightImagePlate != null) + screen.setHeadTrackerToRightImagePlate + (new Transform3D(cs[i].headTrackerToRightImagePlate)); + + // Put the Canvas3D into a JPanel. + cs[i].j3dJPanel = j3dJPanels[i] = new JPanel(); + j3dJPanels[i].setLayout(new BorderLayout()); + j3dJPanels[i].add("Center", canvases[i]); + + // Put the JPanel into the content pane used by JWindow or JFrame. + contentPane.add("Center", j3dJPanels[i]); + + // Attach the Canvas3D to the View. + view.addCanvas3D(canvases[i]); + + // Add a windowListener to detect the window close event. + addWindowCloseListener(j3dWindows[i]); + + // Set Canvas3D focus as required by the JDK 1.4 focus model for + // full screen frames. JDK 1.3.1 sets the focus automatically for + // full screen components. + try { + canvases[i].setFocusable(true) ; + } + catch (NoSuchMethodError e) { + } + + if (debug) { + System.out.println("Viewer: created Canvas3D for screen " + + cs[i].frameBufferNumber + " with size\n " + + j3dWindows[i].getSize()); + System.out.println("Screen3D[" + i + "]: size in pixels (" + + screen.getSize().width + " x " + + screen.getSize().height + ")"); + System.out.println(" physical size in meters: (" + + screen.getPhysicalScreenWidth() + " x " + + screen.getPhysicalScreenHeight() + ")"); + System.out.println(" hashCode = " + screen.hashCode() + "\n"); + } + } + + if (setVisible) + // Call setVisible() on all created Window components. + setVisible(true); + } + + // Create the JFrames and JPanels for application-supplied Canvas3D + // objects. + private void createFramesAndPanels( boolean setVisible ) { + j3dJFrames = new JFrame[canvases.length]; + j3dJPanels = new JPanel[canvases.length]; + j3dWindows = new Window[canvases.length]; + + for (int i = 0; i < canvases.length; i++) { + j3dWindows[i] = j3dJFrames[i] = new JFrame(); + j3dJFrames[i].getContentPane().setLayout(new BorderLayout()); + j3dJFrames[i].setSize(256, 256); + + // Put the Canvas3D into a JPanel. + j3dJPanels[i] = new JPanel(); + j3dJPanels[i].setLayout(new BorderLayout()); + j3dJPanels[i].add("Center", canvases[i]); + j3dJFrames[i].getContentPane().add("Center", j3dJPanels[i]); + if (setVisible) { + j3dJFrames[i].setVisible(true); + } + addWindowCloseListener(j3dJFrames[i]); + } + } + + /** + * Call setVisible() on all Window components created by this Viewer. + * + * @param visible boolean to be passed to the setVisible() calls on the + * Window components created by this Viewer + * @since Java3D 1.3 + */ + public void setVisible(boolean visible) { + for (int i = 0; i < j3dWindows.length; i++) { + j3dWindows[i].setVisible(visible); + } + } + + /** + * Returns the View object associated with the Viewer object. + * + * @return The View object of this Viewer. + */ + public View getView() { + return view; + } + + /** + * Set the ViewingPlatform object used by this Viewer. + * + * @param platform The ViewingPlatform object to set for this + * Viewer object. Use null to unset the current value and + * not assign assign a new ViewingPlatform object. + */ + public void setViewingPlatform(ViewingPlatform platform) { + if (viewingPlatform != null) { + viewingPlatform.removeViewer(this); + } + + viewingPlatform = platform; + + if (platform != null) { + view.attachViewPlatform(platform.getViewPlatform()); + platform.addViewer(this); + + if (avatar != null) + viewingPlatform.setAvatar(this, avatar); + } + else + view.attachViewPlatform(null); + } + /** + * Get the ViewingPlatform object used by this Viewer. + * + * @return The ViewingPlatform object used by this + * Viewer object. + */ + public ViewingPlatform getViewingPlatform() { + return viewingPlatform; + } + + /** + * Sets the geometry to be associated with the viewer's avatar. The + * avatar is the geometry used to represent the viewer in the virtual + * world. + * + * @param avatar The geometry to associate with this Viewer object. + * Passing in null will cause any geometry associated with the Viewer + * to be removed from the scen graph. + */ + public void setAvatar(ViewerAvatar avatar) { + // Just return if trying to set the same ViewerAvatar object. + if (this.avatar == avatar) + return; + + this.avatar = avatar; + if (viewingPlatform != null) + viewingPlatform.setAvatar(this, this.avatar); + } + + /** + * Gets the geometry associated with the viewer's avatar. The + * avatar is the geometry used to represent the viewer in the virtual + * world. + * + * @return The root of the scene graph that is used to represent the + * viewer's avatar. + */ + public ViewerAvatar getAvatar() { + return avatar; + } + + /** + * Returns the PhysicalBody object associated with the Viewer object. + * + * @return A reference to the PhysicalBody object. + */ + public PhysicalBody getPhysicalBody() { + return physicalBody; + } + + /** + * Returns the PhysicalEnvironment object associated with the Viewer + * object. + * + * @return A reference to the PhysicalEnvironment object. + */ + public PhysicalEnvironment getPhysicalEnvironment() { + return physicalEnvironment; + } + + /** + * Returns the 0th Canvas3D object associated with this Viewer object + * + * @return a reference to the 0th Canvas3D object associated with this + * Viewer object + * @since Java3D 1.3 + */ + public Canvas3D getCanvas3D() { + return canvases[0]; + } + + /** + * Returns the Canvas3D object at the specified index associated with + * this Viewer object. + * + * @param canvasNum the index of the Canvas3D object to retrieve; + * if there is no Canvas3D object for the given index, null is returned + * @return a reference to a Canvas3D object associated with this + * Viewer object + * @since Java3D 1.3 + */ + public Canvas3D getCanvas3D(int canvasNum) { + if (canvasNum > canvases.length) { + return null; + } + return canvases[canvasNum]; + } + + /** + * Returns all the Canvas3D objects associated with this Viewer object. + * + * @return an array of references to the Canvas3D objects associated with + * this Viewer object + * @since Java3D 1.3 + */ + public Canvas3D[] getCanvas3Ds() { + Canvas3D[] ret = new Canvas3D[canvases.length]; + for (int i = 0; i < canvases.length; i++) { + ret[i] = canvases[i]; + } + return ret; + } + + /** + * Returns the canvas associated with this Viewer object. + * @deprecated superceded by getCanvas3D() + */ + public Canvas3D getCanvases() { + return getCanvas3D(); + } + + /** + * This method is no longer supported since Java 3D 1.3. + * @exception UnsupportedOperationException if called. + * @deprecated AWT Frame components are no longer created by the + * Viewer class. + */ + public Frame getFrame() { + throw new UnsupportedOperationException + ("\nAWT Frame components are not created by the Viewer class"); + } + + /** + * Returns the JFrame object created by this Viewer object at the + * specified index. If a Viewer is constructed without any Canvas3D + * objects then the Viewer object will create a Canva3D object, a JPanel + * containing the Canvas3D object, and a JFrame to place the JPanel in. + *

+ * NOTE: When running under JDK 1.4 or newer, the JFrame always directly + * contains the JPanel which contains the Canvas3D. When running under + * JDK 1.3.1 and creating a borderless full screen through a configuration + * file, the JFrame will instead contain a JWindow which will contain the + * JPanel and Canvas3D. + *

+ * @param frameNum the index of the JFrame object to retrieve; + * if there is no JFrame object for the given index, null is returned + * @return a reference to JFrame object created by this Viewer object + * @since Java3D 1.3 + */ + public JFrame getJFrame(int frameNum) { + if (j3dJFrames == null || frameNum > j3dJFrames.length) { + return(null); + } + return j3dJFrames[frameNum]; + } + + /** + * Returns all the JFrames created by this Viewer object. If a Viewer is + * constructed without any Canvas3D objects then the Viewer object will + * create a Canva3D object, a JPanel containing the Canvas3D object, and a + * JFrame to place the JPanel in.

+ * + * NOTE: When running under JDK 1.4 or newer, the JFrame always directly + * contains the JPanel which contains the Canvas3D. When running under + * JDK 1.3.1 and creating a borderless full screen through a configuration + * file, the JFrame will instead contain a JWindow which will contain the + * JPanel and Canvas3D.

+ * + * @return an array of references to the JFrame objects created by + * this Viewer object, or null if no JFrame objects were created + * @since Java3D 1.3 + */ + public JFrame[] getJFrames() { + if (j3dJFrames == null) + return null; + + JFrame[] ret = new JFrame[j3dJFrames.length]; + for (int i = 0; i < j3dJFrames.length; i++) { + ret[i] = j3dJFrames[i]; + } + return ret; + } + + /** + * This method is no longer supported since Java 3D 1.3. + * @exception UnsupportedOperationException if called. + * @deprecated AWT Panel components are no longer created by the + * Viewer class. + */ + public Panel getPanel() { + throw new UnsupportedOperationException + ("\nAWT Panel components are not created by the Viewer class"); + } + + /** + * Returns the JPanel object created by this Viewer object at the + * specified index. If a Viewer is constructed without any Canvas3D + * objects then the Viewer object will create a Canva3D object and a + * JPanel into which to place the Canvas3D object. + * + * @param panelNum the index of the JPanel object to retrieve; + * if there is no JPanel object for the given index, null is returned + * @return a reference to a JPanel object created by this Viewer object + * @since Java3D 1.3 + */ + public JPanel getJPanel(int panelNum) { + if (j3dJPanels == null || panelNum > j3dJPanels.length) { + return(null); + } + return j3dJPanels[panelNum]; + } + + /** + * Returns all the JPanel objects created by this Viewer object. If a + * Viewer is constructed without any Canvas3D objects then the Viewer + * object will create a Canva3D object and a JPanel into which to place + * the Canvas3D object. + * + * @return an array of references to the JPanel objects created by + * this Viewer object, or null or no JPanel objects were created + * @since Java3D 1.3 + */ + public JPanel[] getJPanels() { + if (j3dJPanels == null) + return null; + + JPanel[] ret = new JPanel[j3dJPanels.length]; + for (int i = 0; i < j3dJPanels.length; i++) { + ret[i] = j3dJPanels[i]; + } + return ret; + } + + /** + * Used to create and initialize a default AudioDevice3D used for sound + * rendering. + * + * @return reference to created AudioDevice, or null if error occurs. + */ + public AudioDevice createAudioDevice() { + if (physicalEnvironment != null) { + AudioDevice3DL2 mixer = new JavaSoundMixer(physicalEnvironment); + mixer.initialize(); + return mixer; + } + else + return null; + } + + /** + * Returns the Universe to which this Viewer is attached + * + * @return the Universe to which this Viewer is attached + * @since Java 3D 1.3 + */ + public SimpleUniverse getUniverse() { + return getViewingPlatform().getUniverse(); + } + + + /* + * Exit if run as an application + */ + void addWindowCloseListener(Window win) { + SecurityManager sm = System.getSecurityManager(); + boolean doExit = true; + + if (sm != null) { + try { + sm.checkExit(0); + } catch (SecurityException e) { + doExit = false; + } + } + final boolean _doExit = doExit; + + win.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent winEvent) { + Window w = winEvent.getWindow(); + w.hide(); + try { + w.dispose(); + } catch (IllegalStateException e) {} + if (_doExit) { + System.exit(0); + } + } + }); + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ViewerAvatar.java b/src/classes/share/com/sun/j3d/utils/universe/ViewerAvatar.java new file mode 100644 index 0000000..e679763 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ViewerAvatar.java @@ -0,0 +1,66 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import javax.media.j3d.*; +import javax.vecmath.*; + +/** + * This class holds geomtry that should be associated with the View's + * avatar. An avatar is how the user's "virtual self" appears in the + * virtual world. + * + * @see Viewer + */ +public class ViewerAvatar extends BranchGroup { + + /** + * Constructs an instance of the ViewerAvatar node. + */ + public ViewerAvatar() { + setCapability(ALLOW_DETACH); + } + +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/ViewingPlatform.java b/src/classes/share/com/sun/j3d/utils/universe/ViewingPlatform.java new file mode 100644 index 0000000..ff82848 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/ViewingPlatform.java @@ -0,0 +1,519 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package com.sun.j3d.utils.universe; + +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.awt.Component; +import javax.media.j3d.*; +import javax.vecmath.*; +import com.sun.j3d.utils.behaviors.vp.*; +import com.sun.j3d.internal.J3dUtilsI18N; + +/** + * This class is used to set up the "view" side of a Java 3D scene graph. + * The ViewingPlatform object contains a MultiTransformGroup node to allow + * for a series of transforms to be linked together. To this structure + * the ViewPlatform is added as well as any geometry to associate with this + * view platform. + * + * @see ViewPlatform + */ +public class ViewingPlatform extends BranchGroup { + + /** + * Cached ViewPlatform associated with this ViewingPlatform object. + */ + protected ViewPlatform viewPlatform; + + /** + * MultiTransformGroup that holds all TransformGroups between + * the BranchGroup and the View object. + */ + protected MultiTransformGroup mtg; + + /** + * Used to keep track of added geometry. When geometry + * is added to the view platform, an addChild to this BranchGroup + * is performed. + */ + protected BranchGroup platformGeometryRoot; + + /** + * Used to keep track of added geometry. When geometry + * is added for an avatar, an addChild to this BranchGroup + * is performed. + */ + protected BranchGroup avatarRoot; + + /** + * Cached PlatformGeometry object. + */ + protected PlatformGeometry platformGeometry = null; + + /** + * Table of the Viewer objects. + */ + protected Hashtable viewerList; + + /** + * Used to keep track of behaviors. + * + * @since Java 3D 1.2.1 + */ + protected BranchGroup behaviors; + + /** + * The universe to which this viewing platform is attached + * + * @since Java 3D 1.3 + */ + protected SimpleUniverse universe; + + /** + * Creates a default ViewingPlatform object. This consists of a + * MultiTransfromGroup node with one transform and a ViewPlatform + * object. The ViewPlatform is positioned at (0.0, 0.0, 0.0). + */ + public ViewingPlatform() { + // Call main constructor with default values. + this(1); + } + + /** + * Creates the ViewingPlatform object. This consists of a + * MultiTransfromGroup node with the specified number of transforms + * (all initialized to the identity transform). + * and a ViewPlatform object. + * + * @param numTransforms The number of transforms the MultiTransformGroup + * node should contain. If this number is less than 1, 1 is assumed. + */ + public ViewingPlatform(int numTransforms) { + viewerList = new Hashtable(); + + // Set default capabilities for this node. + setCapability(Group.ALLOW_CHILDREN_WRITE); + setCapability(Group.ALLOW_CHILDREN_EXTEND); + setCapability(BranchGroup.ALLOW_DETACH); + + // Create MultiTransformGroup node. + if (numTransforms < 1) + numTransforms = 1; + mtg = new MultiTransformGroup(numTransforms); + + // Get first transform and add it to the scene graph. + TransformGroup tg = mtg.getTransformGroup(0); + addChild(tg); + + // Create ViewPlatform and add it to the last transform in the + // MultiTransformGroup node. + tg = mtg.getTransformGroup(numTransforms - 1); + viewPlatform = new ViewPlatform(); + viewPlatform.setCapability(ViewPlatform.ALLOW_POLICY_READ); + viewPlatform.setCapability(ViewPlatform.ALLOW_POLICY_WRITE); + tg.addChild(viewPlatform); + + // Set capabilities to allow for changes when live. + tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ); + tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE); + + // Initialize the avatarRoot BranchGroup node and add it to the + // last transform in the MultiTransformGroup node. + avatarRoot = new BranchGroup(); + avatarRoot.setCapability(Group.ALLOW_CHILDREN_READ); + avatarRoot.setCapability(Group.ALLOW_CHILDREN_WRITE); + avatarRoot.setCapability(Group.ALLOW_CHILDREN_EXTEND); + tg.addChild(avatarRoot); + + // Initialize the platformGeometry BranchGroup node and add it to the + // last transform in the MultiTransformGroup node. + platformGeometryRoot = new BranchGroup(); + platformGeometryRoot.setCapability(Group.ALLOW_CHILDREN_READ); + platformGeometryRoot.setCapability(Group.ALLOW_CHILDREN_WRITE); + platformGeometryRoot.setCapability(Group.ALLOW_CHILDREN_EXTEND); + tg.addChild(platformGeometryRoot); + } + + /** + * Sets the ViewPlatform node for this ViewingPlatform object. + * + * @param vp The ViewPlatform node to associate with this ViewingPlatform + * object. + */ + public void setViewPlatform(ViewPlatform vp) { + TransformGroup tg = getViewPlatformTransform(); + tg.removeChild(viewPlatform); + tg.addChild(vp); + viewPlatform = vp; + // Assign this to all Viewers. + Enumeration e = viewerList.keys(); + + while (e.hasMoreElements()) + ((Viewer)e.nextElement()).setViewingPlatform(this); + } + + /** + * Returns the ViewPlatform node for this ViewingPlatform object. + * + * @return The ViewPlatform node associated with this ViewingPlatform + * object. + */ + public ViewPlatform getViewPlatform() { + return viewPlatform; + } + + /** + * Assigns the geometry to associate with the ViewingPlatform. + * PlatformGeometry is used to hold any geometry to be associated + * with the ViewingPlatform. If the ViewingPlatform is to be the + * inside of a car, for instance, than the PlatformGeometry could be + * the dashboard of the car. + * + * @param pg The geometry to be associated with this ViewingPlatform. + * Passing in null has the effect of deleting any geometry associated + * with this ViewingPlatform. + */ + public void setPlatformGeometry(PlatformGeometry pg) { + // Just return if trying to set the same PlatformGeometry object. + if (platformGeometry == pg) + return; + + // If the PlatformGeometry is null, will be removing any geometry + // already present. + if (pg == null) { + if (platformGeometryRoot.numChildren() != 0) + platformGeometryRoot.removeChild(0); + } + else { + + // See if there is an old PlatformGeometry to replace. + if (platformGeometryRoot.numChildren() != 0) + platformGeometryRoot.setChild(pg, 0); + else { + platformGeometryRoot.addChild(pg); + } + } + platformGeometry = pg; + } + + /** + * Returns the PlatformGeometry associated with this ViewingPlatform + * + * @return The PlatformGeometry associated with this ViewingPlatform + */ + public PlatformGeometry getPlatformGeometry() { + return platformGeometry; + } + + /** + * Returns the MultitransformGroup object for this + * ViewingPlatform object. + * + * @return The MultitransformGroup object. + */ + public MultiTransformGroup getMultiTransformGroup() { + return mtg; + } + + /** + * Returns a reference to the "bottom most" transform in the + * MultiTransformGroup that is above the ViewPlatform node. + * + * @return The TransformGroup that is immediately above the + * ViewPlatform object. + */ + public TransformGroup getViewPlatformTransform() { + return mtg.getTransformGroup(mtg.getNumTransforms() - 1); + } + + /** + * Sets the nominal viewing distance in the ViewPlatform transform based + * on the current field of view. If the ViewAttachPolicy is not the + * default of View.NOMINAL_HEAD, then this method has no effect.

+ * + * The ViewPlatform is moved back along Z so that objects at the origin + * spanning the normalized X range of -1.0 to +1.0 can be fully viewed + * across the width of the window. This is done by setting a translation + * of 1/(tan(fieldOfView/2)) in the ViewPlatform transform.

+ * + * If there is no Viewer object associated with this ViewingPlatform + * object the default field of view of PI/4.0 is used.

+ * + * NOTE: Support for multiple Viewer objects is not available. If + * multiple viewers are attached to this ViewingPlatform than a + * RuntimeException will be thrown. + */ + public void setNominalViewingTransform() { + if (viewPlatform.getViewAttachPolicy() == View.NOMINAL_HEAD) { + double fieldOfView; + + if (viewerList.size() == 0) { + // No Viewer associated with this ViewingPlatform, so use the + // default field of view value to move the ViewingPlatform. + fieldOfView = Math.PI/4.0; + } + else { + if (viewerList.size() > 1) { + throw new RuntimeException + (J3dUtilsI18N.getString("ViewingPlatform0")); + } + + Viewer viewer = (Viewer)viewerList.keys().nextElement(); + View view = viewer.getView(); + fieldOfView = view.getFieldOfView(); + } + + Transform3D t3d = new Transform3D(); + double viewDistance = 1.0/Math.tan(fieldOfView/2.0); + t3d.set(new Vector3d(0.0, 0.0, viewDistance)); + getViewPlatformTransform().setTransform(t3d); + } + } + + /** + * Returns the avatarRoot child number of the ViewerAvatar object. + * All the children of the avatarRoot are compared with the passed + * in ViewerAvatar. If a match is found, the index is returned. + * + * @param avatar The ViewerAvatar object to look for in the avatarRoot's + * child nodes. + * @return The index of the child that corresponds to the ViewerAvatar. + * If the avatarRoot does not contain the ViewerAvatar -1 is returned. + */ + private int findAvatarChild(ViewerAvatar avatar) { + // Search the avatarRoot for the ViewerAvatar associated with + // with the Viewer object + for (int i = 0; i < avatarRoot.numChildren(); i++) { + if (((ViewerAvatar)avatarRoot.getChild(i)) == avatar) + return i; + } + + // Should never get here. + System.err.println("ViewingPlatform.findAvatarChild:Child not found."); + return -1; + } + + /** + * Adds the ViewerAvatar to the scene graph. An avatar (geometry) + * can be associated with a Viewer object and displayed by Java 3D. + * + * @param viewer The viewer object to associate with this avatar. + * @param avatar The avatar to add to the scene graph. Passing in + * null removes any currently assigned avatar. + */ + void setAvatar(Viewer viewer, ViewerAvatar avatar) { + Object oldAvatar = viewerList.get(viewer); + + // A position of -1 means the avatar is not a child of the avatarRoot. + int avatarPosition = -1; + + // Because "null" cannot be used in a put the avatarRoot object + // is used to signify that there is no ViewerAvatar associated + // with this Viewer. + if (oldAvatar != avatarRoot) + avatarPosition = findAvatarChild((ViewerAvatar)oldAvatar); + + // If the avatar is null, will be removing any geometry already present. + if (avatar == null) { + if (avatarPosition != -1) { + avatarRoot.removeChild(avatarPosition); + + // Reset hashtable entry - avatarRoot == null. + viewerList.put(viewer, avatarRoot); + } + } + else { + // see if there is an old ViewerAvater to replace + if (avatarPosition != -1) + avatarRoot.setChild(avatar, avatarPosition); + else + avatarRoot.addChild(avatar); + + // Update hashtable with new avatar. + viewerList.put(viewer, avatar); + } + } + + /** + * When a ViewingPlatform is set by a Viewer, the ViewingPlatform + * needs to be informed, via a call to this method. This will add + * the Viewer to the ViewingPlatform's viewerList for use when + * things such as the PlatformGeometry are changed and all Viewer + * scene graphs need to be modified. + */ + void addViewer(Viewer viewer) { + // Because the viewerList is also used to associate ViewerAvatars + // with Viewer objects a hashtable is used. This routine does not + // check for the presence of a ViewerAvatar but the Viewer still + // needs to be added to the hashtable. Because "null" cannot be + // used in a put the avatarRoot object is used to signify that there + // is no ViewerAvatar associated with this Viewer. + viewerList.put(viewer, avatarRoot); + } + + /* + * Cleanup when Viewer set another ViewingPlatform + */ + void removeViewer(Viewer viewer) { + viewerList.remove(viewer); + } + + /** + * Adds a new ViewPlatformBehavior to the ViewingPlatform + */ + void addViewPlatformBehavior(ViewPlatformBehavior behavior) { + behavior.setViewingPlatform(this); + if (behaviors == null) { + behaviors = new BranchGroup(); + behaviors.setCapability(BranchGroup.ALLOW_DETACH); + behaviors.setCapability(BranchGroup.ALLOW_CHILDREN_READ); + } + // otherwise detach the BranchGroup so we can add to it + else { + behaviors.detach(); + } + behaviors.addChild(behavior); + this.addChild(behaviors); + } + + /** + * Sets the ViewPlatformBehavior which will operate on the ViewPlatform + * transform (the TransformGroup returned by + * ViewingPlatform.getViewPlatformTransform()). The ViewPlatformBehavior + * may be set after the ViewingPlatform is setLive(). + * If a behavior is already present, it will be detached and it's + * setViewingPlatform method will be called with a parameter of null. + * @param behavior The ViewPlatformBehavior to add to the ViewingPlatform. + * null will remove the ViewingPlatform behavior. + * @since Java 3D 1.2.1 + */ + public void setViewPlatformBehavior(ViewPlatformBehavior behavior) { + if (behaviors != null) { + removeViewPlatformBehavior((ViewPlatformBehavior)behaviors.getChild(0)); + } + if (behavior != null) { + addViewPlatformBehavior(behavior); + } + } + + /** + * Removes the specified ViewPlatformBehavior + */ + void removeViewPlatformBehavior(ViewPlatformBehavior behavior) { + // remove from the behaviors branch group + if (behaviors != null) { + behaviors.detach(); + for (int i = 0; i < behaviors.numChildren(); i++) { + if (behaviors.getChild(i) == behavior) { + behavior.setViewingPlatform( null ); + behaviors.removeChild(i); + break; + } + } + if (behaviors.numChildren() == 0) behaviors = null; + else this.addChild(behaviors); + } + } + + /** + * Returns the number of ViewPlatformBehaviors on the ViewingPlatform + */ + int getViewPlatformBehaviorCount() { + return behaviors.numChildren(); + } + + /** + * Returns the ViewPlatformBehavior at the specified index + */ + ViewPlatformBehavior getViewPlatformBehavior(int index) { + return (ViewPlatformBehavior)behaviors.getChild(index); + } + + /** + * Returns the ViewPlatformBehavior + * @return the ViewPlatformBehavior for the ViewingPlatform. + * Returns null if there is no ViewPlatformBehavior set. + * @since Java 3D 1.2.1 + */ + public ViewPlatformBehavior getViewPlatformBehavior() { + if (behaviors == null) { + return null; + } + return getViewPlatformBehavior(0); + } + + /** + * Returns the Viewers attached to this ViewingPlatform + * + * @return the Viewers attached to this viewing platform + * @since Java 3D 1.3 + */ + public Viewer[] getViewers() { + if (viewerList.size() == 0) return null; + return (Viewer[])viewerList.keySet().toArray( new Viewer[0] ); + } + + /** + * Returns the Universe to which this ViewingPlatform is attached + * + * @return the Universe to which this ViewingPlatform is attached + * @since Java 3D 1.3 + */ + public SimpleUniverse getUniverse() { + return universe; + } + + /** + * Sets the Universe to which this ViewingPlatform is attached + * + * @param universe the Universe to which this ViewingPlatform is attached + * @since Java 3D 1.3 + */ + public void setUniverse( SimpleUniverse universe ) { + this.universe = universe; + } +} diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-examples.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-examples.html new file mode 100644 index 0000000..8533368 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-examples.html @@ -0,0 +1,85 @@ + + + + + + + + + + +  +

+Example Configuration Files

+ +


j3d1x1  A single fullscreen +desktop configuration. +

j3d1x1-behavior  A single +fullscreen desktop configuration with a configurable view platform behavior. +

j3d1x1-stereo  A single +fullscreen desktop configuration with stereo viewing. +

j3d1x1-vr  A single fullscreen +desktop configuration with head tracker, stereo viewing, and 6 degree of +freedom mouse. +

j3d1x1-window  A single screen +desktop configuration with a conventional window. +

j3d1x2-flat  A dual-screen planar +desktop configuration. +

j3d1x2-rot30  A dual-screen +desktop configuration with each screen rotated toward the other by 30 degrees. +

j3d1x3-cave  A three-projector cave +configuration. +

j3d1x3-cave-vr  A +three-projector cave configuration with head tracking and stereo viewing. +

j3d1x3-rot45  A three-screen +desktop configuration with left and right screens rotated 45 degrees from the +center screen. +

j3d2x2-flat  A four-projector +configuration arranged in a 2x2 array. +
  + + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-syntax.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-syntax.html new file mode 100644 index 0000000..9198032 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/config-syntax.html @@ -0,0 +1,1973 @@ + + + + + + + The Java 3D Configuration File + + + + + + + + + + +
+The Java 3D Configuration File
+
+ +Syntax Description
+Command Overview
+ + Overview of Relevant View Model Parameters
+Top-Level Command Details
+Built-In Command Details
+Command Index
+Property Index
+ +

+This document is an informal description of the syntax of the Java 3D +configuration file and a tutorial of the semantics of its various commands. +Such a file is written by a user or site administrator to describe the physical +configuration of a local interactive viewing environment. Configuration +properties that can be described in the file include the sizes, positions, and +orientations of displays in either fixed screen environments or head mounted +displays (HMD devices), as well as the input devices and sensors +available for user interaction apart from the keyboard and mouse abstractions +provided by the AWT.

+

+A configuration file is used by passing its URL to either a ConfigContainer or +a ConfiguredUniverse constructor. The method by which a user specifies the +file is up to the application, but the universe utilities do provide a few +means to easily enable an application to perform this task. These depend upon +a Java 3D property, j3d.configURL, that the user can set on the java +command line with the -D option. Its value should be a URL string +indicating the location of the desired file. The application can then either +call the static ConfigContainer methods +getConfigURL +to retrieve the value of the property, or + +getConfigURL(String) to specify a default file to be used in case the +property is not set. Applications are encouraged to devise their own +user-friendly mechanisms to retrieve the configuration file, although the +setting of the j3d.configURL property should be honored if at all +possible.

+

+If the attempt to open the resource indicated by the URL is successful, then a +parser will be invoked to read and evaluate the commands it contains and +deposit the results in the ConfigContainer. The parser will detect syntax +errors, invalid commands, and bad references, printing descriptive messages to +System.out, including the line number and text of the offending command. In +general the parser attempts to continue processing as much of the file as it +can when encountering an error. Some errors can only be detected after the +entire file has been evaluated; in those cases an exception will be thrown.

+

+An application may choose to override the settings of the configuration file by +accessing view-side scenegraph components directly from the ConfigContainer +once the file is evaluated. Applications should avoid this in general, as most +settings are physical calibration constants specific to the local interactive +viewing environment. Nonetheless, application overrides are still sometimes +appropriate; for example, the application may have knowledge of the contents of +the scenegraph that enables it to make a better judgement of where the view's +clipping planes should be.

+
+ + + + + + +
+Syntax Description
+

+The configuration file syntax is very simple; scanning any of the +sample configuration files should provide +the general idea. At the broadest level there are two main types of +constructs: comments and commands.

+

+Comments can be either C or C++ style. In a C-style +comment all text between successive occurances of /* and */ is ignored. +A C++ comment begins with // and continues to the end of the line.

+

+A command begins with an opening parenthesis and ends with a closing +parenthesis. The elements between the parentheses can be of four types: +alphanumeric strings, numbers, quoted strings, or other commands. During the +evaluation of a command, any nested command encountered is itself evaluated, +and the result of that evaluation replaces the nested command within the +original outer command.

+

+Strings that contain embedded white space, forward slashes, or invalid +tokens must be enclosed in quotes (either single or double). Common cases are +URL strings and Unix path names. Numbers are any non-quoted strings that can +be parsed as double-precision floating point numbers. The strings true, +True, false, and False are converted to their corresponding boolean +values. Strings, quoted strings, and numbers are delimited from each other by +white space.

+

+Commands in the configuration file have four special forms: point, +matrix, top-level, and built-in commands.

+ +
+

+Points

+ +A command that consists entirely of two, three, or four numbers is a 2D point, +a 3D point, or a 4D point respectively. Any other command that starts with a +number is a syntax error. +

+Don't pass 2D, 3D, or 4D points to commands that expect two, three, or four +numbers instead. This will generate a syntax error indicating an invalid +number of arguments.

+ +

+Matrices

+ +A 3D matrix is a command that consists entirely of three 3D points. A 4D +matrix consists entirely of either three or four 4D points; if there are only +three 4D points then the fourth is implicitly considered to be (0.0 0.0 0.0 +1.0). The points define the row elements of each type of matrix. Any other +command that starts with a point is a syntax error. + +

+Top-level and built-in commands

+ +All other commands start with an alphanumeric string, the command name +which identifies it. The remaining elements of the command are its arguments. +

+Command names can either specify top-level or built-in commands. Top-level +commands generally configure Java 3D core and utility classes and can only +appear at the outermost level of parentheses. Built-in commands are provided +by the parser itself to help construct the arguments to top-level commands. +Points, matrices, and built-in commands can only be nested within other +commands.

+

+An error will result if points, matrices, or built-in commands are invoked at +the outermost level of nesting. Sometimes this error is caused by prematurely +closing the opening parenthesis of the current top-level command. Such an +error is usually preceded by an error from the command indicating an invalid +number of arguments.

+

+Similarly, errors will be generated if top-level commands are nested. Errors +to this effect are sometimes caused by failing to close all the open +parentheses of the preceding top-level command.

+
+ +

+Java property substitution syntax

+ +All strings are additionally scanned for text enclosed by a starting ${ and a +matching }. Such text is looked up as a Java system property name and the +result is substituted back into the string after eliding the starting and +ending delimiters. For example, the command: +

+(Include "file:${user.home}/myBody.cfg")

+

+would evaluate the contents of the file "myBody.cfg" in the user's home +directory on Unix systems. An error is issued if no Java property exists with +the specified name.

+

+Java property substitution happens early, after tokenization but before command +evaluation. Substitution can occur any number of times anywhere within any +quoted or non-quoted string, including command names, property names, and +property values, as long as the property substitution syntax is not nested.

+
+ + + + + + +
+Command Overview
+

+Most top-level commands configure concrete Java 3D core and utility classes. +These include PhysicalBody, PhysicalEnvironment, Screen, Sensor, View, and +ViewPlatform. The Screen, View, and ViewPlatform commands also implicitly +configure the Canvas3D core class and the Viewer and ViewingPlatform utility +classes respectively.

+

+These commands come in two forms: the New<class name> and +<class name>Property commands. All New commands except +NewSensor create new class instances and bind names to +them, while the Property commands refer to these names and configure +their corresponding class instances with the parameters specified by the +command arguments. All references must be to objects previously instantiated +in the file; forward referencing is not supported. Names must be unique within +each class.

+

+Implementations of the Java 3D InputDevice interface and concrete subclasses of +the abstract ViewPlatformBehavior utility class can also be instantiated and +configured with the same command forms. The New commands for these +objects accept class names as command arguments and instantiate the objects +through introspection. Since the details of the implementations are not known, +configuration parameters are passed through methods which are invoked through +introspection of the method names specified in the Property command +arguments.

+

+A method invoked through introspection with a Property command gets its +arguments as a single array of Objects. Boolean strings get wrapped into +Boolean objects, and number strings get wrapped into Double. 2D, 3D, and 4D +points get wrapped into Point2d, Point3d, and Point4d objects respectively, +while 3D and 4D matrices get wrapped into Matrix3d and Matrix4d +respectively.

+
+ + + + + + +
+Overview of Relevant View Model Parameters
+

+The sample configuration files are annotated to assist users in modifying them +to suit their particular viewing environments, but it can be helpful to know +some of the basics of the Java 3D view model that are relevant to writing a +configuration file. This overview should only be considered an informal +adjunct to the detailed description in the Java 3D Specification. Reading the +source code to the ViewInfo utility (a public +implementation of the Java 3D view model) may also be helpful in understanding +the details of the view model.

+ + + + + +
+The Camera View Model
+

+In the traditional camera model the camera or eyepoint is positioned and +oriented with respect to the virtual world via a view transform. Objects +contained within the field of view are projected onto an image plane, which is +then mapped onto a display surface, usually a window on a desktop display +system. While the view direction and camera position can be freely oriented +and placed anywhere in the virtual world, the projection is accomplished using +a static frustum defined by the field of view and a flat image plane centered +about and normal to the view direction.

+

+This model emulates a typical physical camera quite well, but there are many +viewing configurations that cannot be implemented using image planes that are +fixed with respect to the view direction. In a multiple screen environment the +projection frustum for each screen is skewed with respect to the image plane, +based on the user's nominal viewing position. Realistic stereo views on fixed +displays use skewed projection frustums derived from the offsets of the two +eyes from the center of the image plane, while head tracking with fixed +displays involves dynamically adjusting projection frustums in response to +varying eye positions relative to the image plane.

+

+In a low-level API such as OpenGL these situations are handled by concatenating +all defined model and viewing transforms into a single modelview matrix, +and for a fixed-screen environment explicitly computing the projection +matrix for each eye, each display surface, and each new head +position. Every application handling such viewing configurations typically +reimplements the framework for computing those matrices itself. In these cases +it would be useful to be able to separate the projection components out of the +view transform in some representation based on display and eye locations.

+ + + + + +
+The Java 3D View Model
+

+Based on these requirements, a high-level view model should provide standard +mechanisms to 1) define a screen's position and orientation in relation to +other screens in the viewing environment; 2) specify the position of the user's +eyes or an HMD relative to the physical space in which the screens or nominal +user position are defined, and to have them updated automatically if tracking +hardware exists; and 3) describe where the whole physical space is placed and +oriented in the virtual world. In such a model the appropriate images could +then be rendered without further computation in application code.

+

+In the Java 3D view model, screen (image plate) positions and +orientations are defined relative to a fixed frame of reference in the physical +world, the tracker base, through the +TrackerBaseToImagePlate property. The +tracker base is somewhat abstract in that it is always used to define that +fixed frame of reference, even if no physical tracking hardware is being +used. If the ViewPolicy is HMD_VIEW, then the left +and right image plates for head mounted displays are defined relative to the +head tracking sensor, which reports head orientation and position relative to +the tracker base.

+

+Coexistence coordinates are defined with the +CoexistenceToTrackerBase property. It +provides a frame of reference in the physical world which can be set up by the +user or application in whatever way is most convenient for positioning and +orienting screens, physical body attributes, and sensing devices in the virtual +world. If tracking is enabled, then the eye positions in coexistence are +computed from the position and orientation of the head tracking sensor and the +HeadToHeadTracker matrix; otherwise the eye +positions in coexistence are set according to the +CenterEyeInCoexistence property. In HMD +mode the eye positions are fixed with respect to the image plates.

+

+The mapping of coexistence coordinates into the virtual world is accomplished +through the ViewAttachPolicy, which specifies +the point in coexistence coordinates to which the origin of the view +platform should be mapped. The view platform is positioned in the virtual +world by manipulating its view transform, the composite of all the +transforms from the view platform up to its Locale. The basis vectors (X, Y, +and Z directions) of the view platform are always aligned with coexistence +coordinates, and the scaling between view platform coordinates and physical +coordinates is specified by the +ScreenScalePolicy, so this +establishes the complete mapping of the physical world into the virtual world. +The projection of the virtual world onto the physical display surfaces is then +performed automatically by the Java 3D renderer.

+

+By default Java 3D tries to emulate the familiar camera model as much as +possible to make it easy to run in a conventional windowed desktop display +environment. It accomplishes this through the following default settings:

+ + +When a configuration file is being used the defaults are oriented towards +making the setup of multiple screen environments as easy as possible. If the +coexistence centering enable has not been explicitly set, and either the +CoexistenceToTrackerBase transform for the view has been set or +TrackerBaseToImagePlate has been set for any screen, then the following +defaults are used instead:

+ + +The avove defaults are also used if coexistence centering enable has been +explictly set false. +


+ + + + + + +
+Top-Level Command Details
+

+Top-level commands can only appear at the outermost command nesting level.

+
+ +

+NewDevice

+Syntax: +
(NewDevice <instance name> <class name> +[Alias <alias name>]) +

+This command instantiates the InputDevice implementation specified by the fully +qualified class name and binds it to instance name. The +InputDevice is instantiated through introspection of the class name. The +implementation must provide a parameterless constructor; this can usually be +done by extending or wrapping available InputDevice implementations.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+

+At the conclusion of configuration file processing all InputDevice +implementations so defined will be initialized and registered with any +PhysicalEnvironments that reference them.

+


+ +

+DeviceProperty

+Syntax: +
(DeviceProperty <instance name> <method name> <arg0> +... <argn>) +

+The details of the InputDevice implementation specified by instance name +are not known to ConfigContainer, so any parameters to be set through the +configuration file are passed to the InputDevice instance by invoking method +name through introspection. The arguments following the method name are +evaluated and the results passed to the method as an array of Objects.

+

+The required methods can usually be provided by extending or wrapping existing +InputDevice implementations.

+
+ +

+NewSensor

+Syntax: +
(NewSensor <instance name> <device name> +<sensor index> [Alias <alias name>]) +

+Retrieves the Sensor at index sensor index in the InputDevice device +name and binds it to the name specified by instance name. The +sensor index is a number truncated to its integer value. The InputDevice +implementation is responsible for creating its own Sensor objects, so this +command does not create any new instances.

+

+Instance name is used by other commands to reference the indicated +Sensor. In addition, the name and its associated Sensor instance is made +available to applications through a Map returned by the ConfigContainer +method +getNamedSensors. +

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+

+If the Sensor is to be used for head tracking, or tracking the position and +orientation of other objects in the physical world, then it must generate 6 +degree of freedom data relative to the tracker base.

+
+ +

+SensorProperty

+Syntax: +
(SensorProperty <instance name> <property name> +<property value>) +

+Sets a Sensor property. The sensor instance is specified by instance +name, the property to be set by property name, and the value to be +set by property value. The following sole property may be +configured:

+ +
+

+Hotspot

+A 3D point in the sensor's local coordinate system. The hotspot specifies the +"active" point which should interact with the virtual world, such as a point +used for picking or grabbing an object. Its actual interpretation is up to the +sensor behavior which uses it. Its value is ignored for head tracking sensors. +
+
+ +

+NewScreen
+NewWindow

+Syntax: +
(NewScreen <instance name> <device index> +[Alias <alias name>]) +
(NewWindow <instance name> <device index> +[Alias <alias name>]) +

+The above two commands are equivalent. Both create new window resources and +associate them with the name specified by instance name. The device +index is a number truncated to its integer value. This integer is the +index at which the desired AWT GraphicsDevice appears in the array returned by +the static getScreenDevices() method of GraphicsEnvironment, and specifies the +physical screen upon which the window should be created. The GraphicsDevice +order in the array is specific to the local viewing site and display +system.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+
+ +

+ScreenProperty
+WindowProperty

+Syntax: +
(ScreenProperty <instance name> <property name> +<property value>) +
(WindowProperty <instance name> <property name> +<property value>) +

+The above two commands are equivalent. The screen or window instance is +specified by instance name, the property to be set by property +name, and the value to be set by property value. The following +properties are configurable:

+ +
+

+PhysicalScreenWidth

+A number specifying the screen's image area width in meters. When using a +configuration file the default is 0.365. For head mounted displays this should +be the apparent width of the display at the focal plane. + +

+PhysicalScreenHeight

+A number specifying the screen's image area height in meters. When using a +configuration file the default is 0.292. For head mounted displays this should +be the apparent height of the display at the focal plane. + +

+WindowSize

+This property's value can be a 2D point to create a window with the specified +X width and Y height in pixels, or it can be either of the strings FullScreen +or NoBorderFullScreen to specify a full screen canvas with visible frame +borders or one with no frame borders. A NoBorderFullScreen canvas uses the +entire physical display surface for its image. The default value is 512 x 512 +pixels. For multiple screen virtual reality installations or head mounted +displays NoBorderFullScreen should be used. + +

+WindowPosition

+This property's value is a 2D point used to create a window with the specified +X and Y position. These are offsets of the window's upper left corner from the +screen's upper left corner. + +

+TrackerBaseToImagePlate

+A 4D matrix which transforms points from tracker base coordinates to the image +plate coordinates for the specified screen. This is only used when a +ViewPolicy of SCREEN_VIEW is in effect. The matrix value is +identity by default. +

+Image plate dimensions are expressed in meters. The origin of the image plate +is the lower left corner of the screen's image area, with X increasing to the +right, Y increasing to the top, and Z increasing away from the screen.

+

+The tracker base is somewhat abstract. It is used as a local fixed frame of +reference for specifying the orientation and position of a screen even when +tracking hardware is not being used. It is also the frame of reference for +defining coexistence coordinates with the +CoexistenceToTrackerBase matrix.

+

+The built-in commands Translate, +Rotate, RotateTranslate, and TranslateRotate are available to make it easier +to create transforms of this type. + +

+HeadTrackerToLeftImagePlate
+HeadTrackerToRightImagePlate

+4D matrices which transform points in the head tracking sensor's local +coordinate system to a head mounted display's left and right image plates +respectively. The default value for each is the identity matrix.

+

+These are only used when a ViewPolicy of HMD_VIEW is in effect. +As with physical screen dimensions, these matrices should indicate the +apparent location and orientation of the screen images as viewed through +the head mounted display's optics.

+

+The HMD manufacturer's specifications in terms of angle of view, distance to +the focal plane, aspect ratio, and percentage of image overlap between the left +and right views can be used to derive the apparent screen positions with +respect to the head, and from there the positions with respect to the head +tracker mounted on the head. In most cases there is 100% overlap between the +two stereo images, so the matrices for both the left and right screens should +be identical.

+

+Some HMD devices support field-sequential stereo and are driven as if they were +a single screen. In that case, only a single screen should be defined, but +both the left and right head tracker to image plate transforms need to +be specified for that same screen.

+ +

+MonoscopicViewPolicy

+This property may have the following string values: CYCLOPEAN_EYE_VIEW, +LEFT_EYE_VIEW, or RIGHT_EYE_VIEW. The default value is +CYCLOPEAN_EYE_VIEW. This default works for non-stereo displays +and field-sequential stereo displays where the two stereo images are generated +on the same canvas.

+

+Some HMD devices can be driven as a single screen if the HMD supports +field-sequential stereo, so the default policy will work for them as well if a +stereo view is enabled. If stereo is not enabled, an IllegalStateException will +be thrown when the ViewPolicy is set to HMD_VIEW with the default +CYCLOPEAN_EYE_VIEW policy in effect.

+

+The LEFT_EYE_VIEW and RIGHT_EYE_VIEW monoscopic view +policies are used for generating stereo pairs on separate monoscopic canvases, +including the left and right canvases needed by HMD devices that are driven by +two video channels. When using these policies, stereo should not be +enabled.

+
+
+ +

+NewPhysicalEnvironment

+Syntax: +
(NewPhysicalEnvironment <instance name> +[Alias <alias name>]) +

+Creates a new PhysicalEnvironment and binds it to the name given by instance +name. This specifies the available input devices, which Sensor to use as +the head tracker, and defines the coexistence coordinate system. Note that +aside from the head tracker, the configuration file does not provide a way to +place Sensors into the array maintained by the PhysicalEnvironment. See the +getNamedSensors +method of ConfigContainer.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+
+ +

+PhysicalEnvironmentProperty

+Syntax: +
(PhysicalEnvironmentProperty <instance name> +<property name> <property value>) +

+Sets a PhysicalEnvironment property. The instance is specified by instance +name, the property to be set by property name, and the value to be +set by property value. The following properties are configurable:

+ +
+

+InputDevice

+Register an InputDevice implementation instantiated by +NewDevice. The InputDevice instance name is specified +by the property value string. If an InputDevice is not registered then +it will not be scheduled to run. + +

+HeadTracker

+Register the Sensor which will be used for head tracking. It must provide 6 +degree of freedom position and orientation reads relative to the tracker base. +The Sensor instance name is specified by the property value string. +Its corresponding input device must be registered with the InputDevice +property. +

+There is no actual method in the core PhysicalEnvironment class to set the head +tracker. This property is a simplified interface for setting the +PhysicalEnvironment head index and assigning the head tracking sensor to that +index. Direct access to the PhysicalEnvironment Sensor array is not supported +by the configuration file.

+ +

+CoexistenceToTrackerBase

+A 4D matrix which transforms points in coexistence coordinates to tracker base +coordinates. This defines the position and orientation of coexistence +coordinates relative to the tracker base. Its default value is the identity +matrix, so if it is not set then coexistence coordinates will be the same as +tracker base coordinates. See +TrackerBaseToImagePlate. +

+The coexistence origin (center of coexistence) positions the physical +world with respect to the origin of the ViewPlatform in the virtual world. +This is established through the +ViewAttachPolicy, which attaches the view +platform to either the center of coexistence, the nominal head, or the nominal +feet. Coexistence coordinates can essentially be thought of as physical +coordinates, but the real purpose is to define the space in which coordinates +systems in the physical world - such as tracker base, image plate, and physical +body - coexist and interact with the virtual world coordinate systems.

+

+The basis vectors (X, Y, and Z directions) of coexistence coordinates are +always aligned with the basis vectors of the ViewPlatform in the virtual world, +and the scale factor going from ViewPlatform coordinates to coexistence +coordinates is set by the ScreenScalePolicy. +Together with the ViewPlatform's view attach policy and view transform this +establishes the complete mapping of the physical world into the virtual +world.

+

+The positioning and orientation of coexistence coordinates with respect to the +physical environment is up to the user or application. In a fixed screen +environment it usually makes most sense to define it in a convenient +relationship to the primary screen or some intersection of the available +screens, such that the coexistence origin is in front of and aligned with the +user's nominal forward gaze direction. This is because the -Z axis of +coexistence coordinates always points along the view direction defined by the +view platform's -Z axis.

+

+For example, when using a single screen, the most common mapping puts the +center of coexistence in the middle of the screen with its basis vectors +aligned with the screen's image plate coordinate system. With a dual-screen +system it is usually most convenient to place the center of coexistence in the +middle of the edge shared by both screens, with its Z axis extending +perpendicular to the shared edge and maintaining an equal angle to both +screens. For a 2x2 array of four screens putting the center of coexistence at +the center of the array is usually a good choice. In a cave configuration +having the center of coexistence in the middle of the viewing environment can +facilitate the sense of immersion.

+

+In HMD mode the view attach policy is ignored and is always effectively +NOMINAL_SCREEN. Coexistence coordinates and view platform +coordinates are then equivalent except for scale. For HMD configurations +placing the coexistence coordinate system aligned with some nominal +front-facing user position works well.

+

+Note: the normal Java 3D default is to place the center of coexistence +in the middle of the screen or canvas by setting +CoexistenceCenteringEnable true by +default. This only works for a single screen and if both the +TrackerBaseToImagePlate and CoexistenceToTrackerBase matrices are identity. If +either of these matrices are set from their default identity values in the +configuration file, and CoexistenceCenteringEnable has not been set, then the +centering property will be set false by default.

+

+
+ +

+NewPhysicalBody

+Syntax: +
(NewPhysicalBody <instance name> +[Alias <alias name>]) +

+Creates a new PhysicalBody and binds it to the name given by instance +name. The PhysicalBody is essentiallly the users's head, with the origin +halfway between the eyes in the plane of the face. Positive X extends to the +right eye, positive Y up, and positive Z extends into the skull opposite to the +forward gaze direction. Dimensions are expressed in meters.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+
+ +

+PhysicalBodyProperty

+Syntax: +
(PhysicalBodyProperty <instance name> <property name> +<property value>) +

+Sets a PhysicalBody property. The instance is specified by instance +name, the property to be set by property name, and the value to be +set by property value. The following properties are configurable:

+ +
+

+StereoEyeSeparation

+A number indicating the interpupilary distance in meters. This will set the +left and right eye positions to offsets of half this distance from the head +origin along its X axis. The default is 0.066 meters. +

+This property is a simplified interface to setting the PhysicalBody's separate +left and right eye positions; there is no actual method in PhysicalBody to set +stereo eye separation, but the results are exactly equivalent.

+ +

+LeftEarPosition

+A 3D point which sets the left ear position relative to head coordinates. +The default is (-0.08, -0.03, 0.09). + +

+RightEarPosition

+A 3D point which sets the right ear position relative to head coordinates. +The default is (0.08, -0.03, 0.09). + +

+HeadToHeadTracker

+A 4D matrix which transforms points from head coordinates to the local +coordinate system of the head tracking sensor. This allows the positions +of the eyes and ears to be determined from the position and orientation +of the head tracker. The default is the identity matrix. + +

+ +NominalEyeOffsetFromNominalScreen

+A distance in meters used as a calibration parameter for +ViewAttachPolicy. It does not actually set +the position of the eyes. The property is ignored if ViewAttachPolicy is +NOMINAL_SCREEN. The default value is 0.4572 meters. + +

+NominalEyeHeightFromGround

+A distance in meters used as a calibration parameter for +ViewAttachPolicy. +It does not actually set the position of the eyes. This property is +ignored if ViewAttachPolicy is not NOMINAL_FEET. The default value is 1.68 +meters. +
+
+ +

+NewView

+Syntax: +
(NewView <instance name> +[Alias <alias name>]) +

+Creates a new view and binds it to the name given by instance name. +In the configuration file the term view refers to an instance of the +Viewer utility class, which contains both an +instance of a core Java 3D View class and an array of Canvas3D instances into +which to render.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+

+ConfiguredUniverse requires that at least one view be defined. If a view +platform is not provided, then ConfiguredUniverse will create a default one and +attach the view to that. If multiple views are defined, then at least one view +platform must be explicitly provided by the configuration, and the +ViewPlatform property must be used to attach each view to a view platform.

+
+ +

+ViewProperty

+Syntax:
+(NewView <instance name> <property name> +<property value>) +

+Sets a View property. The view instance is specified by instance name, +the property to be set by property name, and the value to be set by +property value. The following properties are configurable:

+ +
+

+Screen
+Window

+These two properties are equivalent. They include a screen created by +NewScreen or a window created by +NewWindow into +this view. The screen or window name is specified by the property value +string. Multiple-screen or multiple-window views are created by calling this +command with each window or screen to be used. If no windows or screens are +defined for this view then an IllegalArgumentException will be thrown after the +configuration file has been processed. + +

+PhysicalEnvironment

+Sets the PhysicalEnvironment to be used for this view. The property +value string specifies the name of a PhysicalEnvironment instance created +by the NewPhysicalEnvironment command. +If no PhysicalEnvironment is specified for this view then one with default +values will be created. + +

+PhysicalBody

+Sets the PhysicalBody to be used for this view. The property +value string specifies the name of a PhysicalBody instance created +by the NewPhysicalBody command. If +no PhysicalBody is specified for this view then one with default values +will be created. + +

+ViewPlatform

+The property value string is the name of a view platform defined by a +previous NewViewPlatform command. This +specifies that the view should be attached to the given view platform. +ConfiguredUniverse requires that a view platform be specified for every defined +view unless only a single view without a view platform is provided; in that +case a view platform is created by default and the view is attached to that. +If one or more view platforms are defined then the view attachments must be +made explicitly. + +

+ViewPolicy

+The property value string may be either SCREEN_VIEW or +HMD_VIEW to indicate whether fixed room-mounted screens are being +used or a head mounted display. The default value is SCREEN_VIEW. + +

+CoexistenceCenteringEnable

+The property value is a boolean string. If true, then the origin of the +coexistence coordinate system is set to either the middle of the canvas or the +middle of the screen depending upon whether the WindowMovementPolicy is +PHYSICAL_WORLD or VIRTUAL_WORLD respectively. The X, +Y, and Z directions of coexistence coordinates will point in the same +directions as those of the screen's image plate. +

+This only works if a single screen is being used and if both the +TrackerBaseToImagePlate and +CoexistenceToTrackerBase matrices are +identity. If CoexistenceCenteringEnable is not explicitly set, and either the +CoexistenceToTrackerBase transform for the view has been set or +TrackerBaseToImagePlate has been set for any screen, then the centering enable +will be set to false by default; otherwise, the normal default is true. This +property is also effectively false whenever the ViewPolicy is set to +HMD_VIEW.

+ +

+WindowEyepointPolicy

+The string value for this property may be either RELATIVE_TO_SCREEN, +RELATIVE_TO_COEXISTENCE, RELATIVE_TO_WINDOW, or +RELATIVE_TO_FIELD_OF_VIEW. The normal Java 3D default is +RELATIVE_TO_FIELD_OF_VIEW. When using a configuration file the +default is RELATIVE_TO_COEXISTENCE if +CoexistenceCenteringEnable is false, +otherwise the normal default applies. See the +setWindowEyepointPolicy View method. +

+For the RELATIVE_TO_SCREEN and RELATIVE_TO_WINDOW +policies, the eyepoint is set by using the setLeftManualEyeInImagePlate() and +setRightManualEyeInImagePlate() methods of Canvas3D. The configuration file +currently does not provide any mechanism for altering these properties from +their default values. These default values are (0.142, 0.135, 0.4572) for the +left eye and (0.208, 0.135, 0.4572) for the right eye.

+

+These polices are ignored if head tracking is enabled.

+ +

+WindowMovementPolicy
+WindowResizePolicy

+ +The string values for these properties may be either VIRTUAL_WORLD +or PHYSICAL_WORLD. The normal Java 3D default value for both is +PHYSICAL_WORLD. When using a configuration file the default +values are VIRTUAL_WORLD if +CoexistenceCenteringEnable is false, +otherwise the normal defaults apply. See the +setWindowMovementPolicy and +setWindowResizePolicy View methods.

+ +

+CenterEyeInCoexistence

+A 3D point which specifies the location of the center eye relative to +coexistence coordinates. See +CoexistenceToTrackerBase. If stereo +viewing is enabled, then the left and right eye positions are set to offsets +from this position using the values specified by the PhysicalBody. This +property is ignored if head tracking is enabled or if WindowEyepointPolicy is +not RELATIVE_TO_COEXISTENCE. The default value is (0.0, 0.0, +0.4572). +

+This property is a simplified interface to setting the View's left +and right manual eyes in coexistence; there is no actual method in View +to set a center eye position.

+ +

+ScreenScalePolicy

+The property value string may be either SCALE_SCREEN_SIZE +or SCALE_EXPLICIT and determines the source of the screen +scale, a factor in the scaling of view platform coordinates to physical +world coordinates. +

+If the value is SCALE_SCREEN_SIZE, then the screen scale is half +the physical screen width in meters. If WindowResizePolicy is +PHYSICAL_WORLD, then this scale is further multiplied by the ratio +of the window width to the width of the screen (the window scale) to +produce the scale from view platform coordinates to physical coordinates. This +allows a virtual world which spans a normalized width of [-1.0 .. 1.0] in view +platform coordinates to map directly to the physical width of the window or +screen, depending upon the resize policy.

+

+SCALE_EXPLICIT uses the value of the ScreenScale property as the +screen scale. It is also further multiplied by the window scale when using the +PHYSICAL_WORLD window resize policy to produce the scale from view +platform coordinates to physical coordinates. Viewing configurations +incorporating multiple screens should generally set the policy to +SCALE_EXPLICIT and choose a screen scale based on the aggregate +display size, but the default value of SCALE_SCREEN_SIZE will +usually work if all the screens are the same size and arranged in a linear +array.

+

+The view platform is positioned in the virtual world through a chain of +transforms to the root of the scene graph; this composite transform is its +localToVWorld transform and must be congruent. If we take the the inverse of +the scale factor in this transform and call it the view platform scale, +then the scale from virtual world units to physical world units can be computed +as the product of this view platform scale, the screen scale, and, when using a +resize policy of PHYSICAL_WORLD, the window scale. In the usual +case the view platform scale is 1.0.

+ +

+ScreenScale

+The property value is a number specifying the explicit screen scale +factor. It is only used if ScreenScalePolicy is SCALE_EXPLICIT; +otherwise, the screen scale is half the physical width of the screen in meters. +The default value for this property is 1.0. + +

+BackClipPolicy
+FrontClipPolicy

+The string values of these properties may be either PHYSICAL_EYE, +PHYSICAL_SCREEN, VIRTUAL_EYE, or VIRTUAL_SCREEN. The +default policies are PHYSICAL_EYE. See the +setFrontClipPolicy and +setBackClipPolicy View methods. + +

+FrontClipDistance
+BackClipDistance

+These property values are numbers. The defaults are 0.1 and 10.0 +respectively. See the +setFrontClipDistance and +setBackClipDistance View methods.

+

+With the default clip policies of PHYSICAL_EYE the clip distances +are measured relative to the eye in physical units, so the clip distances +specified must be scaled to virtual world units in order to determine the +distances in the virtual world where they would effectively be applied. As +described in the discussion of +ScreenScalePolicy above, the scale from +virtual units to physical units is the product of the view platform scale +(usually 1.0), the screen scale, and the window scale (if the window resize +policy is PHYSICAL_WORLD), so normally the scale from physical +units to virtual units would be the inverse of that product.

+

+There is a quirk, however, with physical clip plane scaling when the +PHYSICAL_EYE or PHYSICAL_SCREEN clip policies are +used with the PHYSICAL_WORLD window resize policy. The locations +of the clip planes in physical units are not actually set to the physical +distances as specified, but are in fact scaled by the window scale. +This means that when determining where the specified physical clip distances +are in virtual units the scaling to be used is the inverse of the product of +the screen scale and view platform scale only.

+

+This quirk applies only to scaling physical clip plane distances, and only with +the PHYSICAL_WORLD resize policy. It was implemented in this +manner to prevent objects in the virtual world from getting clipped +unexpectedly when the virtual world scaling changed as the result of a window +resize. The quirk can be avoided by using the VIRTUAL_EYE or +VIRTUAL_SCREEN clip policies or by using the +VIRTUAL_WORLD resize policy, but in most cases the effect is +benign and doesn't lead to unexpected results.

+ +

+FieldOfView

+This number is the view's horizontal field of view in radians. The default +value is PI/4. This value is ignored if WindowEyepointPolicy is not +RELATIVE_TO_FIELD_OF_VIEW or if head tracking is enabled. The +eyepoint for each canvas associated with the view is set such that it is +centered in X and Y, with the Z value at the appropriate distance to match the +specified field of view across the width of the canvas. + +

+StereoEnable

+Enable or disable stereo viewing for this view according to the boolean value +specified by the property value string. The default value is false. +

+There is no actual method in the core Java 3D View or utility Viewer class to +enable stereo. A true value for this property causes ConfigContainer to +attempt to create stereo-capable canvases for all the screens associated with +this view. Stereo will then be enabled for each canvas successfully created +with stereo capability.

+ +

+TrackingEnable

+Enable or disable head tracking for this view according to the boolean value +specified by the property value string. The default value is false. +

+Setting this property true causes WindowEyepointPolicy to be ignored; it will +effectively be RELATIVE_TO_COEXISTENCE with the eyepoint in +coexistence coordinates computed from reading the head tracking sensor. +Tracking must be made available by registering an input device and a head +tracking sensor in PhysicalEnvironment.

+ +

+AntialiasingEnable

+Enable or disable scene antialiasing for this view according to the boolean +value specified by the property value string. The default value is +false. +

+A true value for this property causes ConfigContainer to attempt to create +a canvas capable of scene antialiasing on each screen associated with this +view. The scene will then be antialiased on each canvas successfully created +with that capability.

+

+Line and point antialiasing are independent of scene antialiasing and are +controlled by the LineAttribute and PointAttribute components of an Appearance. +If line and point antialiasing is enabled, then they will be antialiased prior +to scene antialiasing; if scene antialiasing is turned off, then antialiased +lines and points will still be antialiased.

+
+
+ +

+NewViewPlatform

+Syntax: +
(NewViewPlatform <instance name> +[Alias <alias name>]) +

+Creates a new view platform and binds it to the name given by instance +name. In the configuration file the term view platform refers to an +instance of the ViewingPlatform utility +class, which is an extension of BranchGroup containing an instance of a core +Java 3D ViewPlatform class.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+
+ +

+ViewPlatformProperty

+Syntax: +
(ViewPlatformProperty <instance name> <property name> +<property value>) +

+Sets a ViewPlatform property. The instance is specified by instance +name, the property to be set by property name, and the value to be +set by property value. The following properties are configurable:

+ +
+

+NominalViewingTransform

+The property value is a boolean string indicating whether or not +the view platform should be backed up in Z to allow objects at the origin to be +viewed. This only has effect if the ViewAttachPolicy is +NOMINAL_HEAD. The default value is false. See the +setNominalViewingTransform +method of ViewingPlatform. + +

+InitialViewingTransform

+Sets the initial transform of the view platform to the 4D matrix +specified by property value. The default value is identity. + +

+AllowPolicyRead

+The property value is a boolean string indicating whether or not reading +the ViewAttachPolicy is allowed. The default value is false. + +

+AllowLocalToVworldRead

+The property value is a boolean string indicating whether or not reading +the view platform's localToVworld transform is allowed. The default value is +false. + +

+ViewPlatformBehavior

+Attaches a ViewPlatformBehavior instantiated by +NewViewPlatformBehavior. +The property value string is the name bound to that instance. + +

+ViewAttachPolicy

+The property value string can be one of NOMINAL_SCREEN, +NOMINAL_HEAD, or NOMINAL_FEET. This establishes the point +in coexistence coordinates where the origin of view platform coordinates is +attached. The basis vectors of view platform coordinates are always aligned +with those of coexistence coordinates. +

+For a ViewAttachPolicy of NOMINAL_SCREEN, the ViewPlatform origin +is set directly to the origin of coexistence, so that ViewPlatform coordinates +and coexistence coordinates are identical except for scale.

+

+For a ViewAttachPolicy of NOMINAL_HEAD, the ViewPlatform origin is +set to the origin of the nominal head, the center eye halfway between the left +and right eyes. The nominal head origin is on the Z axis of coexistence +coordinates at some offset from the coexistence origin. If the +WindowEyepointPolicy is RELATIVE_TO_FIELD_OF_VIEW, then this is +the positive Z offset producing the required field of view relative to the +width of the canvas at the coexistence origin, tracking the Z offset of the +eyepoint defined by that policy; otherwise, the Z offset is the +NominalEyeOffsetFromNominalScreen property of PhysicalBody and is decoupled +from the actual eyepoint offset.

+

+For a ViewAttachPolicy of NOMINAL_FEET, the ViewPlatform origin is +at the ground plane, which is NominalEyeHeightFromGround meters along -Y from +the origin of the nominal head. NominalEyeHeightFromGround is a property of +PhysicalBody.

+

+Note: The normal Java 3D default is NOMINAL_HEAD. When +using a configuration file, the default is NOMINAL_HEAD only if +every view attached to the view platform has a WindowEyepointPolicy of +RELATIVE_TO_FIELD_OF_VIEW; otherwise, the default is +NOMINAL_SCREEN. If the view policy is HMD_VIEW, then +the ViewAttachPolicy is ignored and is always effectively +NOMINAL_SCREEN.

+
+
+ +

+NewViewPlatformBehavior

+Syntax: +
(NewViewPlatformBehavior <instance name> <class name> +[Alias <alias name>]) +

+This command instantiates the concrete subclass of ViewPlatformBehavior +specified by the fully qualified class name and binds it to instance +name. The ViewPlatformBehavior is instantiated through introspection of +the class name. The subclass must provide a parameterless constructor. If no +such constructor is available, then the behavior must be extended or wrapped to +provide one and the derived class used instead.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+

+View platform behaviors often need sensors or canvases as event sources to +drive the behavior action. A subclass of ViewPlatformBehavior always gets the +current ViewingPlatform through its +setViewingPlatform +method. When the behavior is initialized, the canvases used by the +ViewingPlatform can be retrieved by calling its +getViewers method and +then calling each Viewer's +getCanvas3Ds method. Sensors +can be retrieved by calling the ViewingPlatform method +getUniverse, checking +to see if the returned SimpleUniverse is a ConfiguredUniverse, and then calling +its +getNamedSensors +method.

+

+Alternatively, the behavior implementation can define its own properties +and receive canvas and sensor instances directly through the +Canvas3D and Sensor built-in +commands.

+
+ +

+ViewPlatformBehaviorProperty

+Syntax: +
(ViewPlatformBehaviorProperty <instance name> +<property name> <property value>) +

+Sets a property of a ViewPlatformBehavior. The instance is specified by +instance name, the property to be set by property name, and the +value to be set by property value. The following properties are +pre-defined by the abstract ViewPlatformBehavior superclass and may be +configured directly:

+ +

+

+SchedulingBounds

+The scheduling bounds for this behavior. Use the +BoundingSphere built-in command to set this +property. The default is whatever the application or the concrete subclass +sets. + +

+SchedulingInterval

+A number indicating the scheduling interval for this behavior. See the +setSchedulingInterval +method of Behavior. + +

+HomeTransform

+See the ViewPlatformBehavior method +setHomeTransform. +The property value must be a 4D matrix. +
+

+The details of a concrete subclass of ViewPlatformBehavior are not known to +ConfigContainer, so any properties specific to the subclass are set by +using introspection to invoke property name as a method accepting an +array of Objects as its single parameter. The arguments following the property +name are evaluated and the results passed to the method through that array of +Objects. Such methods can usually be provided by extending or wrapping +existing ViewPlatformBehavior implementations.

+
+ +

+NewObject

+Syntax: +
(NewObject <instance name> <class name> +[Alias <alias name>]) +

+This command instantiates a generic object specified by class +name and binds it to instance name. The object is instantiated +through introspection of the class name. The object must provide a +parameterless constructor; this can usually be done by extending or +wrapping existing objects.

+

+The last two arguments are optional and define an alias for instance +name. This is useful in providing a longer descriptive name for the +application to recognize, or conversely, to provide a shorter alias for a +longer instance name. Either name may be used to identify the instance.

+

+Objects so defined may be accessed from ConfigContainer through the +getNamedGenericObjects method.

+
+ +

+ObjectProperty

+Syntax: +
(ObjectProperty <instance name> <method name> <arg0> +... <argn>) +

+Sets a property of a generic object. The details of the object specified by +instance name are not known to ConfigContainer, so any parameters to be +set through the configuration file are passed to the object instance by +invoking method name through introspection. The arguments following the +method name are evaluated and the results passed to the method as an array of +Objects. The required methods can usually be provided by extending or wrapping +existing objects.

+
+ +

+JavaProperty

+Syntax: +
(JavaProperty <propertyName> [Default] +<propertyValue>) +

+Sets the Java system property propertyName to the string +propertyValue. If the optional Default keyword is supplied, then the +property is set only if it doesn't currently have a value.

+

+Java system properties which affect Java 3D are evaluated at the first +reference to a VirtualUniverse. Setting such properties in the configuration +file is therefore ineffective if a ConfiguredUniverse constructor which accepts +a URL directly is used; ConfigContainer must be used instead. Even then, care +must be taken to avoid static references to VirtualUniverse from objects such +as Transform3D.

+

+The special Java property substitution syntax ${<propertyName>} +may be used to access the value of a Java system property anywhere within a +configuration file.

+
+ +

+Include

+Syntax: +
(Include <URL string>) +

+Retrieves the configuration file specified by URL string and includes it +into the current configuration file at the current line. The content of the +included file is evaluated exactly as if it were pasted into the current file +at that line. Included files may be arbitrarily nested.

+

+URL strings must be quoted.

+


+ +

+Alias

+Syntax: +
(<baseName>Alias <aliasName> +<originalName>) +

+Creates an alias for the object specified by originalName (which itself +may be an alias). baseName may be Device, Object, PhysicalBody, +PhysicalEnvironment, Screen, Window, Sensor, View, ViewPlatform, or +ViewPlatformBehavior; it specifies the type of the object being aliased. +Original names and aliases must be unique within a type. Note that there is no +white space between baseName and Alias.

+

+Aliases are useful for providing shorter or longer descriptive names for an +original name. This function is also provided by the optional Alias keyword +available for all New top-level commands. This separate command can be +used to substitute new names for objects created by generic include files in +order to conform to the names expected by specific applications.

+
+ + + + + + +
+Built-In Command Details
+

+Built-in commands are provided by the parser itself to help construct the +arguments to top-level commands. They cannot appear at the top level of +command nesting.

+

+Translate, Rotate, TranslateRotate, and RotateTranslate are useful for +computing simple affine transforms of the form SourceToTarget +(e.g., TrackerBaseToImagePlate). These transform points from the +Source coordinate system to the Target coordinate system by +concatenating translation and rotation matrices. Here is a general rule for +creating such transforms:

+ +
+Subtract (translate) the target origin first if it can be conveniently measured +or computed relative to the source origin along the source X, Y, and Z basis +vectors. Then rotate with Euler angles that move the target basis vectors to +their corresponding source basis vectors. +

+If instead it is easier to measure or compute the source origin relative to the +target origin along the target basis vectors, rotate first with Euler angles +that move the target basis vectors to their corresponding source basis vectors, +and then add (translate) the source origin.

+
+

+The Canvas3D, Sensor, Device, PhysicalBody, PhysicalEnvironment, View, +ViewPlatform, ViewPlatformBehavior, and Object built-in commands return +references to the objects named by their arguments. These are mostly useful +for InputDevice and ViewPlatformBehavior implementations that define their own +properties. The return values of these built-in commands should not be passed +to commands that expect strings.

+
+ +

+Translate

+Syntax: +
(Translate <x offset> <y offset> <z offset>) +

+Returns a 4D matrix that translates by the given X, Y, and Z values.

+
+ +

+Rotate

+Syntax: +
(Rotate <x degrees> <y degrees> <z degrees>) +

+Returns a 4D matrix that rotates by the given Euler angles around static X, Y, +and Z basis vectors: first about X, then Y, and then Z. See the +setEuler +method of Transform3D.

+
+ +

+Concatenate

+Syntax: +
(Concatenate <m1> <m2>) +

+Returns a 4D matrix that concatenates 4D matrices m1 and m2 in +that order. If a point is transformed by the resulting matrix, then in effect +the points are first transformed by m1 and then m2.

+
+ +

+RotateTranslate

+Syntax: +
(RotateTranslate <m1> <m2>) +

+An alias for the Concatenate command. This is useful to make the +result of the concatenation explicit.

+
+ +

+TranslateRotate

+Syntax: +
(TranslateRotate <m1> <m2>) +

+An alias for the Concatenate command. This is useful to make the +result of the concatenation explicit.

+
+ +

+BoundingSphere

+Syntax: +
(BoundingSphere <center> <radius>) +

+Returns a BoundingSphere object using the 3D point center and the given +radius in meters. radius may be either a number or the string +Infinite.

+
+ +

+Canvas3D

+Syntax: +
(Canvas3D <screen or window name>) +

+Returns the Canvas3D instance specified by the given name. A named Canvas3D is +created whenever any of the following configuration commands are used: + +

+(ViewProperty <view> Screen <screenName>)
+(ViewProperty <view> Window <windowName>) +
+ +view is the name of a view created with the NewView command. The +argument to the Canvas3D built-in must be a screenName or +windowName parameter from one of the above commands.

+

+Note: the NewScreen and NewWindow commands do not create Canvas3D +instances themselves; they are created only by the above configuration +commands.

+
+ +

+Sensor

+Syntax: +
(Sensor <sensor name>) +

+Returns the Sensor instance specified by the given name.

+
+ +

+Device

+Syntax: +
(Device <device name>) +

+Returns the InputDevice instance specified by the given name.

+
+ +

+PhysicalBody

+Syntax: +
(PhysicalBody <body name>) +

+Returns the PhysicalBody instance specified by the given name.

+
+ +

+PhysicalEnvironment

+Syntax: +
(PhysicalEnvironment <environment name>) +

+Returns the PhysicalEnvironment instance specified by the given name.

+
+ +

+View

+Syntax: +
(View <view name>) +

+Returns the Viewer instance specified by the given name.

+
+ +

+ViewPlatform

+Syntax: +
(ViewPlatform <view platform name>) +

+Returns the ViewingPlatform instance specified by the given name.

+
+ +

+ViewPlatformBehavior

+Syntax: +
(ViewPlatformBehavior <behavior name>) +

+Returns the ViewPlatformBehavior instance specified by the given name.

+
+ +

+Object

+Syntax: +
(Object <generic object name>) +

+Returns the generic object instance specified by the given name. A generic +named object is created by the following configuration command: + +

+(NewObject <instance name> <class name> +[Alias <alias name>]) +
+
+ +

+ConfigContainer

+Syntax: +
(ConfigContainer) +

+Returns a reference to the current ConfigContainer.

+
+ +
+ + + + +
+Command Index
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandType
Aliastop-level
BoundingSpherebuilt-in
Canvas3Dbuilt-in
Concatenatebuilt-in
ConfigContainerbuilt-in
Devicebuilt-in
DevicePropertytop-level
Includetop-level
JavaPropertytop-level
NewDevicetop-level
NewObjecttop-level
NewPhysicalBodytop-level
NewPhysicalEnvironmenttop-level
NewScreentop-level
NewSensortop-level
NewViewtop-level
NewViewPlatformtop-level
NewViewPlatformBehaviortop-level
NewWindowtop-level
Objectbuilt-in
ObjectPropertytop-level
PhysicalBodybuilt-in
PhysicalBodyPropertytop-level
PhysicalEnvironmentbuilt-in
PhysicalEnvironmentPropertytop-level
Rotatebuilt-in
RotateTranslatebuilt-in
ScreenPropertytop-level
Sensorbuilt-in
SensorPropertytop-level
Translatebuilt-in
TranslateRotatebuilt-in
Viewbuilt-in
ViewPlatformbuilt-in
ViewPlatformBehaviorbuilt-in
ViewPropertytop-level
ViewPlatformPropertytop-level
ViewPlatformBehaviorPropertytop-level
WindowPropertytop-level
+

+ + + + + + +
+Property Index
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyCommand
AllowLocalToVworldReadViewPlatformProperty
AllowPolicyReadViewPlatformProperty
AntialiasingEnableViewProperty
BackClipDistanceViewProperty
BackClipPolicyViewProperty
CenterEyeInCoexistenceViewProperty
CoexistenceCenteringEnableViewProperty
CoexistenceToTrackerBasePhysicalEnvironmentProperty
FieldOfViewViewProperty
FrontClipDistanceViewProperty
FrontClipPolicyViewProperty
HeadToHeadTrackerPhysicalBodyProperty
HeadTrackerPhysicalEnvironmentProperty
HeadTrackerToLeftImagePlateScreenProperty
HeadTrackerToRightImagePlateScreenProperty
HomeTransformViewPlatformBehaviorProperty
HotspotSensorProperty
InitialViewingTransformViewPlatformProperty
InputDevicePhysicalEnvironmentProperty
LeftEarPositionPhysicalBodyProperty
MonoscopicViewPolicyScreenProperty
NominalEyeHeightFromGroundPhysicalBodyProperty
NominalEyeOffsetFromNominalScreenPhysicalBodyProperty
NominalViewingTransformViewPlatformProperty
PhysicalBodyViewProperty
PhysicalEnvironmentViewProperty
PhysicalScreenHeightScreenProperty
PhysicalScreenWidthScreenProperty
RightEarPositionPhysicalBodyProperty
SchedulingBoundsViewPlatformBehaviorProperty
SchedulingIntervalViewPlatformBehaviorProperty
ScreenViewProperty
ScreenScaleViewProperty
ScreenScalePolicyViewProperty
StereoEnableViewProperty
StereoEyeSeparationPhysicalBodyProperty
TrackerBaseToImagePlateScreenProperty
TrackingEnableViewProperty
ViewAttachPolicyViewPlatformProperty
ViewPlatformViewProperty
ViewPlatformBehaviorViewPlatformProperty
ViewPolicyViewProperty
WindowViewProperty
WindowEyepointPolicyViewProperty
WindowMovementPolicyViewProperty
WindowPositionWindowProperty
WindowResizePolicyViewProperty
WindowSizeWindowProperty
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-behavior.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-behavior.html new file mode 100644 index 0000000..c27c3a1 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-behavior.html @@ -0,0 +1,109 @@ + + + + + j3d1x1-behavior config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for single fullscreen desktop configuration.
+ * A view platform behavior is created and configured here as well.
+ *  
+ ************************************************************************
+ */
+
+(NewScreen center 0)
+(ScreenProperty center WindowSize NoBorderFullScreen)
+
+(NewView view0)
+(ViewProperty view0 Screen center)
+
+// Create a view platform behavior.  Here we use OrbitBehavior, although any
+// concrete subclass of the abstract ViewPlatformBehavior with a parameterless
+// constructor could be used.  The logical name to assign to this behavior is
+// specified by the 2nd argument to the NewViewPlatformBehavior command, while
+// the 3rd argument is the name of the ViewPlatformBehavior subclass.  It is
+// instantiated through introspection.
+// 
+(NewViewPlatformBehavior vpb com.sun.j3d.utils.behaviors.vp.OrbitBehavior)
+
+// Set the scheduling bounds to be a BoundingSphere with its center at 
+// (0.0 0.0 0.0) and an infinite radius.
+// 
+(ViewPlatformBehaviorProperty vpb SchedulingBounds
+                                  (BoundingSphere (0.0 0.0 0.0) infinite))
+
+// Set properties specific to OrbitBehavior.  All arguments following the
+// method name are wrapped and passed to the specified method as an array of
+// Objects.  Strings "true" and "false" get turned into Boolean, and number
+// strings get turned into Double.  Constructs such as (0.0 1.0 2.0) and
+// ((0.0 1.0 2.0 0.5) (3.0 4.0 5.0 1.0) (6.0 7.0 8.0 0.0)) get converted to
+// Point3d and Matrix4d respectively. Note that last row of a Matrix4d doesn't
+// need to be specified; it is implicitly (0.0 0.0 0.0 1.0).
+// 
+// The REVERSE_ALL flags are usually passed to the OrbitBehavior constructor.
+// Since it is being instantiated with its parameterless constructor the
+// reverse flags are set here explicitly.
+// 
+(ViewPlatformBehaviorProperty vpb ReverseTranslate true)
+(ViewPlatformBehaviorProperty vpb ReverseRotate    true)
+(ViewPlatformBehaviorProperty vpb ReverseZoom      true)
+
+// Create a new view platform and set the view platform behavior.
+// 
+(NewViewPlatform vp)
+(ViewPlatformProperty vp ViewPlatformBehavior vpb)
+
+// Attach the view to the view platform.
+(ViewProperty view0 ViewPlatform vp)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-stereo.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-stereo.html new file mode 100644 index 0000000..63bc927 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-stereo.html @@ -0,0 +1,90 @@ + + + + + j3d1x1-stereo config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for single fullscreen stereo desktop
+ * configuration with no head tracking.
+ *  
+ ************************************************************************
+ */
+
+(NewScreen center 0)
+(ScreenProperty center WindowSize NoBorderFullScreen)
+
+// Define the physical body.
+//
+// The head origin is halfway between the eyes, with X extending to the right,
+// Y up, and positive Z extending into the skull.
+// 
+(NewPhysicalBody SiteUser)
+
+// Set the interpupilary distance.  This sets the LeftEyePosition and
+// RightEyePosition to offsets of half this distance along both directions of
+// the X axis.
+// 
+(PhysicalBodyProperty SiteUser StereoEyeSeparation 0.066)
+
+// Create a view using the defined screen and physical body.
+//
+(NewView view0)
+(ViewProperty view0 Screen       center)
+(ViewProperty view0 PhysicalBody SiteUser)
+
+// Enable stereo viewing.
+// 
+(ViewProperty view0 StereoEnable true)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-vr.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-vr.html new file mode 100644 index 0000000..eb667b5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-vr.html @@ -0,0 +1,173 @@ + + + + + j3d1x1-vr config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a single screen stereo desktop display
+ * using a head tracker and 6DOF mouse.
+ *
+ ************************************************************************
+ */
+
+// Create a screen object and give it a logical name.
+(NewScreen center 0)
+
+// Set the actual available image area.
+(ScreenProperty center PhysicalScreenWidth     0.398)
+(ScreenProperty center PhysicalScreenHeight    0.282)
+(ScreenProperty center WindowSize              NoBorderFullScreen)
+
+// Set the TrackerBaseToImagePlate transform for this screen.
+(ScreenProperty center TrackerBaseToImagePlate
+                 (RotateTranslate (Rotate    50.000 0.000 0.000)
+                                  (Translate  0.199 0.376 0.000)))
+
+// Configure the head tracker.
+(NewDevice      tracker1 com.sun.j3d.input.LogitechTracker)
+(DeviceProperty tracker1 SerialPort "/dev/ttya")
+(DeviceProperty tracker1 ReceiverBaseline     0.1450)
+(DeviceProperty tracker1 ReceiverLeftLeg      0.0875)
+(DeviceProperty tracker1 ReceiverHeight       0.0470)
+(DeviceProperty tracker1 ReceiverTopOffset    0.0000)
+(DeviceProperty tracker1 RealtimeSerialBuffer true)
+
+// Configure the 6DOF wand.
+(NewDevice      tracker2 com.sun.j3d.input.LogitechTracker)
+(DeviceProperty tracker2 SerialPort "/dev/ttyb")
+(DeviceProperty tracker2 ReceiverBaseline     0.0700)
+(DeviceProperty tracker2 ReceiverLeftLeg      0.0625)
+(DeviceProperty tracker2 ReceiverHeight       0.0510)
+(DeviceProperty tracker2 ReceiverTopOffset    0.0000)
+(DeviceProperty tracker2 RealtimeSerialBuffer true)
+
+// Make the tracker2 device a slave of the tracker1 device.
+(DeviceProperty tracker1 Slave (Device tracker2))
+
+// Create a 2D mouse valuator.
+(NewDevice      mouse com.sun.j3d.input.Mouse2DValuator)
+(DeviceProperty mouse Components (Canvas3D center))
+
+// Create logical names for the available sensors.
+(NewSensor head    tracker1 0)
+(NewSensor mouse6d tracker2 0)
+(NewSensor mouse2d mouse    0)
+
+// Set the 6DOF mouse sensor hotspot in the local sensor coordinate system.
+(SensorProperty mouse6d Hotspot (0.00 0.00 -0.10))
+
+// Create a physical environment.
+(NewPhysicalEnvironment SampleSite)
+
+// Register the input devices and head tracker sensor.
+(PhysicalEnvironmentProperty SampleSite InputDevice tracker1)
+(PhysicalEnvironmentProperty SampleSite InputDevice tracker2)
+(PhysicalEnvironmentProperty SampleSite InputDevice mouse)
+(PhysicalEnvironmentProperty SampleSite HeadTracker head)
+
+// Define coexistence coordinates.
+(PhysicalEnvironmentProperty SampleSite CoexistenceToTrackerBase
+                              (TranslateRotate (Translate 0.0 -0.235 0.0)
+                                               (Rotate -50.0 0.0 0.0)))
+
+// Define the physical body.
+(NewPhysicalBody SiteUser)
+
+// Set the interpupilary distance.
+(PhysicalBodyProperty SiteUser StereoEyeSeparation 0.066)
+
+// Define the head location relative to the tracker mounted on the head.
+(PhysicalBodyProperty SiteUser HeadToHeadTracker ((1.0 0.0 0.0 0.000)
+                                                  (0.0 1.0 0.0 0.020)
+                                                  (0.0 0.0 1.0 0.018)))
+
+// Create a view platform behavior.  
+// 
+(NewViewPlatformBehavior vpb com.sun.j3d.utils.behaviors.vp.WandViewBehavior)
+
+(ViewPlatformBehaviorProperty vpb Sensor6D (Sensor mouse6d))
+(ViewPlatformBehaviorProperty vpb Sensor2D (Sensor mouse2d))
+
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 1 GrabView)
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 2 TranslateForward)
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 0 TranslateBackward)
+
+(ViewPlatformBehaviorProperty vpb RotationCoords ViewPlatform)
+(ViewPlatformBehaviorProperty vpb ButtonAction2D 1 Translation)
+(ViewPlatformBehaviorProperty vpb ButtonAction2D 2 Scale)
+
+(ViewPlatformBehaviorProperty vpb EchoType Beam) 
+(ViewPlatformBehaviorProperty vpb EchoSize 0.004) 
+
+(ViewPlatformBehaviorProperty vpb EchoColor 1.0 0.7 0.0)
+(ViewPlatformBehaviorProperty vpb EchoTransparency 0.4)
+
+// Create a new view platform and set the view platform behavior.
+// 
+(NewViewPlatform vp)
+(ViewPlatformProperty vp ViewPlatformBehavior vpb)
+
+// Create a view.
+//
+(NewView       view0)
+(ViewProperty  view0   Screen                  center)
+(ViewProperty  view0   PhysicalEnvironment     SampleSite)
+(ViewProperty  view0   PhysicalBody            SiteUser)
+(ViewProperty  view0   ViewPlatform            vp)
+
+// Enable stereo viewing and head tracking.
+(ViewProperty  view0   StereoEnable            true)
+(ViewProperty  view0   TrackingEnable          True)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-window.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-window.html new file mode 100644 index 0000000..96da173 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1-window.html @@ -0,0 +1,70 @@ + + + + + j3d1x1-window config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a conventional single screen, windowed
+ * desktop configuration.
+ *  
+ ************************************************************************
+ */
+
+(NewWindow window1 0)
+(WindowProperty window1 WindowSize (700.0 700.0))
+
+(NewView view1)
+(ViewProperty view1 Window window1)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1.html new file mode 100644 index 0000000..42842a4 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x1.html @@ -0,0 +1,69 @@ + + + + + j3d1x1 config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a single fullscreen desktop configuration.
+ *  
+ ************************************************************************
+ */
+
+(NewWindow big 0)
+(WindowProperty big WindowSize NoBorderFullScreen)
+
+(NewView view0)
+(ViewProperty view0 Window big)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-flat.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-flat.html new file mode 100644 index 0000000..3bf83f5 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-flat.html @@ -0,0 +1,152 @@ + + + + + j3d1x2-flat config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for dual-screen (flat) desktop configuration
+ * with no head tracking.
+ *  
+ ************************************************************************
+ */
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen left  0)
+(NewScreen right 1)
+
+// Set the screen dimensions.
+// 
+(ScreenProperty left  PhysicalScreenWidth  0.360)
+(ScreenProperty left  PhysicalScreenHeight 0.288)
+
+(ScreenProperty right PhysicalScreenWidth  0.360)
+(ScreenProperty right PhysicalScreenHeight 0.288)
+
+// Specify full screen windows.
+// 
+(ScreenProperty left  WindowSize NoBorderFullScreen)
+(ScreenProperty right WindowSize NoBorderFullScreen)
+
+// Set the TrackerBaseToImagePlate transforms for these screens.  This
+// transforms points in tracker base coordinates to each screen's image plate
+// coordinates, where the origin of the image plate is defined to be the lower
+// left corner of the screen with X increasing to the right, Y increasing to
+// the top, and Z increasing away from the screen.
+//
+// Without head or sensor tracking the tracker base is still needed as a fixed
+// frame of reference for describing the orientation and position of each
+// screen to the others.  The coexistence to tracker base transform is set to
+// identity by default, so the tracker base origin and orientation will also
+// set the origin and orientation of coexistence coordinates in the physical
+// world.
+//
+// The tracker base and center of coexistence is set here to the middle of the
+// edge shared by the two screens.
+//
+(ScreenProperty left  TrackerBaseToImagePlate
+                       (Translate 0.360 0.144 0.0))
+(ScreenProperty right TrackerBaseToImagePlate
+                       (Translate 0.000 0.144 0.0))
+
+// Sometimes it is desirable to include the bevels in between the monitors in
+// the TrackerBaseToImagePlate transforms, so that the abutting bevels obscure
+// the view of the virtual world instead of stretching it out between the
+// monitors.  For a bevel width of 4.5 cm on each monitor, the above commands
+// would become the following:
+// 
+// (ScreenProperty left  TrackerBaseToImagePlate
+//                        (Translate  0.405 0.144 0.0))
+// (ScreenProperty right TrackerBaseToImagePlate
+//                        (Translate -0.045 0.144 0.0))
+//
+// Conversely, a similar technique may be used to include overlap between the
+// screens.  This is useful for projection systems which use edge blending
+// to provide seamless integration between screens.
+
+
+// Create a view using the defined screens.
+// 
+(NewView view0)
+(ViewProperty view0 Screen left)
+(ViewProperty view0 Screen right)
+
+// Set the eyepoint relative to coexistence coordinates.  Here it is set 45cm
+// toward the user along Z, extending out from the midpoint of the edge shared
+// by the two screens.  This will create the appropriate skewed projection
+// frustums for each image plate.
+// 
+// If a planar display surface is all that is required, the same effect could
+// be achieved in a virtual screen enviroment such as Xinerama by simply
+// creating a canvas that spans both screens.  In some display environments the
+// use of a canvas that spans multiple physical screens may cause significant
+// performance degradation, however.
+// 
+// See j3d1x2-rot30 for an example of a non-planar configuration that cannot be
+// achieved through a single canvas spanning both screens.
+// 
+(ViewProperty view0 CenterEyeInCoexistence (0.0 0.0 0.45))
+
+(NewViewPlatform vp)
+(ViewPlatformProperty vp AllowPolicyRead true)
+(ViewPlatformProperty vp AllowLocalToVworldRead true)
+
+(ViewProperty view0 ViewPlatform vp)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-rot30.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-rot30.html new file mode 100644 index 0000000..279b9d0 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x2-rot30.html @@ -0,0 +1,111 @@ + + + + + j3d1x2-rot30 config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a dual-screen desktop configuration
+ * with each screen rotated toward the other by 30 degrees about Y from
+ * planar.  The inside angle between them is 120 degrees.
+ *  
+ ************************************************************************
+ */
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen left  0)
+(NewScreen right 1)
+
+// Set the available image areas for full screens.
+// 
+(ScreenProperty left  PhysicalScreenWidth  0.360)
+(ScreenProperty left  PhysicalScreenHeight 0.288)
+
+(ScreenProperty right PhysicalScreenWidth  0.360)
+(ScreenProperty right PhysicalScreenHeight 0.288)
+
+// Specify full screen windows.
+// 
+(ScreenProperty left  WindowSize NoBorderFullScreen)
+(ScreenProperty right WindowSize NoBorderFullScreen)
+
+// Set the TrackerBaseToImagePlate transforms for these screens.
+// 
+// The tracker base is set here to the middle of the edge shared by the two
+// screens.  Each screen is rotated 30 degrees toward the other about the
+// tracker base +Y axis, so that the tracker base +Z is centered between the
+// two screens.
+//
+(ScreenProperty left  TrackerBaseToImagePlate
+                       (RotateTranslate (Rotate     0.000 -30.000 0.0)
+                                        (Translate  0.360   0.144 0.0)))
+
+(ScreenProperty right TrackerBaseToImagePlate
+                       (RotateTranslate (Rotate     0.000  30.000 0.0)
+		                        (Translate  0.000   0.144 0.0)))
+
+
+// Create a view using the defined screens.
+// 
+(NewView view0)
+(ViewProperty view0 Screen left)
+(ViewProperty view0 Screen right)
+(ViewProperty view0 CenterEyeInCoexistence (0.0 0.0 0.45))
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave-vr.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave-vr.html new file mode 100644 index 0000000..f296dcb --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave-vr.html @@ -0,0 +1,243 @@ + + + + + j3d1x3-cave-vr config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a cave environment with head tracking and
+ * stereo viewing.  This cave consists of 3 projectors with 3 screens to the
+ * left, front, and right of the user, all at 90 degrees to each other.
+ * 
+ * The projectors in Sun's VirtualPortal sample site are actually turned
+ * on their sides to get more height.  Screen 0 is rotated 90 degrees
+ * counter-clockwise, while screens 1 and 2 are rotated 90 degrees
+ * clockwise.
+ * 
+ ************************************************************************
+ */
+
+// Configure the head tracker.
+// 
+(NewDevice      tracker1 com.sun.j3d.input.LogitechTracker)
+(DeviceProperty tracker1 SerialPort "/dev/ttya") // Unix paths need quoting.
+(DeviceProperty tracker1 TransmitterBaseline            0.4600)
+(DeviceProperty tracker1 TransmitterLeftLeg             0.4400)
+(DeviceProperty tracker1 TransmitterCalibrationDistance 0.4120)
+
+// Configure an InputDevice to use for a 6 degree of freedom wand.
+// 
+(NewDevice      tracker2 com.sun.j3d.input.LogitechTracker)
+(DeviceProperty tracker2 SerialPort "/dev/ttyb")
+(DeviceProperty tracker2 ReceiverBaseline     0.0700)
+(DeviceProperty tracker2 ReceiverLeftLeg      0.0625)
+(DeviceProperty tracker2 ReceiverHeight       0.0510)
+(DeviceProperty tracker2 ReceiverTopOffset    0.0000)
+
+// Make the tracker2 device a slave of the tracker1 device.
+(DeviceProperty tracker1 Slave (Device tracker2))
+
+// Create logical names for the head tracker and wand sensors.  The last
+// argument is the sensor's index in the input device.
+// 
+(NewSensor head     tracker1 0)
+(NewSensor sensor6d tracker2 0)
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen left    0)
+(NewScreen center  1)
+(NewScreen right   2)
+
+
+// Set the available image areas as well as their positition and orientation
+// relative to the tracker base.  From the orientation of a user standing
+// within this VirtualPortal site and facing the center screen, the tracker
+// base is along the vertical midline of the screen, 0.248 meters down from
+// the top edge, and 1.340 meters in front of it.  The tracker base is
+// oriented so that its +X axis points to the left, its +Y axis points toward
+// the screen, and its +Z axis points toward the floor.
+// 
+(ScreenProperty        left    PhysicalScreenWidth     2.480)
+(ScreenProperty        left    PhysicalScreenHeight    1.705)
+(ScreenProperty        left    WindowSize              NoBorderFullScreen)
+(ScreenProperty        left    TrackerBaseToImagePlate
+                                (( 0.0  0.0 -1.0  2.230)
+                                 ( 0.0 -1.0  0.0  1.340)
+                                 (-1.0  0.0  0.0  0.885)))
+
+(ScreenProperty        center  PhysicalScreenWidth     2.485)
+(ScreenProperty        center  PhysicalScreenHeight    1.745)
+(ScreenProperty        center  WindowSize              NoBorderFullScreen)
+(ScreenProperty        center  TrackerBaseToImagePlate
+                                (( 0.0  0.0  1.0  0.248)
+                                 (-1.0  0.0  0.0  0.885)
+                                 ( 0.0 -1.0  0.0  1.340)))
+
+(ScreenProperty        right   PhysicalScreenWidth     2.480)
+(ScreenProperty        right   PhysicalScreenHeight    1.775)
+(ScreenProperty        right   WindowSize              NoBorderFullScreen)
+(ScreenProperty        right   TrackerBaseToImagePlate
+                                (( 0.0  0.0  1.0  0.2488)
+                                 ( 0.0 -1.0  0.0  1.340)
+                                 ( 1.0  0.0  0.0  0.860)))
+
+// Create a physical environment.  This contains the available input devices,
+// audio devices, and sensors, and defines the coexistence coordinate system
+// for mapping between the virtual and physical worlds.
+// 
+(NewPhysicalEnvironment VirtualPortal)
+
+// Register the input device defined in this file and the sensor which will
+// drive head tracking.
+// 
+(PhysicalEnvironmentProperty VirtualPortal InputDevice tracker1)
+(PhysicalEnvironmentProperty VirtualPortal InputDevice tracker2)
+(PhysicalEnvironmentProperty VirtualPortal HeadTracker head)
+
+// Set the location of the center of coexistence relative to the tracker base.
+// Here it set to the center of the center screen.  The default view attach
+// policy of NOMINAL_SCREEN used by ConfiguredUniverse will place the origin of
+// the view platform in coexistence coordinates at the center of coexistence.
+// 
+(PhysicalEnvironmentProperty VirtualPortal
+                             CoexistenceToTrackerBase
+                                ((-1.0  0.0  0.0  0.000)
+                                 ( 0.0  0.0 -1.0  1.340)
+                                 ( 0.0 -1.0  0.0  0.994)))
+
+// Define the physical body.  The head origin is halfway between the eyes, with
+// X extending to the right, Y up, and positive Z extending into the skull.
+// 
+(NewPhysicalBody      LabRat)       
+(PhysicalBodyProperty LabRat StereoEyeSeparation .07)
+
+// Define the position and orientation of the head relative to the tracker
+// mounted on the head.
+// 
+(PhysicalBodyProperty LabRat HeadToHeadTracker 
+                                 ((-1.0  0.0  0.0 0.00)
+                                  ( 0.0  0.0 -1.0 0.05)
+                                  ( 0.0 -1.0  0.0 0.11)))
+
+// Create a view platform behavior for the 6DOF sensor.
+// 
+(NewViewPlatformBehavior vpb com.sun.j3d.utils.behaviors.vp.WandViewBehavior)
+
+(ViewPlatformBehaviorProperty vpb Sensor6D sensor6d)
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 2 GrabView)
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 1 TranslateForward)
+(ViewPlatformBehaviorProperty vpb ButtonAction6D 0 TranslateBackward)
+
+// Default normal translation speed is 0.1 physical meters per second.
+(ViewPlatformBehaviorProperty vpb TranslationSpeed
+                              1.0 PhysicalMeters PerSecond)
+
+// Default rotation coordinates are Sensor.
+(ViewPlatformBehaviorProperty vpb RotationCoords Head)
+
+// Nominal sensor transform for modified joystick RedBarron
+(SensorProperty sensor6d Hotspot (0.00 0.6 0.00))
+(ViewPlatformBehaviorProperty vpb NominalSensorRotation
+                                  ((-1.0  0.0  0.0)
+                                   ( 0.0  0.0 -1.0)
+                                   ( 0.0 -1.0  0.0)))
+
+// Default 6DOF sensor echo is Gnomon
+(ViewPlatformBehaviorProperty vpb EchoSize 0.015) 
+(ViewPlatformBehaviorProperty vpb EchoType Beam) 
+
+// Default 6DOF sensor echo color is white
+(ViewPlatformBehaviorProperty vpb EchoColor 1.0 0.7 0.0)
+
+// Default 6DOF sensor transparency is 0.0 (opaque)
+(ViewPlatformBehaviorProperty vpb EchoTransparency 0.4)
+
+// Create a new view platform and set the view platform behavior.
+// 
+(NewViewPlatform vp)
+(ViewPlatformProperty vp ViewPlatformBehavior vpb)
+
+// Now define the view.
+// 
+(NewView       view0)
+(ViewProperty  view0   Screen                  left)
+(ViewProperty  view0   Screen                  center)
+(ViewProperty  view0   Screen                  right)
+(ViewProperty  view0   PhysicalBody            LabRat)
+(ViewProperty  view0   PhysicalEnvironment     VirtualPortal)
+(ViewProperty  view0   ViewPlatform            vp)
+
+// Set the screen scale.  This is scale factor from virtual to physical
+// coordinates.
+// 
+(ViewProperty  view0   ScreenScalePolicy       SCALE_SCREEN_SIZE)
+
+// Alternative for explict scaling.
+// 
+//(ViewProperty  view0   ScreenScalePolicy       SCALE_EXPLICIT)
+//(ViewProperty  view0   ScreenScale             5.00)
+
+// Enable stereo viewing.  Enable head tracking to get the position of the eyes
+// with respect to coexistence.  Boolean values may be specified as either
+// true, True, false, or False.
+// 
+(ViewProperty    view0   StereoEnable            true)
+(ViewProperty    view0   TrackingEnable          True)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave.html new file mode 100644 index 0000000..cf2b008 --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-cave.html @@ -0,0 +1,156 @@ + + + + + j3d1x3-cave config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for a cave environment.  This cave
+ * consists of 3 projectors with 3 screens to the left, front, and right
+ * of the user, all at 90 degrees to each other.
+ * 
+ * The projectors in the VirtualPortal sample site are actually turned
+ * on their sides to get more height.  Screen 0 is rotated 90 degrees
+ * counter-clockwise, while screens 1 and 2 are rotated 90 degrees
+ * clockwise.
+ * 
+ ************************************************************************
+ */
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen              left    0)
+(NewScreen              center  1)
+(NewScreen              right   2)
+
+
+// Set the available image areas as well as their positition and orientation
+// relative to the tracker base.   Although this config file doesn't enable
+// head tracking, the tracker base is still needed as a point of reference to
+// describe the position and orientation of the screens relative to the
+// environment. 
+// 
+// From the orientation of a user standing within this VirtualPortal site and
+// facing the center screen, the tracker base is along the vertical midline of
+// the screen, 0.248 meters down from the top edge, and 1.340 meters in front
+// of it.  The tracker base is oriented so that its +X axis points to the left,
+// its +Y axis points toward the screen, and its +Z axis points toward the
+// floor.
+// 
+(ScreenProperty        left    PhysicalScreenWidth     2.480)
+(ScreenProperty        left    PhysicalScreenHeight    1.705)
+(ScreenProperty        left    WindowSize              NoBorderFullScreen)
+(ScreenProperty        left    TrackerBaseToImagePlate
+                                (( 0.0  0.0 -1.0  2.230)
+                                 ( 0.0 -1.0  0.0  1.340)
+                                 (-1.0  0.0  0.0  0.885)))
+
+(ScreenProperty        center  PhysicalScreenWidth     2.485)
+(ScreenProperty        center  PhysicalScreenHeight    1.745)
+(ScreenProperty        center  WindowSize              NoBorderFullScreen)
+(ScreenProperty        center  TrackerBaseToImagePlate
+                                (( 0.0  0.0  1.0  0.248)
+                                 (-1.0  0.0  0.0  0.885)
+                                 ( 0.0 -1.0  0.0  1.340)))
+
+(ScreenProperty        right   PhysicalScreenWidth     2.480)
+(ScreenProperty        right   PhysicalScreenHeight    1.775)
+(ScreenProperty        right   WindowSize              NoBorderFullScreen)
+(ScreenProperty        right   TrackerBaseToImagePlate
+                                (( 0.0  0.0  1.0  0.2488)
+                                 ( 0.0 -1.0  0.0  1.340)
+                                 ( 1.0  0.0  0.0  0.860)))
+
+// Set the location of the center of coexistence relative to the tracker base.
+// Here it set to the center of the center screen.  This config file will set
+// the location of the user's eyes relative to this point.  The default view
+// attach policy of NOMINAL_SCREEN used by ConfiguredUniverse will place the
+// origin of the view platform in coexistence coordinates at the center of
+// coexistence. 
+// 
+(NewPhysicalEnvironment      VirtualPortal)
+(PhysicalEnvironmentProperty VirtualPortal
+                             CoexistenceToTrackerBase
+                                ((-1.0  0.0  0.0  0.000)
+                                 ( 0.0  0.0 -1.0  1.340)
+                                 ( 0.0 -1.0  0.0  0.994)))
+
+// Now define the view.
+// 
+(NewView       view0)
+(ViewProperty  view0   Screen                  left)
+(ViewProperty  view0   Screen                  center)
+(ViewProperty  view0   Screen                  right)
+(ViewProperty  view0   PhysicalEnvironment     VirtualPortal)
+
+// Set the user eye position in the display environment.  It is set here to
+// 1.340 meters back from the center screen (directly under the tracker), and
+// 1.737 meters from the floor (about 5 ft 8.4 inches).
+//
+(ViewProperty  view0   CenterEyeInCoexistence  (0.0 0.494 1.340))
+
+// Explict scaling.
+// 
+(ViewProperty  view0   ScreenScalePolicy       SCALE_EXPLICIT)
+(ViewProperty  view0   ScreenScale             0.30)
+
+// No stereo viewing for this configuration.
+// 
+(ViewProperty  view0   StereoEnable            False)
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-rot45.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-rot45.html new file mode 100644 index 0000000..c3e743e --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d1x3-rot45.html @@ -0,0 +1,122 @@ + + + + + j3d1x3-rot45 config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for 3 screens.  Left and right screens are
+ * rotated 45 degrees from the center screen.
+ *  
+ ************************************************************************
+ */
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen left   0)
+(NewScreen center 1)
+(NewScreen right  2)
+
+// Set the available image areas for full screens.  
+// 
+(ScreenProperty left   PhysicalScreenWidth  0.360)
+(ScreenProperty left   PhysicalScreenHeight 0.288)
+
+(ScreenProperty center PhysicalScreenWidth  0.360)
+(ScreenProperty center PhysicalScreenHeight 0.288)
+
+(ScreenProperty right  PhysicalScreenWidth  0.360)
+(ScreenProperty right  PhysicalScreenHeight 0.288)
+
+// Specify full screen windows.
+// 
+(ScreenProperty left   WindowSize NoBorderFullScreen)
+(ScreenProperty center WindowSize NoBorderFullScreen)
+(ScreenProperty right  WindowSize NoBorderFullScreen)
+
+// Set the TrackerBaseToImagePlate transforms for these screens.
+// 
+// The tracker base and center of coexistence are set here to the middle of the
+// center screen. The basis vectors are aligned with the center screen image
+// plate.  The left and right screens are rotated 45 degrees toward each other
+// about their shared edges with the center screen.
+//
+(ScreenProperty center TrackerBaseToImagePlate
+                        (Translate 0.180000    0.144000 0.000000))
+
+// cos(45) * 0.360 * 0.5 = 0.127279; 0.360 + 0.127279 = 0.487279
+(ScreenProperty left  TrackerBaseToImagePlate
+                       (RotateTranslate
+		        (Rotate     0.000000 -45.000000 0.000000)
+                        (Translate  0.487279   0.144000 0.127279)))
+
+// cos(45) * 0.360 * 0.5 = 0.127279
+(ScreenProperty right TrackerBaseToImagePlate
+                       (RotateTranslate
+		        (Rotate     0.000000  45.000000 0.000000)
+                        (Translate -0.127279   0.144000 0.127279)))
+
+// Create a view using the defined screens.
+// 
+(NewView      view0)
+(ViewProperty view0 Screen left)
+(ViewProperty view0 Screen center)
+(ViewProperty view0 Screen right)
+(ViewProperty view0 CenterEyeInCoexistence (0.0 0.0 0.5))
+
+ + diff --git a/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d2x2-flat.html b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d2x2-flat.html new file mode 100644 index 0000000..7ba903f --- /dev/null +++ b/src/classes/share/com/sun/j3d/utils/universe/doc-files/j3d2x2-flat.html @@ -0,0 +1,147 @@ + + + + + j3d2x2-flat config file + + + +
+/*
+ ************************************************************************
+ *  
+ * Java 3D configuration file for 4 screen projection configuration
+ * arranged in a 2x2 power wall.
+ *  
+ ************************************************************************
+ */
+
+// Create new screen objects and associate them with logical names and numbers.
+// These numbers are used as indices to retrieve the AWT GraphicsDevice from
+// the array that GraphicsEnvironment.getScreenDevices() returns.
+// 
+// NOTE: The GraphicsDevice order in the array is specific to the local
+// site and display system.
+// 
+(NewScreen topleft     0)
+(NewScreen topright    1)
+(NewScreen bottomleft  3)
+(NewScreen bottomright 2)
+
+// Set the available image areas for full screens.  This is important when
+// precise scaling between objects in the virtual world and their projections
+// into the physical world is desired through use of explicit ScreenScale view
+// attributes.  The defaults are 0.365 meters for width and 0.292 meters for
+// height.
+// 
+(ScreenProperty topleft     PhysicalScreenWidth  0.912)
+(ScreenProperty topleft     PhysicalScreenHeight 0.680)
+
+(ScreenProperty topright    PhysicalScreenWidth  0.912)
+(ScreenProperty topright    PhysicalScreenHeight 0.680)
+
+(ScreenProperty bottomleft  PhysicalScreenWidth  0.912)
+(ScreenProperty bottomleft  PhysicalScreenHeight 0.685)
+
+(ScreenProperty bottomright PhysicalScreenWidth  0.912)
+(ScreenProperty bottomright PhysicalScreenHeight 0.685)
+
+
+// Specify full screen windows.
+//
+(ScreenProperty topleft     WindowSize NoBorderFullScreen)
+(ScreenProperty topright    WindowSize NoBorderFullScreen)
+(ScreenProperty bottomleft  WindowSize NoBorderFullScreen)
+(ScreenProperty bottomright WindowSize NoBorderFullScreen)
+
+// Set the TrackerBaseToImagePlate transforms for these screens.  This
+// transforms points in tracker base coordinates to each screen's image plate
+// coordinates, where the origin of the image plate is defined to be the lower
+// left corner of the screen with X increasing to the right, Y increasing to
+// the top, and Z increasing away from the screen.
+//
+// Without head or sensor tracking the tracker base is still needed as a point
+// of reference for describing the orientation and position of each screen to
+// the others.  The coexistence to tracker base transform is set to identity by
+// default, so the tracker base origin and orientation will also set the origin
+// and orientation of coexistence coordinates in the physical world.
+//
+// The tracker base and center of coexistence are set here to the center of the
+// 2x2 array with its basis vectors aligned to image plate coordinates.
+//
+(ScreenProperty topleft     TrackerBaseToImagePlate
+                             (Translate 0.912 0.000 0.0))
+(ScreenProperty topright    TrackerBaseToImagePlate
+                             (Translate 0.000 0.000 0.0))
+(ScreenProperty bottomleft  TrackerBaseToImagePlate
+                             (Translate 0.912 0.685 0.0))
+(ScreenProperty bottomright TrackerBaseToImagePlate
+                             (Translate 0.000 0.685 0.0))
+
+// Create a view using the defined screens.
+// 
+(NewView      view0)
+(ViewProperty view0 Screen  topleft)
+(ViewProperty view0 Screen  topright)
+(ViewProperty view0 Screen  bottomleft)
+(ViewProperty view0 Screen  bottomright)
+
+// Set the screen scale.  This is scale factor from virtual to physical
+// coordinates.  The default policy of SCALE_SCREEN_SIZE doesn't work well here
+// since in the 2x2 arrangement the individual screens are too small.  The
+// explicit scale factor below assumes a normalized range of object coordinates
+// of [-1.0 .. +1.0].
+// 
+(ViewProperty view0 ScreenScalePolicy       SCALE_EXPLICIT)
+(ViewProperty view0 ScreenScale             0.912)
+
+// Set the user eye position in the display environment.
+//
+(ViewProperty view0 CenterEyeInCoexistence  (0.0 0.0 1.0))
+
+ + diff --git a/src/native/share/J3DTimer.c b/src/native/share/J3DTimer.c new file mode 100644 index 0000000..cd32a08 --- /dev/null +++ b/src/native/share/J3DTimer.c @@ -0,0 +1,153 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistribution of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistribution in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * Neither the name of Sun Microsystems, Inc. or the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * This software is provided "AS IS," without a warranty of any + * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND + * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY + * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL + * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF + * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS + * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR + * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, + * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND + * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR + * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * You acknowledge that this software is not designed, licensed or + * intended for use in the design, construction, operation or + * maintenance of any nuclear facility. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Portions of this code were derived from work done by the Blackdown + * group (www.blackdown.org), who did the initial Linux implementation + * of the Java 3D API. + */ + +#include "com_sun_j3d_utils_timer_J3DTimer.h" + +#ifdef __linux__ +#include +#include +#include +#endif + +#ifdef SOLARIS + #include + #include + #include +#ifndef CLOCK_HIGHRES +#define CLOCK_HIGHRES 4 /* Solaris 7 does not define this */ +#endif /* constant. When run on Solaris 7 */ +#endif /* CLOCK_HIGHRES is not used. */ + +#ifdef WIN32 + #include + #include + jlong pcRes = -1; +#endif + +/* + * Class: com_sun_j3d_utils_timer_J3DTimer + * Method: getNativeTimer + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_sun_j3d_utils_timer_J3DTimer_getNativeTimer + (JNIEnv *env, jclass clazz) { + +#ifdef SOLARIS + /*struct timespec tp;*/ + /*clock_gettime( CLOCK_HIGHRES, &tp );*/ + + /*return (jlong)tp.tv_nsec + (jlong)tp.tv_sec*1000000000;*/ + + return (jlong)gethrtime(); +#endif + +#ifdef WIN32 + LARGE_INTEGER time; + + QueryPerformanceCounter( &time ); + + if (pcRes==-1) + pcRes = Java_com_sun_j3d_utils_timer_J3DTimer_getNativeTimerResolution(NULL, NULL ); + + return (jlong)time.QuadPart*pcRes; +#endif +#ifdef __linux__ + + struct timeval t; + gettimeofday(&t, 0); + return ((jlong)t.tv_sec) * 1000000000LL + ((jlong)t.tv_usec) * 1000; + +#endif + +} + +/* + * Class: com_sun_j3d_utils_timer_J3DTimer + * Method: getNativeTimerResolution + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_com_sun_j3d_utils_timer_J3DTimer_getNativeTimerResolution + (JNIEnv *env, jclass clazz) { + +#ifdef SOLARIS + + char buf[4]; + jlong res=0; + + sysinfo( SI_RELEASE, &buf[0], 4 ); + + if (strcmp( "5.7", &buf[0] )==0) { + res = (jlong)3; + } else { + struct timespec tp; + clock_getres( CLOCK_HIGHRES, &tp ); + res = (jlong)tp.tv_nsec; + } + + return res; +#endif + +#ifdef WIN32 + LARGE_INTEGER freq; + QueryPerformanceFrequency( &freq ); + + if ( (jlong)freq.QuadPart==0 ) + return 1; + + return (jlong) ceil(1000000000/((jlong)freq.QuadPart)); +#endif + +#ifdef __linux__ + + return ((jlong)1000000000) /sysconf (_SC_CLK_TCK); + + #endif + +} -- cgit v1.2.3