aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/ardor3d-core
diff options
context:
space:
mode:
authorRenanse <[email protected]>2012-10-13 12:58:16 -0500
committerRenanse <[email protected]>2012-10-13 12:58:16 -0500
commite60afe7110d0d43cdc41f44aa631de19f312ec54 (patch)
tree99d94490c346b06186ebfbba79500a918b1f9ca1 /trunk/ardor3d-core
parent555475f5aa57fb5feffefe30fa9a088341cd6a92 (diff)
moved to github
Diffstat (limited to 'trunk/ardor3d-core')
-rw-r--r--trunk/ardor3d-core/.classpath15
-rw-r--r--trunk/ardor3d-core/.project17
-rw-r--r--trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs3
-rw-r--r--trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs278
-rw-r--r--trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs114
-rw-r--r--trunk/ardor3d-core/buildjar.jardesc17
-rw-r--r--trunk/ardor3d-core/lib/google/guava-13.0.1.jarbin0 -> 1891110 bytes
-rw-r--r--trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jarbin0 -> 322362 bytes
-rw-r--r--trunk/ardor3d-core/lib/unittests/easymock.jarbin0 -> 78815 bytes
-rw-r--r--trunk/ardor3d-core/lib/unittests/easymockclassextension.jarbin0 -> 56010 bytes
-rw-r--r--trunk/ardor3d-core/lib/unittests/junit-4.5.jarbin0 -> 198945 bytes
-rw-r--r--trunk/ardor3d-core/pom.xml54
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java24
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java35
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java23
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java874
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java729
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java259
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java595
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java36
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java410
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java1439
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java96
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java52
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java100
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java266
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java193
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java82
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java38
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java377
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java57
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java56
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java1528
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java119
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java132
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java145
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java175
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java366
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java83
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java268
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java36
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java117
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java211
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java491
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java125
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java385
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java63
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java230
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java131
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java18
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java47
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java114
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java30
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java20
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java19
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java75
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java761
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java58
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java23
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java18
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java105
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java37
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java124
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java65
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java264
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java215
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java322
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java382
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java60
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java51
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java49
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java28
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java48
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java47
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java85
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java47
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java47
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java126
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java24
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java47
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java62
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java53
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java53
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java36
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java36
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java33
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java121
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java70
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java46
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java76
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java90
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java89
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java222
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java58
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java148
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java57
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java205
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java54
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java58
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java24
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java42
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java393
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java88
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java355
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java98
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java135
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java291
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java178
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java191
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java1634
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java662
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java17
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java81
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java88
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java122
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java232
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java19
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java591
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java194
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java213
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java103
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java167
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java55
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java45
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java54
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java32
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java66
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java107
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java29
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java44
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java86
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java121
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java165
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java41
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java149
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java96
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java51
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java100
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java148
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java136
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java636
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java125
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java129
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java109
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java225
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java231
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java1333
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java397
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java110
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java401
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java147
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java279
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java91
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java580
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java376
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java246
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java137
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java142
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java67
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java30
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java46
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java28
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java49
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java106
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java120
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java32
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java199
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java32
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java173
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java45
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java21
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java33
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java38
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java115
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java91
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java35
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java320
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java137
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java146
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java66
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java132
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java137
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java134
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java252
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java777
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java1253
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java515
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java320
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java137
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java1423
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java232
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java286
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java139
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java52
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java420
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java36
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java70
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java136
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java18
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java63
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java277
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java113
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java105
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java123
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java406
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java257
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java142
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java41
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java28
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java27
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java493
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java107
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java130
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java329
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java333
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java31
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java399
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java144
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java171
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java299
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java360
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java366
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java141
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java127
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java50
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java124
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java404
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java198
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java200
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java117
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java305
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java436
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java275
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java852
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java216
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java325
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java40
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java29
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java22
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java23
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java270
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java93
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java325
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java62
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java954
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java761
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java90
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java32
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java68
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java49
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java90
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java56
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java192
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java141
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java162
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java100
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java136
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java227
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java59
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java50
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java173
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java417
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java483
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java68
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java77
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java24
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java357
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java37
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java275
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java1417
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java1013
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java1295
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java796
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java210
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java26
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java55
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java62
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java1697
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java22
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java701
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java229
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java448
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java218
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java812
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java40
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java96
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java125
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java46
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java157
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java56
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java127
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java56
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java29
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java193
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java51
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java138
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java106
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java204
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java25
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java129
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java20
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java89
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java37
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java46
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java73
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java37
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java40
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java1
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java34
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java37
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java40
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java43
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java1
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java39
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java1
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java73
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java69
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java69
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java73
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java73
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java54
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java356
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java17
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java67
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java65
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java198
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java96
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java164
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java338
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java33
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java303
-rw-r--r--trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java323
-rw-r--r--trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF46
-rw-r--r--trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tgabin0 -> 30510 bytes
-rw-r--r--trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3dbin0 -> 17840 bytes
-rw-r--r--trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt1403
-rw-r--r--trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.pngbin0 -> 183837 bytes
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java40
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java94
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java81
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java389
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java249
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java202
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java62
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java103
-rw-r--r--trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java43
386 files changed, 69267 insertions, 0 deletions
diff --git a/trunk/ardor3d-core/.classpath b/trunk/ardor3d-core/.classpath
new file mode 100644
index 0000000..b30d97b
--- /dev/null
+++ b/trunk/ardor3d-core/.classpath
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/test/java"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/ardor3d-savable"/>
+ <classpathentry combineaccessrules="false" exported="true" kind="src" path="/ardor3d-math"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/cglib-nodep-2.2.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/easymock.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/easymockclassextension.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/unittests/junit-4.5.jar"/>
+ <classpathentry exported="true" kind="lib" path="lib/google/guava-13.0.1.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/trunk/ardor3d-core/.project b/trunk/ardor3d-core/.project
new file mode 100644
index 0000000..d426d9d
--- /dev/null
+++ b/trunk/ardor3d-core/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>ardor3d-core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs b/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..82a25eb
--- /dev/null
+++ b/trunk/ardor3d-core/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Tue Jan 06 12:48:37 PST 2009
+eclipse.preferences.version=1
+encoding/src=UTF-8
diff --git a/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs b/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..10a0c88
--- /dev/null
+++ b/trunk/ardor3d-core/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,278 @@
+#Tue Apr 06 11:24:38 CDT 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.codeComplete.argumentPrefixes=
+org.eclipse.jdt.core.codeComplete.argumentSuffixes=
+org.eclipse.jdt.core.codeComplete.fieldPrefixes=_
+org.eclipse.jdt.core.codeComplete.fieldSuffixes=
+org.eclipse.jdt.core.codeComplete.localPrefixes=
+org.eclipse.jdt.core.codeComplete.localSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFieldSuffixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldPrefixes=
+org.eclipse.jdt.core.codeComplete.staticFinalFieldSuffixes=
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=1
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=120
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true
+org.eclipse.jdt.core.formatter.indentation.size=4
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
diff --git a/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs b/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 0000000..8110121
--- /dev/null
+++ b/trunk/ardor3d-core/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,114 @@
+#Mon Jan 02 22:40:01 CST 2012
+cleanup.add_default_serial_version_id=true
+cleanup.add_generated_serial_version_id=false
+cleanup.add_missing_annotations=true
+cleanup.add_missing_deprecated_annotations=true
+cleanup.add_missing_methods=true
+cleanup.add_missing_nls_tags=false
+cleanup.add_missing_override_annotations=true
+cleanup.add_serial_version_id=true
+cleanup.always_use_blocks=true
+cleanup.always_use_parentheses_in_expressions=true
+cleanup.always_use_this_for_non_static_field_access=false
+cleanup.always_use_this_for_non_static_method_access=false
+cleanup.convert_to_enhanced_for_loop=false
+cleanup.correct_indentation=true
+cleanup.format_source_code=true
+cleanup.format_source_code_changes_only=false
+cleanup.make_local_variable_final=true
+cleanup.make_parameters_final=true
+cleanup.make_private_fields_final=true
+cleanup.make_type_abstract_if_missing_method=false
+cleanup.make_variable_declarations_final=true
+cleanup.never_use_blocks=false
+cleanup.never_use_parentheses_in_expressions=false
+cleanup.organize_imports=true
+cleanup.qualify_static_field_accesses_with_declaring_class=false
+cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+cleanup.qualify_static_member_accesses_with_declaring_class=true
+cleanup.qualify_static_method_accesses_with_declaring_class=false
+cleanup.remove_private_constructors=true
+cleanup.remove_trailing_whitespaces=true
+cleanup.remove_trailing_whitespaces_all=true
+cleanup.remove_trailing_whitespaces_ignore_empty=false
+cleanup.remove_unnecessary_casts=true
+cleanup.remove_unnecessary_nls_tags=true
+cleanup.remove_unused_imports=true
+cleanup.remove_unused_local_variables=false
+cleanup.remove_unused_private_fields=true
+cleanup.remove_unused_private_members=false
+cleanup.remove_unused_private_methods=true
+cleanup.remove_unused_private_types=true
+cleanup.sort_members=false
+cleanup.sort_members_all=false
+cleanup.use_blocks=true
+cleanup.use_blocks_only_for_return_and_throw=false
+cleanup.use_parentheses_in_expressions=false
+cleanup.use_this_for_non_static_field_access=true
+cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+cleanup.use_this_for_non_static_method_access=true
+cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+cleanup_profile=_ArdorLabs
+cleanup_settings_version=2
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+formatter_profile=_ArdorLabs
+formatter_settings_version=11
+org.eclipse.jdt.ui.exception.name=ex
+org.eclipse.jdt.ui.gettersetter.use.is=true
+org.eclipse.jdt.ui.javadoc=false
+org.eclipse.jdt.ui.keywordthis=false
+org.eclipse.jdt.ui.overrideannotation=true
+org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/**\n * Copyright (c) 2008-2012 Ardor Labs, Inc.\n *\n * This file is part of Ardor3D.\n *\n * Ardor3D is free software\: you can redistribute it and/or modify it \n * under the terms of its license which may be found in the accompanying\n * LICENSE file or at &lt;http\://www.ardor3d.com/LICENSE&gt;.\n */\n\n${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated method stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\n * @return the ${bare_field_name}\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\n * @param ${param} the ${bare_field_name} to set\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created JavaScript files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\n * \n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\n * @author ${user}\n *\n * ${tags}\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for vars" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\n * \n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\n * ${tags}\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Jsdoc)\n * ${see_to_overridden}\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\n * ${tags}\n * ${see_to_target}\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\n${package_declaration}\n\n${typecomment}\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.classbody" name\="classbody">\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created function stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated function stub\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates>
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=true
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=true
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=true
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_blocks=true
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=true
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=true
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/trunk/ardor3d-core/buildjar.jardesc b/trunk/ardor3d-core/buildjar.jardesc
new file mode 100644
index 0000000..2ea7295
--- /dev/null
+++ b/trunk/ardor3d-core/buildjar.jardesc
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
+<jardesc>
+ <jar path="C:/Users/Joshua Slack/Desktop/ardor3d-core.jar"/>
+ <options buildIfNeeded="true" compress="true" descriptionLocation="/ardor3d-core/buildjar.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="true" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
+ <storedRefactorings deprecationInfo="true" structuralOnly="false"/>
+ <selectedProjects/>
+ <manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
+ <sealing sealJar="false">
+ <packagesToSeal/>
+ <packagesToUnSeal/>
+ </sealing>
+ </manifest>
+ <selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
+ <javaElement handleIdentifier="=ardor3d-core/src\/main\/java"/>
+ <javaElement handleIdentifier="=ardor3d-core/src\/main\/resources"/>
+ </selectedElements>
+</jardesc>
diff --git a/trunk/ardor3d-core/lib/google/guava-13.0.1.jar b/trunk/ardor3d-core/lib/google/guava-13.0.1.jar
new file mode 100644
index 0000000..09c5449
--- /dev/null
+++ b/trunk/ardor3d-core/lib/google/guava-13.0.1.jar
Binary files differ
diff --git a/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar b/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar
new file mode 100644
index 0000000..ed07cb5
--- /dev/null
+++ b/trunk/ardor3d-core/lib/unittests/cglib-nodep-2.2.jar
Binary files differ
diff --git a/trunk/ardor3d-core/lib/unittests/easymock.jar b/trunk/ardor3d-core/lib/unittests/easymock.jar
new file mode 100644
index 0000000..de59168
--- /dev/null
+++ b/trunk/ardor3d-core/lib/unittests/easymock.jar
Binary files differ
diff --git a/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar b/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar
new file mode 100644
index 0000000..7d098a7
--- /dev/null
+++ b/trunk/ardor3d-core/lib/unittests/easymockclassextension.jar
Binary files differ
diff --git a/trunk/ardor3d-core/lib/unittests/junit-4.5.jar b/trunk/ardor3d-core/lib/unittests/junit-4.5.jar
new file mode 100644
index 0000000..7339216
--- /dev/null
+++ b/trunk/ardor3d-core/lib/unittests/junit-4.5.jar
Binary files differ
diff --git a/trunk/ardor3d-core/pom.xml b/trunk/ardor3d-core/pom.xml
new file mode 100644
index 0000000..9edd61b
--- /dev/null
+++ b/trunk/ardor3d-core/pom.xml
@@ -0,0 +1,54 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.ardor3d</groupId>
+ <artifactId>ardor3d</artifactId>
+ <version>0.8-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ardor3d-core</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Core</name>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-math</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymockclassextension</artifactId>
+ </dependency>
+ </dependencies>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
+
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java
new file mode 100644
index 0000000..636cc6b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/GuardedBy.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Documents which lock is used to guard a field, if the field is protected by a lock.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.FIELD)
+public @interface GuardedBy {
+ String value();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java
new file mode 100644
index 0000000..e83a8ea
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/Immutable.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a class is, or at least is intended to be, immutable in a strict sense. See
+ * http://www.javaconcurrencyinpractice.com/ or the actual book for more information about immutability.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface Immutable {}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java
new file mode 100644
index 0000000..22efacf
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/MainThread.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Methods flagged with this annotation should only be run in the main thread, that is, the thread that handles the
+ * OpenGL calls. It is possible, and good during development but possibly not during production, to use a method
+ * interceptor that enforces this constraint, but in any case, the presence of the annotation should help programmers
+ * when thinking about threading.
+ *
+ * This annotation should be used on any API method in the framework for which it is necessary to call it only from the
+ * main thread. If it adds to clarity, it may be a good idea to use it for internal methods as well.
+ *
+ * Note that this annotation, when present on an interface method, does not get inherited, and it is therefore there
+ * only for clarity purposes rather than as a way of getting method interception to work. It should always be added to
+ * any class directly implementing an interface method that uses it.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target( { ElementType.METHOD, ElementType.PARAMETER })
+@Inherited
+public @interface MainThread {}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java
new file mode 100644
index 0000000..03e4c7f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/SavableFactory.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Tells the Savable system to instantiate objects of this type using a specific static method. The method should take
+ * no arguments and return a new instance of the annotated class.
+ *
+ * It is recommended the method be named something indicating use for Savable system.
+ */
+@Target( { TYPE })
+@Retention(RUNTIME)
+public @interface SavableFactory {
+
+ /**
+ * @return the name of the static method to use to build this class.
+ */
+ String factoryMethod();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java
new file mode 100644
index 0000000..912e005
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/annotation/ThreadSafe.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a class is, or at least is intended to be, thread safe.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface ThreadSafe {} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java
new file mode 100644
index 0000000..d488d81
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingBox.java
@@ -0,0 +1,874 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyPlane;
+import com.ardor3d.math.type.ReadOnlyPlane.Side;
+import com.ardor3d.math.type.ReadOnlyRay3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>BoundingBox</code> defines an axis-aligned cube that defines a container for a group of vertices of a
+ * particular piece of geometry. This box defines a center and extents from that center along the x, y and z axis. <br>
+ * <br>
+ * A typical usage is to allow the class define the center and radius by calling either <code>containAABB</code> or
+ * <code>averagePoints</code>. A call to <code>computeFramePoint</code> in turn calls <code>containAABB</code>.
+ */
+public class BoundingBox extends BoundingVolume {
+
+ private static final long serialVersionUID = 1L;
+
+ private double _xExtent, _yExtent, _zExtent;
+
+ /**
+ * Default constructor instantiates a new <code>BoundingBox</code> object.
+ */
+ public BoundingBox() {}
+
+ /**
+ * Constructor instantiates a new <code>BoundingBox</code> object with given values.
+ */
+ public BoundingBox(final BoundingBox other) {
+ this(other.getCenter(), other.getXExtent(), other.getYExtent(), other.getZExtent());
+ }
+
+ /**
+ * Constructor instantiates a new <code>BoundingBox</code> object with given values.
+ */
+ public BoundingBox(final ReadOnlyVector3 c, final double x, final double y, final double z) {
+ _center.set(c);
+ setXExtent(x);
+ setYExtent(y);
+ setZExtent(z);
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof BoundingBox)) {
+ return false;
+ }
+ final BoundingBox b = (BoundingBox) other;
+ return _center.equals(b._center) && _xExtent == b._xExtent && _yExtent == b._yExtent && _zExtent == b._zExtent;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.AABB;
+ }
+
+ public void setXExtent(final double xExtent) {
+ _xExtent = xExtent;
+ }
+
+ public double getXExtent() {
+ return _xExtent;
+ }
+
+ public void setYExtent(final double yExtent) {
+ _yExtent = yExtent;
+ }
+
+ public double getYExtent() {
+ return _yExtent;
+ }
+
+ public void setZExtent(final double zExtent) {
+ _zExtent = zExtent;
+ }
+
+ public double getZExtent() {
+ return _zExtent;
+ }
+
+ @Override
+ public double getRadius() {
+ return MathUtils.sqrt(_xExtent * _xExtent + _yExtent * _yExtent + _zExtent * _zExtent);
+ }
+
+ // Some transform matrices are not in decomposed form and in this
+ // situation we need to use a different, more robust, algorithm
+ // for computing the new bounding box.
+ @Override
+ public BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store) {
+
+ if (transform.isRotationMatrix()) {
+ return transformRotational(transform, store);
+ }
+
+ BoundingBox box;
+ if (store == null || store.getType() != Type.AABB) {
+ box = new BoundingBox();
+ } else {
+ box = (BoundingBox) store;
+ }
+
+ final Vector3[] corners = new Vector3[8];
+ for (int i = 0; i < corners.length; i++) {
+ corners[i] = Vector3.fetchTempInstance();
+ }
+ getCorners(corners);
+
+ // Transform all of these points by the transform
+ for (int i = 0; i < corners.length; i++) {
+ transform.applyForward(corners[i]);
+ }
+ // Now compute based on these transformed points
+ double minX = corners[0].getX();
+ double minY = corners[0].getY();
+ double minZ = corners[0].getZ();
+ double maxX = minX;
+ double maxY = minY;
+ double maxZ = minZ;
+ for (int i = 1; i < corners.length; i++) {
+ final double curX = corners[i].getX();
+ final double curY = corners[i].getY();
+ final double curZ = corners[i].getZ();
+ minX = Math.min(minX, curX);
+ minY = Math.min(minY, curY);
+ minZ = Math.min(minZ, curZ);
+ maxX = Math.max(maxX, curX);
+ maxY = Math.max(maxY, curY);
+ maxZ = Math.max(maxZ, curZ);
+ }
+
+ final double ctrX = (maxX + minX) * 0.5;
+ final double ctrY = (maxY + minY) * 0.5;
+ final double ctrZ = (maxZ + minZ) * 0.5;
+
+ box._center.set(ctrX, ctrY, ctrZ);
+ box._xExtent = maxX - ctrX;
+ box._yExtent = maxY - ctrY;
+ box._zExtent = maxZ - ctrZ;
+
+ for (int i = 0; i < corners.length; i++) {
+ Vector3.releaseTempInstance(corners[i]);
+ }
+
+ return box;
+ }
+
+ public BoundingVolume transformRotational(final ReadOnlyTransform transform, final BoundingVolume store) {
+
+ final ReadOnlyMatrix3 rotate = transform.getMatrix();
+ final ReadOnlyVector3 scale = transform.getScale();
+ final ReadOnlyVector3 translate = transform.getTranslation();
+
+ BoundingBox box;
+ if (store == null || store.getType() != Type.AABB) {
+ box = new BoundingBox();
+ } else {
+ box = (BoundingBox) store;
+ }
+
+ _center.multiply(scale, box._center);
+ rotate.applyPost(box._center, box._center);
+ box._center.addLocal(translate);
+
+ final Matrix3 transMatrix = Matrix3.fetchTempInstance();
+ transMatrix.set(rotate);
+ // Make the rotation matrix all positive to get the maximum x/y/z extent
+ transMatrix.setValue(0, 0, Math.abs(transMatrix.getM00()));
+ transMatrix.setValue(0, 1, Math.abs(transMatrix.getM01()));
+ transMatrix.setValue(0, 2, Math.abs(transMatrix.getM02()));
+ transMatrix.setValue(1, 0, Math.abs(transMatrix.getM10()));
+ transMatrix.setValue(1, 1, Math.abs(transMatrix.getM11()));
+ transMatrix.setValue(1, 2, Math.abs(transMatrix.getM12()));
+ transMatrix.setValue(2, 0, Math.abs(transMatrix.getM20()));
+ transMatrix.setValue(2, 1, Math.abs(transMatrix.getM21()));
+ transMatrix.setValue(2, 2, Math.abs(transMatrix.getM22()));
+
+ _compVect1.set(getXExtent() * scale.getX(), getYExtent() * scale.getY(), getZExtent() * scale.getZ());
+ transMatrix.applyPost(_compVect1, _compVect1);
+ // Assign the biggest rotations after scales.
+ box.setXExtent(Math.abs(_compVect1.getX()));
+ box.setYExtent(Math.abs(_compVect1.getY()));
+ box.setZExtent(Math.abs(_compVect1.getZ()));
+
+ Matrix3.releaseTempInstance(transMatrix);
+
+ return box;
+ }
+
+ /**
+ * <code>computeFromPoints</code> creates a new Bounding Box from a given set of points. It uses the
+ * <code>containAABB</code> method as default.
+ *
+ * @param points
+ * the points to contain.
+ */
+ @Override
+ public void computeFromPoints(final FloatBuffer points) {
+ containAABB(points);
+ }
+
+ @Override
+ public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start,
+ final int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ final Vector3 min = _compVect1
+ .set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ final Vector3 max = _compVect2
+ .set(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+
+ final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount();
+ Vector3[] store = new Vector3[vertsPerPrimitive];
+
+ for (int i = start; i < end; i++) {
+ store = data.getPrimitiveVertices(indices[i], section, store);
+ for (int j = 0; j < store.length; j++) {
+ checkMinMax(min, max, store[j]);
+ }
+ }
+
+ _center.set(min.addLocal(max));
+ _center.multiplyLocal(0.5);
+
+ setXExtent(max.getX() - _center.getX());
+ setYExtent(max.getY() - _center.getY());
+ setZExtent(max.getZ() - _center.getZ());
+ }
+
+ private void checkMinMax(final Vector3 min, final Vector3 max, final ReadOnlyVector3 point) {
+ if (point.getX() < min.getX()) {
+ min.setX(point.getX());
+ }
+ if (point.getX() > max.getX()) {
+ max.setX(point.getX());
+ }
+
+ if (point.getY() < min.getY()) {
+ min.setY(point.getY());
+ }
+ if (point.getY() > max.getY()) {
+ max.setY(point.getY());
+ }
+
+ if (point.getZ() < min.getZ()) {
+ min.setZ(point.getZ());
+ }
+ if (point.getZ() > max.getZ()) {
+ max.setZ(point.getZ());
+ }
+ }
+
+ /**
+ * <code>containAABB</code> creates a minimum-volume axis-aligned bounding box of the points, then selects the
+ * smallest enclosing sphere of the box with the sphere centered at the boxes center.
+ *
+ * @param points
+ * the list of points.
+ */
+ public void containAABB(final FloatBuffer points) {
+ if (points == null) {
+ return;
+ }
+
+ points.rewind();
+ if (points.remaining() <= 2) {
+ return;
+ }
+
+ BufferUtils.populateFromBuffer(_compVect1, points, 0);
+ double minX = _compVect1.getX(), minY = _compVect1.getY(), minZ = _compVect1.getZ();
+ double maxX = _compVect1.getX(), maxY = _compVect1.getY(), maxZ = _compVect1.getZ();
+
+ for (int i = 1, len = points.remaining() / 3; i < len; i++) {
+ BufferUtils.populateFromBuffer(_compVect1, points, i);
+
+ if (_compVect1.getX() < minX) {
+ minX = _compVect1.getX();
+ } else if (_compVect1.getX() > maxX) {
+ maxX = _compVect1.getX();
+ }
+
+ if (_compVect1.getY() < minY) {
+ minY = _compVect1.getY();
+ } else if (_compVect1.getY() > maxY) {
+ maxY = _compVect1.getY();
+ }
+
+ if (_compVect1.getZ() < minZ) {
+ minZ = _compVect1.getZ();
+ } else if (_compVect1.getZ() > maxZ) {
+ maxZ = _compVect1.getZ();
+ }
+ }
+
+ _center.set(minX + maxX, minY + maxY, minZ + maxZ);
+ _center.multiplyLocal(0.5);
+
+ setXExtent(maxX - _center.getX());
+ setYExtent(maxY - _center.getY());
+ setZExtent(maxZ - _center.getZ());
+ }
+
+ /**
+ * <code>whichSide</code> takes a plane (typically provided by a view frustum) to determine which side this bound is
+ * on.
+ *
+ * @param plane
+ * the plane to check against.
+ */
+ @Override
+ public Side whichSide(final ReadOnlyPlane plane) {
+ final ReadOnlyVector3 normal = plane.getNormal();
+ final double radius = Math.abs(getXExtent() * normal.getX()) + Math.abs(getYExtent() * normal.getY())
+ + Math.abs(getZExtent() * normal.getZ());
+
+ final double distance = plane.pseudoDistance(_center);
+
+ if (distance < -radius) {
+ return Plane.Side.Inside;
+ } else if (distance > radius) {
+ return Plane.Side.Outside;
+ } else {
+ return Plane.Side.Neither;
+ }
+ }
+
+ /**
+ * <code>merge</code> combines this sphere with a second bounding sphere. This new sphere contains both bounding
+ * spheres and is returned.
+ *
+ * @param volume
+ * the sphere to combine with this sphere.
+ * @return the new sphere
+ */
+ @Override
+ public BoundingVolume merge(final BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+ case AABB: {
+ final BoundingBox vBox = (BoundingBox) volume;
+ return merge(vBox._center, vBox.getXExtent(), vBox.getYExtent(), vBox.getZExtent(), new BoundingBox(
+ new Vector3(0, 0, 0), 0, 0, 0));
+ }
+
+ case Sphere: {
+ final BoundingSphere vSphere = (BoundingSphere) volume;
+ return merge(vSphere._center, vSphere.getRadius(), vSphere.getRadius(), vSphere.getRadius(),
+ new BoundingBox(new Vector3(0, 0, 0), 0, 0, 0));
+ }
+
+ case OBB: {
+ final OrientedBoundingBox box = (OrientedBoundingBox) volume;
+ final BoundingBox rVal = (BoundingBox) this.clone(null);
+ return rVal.mergeOBB(box);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * <code>mergeLocal</code> combines this sphere with a second bounding sphere locally. Altering this sphere to
+ * contain both the original and the additional sphere volumes;
+ *
+ * @param volume
+ * the sphere to combine with this sphere.
+ * @return this
+ */
+ @Override
+ public BoundingVolume mergeLocal(final BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+ case AABB: {
+ final BoundingBox vBox = (BoundingBox) volume;
+ return merge(vBox._center, vBox.getXExtent(), vBox.getYExtent(), vBox.getZExtent(), this);
+ }
+
+ case Sphere: {
+ final BoundingSphere vSphere = (BoundingSphere) volume;
+ return merge(vSphere._center, vSphere.getRadius(), vSphere.getRadius(), vSphere.getRadius(), this);
+ }
+
+ case OBB: {
+ return mergeOBB((OrientedBoundingBox) volume);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Merges this AABB with the given OBB.
+ *
+ * @param volume
+ * the OBB to merge this AABB with.
+ * @return This AABB extended to fit the given OBB.
+ */
+ private BoundingBox mergeOBB(final OrientedBoundingBox volume) {
+ // check for infinite bounds to prevent NaN values
+ if (Double.isInfinite(getXExtent()) || Double.isInfinite(getYExtent()) || Double.isInfinite(getZExtent())
+ || Vector3.isInfinite(volume.getExtent())) {
+ setCenter(Vector3.ZERO);
+ setXExtent(Double.POSITIVE_INFINITY);
+ setYExtent(Double.POSITIVE_INFINITY);
+ setZExtent(Double.POSITIVE_INFINITY);
+ return this;
+ }
+
+ if (!volume.correctCorners) {
+ volume.computeCorners();
+ }
+
+ double minX, minY, minZ;
+ double maxX, maxY, maxZ;
+
+ minX = _center.getX() - getXExtent();
+ minY = _center.getY() - getYExtent();
+ minZ = _center.getZ() - getZExtent();
+
+ maxX = _center.getX() + getXExtent();
+ maxY = _center.getY() + getYExtent();
+ maxZ = _center.getZ() + getZExtent();
+
+ for (int i = 1; i < volume._vectorStore.length; i++) {
+ final Vector3 temp = volume._vectorStore[i];
+ if (temp.getX() < minX) {
+ minX = temp.getX();
+ } else if (temp.getX() > maxX) {
+ maxX = temp.getX();
+ }
+
+ if (temp.getY() < minY) {
+ minY = temp.getY();
+ } else if (temp.getY() > maxY) {
+ maxY = temp.getY();
+ }
+
+ if (temp.getZ() < minZ) {
+ minZ = temp.getZ();
+ } else if (temp.getZ() > maxZ) {
+ maxZ = temp.getZ();
+ }
+ }
+
+ _center.set(minX + maxX, minY + maxY, minZ + maxZ);
+ _center.multiplyLocal(0.5);
+
+ setXExtent(maxX - _center.getX());
+ setYExtent(maxY - _center.getY());
+ setZExtent(maxZ - _center.getZ());
+ return this;
+ }
+
+ /**
+ * <code>merge</code> combines this bounding box with another box which is defined by the center, x, y, z extents.
+ *
+ * @param boxCenter
+ * the center of the box to merge with
+ * @param boxX
+ * the x extent of the box to merge with.
+ * @param boxY
+ * the y extent of the box to merge with.
+ * @param boxZ
+ * the z extent of the box to merge with.
+ * @param store
+ * the box to store our results in.
+ * @return the resulting merged box.
+ */
+ private BoundingBox merge(final Vector3 boxCenter, final double boxX, final double boxY, final double boxZ,
+ final BoundingBox store) {
+ // check for infinite bounds to prevent NaN values
+ if (Double.isInfinite(getXExtent()) || Double.isInfinite(getYExtent()) || Double.isInfinite(getZExtent())
+ || Double.isInfinite(boxX) || Double.isInfinite(boxY) || Double.isInfinite(boxZ)) {
+ store.setCenter(Vector3.ZERO);
+ store.setXExtent(Double.POSITIVE_INFINITY);
+ store.setYExtent(Double.POSITIVE_INFINITY);
+ store.setZExtent(Double.POSITIVE_INFINITY);
+ return store;
+ }
+
+ _compVect1.setX(_center.getX() - getXExtent());
+ if (_compVect1.getX() > boxCenter.getX() - boxX) {
+ _compVect1.setX(boxCenter.getX() - boxX);
+ }
+ _compVect1.setY(_center.getY() - getYExtent());
+ if (_compVect1.getY() > boxCenter.getY() - boxY) {
+ _compVect1.setY(boxCenter.getY() - boxY);
+ }
+ _compVect1.setZ(_center.getZ() - getZExtent());
+ if (_compVect1.getZ() > boxCenter.getZ() - boxZ) {
+ _compVect1.setZ(boxCenter.getZ() - boxZ);
+ }
+
+ _compVect2.setX(_center.getX() + getXExtent());
+ if (_compVect2.getX() < boxCenter.getX() + boxX) {
+ _compVect2.setX(boxCenter.getX() + boxX);
+ }
+ _compVect2.setY(_center.getY() + getYExtent());
+ if (_compVect2.getY() < boxCenter.getY() + boxY) {
+ _compVect2.setY(boxCenter.getY() + boxY);
+ }
+ _compVect2.setZ(_center.getZ() + getZExtent());
+ if (_compVect2.getZ() < boxCenter.getZ() + boxZ) {
+ _compVect2.setZ(boxCenter.getZ() + boxZ);
+ }
+
+ store._center.set(_compVect2).addLocal(_compVect1).multiplyLocal(0.5);
+
+ store.setXExtent(_compVect2.getX() - store._center.getX());
+ store.setYExtent(_compVect2.getY() - store._center.getY());
+ store.setZExtent(_compVect2.getZ() - store._center.getZ());
+
+ return store;
+ }
+
+ /**
+ * <code>clone</code> creates a new BoundingBox object containing the same data as this one.
+ *
+ * @param store
+ * where to store the cloned information. if null or wrong class, a new store is created.
+ * @return the new BoundingBox
+ */
+ @Override
+ public BoundingVolume clone(final BoundingVolume store) {
+ if (store != null && store.getType() == Type.AABB) {
+ final BoundingBox rVal = (BoundingBox) store;
+ rVal._center.set(_center);
+ rVal.setXExtent(_xExtent);
+ rVal.setYExtent(_yExtent);
+ rVal.setZExtent(_zExtent);
+ rVal._checkPlane = _checkPlane;
+ return rVal;
+ }
+
+ final BoundingBox rVal = new BoundingBox(_center, getXExtent(), getYExtent(), getZExtent());
+ return rVal;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this object. The form is:
+ * "Radius: RRR.SSSS Center: <Vector>".
+ *
+ * @return the string representation of this.
+ */
+ @Override
+ public String toString() {
+ return "com.ardor3d.scene.BoundingBox [Center: " + _center + " xExtent: " + getXExtent() + " yExtent: "
+ + getYExtent() + " zExtent: " + getZExtent() + "]";
+ }
+
+ @Override
+ public boolean intersects(final BoundingVolume bv) {
+ if (bv == null) {
+ return false;
+ }
+
+ return bv.intersectsBoundingBox(this);
+ }
+
+ @Override
+ public boolean intersectsSphere(final BoundingSphere bs) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) {
+ return false;
+ }
+
+ if (Math.abs(_center.getX() - bs.getCenter().getX()) < bs.getRadius() + getXExtent()
+ && Math.abs(_center.getY() - bs.getCenter().getY()) < bs.getRadius() + getYExtent()
+ && Math.abs(_center.getZ() - bs.getCenter().getZ()) < bs.getRadius() + getZExtent()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean intersectsBoundingBox(final BoundingBox bb) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) {
+ return false;
+ }
+
+ if (_center.getX() + getXExtent() < bb._center.getX() - bb.getXExtent()
+ || _center.getX() - getXExtent() > bb._center.getX() + bb.getXExtent()) {
+ return false;
+ } else if (_center.getY() + getYExtent() < bb._center.getY() - bb.getYExtent()
+ || _center.getY() - getYExtent() > bb._center.getY() + bb.getYExtent()) {
+ return false;
+ } else if (_center.getZ() + getZExtent() < bb._center.getZ() - bb.getZExtent()
+ || _center.getZ() - getZExtent() > bb._center.getZ() + bb.getZExtent()) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) {
+ return obb.intersectsBoundingBox(this);
+ }
+
+ @Override
+ public boolean intersects(final ReadOnlyRay3 ray) {
+ if (!Vector3.isValid(_center)) {
+ return false;
+ }
+
+ final Vector3 diff = ray.getOrigin().subtract(_center, _compVect1);
+
+ final ReadOnlyVector3 direction = ray.getDirection();
+
+ final double[] t = { 0.0, Double.POSITIVE_INFINITY };
+
+ // Check for degenerate cases and pad using zero tolerance. Should give close enough result.
+ double x = getXExtent();
+ if (x < MathUtils.ZERO_TOLERANCE && x >= 0) {
+ x = MathUtils.ZERO_TOLERANCE;
+ }
+ double y = getYExtent();
+ if (y < MathUtils.ZERO_TOLERANCE && y >= 0) {
+ y = MathUtils.ZERO_TOLERANCE;
+ }
+ double z = getZExtent();
+ if (z < MathUtils.ZERO_TOLERANCE && z >= 0) {
+ z = MathUtils.ZERO_TOLERANCE;
+ }
+
+ // Special case.
+ if (Double.isInfinite(x) && Double.isInfinite(y) && Double.isInfinite(z)) {
+ return true;
+ }
+
+ final boolean notEntirelyClipped = clip(direction.getX(), -diff.getX() - x, t)
+ && clip(-direction.getX(), diff.getX() - x, t) && clip(direction.getY(), -diff.getY() - y, t)
+ && clip(-direction.getY(), diff.getY() - y, t) && clip(direction.getZ(), -diff.getZ() - z, t)
+ && clip(-direction.getZ(), diff.getZ() - z, t);
+
+ return (notEntirelyClipped && (t[0] != 0.0 || t[1] != Double.POSITIVE_INFINITY));
+ }
+
+ @Override
+ public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) {
+ if (!Vector3.isValid(_center)) {
+ return null;
+ }
+
+ final Vector3 diff = ray.getOrigin().subtract(_center, _compVect1);
+
+ final ReadOnlyVector3 direction = ray.getDirection();
+
+ final double[] t = { 0.0, Double.POSITIVE_INFINITY };
+
+ // Check for degenerate cases and pad using zero tolerance. Should give close enough result.
+ double x = getXExtent();
+ if (x < MathUtils.ZERO_TOLERANCE && x >= 0) {
+ x = MathUtils.ZERO_TOLERANCE;
+ }
+ double y = getYExtent();
+ if (y < MathUtils.ZERO_TOLERANCE && y >= 0) {
+ y = MathUtils.ZERO_TOLERANCE;
+ }
+ double z = getZExtent();
+ if (z < MathUtils.ZERO_TOLERANCE && z >= 0) {
+ z = MathUtils.ZERO_TOLERANCE;
+ }
+
+ final boolean notEntirelyClipped = clip(direction.getX(), -diff.getX() - x, t)
+ && clip(-direction.getX(), diff.getX() - x, t) && clip(direction.getY(), -diff.getY() - y, t)
+ && clip(-direction.getY(), diff.getY() - y, t) && clip(direction.getZ(), -diff.getZ() - z, t)
+ && clip(-direction.getZ(), diff.getZ() - z, t);
+
+ if (notEntirelyClipped && (t[0] != 0.0 || t[1] != Double.POSITIVE_INFINITY)) {
+ if (t[1] > t[0]) {
+ final double[] distances = t;
+ final Vector3[] points = new Vector3[] {
+ new Vector3(ray.getDirection()).multiplyLocal(distances[0]).addLocal(ray.getOrigin()),
+ new Vector3(ray.getDirection()).multiplyLocal(distances[1]).addLocal(ray.getOrigin()) };
+ return new IntersectionRecord(distances, points);
+ }
+
+ final double[] distances = new double[] { t[0] };
+ final Vector3[] points = new Vector3[] { new Vector3(ray.getDirection()).multiplyLocal(distances[0])
+ .addLocal(ray.getOrigin()), };
+ return new IntersectionRecord(distances, points);
+ }
+
+ return null;
+
+ }
+
+ @Override
+ public boolean contains(final ReadOnlyVector3 point) {
+ return Math.abs(_center.getX() - point.getX()) < getXExtent()
+ && Math.abs(_center.getY() - point.getY()) < getYExtent()
+ && Math.abs(_center.getZ() - point.getZ()) < getZExtent();
+ }
+
+ @Override
+ public double distanceToEdge(final ReadOnlyVector3 point) {
+ // compute coordinates of point in box coordinate system
+ final Vector3 closest = point.subtract(_center, _compVect1);
+
+ // project test point onto box
+ double sqrDistance = 0.0;
+ double delta;
+
+ if (closest.getX() < -getXExtent()) {
+ delta = closest.getX() + getXExtent();
+ sqrDistance += delta * delta;
+ closest.setX(-getXExtent());
+ } else if (closest.getX() > getXExtent()) {
+ delta = closest.getX() - getXExtent();
+ sqrDistance += delta * delta;
+ closest.setX(getXExtent());
+ }
+
+ if (closest.getY() < -getYExtent()) {
+ delta = closest.getY() + getYExtent();
+ sqrDistance += delta * delta;
+ closest.setY(-getYExtent());
+ } else if (closest.getY() > getYExtent()) {
+ delta = closest.getY() - getYExtent();
+ sqrDistance += delta * delta;
+ closest.setY(getYExtent());
+ }
+
+ if (closest.getZ() < -getZExtent()) {
+ delta = closest.getZ() + getZExtent();
+ sqrDistance += delta * delta;
+ closest.setZ(-getZExtent());
+ } else if (closest.getZ() > getZExtent()) {
+ delta = closest.getZ() - getZExtent();
+ sqrDistance += delta * delta;
+ closest.setZ(getZExtent());
+ }
+
+ return Math.sqrt(sqrDistance);
+ }
+
+ /**
+ * Get our corners using the bounding center and extents.
+ *
+ * @param store
+ * An optional store. Must be at least length of 8. If null, one will be created for you.
+ * @return array filled with our corners.
+ * @throws ArrayIndexOutOfBoundsException
+ * if our store is length < 8.
+ */
+ public Vector3[] getCorners(Vector3[] store) {
+ if (store == null) {
+ store = new Vector3[8];
+ for (int i = 0; i < store.length; i++) {
+ store[i] = new Vector3();
+ }
+ }
+ store[0].set(_center.getX() + _xExtent, _center.getY() + _yExtent, _center.getZ() + _zExtent);
+ store[1].set(_center.getX() + _xExtent, _center.getY() + _yExtent, _center.getZ() - _zExtent);
+ store[2].set(_center.getX() + _xExtent, _center.getY() - _yExtent, _center.getZ() + _zExtent);
+ store[3].set(_center.getX() + _xExtent, _center.getY() - _yExtent, _center.getZ() - _zExtent);
+ store[4].set(_center.getX() - _xExtent, _center.getY() + _yExtent, _center.getZ() + _zExtent);
+ store[5].set(_center.getX() - _xExtent, _center.getY() + _yExtent, _center.getZ() - _zExtent);
+ store[6].set(_center.getX() - _xExtent, _center.getY() - _yExtent, _center.getZ() + _zExtent);
+ store[7].set(_center.getX() - _xExtent, _center.getY() - _yExtent, _center.getZ() - _zExtent);
+ return store;
+ }
+
+ /**
+ * <code>clip</code> determines if a line segment intersects the current test plane.
+ *
+ * @param denom
+ * the denominator of the line segment.
+ * @param numer
+ * the numerator of the line segment.
+ * @param t
+ * test values of the plane.
+ * @return true if the line segment intersects the plane, false otherwise.
+ */
+ private boolean clip(final double denom, final double numer, final double[] t) {
+ // Return value is 'true' if line segment intersects the current test
+ // plane. Otherwise 'false' is returned in which case the line segment
+ // is entirely clipped.
+ if (denom > 0.0) {
+ if (numer > denom * t[1]) {
+ return false;
+ }
+ if (numer > denom * t[0]) {
+ t[0] = numer / denom;
+ }
+ return true;
+ } else if (denom < 0.0) {
+ if (numer > denom * t[0]) {
+ return false;
+ }
+ if (numer > denom * t[1]) {
+ t[1] = numer / denom;
+ }
+ return true;
+ } else {
+ return numer <= 0.0;
+ }
+ }
+
+ /**
+ * Query extent.
+ *
+ * @param store
+ * where extent gets stored - null to return a new vector
+ * @return store / new vector
+ */
+ public Vector3 getExtent(Vector3 store) {
+ if (store == null) {
+ store = new Vector3();
+ }
+ store.set(getXExtent(), getYExtent(), getZExtent());
+ return store;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(getXExtent(), "xExtent", 0);
+ capsule.write(getYExtent(), "yExtent", 0);
+ capsule.write(getZExtent(), "zExtent", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ setXExtent(capsule.readDouble("xExtent", 0));
+ setYExtent(capsule.readDouble("yExtent", 0));
+ setZExtent(capsule.readDouble("zExtent", 0));
+ }
+
+ @Override
+ public double getVolume() {
+ return (8 * getXExtent() * getYExtent() * getZExtent());
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java
new file mode 100644
index 0000000..9bf81b8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingSphere.java
@@ -0,0 +1,729 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyPlane;
+import com.ardor3d.math.type.ReadOnlyRay3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.math.type.ReadOnlyPlane.Side;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>BoundingSphere</code> defines a sphere that defines a container for a group of vertices of a particular piece
+ * of geometry. This sphere defines a radius and a center. <br>
+ * <br>
+ * A typical usage is to allow the class define the center and radius by calling either <code>containAABB</code> or
+ * <code>averagePoints</code>. A call to <code>computeFramePoint</code> in turn calls <code>containAABB</code>.
+ */
+public class BoundingSphere extends BoundingVolume {
+ private static final Logger logger = Logger.getLogger(BoundingSphere.class.getName());
+
+ private static final long serialVersionUID = 1L;
+
+ private double _radius;
+
+ static final private double radiusEpsilon = 1 + 0.00001;
+
+ protected final Vector3 _compVect3 = new Vector3();
+ protected final Vector3 _compVect4 = new Vector3();
+
+ /**
+ * Default constructor instantiates a new <code>BoundingSphere</code> object.
+ */
+ public BoundingSphere() {}
+
+ /**
+ * Constructor instantiates a new <code>BoundingSphere</code> object.
+ *
+ * @param r
+ * the radius of the sphere.
+ * @param c
+ * the center of the sphere.
+ */
+ public BoundingSphere(final double r, final ReadOnlyVector3 c) {
+ _center.set(c);
+ setRadius(r);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.Sphere;
+ }
+
+ @Override
+ public BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store) {
+ BoundingSphere sphere;
+ if (store == null || store.getType() != BoundingVolume.Type.Sphere) {
+ sphere = new BoundingSphere(1, new Vector3(0, 0, 0));
+ } else {
+ sphere = (BoundingSphere) store;
+ }
+
+ transform.applyForward(_center, sphere._center);
+
+ if (!transform.isRotationMatrix()) {
+ final Vector3 scale = new Vector3(1, 1, 1);
+ transform.applyForwardVector(scale);
+ sphere.setRadius(Math.abs(maxAxis(scale) * getRadius()) + radiusEpsilon - 1);
+ } else {
+ final ReadOnlyVector3 scale = transform.getScale();
+ sphere.setRadius(Math.abs(maxAxis(scale) * getRadius()) + radiusEpsilon - 1);
+ }
+
+ return sphere;
+ }
+
+ private double maxAxis(final ReadOnlyVector3 scale) {
+ return Math.max(Math.abs(scale.getX()), Math.max(Math.abs(scale.getY()), Math.abs(scale.getZ())));
+ }
+
+ /**
+ * <code>getRadius</code> returns the radius of the bounding sphere.
+ *
+ * @return the radius of the bounding sphere.
+ */
+ public double getRadius() {
+ return _radius;
+ }
+
+ /**
+ * <code>setRadius</code> sets the radius of this bounding sphere.
+ *
+ * @param radius
+ * the new radius of the bounding sphere.
+ */
+ public void setRadius(final double radius) {
+ _radius = radius;
+ }
+
+ /**
+ * <code>computeFromPoints</code> creates a new Bounding Sphere from a given set of points. It uses the
+ * <code>calcWelzl</code> method as default.
+ *
+ * @param points
+ * the points to contain.
+ */
+ @Override
+ public void computeFromPoints(final FloatBuffer points) {
+ calcWelzl(points);
+ }
+
+ @Override
+ public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start,
+ final int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount();
+ final Vector3[] vertList = new Vector3[(end - start) * vertsPerPrimitive];
+ Vector3[] store = new Vector3[vertsPerPrimitive];
+
+ int count = 0;
+ for (int i = start; i < end; i++) {
+ store = data.getPrimitiveVertices(indices[i], section, store);
+ for (int j = 0; j < vertsPerPrimitive; j++) {
+ vertList[count++] = Vector3.fetchTempInstance().set(store[0]);
+ }
+ }
+
+ averagePoints(vertList);
+ for (int i = 0; i < vertList.length; i++) {
+ Vector3.releaseTempInstance(vertList[i]);
+ }
+ }
+
+ /**
+ * Calculates a minimum bounding sphere for the set of points. The algorithm was originally found at
+ * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1 in C++ and
+ * translated to java by Cep21
+ *
+ * @param points
+ * The points to calculate the minimum bounds from.
+ */
+ public void calcWelzl(final FloatBuffer points) {
+ final float[] buf = new float[points.limit()];
+ points.rewind();
+ points.get(buf);
+ recurseMini(buf, buf.length / 3, 0, 0);
+ }
+
+ /**
+ * Used from calcWelzl. This function recurses to calculate a minimum bounding sphere a few points at a time.
+ *
+ * @param points
+ * The array of points to look through.
+ * @param p
+ * The size of the list to be used.
+ * @param pnts
+ * The number of points currently considering to include with the sphere.
+ * @param ap
+ * A variable simulating pointer arithmetic from C++, and offset in <code>points</code>.
+ */
+ private void recurseMini(final float[] points, final int p, final int pnts, final int ap) {
+ switch (pnts) {
+ case 0:
+ setRadius(0);
+ _center.set(0, 0, 0);
+ break;
+ case 1:
+ setRadius(1f - radiusEpsilon);
+ populateFromBuffer(_center, points, ap - 1);
+ break;
+ case 2:
+ populateFromBuffer(_compVect1, points, ap - 1);
+ populateFromBuffer(_compVect2, points, ap - 2);
+ setSphere(_compVect1, _compVect2);
+ break;
+ case 3:
+ populateFromBuffer(_compVect1, points, ap - 1);
+ populateFromBuffer(_compVect2, points, ap - 2);
+ populateFromBuffer(_compVect3, points, ap - 3);
+ setSphere(_compVect1, _compVect2, _compVect3);
+ break;
+ case 4:
+ populateFromBuffer(_compVect1, points, ap - 1);
+ populateFromBuffer(_compVect2, points, ap - 2);
+ populateFromBuffer(_compVect3, points, ap - 3);
+ populateFromBuffer(_compVect4, points, ap - 4);
+ setSphere(_compVect1, _compVect2, _compVect3, _compVect4);
+ return;
+ }
+ for (int i = 0; i < p; i++) {
+ populateFromBuffer(_compVect1, points, i + ap);
+ if (_compVect1.distanceSquared(_center) - (getRadius() * getRadius()) > radiusEpsilon - 1f) {
+ for (int j = i; j > 0; j--) {
+ populateFromBuffer(_compVect2, points, j + ap);
+ populateFromBuffer(_compVect3, points, j - 1 + ap);
+ setInBuffer(_compVect3, points, j + ap);
+ setInBuffer(_compVect2, points, j - 1 + ap);
+ }
+ recurseMini(points, i, pnts + 1, ap + 1);
+ }
+ }
+ }
+
+ public static void populateFromBuffer(final Vector3 vector, final float[] buf, final int index) {
+ vector.setX(buf[index * 3]);
+ vector.setY(buf[index * 3 + 1]);
+ vector.setZ(buf[index * 3 + 2]);
+ }
+
+ public static void setInBuffer(final ReadOnlyVector3 vector, final float[] buf, final int index) {
+ if (buf == null) {
+ return;
+ }
+ if (vector == null) {
+ buf[index * 3] = 0;
+ buf[(index * 3) + 1] = 0;
+ buf[(index * 3) + 2] = 0;
+ } else {
+ buf[index * 3] = vector.getXf();
+ buf[(index * 3) + 1] = vector.getYf();
+ buf[(index * 3) + 2] = vector.getZf();
+ }
+ }
+
+ /**
+ * Calculates the minimum bounding sphere of 4 points. Used in welzl's algorithm.
+ *
+ * @param O
+ * The 1st point inside the sphere.
+ * @param A
+ * The 2nd point inside the sphere.
+ * @param B
+ * The 3rd point inside the sphere.
+ * @param C
+ * The 4th point inside the sphere.
+ * @see #calcWelzl(java.nio.FloatBuffer)
+ */
+ private void setSphere(final Vector3 O, final Vector3 A, final Vector3 B, final Vector3 C) {
+ final Vector3 a = A.subtract(O, null);
+ final Vector3 b = B.subtract(O, null);
+ final Vector3 c = C.subtract(O, null);
+
+ final double Denominator = 2.0 * (a.getX() * (b.getY() * c.getZ() - c.getY() * b.getZ()) - b.getX()
+ * (a.getY() * c.getZ() - c.getY() * a.getZ()) + c.getX() * (a.getY() * b.getZ() - b.getY() * a.getZ()));
+ if (Denominator == 0) {
+ _center.set(0, 0, 0);
+ setRadius(0);
+ } else {
+ final Vector3 o = a.cross(b, null).multiplyLocal(c.lengthSquared()).addLocal(
+ c.cross(a, null).multiplyLocal(b.lengthSquared())).addLocal(
+ b.cross(c, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator);
+
+ setRadius(o.length() * radiusEpsilon);
+ O.add(o, _center);
+ }
+ }
+
+ /**
+ * Calculates the minimum bounding sphere of 3 points. Used in welzl's algorithm.
+ *
+ * @param O
+ * The 1st point inside the sphere.
+ * @param A
+ * The 2nd point inside the sphere.
+ * @param B
+ * The 3rd point inside the sphere.
+ * @see #calcWelzl(java.nio.FloatBuffer)
+ */
+ private void setSphere(final Vector3 O, final Vector3 A, final Vector3 B) {
+ final Vector3 a = A.subtract(O, null);
+ final Vector3 b = B.subtract(O, null);
+ final Vector3 acrossB = a.cross(b, null);
+
+ final double Denominator = 2.0 * acrossB.dot(acrossB);
+
+ if (Denominator == 0) {
+ _center.set(0, 0, 0);
+ setRadius(0);
+ } else {
+
+ final Vector3 o = acrossB.cross(a, null).multiplyLocal(b.lengthSquared()).addLocal(
+ b.cross(acrossB, null).multiplyLocal(a.lengthSquared())).divideLocal(Denominator);
+ setRadius(o.length() * radiusEpsilon);
+ O.add(o, _center);
+ }
+ }
+
+ /**
+ * Calculates the minimum bounding sphere of 2 points. Used in welzl's algorithm.
+ *
+ * @param O
+ * The 1st point inside the sphere.
+ * @param A
+ * The 2nd point inside the sphere.
+ * @see #calcWelzl(java.nio.FloatBuffer)
+ */
+ private void setSphere(final Vector3 O, final Vector3 A) {
+ setRadius(Math.sqrt(((A.getX() - O.getX()) * (A.getX() - O.getX()) + (A.getY() - O.getY())
+ * (A.getY() - O.getY()) + (A.getZ() - O.getZ()) * (A.getZ() - O.getZ())) / 4f)
+ + radiusEpsilon - 1);
+ Vector3.lerp(O, A, .5, _center);
+ }
+
+ /**
+ * <code>averagePoints</code> selects the sphere center to be the average of the points and the sphere radius to be
+ * the smallest value to enclose all points.
+ *
+ * @param points
+ * the list of points to contain.
+ */
+ public void averagePoints(final Vector3[] points) {
+ _center.set(points[0]);
+
+ for (int i = 1; i < points.length; i++) {
+ _center.addLocal(points[i]);
+ }
+
+ final double quantity = 1.0 / points.length;
+ _center.multiplyLocal(quantity);
+
+ double maxRadiusSqr = 0;
+ for (int i = 0; i < points.length; i++) {
+ final Vector3 diff = points[i].subtract(_center, _compVect1);
+ final double radiusSqr = diff.lengthSquared();
+ if (radiusSqr > maxRadiusSqr) {
+ maxRadiusSqr = radiusSqr;
+ }
+ }
+
+ setRadius(Math.sqrt(maxRadiusSqr) + radiusEpsilon - 1f);
+
+ }
+
+ /**
+ * <code>whichSide</code> takes a plane (typically provided by a view frustum) to determine which side this bound is
+ * on.
+ *
+ * @param plane
+ * the plane to check against.
+ * @return side
+ */
+ @Override
+ public Side whichSide(final ReadOnlyPlane plane) {
+ final double distance = plane.pseudoDistance(_center);
+
+ if (distance <= -getRadius()) {
+ return Plane.Side.Inside;
+ } else if (distance >= getRadius()) {
+ return Plane.Side.Outside;
+ } else {
+ return Plane.Side.Neither;
+ }
+ }
+
+ /**
+ * <code>merge</code> combines this sphere with a second bounding sphere. This new sphere contains both bounding
+ * spheres and is returned.
+ *
+ * @param volume
+ * the sphere to combine with this sphere.
+ * @return a new sphere
+ */
+ @Override
+ public BoundingVolume merge(final BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+
+ case Sphere: {
+ final BoundingSphere sphere = (BoundingSphere) volume;
+ final double temp_radius = sphere.getRadius();
+ final ReadOnlyVector3 tempCenter = sphere.getCenter();
+ final BoundingSphere rVal = new BoundingSphere();
+ return merge(temp_radius, tempCenter, rVal);
+ }
+
+ case AABB: {
+ final BoundingBox box = (BoundingBox) volume;
+ final Vector3 radVect = new Vector3(box.getXExtent(), box.getYExtent(), box.getZExtent());
+ final Vector3 tempCenter = box._center;
+ final BoundingSphere rVal = new BoundingSphere();
+ return merge(radVect.length(), tempCenter, rVal);
+ }
+
+ case OBB: {
+ final OrientedBoundingBox box = (OrientedBoundingBox) volume;
+ final BoundingSphere rVal = (BoundingSphere) this.clone(null);
+ return rVal.mergeLocalOBB(box);
+ }
+
+ default:
+ return null;
+
+ }
+ }
+
+ /**
+ * <code>mergeLocal</code> combines this sphere with a second bounding sphere locally. Altering this sphere to
+ * contain both the original and the additional sphere volumes;
+ *
+ * @param volume
+ * the sphere to combine with this sphere.
+ * @return this
+ */
+ @Override
+ public BoundingVolume mergeLocal(final BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+
+ case Sphere: {
+ final BoundingSphere sphere = (BoundingSphere) volume;
+ final double temp_radius = sphere.getRadius();
+ final ReadOnlyVector3 temp_center = sphere.getCenter();
+ return merge(temp_radius, temp_center, this);
+ }
+
+ case AABB: {
+ final BoundingBox box = (BoundingBox) volume;
+ final Vector3 temp_center = box._center;
+ _compVect1.set(box.getXExtent(), box.getYExtent(), box.getZExtent());
+ final double radius = _compVect1.length();
+ return merge(radius, temp_center, this);
+ }
+
+ case OBB: {
+ return mergeLocalOBB((OrientedBoundingBox) volume);
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Merges this sphere with the given OBB.
+ *
+ * @param volume
+ * The OBB to merge.
+ * @return This sphere, after merging.
+ */
+ private BoundingSphere mergeLocalOBB(final OrientedBoundingBox volume) {
+ // check for infinite bounds to prevent NaN values... is so, return infinite bounds with center at origin
+ if (Double.isInfinite(getRadius()) || Vector3.isInfinite(volume.getExtent())) {
+ setCenter(Vector3.ZERO);
+ setRadius(Double.POSITIVE_INFINITY);
+ return this;
+ }
+
+ // compute edge points from the obb
+ if (!volume.correctCorners) {
+ volume.computeCorners();
+ }
+
+ final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(8 * 3);
+
+ for (int i = 0; i < 8; i++) {
+ mergeBuf.put((float) volume._vectorStore[i].getX());
+ mergeBuf.put((float) volume._vectorStore[i].getY());
+ mergeBuf.put((float) volume._vectorStore[i].getZ());
+ }
+
+ // remember old radius and center
+ final double oldRadius = getRadius();
+ final double oldCenterX = _center.getX();
+ final double oldCenterY = _center.getY();
+ final double oldCenterZ = _center.getZ();
+
+ // compute new radius and center from obb points
+ computeFromPoints(mergeBuf);
+
+ final double newCenterX = _center.getX();
+ final double newCenterY = _center.getY();
+ final double newCenterZ = _center.getZ();
+ final double newRadius = getRadius();
+
+ // restore old center and radius
+ _center.set(oldCenterX, oldCenterY, oldCenterZ);
+ setRadius(oldRadius);
+
+ // merge obb points result
+ merge(newRadius, _compVect4.set(newCenterX, newCenterY, newCenterZ), this);
+
+ return this;
+ }
+
+ private BoundingVolume merge(final double otherRadius, final ReadOnlyVector3 otherCenter, final BoundingSphere store) {
+ // check for infinite bounds... is so, return infinite bounds with center at origin
+ if (Double.isInfinite(otherRadius) || Double.isInfinite(getRadius())) {
+ store.setCenter(Vector3.ZERO);
+ store.setRadius(Double.POSITIVE_INFINITY);
+ return store;
+ }
+
+ final Vector3 diff = otherCenter.subtract(_center, _compVect1);
+ final double lengthSquared = diff.lengthSquared();
+ final double radiusDiff = otherRadius - getRadius();
+ final double radiusDiffSqr = radiusDiff * radiusDiff;
+
+ // if one sphere wholly contains the other
+ if (radiusDiffSqr >= lengthSquared) {
+ // if we contain the other
+ if (radiusDiff <= 0.0) {
+ store.setCenter(_center);
+ store.setRadius(_radius);
+ return store;
+ }
+ // else the other contains us
+ else {
+ store.setCenter(otherCenter);
+ store.setRadius(otherRadius);
+ return store;
+ }
+ }
+
+ // distance between sphere centers
+ final double length = Math.sqrt(lengthSquared);
+
+ // init a center var using our center
+ final Vector3 rCenter = _compVect2;
+ rCenter.set(_center);
+
+ // if our centers are at least a tiny amount apart from each other...
+ if (length > MathUtils.EPSILON) {
+ // place us between the two centers, weighted by radii
+ final double coeff = (length + radiusDiff) / (2.0 * length);
+ rCenter.addLocal(diff.multiplyLocal(coeff));
+ }
+
+ // set center on our resulting bounds
+ store.setCenter(rCenter);
+
+ // Set radius
+ store.setRadius(0.5 * (length + getRadius() + otherRadius));
+ return store;
+ }
+
+ /**
+ * <code>clone</code> creates a new BoundingSphere object containing the same data as this one.
+ *
+ * @param store
+ * where to store the cloned information. if null or wrong class, a new store is created.
+ * @return the new BoundingSphere
+ */
+ @Override
+ public BoundingVolume clone(final BoundingVolume store) {
+ if (store != null && store.getType() == Type.Sphere) {
+ final BoundingSphere rVal = (BoundingSphere) store;
+ rVal._center.set(_center);
+ rVal.setRadius(_radius);
+ rVal._checkPlane = _checkPlane;
+ return rVal;
+ }
+
+ return new BoundingSphere(getRadius(), _center);
+ }
+
+ @Override
+ public String toString() {
+ return "com.ardor3d.scene.BoundingSphere [Radius: " + getRadius() + " Center: " + _center + "]";
+ }
+
+ @Override
+ public boolean intersects(final BoundingVolume bv) {
+ if (bv == null) {
+ return false;
+ }
+
+ return bv.intersectsSphere(this);
+ }
+
+ @Override
+ public boolean intersectsSphere(final BoundingSphere bs) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) {
+ return false;
+ }
+
+ final Vector3 diff = _compVect1.set(getCenter()).subtractLocal(bs.getCenter());
+ final double rsum = getRadius() + bs.getRadius();
+ return (diff.dot(diff) <= rsum * rsum);
+ }
+
+ @Override
+ public boolean intersectsBoundingBox(final BoundingBox bb) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) {
+ return false;
+ }
+
+ if (Math.abs(bb._center.getX() - getCenter().getX()) < getRadius() + bb.getXExtent()
+ && Math.abs(bb._center.getY() - getCenter().getY()) < getRadius() + bb.getYExtent()
+ && Math.abs(bb._center.getZ() - getCenter().getZ()) < getRadius() + bb.getZExtent()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) {
+ return obb.intersectsSphere(this);
+ }
+
+ @Override
+ public boolean intersects(final ReadOnlyRay3 ray) {
+ if (!Vector3.isValid(_center)) {
+ return false;
+ }
+
+ final Vector3 diff = ray.getOrigin().subtract(getCenter(), _compVect1);
+ final double radiusSquared = getRadius() * getRadius();
+ final double a = diff.dot(diff) - radiusSquared;
+ if (a <= 0.0) {
+ // in sphere
+ return true;
+ }
+
+ // outside sphere
+ final Vector3 dir = _compVect2.set(ray.getDirection());
+ final double b = dir.dot(diff);
+ if (b >= 0.0) {
+ return false;
+ }
+ return b * b >= a;
+ }
+
+ @Override
+ public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) {
+
+ final Vector3 diff = ray.getOrigin().subtract(getCenter(), _compVect1);
+ final double a = diff.dot(diff) - (getRadius() * getRadius());
+ double a1, discr, root;
+ if (a <= 0.0) {
+ // inside sphere
+ a1 = ray.getDirection().dot(diff);
+ discr = (a1 * a1) - a;
+ root = Math.sqrt(discr);
+ final double[] distances = new double[] { root - a1 };
+ final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal(
+ ray.getOrigin()) };
+ return new IntersectionRecord(distances, points);
+ }
+
+ a1 = ray.getDirection().dot(diff);
+ if (a1 >= 0.0) {
+ // No intersection
+ return null;
+ }
+
+ discr = a1 * a1 - a;
+ if (discr < 0.0) {
+ return null;
+ } else if (discr >= MathUtils.ZERO_TOLERANCE) {
+ root = Math.sqrt(discr);
+ final double[] distances = new double[] { -a1 - root, -a1 + root };
+ final Vector3[] points = new Vector3[] {
+ ray.getDirection().multiply(distances[0], new Vector3()).addLocal(ray.getOrigin()),
+ ray.getDirection().multiply(distances[1], new Vector3()).addLocal(ray.getOrigin()) };
+ final IntersectionRecord record = new IntersectionRecord(distances, points);
+ return record;
+ }
+
+ final double[] distances = new double[] { -a1 };
+ final Vector3[] points = new Vector3[] { ray.getDirection().multiply(distances[0], new Vector3()).addLocal(
+ ray.getOrigin()) };
+ return new IntersectionRecord(distances, points);
+ }
+
+ @Override
+ public boolean contains(final ReadOnlyVector3 point) {
+ return getCenter().distanceSquared(point) < (getRadius() * getRadius());
+ }
+
+ @Override
+ public double distanceToEdge(final ReadOnlyVector3 point) {
+ return _center.distance(point) - getRadius();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ try {
+ capsule.write(getRadius(), "radius", 0);
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "write(Ardor3DExporter)", "Exception", ex);
+ }
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ try {
+ setRadius(capsule.readDouble("radius", 0));
+ } catch (final IOException ex) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "read(Ardor3DImporter)", "Exception", ex);
+ }
+ }
+
+ @Override
+ public double getVolume() {
+ return 4 * MathUtils.ONE_THIRD * MathUtils.PI * getRadius() * getRadius() * getRadius();
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java
new file mode 100644
index 0000000..5c0ba3d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/BoundingVolume.java
@@ -0,0 +1,259 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyPlane;
+import com.ardor3d.math.type.ReadOnlyRay3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public abstract class BoundingVolume implements Serializable, Savable {
+ private static final long serialVersionUID = 1L;
+
+ public enum Type {
+ Sphere, AABB, OBB;
+ }
+
+ protected int _checkPlane = 0;
+
+ protected final Vector3 _center = new Vector3();
+
+ protected final Vector3 _compVect1 = new Vector3();
+ protected final Vector3 _compVect2 = new Vector3();
+
+ public BoundingVolume() {}
+
+ public BoundingVolume(final Vector3 center) {
+ _center.set(center);
+ }
+
+ /**
+ * Grabs the checkplane we should check first.
+ *
+ */
+ public int getCheckPlane() {
+ return _checkPlane;
+ }
+
+ /**
+ * Sets the index of the plane that should be first checked during rendering.
+ *
+ * @param value
+ */
+ public final void setCheckPlane(final int value) {
+ _checkPlane = value;
+ }
+
+ /**
+ * getType returns the type of bounding volume this is.
+ */
+ public abstract Type getType();
+
+ /**
+ *
+ * <code>transform</code> alters the location of the bounding volume by a transform.
+ *
+ * @param transform
+ * @param store
+ * @return
+ */
+ public abstract BoundingVolume transform(final ReadOnlyTransform transform, final BoundingVolume store);
+
+ /**
+ *
+ * <code>whichSide</code> returns the side on which the bounding volume lies on a plane. Possible values are
+ * POSITIVE_SIDE, NEGATIVE_SIDE, and NO_SIDE.
+ *
+ * @param plane
+ * the plane to check against this bounding volume.
+ * @return the side on which this bounding volume lies.
+ */
+ public abstract ReadOnlyPlane.Side whichSide(ReadOnlyPlane plane);
+
+ /**
+ *
+ * <code>computeFromPoints</code> generates a bounding volume that encompasses a collection of points.
+ *
+ * @param points
+ * the points to contain.
+ */
+ public abstract void computeFromPoints(FloatBuffer points);
+
+ /**
+ * <code>merge</code> combines two bounding volumes into a single bounding volume that contains both this bounding
+ * volume and the parameter volume.
+ *
+ * @param volume
+ * the volume to combine.
+ * @return the new merged bounding volume.
+ */
+ public abstract BoundingVolume merge(BoundingVolume volume);
+
+ /**
+ * <code>mergeLocal</code> combines two bounding volumes into a single bounding volume that contains both this
+ * bounding volume and the parameter volume. The result is stored locally.
+ *
+ * @param volume
+ * the volume to combine.
+ * @return this
+ */
+ public abstract BoundingVolume mergeLocal(BoundingVolume volume);
+
+ /**
+ * <code>clone</code> creates a new BoundingVolume object containing the same data as this one.
+ *
+ * @param store
+ * where to store the cloned information. if null or wrong class, a new store is created.
+ * @return the new BoundingVolume
+ */
+ public abstract BoundingVolume clone(BoundingVolume store);
+
+ /**
+ * @return the distance from the center of this bounding volume to its further edge/corner. Similar to converting
+ * this BoundingVolume to a sphere and asking for radius.
+ */
+ public abstract double getRadius();
+
+ public final ReadOnlyVector3 getCenter() {
+ return _center;
+ }
+
+ public final void setCenter(final ReadOnlyVector3 newCenter) {
+ _center.set(newCenter);
+ }
+
+ public void setCenter(final double x, final double y, final double z) {
+ _center.set(x, y, z);
+ }
+
+ /**
+ * Find the distance from the center of this Bounding Volume to the given point.
+ *
+ * @param point
+ * The point to get the distance to
+ * @return distance
+ */
+ public final double distanceTo(final ReadOnlyVector3 point) {
+ return _center.distance(point);
+ }
+
+ /**
+ * Find the squared distance from the center of this Bounding Volume to the given point.
+ *
+ * @param point
+ * The point to get the distance to
+ * @return distance
+ */
+ public final double distanceSquaredTo(final ReadOnlyVector3 point) {
+ return _center.distanceSquared(point);
+ }
+
+ /**
+ * Find the distance from the nearest edge of this Bounding Volume to the given point.
+ *
+ * @param point
+ * The point to get the distance to
+ * @return distance
+ */
+ public abstract double distanceToEdge(ReadOnlyVector3 point);
+
+ /**
+ * determines if this bounding volume and a second given volume are intersecting. Intersecting being: one volume
+ * contains another, one volume overlaps another or one volume touches another.
+ *
+ * @param bv
+ * the second volume to test against.
+ * @return true if this volume intersects the given volume.
+ */
+ public abstract boolean intersects(BoundingVolume bv);
+
+ /**
+ * determines if a ray intersects this bounding volume.
+ *
+ * @param ray
+ * the ray to test.
+ * @return true if this volume is intersected by a given ray.
+ */
+ public abstract boolean intersects(ReadOnlyRay3 ray);
+
+ /**
+ * determines if a ray intersects this bounding volume and if so, where.
+ *
+ * @param ray
+ * the ray to test.
+ * @return an IntersectionRecord containing information about any intersections made by the given Ray with this
+ * bounding
+ */
+ public abstract IntersectionRecord intersectsWhere(ReadOnlyRay3 ray);
+
+ /**
+ * determines if this bounding volume and a given bounding sphere are intersecting.
+ *
+ * @param bs
+ * the bounding sphere to test against.
+ * @return true if this volume intersects the given bounding sphere.
+ */
+ public abstract boolean intersectsSphere(BoundingSphere bs);
+
+ /**
+ * determines if this bounding volume and a given bounding box are intersecting.
+ *
+ * @param bb
+ * the bounding box to test against.
+ * @return true if this volume intersects the given bounding box.
+ */
+ public abstract boolean intersectsBoundingBox(BoundingBox bb);
+
+ /**
+ * determines if this bounding volume and a given bounding box are intersecting.
+ *
+ * @param bb
+ * the bounding box to test against.
+ * @return true if this volume intersects the given bounding box.
+ */
+ public abstract boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb);
+
+ /**
+ *
+ * determines if a given point is contained within this bounding volume.
+ *
+ * @param point
+ * the point to check
+ * @return true if the point lies within this bounding volume.
+ */
+ public abstract boolean contains(ReadOnlyVector3 point);
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)));
+ }
+
+ public Class<? extends BoundingVolume> getClassTag() {
+ return this.getClass();
+ }
+
+ public abstract void computeFromPrimitives(MeshData data, int section, final int[] indices, int start, int end);
+
+ public abstract double getVolume();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java
new file mode 100644
index 0000000..b623327
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTree.java
@@ -0,0 +1,595 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.intersection.Intersection;
+import com.ardor3d.intersection.PrimitiveKey;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * CollisionTree defines a well balanced red black tree used for triangle accurate collision detection. The
+ * CollisionTree supports three types: Oriented Bounding Box, Axis-Aligned Bounding Box and Sphere. The tree is composed
+ * of a hierarchy of nodes, all but leaf nodes have two children, a left and a right, where the children contain half of
+ * the triangles of the parent. This "half split" is executed down the tree until the node is maintaining a set maximum
+ * of triangles. This node is called the leaf node. Intersection checks are handled as follows:<br>
+ * 1. The bounds of the node is checked for intersection. If no intersection occurs here, no further processing is
+ * needed, the children (nodes or triangles) do not intersect.<br>
+ * 2a. If an intersection occurs and we have children left/right nodes, pass the intersection information to the
+ * children.<br>
+ * 2b. If an intersection occurs and we are a leaf node, pass each triangle individually for intersection checking.<br>
+ * <br>
+ * Optionally, during creation of the collision tree, sorting can be applied. Sorting will attempt to optimize the order
+ * of the triangles in such a way as to best split for left and right sub-trees. This function can lead to faster
+ * intersection tests, but increases the creation time for the tree. The number of triangles a leaf node is responsible
+ * for is defined in CollisionTreeManager. It is actually recommended to allow CollisionTreeManager to maintain the
+ * collision trees for a scene.
+ *
+ * @see com.ardor3d.bounding.CollisionTreeManager
+ */
+public class CollisionTree implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public enum Type {
+ /** CollisionTree using Oriented Bounding Boxes. */
+ OBB,
+ /** CollisionTree using Axis-Aligned Bounding Boxes. */
+ AABB,
+ /** CollisionTree using Bounding Spheres. */
+ Sphere;
+ }
+
+ // Default tree is axis-aligned
+ protected Type _type = Type.AABB;
+
+ // children trees
+ protected CollisionTree _left;
+ protected CollisionTree _right;
+
+ // bounding volumes that contain the triangles that the node is handling
+ protected BoundingVolume _bounds;
+ protected BoundingVolume _worldBounds;
+
+ /**
+ * the list of primitives indices that compose the tree. This list contains all the triangles of the mesh and is
+ * shared between all nodes of this tree. Stored here to allow for sorting.
+ */
+ protected int[] _primitiveIndices;
+
+ // Defines the pointers into the triIndex array that this node is directly responsible for.
+ protected int _start, _end;
+
+ // Required Spatial information
+ protected transient WeakReference<Mesh> _mesh;
+ protected int _section;
+
+ // Comparator used to sort triangle indices
+ protected transient final TreeComparator _comparator = new TreeComparator();
+
+ /**
+ * Constructor creates a new instance of CollisionTree.
+ *
+ * @param type
+ * the type of collision tree to make
+ * @see Type
+ */
+ public CollisionTree(final Type type) {
+ _type = type;
+ }
+
+ /**
+ * Recreate this Collision Tree for the given Node and child index.
+ *
+ * @param childIndex
+ * the index of the child to generate the tree for.
+ * @param parent
+ * The Node that this tree should represent.
+ * @param doSort
+ * true to sort primitives during creation, false otherwise
+ */
+ public void construct(final int childIndex, final int section, final Node parent, final boolean doSort) {
+ final Spatial spat = parent.getChild(childIndex);
+ if (spat instanceof Mesh) {
+ _mesh = makeRef((Mesh) spat);
+ _primitiveIndices = new int[((Mesh) spat).getMeshData().getPrimitiveCount(section)];
+ for (int i = 0; i < _primitiveIndices.length; i++) {
+ _primitiveIndices[i] = i;
+ }
+ createTree(section, 0, _primitiveIndices.length, doSort);
+ }
+ }
+
+ /**
+ * Recreate this Collision Tree for the given mesh.
+ *
+ * @param mesh
+ * The mesh that this tree should represent.
+ * @param doSort
+ * true to sort primitives during creation, false otherwise
+ */
+ public void construct(final Mesh mesh, final boolean doSort) {
+ _mesh = makeRef(mesh);
+ if (mesh.getMeshData().getSectionCount() == 1) {
+ _primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(0)];
+ for (int i = 0; i < _primitiveIndices.length; i++) {
+ _primitiveIndices[i] = i;
+ }
+ createTree(0, 0, _primitiveIndices.length, doSort);
+ } else {
+ // divide up the sections into the tree by adding intermediate nodes as needed.
+ splitMesh(mesh, 0, mesh.getMeshData().getSectionCount(), doSort);
+ }
+ }
+
+ protected void splitMesh(final Mesh mesh, final int sectionStart, final int sectionEnd, final boolean doSort) {
+ _mesh = makeRef(mesh);
+
+ // Split range in half
+ final int rangeSize = sectionEnd - sectionStart;
+ final int halfRange = rangeSize / 2; // odd number will give +1 to right.
+
+ // left half:
+ // if half size == 1, create as regular CollisionTree
+ if (halfRange == 1) {
+ // compute section
+ final int section = sectionStart;
+
+ // create the left child
+ _left = new CollisionTree(_type);
+
+ _left._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)];
+ for (int i = 0; i < _left._primitiveIndices.length; i++) {
+ _left._primitiveIndices[i] = i;
+ }
+ _left._mesh = _mesh;
+ _left.createTree(section, 0, _left._primitiveIndices.length, doSort);
+ } else {
+ // otherwise, make an empty collision tree and call split with new range
+ _left = new CollisionTree(_type);
+ _left.splitMesh(mesh, sectionStart, sectionStart + halfRange, doSort);
+ }
+
+ // right half:
+ // if rangeSize - half size == 1, create as regular CollisionTree
+ if (rangeSize - halfRange == 1) {
+ // compute section
+ final int section = sectionStart + 1;
+
+ // create the left child
+ _right = new CollisionTree(_type);
+
+ _right._primitiveIndices = new int[mesh.getMeshData().getPrimitiveCount(section)];
+ for (int i = 0; i < _right._primitiveIndices.length; i++) {
+ _right._primitiveIndices[i] = i;
+ }
+ _right._mesh = _mesh;
+ _right.createTree(section, 0, _right._primitiveIndices.length, doSort);
+ } else {
+ // otherwise, make an empty collision tree and call split with new range
+ _right = new CollisionTree(_type);
+ _right.splitMesh(mesh, sectionStart + halfRange, sectionEnd, doSort);
+ }
+
+ // Ok, now since we technically have no primitives, we need our bounds to be the merging of our children bounds
+ // instead:
+ _bounds = _left._bounds.clone(_bounds);
+ _bounds.mergeLocal(_right._bounds);
+ _worldBounds = _bounds.clone(_worldBounds);
+ }
+
+ /**
+ * Creates a Collision Tree by recursively creating children nodes, splitting the primitives this node is
+ * responsible for in half until the desired primitive count is reached.
+ *
+ * @param start
+ * The start index of the primitivesArray, inclusive.
+ * @param end
+ * The end index of the primitivesArray, exclusive.
+ * @param doSort
+ * True if the primitives should be sorted at each level, false otherwise.
+ */
+ public void createTree(final int section, final int start, final int end, final boolean doSort) {
+ _section = section;
+ _start = start;
+ _end = end;
+
+ if (_primitiveIndices == null) {
+ return;
+ }
+
+ createBounds();
+
+ // the bounds at this level should contain all the primitives this level is responsible for.
+ _bounds.computeFromPrimitives(getMesh().getMeshData(), _section, _primitiveIndices, _start, _end);
+
+ // check to see if we are a leaf, if the number of primitives we reference is less than or equal to the maximum
+ // defined by the CollisionTreeManager we are done.
+ if (_end - _start + 1 <= CollisionTreeManager.getInstance().getMaxPrimitivesPerLeaf()) {
+ return;
+ }
+
+ // if doSort is set we need to attempt to optimize the referenced primitives. optimizing the sorting of the
+ // primitives will help group them spatially in the left/right children better.
+ if (doSort) {
+ sortPrimitives();
+ }
+
+ // create the left child
+ if (_left == null) {
+ _left = new CollisionTree(_type);
+ }
+
+ _left._primitiveIndices = _primitiveIndices;
+ _left._mesh = _mesh;
+ _left.createTree(_section, _start, (_start + _end) / 2, doSort);
+
+ // create the right child
+ if (_right == null) {
+ _right = new CollisionTree(_type);
+ }
+ _right._primitiveIndices = _primitiveIndices;
+ _right._mesh = _mesh;
+ _right.createTree(_section, (_start + _end) / 2, _end, doSort);
+ }
+
+ /**
+ * Tests if the world bounds of the node at this level intersects a provided bounding volume. If an intersection
+ * occurs, true is returned, otherwise false is returned. If the provided volume is invalid, false is returned.
+ *
+ * @param volume
+ * the volume to intersect with.
+ * @return true if there is an intersect, false otherwise.
+ */
+ public boolean intersectsBounding(final BoundingVolume volume) {
+ switch (volume.getType()) {
+ case AABB:
+ return _worldBounds.intersectsBoundingBox((BoundingBox) volume);
+ case OBB:
+ return _worldBounds.intersectsOrientedBoundingBox((OrientedBoundingBox) volume);
+ case Sphere:
+ return _worldBounds.intersectsSphere((BoundingSphere) volume);
+ default:
+ return false;
+ }
+
+ }
+
+ /**
+ * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned,
+ * otherwise false is returned. If the provided collisionTree is invalid, false is returned.
+ *
+ * @param collisionTree
+ * The Tree to test.
+ * @return True if they intersect, false otherwise.
+ */
+ public boolean intersect(final CollisionTree collisionTree) {
+ if (collisionTree == null) {
+ return false;
+ }
+
+ collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(),
+ collisionTree._worldBounds);
+
+ // our two collision bounds do not intersect, therefore, our primitives
+ // must not intersect. Return false.
+ if (!intersectsBounding(collisionTree._worldBounds)) {
+ return false;
+ }
+
+ // check children
+ if (_left != null) { // This is not a leaf
+ if (collisionTree.intersect(_left)) {
+ return true;
+ }
+ if (collisionTree.intersect(_right)) {
+ return true;
+ }
+ return false;
+ }
+
+ // This is a leaf
+ if (collisionTree._left != null) {
+ // but collision isn't
+ if (intersect(collisionTree._left)) {
+ return true;
+ }
+ if (intersect(collisionTree._right)) {
+ return true;
+ }
+ return false;
+ }
+
+ // both are leaves
+ final ReadOnlyTransform transformA = getMesh().getWorldTransform();
+ final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform();
+
+ final MeshData dataA = getMesh().getMeshData();
+ final MeshData dataB = collisionTree.getMesh().getMeshData();
+
+ Vector3[] storeA = null;
+ Vector3[] storeB = null;
+
+ // for every primitive to compare, put them into world space and check for intersections
+ for (int i = _start; i < _end; i++) {
+ storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA);
+ // to world space
+ for (int t = 0; t < storeA.length; t++) {
+ transformA.applyForward(storeA[t]);
+ }
+ for (int j = collisionTree._start; j < collisionTree._end; j++) {
+ storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB);
+ // to world space
+ for (int t = 0; t < storeB.length; t++) {
+ transformB.applyForward(storeB[t]);
+ }
+ if (Intersection.intersection(storeA, storeB)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if this Collision Tree intersects the given CollisionTree. If a collision occurs, true is returned,
+ * otherwise false is returned. If the provided collisionTree is invalid, false is returned. All collisions that
+ * occur are stored in lists as an integer index into the mesh's triangle buffer. where aList is the primitives for
+ * this mesh and bList is the primitives for the test tree.
+ *
+ * @param collisionTree
+ * The Tree to test.
+ * @param aList
+ * a list to contain the colliding primitives of this mesh.
+ * @param bList
+ * a list to contain the colliding primitives of the testing mesh.
+ * @return True if they intersect, false otherwise.
+ */
+ public boolean intersect(final CollisionTree collisionTree, final List<PrimitiveKey> aList,
+ final List<PrimitiveKey> bList) {
+
+ if (collisionTree == null) {
+ return false;
+ }
+
+ collisionTree._worldBounds = collisionTree._bounds.transform(collisionTree.getMesh().getWorldTransform(),
+ collisionTree._worldBounds);
+
+ // our two collision bounds do not intersect, therefore, our primitives
+ // must not intersect. Return false.
+ if (!intersectsBounding(collisionTree._worldBounds)) {
+ return false;
+ }
+
+ // if our node is not a leaf send the children (both left and right) to
+ // the test tree.
+ if (_left != null) { // This is not a leaf
+ boolean test = collisionTree.intersect(_left, bList, aList);
+ test = collisionTree.intersect(_right, bList, aList) || test;
+ return test;
+ }
+
+ // This node is a leaf, but the testing tree node is not. Therefore,
+ // continue processing the testing tree until we find its leaves.
+ if (collisionTree._left != null) {
+ boolean test = intersect(collisionTree._left, aList, bList);
+ test = intersect(collisionTree._right, aList, bList) || test;
+ return test;
+ }
+
+ // both this node and the testing node are leaves. Therefore, we can
+ // switch to checking the contained primitives with each other. Any
+ // that are found to intersect are placed in the appropriate list.
+ final ReadOnlyTransform transformA = getMesh().getWorldTransform();
+ final ReadOnlyTransform transformB = collisionTree.getMesh().getWorldTransform();
+
+ final MeshData dataA = getMesh().getMeshData();
+ final MeshData dataB = collisionTree.getMesh().getMeshData();
+
+ Vector3[] storeA = null;
+ Vector3[] storeB = null;
+
+ boolean test = false;
+
+ for (int i = _start; i < _end; i++) {
+ storeA = dataA.getPrimitiveVertices(_primitiveIndices[i], _section, storeA);
+ // to world space
+ for (int t = 0; t < storeA.length; t++) {
+ transformA.applyForward(storeA[t]);
+ }
+ for (int j = collisionTree._start; j < collisionTree._end; j++) {
+ storeB = dataB.getPrimitiveVertices(collisionTree._primitiveIndices[j], collisionTree._section, storeB);
+ // to world space
+ for (int t = 0; t < storeB.length; t++) {
+ transformB.applyForward(storeB[t]);
+ }
+ if (Intersection.intersection(storeA, storeB)) {
+ test = true;
+ aList.add(new PrimitiveKey(_primitiveIndices[i], _section));
+ bList.add(new PrimitiveKey(collisionTree._primitiveIndices[j], collisionTree._section));
+ }
+ }
+ }
+
+ return test;
+ }
+
+ /**
+ * intersect checks for collisions between this collision tree and a provided Ray. Any collisions are stored in a
+ * provided list as primitive index values. The ray is assumed to have a normalized direction for accurate
+ * calculations.
+ *
+ * @param ray
+ * the ray to test for intersections.
+ * @param store
+ * a list to fill with the index values of the primitive hit. if null, a new List is created.
+ * @return the list.
+ */
+ public List<PrimitiveKey> intersect(final Ray3 ray, final List<PrimitiveKey> store) {
+ List<PrimitiveKey> result = store;
+ if (result == null) {
+ result = new ArrayList<PrimitiveKey>();
+ }
+
+ // if our ray doesn't hit the bounds, then it must not hit a primitive.
+ if (!_worldBounds.intersects(ray)) {
+ return result;
+ }
+
+ // This is not a leaf node, therefore, check each child (left/right) for intersection with the ray.
+ if (_left != null) {
+ _left._worldBounds = _left._bounds.transform(getMesh().getWorldTransform(), _left._worldBounds);
+ _left.intersect(ray, result);
+ }
+
+ if (_right != null) {
+ _right._worldBounds = _right._bounds.transform(getMesh().getWorldTransform(), _right._worldBounds);
+ _right.intersect(ray, result);
+ } else if (_left == null) {
+ // This is a leaf node. We can therefore check each primitive this node contains. If an intersection occurs,
+ // place it in the list.
+
+ final MeshData data = getMesh().getMeshData();
+ final ReadOnlyTransform transform = getMesh().getWorldTransform();
+
+ Vector3[] points = null;
+ for (int i = _start; i < _end; i++) {
+ points = data.getPrimitiveVertices(_primitiveIndices[i], _section, points);
+ for (int t = 0; t < points.length; t++) {
+ transform.applyForward(points[t]);
+ }
+ if (ray.intersects(points, null)) {
+ result.add(new PrimitiveKey(_primitiveIndices[i], _section));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the bounding volume for this tree node in local space.
+ *
+ * @return the bounding volume for this tree node in local space.
+ */
+ public BoundingVolume getBounds() {
+ return _bounds;
+ }
+
+ /**
+ * Returns the bounding volume for this tree node in world space.
+ *
+ * @return the bounding volume for this tree node in world space.
+ */
+ public BoundingVolume getWorldBounds() {
+ return _worldBounds;
+ }
+
+ /**
+ * creates the appropriate bounding volume based on the type set during construction.
+ */
+ private void createBounds() {
+ switch (_type) {
+ case AABB:
+ _bounds = new BoundingBox();
+ _worldBounds = new BoundingBox();
+ break;
+ case OBB:
+ _bounds = new OrientedBoundingBox();
+ _worldBounds = new OrientedBoundingBox();
+ break;
+ case Sphere:
+ _bounds = new BoundingSphere();
+ _worldBounds = new BoundingSphere();
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * sortPrimitives attempts to optimize the ordering of the subsection of the array of primitives this node is
+ * responsible for. The sorting is based on the most efficient method along an axis. Using the TreeComparator and
+ * quick sort, the subsection of the array is sorted.
+ */
+ public void sortPrimitives() {
+ switch (_type) {
+ case AABB:
+ // determine the longest length of the box, this axis will be best for sorting.
+ if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getYExtent()) {
+ if (((BoundingBox) _bounds).getXExtent() > ((BoundingBox) _bounds).getZExtent()) {
+ _comparator.setAxis(TreeComparator.Axis.X);
+ } else {
+ _comparator.setAxis(TreeComparator.Axis.Z);
+ }
+ } else {
+ if (((BoundingBox) _bounds).getYExtent() > ((BoundingBox) _bounds).getZExtent()) {
+ _comparator.setAxis(TreeComparator.Axis.Y);
+ } else {
+ _comparator.setAxis(TreeComparator.Axis.Z);
+ }
+ }
+ break;
+ case OBB:
+ // determine the longest length of the box, this axis will be best for sorting.
+ if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getY()) {
+ if (((OrientedBoundingBox) _bounds)._extent.getX() > ((OrientedBoundingBox) _bounds)._extent.getZ()) {
+ _comparator.setAxis(TreeComparator.Axis.X);
+ } else {
+ _comparator.setAxis(TreeComparator.Axis.Z);
+ }
+ } else {
+ if (((OrientedBoundingBox) _bounds)._extent.getY() > ((OrientedBoundingBox) _bounds)._extent.getZ()) {
+ _comparator.setAxis(TreeComparator.Axis.Y);
+ } else {
+ _comparator.setAxis(TreeComparator.Axis.Z);
+ }
+ }
+ break;
+ case Sphere:
+ // sort any axis, X is fine.
+ _comparator.setAxis(TreeComparator.Axis.X);
+ break;
+ default:
+ break;
+ }
+
+ _comparator.setMesh(getMesh());
+ // TODO: broken atm
+ // SortUtil.qsort(_primitiveIndices, _start, _end - 1, _comparator);
+ }
+
+ /**
+ * @return the Mesh referenced by _mesh
+ */
+ private Mesh getMesh() {
+ return _mesh.get();
+ }
+
+ /**
+ * @param mesh
+ * Mesh object to reference.
+ * @return a new reference to the given mesh.
+ */
+ private WeakReference<Mesh> makeRef(final Mesh mesh) {
+ return new WeakReference<Mesh>(mesh);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java
new file mode 100644
index 0000000..840b3af
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeController.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * CollisionTreeController defines an interface for determining which collision tree to remove from a supplied cache.
+ * The desired size is given for the controller to attempt to reduce the cache to, as well as a list of protected
+ * elements that should <b>not</b> be removed from the cache.
+ */
+public interface CollisionTreeController {
+ /**
+ * clean will reduce the size of cache to the provided desiredSize. The protectedList defines elements that should
+ * not be removed from the cache.
+ *
+ * @param cache
+ * the cache to reduce.
+ * @param protectedList
+ * the list of elements to not remove.
+ * @param desiredSize
+ * the desiredSize of the final cache.
+ */
+ void clean(Map<Mesh, CollisionTree> cache, List<Mesh> protectedList, int desiredSize);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java
new file mode 100644
index 0000000..bfe0c1d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/CollisionTreeManager.java
@@ -0,0 +1,410 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.MapMaker;
+
+/**
+ * CollisionTreeManager is an automated system for handling the creation and deletion of CollisionTrees. The manager
+ * maintains a cache map of currently generated collision trees. The collision system itself requests a collision tree
+ * from the manager via the <code>getCollisionTree</code> method. The cache is checked for the tree, and if it is
+ * available, sent to the caller. If the tree is not in the cache, and generateTrees is true, a new CollisionTree is
+ * generated on the fly and sent to the caller. When a new tree is created, the cache size is compared to the
+ * maxElements value. If the cache is larger than maxElements, the cache is sent to the CollisionTreeController for
+ * cleaning.
+ * <p>
+ * There are a number of settings that can be used to control how trees are generated. First, generateTrees denotes
+ * whether the manager should be creating trees at all. This is set to true by default. doSort defines if the
+ * CollisionTree primitive array should be sorted as it is built. This is false by default. Sorting is beneficial for
+ * model data that is not well ordered spatially. This occurrence is rare, and sorting slows creation time. It is,
+ * therefore, only to be used when model data requires it. maxPrimitivesPerLeaf defines the number of primitives a leaf
+ * node in the collision tree should maintain. The larger number of primitives maintained in a leaf node, the smaller
+ * the tree, but the larger the number of checks during a collision. By default, this value is set to 16. maxElements
+ * defines the maximum number of trees that will be maintained before clean-up is required. A collision tree is defined
+ * for each mesh that is being collided with. The user should determine the optimal number of trees to maintain (a
+ * memory/performance tradeoff), based on the number of meshes, their population density and their primitive size. By
+ * default, this value is set to 25. The type of trees that will be generated is defined by the treeType value, where
+ * valid options are define in CollisionTree as AABB_TREE, OBB_TREE and SPHERE_TREE. You can set the functionality of
+ * how trees are removed from the cache by providing the manager with a CollisionTreeController implementation. By
+ * default, the manager will use the UsageTreeController for removing trees, but any other CollisionTreeController is
+ * acceptable. You can create protected tree manually. These are collision trees that you request the manager to create
+ * and not allow them to be removed by the CollisionTreeController.
+ *
+ * @see com.ardor3d.bounding.CollisionTree
+ * @see com.ardor3d.bounding.CollisionTreeController
+ */
+public enum CollisionTreeManager {
+ INSTANCE;
+
+ /**
+ * defines the default maximum number of trees to maintain.
+ */
+ public static final int DEFAULT_MAX_ELEMENTS = 25;
+ /**
+ * defines the default maximum number of primitives in a tree leaf.
+ */
+ public static final int DEFAULT_MAX_PRIMITIVES_PER_LEAF = 16;
+
+ // the cache and protected list for storing trees.
+ private final Map<Mesh, CollisionTree> _cache;
+ private final List<Mesh> _protectedList;
+
+ private boolean _generateTrees = true;
+ private boolean _doSort;
+
+ private CollisionTree.Type _treeType = CollisionTree.Type.AABB;
+
+ private int _maxPrimitivesPerLeaf = DEFAULT_MAX_PRIMITIVES_PER_LEAF;
+ private int _maxElements = DEFAULT_MAX_ELEMENTS;
+
+ private CollisionTreeController _treeRemover;
+
+ /**
+ * private constructor for the Singleton. Initializes the cache.
+ */
+ private CollisionTreeManager() {
+ _cache = new MapMaker().weakKeys().makeMap();
+ _protectedList = Collections.synchronizedList(new ArrayList<Mesh>(1));
+ setCollisionTreeController(new UsageTreeController());
+ }
+
+ /**
+ * retrieves the singleton instance of the CollisionTreeManager.
+ *
+ * @return the singleton instance of the manager.
+ */
+ public static CollisionTreeManager getInstance() {
+ return INSTANCE;
+ }
+
+ private CollisionTree cacheGet(final Mesh mesh) {
+ return _cache.get(mesh);
+ }
+
+ private void cacheRemove(final Mesh mesh) {
+ _cache.remove(mesh);
+ }
+
+ private void cachePut(final Mesh mesh, final CollisionTree tree) {
+ _cache.put(mesh, tree);
+ }
+
+ /**
+ * sets the CollisionTreeController used for cleaning the cache when the maximum number of elements is reached.
+ *
+ * @param treeRemover
+ * the controller used to clean the cache.
+ */
+ public void setCollisionTreeController(final CollisionTreeController treeRemover) {
+ _treeRemover = treeRemover;
+ }
+
+ /**
+ * getCollisionTree obtains a collision tree that is assigned to a supplied Mesh. The cache is checked for a
+ * pre-existing tree, if none is available and generateTrees is true, a new tree is created and returned.
+ *
+ * @param mesh
+ * the mesh to use as the key for the tree to obtain.
+ * @return the tree associated with a given mesh
+ */
+ public synchronized CollisionTree getCollisionTree(final Mesh mesh) {
+ CollisionTree toReturn = null;
+
+ toReturn = cacheGet(mesh);
+
+ // we didn't have it in the cache, create it if possible.
+ if (toReturn == null) {
+ if (_generateTrees) {
+ return generateCollisionTree(_treeType, mesh, false);
+ } else {
+ return null;
+ }
+ } else {
+ // we had it in the cache, to keep the keyset in order, reinsert this element
+ cacheRemove(mesh);
+ cachePut(mesh, toReturn);
+ return toReturn;
+ }
+ }
+
+ /**
+ * creates a new collision tree for the provided spatial. If the spatial is a node, it recursively calls
+ * generateCollisionTree for each child. If it is a Mesh, a call to generateCollisionTree is made for each mesh. If
+ * this tree(s) is to be protected, i.e. not deleted by the CollisionTreeController, set protect to true.
+ *
+ * @param type
+ * the type of collision tree to generate.
+ * @param object
+ * the Spatial to generate tree(s) for.
+ * @param protect
+ * true to keep these trees from being removed, false otherwise.
+ */
+ public void generateCollisionTree(final CollisionTree.Type type, final Spatial object, final boolean protect) {
+ if (object instanceof Mesh) {
+ generateCollisionTree(type, (Mesh) object, protect);
+ }
+ if (object instanceof Node) {
+ if (((Node) object).getNumberOfChildren() > 0) {
+ for (final Spatial sp : ((Node) object).getChildren()) {
+ generateCollisionTree(type, sp, protect);
+ }
+ }
+ }
+ }
+
+ /**
+ * generates a new tree for the associated mesh. The type is provided and a new tree is constructed of this type.
+ * The tree is placed in the cache. If the cache's size then becomes too large, the cache is sent to the
+ * CollisionTreeController for clean-up. If this tree is to be protected, i.e. protected from the
+ * CollisionTreeController, set protect to true.
+ *
+ * @param type
+ * the type of collision tree to generate.
+ * @param mesh
+ * the mesh to generate the tree for.
+ * @param protect
+ * true if this tree is to be protected, false otherwise.
+ * @return the new collision tree.
+ */
+ public CollisionTree generateCollisionTree(final CollisionTree.Type type, final Mesh mesh, final boolean protect) {
+ if (mesh == null) {
+ return null;
+ }
+
+ final CollisionTree tree = new CollisionTree(type);
+
+ generateCollisionTree(tree, mesh, protect);
+
+ return tree;
+ }
+
+ /**
+ * generates a new tree for the associated mesh. It is provided with a pre-existing, non-null tree. The tree is
+ * placed in the cache. If the cache's size then becomes too large, the cache is sent to the CollisionTreeController
+ * for clean-up. If this tree is to be protected, i.e. protected from the CollisionTreeController, set protect to
+ * true.
+ *
+ * @param tree
+ * the tree to use for generation
+ * @param mesh
+ * the mesh to generate the tree for.
+ * @param protect
+ * true if this tree is to be protected, false otherwise.
+ */
+ protected void generateCollisionTree(final CollisionTree tree, final Mesh mesh, final boolean protect) {
+ tree.construct(mesh, _doSort);
+ cachePut(mesh, tree);
+ // This mesh has been added by outside sources and labeled
+ // as protected. Therefore, put it in the protected list
+ // so it is not removed by a controller.
+ if (protect) {
+ setProtected(mesh);
+ }
+
+ // Are we over our max? Test
+ if (_cache.size() > _maxElements && _treeRemover != null) {
+ _treeRemover.clean(_cache, _protectedList, _maxElements);
+ }
+ }
+
+ /**
+ * removes a collision tree from the manager based on the mesh supplied.
+ *
+ * @param mesh
+ * the mesh to remove the corresponding collision tree.
+ */
+ public void removeCollisionTree(final Mesh mesh) {
+ cacheRemove(mesh);
+ removeProtected(mesh);
+ }
+
+ /**
+ * removes all collision trees associated with a Spatial object.
+ *
+ * @param object
+ * the spatial to remove all collision trees from.
+ */
+ public void removeCollisionTree(final Spatial object) {
+ if (object instanceof Node) {
+ final Node n = (Node) object;
+ for (int i = n.getNumberOfChildren() - 1; i >= 0; i--) {
+ removeCollisionTree(n.getChild(i));
+ }
+ } else if (object instanceof Mesh) {
+ removeCollisionTree((Mesh) object);
+ }
+ }
+
+ /**
+ * updates the existing tree for a supplied mesh. If this tree does not exist, the tree is not updated. If the tree
+ * is not in the cache, no further operations are handled.
+ *
+ * @param mesh
+ * the mesh key for the tree to update.
+ */
+ public void updateCollisionTree(final Mesh mesh) {
+ final CollisionTree ct = cacheGet(mesh);
+ if (ct != null) {
+ generateCollisionTree(ct, mesh, _protectedList != null && _protectedList.contains(mesh));
+ }
+ }
+
+ /**
+ * updates the existing tree(s) for a supplied spatial. If this tree does not exist, the tree is not updated. If the
+ * tree is not in the cache, no further operations are handled.
+ *
+ * @param object
+ * the object on which to update the tree.
+ */
+ public void updateCollisionTree(final Spatial object) {
+ if (object instanceof Node) {
+ final Node n = (Node) object;
+ for (int i = n.getNumberOfChildren() - 1; i >= 0; i--) {
+ updateCollisionTree(n.getChild(i));
+ }
+ } else if (object instanceof Mesh) {
+ updateCollisionTree((Mesh) object);
+ }
+ }
+
+ /**
+ * returns true if the manager is set to sort new generated trees. False otherwise.
+ *
+ * @return true to sort tree, false otherwise.
+ */
+ public boolean isDoSort() {
+ return _doSort;
+ }
+
+ /**
+ * set if this manager should have newly generated trees sort primitives.
+ *
+ * @param doSort
+ * true to sort trees, false otherwise.
+ */
+ public void setDoSort(final boolean doSort) {
+ _doSort = doSort;
+ }
+
+ /**
+ * returns true if the manager will automatically generate new trees as needed, false otherwise.
+ *
+ * @return true if this manager is generating trees, false otherwise.
+ */
+ public boolean isGenerateTrees() {
+ return _generateTrees;
+ }
+
+ /**
+ * set if this manager should generate new trees as needed.
+ *
+ * @param generateTrees
+ * true to generate trees, false otherwise.
+ */
+ public void setGenerateTrees(final boolean generateTrees) {
+ _generateTrees = generateTrees;
+ }
+
+ /**
+ * @return the type of tree the manager will create.
+ * @see CollisionTree.Type
+ */
+ public CollisionTree.Type getTreeType() {
+ return _treeType;
+ }
+
+ /**
+ * @param treeType
+ * the type of tree to create.
+ * @see CollisionTree.Type
+ */
+ public void setTreeType(final CollisionTree.Type treeType) {
+ _treeType = treeType;
+ }
+
+ /**
+ * returns the maximum number of primitives a leaf of the collision tree may contain.
+ *
+ * @return the maximum number of primitives a leaf may contain.
+ */
+ public int getMaxPrimitivesPerLeaf() {
+ return _maxPrimitivesPerLeaf;
+ }
+
+ /**
+ * set the maximum number of primitives a leaf of the collision tree may contain.
+ *
+ * @param maxPrimitivesPerLeaf
+ * the maximum number of primitives a leaf may contain.
+ */
+ public void setMaxPrimitivesPerLeaf(final int maxPrimitivesPerLeaf) {
+ _maxPrimitivesPerLeaf = maxPrimitivesPerLeaf;
+ }
+
+ /**
+ * returns the maximum number of CollisionTree elements this manager will hold on to before starting to clear some.
+ *
+ * @return the maximum number of CollisionTree elements.
+ */
+ public int getMaxElements() {
+ return _maxElements;
+ }
+
+ /**
+ * set the maximum number of CollisionTree elements this manager will hold on to before starting to clear some.
+ *
+ * @param maxElements
+ * the maximum number of CollisionTree elements.
+ */
+ public void setMaxElements(final int maxElements) {
+ _maxElements = maxElements;
+ }
+
+ /**
+ * Add the given mesh to our "protected" list. This will signal to our cleanup operation that when deciding which
+ * trees to trim in an effort to keep our cache size to a certain desired size, do not trim the tree associated with
+ * this mesh.
+ *
+ * @param meshToProtect
+ * the mesh whose CollisionTree we want to protect.
+ */
+ public void setProtected(final Mesh meshToProtect) {
+ if (!_protectedList.contains(meshToProtect)) {
+ _protectedList.add(meshToProtect);
+ }
+ }
+
+ /**
+ * Removes the supplied mesh from the "protected" list.
+ *
+ * @param mesh
+ */
+ public void removeProtected(final Mesh mesh) {
+ _protectedList.remove(mesh);
+ }
+
+ /**
+ *
+ * @return an immutable copy of the list of protected meshes.
+ */
+ public List<Mesh> getProtectedMeshes() {
+ return ImmutableList.copyOf(_protectedList);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java
new file mode 100644
index 0000000..e3e77b0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/OrientedBoundingBox.java
@@ -0,0 +1,1439 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyPlane;
+import com.ardor3d.math.type.ReadOnlyPlane.Side;
+import com.ardor3d.math.type.ReadOnlyRay3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class OrientedBoundingBox extends BoundingVolume {
+
+ private static final long serialVersionUID = 1L;
+
+ /** X axis of the Oriented Box. */
+ protected final Vector3 _xAxis = new Vector3(1, 0, 0);
+
+ /** Y axis of the Oriented Box. */
+ protected final Vector3 _yAxis = new Vector3(0, 1, 0);
+
+ /** Z axis of the Oriented Box. */
+ protected final Vector3 _zAxis = new Vector3(0, 0, 1);
+
+ /** Extents of the box along the x,y,z axis. */
+ protected final Vector3 _extent = new Vector3(0, 0, 0);
+
+ /** Vector array used to store the array of 8 corners the box has. */
+ protected final Vector3[] _vectorStore = new Vector3[8];
+
+ /**
+ * If true, the box's vectorStore array correctly represents the box's corners.
+ */
+ public boolean correctCorners = false;
+
+ protected final Vector3 _compVect3 = new Vector3();
+
+ public OrientedBoundingBox() {
+ for (int x = 0; x < 8; x++) {
+ _vectorStore[x] = new Vector3();
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return Type.OBB;
+ }
+
+ @Override
+ // XXX: HACK, revisit.
+ public BoundingVolume transform(final ReadOnlyTransform transform, BoundingVolume store) {
+ if (store == null || store.getType() != Type.OBB) {
+ store = new OrientedBoundingBox();
+ }
+ final OrientedBoundingBox toReturn = (OrientedBoundingBox) store;
+ final Vector3 helper = new Vector3();
+ helper.set(1, 0, 0);
+ final double scaleX = transform.applyForwardVector(helper).length();
+ helper.set(0, 1, 0);
+ final double scaleY = transform.applyForwardVector(helper).length();
+ helper.set(0, 0, 1);
+ final double scaleZ = transform.applyForwardVector(helper).length();
+ toReturn._extent.set(Math.abs(_extent.getX() * scaleX), Math.abs(_extent.getY() * scaleY),
+ Math.abs(_extent.getZ() * scaleZ));
+
+ transform.getMatrix().applyPost(_xAxis, toReturn._xAxis);
+ transform.getMatrix().applyPost(_yAxis, toReturn._yAxis);
+ transform.getMatrix().applyPost(_zAxis, toReturn._zAxis);
+ if (!transform.isRotationMatrix()) {
+ toReturn._xAxis.normalizeLocal();
+ toReturn._yAxis.normalizeLocal();
+ toReturn._zAxis.normalizeLocal();
+ }
+
+ transform.applyForward(_center, toReturn._center);
+ toReturn.correctCorners = false;
+ toReturn.computeCorners();
+ return toReturn;
+ }
+
+ @Override
+ public Side whichSide(final ReadOnlyPlane plane) {
+ final ReadOnlyVector3 planeNormal = plane.getNormal();
+ final double fRadius = Math.abs(_extent.getX() * (planeNormal.dot(_xAxis)))
+ + Math.abs(_extent.getY() * (planeNormal.dot(_yAxis)))
+ + Math.abs(_extent.getZ() * (planeNormal.dot(_zAxis)));
+ final double fDistance = plane.pseudoDistance(_center);
+ if (fDistance <= -fRadius) {
+ return Plane.Side.Inside;
+ } else if (fDistance >= fRadius) {
+ return Plane.Side.Outside;
+ } else {
+ return Plane.Side.Neither;
+ }
+ }
+
+ @Override
+ public void computeFromPoints(final FloatBuffer points) {
+ containAABB(points);
+ }
+
+ /**
+ * Calculates an AABB of the given point values for this OBB.
+ *
+ * @param points
+ * The points this OBB should contain.
+ */
+ private void containAABB(final FloatBuffer points) {
+ if (points == null || points.limit() <= 2) { // we need at least a 3
+ // double vector
+ return;
+ }
+
+ BufferUtils.populateFromBuffer(_compVect1, points, 0);
+ double minX = _compVect1.getX(), minY = _compVect1.getY(), minZ = _compVect1.getZ();
+ double maxX = _compVect1.getX(), maxY = _compVect1.getY(), maxZ = _compVect1.getZ();
+
+ for (int i = 1, len = points.limit() / 3; i < len; i++) {
+ BufferUtils.populateFromBuffer(_compVect1, points, i);
+
+ minX = Math.min(_compVect1.getX(), minX);
+ maxX = Math.max(_compVect1.getX(), maxX);
+
+ minY = Math.min(_compVect1.getY(), minY);
+ maxY = Math.max(_compVect1.getY(), maxY);
+
+ minZ = Math.min(_compVect1.getZ(), minZ);
+ maxZ = Math.max(_compVect1.getZ(), maxZ);
+ }
+
+ _center.set(minX + maxX, minY + maxY, minZ + maxZ);
+ _center.multiplyLocal(0.5);
+
+ _extent.set(maxX - _center.getX(), maxY - _center.getY(), maxZ - _center.getZ());
+
+ _xAxis.set(1, 0, 0);
+ _yAxis.set(0, 1, 0);
+ _zAxis.set(0, 0, 1);
+
+ correctCorners = false;
+ }
+
+ @Override
+ public BoundingVolume merge(final BoundingVolume volume) {
+ // clone ourselves into a new bounding volume, then merge.
+ return clone(new OrientedBoundingBox()).mergeLocal(volume);
+ }
+
+ @Override
+ public BoundingVolume mergeLocal(final BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+
+ case OBB: {
+ return mergeOBB((OrientedBoundingBox) volume);
+ }
+
+ case AABB: {
+ return mergeAABB((BoundingBox) volume);
+ }
+
+ case Sphere: {
+ return mergeSphere((BoundingSphere) volume);
+ }
+
+ default:
+ return null;
+
+ }
+ }
+
+ private BoundingVolume mergeSphere(final BoundingSphere volume) {
+ // check for infinite bounds to prevent NaN values
+ if (Vector3.isInfinite(getExtent()) || Double.isInfinite(volume.getRadius())) {
+ setCenter(Vector3.ZERO);
+ _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ return this;
+ }
+
+ final BoundingSphere mergeSphere = volume;
+ if (!correctCorners) {
+ computeCorners();
+ }
+
+ final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(16 * 3);
+
+ mergeBuf.rewind();
+ for (int i = 0; i < 8; i++) {
+ mergeBuf.put((float) _vectorStore[i].getX());
+ mergeBuf.put((float) _vectorStore[i].getY());
+ mergeBuf.put((float) _vectorStore[i].getZ());
+ }
+ mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() + mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() + mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius()));
+ mergeBuf.put((float) (mergeSphere._center.getX() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getY() - mergeSphere.getRadius()))
+ .put((float) (mergeSphere._center.getZ() - mergeSphere.getRadius()));
+ containAABB(mergeBuf);
+ correctCorners = false;
+ return this;
+ }
+
+ private BoundingVolume mergeAABB(final BoundingBox volume) {
+ // check for infinite bounds to prevent NaN values
+ if (Vector3.isInfinite(getExtent()) || Double.isInfinite(volume.getXExtent())
+ || Double.isInfinite(volume.getYExtent()) || Double.isInfinite(volume.getZExtent())) {
+ setCenter(Vector3.ZERO);
+ _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ return this;
+ }
+
+ final BoundingBox mergeBox = volume;
+ if (!correctCorners) {
+ computeCorners();
+ }
+
+ final FloatBuffer mergeBuf = BufferUtils.createFloatBufferOnHeap(16 * 3);
+
+ mergeBuf.rewind();
+ for (int i = 0; i < 8; i++) {
+ mergeBuf.put((float) _vectorStore[i].getX());
+ mergeBuf.put((float) _vectorStore[i].getY());
+ mergeBuf.put((float) _vectorStore[i].getZ());
+ }
+ mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() + mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() + mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() - mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() + mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() - mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() + mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() + mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() + mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() - mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent()));
+ mergeBuf.put((float) (mergeBox._center.getX() - mergeBox.getXExtent()))
+ .put((float) (mergeBox._center.getY() - mergeBox.getYExtent()))
+ .put((float) (mergeBox._center.getZ() - mergeBox.getZExtent()));
+ containAABB(mergeBuf);
+ correctCorners = false;
+ return this;
+ }
+
+ private BoundingVolume mergeOBB(final OrientedBoundingBox volume) {
+ // check for infinite bounds to prevent NaN values
+ if (Vector3.isInfinite(getExtent()) || Vector3.isInfinite(volume.getExtent())) {
+ setCenter(Vector3.ZERO);
+ _extent.set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ return this;
+ }
+
+ // OrientedBoundingBox mergeBox=(OrientedBoundingBox) volume;
+ // if (!correctCorners) this.computeCorners();
+ // if (!mergeBox.correctCorners) mergeBox.computeCorners();
+ // Vector3[] mergeArray=new Vector3[16];
+ // for (int i=0;i<vectorStore.length;i++){
+ // mergeArray[i*2+0]=this .vectorStore[i];
+ // mergeArray[i*2+1]=mergeBox.vectorStore[i];
+ // }
+ // containAABB(mergeArray);
+ // correctCorners=false;
+ // return this;
+ // construct a box that contains the input boxes
+ // Box3<Real> kBox;
+ final OrientedBoundingBox rkBox0 = this;
+ final OrientedBoundingBox rkBox1 = volume;
+
+ // The first guess at the box center. This value will be updated later
+ // after the input box vertices are projected onto axes determined by an
+ // average of box axes.
+ final Vector3 kBoxCenter = (rkBox0._center.add(rkBox1._center, Vector3.fetchTempInstance())).multiplyLocal(.5);
+
+ // A box's axes, when viewed as the columns of a matrix, form a rotation
+ // matrix. The input box axes are converted to quaternions. The average
+ // quaternion is computed, then normalized to unit length. The result is
+ // the slerp of the two input quaternions with t-value of 1/2. The
+ // result is converted back to a rotation matrix and its columns are
+ // selected as the merged box axes.
+ final Quaternion kQ0 = Quaternion.fetchTempInstance(), kQ1 = Quaternion.fetchTempInstance();
+ kQ0.fromAxes(rkBox0._xAxis, rkBox0._yAxis, rkBox0._zAxis);
+ kQ1.fromAxes(rkBox1._xAxis, rkBox1._yAxis, rkBox1._zAxis);
+
+ if (kQ0.dot(kQ1) < 0.0) {
+ kQ1.multiplyLocal(-1.0);
+ }
+
+ final Quaternion kQ = kQ0.addLocal(kQ1);
+ kQ.normalizeLocal();
+
+ final Matrix3 kBoxaxis = kQ.toRotationMatrix(Matrix3.fetchTempInstance());
+ final Vector3 newXaxis = kBoxaxis.getColumn(0, Vector3.fetchTempInstance());
+ final Vector3 newYaxis = kBoxaxis.getColumn(1, Vector3.fetchTempInstance());
+ final Vector3 newZaxis = kBoxaxis.getColumn(2, Vector3.fetchTempInstance());
+
+ // Project the input box vertices onto the merged-box axes. Each axis
+ // D[i] containing the current center C has a minimum projected value
+ // pmin[i] and a maximum projected value pmax[i]. The corresponding end
+ // points on the axes are C+pmin[i]*D[i] and C+pmax[i]*D[i]. The point C
+ // is not necessarily the midpoint for any of the intervals. The actual
+ // box center will be adjusted from C to a point C' that is the midpoint
+ // of each interval,
+ // C' = C + sum_{i=0}^1 0.5*(pmin[i]+pmax[i])*D[i]
+ // The box extents are
+ // e[i] = 0.5*(pmax[i]-pmin[i])
+
+ int i;
+ double fDot;
+ final Vector3 kDiff = Vector3.fetchTempInstance();
+ final Vector3 kMin = Vector3.fetchTempInstance();
+ final Vector3 kMax = Vector3.fetchTempInstance();
+
+ if (!rkBox0.correctCorners) {
+ rkBox0.computeCorners();
+ }
+ for (i = 0; i < 8; i++) {
+ rkBox0._vectorStore[i].subtract(kBoxCenter, kDiff);
+
+ fDot = kDiff.dot(newXaxis);
+ if (fDot > kMax.getX()) {
+ kMax.setX(fDot);
+ } else if (fDot < kMin.getX()) {
+ kMin.setX(fDot);
+ }
+
+ fDot = kDiff.dot(newYaxis);
+ if (fDot > kMax.getY()) {
+ kMax.setY(fDot);
+ } else if (fDot < kMin.getY()) {
+ kMin.setY(fDot);
+ }
+
+ fDot = kDiff.dot(newZaxis);
+ if (fDot > kMax.getZ()) {
+ kMax.setZ(fDot);
+ } else if (fDot < kMin.getZ()) {
+ kMin.setZ(fDot);
+ }
+
+ }
+
+ if (!rkBox1.correctCorners) {
+ rkBox1.computeCorners();
+ }
+ for (i = 0; i < 8; i++) {
+ rkBox1._vectorStore[i].subtract(kBoxCenter, kDiff);
+
+ fDot = kDiff.dot(newXaxis);
+ if (fDot > kMax.getX()) {
+ kMax.setX(fDot);
+ } else if (fDot < kMin.getX()) {
+ kMin.setX(fDot);
+ }
+
+ fDot = kDiff.dot(newYaxis);
+ if (fDot > kMax.getY()) {
+ kMax.setY(fDot);
+ } else if (fDot < kMin.getY()) {
+ kMin.setY(fDot);
+ }
+
+ fDot = kDiff.dot(newZaxis);
+ if (fDot > kMax.getZ()) {
+ kMax.setZ(fDot);
+ } else if (fDot < kMin.getZ()) {
+ kMin.setZ(fDot);
+ }
+ }
+
+ _xAxis.set(newXaxis);
+ _yAxis.set(newYaxis);
+ _zAxis.set(newZaxis);
+
+ final Vector3 tempVec = Vector3.fetchTempInstance();
+ _extent.setX(.5 * (kMax.getX() - kMin.getX()));
+ kBoxCenter.addLocal(_xAxis.multiply(.5 * (kMax.getX() + kMin.getX()), tempVec));
+
+ _extent.setY(.5 * (kMax.getY() - kMin.getY()));
+ kBoxCenter.addLocal(_yAxis.multiply(.5 * (kMax.getY() + kMin.getY()), tempVec));
+
+ _extent.setZ(.5 * (kMax.getZ() - kMin.getZ()));
+ kBoxCenter.addLocal(_zAxis.multiply(.5 * (kMax.getZ() + kMin.getZ()), tempVec));
+
+ _center.set(kBoxCenter);
+
+ correctCorners = false;
+
+ Quaternion.releaseTempInstance(kQ0);
+ Quaternion.releaseTempInstance(kQ1);
+ Matrix3.releaseTempInstance(kBoxaxis);
+ Vector3.releaseTempInstance(kBoxCenter);
+ Vector3.releaseTempInstance(newXaxis);
+ Vector3.releaseTempInstance(newYaxis);
+ Vector3.releaseTempInstance(newZaxis);
+ Vector3.releaseTempInstance(kDiff);
+ Vector3.releaseTempInstance(kMin);
+ Vector3.releaseTempInstance(kMax);
+ Vector3.releaseTempInstance(tempVec);
+
+ return this;
+ }
+
+ @Override
+ public BoundingVolume clone(final BoundingVolume store) {
+ OrientedBoundingBox toReturn;
+ if (store instanceof OrientedBoundingBox) {
+ toReturn = (OrientedBoundingBox) store;
+ } else {
+ toReturn = new OrientedBoundingBox();
+ }
+ toReturn._extent.set(_extent);
+ toReturn._xAxis.set(_xAxis);
+ toReturn._yAxis.set(_yAxis);
+ toReturn._zAxis.set(_zAxis);
+ toReturn._center.set(_center);
+ toReturn._checkPlane = _checkPlane;
+ for (int x = _vectorStore.length; --x >= 0;) {
+ toReturn._vectorStore[x].set(_vectorStore[x]);
+ }
+ toReturn.correctCorners = correctCorners;
+ return toReturn;
+ }
+
+ @Override
+ public double getRadius() {
+ double radius = 0.0;
+ radius = Math.max(radius, _xAxis.multiply(_extent.getX(), _compVect1).length());
+ radius = Math.max(radius, _yAxis.multiply(_extent.getY(), _compVect1).length());
+ radius = Math.max(radius, _zAxis.multiply(_extent.getZ(), _compVect1).length());
+
+ return radius;
+ }
+
+ /**
+ * Sets the vectorStore information to the 8 corners of the box.
+ */
+ public void computeCorners() {
+ final Vector3 tempAxis0 = _xAxis.multiply(_extent.getX(), _compVect1);
+ final Vector3 tempAxis1 = _yAxis.multiply(_extent.getY(), _compVect2);
+ final Vector3 tempAxis2 = _zAxis.multiply(_extent.getZ(), _compVect3);
+
+ _vectorStore[0].set(_center).subtractLocal(tempAxis0).subtractLocal(tempAxis1).subtractLocal(tempAxis2);
+ _vectorStore[1].set(_center).addLocal(tempAxis0).subtractLocal(tempAxis1).subtractLocal(tempAxis2);
+ _vectorStore[2].set(_center).addLocal(tempAxis0).addLocal(tempAxis1).subtractLocal(tempAxis2);
+ _vectorStore[3].set(_center).subtractLocal(tempAxis0).addLocal(tempAxis1).subtractLocal(tempAxis2);
+ _vectorStore[4].set(_center).subtractLocal(tempAxis0).subtractLocal(tempAxis1).addLocal(tempAxis2);
+ _vectorStore[5].set(_center).addLocal(tempAxis0).subtractLocal(tempAxis1).addLocal(tempAxis2);
+ _vectorStore[6].set(_center).addLocal(tempAxis0).addLocal(tempAxis1).addLocal(tempAxis2);
+ _vectorStore[7].set(_center).subtractLocal(tempAxis0).addLocal(tempAxis1).addLocal(tempAxis2);
+
+ correctCorners = true;
+ }
+
+ @Override
+ public void computeFromPrimitives(final MeshData data, final int section, final int[] indices, final int start,
+ final int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ final int vertsPerPrimitive = data.getIndexMode(section).getVertexCount();
+ Vector3[] store = new Vector3[vertsPerPrimitive];
+
+ final Vector3 min = _compVect1
+ .set(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ final Vector3 max = _compVect2
+ .set(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+
+ Vector3 point;
+ for (int i = start; i < end; i++) {
+ store = data.getPrimitiveVertices(indices[i], section, store);
+ for (int j = 0; j < vertsPerPrimitive; j++) {
+ point = store[j];
+ if (point.getX() < min.getX()) {
+ min.setX(point.getX());
+ } else if (point.getX() > max.getX()) {
+ max.setX(point.getX());
+ }
+ if (point.getY() < min.getY()) {
+ min.setY(point.getY());
+ } else if (point.getY() > max.getY()) {
+ max.setY(point.getY());
+ }
+ if (point.getZ() < min.getZ()) {
+ min.setZ(point.getZ());
+ } else if (point.getZ() > max.getZ()) {
+ max.setZ(point.getZ());
+ }
+ }
+ }
+
+ _center.set(min.addLocal(max));
+ _center.multiplyLocal(0.5);
+
+ _extent.set(max.getX() - _center.getX(), max.getY() - _center.getY(), max.getZ() - _center.getZ());
+
+ _xAxis.set(1, 0, 0);
+ _yAxis.set(0, 1, 0);
+ _zAxis.set(0, 0, 1);
+
+ correctCorners = false;
+ }
+
+ public boolean intersection(final OrientedBoundingBox box1) {
+ // Cutoff for cosine of angles between box axes. This is used to catch the cases when at least one pair of axes
+ // are parallel. If this happens, there is no need to test for separation along the Cross(A[i],B[j]) directions.
+ final OrientedBoundingBox box0 = this;
+ final double cutoff = 0.999999;
+ boolean parallelPairExists = false;
+ int i;
+
+ // convenience variables
+ final ReadOnlyVector3 akA[] = new ReadOnlyVector3[] { box0.getXAxis(), box0.getYAxis(), box0.getZAxis() };
+ final ReadOnlyVector3[] akB = new ReadOnlyVector3[] { box1.getXAxis(), box1.getYAxis(), box1.getZAxis() };
+ final ReadOnlyVector3 afEA = box0._extent;
+ final ReadOnlyVector3 afEB = box1._extent;
+
+ // compute difference of box centers, D = C1-C0
+ final Vector3 kD = box1._center.subtract(box0._center, _compVect1);
+
+ final double[][] aafC = { new double[3], new double[3], new double[3] };
+
+ final double[][] aafAbsC = { new double[3], new double[3], new double[3] };
+
+ final double[] afAD = new double[3];
+ double fR0, fR1, fR; // interval radii and distance between centers
+ double fR01; // = R0 + R1
+
+ // axis C0+t*A0
+ for (i = 0; i < 3; i++) {
+ aafC[0][i] = akA[0].dot(akB[i]);
+ aafAbsC[0][i] = Math.abs(aafC[0][i]);
+ if (aafAbsC[0][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[0] = akA[0].dot(kD);
+ fR = Math.abs(afAD[0]);
+ fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2];
+ fR01 = afEA.getX() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1
+ for (i = 0; i < 3; i++) {
+ aafC[1][i] = akA[1].dot(akB[i]);
+ aafAbsC[1][i] = Math.abs(aafC[1][i]);
+ if (aafAbsC[1][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[1] = akA[1].dot(kD);
+ fR = Math.abs(afAD[1]);
+ fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2];
+ fR01 = afEA.getY() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2
+ for (i = 0; i < 3; i++) {
+ aafC[2][i] = akA[2].dot(akB[i]);
+ aafAbsC[2][i] = Math.abs(aafC[2][i]);
+ if (aafAbsC[2][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[2] = akA[2].dot(kD);
+ fR = Math.abs(afAD[2]);
+ fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2];
+ fR01 = afEA.getZ() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B0
+ fR = Math.abs(akB[0].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + afEB.getX();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B1
+ fR = Math.abs(akB[1].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + afEB.getY();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B2
+ fR = Math.abs(akB[2].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2];
+ fR01 = fR0 + afEB.getZ();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // At least one pair of box axes was parallel, so the separation is
+ // effectively in 2D where checking the "edge" normals is sufficient for
+ // the separation of the boxes.
+ if (parallelPairExists) {
+ return true;
+ }
+
+ // axis C0+t*A0xB0
+ fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+ fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0];
+ fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB1
+ fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+ fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1];
+ fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB2
+ fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+ fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2];
+ fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB0
+ fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+ fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB1
+ fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+ fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB2
+ fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+ fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB0
+ fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+ fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB1
+ fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+ fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB2
+ fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+ fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean intersects(final BoundingVolume bv) {
+ if (bv == null) {
+ return false;
+ }
+
+ return bv.intersectsOrientedBoundingBox(this);
+ }
+
+ @Override
+ public boolean intersectsSphere(final BoundingSphere bs) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bs._center)) {
+ return false;
+ }
+
+ _compVect1.set(bs.getCenter()).subtractLocal(_center);
+ final Matrix3 tempMa = Matrix3.fetchTempInstance().fromAxes(_xAxis, _yAxis, _zAxis);
+
+ tempMa.applyPost(_compVect1, _compVect1);
+
+ boolean result = false;
+ if (Math.abs(_compVect1.getX()) < bs.getRadius() + _extent.getX()
+ && Math.abs(_compVect1.getY()) < bs.getRadius() + _extent.getY()
+ && Math.abs(_compVect1.getZ()) < bs.getRadius() + _extent.getZ()) {
+ result = true;
+ }
+
+ Matrix3.releaseTempInstance(tempMa);
+ return result;
+ }
+
+ @Override
+ public boolean intersectsBoundingBox(final BoundingBox bb) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(bb._center)) {
+ return false;
+ }
+
+ // Cutoff for cosine of angles between box axes. This is used to catch
+ // the cases when at least one pair of axes are parallel. If this
+ // happens,
+ // there is no need to test for separation along the Cross(A[i],B[j])
+ // directions.
+ final double cutoff = 0.999999f;
+ boolean parallelPairExists = false;
+ int i;
+
+ // convenience variables
+ final Vector3 akA[] = new Vector3[] { _xAxis, _yAxis, _zAxis };
+ final Vector3[] akB = new Vector3[] { Vector3.fetchTempInstance(), Vector3.fetchTempInstance(),
+ Vector3.fetchTempInstance() };
+ final Vector3 afEA = _extent;
+ final Vector3 afEB = Vector3.fetchTempInstance().set(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());
+
+ // compute difference of box centers, D = C1-C0
+ final Vector3 kD = bb.getCenter().subtract(_center, Vector3.fetchTempInstance());
+
+ final double[][] aafC = { new double[3], new double[3], new double[3] };
+
+ final double[][] aafAbsC = { new double[3], new double[3], new double[3] };
+
+ final double[] afAD = new double[3];
+ double fR0, fR1, fR; // interval radii and distance between centers
+ double fR01; // = R0 + R1
+
+ try {
+
+ // axis C0+t*A0
+ for (i = 0; i < 3; i++) {
+ aafC[0][i] = akA[0].dot(akB[i]);
+ aafAbsC[0][i] = Math.abs(aafC[0][i]);
+ if (aafAbsC[0][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[0] = akA[0].dot(kD);
+ fR = Math.abs(afAD[0]);
+ fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2];
+ fR01 = afEA.getX() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1
+ for (i = 0; i < 3; i++) {
+ aafC[1][i] = akA[1].dot(akB[i]);
+ aafAbsC[1][i] = Math.abs(aafC[1][i]);
+ if (aafAbsC[1][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[1] = akA[1].dot(kD);
+ fR = Math.abs(afAD[1]);
+ fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2];
+ fR01 = afEA.getY() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2
+ for (i = 0; i < 3; i++) {
+ aafC[2][i] = akA[2].dot(akB[i]);
+ aafAbsC[2][i] = Math.abs(aafC[2][i]);
+ if (aafAbsC[2][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[2] = akA[2].dot(kD);
+ fR = Math.abs(afAD[2]);
+ fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2];
+ fR01 = afEA.getZ() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B0
+ fR = Math.abs(akB[0].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + afEB.getX();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B1
+ fR = Math.abs(akB[1].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + afEB.getY();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B2
+ fR = Math.abs(akB[2].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2];
+ fR01 = fR0 + afEB.getZ();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // At least one pair of box axes was parallel, so the separation is
+ // effectively in 2D where checking the "edge" normals is sufficient for
+ // the separation of the boxes.
+ if (parallelPairExists) {
+ return true;
+ }
+
+ // axis C0+t*A0xB0
+ fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+ fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0];
+ fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB1
+ fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+ fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1];
+ fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB2
+ fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+ fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2];
+ fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB0
+ fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+ fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB1
+ fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+ fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB2
+ fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+ fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB0
+ fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+ fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB1
+ fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+ fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB2
+ fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+ fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ return true;
+ } finally {
+ // Make sure we release the temp vars
+ Vector3.releaseTempInstance(kD);
+ Vector3.releaseTempInstance(afEB);
+ for (final Vector3 vec : akB) {
+ Vector3.releaseTempInstance(vec);
+ }
+
+ }
+ }
+
+ @Override
+ public boolean intersectsOrientedBoundingBox(final OrientedBoundingBox obb) {
+ if (!Vector3.isValid(_center) || !Vector3.isValid(obb._center)) {
+ return false;
+ }
+
+ // Cutoff for cosine of angles between box axes. This is used to catch
+ // the cases when at least one pair of axes are parallel. If this
+ // happens,
+ // there is no need to test for separation along the Cross(A[i],B[j])
+ // directions.
+ final double cutoff = 0.999999f;
+ boolean parallelPairExists = false;
+ int i;
+
+ // convenience variables
+ final Vector3 akA[] = new Vector3[] { _xAxis, _yAxis, _zAxis };
+ final Vector3[] akB = new Vector3[] { obb._xAxis, obb._yAxis, obb._zAxis };
+ final Vector3 afEA = _extent;
+ final Vector3 afEB = obb._extent;
+
+ // compute difference of box centers, D = C1-C0
+ final Vector3 kD = obb._center.subtract(_center, _compVect1);
+
+ final double[][] aafC = { new double[3], new double[3], new double[3] };
+
+ final double[][] aafAbsC = { new double[3], new double[3], new double[3] };
+
+ final double[] afAD = new double[3];
+ double fR0, fR1, fR; // interval radii and distance between centers
+ double fR01; // = R0 + R1
+
+ // axis C0+t*A0
+ for (i = 0; i < 3; i++) {
+ aafC[0][i] = akA[0].dot(akB[i]);
+ aafAbsC[0][i] = Math.abs(aafC[0][i]);
+ if (aafAbsC[0][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[0] = akA[0].dot(kD);
+ fR = Math.abs(afAD[0]);
+ fR1 = afEB.getX() * aafAbsC[0][0] + afEB.getY() * aafAbsC[0][1] + afEB.getZ() * aafAbsC[0][2];
+ fR01 = afEA.getX() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1
+ for (i = 0; i < 3; i++) {
+ aafC[1][i] = akA[1].dot(akB[i]);
+ aafAbsC[1][i] = Math.abs(aafC[1][i]);
+ if (aafAbsC[1][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[1] = akA[1].dot(kD);
+ fR = Math.abs(afAD[1]);
+ fR1 = afEB.getX() * aafAbsC[1][0] + afEB.getY() * aafAbsC[1][1] + afEB.getZ() * aafAbsC[1][2];
+ fR01 = afEA.getY() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2
+ for (i = 0; i < 3; i++) {
+ aafC[2][i] = akA[2].dot(akB[i]);
+ aafAbsC[2][i] = Math.abs(aafC[2][i]);
+ if (aafAbsC[2][i] > cutoff) {
+ parallelPairExists = true;
+ }
+ }
+ afAD[2] = akA[2].dot(kD);
+ fR = Math.abs(afAD[2]);
+ fR1 = afEB.getX() * aafAbsC[2][0] + afEB.getY() * aafAbsC[2][1] + afEB.getZ() * aafAbsC[2][2];
+ fR01 = afEA.getZ() + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B0
+ fR = Math.abs(akB[0].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][0] + afEA.getY() * aafAbsC[1][0] + afEA.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + afEB.getX();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B1
+ fR = Math.abs(akB[1].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][1] + afEA.getY() * aafAbsC[1][1] + afEA.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + afEB.getY();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*B2
+ fR = Math.abs(akB[2].dot(kD));
+ fR0 = afEA.getX() * aafAbsC[0][2] + afEA.getY() * aafAbsC[1][2] + afEA.getZ() * aafAbsC[2][2];
+ fR01 = fR0 + afEB.getZ();
+ if (fR > fR01) {
+ return false;
+ }
+
+ // At least one pair of box axes was parallel, so the separation is
+ // effectively in 2D where checking the "edge" normals is sufficient for
+ // the separation of the boxes.
+ if (parallelPairExists) {
+ return true;
+ }
+
+ // axis C0+t*A0xB0
+ fR = Math.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+ fR0 = afEA.getY() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[1][0];
+ fR1 = afEB.getY() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB1
+ fR = Math.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+ fR0 = afEA.getY() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[1][1];
+ fR1 = afEB.getX() * aafAbsC[0][2] + afEB.getZ() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A0xB2
+ fR = Math.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+ fR0 = afEA.getY() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[1][2];
+ fR1 = afEB.getX() * aafAbsC[0][1] + afEB.getY() * aafAbsC[0][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB0
+ fR = Math.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+ fR0 = afEA.getX() * aafAbsC[2][0] + afEA.getZ() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB1
+ fR = Math.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+ fR0 = afEA.getX() * aafAbsC[2][1] + afEA.getZ() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[1][2] + afEB.getZ() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A1xB2
+ fR = Math.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+ fR0 = afEA.getX() * aafAbsC[2][2] + afEA.getZ() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[1][1] + afEB.getY() * aafAbsC[1][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB0
+ fR = Math.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+ fR0 = afEA.getX() * aafAbsC[1][0] + afEA.getY() * aafAbsC[0][0];
+ fR1 = afEB.getY() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][1];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB1
+ fR = Math.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+ fR0 = afEA.getX() * aafAbsC[1][1] + afEA.getY() * aafAbsC[0][1];
+ fR1 = afEB.getX() * aafAbsC[2][2] + afEB.getZ() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ // axis C0+t*A2xB2
+ fR = Math.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+ fR0 = afEA.getX() * aafAbsC[1][2] + afEA.getY() * aafAbsC[0][2];
+ fR1 = afEB.getX() * aafAbsC[2][1] + afEB.getY() * aafAbsC[2][0];
+ fR01 = fR0 + fR1;
+ if (fR > fR01) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean intersects(final ReadOnlyRay3 ray) {
+ if (!Vector3.isValid(_center)) {
+ return false;
+ }
+
+ double rhs;
+ final ReadOnlyVector3 rayDir = ray.getDirection();
+ final Vector3 diff = _compVect1.set(ray.getOrigin()).subtractLocal(_center);
+ final Vector3 wCrossD = _compVect2;
+
+ final double[] fWdU = new double[3];
+ final double[] fAWdU = new double[3];
+ final double[] fDdU = new double[3];
+ final double[] fADdU = new double[3];
+ final double[] fAWxDdU = new double[3];
+
+ fWdU[0] = rayDir.dot(_xAxis);
+ fAWdU[0] = Math.abs(fWdU[0]);
+ fDdU[0] = diff.dot(_xAxis);
+ fADdU[0] = Math.abs(fDdU[0]);
+ if (fADdU[0] > _extent.getX() && fDdU[0] * fWdU[0] >= 0.0) {
+ return false;
+ }
+
+ fWdU[1] = rayDir.dot(_yAxis);
+ fAWdU[1] = Math.abs(fWdU[1]);
+ fDdU[1] = diff.dot(_yAxis);
+ fADdU[1] = Math.abs(fDdU[1]);
+ if (fADdU[1] > _extent.getY() && fDdU[1] * fWdU[1] >= 0.0) {
+ return false;
+ }
+
+ fWdU[2] = rayDir.dot(_zAxis);
+ fAWdU[2] = Math.abs(fWdU[2]);
+ fDdU[2] = diff.dot(_zAxis);
+ fADdU[2] = Math.abs(fDdU[2]);
+ if (fADdU[2] > _extent.getZ() && fDdU[2] * fWdU[2] >= 0.0) {
+ return false;
+ }
+
+ rayDir.cross(diff, wCrossD);
+
+ fAWxDdU[0] = Math.abs(wCrossD.dot(_xAxis));
+ rhs = _extent.getY() * fAWdU[2] + _extent.getZ() * fAWdU[1];
+ if (fAWxDdU[0] > rhs) {
+ return false;
+ }
+
+ fAWxDdU[1] = Math.abs(wCrossD.dot(_yAxis));
+ rhs = _extent.getX() * fAWdU[2] + _extent.getZ() * fAWdU[0];
+ if (fAWxDdU[1] > rhs) {
+ return false;
+ }
+
+ fAWxDdU[2] = Math.abs(wCrossD.dot(_zAxis));
+ rhs = _extent.getX() * fAWdU[1] + _extent.getY() * fAWdU[0];
+ if (fAWxDdU[2] > rhs) {
+ return false;
+
+ }
+
+ return true;
+ }
+
+ @Override
+ public IntersectionRecord intersectsWhere(final ReadOnlyRay3 ray) {
+ final ReadOnlyVector3 rayDir = ray.getDirection();
+ final ReadOnlyVector3 rayOrigin = ray.getOrigin();
+
+ // convert ray to box coordinates
+ final Vector3 diff = rayOrigin.subtract(getCenter(), _compVect1);
+ diff.set(_xAxis.dot(diff), _yAxis.dot(diff), _zAxis.dot(diff));
+ final Vector3 direction = _compVect2.set(_xAxis.dot(rayDir), _yAxis.dot(rayDir), _zAxis.dot(rayDir));
+
+ final double[] t = { 0, Double.POSITIVE_INFINITY };
+
+ final double saveT0 = t[0], saveT1 = t[1];
+ final boolean notEntirelyClipped = clip(+direction.getX(), -diff.getX() - _extent.getX(), t)
+ && clip(-direction.getX(), +diff.getX() - _extent.getX(), t)
+ && clip(+direction.getY(), -diff.getY() - _extent.getY(), t)
+ && clip(-direction.getY(), +diff.getY() - _extent.getY(), t)
+ && clip(+direction.getZ(), -diff.getZ() - _extent.getZ(), t)
+ && clip(-direction.getZ(), +diff.getZ() - _extent.getZ(), t);
+
+ if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) {
+ if (t[1] > t[0]) {
+ final double[] distances = t;
+ final Vector3[] points = new Vector3[] {
+ rayDir.multiply(distances[0], new Vector3()).addLocal(rayOrigin),
+ rayDir.multiply(distances[1], new Vector3()).addLocal(rayOrigin) };
+ final IntersectionRecord record = new IntersectionRecord(distances, points);
+ return record;
+ }
+
+ final double[] distances = new double[] { t[0] };
+ final Vector3[] points = new Vector3[] { rayDir.multiply(distances[0], new Vector3()).addLocal(rayOrigin) };
+ final IntersectionRecord record = new IntersectionRecord(distances, points);
+ return record;
+ }
+
+ return null;
+
+ }
+
+ /**
+ * <code>clip</code> determines if a line segment intersects the current test plane.
+ *
+ * @param denom
+ * the denominator of the line segment.
+ * @param numer
+ * the numerator of the line segment.
+ * @param t
+ * test values of the plane.
+ * @return true if the line segment intersects the plane, false otherwise.
+ */
+ private boolean clip(final double denom, final double numer, final double[] t) {
+ // Return value is 'true' if line segment intersects the current test
+ // plane. Otherwise 'false' is returned in which case the line segment
+ // is entirely clipped.
+ if (denom > 0.0) {
+ if (numer > denom * t[1]) {
+ return false;
+ }
+ if (numer > denom * t[0]) {
+ t[0] = numer / denom;
+ }
+ return true;
+ } else if (denom < 0.0) {
+ if (numer > denom * t[0]) {
+ return false;
+ }
+ if (numer > denom * t[1]) {
+ t[1] = numer / denom;
+ }
+ return true;
+ } else {
+ return numer <= 0.0;
+ }
+ }
+
+ public void setXAxis(final ReadOnlyVector3 axis) {
+ _xAxis.set(axis);
+ correctCorners = false;
+ }
+
+ public void setYAxis(final ReadOnlyVector3 axis) {
+ _yAxis.set(axis);
+ correctCorners = false;
+ }
+
+ public void setZAxis(final ReadOnlyVector3 axis) {
+ _zAxis.set(axis);
+ correctCorners = false;
+ }
+
+ public void setExtent(final ReadOnlyVector3 ext) {
+ _extent.set(ext);
+ correctCorners = false;
+ }
+
+ public ReadOnlyVector3 getXAxis() {
+ return _xAxis;
+ }
+
+ public ReadOnlyVector3 getYAxis() {
+ return _yAxis;
+ }
+
+ public ReadOnlyVector3 getZAxis() {
+ return _zAxis;
+ }
+
+ public ReadOnlyVector3 getExtent() {
+ return _extent;
+ }
+
+ @Override
+ public boolean contains(final ReadOnlyVector3 point) {
+ _compVect1.set(point).subtractLocal(_center);
+ double coeff = _compVect1.dot(_xAxis);
+ if (Math.abs(coeff) > _extent.getX()) {
+ return false;
+ }
+
+ coeff = _compVect1.dot(_yAxis);
+ if (Math.abs(coeff) > _extent.getY()) {
+ return false;
+ }
+
+ coeff = _compVect1.dot(_zAxis);
+ if (Math.abs(coeff) > _extent.getZ()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public double distanceToEdge(final ReadOnlyVector3 point) {
+ // compute coordinates of point in box coordinate system
+ final Vector3 diff = point.subtract(_center, _compVect1);
+ final Vector3 closest = _compVect2.set(diff.dot(_xAxis), diff.dot(_yAxis), diff.dot(_zAxis));
+
+ // project test point onto box
+ double sqrDistance = 0.0;
+ double delta;
+
+ if (closest.getX() < -_extent.getX()) {
+ delta = closest.getX() + _extent.getX();
+ sqrDistance += delta * delta;
+ closest.setX(-_extent.getX());
+ } else if (closest.getX() > _extent.getX()) {
+ delta = closest.getX() - _extent.getX();
+ sqrDistance += delta * delta;
+ closest.setX(_extent.getX());
+ }
+
+ if (closest.getY() < -_extent.getY()) {
+ delta = closest.getY() + _extent.getY();
+ sqrDistance += delta * delta;
+ closest.setY(-_extent.getY());
+ } else if (closest.getY() > _extent.getY()) {
+ delta = closest.getY() - _extent.getY();
+ sqrDistance += delta * delta;
+ closest.setY(_extent.getY());
+ }
+
+ if (closest.getZ() < -_extent.getZ()) {
+ delta = closest.getZ() + _extent.getZ();
+ sqrDistance += delta * delta;
+ closest.setZ(-_extent.getZ());
+ } else if (closest.getZ() > _extent.getZ()) {
+ delta = closest.getZ() - _extent.getZ();
+ sqrDistance += delta * delta;
+ closest.setZ(_extent.getZ());
+ }
+
+ return Math.sqrt(sqrDistance);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xAxis, "_xAxis", new Vector3(Vector3.UNIT_X));
+ capsule.write(_yAxis, "yAxis", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_zAxis, "zAxis", new Vector3(Vector3.UNIT_Z));
+ capsule.write(_extent, "extent", new Vector3(Vector3.ZERO));
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xAxis.set((Vector3) capsule.readSavable("xAxis", new Vector3(Vector3.UNIT_X)));
+ _yAxis.set((Vector3) capsule.readSavable("yAxis", new Vector3(Vector3.UNIT_Y)));
+ _zAxis.set((Vector3) capsule.readSavable("zAxis", new Vector3(Vector3.UNIT_Z)));
+ _extent.set((Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO)));
+ correctCorners = false;
+ }
+
+ @Override
+ public double getVolume() {
+ return (8 * _extent.getX() * _extent.getY() * _extent.getZ());
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java
new file mode 100644
index 0000000..9f4626e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/TreeComparator.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.util.Comparator;
+
+import com.ardor3d.intersection.PrimitiveKey;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+
+public class TreeComparator implements Comparator<PrimitiveKey> {
+ enum Axis {
+ X, Y, Z;
+ }
+
+ private Axis _axis;
+
+ private Mesh _mesh;
+
+ private Vector3[] _aCompare = null;
+
+ private Vector3[] _bCompare = null;
+
+ public void setAxis(final Axis axis) {
+ _axis = axis;
+ }
+
+ public void setMesh(final Mesh mesh) {
+ _mesh = mesh;
+ }
+
+ public int compare(final PrimitiveKey o1, final PrimitiveKey o2) {
+
+ if (o1.equals(o2)) {
+ return 0;
+ }
+
+ Vector3 centerA = null;
+ Vector3 centerB = null;
+ _aCompare = _mesh.getMeshData().getPrimitiveVertices(o1.getPrimitiveIndex(), o1.getSection(), _aCompare);
+ _bCompare = _mesh.getMeshData().getPrimitiveVertices(o2.getPrimitiveIndex(), o2.getSection(), _bCompare);
+
+ for (int i = 1; i < _aCompare.length; i++) {
+ _aCompare[0].addLocal(_aCompare[i]);
+ }
+ for (int i = 1; i < _bCompare.length; i++) {
+ _bCompare[0].addLocal(_bCompare[i]);
+ }
+ if (_aCompare.length == _bCompare.length) {
+ // don't need average since lists are same size. (3X < 3Y ? X < Y)
+ centerA = _aCompare[0];
+ centerB = _bCompare[0];
+ } else {
+ // perform average since we have different size lists
+ centerA = _aCompare[0].divideLocal(_aCompare.length);
+ centerB = _bCompare[0].divideLocal(_bCompare.length);
+ }
+
+ switch (_axis) {
+ case X:
+ if (centerA.getX() < centerB.getX()) {
+ return -1;
+ }
+ if (centerA.getX() > centerB.getX()) {
+ return 1;
+ }
+ return 0;
+ case Y:
+ if (centerA.getY() < centerB.getY()) {
+ return -1;
+ }
+ if (centerA.getY() > centerB.getY()) {
+ return 1;
+ }
+ return 0;
+ case Z:
+ if (centerA.getZ() < centerB.getZ()) {
+ return -1;
+ }
+ if (centerA.getZ() > centerB.getZ()) {
+ return 1;
+ }
+ return 0;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java
new file mode 100644
index 0000000..ba4d285
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/bounding/UsageTreeController.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * UsageTreeController defines a CollisionTreeController implementation that removes cache elements based on the
+ * frequency of usage. By default, and implementation in the CollisionTreeManager, the cache's key set will be ordered
+ * with the first element being the oldest used. Therefore, UsageTreeController simply removes elements from the cache
+ * starting at the first key and working up until the desired size is reached or we run out of elements.
+ */
+public class UsageTreeController implements CollisionTreeController {
+
+ /**
+ * removes elements from cache (that are not in the protectedList) until the desiredSize is reached. It removes
+ * elements from the keyset as they are ordered.
+ *
+ * @param cache
+ * the cache to clean.
+ * @param protectedList
+ * the list of elements to not remove.
+ * @param desiredSize
+ * the final size of the cache to attempt to reach.
+ */
+ public void clean(final Map<Mesh, CollisionTree> cache, final List<Mesh> protectedList, final int desiredSize) {
+
+ // get the ordered keyset (this will be ordered with oldest to newest).
+ final Object[] set = cache.keySet().toArray();
+ int count = 0;
+ // go through the cache removing items that are not protected until the
+ // size of the cache is small enough to return.
+ while (cache.size() > desiredSize && count < set.length) {
+ if (protectedList == null || !protectedList.contains(set[count])) {
+ cache.remove(set[count]);
+ }
+ count++;
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java
new file mode 100644
index 0000000..8bab195
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Canvas.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import java.util.concurrent.CountDownLatch;
+
+import com.ardor3d.annotation.MainThread;
+
+/**
+ * This interface defines the View, and should maybe be called the ViewUpdater. It owns the rendering phase, and
+ * controls all interactions with the Renderer.
+ */
+public interface Canvas {
+
+ /**
+ * Do work to initialize this canvas, generally setting up the associated CanvasRenderer, etc.
+ */
+ @MainThread
+ void init();
+
+ /**
+ * Ask the canvas to render itself. Note that this may occur in another thread and therefore a latch is given so the
+ * caller may know when the draw has completed.
+ *
+ * @param latch
+ * a counter that should be decremented once drawing has completed.
+ */
+ @MainThread
+ void draw(CountDownLatch latch);
+
+ /**
+ * @return the CanvasRenderer associated with this Canvas.
+ */
+ CanvasRenderer getCanvasRenderer();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java
new file mode 100644
index 0000000..5c5ba52
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/CanvasRenderer.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.util.Ardor3dException;
+
+/**
+ * Represents a class that knows how to render a scene using a specific Open GL implementation.
+ */
+public interface CanvasRenderer {
+ void init(DisplaySettings settings, boolean doSwap);
+
+ /**
+ * Draw the current state of the scene.
+ */
+ @MainThread
+ boolean draw();
+
+ /**
+ * Returns the camera being used by this canvas renderer. Modifying the returned {@link Camera} instance effects the
+ * view being rendered, so this method can be used to move the camera, etc.
+ *
+ * @return the camera used by this canvas renderer
+ */
+ Camera getCamera();
+
+ /**
+ * Replaces the camera being used by this canvas renderer.
+ *
+ * @param camera
+ * the camera to use
+ */
+ void setCamera(Camera camera);
+
+ /**
+ * Returns the scene being used by this canvas renderer.
+ *
+ * @return the camera used by this canvas renderer
+ */
+ Scene getScene();
+
+ /**
+ * Replaces the scene being used by this canvas renderer.
+ *
+ * @param scene
+ * the scene to use
+ */
+ void setScene(Scene scene);
+
+ /**
+ * Returns the renderer being used by this canvas renderer.
+ *
+ * @return the renderer used by this canvas renderer
+ */
+ Renderer getRenderer();
+
+ /**
+ * Have the CanvasRenderer claim the graphics context.
+ *
+ * @throws Ardor3dException
+ * if we can not claim the context.
+ */
+ void makeCurrentContext() throws Ardor3dException;
+
+ /**
+ * Have the CanvasRenderer release the graphics context.
+ */
+ void releaseCurrentContext();
+
+ /**
+ * @return the Ardor3D RenderContext associated with this CanvasRenderer.
+ */
+ RenderContext getRenderContext();
+
+ /**
+ * @return an int representing the buffers to clear at the start of each frame. Default is
+ * Renderer.BUFFER_COLOR_AND_DEPTH
+ */
+ int getFrameClear();
+
+ /**
+ * @param buffers
+ * an int representing the buffers to clear at the start of each frame. Default is
+ * Renderer.BUFFER_COLOR_AND_DEPTH
+ */
+ void setFrameClear(final int buffers);
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java
new file mode 100644
index 0000000..19aed1c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/DisplaySettings.java
@@ -0,0 +1,266 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+public class DisplaySettings {
+ private final int _width;
+ private final int _height;
+ private final int _colorDepth;
+ private final int _frequency;
+ private final int _alphaBits;
+ private final int _depthBits;
+ private final int _stencilBits;
+ private final int _samples;
+ private final boolean _fullScreen;
+ private final boolean _stereo;
+ private final CanvasRenderer _shareContext;
+
+ /**
+ * Convenience method equivalent to <code>DisplaySettings(width, height, 0, 0, 0, 8, 0, 0,
+ * false, false, null)</code>
+ *
+ * @param width
+ * the canvas width
+ * @param height
+ * the canvas height
+ * @param depthBits
+ * the number of bits making up the z-buffer
+ * @param samples
+ * the number of samples used to anti-alias
+ * @see http://en.wikipedia.org/wiki/Z-buffering
+ * @see http://en.wikipedia.org/wiki/Multisample_anti-aliasing
+ */
+ public DisplaySettings(final int width, final int height, final int depthBits, final int samples) {
+ _width = width;
+ _height = height;
+ _colorDepth = 0;
+ _frequency = 0;
+ _alphaBits = 0;
+ _depthBits = depthBits;
+ _stencilBits = 0;
+ _samples = samples;
+ _fullScreen = false;
+ _stereo = false;
+ _shareContext = null;
+ }
+
+ /**
+ * Convenience method equivalent to <code>DisplaySettings(width, height, colorDepth, frequency,
+ * 0, 8, 0, 0, fullScreen, false, null)</code>
+ *
+ * @param width
+ * the canvas width
+ * @param height
+ * the canvas height
+ * @param colorDepth
+ * the number of color bits used to represent the color of a single pixel
+ * @param frequency
+ * the number of times per second to repaint the canvas
+ * @param fullScreen
+ * true if the canvas should assume exclusive access to the screen
+ * @see http://en.wikipedia.org/wiki/Refresh_rate
+ */
+ public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency,
+ final boolean fullScreen) {
+ _width = width;
+ _height = height;
+ _colorDepth = colorDepth;
+ _frequency = frequency;
+ _alphaBits = 0;
+ _depthBits = 8;
+ _stencilBits = 0;
+ _samples = 0;
+ _fullScreen = fullScreen;
+ _stereo = false;
+ _shareContext = null;
+ }
+
+ /**
+ * Convenience method equivalent to <code>DisplaySettings(width, height, colorDepth, frequency,
+ * alphaBits, depthBits, stencilBits, samples, fullScreen, stereo, null)</code>
+ *
+ * @param width
+ * the canvas width
+ * @param height
+ * the canvas height
+ * @param colorDepth
+ * the number of color bits used to represent the color of a single pixel
+ * @param frequency
+ * the number of times per second to repaint the canvas
+ * @param alphaBits
+ * the numner of bits used to represent the translucency of a single pixel
+ * @param depthBits
+ * the number of bits making up the z-buffer
+ * @param stencilBits
+ * the number of bits making up the stencil buffer
+ * @param samples
+ * the number of samples used to anti-alias
+ * @param fullScreen
+ * true if the canvas should assume exclusive access to the screen
+ * @param stereo
+ * true if the canvas should be rendered stereoscopically (for 3D glasses)
+ * @see http://en.wikipedia.org/wiki/Refresh_rate
+ * @see http://en.wikipedia.org/wiki/Alpha_compositing
+ * @see http://en.wikipedia.org/wiki/Stencil_buffer
+ * @see http://en.wikipedia.org/wiki/Stereoscopy
+ */
+ public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency,
+ final int alphaBits, final int depthBits, final int stencilBits, final int samples,
+ final boolean fullScreen, final boolean stereo) {
+ _width = width;
+ _height = height;
+ _colorDepth = colorDepth;
+ _frequency = frequency;
+ _alphaBits = alphaBits;
+ _depthBits = depthBits;
+ _stencilBits = stencilBits;
+ _samples = samples;
+ _fullScreen = fullScreen;
+ _stereo = stereo;
+ _shareContext = null;
+ }
+
+ /**
+ * Creates a new <code>DisplaySettings</code> object.
+ *
+ * @param width
+ * the canvas width
+ * @param height
+ * the canvas height
+ * @param colorDepth
+ * the number of color bits used to represent the color of a single pixel
+ * @param frequency
+ * the number of times per second to repaint the canvas
+ * @param alphaBits
+ * the numner of bits used to represent the translucency of a single pixel
+ * @param depthBits
+ * the number of bits making up the z-buffer
+ * @param stencilBits
+ * the number of bits making up the stencil buffer
+ * @param samples
+ * the number of samples used to anti-alias
+ * @param fullScreen
+ * true if the canvas should assume exclusive access to the screen
+ * @param stereo
+ * true if the canvas should be rendered stereoscopically (for 3D glasses)
+ * @param shareContext
+ * the renderer used to render the canvas (see "ardor3d.useMultipleContexts" property)
+ * @see http://en.wikipedia.org/wiki/Z-buffering
+ * @see http://en.wikipedia.org/wiki/Multisample_anti-aliasing
+ * @see http://en.wikipedia.org/wiki/Refresh_rate
+ * @see http://en.wikipedia.org/wiki/Alpha_compositing
+ * @see http://en.wikipedia.org/wiki/Stencil_buffer
+ * @see http://en.wikipedia.org/wiki/Stereoscopy
+ * @see http://www.ardor3d.com/forums/viewtopic.php?f=13&t=318&p=2311&hilit=ardor3d.useMultipleContexts#p2311
+ */
+ public DisplaySettings(final int width, final int height, final int colorDepth, final int frequency,
+ final int alphaBits, final int depthBits, final int stencilBits, final int samples,
+ final boolean fullScreen, final boolean stereo, final CanvasRenderer shareContext) {
+ _width = width;
+ _height = height;
+ _colorDepth = colorDepth;
+ _frequency = frequency;
+ _alphaBits = alphaBits;
+ _depthBits = depthBits;
+ _stencilBits = stencilBits;
+ _samples = samples;
+ _fullScreen = fullScreen;
+ _stereo = stereo;
+ _shareContext = shareContext;
+ }
+
+ public CanvasRenderer getShareContext() {
+ return _shareContext;
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+
+ public int getHeight() {
+ return _height;
+ }
+
+ public int getColorDepth() {
+ return _colorDepth;
+ }
+
+ public int getFrequency() {
+ return _frequency;
+ }
+
+ public int getAlphaBits() {
+ return _alphaBits;
+ }
+
+ public int getDepthBits() {
+ return _depthBits;
+ }
+
+ public int getStencilBits() {
+ return _stencilBits;
+ }
+
+ public int getSamples() {
+ return _samples;
+ }
+
+ public boolean isFullScreen() {
+ return _fullScreen;
+ }
+
+ public boolean isStereo() {
+ return _stereo;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final DisplaySettings that = (DisplaySettings) o;
+
+ return _colorDepth == that._colorDepth
+ && _frequency == that._frequency
+ && _fullScreen != that._fullScreen
+ && _height != that._height
+ && _width != that._width
+ && _alphaBits != that._alphaBits
+ && _depthBits != that._depthBits
+ && _stencilBits != that._stencilBits
+ && _samples != that._samples
+ && _stereo != that._stereo
+ && ((_shareContext == that._shareContext) || (_shareContext != null && _shareContext
+ .equals(that._shareContext)));
+ }
+
+ @Override
+ public int hashCode() {
+ int result;
+ result = 17;
+ result = 31 * result + _height;
+ result = 31 * result + _width;
+ result = 31 * result + _colorDepth;
+ result = 31 * result + _frequency;
+ result = 31 * result + _alphaBits;
+ result = 31 * result + _depthBits;
+ result = 31 * result + _stencilBits;
+ result = 31 * result + _samples;
+ result = 31 * result + (_fullScreen ? 1 : 0);
+ result = 31 * result + (_stereo ? 1 : 0);
+ result = 31 * result + (_shareContext != null ? _shareContext.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java
new file mode 100644
index 0000000..c5465cf
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/FrameHandler.java
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.annotation.GuardedBy;
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.util.Timer;
+
+/**
+ * Does the work needed in a given frame.
+ */
+public final class FrameHandler {
+ private static final Logger logger = Logger.getLogger(FrameHandler.class.toString());
+
+ /**
+ * Thread synchronization of the updaters list is delegated to the CopyOnWriteArrayList.
+ */
+ private final CopyOnWriteArrayList<Updater> _updaters;
+
+ /**
+ * Canvases is both protected by an intrinsic lock and by the fact that it is a CopyOnWriteArrayList. This is
+ * because it is necessary to check the size of the list and then fetch an iterator that is guaranteed to iterate
+ * over that number of elements. See {@link #updateFrame()} for the code that does this.
+ */
+ @GuardedBy("this")
+ private final CopyOnWriteArrayList<Canvas> _canvases;
+ private final Timer _timer;
+
+ /**
+ * Number of seconds we'll wait for a latch to count down to 0. Default is 5.
+ */
+ private long _timeoutSeconds = 5;
+
+ public FrameHandler(final Timer timer) {
+ _timer = timer;
+ _updaters = new CopyOnWriteArrayList<Updater>();
+ _canvases = new CopyOnWriteArrayList<Canvas>();
+ }
+
+ @MainThread
+ public void updateFrame() {
+ // calculate tpf
+ // update updaters
+ // draw canvases
+
+ _timer.update();
+
+ // using the CopyOnWriteArrayList synchronization here, since that means
+ // that we don't have to hold any locks while calling Updater.update(double),
+ // and also makes the code simple. An updater that is registered after the below
+ // loop has started will be updated at the next call to updateFrame().
+ for (final Updater updater : _updaters) {
+ updater.update(_timer);
+ }
+
+ int numCanvases;
+ Iterator<Canvas> iterator;
+
+ // make sure that there is no race condition with addCanvas - getting the iterator and
+ // the number of canvases currently in the list in a synchronized section, and ensuring that
+ // the addCanvas() method is also synchronized on this, means that they will
+ // both remain valid outside the section later, when we call the probably long-running, alien
+ // draw() methods. Since 'canvases' is a CopyOnWriteArrayList, the iterator is guaranteed to
+ // be valid outside the synchronized section, and getting them both inside the synchronized section
+ // means that the number of canvases read will be the same as the number of elements the iterator
+ // will iterate over.
+ synchronized (this) {
+ numCanvases = _canvases.size();
+ iterator = _canvases.iterator();
+ }
+
+ final CountDownLatch latch = new CountDownLatch(numCanvases);
+
+ while (iterator.hasNext()) {
+ iterator.next().draw(latch);
+ }
+
+ try {
+ // wait for all canvases to be drawn - the reason for using the latch is that
+ // in some cases (AWT, for instance), the thread that calls canvas.draw() is not the
+ // one that holds the OpenGL context, which means that drawing is simply queued.
+ // When the actual OpenGL rendering has been done, the OpenGL thread will countdown
+ // on the latch, and once all the canvases have finished rendering, this method
+ // will return.
+ final boolean success = latch.await(_timeoutSeconds, TimeUnit.SECONDS);
+
+ if (!success) {
+ logger.logp(Level.SEVERE, FrameHandler.class.toString(), "updateFrame",
+ "Timeout while waiting for renderers");
+ // FIXME: should probably reset update flag in canvases?
+ }
+ } catch (final InterruptedException e) {
+ // restore updated status
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Add an updater to the frame handler.
+ * <p>
+ * The frame handler calls the {@link Updater#update(com.ardor3d.util.ReadOnlyTimer) update} method of each updater
+ * that has been added to it once per frame, before rendering begins.
+ * <p>
+ * <strong>Note:</strong> that is the frame handler has already been initialized then the updater will <em>not</em>
+ * have it's {@code init} method called automatically, it is up to the client code to perform any initialization
+ * explicitly under this scenario.
+ *
+ * @param updater
+ * the updater to add.
+ */
+ public void addUpdater(final Updater updater) {
+ _updaters.addIfAbsent(updater);
+ }
+
+ /**
+ * Remove an updater from the frame handler.
+ *
+ * @param updater
+ * the updater to remove.
+ * @return {@code true} if the updater was removed, {@code false} otherwise (which will happen if, for example, the
+ * updater had not previously been added to the frame handler).
+ */
+ public boolean removeUpdater(final Updater updater) {
+ return _updaters.remove(updater);
+ }
+
+ /**
+ * Add a canvas to the frame handler.
+ * <p>
+ * The frame handler calls the {@link Canvas#draw(java.util.concurrent.CountDownLatch)} draw} method of each canvas
+ * that has been added to it once per frame, after updating is complete.
+ * <p>
+ * <strong>Note:</strong> that is the frame handler has already been initialized then the canvas will <em>not</em>
+ * have it's {@code init} method called automatically, it is up to the client code to perform any initialization
+ * explicitly under this scenario.
+ *
+ * @param canvas
+ * the canvas to add.
+ */
+ public synchronized void addCanvas(final Canvas canvas) {
+ _canvases.addIfAbsent(canvas);
+ }
+
+ /**
+ * Remove a canvas from the frame handler.
+ *
+ * @param canvas
+ * the canvas to remove.
+ * @return {@code true} if the canvas was removed, {@code false} otherwise (which will happen if, for example, the
+ * canvas had not previously been added to the frame handler).
+ */
+ public synchronized boolean removeCanvas(final Canvas canvas) {
+ return _canvases.remove(canvas);
+ }
+
+ public void init() {
+ // TODO: this can lead to problems with canvases and updaters added after init() has been called once...
+ for (final Canvas canvas : _canvases) {
+ canvas.init();
+ }
+
+ for (final Updater updater : _updaters) {
+ updater.init();
+ }
+ }
+
+ public long getTimeoutSeconds() {
+ return _timeoutSeconds;
+ }
+
+ public void setTimeoutSeconds(final long timeoutSeconds) {
+ _timeoutSeconds = timeoutSeconds;
+ }
+
+ public Timer getTimer() {
+ return _timer;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java
new file mode 100644
index 0000000..5a0dc2d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/NativeCanvas.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.image.Image;
+
+public interface NativeCanvas extends Canvas {
+
+ /**
+ * <code>close</code> shutdowns and destroys any window contexts.
+ */
+ void close();
+
+ /**
+ * <code>isActive</code> returns true if the display is active.
+ *
+ * @return whether the display system is active.
+ */
+ boolean isActive();
+
+ /**
+ * <code>isClosing</code> notifies if the window is currently closing. This could be caused via the application
+ * itself or external interrupts such as alt-f4 etc.
+ *
+ * @return true if the window is closing, false otherwise.
+ */
+ boolean isClosing();
+
+ /**
+ * <code>setVSyncEnabled</code> attempts to enable or disable monitor vertical synchronization. The method is a
+ * "best attempt" to change the monitor vertical refresh synchronization, and is <b>not </b> guaranteed to be
+ * successful. This is dependent on OS.
+ *
+ * @param enabled
+ * <code>true</code> to synchronize, <code>false</code> to ignore synchronization
+ */
+ void setVSyncEnabled(boolean enabled);
+
+ /**
+ * Sets the title of the display system. This is usually reflected by the renderer as text in the menu bar.
+ *
+ * @param title
+ * The new display title.
+ */
+ void setTitle(String title);
+
+ /**
+ * Sets one or more icons for the Canvas.
+ * <p>
+ * As a reference for usual platforms on number of icons and their sizes:
+ * <ul>
+ * <li>On Windows you should supply at least one 16x16 image and one 32x32.</li>
+ * <li>Linux (and similar platforms) expect one 32x32 image.</li>
+ * <li>Mac OS X should be supplied one 128x128 image.</li>
+ * </ul>
+ * </p>
+ * <p>
+ * Images should be in format RGBA8888. If they are not ardor3d will try to convert them using ImageUtils. If that
+ * fails a <code>Ardor3dException</code> could be thrown.
+ * </p>
+ *
+ * @param iconImages
+ * Array of Images to be used as icons.
+ */
+ void setIcon(Image[] iconImages);
+
+ /**
+ * If running in windowed mode, move the window's position to the given display coordinates.
+ *
+ * @param locX
+ * @param locY
+ */
+ void moveWindowTo(int locX, int locY);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java
new file mode 100644
index 0000000..bc7bc68
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Scene.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.intersection.PickResults;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * Owns all the data that is related to the scene. This class should not really know anything about rendering or the
+ * screen, it's just the scene data.
+ */
+public interface Scene {
+ /**
+ *
+ * @param renderer
+ * @return true if a render occurred and we should swap the buffers.
+ */
+ @MainThread
+ boolean renderUnto(Renderer renderer);
+
+ /**
+ * A scene should be able to handle a pick execution as it is the only thing that has a complete picture of the
+ * scenegraph(s).
+ *
+ * @param pickRay
+ */
+ PickResults doPick(Ray3 pickRay);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java
new file mode 100644
index 0000000..c984f51
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/framework/Updater.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.framework;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.util.ReadOnlyTimer;
+
+/**
+ * The purpose of this class is to own the update phase and separate update logic from the view.
+ */
+public interface Updater {
+ @MainThread
+ public void init();
+
+ @MainThread
+ public void update(final ReadOnlyTimer timer);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java
new file mode 100644
index 0000000..28765af
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Image.java
@@ -0,0 +1,377 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Lists;
+
+/**
+ * <code>Image</code> defines a data format for a graphical image. The image is defined by a format, a height and width,
+ * and the image data. The width and height must be greater than 0. The data is contained in a byte buffer, and should
+ * be packed before creation of the image object.
+ *
+ */
+public class Image implements Serializable, Savable {
+
+ private static final long serialVersionUID = 1L;
+
+ // image attributes
+ protected ImageDataFormat _format = ImageDataFormat.RGBA;
+ protected PixelDataType _type = PixelDataType.UnsignedByte;
+ protected int _width, _height, _depth;
+ protected int[] _mipMapSizes;
+ protected List<ByteBuffer> _data;
+
+ /**
+ * Constructor instantiates a new <code>Image</code> object. All values are undefined.
+ */
+ public Image() {
+ _data = new ArrayList<ByteBuffer>(1);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Image</code> object. The attributes of the image are defined during
+ * construction.
+ *
+ * @param format
+ * the data format of the image. Must not be null.
+ * @param type
+ * the data type of the image. Must not be null.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data. Must not be null.
+ * @param mipMapSizes
+ * the array of mipmap sizes, or null for no mipmaps.
+ */
+ public Image(final ImageDataFormat format, final PixelDataType type, final int width, final int height,
+ final List<ByteBuffer> data, int[] mipMapSizes) {
+
+ if (mipMapSizes != null && mipMapSizes.length <= 1) {
+ mipMapSizes = null;
+ }
+
+ setDataFormat(format);
+ setDataType(type);
+ setData(data);
+ _width = width;
+ _height = height;
+ _depth = data.size();
+ _mipMapSizes = mipMapSizes;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Image</code> object. The attributes of the image are defined during
+ * construction.
+ *
+ * @param format
+ * the data format of the image. Must not be null.
+ * @param type
+ * the data type of the image. Must not be null.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data. Must not be null.
+ * @param mipMapSizes
+ * the array of mipmap sizes, or null for no mipmaps.
+ */
+ public Image(final ImageDataFormat format, final PixelDataType type, final int width, final int height,
+ final ByteBuffer data, final int[] mipMapSizes) {
+ this(format, type, width, height, Lists.newArrayList(data), mipMapSizes);
+ }
+
+ /**
+ * <code>setData</code> sets the data that makes up the image. This data is packed into an array of
+ * <code>ByteBuffer</code> objects.
+ *
+ * @param data
+ * the data that contains the image information. Must not be null.
+ */
+ public void setData(final List<ByteBuffer> data) {
+ if (data == null) {
+ throw new NullPointerException("data may not be null.");
+ }
+ _data = data;
+ }
+
+ /**
+ * <code>setData</code> sets the data that makes up the image. This data is packed into a single
+ * <code>ByteBuffer</code>.
+ *
+ * @param data
+ * the data that contains the image information.
+ */
+ public void setData(final ByteBuffer data) {
+ _data = Lists.newArrayList(data);
+ }
+
+ /**
+ * Adds the given buffer onto the current list of image data
+ *
+ * @param data
+ * the data that contains the image information.
+ */
+ public void addData(final ByteBuffer data) {
+ if (_data == null) {
+ _data = new ArrayList<ByteBuffer>(1);
+ }
+ _data.add(data);
+ }
+
+ public void setData(final int index, final ByteBuffer data) {
+ if (index >= 0) {
+ while (_data.size() <= index) {
+ _data.add(null);
+ }
+ _data.set(index, data);
+ } else {
+ throw new IllegalArgumentException("index must be greater than or equal to 0.");
+ }
+ }
+
+ /**
+ * Sets the mipmap data sizes stored in this image's data buffer. Mipmaps are stored sequentially, and the first
+ * mipmap is the main image data. To specify no mipmaps, pass null.
+ *
+ * @param mipMapSizes
+ * the mipmap sizes array, or null to indicate no mip maps.
+ */
+ public void setMipMapByteSizes(int[] mipMapSizes) {
+ if (mipMapSizes != null && mipMapSizes.length <= 1) {
+ mipMapSizes = null;
+ }
+
+ _mipMapSizes = mipMapSizes;
+ }
+
+ /**
+ * <code>setHeight</code> sets the height value of the image. It is typically a good idea to try to keep this as a
+ * multiple of 2.
+ *
+ * @param height
+ * the height of the image.
+ */
+ public void setHeight(final int height) {
+ _height = height;
+ }
+
+ /**
+ * <code>setDepth</code> sets the depth value of the image. It is typically a good idea to try to keep this as a
+ * multiple of 2. This is used for 3d images.
+ *
+ * @param depth
+ * the depth of the image.
+ */
+ public void setDepth(final int depth) {
+ _depth = depth;
+ }
+
+ /**
+ * <code>setWidth</code> sets the width value of the image. It is typically a good idea to try to keep this as a
+ * multiple of 2.
+ *
+ * @param width
+ * the width of the image.
+ */
+ public void setWidth(final int width) {
+ _width = width;
+ }
+
+ /**
+ * @param format
+ * the image data format.
+ * @throws NullPointerException
+ * if format is null
+ * @see ImageDataFormat
+ */
+ public void setDataFormat(final ImageDataFormat format) {
+ if (format == null) {
+ throw new NullPointerException("format may not be null.");
+ }
+
+ _format = format;
+ }
+
+ /**
+ * @return the image data format.
+ * @see ImageDataFormat
+ */
+ public ImageDataFormat getDataFormat() {
+ return _format;
+ }
+
+ /**
+ * @param type
+ * the image data type.
+ * @throws NullPointerException
+ * if type is null
+ * @see PixelDataType
+ */
+ public void setDataType(final PixelDataType type) {
+ if (type == null) {
+ throw new NullPointerException("type may not be null.");
+ }
+
+ _type = type;
+ }
+
+ /**
+ * @return the image data type.
+ * @see PixelDataType
+ */
+ public PixelDataType getDataType() {
+ return _type;
+ }
+
+ /**
+ * @return the width of this image.
+ */
+ public int getWidth() {
+ return _width;
+ }
+
+ /**
+ * @return the height of this image.
+ */
+ public int getHeight() {
+ return _height;
+ }
+
+ /**
+ * @return the depth of this image (used for 3d textures and 2d texture arrays)
+ */
+ public int getDepth() {
+ return _depth;
+ }
+
+ /**
+ * <code>getData</code> returns the data for this image. If the data is undefined, null will be returned.
+ *
+ * @return the data for this image.
+ */
+ public List<ByteBuffer> getData() {
+ return _data;
+ }
+
+ /**
+ * @return the number of individual data buffers or slices in this Image.
+ */
+ public int getDataSize() {
+ if (_data == null) {
+ return 0;
+ } else {
+ return _data.size();
+ }
+ }
+
+ /**
+ * <code>getData</code> returns the data for this image at a given index. If the data is undefined, null will be
+ * returned.
+ *
+ * @return the data for this image.
+ */
+ public ByteBuffer getData(final int index) {
+ if (_data.size() > index) {
+ return _data.get(index);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns whether the image data contains mipmaps.
+ *
+ * @return true if the image data contains mipmaps, false if not.
+ */
+ public boolean hasMipmaps() {
+ return _mipMapSizes != null;
+ }
+
+ /**
+ * Returns the mipmap sizes for this image.
+ *
+ * @return the mipmap sizes for this image.
+ */
+ public int[] getMipMapByteSizes() {
+ return _mipMapSizes;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Image)) {
+ return false;
+ }
+ final Image that = (Image) other;
+ if (getDataFormat() != that.getDataFormat()) {
+ return false;
+ }
+ if (getDataType() != that.getDataType()) {
+ return false;
+ }
+ if (getWidth() != that.getWidth()) {
+ return false;
+ }
+ if (getHeight() != that.getHeight()) {
+ return false;
+ }
+ if (!this.getData().equals(that.getData())) {
+ return false;
+ }
+ if (getMipMapByteSizes() != null && !Arrays.equals(getMipMapByteSizes(), that.getMipMapByteSizes())) {
+ return false;
+ }
+ if (getMipMapByteSizes() == null && that.getMipMapByteSizes() != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_format, "dataformat", ImageDataFormat.RGBA);
+ capsule.write(_type, "datatype", PixelDataType.UnsignedByte);
+ capsule.write(_width, "width", 0);
+ capsule.write(_height, "height", 0);
+ capsule.write(_depth, "depth", 0);
+ capsule.write(_mipMapSizes, "mipMapSizes", null);
+ capsule.writeByteBufferList(_data, "data", null);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _format = capsule.readEnum("dataformat", ImageDataFormat.class, ImageDataFormat.RGBA);
+ _type = capsule.readEnum("datatype", PixelDataType.class, PixelDataType.UnsignedByte);
+ _width = capsule.readInt("width", 0);
+ _height = capsule.readInt("height", 0);
+ _depth = capsule.readInt("depth", 0);
+ _mipMapSizes = capsule.readIntArray("mipMapSizes", null);
+ _data = capsule.readByteBufferList("data", null);
+ }
+
+ public Class<? extends Image> getClassTag() {
+ return this.getClass();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java
new file mode 100644
index 0000000..e1f8c7c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/ImageDataFormat.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+public enum ImageDataFormat {
+ RG(2, false, false), //
+ RGB(3, false, false), //
+ RGBA(4, false, true), //
+ BGR(3, false, false), //
+ BGRA(4, false, true), //
+ Luminance(1, false, false), //
+ LuminanceAlpha(2, false, true), //
+ Alpha(1, false, true), //
+ Intensity(1, false, false), //
+ Red(1, false, false), //
+ Green(1, false, false), //
+ Blue(1, false, false), //
+ StencilIndex(1, false, false), //
+ ColorIndex(1, false, false), //
+ Depth(1, false, false), //
+ PrecompressedDXT1(1, true, false), //
+ PrecompressedDXT1A(1, true, true), //
+ PrecompressedDXT3(2, true, true), //
+ PrecompressedDXT5(2, true, true), //
+ PrecompressedLATC_L(1, true, true), //
+ PrecompressedLATC_LA(2, true, true);
+
+ private final int _components;
+ private final boolean _compressed;
+ private final boolean _hasAlpha;
+
+ ImageDataFormat(final int components, final boolean isCompressed, final boolean hasAlpha) {
+ _components = components;
+ _compressed = isCompressed;
+ _hasAlpha = hasAlpha;
+ }
+
+ public int getComponents() {
+ return _components;
+ }
+
+ public boolean isCompressed() {
+ return _compressed;
+ }
+
+ public boolean hasAlpha() {
+ return _hasAlpha;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java
new file mode 100644
index 0000000..c465d4b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/PixelDataType.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+public enum PixelDataType {
+ UnsignedByte(1, null, null), //
+ Byte(1, null, null), //
+ UnsignedShort(2, null, null), //
+ Short(2, null, null), //
+ UnsignedInt(4, null, null), //
+ Int(4, null, null), //
+ Float(4, null, null), //
+ HalfFloat(2, null, null), //
+ UnsignedByte_3_3_2(null, 8, 3), //
+ UnsignedByte_2_3_3_Rev(null, 8, 3), //
+ UnsignedShort_5_6_5(null, 16, 3), //
+ UnsignedShort_5_6_5_Rev(null, 16, 3), //
+ UnsignedShort_4_4_4_4(null, 16, 4), //
+ UnsignedShort_4_4_4_4_Rev(null, 16, 4), //
+ UnsignedShort_5_5_5_1(null, 16, 4), //
+ UnsignedShort_1_5_5_5_Rev(null, 16, 4), //
+ UnsignedInt_8_8_8_8(null, 32, 4), //
+ UnsignedInt_8_8_8_8_Rev(null, 32, 4), //
+ UnsignedInt_10_10_10_2(null, 32, 4), //
+ UnsignedInt_2_10_10_10_Rev(null, 32, 4);
+
+ final Integer _bytesPerComponent;
+ final Integer _bytesPerPixel;
+ final Integer _components;
+
+ PixelDataType(final Integer bytesPerComponent, final Integer bytesPerPixel, final Integer components) {
+ _bytesPerComponent = bytesPerComponent;
+ _bytesPerPixel = bytesPerPixel;
+ _components = components;
+ }
+
+ public int getBytesPerPixel(final int components) {
+ if (_components != null && components != _components.intValue()) {
+ throw new IllegalArgumentException("invalid number of components for " + name());
+ }
+
+ if (_bytesPerPixel != null) {
+ return _bytesPerPixel.intValue();
+ } else {
+ return components * _bytesPerComponent;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java
new file mode 100644
index 0000000..add9847
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture.java
@@ -0,0 +1,1528 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector4;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.TextureKey;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * <code>Texture</code> defines a texture object to be used to display an image on a piece of geometry. The image to be
+ * displayed is defined by the <code>Image</code> class. All attributes required for texture mapping are contained
+ * within this class. This includes mipmapping if desired, magnificationFilter options, apply options and correction
+ * options. Default values are as follows: minificationFilter - NearestNeighborNoMipMaps, magnificationFilter -
+ * NearestNeighbor, wrap - EdgeClamp on S,T and R, apply - Modulate, environment - None.
+ *
+ * @see com.ardor3d.image.Image
+ */
+public abstract class Texture implements Savable {
+
+ public static boolean DEFAULT_STORE_IMAGE = Constants.storeSavableImages;
+
+ public enum Type {
+ /**
+ * One dimensional texture. (basically a line)
+ */
+ OneDimensional,
+ /**
+ * Two dimensional texture (default). A rectangle.
+ */
+ TwoDimensional,
+ /**
+ * Three dimensional texture. (A cube)
+ */
+ ThreeDimensional,
+ /**
+ * A set of 6 TwoDimensional textures arranged as faces of a cube facing inwards.
+ */
+ CubeMap,
+ /**
+ * A non-power of 2 texture. Does not support mipmapping. Only supports {@link WrapMode#Clamp},
+ * {@link WrapMode#EdgeClamp}, and {@link WrapMode#BorderClamp} wrap modes. Texture coordinates are not
+ * normalized [0, 1] but rather [0,w] and [0,h] for u and v respectively.
+ */
+ Rectangle;
+ }
+
+ public enum MinificationFilter {
+
+ /**
+ * Nearest neighbor interpolation is the fastest and crudest filtering method - it simply uses the color of the
+ * texel closest to the pixel center for the pixel color. While fast, this results in aliasing and shimmering
+ * during minification. (GL equivalent: GL_NEAREST)
+ */
+ NearestNeighborNoMipMaps(false),
+
+ /**
+ * In this method the four nearest texels to the pixel center are sampled (at texture level 0), and their colors
+ * are combined by weighted averages. Though smoother, without mipmaps it suffers the same aliasing and
+ * shimmering problems as nearest NearestNeighborNoMipMaps. (GL equivalent: GL_LINEAR)
+ */
+ BilinearNoMipMaps(false),
+
+ /**
+ * Same as NearestNeighborNoMipMaps except that instead of using samples from texture level 0, the closest
+ * mipmap level is chosen based on distance. This reduces the aliasing and shimmering significantly, but does
+ * not help with blockiness. (GL equivalent: GL_NEAREST_MIPMAP_NEAREST)
+ */
+ NearestNeighborNearestMipMap(true),
+
+ /**
+ * Same as BilinearNoMipMaps except that instead of using samples from texture level 0, the closest mipmap level
+ * is chosen based on distance. By using mipmapping we avoid the aliasing and shimmering problems of
+ * BilinearNoMipMaps. (GL equivalent: GL_LINEAR_MIPMAP_NEAREST)
+ */
+ BilinearNearestMipMap(true),
+
+ /**
+ * Similar to NearestNeighborNoMipMaps except that instead of using samples from texture level 0, a sample is
+ * chosen from each of the closest (by distance) two mipmap levels. A weighted average of these two samples is
+ * returned. (GL equivalent: GL_NEAREST_MIPMAP_LINEAR)
+ */
+ NearestNeighborLinearMipMap(true),
+
+ /**
+ * Trilinear filtering is a remedy to a common artifact seen in mipmapped bilinearly filtered images: an abrupt
+ * and very noticeable change in quality at boundaries where the renderer switches from one mipmap level to the
+ * next. Trilinear filtering solves this by doing a texture lookup and bilinear filtering on the two closest
+ * mipmap levels (one higher and one lower quality), and then linearly interpolating the results. This results
+ * in a smooth degradation of texture quality as distance from the viewer increases, rather than a series of
+ * sudden drops. Of course, closer than Level 0 there is only one mipmap level available, and the algorithm
+ * reverts to bilinear filtering (GL equivalent: GL_LINEAR_MIPMAP_LINEAR)
+ */
+ Trilinear(true);
+
+ private boolean _usesMipMapLevels;
+
+ private MinificationFilter(final boolean usesMipMapLevels) {
+ _usesMipMapLevels = usesMipMapLevels;
+ }
+
+ public boolean usesMipMapLevels() {
+ return _usesMipMapLevels;
+ }
+ }
+
+ public enum MagnificationFilter {
+
+ /**
+ * Nearest neighbor interpolation is the fastest and crudest filtering mode - it simply uses the color of the
+ * texel closest to the pixel center for the pixel color. While fast, this results in texture 'blockiness'
+ * during magnification. (GL equivalent: GL_NEAREST)
+ */
+ NearestNeighbor,
+
+ /**
+ * In this mode the four nearest texels to the pixel center are sampled (at the closest mipmap level), and their
+ * colors are combined by weighted average according to distance. This removes the 'blockiness' seen during
+ * magnification, as there is now a smooth gradient of color change from one texel to the next, instead of an
+ * abrupt jump as the pixel center crosses the texel boundary. (GL equivalent: GL_LINEAR)
+ */
+ Bilinear;
+
+ }
+
+ public enum WrapMode {
+ /**
+ * Only the fractional portion of the coordinate is considered.
+ */
+ Repeat,
+ /**
+ * Only the fractional portion of the coordinate is considered, but if the integer portion is odd, we'll use 1 -
+ * the fractional portion. (Introduced around OpenGL1.4) Falls back on Repeat if not supported.
+ */
+ MirroredRepeat,
+ /**
+ * coordinate will be clamped to [0,1]
+ */
+ Clamp,
+ /**
+ * mirrors and clamps the texture coordinate, where mirroring and clamping a value f computes:
+ * <code>mirrorClamp(f) = min(1, max(1/(2*N),
+ * abs(f)))</code> where N is the size of the one-, two-, or three-dimensional texture image in the direction of
+ * wrapping. (Introduced after OpenGL1.4) Falls back on Clamp if not supported.
+ */
+ MirrorClamp,
+ /**
+ * coordinate will be clamped to the range [-1/(2N), 1 + 1/(2N)] where N is the size of the texture in the
+ * direction of clamping. Falls back on Clamp if not supported.
+ */
+ BorderClamp,
+ /**
+ * Wrap mode MIRROR_CLAMP_TO_BORDER_EXT mirrors and clamps to border the texture coordinate, where mirroring and
+ * clamping to border a value f computes:
+ * <code>mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f)))</code> where N is the size of the one-,
+ * two-, or three-dimensional texture image in the direction of wrapping." (Introduced after OpenGL1.4) Falls
+ * back on BorderClamp if not supported.
+ */
+ MirrorBorderClamp,
+ /**
+ * coordinate will be clamped to the range [1/(2N), 1 - 1/(2N)] where N is the size of the texture in the
+ * direction of clamping. Falls back on Clamp if not supported.
+ */
+ EdgeClamp,
+ /**
+ * mirrors and clamps to edge the texture coordinate, where mirroring and clamping to edge a value f computes:
+ * <code>mirrorClampToEdge(f) = min(1-1/(2*N), max(1/(2*N), abs(f)))</code> where N is the size of the one-,
+ * two-, or three-dimensional texture image in the direction of wrapping. (Introduced after OpenGL1.4) Falls
+ * back on EdgeClamp if not supported.
+ */
+ MirrorEdgeClamp;
+ }
+
+ public enum WrapAxis {
+ /**
+ * S wrapping (u or "horizontal" wrap)
+ */
+ S,
+ /**
+ * T wrapping (v or "vertical" wrap)
+ */
+ T,
+ /**
+ * R wrapping (w or "depth" wrap)
+ */
+ R;
+ }
+
+ public enum ApplyMode {
+ /**
+ * Apply modifier that replaces the previous pixel color with the texture color.
+ */
+ Replace,
+ /**
+ * Apply modifier that replaces the color values of the pixel but makes use of the alpha values.
+ */
+ Decal,
+ /**
+ * Apply modifier multiples the color of the pixel with the texture color.
+ */
+ Modulate,
+ /**
+ * Apply modifier that interpolates the color of the pixel with a blend color using the texture color, such that
+ * the final color value is Cv = (1 - Ct) * Cf + BlendColor * Ct Where Ct is the color of the texture and Cf is
+ * the initial pixel color.
+ */
+ Blend,
+ /**
+ * Apply modifier combines two textures based on the combine parameters set on this texture.
+ */
+ Combine,
+ /**
+ * Apply modifier adds two textures.
+ */
+ Add;
+ }
+
+ /**
+ * Formula to use for texture coordinate generation
+ */
+ public enum EnvironmentalMapMode {
+ /**
+ * Use texture coordinates as they are. (Do not do texture coordinate generation.)
+ */
+ None,
+ /**
+ * TODO: add documentation
+ */
+ EyeLinear,
+ /**
+ * TODO: add documentation
+ */
+ ObjectLinear,
+ /**
+ * TODO: add documentation
+ */
+ SphereMap,
+ /**
+ * TODO: add documentation
+ */
+ NormalMap,
+ /**
+ * TODO: add documentation
+ */
+ ReflectionMap;
+ }
+
+ public enum CombinerFunctionRGB {
+ /** Arg0 */
+ Replace,
+ /** Arg0 * Arg1 */
+ Modulate,
+ /** Arg0 + Arg1 */
+ Add,
+ /** Arg0 + Arg1 - 0.5 */
+ AddSigned,
+ /** Arg0 * Arg2 + Arg1 * (1 - Arg2) */
+ Interpolate,
+ /** Arg0 - Arg1 */
+ Subtract,
+ /**
+ * 4 * ((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5)) [ result
+ * placed in R,G,B ]
+ */
+ Dot3RGB,
+ /**
+ * 4 * ((Arg0r - 0.5) * (Arg1r - 0.5) + (Arg0g - 0.5) * (Arg1g - 0.5) + (Arg0b - 0.5) * (Arg1b - 0.5)) [ result
+ * placed in R,G,B,A ]
+ */
+ Dot3RGBA;
+ }
+
+ public enum CombinerFunctionAlpha {
+ /** Arg0 */
+ Replace,
+ /** Arg0 * Arg1 */
+ Modulate,
+ /** Arg0 + Arg1 */
+ Add,
+ /** Arg0 + Arg1 - 0.5 */
+ AddSigned,
+ /** Arg0 * Arg2 + Arg1 * (1 - Arg2) */
+ Interpolate,
+ /** Arg0 - Arg1 */
+ Subtract;
+ }
+
+ public enum CombinerSource {
+ /**
+ * The incoming fragment color from the previous texture unit. When used on texture unit 0, this is the same as
+ * using PrimaryColor.
+ */
+ Previous,
+ /** The blend color set on this texture. */
+ Constant,
+ /** The incoming fragment color before any texturing is applied. */
+ PrimaryColor,
+ /** The current texture unit's bound texture. */
+ CurrentTexture,
+ /** The texture bound on texture unit 0. */
+ TextureUnit0,
+ /** The texture bound on texture unit 1. */
+ TextureUnit1,
+ /** The texture bound on texture unit 2. */
+ TextureUnit2,
+ /** The texture bound on texture unit 3. */
+ TextureUnit3,
+ /** The texture bound on texture unit 4. */
+ TextureUnit4,
+ /** The texture bound on texture unit 5. */
+ TextureUnit5,
+ /** The texture bound on texture unit 6. */
+ TextureUnit6,
+ /** The texture bound on texture unit 7. */
+ TextureUnit7,
+ /** The texture bound on texture unit 8. */
+ TextureUnit8,
+ /** The texture bound on texture unit 9. */
+ TextureUnit9,
+ /** The texture bound on texture unit 10. */
+ TextureUnit10,
+ /** The texture bound on texture unit 11. */
+ TextureUnit11,
+ /** The texture bound on texture unit 12. */
+ TextureUnit12,
+ /** The texture bound on texture unit 13. */
+ TextureUnit13,
+ /** The texture bound on texture unit 14. */
+ TextureUnit14,
+ /** The texture bound on texture unit 15. */
+ TextureUnit15,
+ /** The texture bound on texture unit 16. */
+ TextureUnit16,
+ /** The texture bound on texture unit 17. */
+ TextureUnit17,
+ /** The texture bound on texture unit 18. */
+ TextureUnit18,
+ /** The texture bound on texture unit 19. */
+ TextureUnit19,
+ /** The texture bound on texture unit 20. */
+ TextureUnit20,
+ /** The texture bound on texture unit 21. */
+ TextureUnit21,
+ /** The texture bound on texture unit 22. */
+ TextureUnit22,
+ /** The texture bound on texture unit 23. */
+ TextureUnit23,
+ /** The texture bound on texture unit 24. */
+ TextureUnit24,
+ /** The texture bound on texture unit 25. */
+ TextureUnit25,
+ /** The texture bound on texture unit 26. */
+ TextureUnit26,
+ /** The texture bound on texture unit 27. */
+ TextureUnit27,
+ /** The texture bound on texture unit 28. */
+ TextureUnit28,
+ /** The texture bound on texture unit 29. */
+ TextureUnit29,
+ /** The texture bound on texture unit 30. */
+ TextureUnit30,
+ /** The texture bound on texture unit 31. */
+ TextureUnit31;
+ }
+
+ public enum CombinerOperandRGB {
+ SourceColor, OneMinusSourceColor, SourceAlpha, OneMinusSourceAlpha;
+ }
+
+ public enum CombinerOperandAlpha {
+ SourceAlpha, OneMinusSourceAlpha;
+ }
+
+ public enum CombinerScale {
+ /** No scale (1.0x) */
+ One(1.0f),
+ /** 2.0x */
+ Two(2.0f),
+ /** 4.0x */
+ Four(4.0f);
+
+ private float scale;
+
+ private CombinerScale(final float scale) {
+ this.scale = scale;
+ }
+
+ public float floatValue() {
+ return scale;
+ }
+ }
+
+ /**
+ * The shadowing texture compare mode
+ */
+ public enum DepthTextureCompareMode {
+ /** Perform no shadow based comparsion */
+ None,
+ /** Perform a comparison between source depth and texture depth */
+ RtoTexture,
+ }
+
+ /**
+ * The shadowing texture compare function
+ */
+ public enum DepthTextureCompareFunc {
+ /** Outputs if the source depth is less than the texture depth */
+ LessThanEqual,
+ /** Outputs if the source depth is greater than the texture depth */
+ GreaterThanEqual
+ }
+
+ /**
+ * The type of depth texture translation to output
+ */
+ public enum DepthTextureMode {
+ /** Output luminance values based on the depth comparison */
+ Luminance,
+ /** Output alpha values based on the depth comparison */
+ Alpha,
+ /** Output intensity values based on the depth comparison */
+ Intensity
+ }
+
+ // texture attributes.
+ private Image _image = null;
+ private final ColorRGBA _constantColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA);
+ private final ColorRGBA _borderColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA);
+
+ private final Matrix4 _texMatrix = new Matrix4();
+
+ private float _anisotropicFilterPercent = 0.0f;
+ private float _lodBias = 0.0f;
+
+ private ApplyMode _apply = ApplyMode.Modulate;
+ private MinificationFilter _minificationFilter = MinificationFilter.NearestNeighborNoMipMaps;
+ private MagnificationFilter _magnificationFilter = MagnificationFilter.Bilinear;
+
+ private EnvironmentalMapMode _envMapMode = EnvironmentalMapMode.None;
+
+ private Vector4 _envPlaneS = null;
+ private Vector4 _envPlaneT = null;
+ private Vector4 _envPlaneR = null;
+ private Vector4 _envPlaneQ = null;
+
+ private boolean _hasBorder = false;
+
+ // The following will only used if apply is set to ApplyMode.Combine
+ private CombinerFunctionRGB _combineFuncRGB = CombinerFunctionRGB.Modulate;
+ private CombinerSource _combineSrc0RGB = CombinerSource.CurrentTexture;
+ private CombinerSource _combineSrc1RGB = CombinerSource.Previous;
+ private CombinerSource _combineSrc2RGB = CombinerSource.Constant;
+ private CombinerOperandRGB _combineOp0RGB = CombinerOperandRGB.SourceColor;
+ private CombinerOperandRGB _combineOp1RGB = CombinerOperandRGB.SourceColor;
+ private CombinerOperandRGB _combineOp2RGB = CombinerOperandRGB.SourceAlpha;
+ private CombinerScale _combineScaleRGB = CombinerScale.One;
+
+ private CombinerFunctionAlpha _combineFuncAlpha = CombinerFunctionAlpha.Modulate;
+ private CombinerSource _combineSrc0Alpha = CombinerSource.CurrentTexture;
+ private CombinerSource _combineSrc1Alpha = CombinerSource.Previous;
+ private CombinerSource _combineSrc2Alpha = CombinerSource.Constant;
+ private CombinerOperandAlpha _combineOp0Alpha = CombinerOperandAlpha.SourceAlpha;
+ private CombinerOperandAlpha _combineOp1Alpha = CombinerOperandAlpha.SourceAlpha;
+ private CombinerOperandAlpha _combineOp2Alpha = CombinerOperandAlpha.SourceAlpha;
+ private CombinerScale _combineScaleAlpha = CombinerScale.One;
+
+ private TextureKey _key = null;
+ private TextureStoreFormat _storeFormat = TextureStoreFormat.RGBA8;
+ private PixelDataType _rttPixelDataType = PixelDataType.UnsignedByte;
+ private transient boolean _storeImage = DEFAULT_STORE_IMAGE;
+
+ private DepthTextureCompareMode _depthCompareMode = DepthTextureCompareMode.None;
+ private DepthTextureCompareFunc _depthCompareFunc = DepthTextureCompareFunc.GreaterThanEqual;
+ private DepthTextureMode _depthMode = DepthTextureMode.Intensity;
+
+ private int _textureBaseLevel = 0;
+ private int _textureMaxLevel = -1;
+
+ /**
+ * Constructor instantiates a new <code>Texture</code> object with default attributes.
+ */
+ public Texture() {}
+
+ /**
+ * sets a color that is used with CombinerSource.Constant
+ *
+ * @param color
+ * the new constant color (the default is {@link ColorRGBA#BLACK_NO_ALPHA})
+ */
+ public void setConstantColor(final ReadOnlyColorRGBA color) {
+ _constantColor.set(color);
+ }
+
+ /**
+ * sets a color that is used with CombinerSource.Constant
+ *
+ * @param red
+ * @param green
+ * @param blue
+ * @param alpha
+ */
+ public void setConstantColor(final float red, final float green, final float blue, final float alpha) {
+ _constantColor.set(red, green, blue, alpha);
+ }
+
+ /**
+ * sets the color used when texture operations encounter the border of a texture.
+ *
+ * @param color
+ * the new border color (the default is {@link ColorRGBA#BLACK_NO_ALPHA})
+ */
+ public void setBorderColor(final ReadOnlyColorRGBA color) {
+ _borderColor.set(color);
+ }
+
+ /**
+ * sets the color used when texture operations encounter the border of a texture.
+ *
+ * @param red
+ * @param green
+ * @param blue
+ * @param alpha
+ */
+ public void setBorderColor(final float red, final float green, final float blue, final float alpha) {
+ _borderColor.set(red, green, blue, alpha);
+ }
+
+ /**
+ * @return the MinificationFilterMode of this texture.
+ */
+ public MinificationFilter getMinificationFilter() {
+ return _minificationFilter;
+ }
+
+ /**
+ * @param minificationFilter
+ * the new MinificationFilterMode for this texture.
+ * @throws IllegalArgumentException
+ * if minificationFilter is null
+ */
+ public void setMinificationFilter(final MinificationFilter minificationFilter) {
+ if (minificationFilter == null) {
+ throw new IllegalArgumentException("minificationFilter can not be null.");
+ }
+ _minificationFilter = minificationFilter;
+ }
+
+ /**
+ * @return the MagnificationFilterMode of this texture.
+ */
+ public MagnificationFilter getMagnificationFilter() {
+ return _magnificationFilter;
+ }
+
+ /**
+ * @param magnificationFilter
+ * the new MagnificationFilter for this texture.
+ * @throws IllegalArgumentException
+ * if magnificationFilter is null
+ */
+ public void setMagnificationFilter(final MagnificationFilter magnificationFilter) {
+ if (magnificationFilter == null) {
+ throw new IllegalArgumentException("magnificationFilter can not be null.");
+ }
+ _magnificationFilter = magnificationFilter;
+ }
+
+ /**
+ * <code>setApply</code> sets the apply mode for this texture.
+ *
+ * @param apply
+ * the apply mode for this texture.
+ * @throws IllegalArgumentException
+ * if apply is null
+ */
+ public void setApply(final ApplyMode apply) {
+ if (apply == null) {
+ throw new IllegalArgumentException("apply can not be null.");
+ }
+ _apply = apply;
+ }
+
+ /**
+ * <code>setImage</code> sets the image object that defines the texture.
+ *
+ * @param image
+ * the image that defines the texture.
+ */
+ public void setImage(final Image image) {
+ _image = image;
+ setDirty();
+ }
+
+ /**
+ * @param glContext
+ * the object representing the OpenGL context this texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the texture id of this texture in the given context. If the texture is not found in the given context, 0
+ * is returned.
+ */
+ public int getTextureIdForContext(final Object glContext) {
+ return _key.getTextureIdForContext(glContext);
+ }
+
+ /**
+ * @param glContext
+ * the object representing the OpenGL context this texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the texture id of this texture in the given context as an Integer object. If the texture is not found in
+ * the given context, a 0 integer is returned.
+ */
+ public Integer getTextureIdForContextAsInteger(final Object glContext) {
+ return _key.getTextureIdForContext(glContext);
+ }
+
+ /**
+ * Sets the id for this texture in regards to the given OpenGL context.
+ *
+ * @param glContext
+ * the object representing the OpenGL context this texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @param textureId
+ * the texture id of this texture. To be valid, this must be greater than 0.
+ * @throws IllegalArgumentException
+ * if textureId is less than or equal to 0.
+ */
+ public void setTextureIdForContext(final Object glContext, final int textureId) {
+ _key.setTextureIdForContext(glContext, textureId);
+ }
+
+ /**
+ * <p>
+ * Removes any texture id for this texture for the given OpenGL context.
+ * </p>
+ * <p>
+ * Note: This does not remove the texture from the card and is provided for use by code that does remove textures
+ * from the card.
+ * </p>
+ *
+ * @param glContext
+ * the object representing the OpenGL context this texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ */
+ public void removeFromIdCache(final Object glContext) {
+ _key.removeFromIdCache(glContext);
+ }
+
+ /**
+ * @return the image data that makes up this texture. If no image data has been set, this will return null.
+ */
+ public Image getImage() {
+ return _image;
+ }
+
+ /**
+ * @return the apply mode of the texture.
+ */
+ public ApplyMode getApply() {
+ return _apply;
+ }
+
+ /**
+ * @return the color set to be used with CombinerSource.Constant for this texture (as applicable). (the default is
+ * {@link ColorRGBA#BLACK_NO_ALPHA})
+ */
+ public ReadOnlyColorRGBA getConstantColor() {
+ return _constantColor;
+ }
+
+ /**
+ * @return the color to be used for border operations. (the default is {@link ColorRGBA#BLACK_NO_ALPHA})
+ */
+ public ReadOnlyColorRGBA getBorderColor() {
+ return _borderColor;
+ }
+
+ /**
+ * Sets the wrap mode of this texture for a particular axis.
+ *
+ * @param axis
+ * the texture axis to define a wrapmode on.
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if axis or mode are null or invalid for this type of texture
+ */
+ public abstract void setWrap(WrapAxis axis, WrapMode mode);
+
+ /**
+ * Sets the wrap mode of this texture for all axis.
+ *
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if mode is null or invalid for this type of texture
+ */
+ public abstract void setWrap(WrapMode mode);
+
+ /**
+ * @param axis
+ * the axis to return for
+ * @return the wrap mode for the given coordinate axis on this texture.
+ * @throws IllegalArgumentException
+ * if axis is null or invalid for this type of texture
+ */
+ public abstract WrapMode getWrap(WrapAxis axis);
+
+ /**
+ * @return the {@link Type} enum value of this Texture object.
+ */
+ public abstract Type getType();
+
+ /**
+ * @return the combineFuncRGB.
+ */
+ public CombinerFunctionRGB getCombineFuncRGB() {
+ return _combineFuncRGB;
+ }
+
+ /**
+ * @param combineFuncRGB
+ * The combineFuncRGB to set.
+ * @throws IllegalArgumentException
+ * if combineFuncRGB is null
+ */
+ public void setCombineFuncRGB(final CombinerFunctionRGB combineFuncRGB) {
+ if (combineFuncRGB == null) {
+ throw new IllegalArgumentException("invalid CombinerFunctionRGB: null");
+ }
+ _combineFuncRGB = combineFuncRGB;
+ }
+
+ /**
+ * @return Returns the combineOp0Alpha.
+ */
+ public CombinerOperandAlpha getCombineOp0Alpha() {
+ return _combineOp0Alpha;
+ }
+
+ /**
+ * @param combineOp0Alpha
+ * The combineOp0Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineOp0Alpha is null
+ */
+ public void setCombineOp0Alpha(final CombinerOperandAlpha combineOp0Alpha) {
+ if (combineOp0Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandAlpha: null");
+ }
+
+ _combineOp0Alpha = combineOp0Alpha;
+ }
+
+ /**
+ * @return Returns the combineOp0RGB.
+ */
+ public CombinerOperandRGB getCombineOp0RGB() {
+ return _combineOp0RGB;
+ }
+
+ /**
+ * @param combineOp0RGB
+ * The combineOp0RGB to set.
+ * @throws IllegalArgumentException
+ * if combineOp0RGB is null
+ */
+ public void setCombineOp0RGB(final CombinerOperandRGB combineOp0RGB) {
+ if (combineOp0RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandRGB: null");
+ }
+ _combineOp0RGB = combineOp0RGB;
+ }
+
+ /**
+ * @return Returns the combineOp1Alpha.
+ */
+ public CombinerOperandAlpha getCombineOp1Alpha() {
+ return _combineOp1Alpha;
+ }
+
+ /**
+ * @param combineOp1Alpha
+ * The combineOp1Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineOp1Alpha is null
+ */
+ public void setCombineOp1Alpha(final CombinerOperandAlpha combineOp1Alpha) {
+ if (combineOp1Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandAlpha: null");
+ }
+ _combineOp1Alpha = combineOp1Alpha;
+ }
+
+ /**
+ * @return Returns the combineOp1RGB.
+ */
+ public CombinerOperandRGB getCombineOp1RGB() {
+ return _combineOp1RGB;
+ }
+
+ /**
+ * @param combineOp1RGB
+ * The combineOp1RGB to set.
+ * @throws IllegalArgumentException
+ * if combineOp1RGB is null
+ */
+ public void setCombineOp1RGB(final CombinerOperandRGB combineOp1RGB) {
+ if (combineOp1RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandRGB: null");
+ }
+ _combineOp1RGB = combineOp1RGB;
+ }
+
+ /**
+ * @return Returns the combineOp2Alpha.
+ */
+ public CombinerOperandAlpha getCombineOp2Alpha() {
+ return _combineOp2Alpha;
+ }
+
+ /**
+ * @param combineOp2Alpha
+ * The combineOp2Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineOp2Alpha is null
+ */
+ public void setCombineOp2Alpha(final CombinerOperandAlpha combineOp2Alpha) {
+ if (combineOp2Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandAlpha: null");
+ }
+ _combineOp2Alpha = combineOp2Alpha;
+ }
+
+ /**
+ * @return Returns the combineOp2RGB.
+ */
+ public CombinerOperandRGB getCombineOp2RGB() {
+ return _combineOp2RGB;
+ }
+
+ /**
+ * @param combineOp2RGB
+ * The combineOp2RGB to set.
+ * @throws IllegalArgumentException
+ * if combineOp2RGB is null
+ */
+ public void setCombineOp2RGB(final CombinerOperandRGB combineOp2RGB) {
+ if (combineOp2RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerOperandRGB: null");
+ }
+ _combineOp2RGB = combineOp2RGB;
+ }
+
+ /**
+ * @return Returns the combineScaleAlpha.
+ */
+ public CombinerScale getCombineScaleAlpha() {
+ return _combineScaleAlpha;
+ }
+
+ /**
+ * @param combineScaleAlpha
+ * The combineScaleAlpha to set.
+ * @throws IllegalArgumentException
+ * if combineScaleAlpha is null
+ */
+ public void setCombineScaleAlpha(final CombinerScale combineScaleAlpha) {
+ if (combineScaleAlpha == null) {
+ throw new IllegalArgumentException("invalid CombinerScale: null");
+ }
+ _combineScaleAlpha = combineScaleAlpha;
+ }
+
+ /**
+ * @return Returns the combineScaleRGB.
+ */
+ public CombinerScale getCombineScaleRGB() {
+ return _combineScaleRGB;
+ }
+
+ /**
+ * @param combineScaleRGB
+ * The combineScaleRGB to set.
+ * @throws IllegalArgumentException
+ * if combineScaleRGB is null
+ */
+ public void setCombineScaleRGB(final CombinerScale combineScaleRGB) {
+ if (combineScaleRGB == null) {
+ throw new IllegalArgumentException("invalid CombinerScale: null");
+ }
+ _combineScaleRGB = combineScaleRGB;
+ }
+
+ /**
+ * @return Returns the combineSrc0Alpha.
+ */
+ public CombinerSource getCombineSrc0Alpha() {
+ return _combineSrc0Alpha;
+ }
+
+ /**
+ * @param combineSrc0Alpha
+ * The combineSrc0Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineSrc0Alpha is null
+ */
+ public void setCombineSrc0Alpha(final CombinerSource combineSrc0Alpha) {
+ if (combineSrc0Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc0Alpha = combineSrc0Alpha;
+ }
+
+ /**
+ * @return Returns the combineSrc0RGB.
+ */
+ public CombinerSource getCombineSrc0RGB() {
+ return _combineSrc0RGB;
+ }
+
+ /**
+ * @param combineSrc0RGB
+ * The combineSrc0RGB to set.
+ * @throws IllegalArgumentException
+ * if combineSrc0RGB is null
+ */
+ public void setCombineSrc0RGB(final CombinerSource combineSrc0RGB) {
+ if (combineSrc0RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc0RGB = combineSrc0RGB;
+ }
+
+ /**
+ * @return Returns the combineSrc1Alpha.
+ */
+ public CombinerSource getCombineSrc1Alpha() {
+ return _combineSrc1Alpha;
+ }
+
+ /**
+ * @param combineSrc1Alpha
+ * The combineSrc1Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineSrc1Alpha is null
+ */
+ public void setCombineSrc1Alpha(final CombinerSource combineSrc1Alpha) {
+ if (combineSrc1Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc1Alpha = combineSrc1Alpha;
+ }
+
+ /**
+ * @return Returns the combineSrc1RGB.
+ */
+ public CombinerSource getCombineSrc1RGB() {
+ return _combineSrc1RGB;
+ }
+
+ /**
+ * @param combineSrc1RGB
+ * The combineSrc1RGB to set.
+ * @throws IllegalArgumentException
+ * if combineSrc1RGB is null
+ */
+ public void setCombineSrc1RGB(final CombinerSource combineSrc1RGB) {
+ if (combineSrc1RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc1RGB = combineSrc1RGB;
+ }
+
+ /**
+ * @return Returns the combineSrc2Alpha.
+ */
+ public CombinerSource getCombineSrc2Alpha() {
+ return _combineSrc2Alpha;
+ }
+
+ /**
+ * @param combineSrc2Alpha
+ * The combineSrc2Alpha to set.
+ * @throws IllegalArgumentException
+ * if combineSrc2Alpha is null
+ */
+ public void setCombineSrc2Alpha(final CombinerSource combineSrc2Alpha) {
+ if (combineSrc2Alpha == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc2Alpha = combineSrc2Alpha;
+ }
+
+ /**
+ * @return Returns the combineSrc2RGB.
+ */
+ public CombinerSource getCombineSrc2RGB() {
+ return _combineSrc2RGB;
+ }
+
+ /**
+ * @param combineSrc2RGB
+ * The combineSrc2RGB to set.
+ * @throws IllegalArgumentException
+ * if combineSrc2RGB is null
+ */
+ public void setCombineSrc2RGB(final CombinerSource combineSrc2RGB) {
+ if (combineSrc2RGB == null) {
+ throw new IllegalArgumentException("invalid CombinerSource: null");
+ }
+ _combineSrc2RGB = combineSrc2RGB;
+ }
+
+ /**
+ * @return Returns the combineFuncAlpha.
+ */
+ public CombinerFunctionAlpha getCombineFuncAlpha() {
+ return _combineFuncAlpha;
+ }
+
+ /**
+ * @param combineFuncAlpha
+ * The combineFuncAlpha to set.
+ * @throws IllegalArgumentException
+ * if combineFuncAlpha is null
+ */
+ public void setCombineFuncAlpha(final CombinerFunctionAlpha combineFuncAlpha) {
+ if (combineFuncAlpha == null) {
+ throw new IllegalArgumentException("invalid CombinerFunctionAlpha: null");
+ }
+ _combineFuncAlpha = combineFuncAlpha;
+ }
+
+ /**
+ * @param envMapMode
+ * @throws IllegalArgumentException
+ * if envMapMode is null
+ */
+ public void setEnvironmentalMapMode(final EnvironmentalMapMode envMapMode) {
+ if (envMapMode == null) {
+ throw new IllegalArgumentException("invalid EnvironmentalMapMode: null");
+ }
+ _envMapMode = envMapMode;
+ }
+
+ public EnvironmentalMapMode getEnvironmentalMapMode() {
+ return _envMapMode;
+ }
+
+ public ReadOnlyVector4 getEnvPlaneS() {
+ return _envPlaneS;
+ }
+
+ public void setEnvPlaneS(final ReadOnlyVector4 plane) {
+ if (plane == null) {
+ _envPlaneS = null;
+ return;
+ } else if (_envPlaneS == null) {
+ _envPlaneS = new Vector4(plane);
+ } else {
+ _envPlaneS.set(plane);
+ }
+ }
+
+ public ReadOnlyVector4 getEnvPlaneT() {
+ return _envPlaneT;
+ }
+
+ public void setEnvPlaneT(final ReadOnlyVector4 plane) {
+ if (plane == null) {
+ _envPlaneT = null;
+ return;
+ } else if (_envPlaneT == null) {
+ _envPlaneT = new Vector4(plane);
+ } else {
+ _envPlaneT.set(plane);
+ }
+ }
+
+ public ReadOnlyVector4 getEnvPlaneR() {
+ return _envPlaneR;
+ }
+
+ public void setEnvPlaneR(final ReadOnlyVector4 plane) {
+ if (plane == null) {
+ _envPlaneR = null;
+ return;
+ } else if (_envPlaneR == null) {
+ _envPlaneR = new Vector4(plane);
+ } else {
+ _envPlaneR.set(plane);
+ }
+ }
+
+ public ReadOnlyVector4 getEnvPlaneQ() {
+ return _envPlaneQ;
+ }
+
+ public void setEnvPlaneQ(final ReadOnlyVector4 plane) {
+ if (plane == null) {
+ _envPlaneQ = null;
+ return;
+ } else if (_envPlaneQ == null) {
+ _envPlaneQ = new Vector4(plane);
+ } else {
+ _envPlaneQ.set(plane);
+ }
+ }
+
+ /**
+ * @return the anisotropic filtering level for this texture as a percentage (0.0 - 1.0)
+ */
+ public float getAnisotropicFilterPercent() {
+ return _anisotropicFilterPercent;
+ }
+
+ /**
+ * @param percent
+ * the anisotropic filtering level for this texture as a percentage (0.0 - 1.0)
+ */
+ public void setAnisotropicFilterPercent(float percent) {
+ if (percent > 1.0f) {
+ percent = 1.0f;
+ } else if (percent < 0.0f) {
+ percent = 0.0f;
+ }
+ _anisotropicFilterPercent = percent;
+ }
+
+ /**
+ * @return the lodBias for this texture
+ */
+ public float getLodBias() {
+ return _lodBias;
+ }
+
+ /**
+ * @param bias
+ * the lod bias for this texture. The default is 0.
+ */
+ public void setLodBias(final float bias) {
+ _lodBias = bias;
+ }
+
+ public void setTextureKey(final TextureKey tkey) {
+ _key = tkey;
+ }
+
+ public TextureKey getTextureKey() {
+ return _key;
+ }
+
+ public void setTextureStoreFormat(final TextureStoreFormat storeFormat) {
+ _storeFormat = storeFormat;
+ }
+
+ public void setRenderedTexturePixelDataType(final PixelDataType type) {
+ _rttPixelDataType = type;
+ }
+
+ public PixelDataType getRenderedTexturePixelDataType() {
+ return _rttPixelDataType;
+ }
+
+ public TextureStoreFormat getTextureStoreFormat() {
+ return _storeFormat;
+ }
+
+ public boolean isStoreImage() {
+ return _storeImage;
+ }
+
+ public void setStoreImage(final boolean store) {
+ _storeImage = store;
+ }
+
+ public boolean hasBorder() {
+ return _hasBorder;
+ }
+
+ public void setHasBorder(final boolean hasBorder) {
+ _hasBorder = hasBorder;
+ }
+
+ /**
+ * Get the depth texture compare function
+ *
+ * @return The depth texture compare function
+ */
+ public DepthTextureCompareFunc getDepthCompareFunc() {
+ return _depthCompareFunc;
+ }
+
+ /**
+ * Set the depth texture compare function
+ *
+ * param depthCompareFunc The depth texture compare function
+ */
+ public void setDepthCompareFunc(final DepthTextureCompareFunc depthCompareFunc) {
+ _depthCompareFunc = depthCompareFunc;
+ }
+
+ /**
+ * Get the depth texture apply mode
+ *
+ * @return The depth texture apply mode
+ */
+ public DepthTextureMode getDepthMode() {
+ return _depthMode;
+ }
+
+ /**
+ * Set the depth texture apply mode
+ *
+ * @param depthMode
+ * The depth texture apply mode
+ */
+ public void setDepthMode(final DepthTextureMode depthMode) {
+ _depthMode = depthMode;
+ }
+
+ /**
+ * Get the depth texture compare mode
+ *
+ * @return The depth texture compare mode
+ */
+ public DepthTextureCompareMode getDepthCompareMode() {
+ return _depthCompareMode;
+ }
+
+ /**
+ * Set the depth texture compare mode
+ *
+ * @param depthCompareMode
+ * The depth texture compare mode
+ */
+ public void setDepthCompareMode(final DepthTextureCompareMode depthCompareMode) {
+ _depthCompareMode = depthCompareMode;
+ }
+
+ public void setDirty() {
+ if (_key != null) {
+ _key.setDirty();
+ }
+ }
+
+ public boolean isDirty(final Object glContext) {
+ return _key.isDirty(glContext);
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Texture)) {
+ return false;
+ }
+
+ final Texture that = (Texture) other;
+ if (getImage() != null && !getImage().equals(that.getImage())) {
+ return false;
+ }
+ if (getImage() == null && that.getImage() != null) {
+ return false;
+ }
+ if (getAnisotropicFilterPercent() != that.getAnisotropicFilterPercent()) {
+ return false;
+ }
+ if (getApply() != that.getApply()) {
+ return false;
+ }
+ if (getCombineFuncAlpha() != that.getCombineFuncAlpha()) {
+ return false;
+ }
+ if (getCombineFuncRGB() != that.getCombineFuncRGB()) {
+ return false;
+ }
+ if (getCombineOp0Alpha() != that.getCombineOp0Alpha()) {
+ return false;
+ }
+ if (getCombineOp1RGB() != that.getCombineOp1RGB()) {
+ return false;
+ }
+ if (getCombineOp2Alpha() != that.getCombineOp2Alpha()) {
+ return false;
+ }
+ if (getCombineOp2RGB() != that.getCombineOp2RGB()) {
+ return false;
+ }
+ if (getCombineScaleAlpha() != that.getCombineScaleAlpha()) {
+ return false;
+ }
+ if (getCombineScaleRGB() != that.getCombineScaleRGB()) {
+ return false;
+ }
+ if (getCombineSrc0Alpha() != that.getCombineSrc0Alpha()) {
+ return false;
+ }
+ if (getCombineSrc0RGB() != that.getCombineSrc0RGB()) {
+ return false;
+ }
+ if (getCombineSrc1Alpha() != that.getCombineSrc1Alpha()) {
+ return false;
+ }
+ if (getCombineSrc1RGB() != that.getCombineSrc1RGB()) {
+ return false;
+ }
+ if (getCombineSrc2Alpha() != that.getCombineSrc2Alpha()) {
+ return false;
+ }
+ if (getCombineSrc2RGB() != that.getCombineSrc2RGB()) {
+ return false;
+ }
+ if (getEnvironmentalMapMode() != that.getEnvironmentalMapMode()) {
+ return false;
+ }
+ if (getMagnificationFilter() != that.getMagnificationFilter()) {
+ return false;
+ }
+ if (getMinificationFilter() != that.getMinificationFilter()) {
+ return false;
+ }
+ if (!_constantColor.equals(that._constantColor)) {
+ return false;
+ }
+ if (!_borderColor.equals(that._borderColor)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public abstract Texture createSimpleClone();
+
+ /**
+ * Retrieve a basic clone of this Texture (ie, clone everything but the image data, which is shared)
+ *
+ * @return Texture
+ */
+ public Texture createSimpleClone(final Texture rVal) {
+ rVal.setAnisotropicFilterPercent(_anisotropicFilterPercent);
+ rVal.setApply(_apply);
+ rVal.setConstantColor(_constantColor);
+ rVal.setBorderColor(_borderColor);
+ rVal.setCombineFuncAlpha(_combineFuncAlpha);
+ rVal.setCombineFuncRGB(_combineFuncRGB);
+ rVal.setCombineOp0Alpha(_combineOp0Alpha);
+ rVal.setCombineOp0RGB(_combineOp0RGB);
+ rVal.setCombineOp1Alpha(_combineOp1Alpha);
+ rVal.setCombineOp1RGB(_combineOp1RGB);
+ rVal.setCombineOp2Alpha(_combineOp2Alpha);
+ rVal.setCombineOp2RGB(_combineOp2RGB);
+ rVal.setCombineScaleAlpha(_combineScaleAlpha);
+ rVal.setCombineScaleRGB(_combineScaleRGB);
+ rVal.setCombineSrc0Alpha(_combineSrc0Alpha);
+ rVal.setCombineSrc0RGB(_combineSrc0RGB);
+ rVal.setCombineSrc1Alpha(_combineSrc1Alpha);
+ rVal.setCombineSrc1RGB(_combineSrc1RGB);
+ rVal.setCombineSrc2Alpha(_combineSrc2Alpha);
+ rVal.setCombineSrc2RGB(_combineSrc2RGB);
+ rVal.setDepthCompareFunc(_depthCompareFunc);
+ rVal.setDepthCompareMode(_depthCompareMode);
+ rVal.setDepthMode(_depthMode);
+ rVal.setEnvironmentalMapMode(_envMapMode);
+ rVal.setEnvPlaneS(_envPlaneS);
+ rVal.setEnvPlaneT(_envPlaneT);
+ rVal.setEnvPlaneR(_envPlaneR);
+ rVal.setEnvPlaneQ(_envPlaneQ);
+ rVal.setHasBorder(_hasBorder);
+ rVal.setTextureStoreFormat(_storeFormat);
+ rVal.setRenderedTexturePixelDataType(_rttPixelDataType);
+ rVal.setImage(_image); // NOT CLONED.
+ rVal.setLodBias(_lodBias);
+ rVal.setMinificationFilter(_minificationFilter);
+ rVal.setMagnificationFilter(_magnificationFilter);
+ rVal.setStoreImage(_storeImage);
+ rVal.setTextureMatrix(_texMatrix);
+ if (getTextureKey() != null) {
+ rVal.setTextureKey(getTextureKey());
+ }
+ return rVal;
+ }
+
+ public ReadOnlyMatrix4 getTextureMatrix() {
+ return _texMatrix;
+ }
+
+ public void setTextureMatrix(final ReadOnlyMatrix4 matrix) {
+ _texMatrix.set(matrix);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ if (_storeImage) {
+ capsule.write(_image, "image", null);
+ }
+ capsule.write(_constantColor, "constantColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA));
+ capsule.write(_borderColor, "borderColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA));
+ capsule.write(_texMatrix, "texMatrix", new Matrix4(Matrix4.IDENTITY));
+ capsule.write(_hasBorder, "hasBorder", false);
+ capsule.write(_anisotropicFilterPercent, "anisotropicFilterPercent", 0.0f);
+ capsule.write(_lodBias, "lodBias", 0.0f);
+ capsule.write(_minificationFilter, "minificationFilter", MinificationFilter.NearestNeighborNoMipMaps);
+ capsule.write(_magnificationFilter, "magnificationFilter", MagnificationFilter.Bilinear);
+ capsule.write(_apply, "apply", ApplyMode.Modulate);
+ capsule.write(_envMapMode, "envMapMode", EnvironmentalMapMode.None);
+ capsule.write(_envPlaneS, "envPlaneS", null);
+ capsule.write(_envPlaneT, "envPlaneT", null);
+ capsule.write(_envPlaneR, "envPlaneR", null);
+ capsule.write(_envPlaneQ, "envPlaneQ", null);
+ capsule.write(_combineFuncRGB, "combineFuncRGB", CombinerFunctionRGB.Replace);
+ capsule.write(_combineFuncAlpha, "combineFuncAlpha", CombinerFunctionAlpha.Replace);
+ capsule.write(_combineSrc0RGB, "combineSrc0RGB", CombinerSource.CurrentTexture);
+ capsule.write(_combineSrc1RGB, "combineSrc1RGB", CombinerSource.Previous);
+ capsule.write(_combineSrc2RGB, "combineSrc2RGB", CombinerSource.Constant);
+ capsule.write(_combineSrc0Alpha, "combineSrc0Alpha", CombinerSource.CurrentTexture);
+ capsule.write(_combineSrc1Alpha, "combineSrc1Alpha", CombinerSource.Previous);
+ capsule.write(_combineSrc2Alpha, "combineSrc2Alpha", CombinerSource.Constant);
+ capsule.write(_combineOp0RGB, "combineOp0RGB", CombinerOperandRGB.SourceColor);
+ capsule.write(_combineOp1RGB, "combineOp1RGB", CombinerOperandRGB.SourceColor);
+ capsule.write(_combineOp2RGB, "combineOp2RGB", CombinerOperandRGB.SourceAlpha);
+ capsule.write(_combineOp0Alpha, "combineOp0Alpha", CombinerOperandAlpha.SourceAlpha);
+ capsule.write(_combineOp1Alpha, "combineOp1Alpha", CombinerOperandAlpha.SourceAlpha);
+ capsule.write(_combineOp2Alpha, "combineOp2Alpha", CombinerOperandAlpha.SourceAlpha);
+ capsule.write(_combineScaleRGB, "combineScaleRGB", CombinerScale.One);
+ capsule.write(_combineScaleAlpha, "combineScaleAlpha", CombinerScale.One);
+ capsule.write(_storeFormat, "storeFormat", TextureStoreFormat.RGBA8);
+ capsule.write(_rttPixelDataType, "rttPixelDataType", PixelDataType.UnsignedByte);
+ capsule.write(_key, "textureKey", null);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _minificationFilter = capsule.readEnum("minificationFilter", MinificationFilter.class,
+ MinificationFilter.NearestNeighborNoMipMaps);
+ _image = (Image) capsule.readSavable("image", null);
+
+ // pull our key, if exists
+ final TextureKey key = (TextureKey) capsule.readSavable("textureKey", null);
+ if (key != null) {
+ _key = TextureKey.getKey(key.getSource(), key.isFlipped(), key.getFormat(), key.getId(),
+ key.getMinificationFilter());
+ } else {
+ // none set, so pop in a generated key
+ _key = TextureKey.getRTTKey(_minificationFilter);
+ }
+
+ // pull texture image from resource, if possible.
+ if (_image == null && _key != null && _key.getSource() != null) {
+ TextureManager.loadFromKey(_key, null, this);
+ }
+
+ _constantColor.set((ColorRGBA) capsule.readSavable("constantColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA)));
+ _borderColor.set((ColorRGBA) capsule.readSavable("borderColor", new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA)));
+ _texMatrix.set((Matrix4) capsule.readSavable("texMatrix", new Matrix4(Matrix4.IDENTITY)));
+ _hasBorder = capsule.readBoolean("hasBorder", false);
+ _anisotropicFilterPercent = capsule.readFloat("anisotropicFilterPercent", 0.0f);
+ _lodBias = capsule.readFloat("lodBias", 0.0f);
+ _magnificationFilter = capsule.readEnum("magnificationFilter", MagnificationFilter.class,
+ MagnificationFilter.Bilinear);
+ _apply = capsule.readEnum("apply", ApplyMode.class, ApplyMode.Modulate);
+ _envMapMode = capsule.readEnum("envMapMode", EnvironmentalMapMode.class, EnvironmentalMapMode.None);
+ _envPlaneS = (Vector4) capsule.readSavable("envPlaneS", null);
+ _envPlaneT = (Vector4) capsule.readSavable("envPlaneT", null);
+ _envPlaneR = (Vector4) capsule.readSavable("envPlaneR", null);
+ _envPlaneQ = (Vector4) capsule.readSavable("envPlaneQ", null);
+ _combineFuncRGB = capsule.readEnum("combineFuncRGB", CombinerFunctionRGB.class, CombinerFunctionRGB.Replace);
+ _combineFuncAlpha = capsule.readEnum("combineFuncAlpha", CombinerFunctionAlpha.class,
+ CombinerFunctionAlpha.Replace);
+ _combineSrc0RGB = capsule.readEnum("combineSrc0RGB", CombinerSource.class, CombinerSource.CurrentTexture);
+ _combineSrc1RGB = capsule.readEnum("combineSrc1RGB", CombinerSource.class, CombinerSource.Previous);
+ _combineSrc2RGB = capsule.readEnum("combineSrc2RGB", CombinerSource.class, CombinerSource.Constant);
+ _combineSrc0Alpha = capsule.readEnum("combineSrc0Alpha", CombinerSource.class, CombinerSource.CurrentTexture);
+ _combineSrc1Alpha = capsule.readEnum("combineSrc1Alpha", CombinerSource.class, CombinerSource.Previous);
+ _combineSrc2Alpha = capsule.readEnum("combineSrc2Alpha", CombinerSource.class, CombinerSource.Constant);
+ _combineOp0RGB = capsule.readEnum("combineOp0RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceColor);
+ _combineOp1RGB = capsule.readEnum("combineOp1RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceColor);
+ _combineOp2RGB = capsule.readEnum("combineOp2RGB", CombinerOperandRGB.class, CombinerOperandRGB.SourceAlpha);
+ _combineOp0Alpha = capsule.readEnum("combineOp0Alpha", CombinerOperandAlpha.class,
+ CombinerOperandAlpha.SourceAlpha);
+ _combineOp1Alpha = capsule.readEnum("combineOp1Alpha", CombinerOperandAlpha.class,
+ CombinerOperandAlpha.SourceAlpha);
+ _combineOp2Alpha = capsule.readEnum("combineOp2Alpha", CombinerOperandAlpha.class,
+ CombinerOperandAlpha.SourceAlpha);
+ _combineScaleRGB = capsule.readEnum("combineScaleRGB", CombinerScale.class, CombinerScale.One);
+ _combineScaleAlpha = capsule.readEnum("combineScaleAlpha", CombinerScale.class, CombinerScale.One);
+ _storeFormat = capsule.readEnum("storeFormat", TextureStoreFormat.class, TextureStoreFormat.RGBA8);
+ _rttPixelDataType = capsule.readEnum("rttPixelDataType", PixelDataType.class, PixelDataType.UnsignedByte);
+ }
+
+ public Class<? extends Texture> getClassTag() {
+ return this.getClass();
+ }
+
+ public int getTextureBaseLevel() {
+ return _textureBaseLevel;
+ }
+
+ public void setTextureBaseLevel(final int textureBaseLevel) {
+ _textureBaseLevel = textureBaseLevel;
+ }
+
+ public int getTextureMaxLevel() {
+ return _textureMaxLevel;
+ }
+
+ public void setTextureMaxLevel(final int textureMaxLevel) {
+ _textureMaxLevel = textureMaxLevel;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java
new file mode 100644
index 0000000..5ba42cd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture1D.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class Texture1D extends Texture {
+
+ private WrapMode _wrapS = WrapMode.Repeat;
+
+ @Override
+ public Texture createSimpleClone() {
+ return createSimpleClone(new Texture1D());
+ }
+
+ @Override
+ public Texture createSimpleClone(final Texture rVal) {
+ rVal.setWrap(WrapAxis.S, _wrapS);
+ return super.createSimpleClone(rVal);
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for a particular axis.
+ *
+ * @param axis
+ * the texture axis to define a wrapmode on.
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if axis or mode are null
+ */
+ @Override
+ public void setWrap(final WrapAxis axis, final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ } else if (axis == null) {
+ throw new IllegalArgumentException("axis can not be null.");
+ }
+ switch (axis) {
+ case S:
+ _wrapS = mode;
+ break;
+ }
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+ *
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ @Override
+ public void setWrap(final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _wrapS = mode;
+ }
+
+ /**
+ * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture.
+ *
+ * @param axis
+ * the axis to return for
+ * @return the wrap mode of the texture.
+ * @throws IllegalArgumentException
+ * if axis is null
+ */
+ @Override
+ public WrapMode getWrap(final WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return _wrapS;
+ }
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.OneDimensional;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof Texture1D)) {
+ return false;
+ }
+ final Texture1D that = (Texture1D) other;
+ if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java
new file mode 100644
index 0000000..dc54a94
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture2D.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class Texture2D extends Texture {
+
+ private WrapMode _wrapS = WrapMode.Repeat;
+ private WrapMode _wrapT = WrapMode.Repeat;
+
+ @Override
+ public Texture createSimpleClone() {
+ return createSimpleClone(new Texture2D());
+ }
+
+ @Override
+ public Texture createSimpleClone(final Texture rVal) {
+ rVal.setWrap(WrapAxis.S, _wrapS);
+ rVal.setWrap(WrapAxis.T, _wrapT);
+ return super.createSimpleClone(rVal);
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for a particular axis.
+ *
+ * @param axis
+ * the texture axis to define a wrapmode on.
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if axis or mode are null
+ */
+ @Override
+ public void setWrap(final WrapAxis axis, final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ } else if (axis == null) {
+ throw new IllegalArgumentException("axis can not be null.");
+ }
+ switch (axis) {
+ case S:
+ _wrapS = mode;
+ break;
+ case T:
+ _wrapT = mode;
+ break;
+ }
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+ *
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ @Override
+ public void setWrap(final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _wrapS = mode;
+ _wrapT = mode;
+ }
+
+ /**
+ * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture.
+ *
+ * @param axis
+ * the axis to return for
+ * @return the wrap mode of the texture.
+ * @throws IllegalArgumentException
+ * if axis is null
+ */
+ @Override
+ public WrapMode getWrap(final WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return _wrapS;
+ case T:
+ return _wrapT;
+ }
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TwoDimensional;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof Texture2D)) {
+ return false;
+ }
+ final Texture2D that = (Texture2D) other;
+ if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+ return false;
+ }
+ if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java
new file mode 100644
index 0000000..8258e5e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/Texture3D.java
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class Texture3D extends Texture {
+
+ private WrapMode _wrapS = WrapMode.Repeat;
+ private WrapMode _wrapT = WrapMode.Repeat;
+ private WrapMode _wrapR = WrapMode.Repeat;
+
+ @Override
+ public Texture createSimpleClone() {
+ return createSimpleClone(new Texture3D());
+ }
+
+ @Override
+ public Texture createSimpleClone(final Texture rVal) {
+ rVal.setWrap(WrapAxis.S, _wrapS);
+ rVal.setWrap(WrapAxis.T, _wrapT);
+ rVal.setWrap(WrapAxis.R, _wrapR);
+ return super.createSimpleClone(rVal);
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for a particular axis.
+ *
+ * @param axis
+ * the texture axis to define a wrapmode on.
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if axis or mode are null
+ */
+ @Override
+ public void setWrap(final WrapAxis axis, final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ } else if (axis == null) {
+ throw new IllegalArgumentException("axis can not be null.");
+ }
+ switch (axis) {
+ case S:
+ _wrapS = mode;
+ break;
+ case T:
+ _wrapT = mode;
+ break;
+ case R:
+ _wrapR = mode;
+ break;
+ }
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+ *
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ @Override
+ public void setWrap(final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _wrapS = mode;
+ _wrapT = mode;
+ _wrapR = mode;
+ }
+
+ /**
+ * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture.
+ *
+ * @param axis
+ * the axis to return for
+ * @return the wrap mode of the texture.
+ * @throws IllegalArgumentException
+ * if axis is null
+ */
+ @Override
+ public WrapMode getWrap(final WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return _wrapS;
+ case T:
+ return _wrapT;
+ case R:
+ return _wrapR;
+ }
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.ThreeDimensional;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof Texture3D)) {
+ return false;
+ }
+ final Texture3D that = (Texture3D) other;
+ if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+ return false;
+ }
+ if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) {
+ return false;
+ }
+ if (getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp);
+ capsule.write(_wrapR, "wrapR", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+ _wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java
new file mode 100644
index 0000000..0253976
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureCubeMap.java
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class TextureCubeMap extends Texture {
+
+ private WrapMode _wrapS = WrapMode.Repeat;
+ private WrapMode _wrapT = WrapMode.Repeat;
+ private WrapMode _wrapR = WrapMode.Repeat;
+
+ private transient Face _currentRTTFace = Face.PositiveX;
+
+ /**
+ * Face of the Cubemap as described by its directional offset from the origin.
+ */
+ public enum Face {
+ PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ;
+ }
+
+ @Override
+ public Texture createSimpleClone() {
+ return createSimpleClone(new TextureCubeMap());
+ }
+
+ @Override
+ public Texture createSimpleClone(final Texture rVal) {
+ rVal.setWrap(WrapAxis.S, _wrapS);
+ rVal.setWrap(WrapAxis.T, _wrapT);
+ rVal.setWrap(WrapAxis.R, _wrapR);
+ if (rVal instanceof TextureCubeMap) {
+ ((TextureCubeMap) rVal).setCurrentRTTFace(_currentRTTFace);
+ }
+ return super.createSimpleClone(rVal);
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for a particular axis.
+ *
+ * @param axis
+ * the texture axis to define a wrapmode on.
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if axis or mode are null
+ */
+ @Override
+ public void setWrap(final WrapAxis axis, final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ } else if (axis == null) {
+ throw new IllegalArgumentException("axis can not be null.");
+ }
+ switch (axis) {
+ case S:
+ _wrapS = mode;
+ break;
+ case T:
+ _wrapT = mode;
+ break;
+ case R:
+ _wrapR = mode;
+ break;
+ }
+ }
+
+ /**
+ * <code>setWrap</code> sets the wrap mode of this texture for all axis.
+ *
+ * @param mode
+ * the wrap mode for the given axis of the texture.
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ @Override
+ public void setWrap(final WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _wrapS = mode;
+ _wrapT = mode;
+ _wrapR = mode;
+ }
+
+ /**
+ * <code>getWrap</code> returns the wrap mode for a given coordinate axis on this texture.
+ *
+ * @param axis
+ * the axis to return for
+ * @return the wrap mode of the texture.
+ * @throws IllegalArgumentException
+ * if axis is null
+ */
+ @Override
+ public WrapMode getWrap(final WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return _wrapS;
+ case T:
+ return _wrapT;
+ case R:
+ return _wrapR;
+ }
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+
+ /**
+ * Set the cubemap Face to use for the next Render To Texture operation (when used with TextureRenderer.) NB: This
+ * field is transient - not saved by Savable.
+ *
+ * @param currentRTTFace
+ * the face to use
+ */
+ public void setCurrentRTTFace(final Face currentRTTFace) {
+ _currentRTTFace = currentRTTFace;
+ }
+
+ /**
+ * @return the cubemap Face to use for the next Render To Texture operation (when used with TextureRenderer.)
+ */
+ public Face getCurrentRTTFace() {
+ return _currentRTTFace;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CubeMap;
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (!(other instanceof TextureCubeMap)) {
+ return false;
+ }
+ final TextureCubeMap that = (TextureCubeMap) other;
+ if (getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+ return false;
+ }
+ if (getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) {
+ return false;
+ }
+ if (getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(_wrapT, "wrapT", WrapMode.EdgeClamp);
+ capsule.write(_wrapR, "wrapR", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ _wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+ _wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java
new file mode 100644
index 0000000..62580b0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/TextureStoreFormat.java
@@ -0,0 +1,366 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image;
+
+public enum TextureStoreFormat {
+ /**
+ * When used in texture loading, this indicates to convert the image's data properties to its closest
+ * TextureStoreFormat <strong>compressed</strong> format.
+ */
+ GuessCompressedFormat,
+ /**
+ * When used in texture loading, this indicates to convert the image's data properties to its closest
+ * TextureStoreFormat format.
+ */
+ GuessNoCompressedFormat,
+ /**
+ * 4 bit alpha only format - usually forced to 8bit by the card
+ */
+ Alpha4,
+ /**
+ * 8 bit alpha only format
+ */
+ Alpha8,
+ /**
+ * 12 bit alpha only format - often forced to 8bit or 16bit by the card
+ */
+ Alpha12,
+ /**
+ * 16 bit alpha only format - older cards will often use 8bit instead.
+ */
+ Alpha16,
+ /**
+ * 4 bit luminance only format - usually forced to 8bit by the card
+ */
+ Luminance4,
+ /**
+ * 8 bit luminance only format
+ */
+ Luminance8,
+ /**
+ * 12 bit luminance only format - often forced to 8bit or 16bit by the card
+ */
+ Luminance12,
+ /**
+ * 16 bit luminance only format - older cards will often use 8bit instead.
+ */
+ Luminance16,
+ /**
+ * 4 bit luminance, 4 bit alpha format
+ */
+ Luminance4Alpha4,
+ /**
+ * 6 bit luminance, 2 bit alpha format
+ */
+ Luminance6Alpha2,
+ /**
+ * 8 bit luminance, 8 bit alpha format
+ */
+ Luminance8Alpha8,
+ /**
+ * 12 bit luminance, 4 bit alpha format
+ */
+ Luminance12Alpha4,
+ /**
+ * 12 bit luminance, 12 bit alpha format
+ */
+ Luminance12Alpha12,
+ /**
+ * 16 bit luminance, 16 bit alpha format
+ */
+ Luminance16Alpha16,
+ /**
+ * 4 bit intensity only format - usually forced to 8bit by the card
+ */
+ Intensity4,
+ /**
+ * 8 bit intensity only format
+ */
+ Intensity8,
+ /**
+ * 12 bit intensity only format - often forced to 8bit or 16bit by the card
+ */
+ Intensity12,
+ /**
+ * 16 bit intensity only format - older cards will often use 8bit instead.
+ */
+ Intensity16,
+ /**
+ * 3 bit red, 3 bit green, 3 bit blue - often forced to 16 bit by the card
+ */
+ R3G3B2,
+ /**
+ * 4 bits per red, green and blue
+ */
+ RGB4,
+ /**
+ * 5 bits per red, green and blue
+ */
+ RGB5,
+ /**
+ * 8 bits per red, green and blue
+ */
+ RGB8,
+ /**
+ * 10 bits per red, green and blue - usually falls back to 8 bits on the card
+ */
+ RGB10,
+ /**
+ * 12 bits per red, green and blue - usually falls back to 8 bits on the card
+ */
+ RGB12,
+ /**
+ * 16 bits per red, green and blue - usually falls back to 8 bits on the card
+ */
+ RGB16,
+ /**
+ * 2 bits per red, green, blue and alpha - often forced to RGBA4 by the card
+ */
+ RGBA2,
+ /**
+ * 4 bits per red, green, blue and alpha
+ */
+ RGBA4,
+ /**
+ * 5 bits per red, green and blue. 1 bit of alpha
+ */
+ RGB5A1,
+ /**
+ * 8 bits per red, green, blue and alpha
+ */
+ RGBA8,
+ /**
+ * 10 bits per red, green and blue. 2 bits of alpha - often forced to RGBA8 by the card
+ */
+ RGB10A2,
+ /**
+ * 12 bits per red, green, blue and alpha - often forced to RGBA8 by the card
+ */
+ RGBA12,
+ /**
+ * 16 bits per red, green, blue and alpha - often forced to RGBA8 by the card
+ */
+ RGBA16,
+ /**
+ * RGB, potentially compressed and stored by the card.
+ */
+ CompressedRed,
+ /**
+ * RGB, potentially compressed and stored by the card.
+ */
+ CompressedRG,
+ /**
+ * RGB, potentially compressed and stored by the card.
+ */
+ CompressedRGB,
+ /**
+ * RGBA, potentially compressed and stored by the card.
+ */
+ CompressedRGBA,
+ /**
+ * Luminance, potentially compressed and stored by the card.
+ */
+ CompressedLuminance,
+ /**
+ * LuminanceAlpha, potentially compressed and stored by the card.
+ */
+ CompressedLuminanceAlpha,
+ /**
+ * Image data already in DXT1 format.
+ */
+ NativeDXT1,
+ /**
+ * Image data already in DXT1 (with Alpha) format.
+ */
+ NativeDXT1A,
+ /**
+ * Image data already in DXT3 format.
+ */
+ NativeDXT3,
+ /**
+ * Image data already in DXT5 format.
+ */
+ NativeDXT5,
+ /**
+ * Image data already in LATC format - Luminance only
+ */
+ NativeLATC_L,
+ /**
+ * Image data already in LATC format - Luminance+Alpha
+ */
+ NativeLATC_LA,
+ /**
+ * depth component format - let card choose bit size
+ */
+ Depth,
+ /**
+ * 16 bit depth component format
+ */
+ Depth16,
+ /**
+ * 24 bit depth component format
+ */
+ Depth24,
+ /**
+ * 32 bit depth component format - often stored in Depth24 format by the card.
+ */
+ Depth32,
+ /**
+ * Floating point depth format.
+ */
+ Depth32F,
+ /**
+ * 16 bit float per red, green and blue
+ */
+ RGB16F,
+ /**
+ * 32 bit float per red, green and blue
+ */
+ RGB32F,
+ /**
+ * 16 bit float per red, green, blue and alpha
+ */
+ RGBA16F,
+ /**
+ * 32 bit float per red, green, blue and alpha
+ */
+ RGBA32F,
+ /**
+ * 16 bit float, alpha only format
+ */
+ Alpha16F,
+ /**
+ * 16 bit float, alpha only format
+ */
+ Alpha32F,
+ /**
+ * 16 bit float, luminance only format
+ */
+ Luminance16F,
+ /**
+ * 32 bit float, luminance only format
+ */
+ Luminance32F,
+ /**
+ * 16 bit float per luminance and alpha
+ */
+ LuminanceAlpha16F,
+ /**
+ * 32 bit float per luminance and alpha
+ */
+ LuminanceAlpha32F,
+ /**
+ * 16 bit float, intensity only format
+ */
+ Intensity16F,
+ /**
+ * 32 bit float, intensity only format
+ */
+ Intensity32F,
+ /**
+ * 8 bit, one-component format
+ */
+ R8,
+ /**
+ * 8 bit integer, one-component format
+ */
+ R8I,
+ /**
+ * 8 bit unsigned integer, one-component format
+ */
+ R8UI,
+ /**
+ * 16 bit, one-component format
+ */
+ R16,
+ /**
+ * 16 bit integer, one-component format
+ */
+ R16I,
+ /**
+ * 16 bit unsigned integer, one-component format
+ */
+ R16UI,
+ /**
+ * 16 bit float, one-component format
+ */
+ R16F,
+ /**
+ * 32 bit integer, one-component format
+ */
+ R32I,
+ /**
+ * 32 bit unsigned integer, one-component format
+ */
+ R32UI,
+ /**
+ * 32 bit float, one-component format
+ */
+ R32F,
+ /**
+ * 8 bit, two-component format
+ */
+ RG8,
+ /**
+ * 8 bit integer, two-component format
+ */
+ RG8I,
+ /**
+ * 8 bit unsigned integer, two-component format
+ */
+ RG8UI,
+ /**
+ * 16 bit, two-component format
+ */
+ RG16,
+ /**
+ * 16 bit integer, two-component format
+ */
+ RG16I,
+ /**
+ * 16 bit unsigned integer, two-component format
+ */
+ RG16UI,
+ /**
+ * 16 bit float, two-component format
+ */
+ RG16F,
+ /**
+ * 32 bit integer, two-component format
+ */
+ RG32I,
+ /**
+ * 32 bit unsigned integer, two-component format
+ */
+ RG32UI,
+ /**
+ * 32 bit float, two-component format.
+ */
+ RG32F;
+
+ public boolean isDepthFormat() {
+ return this == Depth16 || this == Depth24 || this == Depth32;
+ }
+
+ public boolean isCompressed() {
+ switch (this) {
+ case NativeDXT1:
+ case NativeDXT1A:
+ case NativeDXT3:
+ case NativeDXT5:
+ case NativeLATC_L:
+ case NativeLATC_LA:
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java
new file mode 100644
index 0000000..b69ffa8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/AbiLoader.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.util.export.binary.BinaryImporter;
+
+/**
+ * Loads Image objects from binary Ardor3D format.
+ */
+public final class AbiLoader implements ImageLoader {
+ public AbiLoader() {}
+
+ /**
+ * Load an image from Ardor3D binary format.
+ *
+ * @param is
+ * the input stream delivering the binary data.
+ * @param flip
+ * ignored... for now.
+ * @return the new loaded Image.
+ * @throws IOException
+ * if an error occurs during read.
+ */
+ public Image load(final InputStream is, final boolean flip) throws IOException {
+ return (Image) new BinaryImporter().load(is);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java
new file mode 100644
index 0000000..5035392
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ColorMipMapGenerator.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.nio.ByteBuffer;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ *
+ * <code>ColorMipMapGenerator</code>
+ *
+ */
+public abstract class ColorMipMapGenerator {
+
+ /**
+ * Generates an ardor3d Image object containing a mipmapped Image. Each mipmap is a solid color. The first X mipmap
+ * colors are defined in topColors, any remaining mipmaps are a shade of default color.
+ *
+ * @param size
+ * dimensions of the texture (square)
+ * @param topColors
+ * initial colors to use for the mipmaps
+ * @param defaultColor
+ * color to use for remaining mipmaps, scaled darker for each successive mipmap
+ * @return generated Image object
+ */
+ public static Image generateColorMipMap(final int size, final ColorRGBA[] topColors, final ColorRGBA defaultColor) {
+
+ if (!MathUtils.isPowerOfTwo(size)) {
+ throw new Ardor3dException("size must be power of two!");
+ }
+
+ final int mips = (int) (MathUtils.log(size, 2)) + 1;
+
+ int bufLength = size * size * 4;
+ final int[] mipLengths = new int[mips];
+ mipLengths[0] = bufLength;
+ for (int x = 1; x < mips; x++) {
+ mipLengths[x] = mipLengths[x - 1] >> 1;
+ bufLength += (mipLengths[x]);
+ }
+
+ final ByteBuffer bb = BufferUtils.createByteBuffer(bufLength);
+
+ final int[] base = new int[] { (int) (defaultColor.getRed() * 255), (int) (defaultColor.getGreen() * 255),
+ (int) (defaultColor.getBlue() * 255) };
+
+ for (int x = 0; x < mips; x++) {
+ final int length = mipLengths[x] >> 2;
+ final float div = (float) (mips - x + topColors.length) / mips;
+ for (int i = 0; i < length; i++) {
+ if (x >= topColors.length) {
+ bb.put((byte) (base[0] * div));
+ bb.put((byte) (base[1] * div));
+ bb.put((byte) (base[2] * div));
+ } else {
+ bb.put((byte) (topColors[x].getRed() * 255));
+ bb.put((byte) (topColors[x].getGreen() * 255));
+ bb.put((byte) (topColors[x].getBlue() * 255));
+ }
+ bb.put((byte) 255);
+ }
+ }
+ bb.rewind();
+
+ return new Image(ImageDataFormat.RGBA, PixelDataType.UnsignedByte, size, size, bb, mipLengths);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java
new file mode 100644
index 0000000..3591455
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/GeneratedImageFactory.java
@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.functions.Function3D;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.util.geom.BufferUtils;
+
+public abstract class GeneratedImageFactory {
+
+ /**
+ * Creates a side x side sized image of a single solid color, of data type byte.
+ *
+ * @param color
+ * the color of our image
+ * @param useAlpha
+ * if true, the image will have an alpha component (whose value will come from the supplied color.)
+ * @param side
+ * the length of a side of our square image
+ * @return the new Image
+ */
+ public static Image createSolidColorImage(final ReadOnlyColorRGBA color, final boolean useAlpha, final int side) {
+ final ByteBuffer data = BufferUtils.createByteBuffer(side * side * (useAlpha ? 4 : 3));
+ final byte[] b = new byte[useAlpha ? 4 : 3];
+ b[0] = (byte) (color.getRed() * 255);
+ b[1] = (byte) (color.getGreen() * 255);
+ b[2] = (byte) (color.getBlue() * 255);
+ if (useAlpha) {
+ b[3] = (byte) (color.getAlpha() * 255);
+ }
+
+ for (int i = 0, max = side * side; i < max; i++) {
+ data.put(b);
+ }
+ data.rewind();
+ final ImageDataFormat fmt = useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB;
+ return new Image(fmt, PixelDataType.UnsignedByte, side, side, data, null);
+ }
+
+ /**
+ * Creates a one dimensional color image using each color given as a single pixel.
+ *
+ * @param useAlpha
+ * if true, the image will have an alpha component (whose value will come from each supplied color.)
+ * @param colors
+ * one or more colors
+ * @return a 1D image, width == colors.length
+ */
+ public static Image create1DColorImage(final boolean useAlpha, final ReadOnlyColorRGBA... colors) {
+ final ByteBuffer data = BufferUtils.createByteBuffer(colors.length * (useAlpha ? 4 : 3));
+ for (int i = 0; i < colors.length; i++) {
+ final ReadOnlyColorRGBA color = colors[i];
+ data.put((byte) (color.getRed() * 255));
+ data.put((byte) (color.getGreen() * 255));
+ data.put((byte) (color.getBlue() * 255));
+
+ if (useAlpha) {
+ data.put((byte) (color.getAlpha() * 255));
+ }
+ }
+ data.rewind();
+ final ImageDataFormat fmt = (useAlpha) ? ImageDataFormat.RGBA : ImageDataFormat.RGB;
+ return new Image(fmt, PixelDataType.UnsignedByte, colors.length, 1, data, null);
+ }
+
+ /**
+ * Creates an 8 bit luminance image using the given function as input. The domain of the image will be [-1, 1] on
+ * each axis unless that axis is size 0 (in which case the domain is [0, 0]. The expected range of the function
+ * result set is also [-1, 1] and this is remapped to [0, 255] for storage as a byte.
+ *
+ * @param source
+ * the source function to evaluate at each pixel of the image.
+ * @param width
+ * the width of the image
+ * @param height
+ * the height of the image
+ * @param depth
+ * the depth of the image (for 3D texture or texture array use)
+ * @return the resulting Image.
+ */
+ public static Image createLuminance8Image(final Function3D source, final int width, final int height,
+ final int depth) {
+ // default range is [-1, 1] on each axis, unless that axis is size 1.
+ return createLuminance8Image(source, width, height, depth, //
+ width == 1 ? 0 : -1, width == 1 ? 0 : 1, // X
+ height == 1 ? 0 : -1, height == 1 ? 0 : 1, // Y
+ depth == 1 ? 0 : -1, depth == 1 ? 0 : 1, // Z
+ -1, 1);
+ }
+
+ /**
+ * Creates an 8 bit luminance image using the given function as input.
+ *
+ * @param source
+ * the source function to evaluate at each pixel of the image.
+ * @param width
+ * the width of the image
+ * @param height
+ * the height of the image
+ * @param depth
+ * the depth of the image (for 3D texture or texture array use)
+ * @param startX
+ * the lowest domain value of the X axis. Or in other words, when a pixel on the leftmost side of the
+ * image is evaluated, this is the X value sent to the function.
+ * @param endX
+ * the highest domain value of the X axis. Or in other words, when a pixel on the rightmost side of the
+ * image is evaluated, this is the X value sent to the function.
+ * @param startY
+ * the lowest domain value of the Y axis.
+ * @param endY
+ * the highest domain value of the Y axis.
+ * @param startZ
+ * the lowest domain value of the Z axis.
+ * @param endZ
+ * the highest domain value of the Z axis.
+ * @param rangeStart
+ * the expected lowest value of the output from the source function. This is used to map the output to a
+ * byte.
+ * @param rangeEnd
+ * the expected highest value of the output from the source function. This is used to map the output to a
+ * byte.
+ * @return the resulting Image.
+ */
+ public static Image createLuminance8Image(final Function3D source, final int width, final int height,
+ final int depth, final double startX, final double endX, final double startY, final double endY,
+ final double startZ, final double endZ, final double rangeStart, final double rangeEnd) {
+ double val;
+ final double rangeDiv = 1.0 / (rangeEnd - rangeStart);
+ // prepare list of image slices.
+ final List<ByteBuffer> dataList = new ArrayList<ByteBuffer>(depth);
+
+ final byte[] data = new byte[width * height];
+ for (double z = 0; z < depth; z++) {
+ // calc our z coordinate, using start and end Z and our current progress along depth
+ final double dz = (z / depth) * (endZ - startZ) + startZ;
+ int i = 0;
+ for (double y = 0; y < height; y++) {
+ // calc our y coordinate, using start and end Y and our current progress along height
+ final double dy = (y / height) * (endY - startY) + startY;
+ for (double x = 0; x < width; x++) {
+ // calc our x coordinate, using start and end X and our current progress along width
+ final double dx = (x / width) * (endX - startX) + startX;
+
+ // Evaluate for dx, dy, dz
+ val = source.eval(dx, dy, dz);
+
+ // Keep us in [rangeStart, rangeEnd]
+ val = MathUtils.clamp(val, rangeStart, rangeEnd);
+
+ // Convert to [0, 255]
+ val = ((val - rangeStart) * rangeDiv) * 255.0;
+ data[i++] = (byte) val;
+ }
+ }
+ final ByteBuffer dataBuf = BufferUtils.createByteBuffer(data.length);
+ dataBuf.put(data);
+ dataList.add(dataBuf);
+ }
+
+ return new Image(ImageDataFormat.Luminance, PixelDataType.UnsignedByte, width, height, dataList, null);
+ }
+
+ /**
+ * Converts an 8 bit luminance Image to an RGB or RGBA color image by mapping the given values to a corresponding
+ * color value in the given colorTable.
+ *
+ * <p>
+ * XXX: perhaps replace the color array with some gradient class?
+ * </p>
+ *
+ * @param lumImage
+ * the Image to convert.
+ * @param useAlpha
+ * if true, the final image will use have an alpha channel, populated by the alpha values of the given
+ * colors.
+ * @param colorTable
+ * a set of colors, should be length 256.
+ * @return the new Image.
+ */
+ public static Image createColorImageFromLuminance8(final Image lumImage, final boolean useAlpha,
+ final ReadOnlyColorRGBA... colorTable) {
+ assert (colorTable.length == 256) : "color table must be size 256.";
+
+ final List<ByteBuffer> dataList = new ArrayList<ByteBuffer>(lumImage.getDepth());
+ ReadOnlyColorRGBA c;
+ for (int i = 0; i < lumImage.getDepth(); i++) {
+ final ByteBuffer src = lumImage.getData(i);
+ final int size = src.capacity();
+ final ByteBuffer out = BufferUtils.createByteBuffer(size * (useAlpha ? 4 : 3));
+ final byte[] data = new byte[out.capacity()];
+ int j = 0;
+ for (int x = 0; x < size; x++) {
+ c = colorTable[src.get(x) & 0xFF];
+ data[j++] = (byte) (c.getRed() * 255);
+ data[j++] = (byte) (c.getGreen() * 255);
+ data[j++] = (byte) (c.getBlue() * 255);
+ if (useAlpha) {
+ data[j++] = (byte) (c.getAlpha() * 255);
+ }
+ }
+ out.put(data);
+ dataList.add(out);
+ }
+
+ return new Image(useAlpha ? ImageDataFormat.RGBA : ImageDataFormat.RGB, PixelDataType.UnsignedByte, lumImage
+ .getWidth(), lumImage.getHeight(), dataList, null);
+ }
+
+ /**
+ * Fill any empty spots in the given color array by linearly interpolating the non-empty values above and below it.
+ *
+ * @param colors
+ * the color table - must be length 256.
+ */
+ public static void fillInColorTable(final ReadOnlyColorRGBA[] colors) {
+ assert (colors.length == 256) : "colors must be length 256";
+
+ // make sure we have a start color...
+ if (colors[0] == null) {
+ colors[0] = ColorRGBA.BLACK;
+ }
+ // make sure we have a end color...
+ if (colors[255] == null) {
+ colors[255] = ColorRGBA.WHITE;
+ }
+
+ int begin = 0, end = findNonNull(1, colors);
+ // step through
+ for (int i = 1; i < 255; i++) {
+ if (colors[i] != null) {
+ begin = i;
+ end = findNonNull(begin + 1, colors);
+ if (end == -1) {
+ break; // done!
+ }
+ continue;
+ }
+ final float scalar = (float) (i - begin) / (end - begin);
+ colors[i] = ColorRGBA.lerp(colors[begin], colors[end], scalar, null);
+ }
+ }
+
+ private static int findNonNull(final int start, final ReadOnlyColorRGBA[] colors) {
+ for (int i = start; i < colors.length; i++) {
+ if (colors[i] != null) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java
new file mode 100644
index 0000000..4a5bd94
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoader.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.ardor3d.image.Image;
+
+/**
+ * Interface for image loaders. Implementing classes can be registered with the TextureManager to decode image formats
+ * with a certain file extension.
+ *
+ * @see com.ardor3d.util.TextureManager#addHandler(String, ImageLoader)
+ *
+ */
+public interface ImageLoader {
+
+ /**
+ * Decodes image data from an InputStream.
+ *
+ * @param is
+ * The InputStream to create the image from. The inputstream should be closed before this method returns.
+ * @return The decoded Image.
+ * @throws IOException
+ */
+ public Image load(InputStream is, boolean flipped) throws IOException;
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java
new file mode 100644
index 0000000..b12475a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageLoaderUtil.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.util.dds.DdsLoader;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.util.resource.ResourceSource;
+
+public abstract class ImageLoaderUtil {
+ private static final Logger logger = Logger.getLogger(ImageLoaderUtil.class.getName());
+
+ private static ImageLoader defaultLoader;
+ private static Map<String, ImageLoader> loaders = Collections.synchronizedMap(new HashMap<String, ImageLoader>());
+
+ static {
+ registerHandler(new DdsLoader(), ".DDS"); // dxt image
+ registerHandler(new TgaLoader(), ".TGA"); // targa image
+ registerHandler(new AbiLoader(), ".ABI"); // ardor3d binary image
+ }
+
+ public static Image loadImage(final ResourceSource src, final boolean flipped) {
+ if (src == null) {
+ logger.warning("loadImage(ResourceSource, boolean): file is null, defaultTexture used.");
+ return TextureState.getDefaultTextureImage();
+ }
+
+ final String type = src.getType();
+ if (type == null) {
+ logger.warning("loadImage(ResourceSource, boolean): type is null, defaultTexture used.");
+ return TextureState.getDefaultTextureImage();
+ }
+
+ InputStream is = null;
+ try {
+ is = src.openStream();
+ logger.log(Level.FINER, "loadImage(ResourceSource, boolean) opened stream: {0}", src);
+ return loadImage(type, is, flipped);
+ } catch (final IOException e) {
+ logger.log(Level.WARNING, "loadImage(ResourceSource, boolean): defaultTexture used", e);
+ return TextureState.getDefaultTextureImage();
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (final IOException ioe) {
+ } // ignore
+ }
+ }
+ }
+
+ public static Image loadImage(final String type, final InputStream stream, final boolean flipped) {
+
+ Image imageData = null;
+ try {
+ ImageLoader loader = loaders.get(type.toLowerCase());
+ if (loader == null) {
+ loader = defaultLoader;
+ }
+ if (loader != null) {
+ imageData = loader.load(stream, flipped);
+ } else {
+ logger.log(Level.WARNING, "Unable to read image of type: {0}", type);
+ }
+ if (imageData == null) {
+ logger.warning("loadImage(String, InputStream, boolean): no imageData found. defaultTexture used.");
+ imageData = TextureState.getDefaultTextureImage();
+ }
+ } catch (final IOException e) {
+ logger.log(Level.WARNING, "Could not load Image.", e);
+ imageData = TextureState.getDefaultTextureImage();
+ }
+ return imageData;
+ }
+
+ /**
+ * Register an ImageLoader to handle all files with a specific type. An ImageLoader can be registered to handle
+ * several formats without problems.
+ *
+ * @param handler
+ * the handler to use
+ * @param types
+ * The type or types for the format this ImageLoader will handle. This value is case insensitive.
+ * Examples include ".jpeg", ".gif", ".dds", etc.
+ */
+ public static void registerHandler(final ImageLoader handler, final String... types) {
+ for (final String type : types) {
+ loaders.put(type.toLowerCase(), handler);
+ }
+ }
+
+ public static void unregisterHandler(final String... types) {
+ for (final String type : types) {
+ loaders.remove(type.toLowerCase());
+ }
+ }
+
+ public static void registerDefaultHandler(final ImageLoader handler) {
+ defaultLoader = handler;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java
new file mode 100644
index 0000000..f6064f5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/ImageUtils.java
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.image.TextureStoreFormat;
+
+public abstract class ImageUtils {
+
+ public static final int getPixelByteSize(final ImageDataFormat format, final PixelDataType type) {
+ return type.getBytesPerPixel(format.getComponents());
+ }
+
+ public static final TextureStoreFormat getTextureStoreFormat(final TextureStoreFormat format, final Image image) {
+ if (format != TextureStoreFormat.GuessCompressedFormat && format != TextureStoreFormat.GuessNoCompressedFormat) {
+ return format;
+ }
+ if (image == null) {
+ throw new Error("Unable to guess format type... Image is null.");
+ }
+
+ final PixelDataType type = image.getDataType();
+ final ImageDataFormat dataFormat = image.getDataFormat();
+ switch (dataFormat) {
+ case ColorIndex:
+ case BGRA:
+ case RGBA:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedRGBA;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.RGBA8;
+ case Short:
+ case UnsignedShort:
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.RGBA16;
+ case HalfFloat:
+ return TextureStoreFormat.RGBA16F;
+ case Float:
+ return TextureStoreFormat.RGBA32F;
+ }
+ break;
+ case BGR:
+ case RGB:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedRGB;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.RGB8;
+ case Short:
+ case UnsignedShort:
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.RGB16;
+ case HalfFloat:
+ return TextureStoreFormat.RGB16F;
+ case Float:
+ return TextureStoreFormat.RGB32F;
+ }
+ break;
+ case RG:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedRG;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.RG8;
+ case Short:
+ case UnsignedShort:
+ return TextureStoreFormat.RG16;
+ case Int:
+ return TextureStoreFormat.RG16I;
+ case UnsignedInt:
+ return TextureStoreFormat.RG16UI;
+ case HalfFloat:
+ return TextureStoreFormat.RG16F;
+ case Float:
+ return TextureStoreFormat.RG32F;
+ }
+ break;
+ case Luminance:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedLuminance;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.Luminance8;
+ case Short:
+ case UnsignedShort:
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.Luminance16;
+ case HalfFloat:
+ return TextureStoreFormat.Luminance16F;
+ case Float:
+ return TextureStoreFormat.Luminance32F;
+ }
+ break;
+ case LuminanceAlpha:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedLuminanceAlpha;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.Luminance4Alpha4;
+ case Short:
+ case UnsignedShort:
+ return TextureStoreFormat.Luminance8Alpha8;
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.Luminance16Alpha16;
+ case HalfFloat:
+ return TextureStoreFormat.LuminanceAlpha16F;
+ case Float:
+ return TextureStoreFormat.LuminanceAlpha32F;
+ }
+ break;
+ case Alpha:
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.Alpha8;
+ case Short:
+ case UnsignedShort:
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.Alpha16;
+ case HalfFloat:
+ return TextureStoreFormat.Alpha16F;
+ case Float:
+ return TextureStoreFormat.Alpha32F;
+ }
+ break;
+ case Red:
+ if (format == TextureStoreFormat.GuessCompressedFormat) {
+ return TextureStoreFormat.CompressedRed;
+ }
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.R8;
+ case Short:
+ case UnsignedShort:
+ return TextureStoreFormat.R16;
+ case Int:
+ return TextureStoreFormat.R16I;
+ case UnsignedInt:
+ return TextureStoreFormat.R16UI;
+ case HalfFloat:
+ return TextureStoreFormat.R16F;
+ case Float:
+ return TextureStoreFormat.R32F;
+ }
+ break;
+ case Intensity:
+ case Green:
+ case Blue:
+ case StencilIndex:
+ switch (type) {
+ case Byte:
+ case UnsignedByte:
+ return TextureStoreFormat.Intensity8;
+ case Short:
+ case UnsignedShort:
+ case Int:
+ case UnsignedInt:
+ return TextureStoreFormat.Intensity16;
+ case HalfFloat:
+ return TextureStoreFormat.Intensity16F;
+ case Float:
+ return TextureStoreFormat.Intensity32F;
+ }
+ break;
+ case Depth:
+ // XXX: Should we actually switch here? Depth textures can be slightly fussy.
+ return TextureStoreFormat.Depth;
+ case PrecompressedDXT1:
+ return TextureStoreFormat.NativeDXT1;
+ case PrecompressedDXT1A:
+ return TextureStoreFormat.NativeDXT1A;
+ case PrecompressedDXT3:
+ return TextureStoreFormat.NativeDXT3;
+ case PrecompressedDXT5:
+ return TextureStoreFormat.NativeDXT5;
+ case PrecompressedLATC_L:
+ return TextureStoreFormat.NativeLATC_L;
+ case PrecompressedLATC_LA:
+ return TextureStoreFormat.NativeLATC_LA;
+ }
+
+ throw new Error("Unhandled type / format combination: " + type + " / " + dataFormat);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java
new file mode 100644
index 0000000..e39b826
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TextureProjector.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.renderer.Camera;
+
+public class TextureProjector extends Camera {
+
+ private final static ReadOnlyMatrix4 BIAS = new Matrix4( //
+ 0.5, 0.0, 0.0, 0.0, //
+ 0.0, 0.5, 0.0, 0.0, //
+ 0.0, 0.0, 0.5, 0.0, //
+ 0.5, 0.5, 0.5, 1.0);
+
+ public TextureProjector() {
+ super(1, 1);
+ }
+
+ public void updateTextureMatrix(final Texture texture) {
+ final Matrix4 texMat = Matrix4.fetchTempInstance();
+ updateTextureMatrix(texMat);
+ texture.setTextureMatrix(texMat);
+ Matrix4.releaseTempInstance(texMat);
+ }
+
+ public void updateTextureMatrix(final Matrix4 matrixStore) {
+ update();
+ final ReadOnlyMatrix4 projectorView = getModelViewMatrix();
+ final ReadOnlyMatrix4 projectorProjection = getProjectionMatrix();
+ matrixStore.set(projectorView).multiplyLocal(projectorProjection).multiplyLocal(BIAS);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java
new file mode 100644
index 0000000..8068501
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/TgaLoader.java
@@ -0,0 +1,491 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Loads image files in the Targa format. Handles RLE Targa files. Does not handle Targa files in Black-and-White
+ * format.
+ */
+public final class TgaLoader implements ImageLoader {
+
+ // 0 - no image data in file
+ public static final int TYPE_NO_IMAGE = 0;
+
+ // 1 - uncompressed, color-mapped image
+ public static final int TYPE_COLORMAPPED = 1;
+
+ // 2 - uncompressed, true-color image
+ public static final int TYPE_TRUECOLOR = 2;
+
+ // 3 - uncompressed, black and white image
+ public static final int TYPE_BLACKANDWHITE = 3;
+
+ // 9 - run-length encoded, color-mapped image
+ public static final int TYPE_COLORMAPPED_RLE = 9;
+
+ // 10 - run-length encoded, true-color image
+ public static final int TYPE_TRUECOLOR_RLE = 10;
+
+ // 11 - run-length encoded, black and white image
+ public static final int TYPE_BLACKANDWHITE_RLE = 11;
+
+ public TgaLoader() {}
+
+ /**
+ * Load an image from Targa format.
+ *
+ * @param is
+ * the input stream delivering the targa data.
+ * @param flip
+ * if true, we will flip the given targa image on the vertical axis.
+ * @return the new loaded Image.
+ * @throws IOException
+ * if an error occurs during read.
+ */
+ public Image load(final InputStream is, boolean flip) throws IOException {
+ boolean flipH = false;
+ // open a stream to the file
+ final BufferedInputStream bis = new BufferedInputStream(is, 8192);
+ final DataInputStream dis = new DataInputStream(bis);
+ boolean createAlpha = false;
+
+ // ---------- Start Reading the TGA header ---------- //
+ // length of the image id (1 byte)
+ final int idLength = dis.readUnsignedByte();
+
+ // Type of color map (if any) included with the image
+ // 0 - no color map data is included
+ // 1 - a color map is included
+ final int colorMapType = dis.readUnsignedByte();
+
+ // Type of image being read:
+ final int imageType = dis.readUnsignedByte();
+
+ // Read Color Map Specification (5 bytes)
+ // Index of first color map entry (if we want to use it, uncomment and remove extra read.)
+ // short cMapStart = flipEndian(dis.readShort());
+ dis.readShort();
+ // number of entries in the color map
+ final short cMapLength = flipEndian(dis.readShort());
+ // number of bits per color map entry
+ final int cMapDepth = dis.readUnsignedByte();
+
+ // Read Image Specification (10 bytes)
+ // horizontal coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
+ // int xOffset = flipEndian(dis.readShort());
+ dis.readShort();
+ // vertical coordinate of lower left corner of image. (if we want to use it, uncomment and remove extra read.)
+ // int yOffset = flipEndian(dis.readShort());
+ dis.readShort();
+ // width of image - in pixels
+ final int width = flipEndian(dis.readShort());
+ // height of image - in pixels
+ final int height = flipEndian(dis.readShort());
+ // bits per pixel in image.
+ final int pixelDepth = dis.readUnsignedByte();
+ final int imageDescriptor = dis.readUnsignedByte();
+ if ((imageDescriptor & 32) != 0) {
+ flip = !flip;
+ }
+ if ((imageDescriptor & 16) != 0) {
+ flipH = !flipH;
+ }
+
+ // ---------- Done Reading the TGA header ---------- //
+
+ // Skip image ID
+ if (idLength > 0) {
+ if (idLength != bis.skip(idLength)) {
+ throw new IOException("Unexpected number of bytes in file - too few.");
+ }
+ }
+
+ ColorMapEntry[] cMapEntries = null;
+ if (colorMapType != 0) {
+ // read the color map.
+ final int bytesInColorMap = (cMapDepth * cMapLength) >> 3;
+ final int bitsPerColor = Math.min(cMapDepth / 3, 8);
+
+ final byte[] cMapData = new byte[bytesInColorMap];
+ if (-1 == bis.read(cMapData)) {
+ throw new EOFException();
+ }
+
+ // Only go to the trouble of constructing the color map
+ // table if this is declared a color mapped image.
+ if (imageType == TYPE_COLORMAPPED || imageType == TYPE_COLORMAPPED_RLE) {
+ cMapEntries = new ColorMapEntry[cMapLength];
+ final int alphaSize = cMapDepth - (3 * bitsPerColor);
+ final float scalar = 255f / (int) (Math.pow(2, bitsPerColor) - 1);
+ final float alphaScalar = 255f / (int) (Math.pow(2, alphaSize) - 1);
+ for (int i = 0; i < cMapLength; i++) {
+ final ColorMapEntry entry = new ColorMapEntry();
+ final int offset = cMapDepth * i;
+ entry.red = (byte) (int) (getBitsAsByte(cMapData, offset, bitsPerColor) * scalar);
+ entry.green = (byte) (int) (getBitsAsByte(cMapData, offset + bitsPerColor, bitsPerColor) * scalar);
+ entry.blue = (byte) (int) (getBitsAsByte(cMapData, offset + (2 * bitsPerColor), bitsPerColor) * scalar);
+ if (alphaSize <= 0) {
+ entry.alpha = (byte) 255;
+ } else {
+ entry.alpha = (byte) (int) (getBitsAsByte(cMapData, offset + (3 * bitsPerColor), alphaSize) * alphaScalar);
+ }
+
+ cMapEntries[i] = entry;
+ }
+ }
+ }
+
+ // Allocate image data array
+ byte[] rawData = null;
+ int dl;
+ if ((pixelDepth == 32)) {
+ rawData = new byte[width * height * 4];
+ dl = 4;
+ createAlpha = true;
+ } else {
+ rawData = new byte[width * height * 3];
+ dl = 3;
+ }
+ int rawDataIndex = 0;
+
+ if (imageType == TYPE_TRUECOLOR) {
+ byte red = 0;
+ byte green = 0;
+ byte blue = 0;
+ byte alpha = 0;
+
+ // Faster than doing a 16-or-24-or-32 check on each individual pixel,
+ // just make a separate loop for each.
+ if (pixelDepth == 16) {
+ final byte[] data = new byte[2];
+ final float scalar = 255f / 31f;
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ data[1] = dis.readByte();
+ data[0] = dis.readByte();
+ rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
+ rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
+ rawData[rawDataIndex++] = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
+ if (createAlpha) {
+ alpha = getBitsAsByte(data, 0, 1);
+ if (alpha == 1) {
+ alpha = (byte) 255;
+ }
+ rawData[rawDataIndex++] = alpha;
+ }
+ }
+ }
+ } else if (pixelDepth == 24) {
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ if (createAlpha) {
+ rawData[rawDataIndex++] = (byte) 255;
+ }
+
+ }
+ }
+ } else if (pixelDepth == 32) {
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ alpha = dis.readByte();
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ rawData[rawDataIndex++] = alpha;
+ }
+ }
+ } else {
+ throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth);
+ }
+ } else if (imageType == TYPE_TRUECOLOR_RLE) {
+ byte red = 0;
+ byte green = 0;
+ byte blue = 0;
+ byte alpha = 0;
+
+ // Faster than doing a 16-or-24-or-32 check on each individual pixel,
+ // just make a separate loop for each.
+ if (pixelDepth == 32) {
+ for (int i = 0; i <= (height - 1); ++i) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+
+ for (int j = 0; j < width; ++j) {
+ // Get the number of pixels the next chunk covers (either packed or unpacked)
+ int count = dis.readByte();
+ if ((count & 0x80) != 0) {
+ // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+ count &= 0x07f;
+ j += count;
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ alpha = dis.readByte();
+ while (count-- >= 0) {
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ rawData[rawDataIndex++] = alpha;
+ }
+ } else {
+ // Its not RLE packed, but the next <count> pixels are raw.
+ j += count;
+ while (count-- >= 0) {
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ alpha = dis.readByte();
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ rawData[rawDataIndex++] = alpha;
+ }
+ }
+ }
+ }
+
+ } else if (pixelDepth == 24) {
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; ++j) {
+ // Get the number of pixels the next chunk covers (either packed or unpacked)
+ int count = dis.readByte();
+ if ((count & 0x80) != 0) {
+ // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+ count &= 0x07f;
+ j += count;
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ while (count-- >= 0) {
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ if (createAlpha) {
+ rawData[rawDataIndex++] = (byte) 255;
+ }
+ }
+ } else {
+ // Its not RLE packed, but the next <count> pixels are raw.
+ j += count;
+ while (count-- >= 0) {
+ blue = dis.readByte();
+ green = dis.readByte();
+ red = dis.readByte();
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ if (createAlpha) {
+ rawData[rawDataIndex++] = (byte) 255;
+ }
+ }
+ }
+ }
+ }
+
+ } else if (pixelDepth == 16) {
+ final byte[] data = new byte[2];
+ final float scalar = 255f / 31f;
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ // Get the number of pixels the next chunk covers (either packed or unpacked)
+ int count = dis.readByte();
+ if ((count & 0x80) != 0) {
+ // Its an RLE packed block - use the following 1 pixel for the next <count> pixels
+ count &= 0x07f;
+ j += count;
+ data[1] = dis.readByte();
+ data[0] = dis.readByte();
+ blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
+ green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
+ red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
+ while (count-- >= 0) {
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ if (createAlpha) {
+ rawData[rawDataIndex++] = (byte) 255;
+ }
+ }
+ } else {
+ // Its not RLE packed, but the next <count> pixels are raw.
+ j += count;
+ while (count-- >= 0) {
+ data[1] = dis.readByte();
+ data[0] = dis.readByte();
+ blue = (byte) (int) (getBitsAsByte(data, 1, 5) * scalar);
+ green = (byte) (int) (getBitsAsByte(data, 6, 5) * scalar);
+ red = (byte) (int) (getBitsAsByte(data, 11, 5) * scalar);
+ rawData[rawDataIndex++] = red;
+ rawData[rawDataIndex++] = green;
+ rawData[rawDataIndex++] = blue;
+ if (createAlpha) {
+ rawData[rawDataIndex++] = (byte) 255;
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ throw new Ardor3dException("Unsupported TGA true color depth: " + pixelDepth);
+ }
+
+ } else if (imageType == TYPE_COLORMAPPED) {
+ final int bytesPerIndex = pixelDepth / 8;
+
+ if (bytesPerIndex == 1) {
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ final int index = dis.readUnsignedByte();
+ if (index >= cMapEntries.length || index < 0) {
+ throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index);
+ }
+ final ColorMapEntry entry = cMapEntries[index];
+ rawData[rawDataIndex++] = entry.red;
+ rawData[rawDataIndex++] = entry.green;
+ rawData[rawDataIndex++] = entry.blue;
+ if (dl == 4) {
+ rawData[rawDataIndex++] = entry.alpha;
+ }
+
+ }
+ }
+ } else if (bytesPerIndex == 2) {
+ for (int i = 0; i <= (height - 1); i++) {
+ if (!flip) {
+ rawDataIndex = (height - 1 - i) * width * dl;
+ }
+ for (int j = 0; j < width; j++) {
+ final int index = flipEndian(dis.readShort());
+ if (index >= cMapEntries.length || index < 0) {
+ throw new Ardor3dException("TGA: Invalid color map entry referenced: " + index);
+ }
+ final ColorMapEntry entry = cMapEntries[index];
+ rawData[rawDataIndex++] = entry.red;
+ rawData[rawDataIndex++] = entry.green;
+ rawData[rawDataIndex++] = entry.blue;
+ if (dl == 4) {
+ rawData[rawDataIndex++] = entry.alpha;
+ }
+ }
+ }
+ } else {
+ throw new Ardor3dException("TGA: unknown colormap indexing size used: " + bytesPerIndex);
+ }
+ }
+
+ // Get a pointer to the image memory
+ final ByteBuffer scratch = BufferUtils.createByteBuffer(rawData.length);
+ scratch.clear();
+ scratch.put(rawData);
+ scratch.rewind();
+ // Create the ardor3d.image.Image object
+ final com.ardor3d.image.Image textureImage = new com.ardor3d.image.Image();
+ if (dl == 4) {
+ textureImage.setDataFormat(ImageDataFormat.RGBA);
+ } else {
+ textureImage.setDataFormat(ImageDataFormat.RGB);
+ }
+ textureImage.setDataType(PixelDataType.UnsignedByte);
+ textureImage.setWidth(width);
+ textureImage.setHeight(height);
+ textureImage.setData(scratch);
+ return textureImage;
+ }
+
+ private static byte getBitsAsByte(final byte[] data, final int offset, final int length) {
+ int offsetBytes = offset / 8;
+ int indexBits = offset % 8;
+ int rVal = 0;
+
+ // start at data[offsetBytes]... spill into next byte as needed.
+ for (int i = length; --i >= 0;) {
+ final byte b = data[offsetBytes];
+ final int test = indexBits == 7 ? 1 : 2 << (6 - indexBits);
+ if ((b & test) != 0) {
+ if (i == 0) {
+ rVal++;
+ } else {
+ rVal += (2 << i - 1);
+ }
+ }
+ indexBits++;
+ if (indexBits == 8) {
+ indexBits = 0;
+ offsetBytes++;
+ }
+ }
+
+ return (byte) rVal;
+ }
+
+ /**
+ * <code>flipEndian</code> is used to flip the endian bit of the header file.
+ *
+ * @param signedShort
+ * the bit to flip.
+ * @return the flipped bit.
+ */
+ private static short flipEndian(final short signedShort) {
+ final int input = signedShort & 0xFFFF;
+ return (short) (input << 8 | (input & 0xFF00) >>> 8);
+ }
+
+ private static class ColorMapEntry {
+ byte red, green, blue, alpha;
+
+ @Override
+ public String toString() {
+ return "entry: " + red + "," + green + "," + blue + "," + alpha;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java
new file mode 100644
index 0000000..b777d12
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/D3d10ResourceDimension.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+enum D3d10ResourceDimension {
+ D3D10_RESOURCE_DIMENSION_UNKNOWN(0), //
+ D3D10_RESOURCE_DIMENSION_BUFFER(1), //
+ D3D10_RESOURCE_DIMENSION_TEXTURE1D(2), //
+ D3D10_RESOURCE_DIMENSION_TEXTURE2D(3), //
+ D3D10_RESOURCE_DIMENSION_TEXTURE3D(4); //
+
+ int _value;
+
+ D3d10ResourceDimension(final int value) {
+ _value = value;
+ }
+
+ static D3d10ResourceDimension forInt(final int value) {
+ for (final D3d10ResourceDimension dim : values()) {
+ if (dim._value == value) {
+ return dim;
+ }
+ }
+ throw new Error("unknown D3D10ResourceDimension: " + value);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java
new file mode 100644
index 0000000..e624f2f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeader.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+import static com.ardor3d.image.util.dds.DdsUtils.isSet;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.LittleEndianDataInput;
+
+class DdsHeader {
+ private static final Logger logger = Logger.getLogger(DdsHeader.class.getName());
+
+ // ---- VALUES USED IN dwFlags ----
+ // Required caps flag.
+ final static int DDSD_CAPS = 0x1;
+ // Required caps flag.
+ final static int DDSD_HEIGHT = 0x2;
+ // Required caps flag.
+ final static int DDSD_WIDTH = 0x4;
+ // Required when pitch is provided for an uncompressed texture.
+ final static int DDSD_PITCH = 0x8;
+ // Required caps flag.
+ final static int DDSD_PIXELFORMAT = 0x1000;
+ // Required in a mipmapped texture.
+ final static int DDSD_MIPMAPCOUNT = 0x20000;
+ // Required when pitch is provided for a compressed texture.
+ final static int DDSD_LINEARSIZE = 0x80000;
+ // Required in a depth texture.
+ final static int DDSD_DEPTH = 0x800000;
+ // ---- /end VALUES USED IN dwFlags ----
+
+ // ---- VALUES USED IN dwCaps ----
+ // Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or
+ // volume texture).
+ final static int DDSCAPS_COMPLEX = 0x8;
+ // Optional; should be used for a mipmap.
+ final static int DDSCAPS_MIPMAP = 0x400000;
+ // Required caps flag.
+ final static int DDSCAPS_TEXTURE = 0x1000;
+ // ---- /end VALUES USED IN dwCaps ----
+
+ // ---- VALUES USED IN dwCaps2 ----
+ // Required for a cube map.
+ final static int DDSCAPS2_CUBEMAP = 0x200;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_POSITIVEX = 0x400;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000;
+ // Required when these surfaces are stored in a cube map.
+ final static int DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000;
+ // Required for a volume texture.
+ final static int DDSCAPS2_VOLUME = 0x200000;
+ // ---- /end VALUES USED IN dwCaps2 ----
+
+ int dwSize;
+ int dwFlags;
+ int dwHeight;
+ int dwWidth;
+ int dwLinearSize;
+ int dwDepth;
+ int dwMipMapCount;
+ int dwAlphaBitDepth;
+ int[] dwReserved1 = new int[10];
+ DdsPixelFormat ddpf;
+ int dwCaps;
+ int dwCaps2;
+ int dwCaps3;
+ int dwCaps4;
+ int dwTextureStage;
+
+ static DdsHeader read(final LittleEndianDataInput in) throws IOException {
+ final DdsHeader header = new DdsHeader();
+ header.dwSize = in.readInt();
+ if (header.dwSize != 124) {
+ throw new Error("invalid dds header size: " + header.dwSize);
+ }
+ header.dwFlags = in.readInt();
+ header.dwHeight = in.readInt();
+ header.dwWidth = in.readInt();
+ header.dwLinearSize = in.readInt();
+ header.dwDepth = in.readInt();
+ header.dwMipMapCount = in.readInt();
+ header.dwAlphaBitDepth = in.readInt();
+ for (int i = 0; i < header.dwReserved1.length; i++) {
+ header.dwReserved1[i] = in.readInt();
+ }
+ header.ddpf = DdsPixelFormat.read(in);
+ header.dwCaps = in.readInt();
+ header.dwCaps2 = in.readInt();
+ header.dwCaps3 = in.readInt();
+ header.dwCaps4 = in.readInt();
+ header.dwTextureStage = in.readInt();
+
+ final int expectedMipmaps = 1 + (int) Math.ceil(Math.log(Math.max(header.dwHeight, header.dwWidth))
+ / Math.log(2));
+
+ if (isSet(header.dwCaps, DDSCAPS_MIPMAP)) {
+ if (!isSet(header.dwFlags, DDSD_MIPMAPCOUNT)) {
+ header.dwMipMapCount = expectedMipmaps;
+ } else if (header.dwMipMapCount != expectedMipmaps) {
+ logger.fine("Got " + header.dwMipMapCount + " mipmaps, expected " + expectedMipmaps);
+ }
+ } else {
+ header.dwMipMapCount = 1;
+ }
+
+ return header;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java
new file mode 100644
index 0000000..7ef5c62
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsHeaderDX10.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+import java.io.IOException;
+
+import com.ardor3d.util.LittleEndianDataInput;
+
+class DdsHeaderDX10 {
+ final static int D3D10_RESOURCE_MISC_GENERATE_MIPS = 0x1;
+ final static int D3D10_RESOURCE_MISC_SHARED = 0x2;
+ final static int D3D10_RESOURCE_MISC_TEXTURECUBE = 0x4;
+ final static int D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX = 0x10;
+ final static int D3D10_RESOURCE_MISC_GDI_COMPATIBLE = 0x20;
+
+ DxgiFormat dxgiFormat;
+ D3d10ResourceDimension resourceDimension;
+ int miscFlag;
+ int arraySize;
+ int reserved;
+
+ static DdsHeaderDX10 read(final LittleEndianDataInput in) throws IOException {
+ final DdsHeaderDX10 header = new DdsHeaderDX10();
+ header.dxgiFormat = DxgiFormat.forInt(in.readInt());
+ header.resourceDimension = D3d10ResourceDimension.forInt(in.readInt());
+ header.miscFlag = in.readInt();
+ header.arraySize = in.readInt();
+ header.reserved = in.readInt();
+ return header;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java
new file mode 100644
index 0000000..8d551ac
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsLoader.java
@@ -0,0 +1,385 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+import static com.ardor3d.image.util.dds.DdsUtils.flipDXT;
+import static com.ardor3d.image.util.dds.DdsUtils.getInt;
+import static com.ardor3d.image.util.dds.DdsUtils.isSet;
+import static com.ardor3d.image.util.dds.DdsUtils.shiftCount;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.image.util.ImageLoader;
+import com.ardor3d.image.util.ImageUtils;
+import com.ardor3d.util.LittleEndianDataInput;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+
+/**
+ * <p>
+ * <code>DdsLoader</code> is an image loader that reads in a DirectX DDS file.
+ * </p>
+ * Supports 2D images, volume images and cubemaps in the following formats:<br>
+ * Compressed:<br>
+ * <ul>
+ * <li>DXT1A</li>
+ * <li>DXT3</li>
+ * <li>DXT5</li>
+ * <li>LATC</li>
+ * </ul>
+ * Uncompressed:<br>
+ * <ul>
+ * <li>RGB</li>
+ * <li>RGBA</li>
+ * <li>Luminance</li>
+ * <li>LuminanceAlpha</li>
+ * <li>Alpha</li>
+ * </ul>
+ * Note that Cubemaps must have all 6 faces defined to load properly. FIXME: Needs a software inflater for compressed
+ * formats in cases where support is not present? Maybe JSquish?
+ */
+public class DdsLoader implements ImageLoader {
+ private static final Logger logger = Logger.getLogger(DdsLoader.class.getName());
+
+ public Image load(final InputStream is, final boolean flipVertically) throws IOException {
+ final LittleEndianDataInput in = new LittleEndianDataInput(is);
+
+ // Read and check magic word...
+ final int dwMagic = in.readInt();
+ if (dwMagic != getInt("DDS ")) {
+ throw new Error("Not a dds file.");
+ }
+ logger.finest("Reading DDS file.");
+
+ // Create our data store;
+ final DdsImageInfo info = new DdsImageInfo();
+
+ info.flipVertically = flipVertically;
+
+ // Read standard dds header
+ info.header = DdsHeader.read(in);
+
+ // if applicable, read DX10 header
+ info.headerDX10 = info.header.ddpf.dwFourCC == getInt("DX10") ? DdsHeaderDX10.read(in) : null;
+
+ // Create our new image
+ final Image image = new Image();
+ image.setWidth(info.header.dwWidth);
+ image.setHeight(info.header.dwHeight);
+
+ // update depth based on flags / header
+ updateDepth(image, info);
+
+ // add our format and image data.
+ populateImage(image, info, in);
+
+ // return the loaded image
+ return image;
+ }
+
+ private static final void updateDepth(final Image image, final DdsImageInfo info) {
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP)) {
+ int depth = 0;
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEX)) {
+ depth++;
+ }
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEX)) {
+ depth++;
+ }
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEY)) {
+ depth++;
+ }
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEY)) {
+ depth++;
+ }
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_POSITIVEZ)) {
+ depth++;
+ }
+ if (isSet(info.header.dwCaps2, DdsHeader.DDSCAPS2_CUBEMAP_NEGATIVEZ)) {
+ depth++;
+ }
+
+ if (depth != 6) {
+ throw new Error("Cubemaps without all faces defined are not currently supported.");
+ }
+
+ image.setDepth(depth);
+ } else {
+ // make sure we have at least depth of 1.
+ image.setDepth(info.header.dwDepth > 0 ? info.header.dwDepth : 1);
+ }
+ }
+
+ private static final void populateImage(final Image image, final DdsImageInfo info, final LittleEndianDataInput in)
+ throws IOException {
+ final int flags = info.header.ddpf.dwFlags;
+
+ final boolean compressedFormat = isSet(flags, DdsPixelFormat.DDPF_FOURCC);
+ final boolean rgb = isSet(flags, DdsPixelFormat.DDPF_RGB);
+ final boolean alphaPixels = isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS);
+ final boolean lum = isSet(flags, DdsPixelFormat.DDPF_LUMINANCE);
+ final boolean alpha = isSet(flags, DdsPixelFormat.DDPF_ALPHA);
+
+ if (compressedFormat) {
+ final int fourCC = info.header.ddpf.dwFourCC;
+ // DXT1 format
+ if (fourCC == getInt("DXT1")) {
+ info.bpp = 4;
+ // if (isSet(flags, DdsPixelFormat.DDPF_ALPHAPIXELS)) {
+ // XXX: many authoring tools do not set alphapixels, so we'll error on the side of alpha
+ logger.finest("DDS format: DXT1A");
+ image.setDataFormat(ImageDataFormat.PrecompressedDXT1A);
+ // } else {
+ // logger.finest("DDS format: DXT1");
+ // image.setDataFormat(ImageDataFormat.PrecompressedDXT1);
+ // }
+ }
+
+ // DXT3 format
+ else if (fourCC == getInt("DXT3")) {
+ logger.finest("DDS format: DXT3");
+ info.bpp = 8;
+ image.setDataFormat(ImageDataFormat.PrecompressedDXT3);
+ }
+
+ // DXT5 format
+ else if (fourCC == getInt("DXT5")) {
+ logger.finest("DDS format: DXT5");
+ info.bpp = 8;
+ image.setDataFormat(ImageDataFormat.PrecompressedDXT5);
+ }
+
+ // DXT10 info present...
+ else if (fourCC == getInt("DX10")) {
+ switch (info.headerDX10.dxgiFormat) {
+ case DXGI_FORMAT_BC4_UNORM:
+ logger.finest("DXGI format: BC4_UNORM");
+ info.bpp = 4;
+ image.setDataFormat(ImageDataFormat.PrecompressedLATC_L);
+ break;
+ case DXGI_FORMAT_BC5_UNORM:
+ logger.finest("DXGI format: BC5_UNORM");
+ info.bpp = 8;
+ image.setDataFormat(ImageDataFormat.PrecompressedLATC_LA);
+ break;
+ default:
+ throw new Error("dxgiFormat not supported: " + info.headerDX10.dxgiFormat);
+ }
+ }
+
+ // DXT2 format - unsupported
+ else if (fourCC == getInt("DXT2")) {
+ logger.finest("DDS format: DXT2");
+ throw new Error("DXT2 is not supported.");
+ }
+
+ // DXT4 format - unsupported
+ else if (fourCC == getInt("DXT4")) {
+ logger.finest("DDS format: DXT4");
+ throw new Error("DXT4 is not supported.");
+ }
+
+ // Unsupported compressed type.
+ else {
+ throw new Error("unsupported compressed dds format found (" + fourCC + ")");
+ }
+ }
+
+ // not a compressed format
+ else {
+ // TODO: more use of bit masks?
+ // TODO: Use bit size instead of hardcoded 8 bytes? (need to also implement in readUncompressed)
+ image.setDataType(PixelDataType.UnsignedByte);
+
+ info.bpp = info.header.ddpf.dwRGBBitCount;
+
+ // One of the RGB formats?
+ if (rgb) {
+ if (alphaPixels) {
+ logger.finest("DDS format: uncompressed rgba");
+ image.setDataFormat(ImageDataFormat.RGBA);
+ } else {
+ logger.finest("DDS format: uncompressed rgb ");
+ image.setDataFormat(ImageDataFormat.RGB);
+ }
+ }
+
+ // A luminance or alpha format
+ else if (lum || alphaPixels) {
+ if (lum && alphaPixels) {
+ logger.finest("DDS format: uncompressed LumAlpha");
+ image.setDataFormat(ImageDataFormat.LuminanceAlpha);
+ }
+
+ else if (lum) {
+ logger.finest("DDS format: uncompressed Lum");
+ image.setDataFormat(ImageDataFormat.Luminance);
+ }
+
+ else if (alpha) {
+ logger.finest("DDS format: uncompressed Alpha");
+ image.setDataFormat(ImageDataFormat.Alpha);
+ }
+ } // end luminance/alpha type
+
+ // Unsupported type.
+ else {
+ throw new Error("unsupported uncompressed dds format found.");
+ }
+ }
+
+ info.calcMipmapSizes(compressedFormat);
+ image.setMipMapByteSizes(info.mipmapByteSizes);
+
+ // Add up total byte size of single depth layer
+ int totalSize = 0;
+ for (final int size : info.mipmapByteSizes) {
+ totalSize += size;
+ }
+
+ // Go through and load in image data
+ final List<ByteBuffer> imageData = Lists.newArrayList();
+ for (int i = 0; i < image.getDepth(); i++) {
+ // read in compressed data
+ if (compressedFormat) {
+ imageData.add(readDXT(in, totalSize, info, image));
+ }
+
+ // read in uncompressed data
+ else if (rgb || lum || alpha) {
+ imageData.add(readUncompressed(in, totalSize, rgb, lum, alpha, alphaPixels, info, image));
+ }
+ }
+
+ // set on image
+ image.setData(imageData);
+ }
+
+ static final ByteBuffer readDXT(final LittleEndianDataInput in, final int totalSize, final DdsImageInfo info,
+ final Image image) throws IOException {
+ int mipWidth = info.header.dwWidth;
+ int mipHeight = info.header.dwHeight;
+
+ final ByteBuffer buffer = BufferUtils.createByteBuffer(totalSize);
+ for (int mip = 0; mip < info.header.dwMipMapCount; mip++) {
+ final byte[] data = new byte[info.mipmapByteSizes[mip]];
+ in.readFully(data);
+ if (!info.flipVertically) {
+ buffer.put(data);
+ } else {
+ final byte[] flipped = flipDXT(data, mipWidth, mipHeight, image.getDataFormat());
+ buffer.put(flipped);
+
+ mipWidth = Math.max(mipWidth / 2, 1);
+ mipHeight = Math.max(mipHeight / 2, 1);
+ }
+ }
+ buffer.rewind();
+ return buffer;
+ }
+
+ private static ByteBuffer readUncompressed(final LittleEndianDataInput in, final int totalSize,
+ final boolean useRgb, final boolean useLum, final boolean useAlpha, final boolean useAlphaPixels,
+ final DdsImageInfo info, final Image image) throws IOException {
+ final int redLumShift = shiftCount(info.header.ddpf.dwRBitMask);
+ final int greenShift = shiftCount(info.header.ddpf.dwGBitMask);
+ final int blueShift = shiftCount(info.header.ddpf.dwBBitMask);
+ final int alphaShift = shiftCount(info.header.ddpf.dwABitMask);
+
+ final int sourcebytesPP = info.header.ddpf.dwRGBBitCount / 8;
+ final int targetBytesPP = ImageUtils.getPixelByteSize(image.getDataFormat(), image.getDataType());
+
+ final ByteBuffer dataBuffer = BufferUtils.createByteBuffer(totalSize);
+
+ int mipWidth = info.header.dwWidth;
+ int mipHeight = info.header.dwHeight;
+ int offset = 0;
+
+ for (int mip = 0; mip < info.header.dwMipMapCount; mip++) {
+ for (int y = 0; y < mipHeight; y++) {
+ for (int x = 0; x < mipWidth; x++) {
+ final byte[] b = new byte[sourcebytesPP];
+ in.readFully(b);
+
+ final int i = getInt(b);
+
+ final byte redLum = (byte) (((i & info.header.ddpf.dwRBitMask) >> redLumShift));
+ final byte green = (byte) (((i & info.header.ddpf.dwGBitMask) >> greenShift));
+ final byte blue = (byte) (((i & info.header.ddpf.dwBBitMask) >> blueShift));
+ final byte alpha = (byte) (((i & info.header.ddpf.dwABitMask) >> alphaShift));
+
+ if (info.flipVertically) {
+ dataBuffer.position(offset + ((mipHeight - y - 1) * mipWidth + x) * targetBytesPP);
+ }
+
+ if (useAlpha) {
+ dataBuffer.put(alpha);
+ } else if (useLum) {
+ if (useAlphaPixels) {
+ dataBuffer.put(redLum).put(alpha);
+ } else {
+ dataBuffer.put(redLum);
+ }
+ } else if (useRgb) {
+ if (useAlphaPixels) {
+ dataBuffer.put(redLum).put(green).put(blue).put(alpha);
+ } else {
+ dataBuffer.put(redLum).put(green).put(blue);
+ }
+ }
+ }
+ }
+
+ offset += mipWidth * mipHeight * targetBytesPP;
+
+ mipWidth = Math.max(mipWidth / 2, 1);
+ mipHeight = Math.max(mipHeight / 2, 1);
+ }
+
+ return dataBuffer;
+ }
+
+ private final static class DdsImageInfo {
+ boolean flipVertically;
+ int bpp = 0;
+ DdsHeader header;
+ DdsHeaderDX10 headerDX10;
+ int mipmapByteSizes[];
+
+ void calcMipmapSizes(final boolean compressed) {
+ int width = header.dwWidth;
+ int height = header.dwHeight;
+ int size = 0;
+
+ mipmapByteSizes = new int[header.dwMipMapCount];
+
+ for (int i = 0; i < header.dwMipMapCount; i++) {
+ if (compressed) {
+ size = ((width + 3) / 4) * ((height + 3) / 4) * bpp * 2;
+ } else {
+ size = width * height * bpp / 8;
+ }
+
+ mipmapByteSizes[i] = ((size + 3) / 4) * 4;
+
+ width = Math.max(width / 2, 1);
+ height = Math.max(height / 2, 1);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java
new file mode 100644
index 0000000..71ee4b0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsPixelFormat.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+import java.io.IOException;
+
+import com.ardor3d.util.LittleEndianDataInput;
+
+class DdsPixelFormat {
+
+ // ---- VALUES USED IN dwFlags ----
+ // Texture contains alpha data; dwABitMask contains valid data.
+ final static int DDPF_ALPHAPIXELS = 0x1;
+ // Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount contains the alpha channel
+ // bitcount; dwABitMask contains valid data)
+ final static int DDPF_ALPHA = 0x2;
+ // Texture contains compressed RGB data; dwFourCC contains valid data.
+ final static int DDPF_FOURCC = 0x4;
+ // Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks (dwRBitMask, dwGBitMask, dwBBitMask)
+ // contain valid data.
+ final static int DDPF_RGB = 0x40;
+ // Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains the YUV bit count; dwRBitMask
+ // contains the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask)
+ final static int DDPF_YUV = 0x200;
+ // Used in some older DDS files for single channel color uncompressed data (dwRGBBitCount contains the luminance
+ // channel bit count; dwRBitMask contains the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel
+ // DDS file.
+ final static int DDPF_LUMINANCE = 0x20000;
+ // ---- /end VALUES USED IN dwFlags ----
+
+ int dwSize;
+ int dwFlags;
+ int dwFourCC;
+ int dwRGBBitCount;
+ int dwRBitMask;
+ int dwGBitMask;
+ int dwBBitMask;
+ int dwABitMask;
+
+ static DdsPixelFormat read(final LittleEndianDataInput in) throws IOException {
+ final DdsPixelFormat format = new DdsPixelFormat();
+ format.dwSize = in.readInt();
+ if (format.dwSize != 32) {
+ throw new Error("invalid pixel format size: " + format.dwSize);
+ }
+ format.dwFlags = in.readInt();
+ format.dwFourCC = in.readInt();
+ format.dwRGBBitCount = in.readInt();
+ format.dwRBitMask = in.readInt();
+ format.dwGBitMask = in.readInt();
+ format.dwBBitMask = in.readInt();
+ format.dwABitMask = in.readInt();
+ return format;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java
new file mode 100644
index 0000000..ffcde82
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DdsUtils.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+import com.ardor3d.image.ImageDataFormat;
+
+public class DdsUtils {
+
+ /**
+ * Get the necessary bit shifts needed to align mask with 0.
+ *
+ * @param mask
+ * the bit mask to test
+ * @return number of bits to shift to the right to align mask with 0.
+ */
+ static final int shiftCount(int mask) {
+ if (mask == 0) {
+ return 0;
+ }
+
+ int i = 0;
+ while ((mask & 0x1) == 0) {
+ mask = mask >> 1;
+ i++;
+ if (i > 32) {
+ throw new Error(Integer.toHexString(mask));
+ }
+ }
+
+ return i;
+ }
+
+ /**
+ * Check a value against a bit mask to see if it is set.
+ *
+ * @param value
+ * the value to check
+ * @param bitMask
+ * our mask
+ * @return true if the mask passes
+ */
+ static final boolean isSet(final int value, final int bitMask) {
+ return (value & bitMask) == bitMask;
+ }
+
+ /**
+ * Get the string as a dword int value.
+ *
+ * @param string
+ * our string... should only be 1-4 chars long.
+ * @return the int value
+ */
+ static final int getInt(final String string) {
+ return getInt(string.getBytes());
+ }
+
+ /**
+ * Get the byte array as a dword int value.
+ *
+ * @param bytes
+ * our array... should only be 1-4 bytes long.
+ * @return the int value
+ */
+ static final int getInt(final byte[] bytes) {
+ int rVal = 0;
+ rVal |= ((bytes[0] & 0xff) << 0);
+ if (bytes.length > 1) {
+ rVal |= ((bytes[1] & 0xff) << 8);
+ }
+ if (bytes.length > 2) {
+ rVal |= ((bytes[2] & 0xff) << 16);
+ }
+ if (bytes.length > 3) {
+ rVal |= ((bytes[3] & 0xff) << 24);
+ }
+ return rVal;
+ }
+
+ /**
+ * Flip a dxt mipmap/image. Inspired by similar code in opentk and the nvidia sdk.
+ *
+ * @param rawData
+ * our unflipped image as raw bytes
+ * @param width
+ * our image's width
+ * @param height
+ * our image's height
+ * @param format
+ * our image's format
+ * @return the flipped image as raw bytes.
+ */
+ public static byte[] flipDXT(final byte[] rawData, final int width, final int height, final ImageDataFormat format) {
+ final byte[] returnData = new byte[rawData.length];
+
+ final int blocksPerColumn = (width + 3) >> 2;
+ final int blocksPerRow = (height + 3) >> 2;
+ final int bytesPerBlock = format.getComponents() * 8;
+
+ for (int sourceRow = 0; sourceRow < blocksPerRow; sourceRow++) {
+ final int targetRow = blocksPerRow - sourceRow - 1;
+ for (int column = 0; column < blocksPerColumn; column++) {
+ final int target = (targetRow * blocksPerColumn + column) * bytesPerBlock;
+ final int source = (sourceRow * blocksPerColumn + column) * bytesPerBlock;
+ switch (format) {
+ case PrecompressedDXT1:
+ case PrecompressedDXT1A:
+ case PrecompressedLATC_L:
+ System.arraycopy(rawData, source, returnData, target, 4);
+ returnData[target + 4] = rawData[source + 7];
+ returnData[target + 5] = rawData[source + 6];
+ returnData[target + 6] = rawData[source + 5];
+ returnData[target + 7] = rawData[source + 4];
+ break;
+ case PrecompressedDXT3:
+ // Alpha
+ returnData[target + 0] = rawData[source + 6];
+ returnData[target + 1] = rawData[source + 7];
+ returnData[target + 2] = rawData[source + 4];
+ returnData[target + 3] = rawData[source + 5];
+ returnData[target + 4] = rawData[source + 2];
+ returnData[target + 5] = rawData[source + 3];
+ returnData[target + 6] = rawData[source + 0];
+ returnData[target + 7] = rawData[source + 1];
+
+ // Color
+ System.arraycopy(rawData, source + 8, returnData, target + 8, 4);
+ returnData[target + 12] = rawData[source + 15];
+ returnData[target + 13] = rawData[source + 14];
+ returnData[target + 14] = rawData[source + 13];
+ returnData[target + 15] = rawData[source + 12];
+ break;
+ case PrecompressedDXT5:
+ // Alpha, the first 2 bytes remain
+ returnData[target + 0] = rawData[source + 0];
+ returnData[target + 1] = rawData[source + 1];
+
+ // extract 3 bits each and flip them
+ getBytesFromUInt24(returnData, target + 5, flipUInt24(getUInt24(rawData, source + 2)));
+ getBytesFromUInt24(returnData, target + 2, flipUInt24(getUInt24(rawData, source + 5)));
+
+ // Color
+ System.arraycopy(rawData, source + 8, returnData, target + 8, 4);
+ returnData[target + 12] = rawData[source + 15];
+ returnData[target + 13] = rawData[source + 14];
+ returnData[target + 14] = rawData[source + 13];
+ returnData[target + 15] = rawData[source + 12];
+ break;
+ case PrecompressedLATC_LA:
+ // alpha
+ System.arraycopy(rawData, source, returnData, target, 4);
+ returnData[target + 4] = rawData[source + 7];
+ returnData[target + 5] = rawData[source + 6];
+ returnData[target + 6] = rawData[source + 5];
+ returnData[target + 7] = rawData[source + 4];
+
+ // Color
+ System.arraycopy(rawData, source + 8, returnData, target + 8, 4);
+ returnData[target + 12] = rawData[source + 15];
+ returnData[target + 13] = rawData[source + 14];
+ returnData[target + 14] = rawData[source + 13];
+ returnData[target + 15] = rawData[source + 12];
+ break;
+ }
+ }
+ }
+ return returnData;
+ }
+
+ // DXT5 Alpha block flipping, inspired by code from Evan Hart (nVidia SDK)
+ private static int getUInt24(final byte[] input, final int offset) {
+ int result = 0;
+ result |= (input[offset + 0] & 0xff) << 0;
+ result |= (input[offset + 1] & 0xff) << 8;
+ result |= (input[offset + 2] & 0xff) << 16;
+ return result;
+ }
+
+ private static void getBytesFromUInt24(final byte[] input, final int offset, final int uint24) {
+ input[offset + 0] = (byte) (uint24 & 0x000000ff);
+ input[offset + 1] = (byte) ((uint24 & 0x0000ff00) >> 8);
+ input[offset + 2] = (byte) ((uint24 & 0x00ff0000) >> 16);
+ return;
+ }
+
+ private static final int ThreeBitMask = 0x7;
+
+ private static int flipUInt24(int uint24) {
+ final byte[][] threeBits = new byte[2][];
+ for (int i = 0; i < 2; i++) {
+ threeBits[i] = new byte[4];
+ }
+
+ // extract 3 bits each into the array
+ threeBits[0][0] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[0][1] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[0][2] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[0][3] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[1][0] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[1][1] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[1][2] = (byte) (uint24 & ThreeBitMask);
+ uint24 >>= 3;
+ threeBits[1][3] = (byte) (uint24 & ThreeBitMask);
+
+ // stuff 8x 3bits into 3 bytes
+ int result = 0;
+ result = result | (threeBits[1][0] << 0);
+ result = result | (threeBits[1][1] << 3);
+ result = result | (threeBits[1][2] << 6);
+ result = result | (threeBits[1][3] << 9);
+ result = result | (threeBits[0][0] << 12);
+ result = result | (threeBits[0][1] << 15);
+ result = result | (threeBits[0][2] << 18);
+ result = result | (threeBits[0][3] << 21);
+ return result;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java
new file mode 100644
index 0000000..3c99126
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/image/util/dds/DxgiFormat.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.image.util.dds;
+
+enum DxgiFormat {
+
+ DXGI_FORMAT_UNKNOWN(0), //
+ DXGI_FORMAT_R32G32B32A32_TYPELESS(1), //
+ DXGI_FORMAT_R32G32B32A32_FLOAT(2), //
+ DXGI_FORMAT_R32G32B32A32_UINT(3), //
+ DXGI_FORMAT_R32G32B32A32_SINT(4), //
+ DXGI_FORMAT_R32G32B32_TYPELESS(5), //
+ DXGI_FORMAT_R32G32B32_FLOAT(6), //
+ DXGI_FORMAT_R32G32B32_UINT(7), //
+ DXGI_FORMAT_R32G32B32_SINT(8), //
+ DXGI_FORMAT_R16G16B16A16_TYPELESS(9), //
+ DXGI_FORMAT_R16G16B16A16_FLOAT(10), //
+ DXGI_FORMAT_R16G16B16A16_UNORM(11), //
+ DXGI_FORMAT_R16G16B16A16_UINT(12), //
+ DXGI_FORMAT_R16G16B16A16_SNORM(13), //
+ DXGI_FORMAT_R16G16B16A16_SINT(14), //
+ DXGI_FORMAT_R32G32_TYPELESS(15), //
+ DXGI_FORMAT_R32G32_FLOAT(16), //
+ DXGI_FORMAT_R32G32_UINT(17), //
+ DXGI_FORMAT_R32G32_SINT(18), //
+ DXGI_FORMAT_R32G8X24_TYPELESS(19), //
+ DXGI_FORMAT_D32_FLOAT_S8X24_UINT(20), //
+ DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS(21), //
+ DXGI_FORMAT_X32_TYPELESS_G8X24_UINT(22), //
+ DXGI_FORMAT_R10G10B10A2_TYPELESS(23), //
+ DXGI_FORMAT_R10G10B10A2_UNORM(24), //
+ DXGI_FORMAT_R10G10B10A2_UINT(25), //
+ DXGI_FORMAT_R11G11B10_FLOAT(26), //
+ DXGI_FORMAT_R8G8B8A8_TYPELESS(27), //
+ DXGI_FORMAT_R8G8B8A8_UNORM(28), //
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB(29), //
+ DXGI_FORMAT_R8G8B8A8_UINT(30), //
+ DXGI_FORMAT_R8G8B8A8_SNORM(31), //
+ DXGI_FORMAT_R8G8B8A8_SINT(32), //
+ DXGI_FORMAT_R16G16_TYPELESS(33), //
+ DXGI_FORMAT_R16G16_FLOAT(34), //
+ DXGI_FORMAT_R16G16_UNORM(35), //
+ DXGI_FORMAT_R16G16_UINT(36), //
+ DXGI_FORMAT_R16G16_SNORM(37), //
+ DXGI_FORMAT_R16G16_SINT(38), //
+ DXGI_FORMAT_R32_TYPELESS(39), //
+ DXGI_FORMAT_D32_FLOAT(40), //
+ DXGI_FORMAT_R32_FLOAT(41), //
+ DXGI_FORMAT_R32_UINT(42), //
+ DXGI_FORMAT_R32_SINT(43), //
+ DXGI_FORMAT_R24G8_TYPELESS(44), //
+ DXGI_FORMAT_D24_UNORM_S8_UINT(45), //
+ DXGI_FORMAT_R24_UNORM_X8_TYPELESS(46), //
+ DXGI_FORMAT_X24_TYPELESS_G8_UINT(47), //
+ DXGI_FORMAT_R8G8_TYPELESS(48), //
+ DXGI_FORMAT_R8G8_UNORM(49), //
+ DXGI_FORMAT_R8G8_UINT(50), //
+ DXGI_FORMAT_R8G8_SNORM(51), //
+ DXGI_FORMAT_R8G8_SINT(52), //
+ DXGI_FORMAT_R16_TYPELESS(53), //
+ DXGI_FORMAT_R16_FLOAT(54), //
+ DXGI_FORMAT_D16_UNORM(55), //
+ DXGI_FORMAT_R16_UNORM(56), //
+ DXGI_FORMAT_R16_UINT(57), //
+ DXGI_FORMAT_R16_SNORM(58), //
+ DXGI_FORMAT_R16_SINT(59), //
+ DXGI_FORMAT_R8_TYPELESS(60), //
+ DXGI_FORMAT_R8_UNORM(61), //
+ DXGI_FORMAT_R8_UINT(62), //
+ DXGI_FORMAT_R8_SNORM(63), //
+ DXGI_FORMAT_R8_SINT(64), //
+ DXGI_FORMAT_A8_UNORM(65), //
+ DXGI_FORMAT_R1_UNORM(66), //
+ DXGI_FORMAT_R9G9B9E5_SHAREDEXP(67), //
+ DXGI_FORMAT_R8G8_B8G8_UNORM(68), //
+ DXGI_FORMAT_G8R8_G8B8_UNORM(69), //
+ DXGI_FORMAT_BC1_TYPELESS(70), //
+ DXGI_FORMAT_BC1_UNORM(71), //
+ DXGI_FORMAT_BC1_UNORM_SRGB(72), //
+ DXGI_FORMAT_BC2_TYPELESS(73), //
+ DXGI_FORMAT_BC2_UNORM(74), //
+ DXGI_FORMAT_BC2_UNORM_SRGB(75), //
+ DXGI_FORMAT_BC3_TYPELESS(76), //
+ DXGI_FORMAT_BC3_UNORM(77), //
+ DXGI_FORMAT_BC3_UNORM_SRGB(78), //
+ DXGI_FORMAT_BC4_TYPELESS(79), //
+ DXGI_FORMAT_BC4_UNORM(80), //
+ DXGI_FORMAT_BC4_SNORM(81), //
+ DXGI_FORMAT_BC5_TYPELESS(82), //
+ DXGI_FORMAT_BC5_UNORM(83), //
+ DXGI_FORMAT_BC5_SNORM(84), //
+ DXGI_FORMAT_B5G6R5_UNORM(85), //
+ DXGI_FORMAT_B5G5R5A1_UNORM(86), //
+ DXGI_FORMAT_B8G8R8A8_UNORM(87), //
+ DXGI_FORMAT_B8G8R8X8_UNORM(88), //
+ DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM(89), //
+ DXGI_FORMAT_B8G8R8A8_TYPELESS(90), //
+ DXGI_FORMAT_B8G8R8A8_UNORM_SRGB(91), //
+ DXGI_FORMAT_B8G8R8X8_TYPELESS(92), //
+ DXGI_FORMAT_B8G8R8X8_UNORM_SRGB(93), //
+ DXGI_FORMAT_BC6H_TYPELESS(94), //
+ DXGI_FORMAT_BC6H_UF16(95), //
+ DXGI_FORMAT_BC6H_SF16(96), //
+ DXGI_FORMAT_BC7_TYPELESS(97), //
+ DXGI_FORMAT_BC7_UNORM(98), //
+ DXGI_FORMAT_BC7_UNORM_SRGB(99), //
+ DXGI_FORMAT_FORCE_UINT(0xffffffff); //
+
+ int _value;
+
+ DxgiFormat(final int value) {
+ _value = value;
+ }
+
+ static DxgiFormat forInt(final int value) {
+ for (final DxgiFormat dim : values()) {
+ if (dim._value == value) {
+ return dim;
+ }
+ }
+ throw new Error("unknown DXGIFormat: " + value);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java
new file mode 100644
index 0000000..65150fc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ButtonState.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Enumerates the different states a mouse button can be in.
+ */
+public enum ButtonState {
+ UP, DOWN, UNDEFINED
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java
new file mode 100644
index 0000000..0f7c895
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerEvent.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+public class ControllerEvent {
+
+ private final long nanos;
+ private final String controllerName;
+ private final String componentName;
+ private final float value;
+
+ public ControllerEvent(final long nanos, final String controllerName, final String componentName, final float value) {
+ this.nanos = nanos;
+ this.controllerName = controllerName;
+ this.componentName = componentName;
+ this.value = value;
+ }
+
+ public long getNanos() {
+ return nanos;
+ }
+
+ public String getControllerName() {
+ return controllerName;
+ }
+
+ public String getComponentName() {
+ return componentName;
+ }
+
+ public float getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "ControllerEvent: " + controllerName + ", " + componentName + ", " + value + ", " + nanos;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java
new file mode 100644
index 0000000..a65032d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerState.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.ardor3d.annotation.Immutable;
+import com.google.common.collect.Maps;
+
+@Immutable
+public class ControllerState {
+
+ public static final ControllerState NOTHING = new ControllerState();
+
+ private final Map<String, Map<String, Float>> controllerStates = new LinkedHashMap<String, Map<String, Float>>();
+ private final List<ControllerEvent> eventsSinceLastState = new ArrayList<ControllerEvent>();
+
+ /**
+ * Sets a components state
+ */
+ public void set(final String controllerName, final String componentName, final float value) {
+ Map<String, Float> controllerState = null;
+ if (controllerStates.containsKey(controllerName)) {
+ controllerState = controllerStates.get(controllerName);
+ } else {
+ controllerState = new LinkedHashMap<String, Float>();
+ controllerStates.put(controllerName, controllerState);
+ }
+
+ controllerState.put(componentName, value);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof ControllerState) {
+ final ControllerState other = (ControllerState) obj;
+ return other.controllerStates.equals(controllerStates);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder stateString = new StringBuilder("ControllerState: ");
+
+ for (final String controllerStateKey : controllerStates.keySet()) {
+ final Map<String, Float> state = controllerStates.get(controllerStateKey);
+ stateString.append("[").append(controllerStateKey);
+ for (final String stateKey : state.keySet()) {
+ stateString.append("[").append(stateKey).append(":").append(state.get(stateKey)).append("]");
+ }
+ stateString.append("]");
+ }
+
+ return stateString.toString();
+ }
+
+ public ControllerState snapshot() {
+ final ControllerState snapshot = new ControllerState();
+ duplicateStates(snapshot.controllerStates);
+ snapshot.eventsSinceLastState.addAll(eventsSinceLastState);
+
+ return snapshot;
+ }
+
+ private void duplicateStates(final Map<String, Map<String, Float>> store) {
+ store.clear();
+ for (final Entry<String, Map<String, Float>> entry : controllerStates.entrySet()) {
+ store.put(entry.getKey(), Maps.newLinkedHashMap(entry.getValue()));
+ }
+ }
+
+ public void addEvent(final ControllerEvent event) {
+ eventsSinceLastState.add(event);
+ set(event.getControllerName(), event.getComponentName(), event.getValue());
+ }
+
+ public List<ControllerEvent> getEvents() {
+ Collections.sort(eventsSinceLastState, new Comparator<ControllerEvent>() {
+ public int compare(final ControllerEvent o1, final ControllerEvent o2) {
+ return (int) (o2.getNanos() - o1.getNanos());
+ }
+ });
+
+ return Collections.unmodifiableList(eventsSinceLastState);
+ }
+
+ public void clearEvents() {
+ eventsSinceLastState.clear();
+ }
+
+ public List<String> getControllerNames() {
+ return new ArrayList<String>(controllerStates.keySet());
+ }
+
+ public List<String> getControllerComponentNames(final String controller) {
+ return new ArrayList<String>(controllerStates.get(controller).keySet());
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java
new file mode 100644
index 0000000..f718c1b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/ControllerWrapper.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+public interface ControllerWrapper {
+ /**
+ * Allows the keyboard wrapper implementation to initialise itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all keyboard events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<ControllerEvent> getEvents();
+
+ public ControllerState getBlankState();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java
new file mode 100644
index 0000000..6ac7b40
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/FocusWrapper.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Describes the interface to implement to keep track of focus changes.
+ */
+public interface FocusWrapper {
+ public boolean getAndClearFocusLost();
+
+ void init();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java
new file mode 100644
index 0000000..9a3716b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/GrabbedState.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Enumeration of possible states of 'grabbedness' for a mouse.
+ *
+ */
+public enum GrabbedState {
+ GRABBED, NOT_GRABBED
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java
new file mode 100644
index 0000000..7cfb58a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/InputState.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.ardor3d.annotation.Immutable;
+
+/**
+ * The total input state of the devices that are being handled.
+ */
+@Immutable
+public class InputState {
+ public static final InputState LOST_FOCUS = new InputState(KeyboardState.NOTHING, MouseState.NOTHING,
+ ControllerState.NOTHING);
+ public static final InputState EMPTY = new InputState(KeyboardState.NOTHING, MouseState.NOTHING,
+ ControllerState.NOTHING);
+
+ private final KeyboardState keyboardState;
+ private final MouseState mouseState;
+ private final ControllerState controllerState;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param keyboardState
+ * a non-null KeyboardState instance
+ * @param mouseState
+ * a non-null MouseState instance
+ * @throws NullPointerException
+ * if either parameter is null
+ */
+ public InputState(final KeyboardState keyboardState, final MouseState mouseState,
+ final ControllerState controllerState) {
+ if (keyboardState == null) {
+ throw new NullPointerException("Keyboard state");
+ }
+
+ if (mouseState == null) {
+ throw new NullPointerException("Mouse state");
+ }
+
+ if (controllerState == null) {
+ throw new NullPointerException("Controller state");
+ }
+
+ this.keyboardState = keyboardState;
+ this.mouseState = mouseState;
+ this.controllerState = controllerState;
+ }
+
+ public KeyboardState getKeyboardState() {
+ return keyboardState;
+ }
+
+ public MouseState getMouseState() {
+ return mouseState;
+ }
+
+ public ControllerState getControllerState() {
+ return controllerState;
+ }
+
+ @Override
+ public String toString() {
+ return "InputState{" + "keyboardState=" + keyboardState + ", mouseState=" + mouseState + ", controllerState="
+ + controllerState + '}';
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java
new file mode 100644
index 0000000..d9cad0d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/Key.java
@@ -0,0 +1,761 @@
+/**
+ * Copyright 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Keys supported by Ardor3D platforms. Note that all keys are not likely supported by any one platform.
+ */
+public enum Key {
+ /**
+ * Returned when a key character is not supported.
+ */
+ UNKNOWN,
+
+ /**
+ * escape key.
+ */
+ ESCAPE,
+
+ /**
+ * 1 key.
+ */
+ ONE,
+
+ /**
+ * 2 key.
+ */
+ TWO,
+
+ /**
+ * 3 key.
+ */
+ THREE,
+
+ /**
+ * 4 key.
+ */
+ FOUR,
+
+ /**
+ * 5 key.
+ */
+ FIVE,
+
+ /**
+ * 6 key.
+ */
+ SIX,
+
+ /**
+ * 7 key.
+ */
+ SEVEN,
+
+ /**
+ * 8 key.
+ */
+ EIGHT,
+
+ /**
+ * 9 key.
+ */
+ NINE,
+
+ /**
+ * 0 key.
+ */
+ ZERO,
+
+ /**
+ * - key.
+ */
+ MINUS,
+
+ /**
+ * = key.
+ */
+ EQUALS,
+
+ /**
+ * back key.
+ */
+ BACK,
+
+ /**
+ * tab key.
+ */
+ TAB,
+
+ /**
+ * q key.
+ */
+ Q,
+
+ /**
+ * w key.
+ */
+ W,
+
+ /**
+ * e key.
+ */
+ E,
+
+ /**
+ * r key.
+ */
+ R,
+
+ /**
+ * t key.
+ */
+ T,
+
+ /**
+ * y key.
+ */
+ Y,
+
+ /**
+ * u key.
+ */
+ U,
+
+ /**
+ * i key.
+ */
+ I,
+
+ /**
+ * o key.
+ */
+ O,
+
+ /**
+ * p key.
+ */
+ P,
+
+ /**
+ * [ key.
+ */
+ LBRACKET,
+
+ /**
+ * ] key.
+ */
+ RBRACKET,
+
+ /**
+ * enter key.
+ */
+ RETURN,
+
+ /**
+ * left control key.
+ */
+ LCONTROL,
+
+ /**
+ * a key.
+ */
+ A,
+
+ /**
+ * s key.
+ */
+ S,
+
+ /**
+ * d key.
+ */
+ D,
+
+ /**
+ * f key.
+ */
+ F,
+
+ /**
+ * g key.
+ */
+ G,
+
+ /**
+ * h key.
+ */
+ H,
+
+ /**
+ * j key.
+ */
+ J,
+
+ /**
+ * k key.
+ */
+ K,
+
+ /**
+ * l key.
+ */
+ L,
+
+ /**
+ * ; key.
+ */
+ SEMICOLON,
+
+ /**
+ * ' key.
+ */
+ APOSTROPHE,
+
+ /**
+ * Applications key.
+ */
+ APPS,
+
+ /**
+ * ` key.
+ */
+ GRAVE,
+
+ /**
+ * left shift key.
+ */
+ LSHIFT,
+
+ /**
+ * \ key.
+ */
+ BACKSLASH,
+
+ /**
+ * z key.
+ */
+ Z,
+
+ /**
+ * x key.
+ */
+ X,
+
+ /**
+ * c key.
+ */
+ C,
+
+ /**
+ * v key.
+ */
+ V,
+
+ /**
+ * b key.
+ */
+ B,
+
+ /**
+ * n key.
+ */
+ N,
+
+ /**
+ * m key.
+ */
+ M,
+
+ /**
+ * , key.
+ */
+ COMMA,
+
+ /**
+ * . key .
+ */
+ PERIOD,
+
+ /**
+ * / key .
+ */
+ SLASH,
+
+ /**
+ * right shift key.
+ */
+ RSHIFT,
+
+ /**
+ * * key .
+ */
+ MULTIPLY,
+
+ /**
+ * left alt key.
+ */
+ LMENU,
+
+ /**
+ * space key.
+ */
+ SPACE,
+
+ /**
+ * caps lock key.
+ */
+ CAPITAL,
+
+ /**
+ * F1 key.
+ */
+ F1,
+
+ /**
+ * F2 key.
+ */
+ F2,
+
+ /**
+ * F3 key.
+ */
+ F3,
+
+ /**
+ * F4 key.
+ */
+ F4,
+
+ /**
+ * F5 key.
+ */
+ F5,
+
+ /**
+ * F6 key.
+ */
+ F6,
+
+ /**
+ * F7 key.
+ */
+ F7,
+
+ /**
+ * F8 key.
+ */
+ F8,
+
+ /**
+ * F9 key.
+ */
+ F9,
+
+ /**
+ * F10 key.
+ */
+ F10,
+
+ /**
+ * NumLK key.
+ */
+ NUMLOCK,
+
+ /**
+ * Scroll lock key.
+ */
+ SCROLL,
+
+ /**
+ * 7 key .
+ */
+ NUMPAD7,
+
+ /**
+ * 8 key .
+ */
+ NUMPAD8,
+
+ /**
+ * 9 key .
+ */
+ NUMPAD9,
+
+ /**
+ * - key .
+ */
+ NUMPADSUBTRACT,
+
+ /**
+ * 4 key .
+ */
+ NUMPAD4,
+
+ /**
+ * 5 key .
+ */
+ NUMPAD5,
+
+ /**
+ * 6 key .
+ */
+ NUMPAD6,
+
+ /**
+ * + key .
+ */
+ NUMPADADD,
+
+ /**
+ * 1 key .
+ */
+ NUMPAD1,
+
+ /**
+ * 2 key .
+ */
+ NUMPAD2,
+
+ /**
+ * 3 key .
+ */
+ NUMPAD3,
+
+ /**
+ * 0 key .
+ */
+ NUMPAD0,
+
+ /**
+ * . key .
+ */
+ DECIMAL,
+
+ /**
+ * F11 key.
+ */
+ F11,
+
+ /**
+ * F12 key.
+ */
+ F12,
+
+ /**
+ * F13 key.
+ */
+ F13,
+
+ /**
+ * F14 key.
+ */
+ F14,
+
+ /**
+ * F15 key.
+ */
+ F15,
+
+ /**
+ * kana key .
+ */
+ KANA,
+
+ /**
+ * convert key .
+ */
+ CONVERT,
+
+ /**
+ * noconvert key .
+ */
+ NOCONVERT,
+
+ /**
+ * yen key .
+ */
+ YEN,
+
+ /**
+ * = on num pad .
+ */
+ NUMPADEQUALS,
+
+ /**
+ * circum flex key .
+ */
+ CIRCUMFLEX,
+
+ /**
+ * &#064; key .
+ */
+ AT,
+
+ /**
+ * : key
+ */
+ COLON,
+
+ /**
+ * _ key .
+ */
+ UNDERLINE,
+
+ /**
+ * kanji key .
+ */
+ KANJI,
+
+ /**
+ * stop key .
+ */
+ STOP,
+
+ /**
+ * ax key .
+ */
+ AX,
+
+ /**
+ * .
+ */
+ UNLABELED,
+
+ /**
+ * Enter key .
+ */
+ NUMPADENTER,
+
+ /**
+ * right control key.
+ */
+ RCONTROL,
+
+ /**
+ * , key on num pad .
+ */
+ NUMPADCOMMA,
+
+ /**
+ * / key .
+ */
+ DIVIDE,
+
+ /**
+ * SysRq key.
+ */
+ SYSRQ,
+
+ /**
+ * right alt key.
+ */
+ RMENU,
+
+ /**
+ * pause key.
+ */
+ PAUSE,
+
+ /**
+ * home key.
+ */
+ HOME,
+
+ /**
+ * up arrow key.
+ */
+ UP,
+
+ /**
+ * PageUp/Prior key.
+ */
+ PAGEUP_PRIOR,
+
+ /**
+ * left arrow key.
+ */
+ LEFT,
+
+ /**
+ * right arrow key.
+ */
+ RIGHT,
+
+ /**
+ * end key.
+ */
+ END,
+
+ /**
+ * down arrow key.
+ */
+ DOWN,
+
+ /**
+ * PageDown/Next key.
+ */
+ PAGEDOWN_NEXT,
+
+ /**
+ * insert key.
+ */
+ INSERT,
+
+ /**
+ * delete key.
+ */
+ DELETE,
+
+ /**
+ * Left Windows/Option key
+ */
+ LMETA,
+
+ /**
+ * Right Windows/Option key
+ */
+ RMETA,
+
+ /**
+ * power key.
+ */
+ POWER,
+
+ /**
+ * sleep key.
+ */
+ SLEEP,
+
+ /**
+ * mobile call button
+ */
+ CALL,
+
+ /**
+ * mobile camera button
+ */
+ CAMERA,
+
+ /**
+ * mobile clear button
+ */
+ CLEAR,
+
+ /**
+ * dpad center button
+ */
+ CENTER,
+
+ /**
+ * mobile end call button
+ */
+ ENDCALL,
+
+ /**
+ * mobile envelope button
+ */
+ ENVELOPE,
+
+ /**
+ * mobile explorer button
+ */
+ EXPLORER,
+
+ /**
+ * mobile focus button
+ */
+ FOCUS,
+
+ /**
+ * mobile headsethook button
+ */
+ HEADSETHOOK,
+
+ /**
+ * mobile fast fwd button
+ */
+ MEDIA_FAST_FORWARD,
+
+ /**
+ * mobile next button
+ */
+ MEDIA_NEXT,
+
+ /**
+ * mobile play/pause button
+ */
+ PLAY_PAUSE,
+
+ /**
+ * mobile previous button
+ */
+ MEDIA_PREVIOUS,
+
+ /**
+ * mobile rewind button
+ */
+ MEDIA_REWIND,
+
+ /**
+ * mobile stop button
+ */
+ MEDIA_STOP,
+
+ /**
+ * mobile menu button
+ */
+ MENU,
+
+ /**
+ * mobile mute button
+ */
+ MUTE,
+
+ /**
+ * mobile notification button
+ */
+ NOTIFICATION,
+
+ /**
+ * plus key
+ */
+ PLUS,
+
+ /**
+ * pound key
+ */
+ POUND,
+
+ /**
+ * mobile call button
+ */
+ SEARCH,
+
+ /**
+ * mobile star button
+ */
+ STAR,
+
+ /**
+ * mobile # button
+ */
+ SYM,
+
+ /**
+ * volume down button
+ */
+ VOLUME_DOWN,
+
+ /**
+ * volume up button
+ */
+ VOLUME_UP;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java
new file mode 100644
index 0000000..3a13675
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyEvent.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.ardor3d.annotation.Immutable;
+
+/**
+ * Describes the state of a key - either it has been pressed or it has been released. Also keeps track of which
+ * character the event corresponds to - the difference between a key and a character is that a key corresponds to a
+ * physical key on the keyboard, whereas the character is the character that a keypress combination represents. Keys are
+ * universal and mapped into the Key enum, whereas characters can be any char value. Some examples of the differences:
+ * <ul>
+ * <li>On almost any keyboard, pressing {@link Key#EIGHT} results in the character '8'.</li>
+ * <li>On an English keyboard, pressing {@link Key#EIGHT} when the {@link Key#LSHIFT} is down leads to the character
+ * '*'.</li>
+ * <li>On a Swedish keyboard, pressing {@link Key#EIGHT} when the {@link Key#LSHIFT} is down leads to the character '('.
+ * </li>
+ * </ul>
+ */
+@Immutable
+public class KeyEvent {
+ public static final KeyEvent NOTHING = new KeyEvent(Key.UNKNOWN, KeyState.UP, (char) 0);
+
+ private final Key _key;
+ private final KeyState _state;
+ private final char _keyChar;
+
+ public KeyEvent(final Key key, final KeyState state, final char keyChar) {
+ _key = key;
+ _state = state;
+ _keyChar = keyChar;
+ }
+
+ public Key getKey() {
+ return _key;
+ }
+
+ public KeyState getState() {
+ return _state;
+ }
+
+ public char getKeyChar() {
+ return _keyChar;
+ }
+
+ @Override
+ public String toString() {
+ return "KeyEvent{" + "_key=" + _key + ", _state=" + _state + ", _keyChar=" + _keyChar + '}';
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java
new file mode 100644
index 0000000..4ca5df1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyNotFoundException.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Thrown when an attempt at fetching a {@link Key} instance for an invalid/unknown key code is made.
+ */
+public class KeyNotFoundException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public KeyNotFoundException(final int keyCode) {
+ super("No Key enum value found for code: " + keyCode);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java
new file mode 100644
index 0000000..770e2f3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyState.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Describes whether a key is down or up.
+ */
+public enum KeyState {
+ DOWN, UP
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java
new file mode 100644
index 0000000..d5f73bb
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardState.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import com.ardor3d.annotation.Immutable;
+
+/**
+ * A keyboard state at some point in time. Contains an EnumSet of the keys that are down, as well as a KeyEvent that
+ * describes the latest event (a key being pressed or released).
+ */
+@Immutable
+public class KeyboardState {
+ public static final KeyboardState NOTHING = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+
+ private final EnumSet<Key> _keysDown;
+ private final Set<Key> _keysDownView;
+ private final KeyEvent _keyEvent;
+
+ public KeyboardState(final EnumSet<Key> keysDown, final KeyEvent keyEvent) {
+ // keeping the keysDown as an EnumSet rather than as an unmodifiableSet in order to get
+ // the performance benefit of working with the fast implementations of contains(),
+ // removeAll(), etc., in EnumSet. The intention is that the keysDown set should never change.
+ // The reason why the performance benefits are lost when using an unmodifiableSet is that
+ // methods like containsAll(), etc., are not symmetrical in the EnumSet implementations.
+ // So typically, unmodifiableSet.containsAll(EnumSet) will be faster than
+ // enumSet.containsAll(unmodifiableSet).
+ _keysDown = keysDown;
+ _keyEvent = keyEvent;
+ _keysDownView = Collections.unmodifiableSet(keysDown);
+ }
+
+ public boolean isDown(final Key key) {
+ return _keysDown.contains(key);
+ }
+
+ public boolean isAllDown(final Key... keys) {
+ for (final Key key : keys) {
+ if (!_keysDown.contains(key)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean isAtLeastOneDown(final Key... keys) {
+ for (final Key key : keys) {
+ if (_keysDown.contains(key)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public Set<Key> getKeysDown() {
+ return _keysDownView;
+ }
+
+ public KeyEvent getKeyEvent() {
+ return _keyEvent;
+ }
+
+ public EnumSet<Key> getKeysReleasedSince(final KeyboardState previous) {
+ final EnumSet<Key> result = EnumSet.copyOf(previous._keysDown);
+
+ result.removeAll(_keysDown);
+
+ return result;
+ }
+
+ public EnumSet<Key> getKeysPressedSince(final KeyboardState previous) {
+ final EnumSet<Key> result = EnumSet.copyOf(_keysDown);
+
+ result.removeAll(previous._keysDown);
+
+ return result;
+
+ }
+
+ public EnumSet<Key> getKeysHeldSince(final KeyboardState previous) {
+ final EnumSet<Key> result = EnumSet.copyOf(_keysDown);
+
+ result.retainAll(previous._keysDown);
+
+ return result;
+
+ }
+
+ @Override
+ public String toString() {
+ return "KeyboardState{_keysDown=" + _keysDown + ", _keyEvent=" + _keyEvent + '}';
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java
new file mode 100644
index 0000000..106db04
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/KeyboardWrapper.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * Defines the API for keyboard wrappers.
+ */
+public interface KeyboardWrapper {
+ /**
+ * Allows the keyboard wrapper implementation to initialise itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all keyboard events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<KeyEvent> getEvents();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java
new file mode 100644
index 0000000..7c088a7
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseButton.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.EnumMap;
+
+import com.google.common.collect.Maps;
+
+public enum MouseButton {
+ LEFT, RIGHT, MIDDLE;
+
+ public static EnumMap<MouseButton, ButtonState> makeMap(final ButtonState left, final ButtonState right,
+ final ButtonState middle) {
+ if (left == null) {
+ throw new NullPointerException("left");
+ }
+ if (right == null) {
+ throw new NullPointerException("right");
+ }
+ if (middle == null) {
+ throw new NullPointerException("middle");
+ }
+ final EnumMap<MouseButton, ButtonState> map = Maps.newEnumMap(MouseButton.class);
+ map.put(LEFT, left);
+ map.put(RIGHT, right);
+ map.put(MIDDLE, middle);
+ return map;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java
new file mode 100644
index 0000000..e24e4ce
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseCursor.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.image.Image;
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * An immutable representation of a mouse cursor. A mouse cursor consists of an image and a hotspot where clicking is
+ * done.
+ *
+ */
+@Immutable
+public class MouseCursor {
+ /**
+ * This constant is used to identify that the native operating system's default cursor should be used. It is not a
+ * valid mouse cursor in itself.
+ */
+ public static final MouseCursor SYSTEM_DEFAULT = new MouseCursor("system default", new Image(ImageDataFormat.RGBA,
+ PixelDataType.UnsignedByte, 1, 1, BufferUtils.createByteBuffer(4), null), 0, 0);
+
+ private final String _name;
+ private final Image _image;
+ private final int _hotspotX;
+ private final int _hotspotY;
+
+ /**
+ * Instantiates a MouseCursor.
+ *
+ * @param name
+ * the name of this cursor, for debugging purposes.
+ * @param image
+ * the image that will be shown when this cursor is active.
+ * @param hotspotX
+ * the X coordinate of the clicking hotspot, 0 = left side
+ * @param hotspotY
+ * the Y coordinate of the clicking hotspot, 0 = bottom
+ */
+ public MouseCursor(final String name, final Image image, final int hotspotX, final int hotspotY) {
+ _name = name;
+ _image = image;
+ _hotspotX = hotspotX;
+ _hotspotY = hotspotY;
+
+ checkArgument(hotspotX >= 0 && hotspotX < image.getWidth(), "hotspot X is out of bounds: 0 <= %s < "
+ + image.getWidth(), hotspotX);
+ checkArgument(hotspotY >= 0 && hotspotY < image.getHeight(), "hotspot Y is out of bounds: 0 <= %s < "
+ + image.getHeight(), hotspotY);
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public Image getImage() {
+ return _image;
+ }
+
+ public int getWidth() {
+ return _image.getWidth();
+ }
+
+ public int getHeight() {
+ return _image.getHeight();
+ }
+
+ public int getHotspotX() {
+ return _hotspotX;
+ }
+
+ public int getHotspotY() {
+ return _hotspotY;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final MouseCursor that = (MouseCursor) o;
+
+ if (_hotspotX != that._hotspotX) {
+ return false;
+ }
+ if (_hotspotY != that._hotspotY) {
+ return false;
+ }
+ if (_image != null ? !_image.equals(that._image) : that._image != null) {
+ return false;
+ }
+ // noinspection RedundantIfStatement
+ if (_name != null ? !_name.equals(that._name) : that._name != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = _name != null ? _name.hashCode() : 0;
+ result = 31 * result + (_image != null ? _image.hashCode() : 0);
+ result = 31 * result + _hotspotX;
+ result = 31 * result + _hotspotY;
+ return result;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java
new file mode 100644
index 0000000..641df06
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseManager.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+/**
+ * Defines the contract for managing the native mouse.
+ */
+public interface MouseManager {
+ /**
+ * Change the mouse cursor presently used. This is a mandatory operation that all implementing classes must support.
+ *
+ * @param cursor
+ * the cursor to use
+ */
+ public void setCursor(MouseCursor cursor);
+
+ /**
+ * Optional method for changing the mouse cursor position to the specified coordinates. A client can confirm whether
+ * or not this method is support by calling {@link #isSetPositionSupported()}.
+ *
+ * @param x
+ * x position within the current canvas, 0 = left
+ * @param y
+ * y position within the current canvas, 0 = bottom
+ */
+ public void setPosition(int x, int y);
+
+ /**
+ * Optional method for changing the mouse to behave as if it is grabbed or not. A client can confirm whether or not
+ * this method is support by calling {@link #isSetGrabbedSupported()}.
+ *
+ * @param grabbedState
+ * the value determines which grabbed state is selected
+ */
+ public void setGrabbed(GrabbedState grabbedState);
+
+ /**
+ * @return current grabbed state of the mouse.
+ */
+ public GrabbedState getGrabbed();
+
+ /**
+ * Indicates to clients whether or not it is safe to call the {@link #setPosition(int, int)} method. Note that if
+ * this method returns false, a runtime exception may be thrown by the {@link #setPosition(int, int)} method.
+ *
+ * @return true if the mouse's position can be changed by this implementation, false otherwise.
+ */
+ public boolean isSetPositionSupported();
+
+ /**
+ * Indicates to clients whether or not it is safe to call the {@link #setGrabbed(GrabbedState)} method. Note that if
+ * this method returns false, a runtime exception may be thrown by the {@link #setGrabbed(GrabbedState)} method.
+ *
+ * @return true if the mouse's grabbed state can be changed by this implementation, false otherwise.
+ */
+ public boolean isSetGrabbedSupported();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java
new file mode 100644
index 0000000..8ee44ce
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseState.java
@@ -0,0 +1,264 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+
+import com.ardor3d.annotation.Immutable;
+import com.google.common.collect.EnumMultiset;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.ImmutableMultiset.Builder;
+
+/**
+ * Describes the mouse state at some point in time.
+ */
+@Immutable
+public class MouseState {
+ public static final MouseState NOTHING = new MouseState(0, 0, 0, 0, 0, null, null);
+ public static long CLICK_TIME_MS = 500;
+
+ private final int _x;
+ private final int _y;
+ private final int _dx;
+ private final int _dy;
+ private final int _dwheel;
+ private final ImmutableMap<MouseButton, ButtonState> _buttonStates;
+ private final ImmutableMultiset<MouseButton> _clickCounts;
+
+ /**
+ * Constructs a new MouseState instance.
+ *
+ * @param x
+ * the mouse's x position
+ * @param y
+ * the mouse's y position
+ * @param dx
+ * the delta in the mouse's x position since the last update
+ * @param dy
+ * the delta in the mouse's y position since the last update
+ * @param dwheel
+ * the delta in the mouse's wheel movement since the last update
+ * @param buttonStates
+ * the states of the various given buttons.
+ * @param clicks
+ * the number of times each button has been clicked
+ */
+ public MouseState(final int x, final int y, final int dx, final int dy, final int dwheel,
+ final EnumMap<MouseButton, ButtonState> buttonStates, final Multiset<MouseButton> clicks) {
+ _x = x;
+ _y = y;
+ _dx = dx;
+ _dy = dy;
+ _dwheel = dwheel;
+ if (buttonStates != null) {
+ final com.google.common.collect.ImmutableMap.Builder<MouseButton, ButtonState> builder = ImmutableMap
+ .builder();
+ _buttonStates = builder.putAll(buttonStates).build();
+ } else {
+ _buttonStates = ImmutableMap.of();
+ }
+ if (clicks != null) {
+ final Builder<MouseButton> builder = ImmutableMultiset.builder();
+ _clickCounts = builder.addAll(clicks).build();
+ } else {
+ _clickCounts = ImmutableMultiset.of();
+ }
+ }
+
+ public int getX() {
+ return _x;
+ }
+
+ public int getY() {
+ return _y;
+ }
+
+ public int getDx() {
+ return _dx;
+ }
+
+ public int getDy() {
+ return _dy;
+ }
+
+ public int getDwheel() {
+ return _dwheel;
+ }
+
+ /**
+ *
+ * @param state
+ * the button state to look for
+ * @return true if at least one mouse button is in the given button state.
+ */
+ public boolean hasButtonState(final ButtonState state) {
+ return _buttonStates.containsValue(state);
+ }
+
+ /**
+ *
+ * @param state
+ * the button to look for
+ * @return true if the given mouse button is currently mapped to a state.
+ */
+ public boolean hasButtonState(final MouseButton button) {
+ return _buttonStates.containsKey(button);
+ }
+
+ /**
+ * Returns all the buttons' states. It could be easier for most classes to use the
+ * {@link #getButtonState(MouseButton)} methods, and that also results in less object creation.
+ *
+ * @return a defensive copy of the states of all the buttons at this point in time.
+ */
+ public EnumMap<MouseButton, ButtonState> getButtonStates() {
+ return getButtonStates(null);
+ }
+
+ /**
+ * Returns all the buttons' states. It could be easier for most classes to use the
+ * {@link #getButtonState(MouseButton)} methods, and that also results in less object creation.
+ *
+ * @param store
+ * a map to store the states in... any values in store are cleared first. If store is null, a new map is
+ * created.
+ * @return a defensive copy of the states of all the buttons at this point in time.
+ */
+ public EnumMap<MouseButton, ButtonState> getButtonStates(final EnumMap<MouseButton, ButtonState> store) {
+ EnumMap<MouseButton, ButtonState> rVal = store;
+ if (store == null) {
+ rVal = Maps.newEnumMap(MouseButton.class);
+ }
+ rVal.clear();
+ rVal.putAll(_buttonStates);
+ return rVal;
+ }
+
+ /**
+ * Returns the current state for the supplied button, or UP if no state for that button is registered.
+ *
+ * @param button
+ * the mouse button to check
+ * @return the button's state, or {@link ButtonState#UP} if no button state registered.
+ */
+ public ButtonState getButtonState(final MouseButton button) {
+ if (_buttonStates.containsKey(button)) {
+ return _buttonStates.get(button);
+ }
+
+ return ButtonState.UP;
+ }
+
+ public EnumSet<MouseButton> getButtonsReleasedSince(final MouseState previous) {
+ final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class);
+ for (final MouseButton button : MouseButton.values()) {
+ if (previous.getButtonState(button) == ButtonState.DOWN) {
+ if (getButtonState(button) != ButtonState.DOWN) {
+ result.add(button);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public EnumSet<MouseButton> getButtonsPressedSince(final MouseState previous) {
+ final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class);
+ for (final MouseButton button : MouseButton.values()) {
+ if (getButtonState(button) == ButtonState.DOWN) {
+ if (previous.getButtonState(button) != ButtonState.DOWN) {
+ result.add(button);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns all the buttons' states. It could be easier for most classes to use the
+ * {@link #getClickCount(MouseButton)} method, and that also results in less object creation.
+ *
+ * @return a defensive copy of the click counts of all the buttons at this point in time.
+ */
+ public Multiset<MouseButton> getClickCounts() {
+ if (_clickCounts.isEmpty()) {
+ return EnumMultiset.create(MouseButton.class);
+ } else {
+ return EnumMultiset.create(_clickCounts);
+ }
+ }
+
+ public Multiset<MouseButton> getClickCounts(final EnumMultiset<MouseButton> store) {
+ final EnumMultiset<MouseButton> rVal = store;
+ if (store == null) {
+ if (_clickCounts.isEmpty()) {
+ return EnumMultiset.create(MouseButton.class);
+ } else {
+ return EnumMultiset.create(_clickCounts);
+ }
+ }
+ rVal.clear();
+ rVal.addAll(_clickCounts);
+ return rVal;
+ }
+
+ /**
+ * Returns the click count of a mouse button as of this frame. Click counts are non-zero only for frames when the
+ * mouse button is released. A double-click sequence, for instance, could show up like this:
+ * <nl>
+ * <li>Frame 1, mouse button pressed - click count == 0</li>
+ * <li>Frame 2, mouse button down - click count == 0</li>
+ * <li>Frame 3, mouse button released - click count == 1</li>
+ * <li>Frame 4, mouse button up - click count == 0</li>
+ * <li>Frame 5, mouse button pressed - click count == 0</li>
+ * <li>Frame 6, mouse button down - click count == 0</li>
+ * <li>Frame 7, mouse button released - click count == 2</li>
+ * </nl>
+ *
+ * Whether or not a mouse press/release sequence counts as a click (or double-click) depends on the time passed
+ * between them. See {@link #CLICK_TIME_MS}.
+ *
+ *
+ * @param button
+ * the button to check for clicks
+ * @return the click count in this frame
+ */
+ public int getClickCount(final MouseButton button) {
+ return _clickCounts.count(button);
+ }
+
+ /**
+ * Returns a new EnumSet of all buttons that were clicked this frame.
+ *
+ * @return every mouse button whose click count this frame is > 0
+ */
+ public EnumSet<MouseButton> getButtonsClicked() {
+ final EnumSet<MouseButton> result = EnumSet.noneOf(MouseButton.class);
+ for (final MouseButton button : MouseButton.values()) {
+ if (getClickCount(button) != 0) {
+ result.add(button);
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "MouseState{" + "x=" + _x + ", y=" + _y + ", dx=" + _dx + ", dy=" + _dy + ", dwheel=" + _dwheel
+ + ", buttonStates=" + _buttonStates.toString() + ", clickCounts=" + _clickCounts.toString() + '}';
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java
new file mode 100644
index 0000000..c44ef28
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/MouseWrapper.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * Defines the API for mouse wrappers.
+ */
+public interface MouseWrapper {
+ /**
+ * Allows the mouse wrapper implementation to initialize itself.
+ */
+ public void init();
+
+ /**
+ * Returns a peeking iterator that allows the client to loop through all mouse events that have not yet been
+ * handled.
+ *
+ * @return an iterator that allows the client to check which events have still not been handled
+ */
+ public PeekingIterator<MouseState> getEvents();
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java
new file mode 100644
index 0000000..fa125f9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/PhysicalLayer.java
@@ -0,0 +1,215 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import java.util.EnumSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+import com.ardor3d.input.logical.DummyControllerWrapper;
+import com.ardor3d.input.logical.DummyFocusWrapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * Provides access to the physical layer of the input system. This is done via one method that polls the input system,
+ * causing it to track which states it has been in {@link #readState()}, and one method that fetches the list of states
+ * that are new {@link #drainAvailableStates()}.
+ */
+public class PhysicalLayer {
+
+ private static final Logger logger = Logger.getLogger(PhysicalLayer.class.getName());
+
+ private final BlockingQueue<InputState> _stateQueue;
+ private final KeyboardWrapper _keyboardWrapper;
+ private final MouseWrapper _mouseWrapper;
+ private final FocusWrapper _focusWrapper;
+ private final ControllerWrapper _controllerWrapper;
+
+ private KeyboardState _currentKeyboardState;
+ private MouseState _currentMouseState;
+ private ControllerState _currentControllerState;
+
+ private boolean _inited = false;
+
+ private static final long MAX_INPUT_POLL_TIME = TimeUnit.SECONDS.toNanos(2);
+ private static final List<InputState> EMPTY_LIST = ImmutableList.of();
+
+ public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper) {
+ this(keyboardWrapper, mouseWrapper, DummyControllerWrapper.INSTANCE, DummyFocusWrapper.INSTANCE);
+ }
+
+ public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper,
+ final FocusWrapper focusWrapper) {
+ this(keyboardWrapper, mouseWrapper, DummyControllerWrapper.INSTANCE, focusWrapper);
+ }
+
+ public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper,
+ final ControllerWrapper controllerWrapper) {
+ this(keyboardWrapper, mouseWrapper, controllerWrapper, DummyFocusWrapper.INSTANCE);
+ }
+
+ public PhysicalLayer(final KeyboardWrapper keyboardWrapper, final MouseWrapper mouseWrapper,
+ final ControllerWrapper controllerWrapper, final FocusWrapper focusWrapper) {
+ _keyboardWrapper = keyboardWrapper;
+ _mouseWrapper = mouseWrapper;
+ _focusWrapper = focusWrapper;
+ _controllerWrapper = controllerWrapper;
+ _stateQueue = new LinkedBlockingQueue<InputState>();
+
+ _currentKeyboardState = KeyboardState.NOTHING;
+ _currentMouseState = MouseState.NOTHING;
+ }
+
+ /**
+ * Causes a poll of the input devices to happen, making any updates to input states available via the
+ * {@link #drainAvailableStates()} method.
+ *
+ * @throws IllegalStateException
+ * if too many state changes have happened since the last call to this method
+ */
+ public void readState() {
+ if (!_inited) {
+ init();
+ }
+
+ KeyboardState oldKeyState = _currentKeyboardState;
+ if (_currentMouseState.getDwheel() == 0 && _currentMouseState.getDx() == 0 && _currentMouseState.getDy() == 0) {
+ // we can reuse - do nothing
+ } else {
+ // we can't reuse
+ _currentMouseState = new MouseState(_currentMouseState.getX(), _currentMouseState.getY(), 0, 0, 0,
+ _currentMouseState.getButtonStates(), _currentMouseState.getClickCounts());
+ }
+ MouseState oldMouseState = _currentMouseState;
+ ControllerState oldControllerState = _currentControllerState;
+
+ final long loopExitTime = System.nanoTime() + MAX_INPUT_POLL_TIME;
+
+ while (true) {
+ readKeyboardState();
+ readMouseState();
+ readControllerState();
+
+ // if there is no new input, exit the loop. Otherwise, add a new input state to the queue, and
+ // see if there is even more input to read.
+ if (oldKeyState.equals(_currentKeyboardState) && oldMouseState.equals(_currentMouseState)
+ && oldControllerState.equals(_currentControllerState)) {
+ break;
+ }
+
+ _stateQueue.add(new InputState(_currentKeyboardState, _currentMouseState, _currentControllerState));
+
+ oldKeyState = _currentKeyboardState;
+ oldMouseState = _currentMouseState;
+ oldControllerState = _currentControllerState;
+
+ if (System.nanoTime() > loopExitTime) {
+ logger.severe("Spent too long collecting input data, this is probably an input system bug");
+ break;
+ }
+ }
+
+ if (_focusWrapper.getAndClearFocusLost()) {
+ lostFocus();
+ }
+ }
+
+ private void readControllerState() {
+ final PeekingIterator<ControllerEvent> eventIterator = _controllerWrapper.getEvents();
+
+ if (eventIterator.hasNext()) {
+ _currentControllerState = new ControllerState();
+ while (eventIterator.hasNext()) {
+ final ControllerEvent event = eventIterator.next();
+ _currentControllerState.addEvent(event);
+ }
+ }
+ }
+
+ private void readMouseState() {
+ final PeekingIterator<MouseState> eventIterator = _mouseWrapper.getEvents();
+
+ if (eventIterator.hasNext()) {
+ _currentMouseState = eventIterator.next();
+ }
+ }
+
+ private void readKeyboardState() {
+ final PeekingIterator<KeyEvent> eventIterator = _keyboardWrapper.getEvents();
+
+ // if no new events, just leave the current state as is
+ if (!eventIterator.hasNext()) {
+ return;
+ }
+
+ final KeyEvent keyEvent = eventIterator.next();
+
+ // EnumSet.copyOf fails if the collection is empty, since it needs at least one object to
+ // figure out which type of enum to deal with. Hence the check below.
+ final EnumSet<Key> keysDown = _currentKeyboardState.getKeysDown().isEmpty() ? EnumSet.noneOf(Key.class)
+ : EnumSet.copyOf(_currentKeyboardState.getKeysDown());
+
+ if (keyEvent.getState() == KeyState.DOWN) {
+ keysDown.add(keyEvent.getKey());
+ } else {
+ // ignore the fact that this removal might fail - for instance, at startup, the
+ // set of keys tracked as down will be empty even if somebody presses a key when the
+ // app starts.
+ keysDown.remove(keyEvent.getKey());
+ }
+
+ _currentKeyboardState = new KeyboardState(keysDown, keyEvent);
+ }
+
+ /**
+ * Fetches any new <code>InputState</code>s since the last call to this method. If no input system changes have been
+ * made since the last call (no mouse movements, no keys pressed or released), an empty list is returned.
+ *
+ * @return the list of new <code>InputState</code>, or an empty list if there have been no changes in input
+ */
+ public List<InputState> drainAvailableStates() {
+ // returning a reusable empty list to avoid object creation if there is no new
+ // input available. There is a race condition here (input might become available right after
+ // the check of isEmpty()) but that's OK, it won't do any harm if that is picked up next frame.
+ if (_stateQueue.isEmpty()) {
+ return EMPTY_LIST;
+ }
+
+ final LinkedList<InputState> result = new LinkedList<InputState>();
+
+ _stateQueue.drainTo(result);
+
+ return result;
+ }
+
+ private void lostFocus() {
+ _stateQueue.add(InputState.LOST_FOCUS);
+ _currentKeyboardState = KeyboardState.NOTHING;
+ _currentMouseState = MouseState.NOTHING;
+ _currentControllerState = _controllerWrapper.getBlankState();
+ }
+
+ private void init() {
+ _inited = true;
+
+ _keyboardWrapper.init();
+ _mouseWrapper.init();
+ _focusWrapper.init();
+ _controllerWrapper.init();
+
+ _currentControllerState = _controllerWrapper.getBlankState();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java
new file mode 100644
index 0000000..d93f9c0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/FirstPersonControl.java
@@ -0,0 +1,322 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.control;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+public class FirstPersonControl {
+
+ protected final Vector3 _upAxis = new Vector3();
+ protected double _mouseRotateSpeed = .005;
+ protected double _moveSpeed = 50;
+ protected double _keyRotateSpeed = 2.25;
+ protected final Matrix3 _workerMatrix = new Matrix3();
+ protected final Vector3 _workerStoreA = new Vector3();
+ protected InputTrigger _mouseTrigger;
+ protected InputTrigger _keyTrigger;
+
+ protected boolean _clampVerticalAngle = false;
+ protected double _minVerticalAngle = -60 * MathUtils.DEG_TO_RAD;
+ protected double _maxVerticalAngle = 60 * MathUtils.DEG_TO_RAD;
+
+ public FirstPersonControl(final ReadOnlyVector3 upAxis) {
+ _upAxis.set(upAxis);
+ }
+
+ public ReadOnlyVector3 getUpAxis() {
+ return _upAxis;
+ }
+
+ public void setUpAxis(final ReadOnlyVector3 upAxis) {
+ _upAxis.set(upAxis);
+ }
+
+ public double getMouseRotateSpeed() {
+ return _mouseRotateSpeed;
+ }
+
+ public void setMouseRotateSpeed(final double speed) {
+ _mouseRotateSpeed = speed;
+ }
+
+ public double getMoveSpeed() {
+ return _moveSpeed;
+ }
+
+ public void setMoveSpeed(final double speed) {
+ _moveSpeed = speed;
+ }
+
+ public double getKeyRotateSpeed() {
+ return _keyRotateSpeed;
+ }
+
+ public void setKeyRotateSpeed(final double speed) {
+ _keyRotateSpeed = speed;
+ }
+
+ protected void move(final Camera camera, final KeyboardState kb, final double tpf) {
+ // MOVEMENT
+ int moveFB = 0, strafeLR = 0;
+ if (kb.isDown(Key.W)) {
+ moveFB += 1;
+ }
+ if (kb.isDown(Key.S)) {
+ moveFB -= 1;
+ }
+ if (kb.isDown(Key.A)) {
+ strafeLR += 1;
+ }
+ if (kb.isDown(Key.D)) {
+ strafeLR -= 1;
+ }
+
+ if (moveFB != 0 || strafeLR != 0) {
+ final Vector3 loc = _workerStoreA.zero();
+ if (moveFB == 1) {
+ loc.addLocal(camera.getDirection());
+ } else if (moveFB == -1) {
+ loc.subtractLocal(camera.getDirection());
+ }
+ if (strafeLR == 1) {
+ loc.addLocal(camera.getLeft());
+ } else if (strafeLR == -1) {
+ loc.subtractLocal(camera.getLeft());
+ }
+ loc.normalizeLocal().multiplyLocal(_moveSpeed * tpf).addLocal(camera.getLocation());
+ camera.setLocation(loc);
+ }
+
+ // ROTATION
+ int rotX = 0, rotY = 0;
+ if (kb.isDown(Key.UP)) {
+ rotY -= 1;
+ }
+ if (kb.isDown(Key.DOWN)) {
+ rotY += 1;
+ }
+ if (kb.isDown(Key.LEFT)) {
+ rotX += 1;
+ }
+ if (kb.isDown(Key.RIGHT)) {
+ rotX -= 1;
+ }
+ if (rotX != 0 || rotY != 0) {
+ rotate(camera, rotX * (_keyRotateSpeed / _mouseRotateSpeed) * tpf, rotY
+ * (_keyRotateSpeed / _mouseRotateSpeed) * tpf);
+ }
+ }
+
+ protected void rotate(final Camera camera, final double dx, final double dy) {
+ if (dx != 0) {
+ applyDx(dx, camera);
+ }
+
+ if (dy != 0) {
+ applyDY(dy, camera);
+ }
+
+ if (dx != 0 || dy != 0) {
+ camera.normalize();
+ }
+ }
+
+ private void applyDx(final double dx, final Camera camera) {
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx, _upAxis);
+ _workerMatrix.applyPost(camera.getLeft(), _workerStoreA);
+ camera.setLeft(_workerStoreA);
+ _workerMatrix.applyPost(camera.getDirection(), _workerStoreA);
+ camera.setDirection(_workerStoreA);
+ _workerMatrix.applyPost(camera.getUp(), _workerStoreA);
+ camera.setUp(_workerStoreA);
+ }
+
+ private void applyDY(final double dy, final Camera camera) {
+ // apply dy angle change to direction vector
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy, camera.getLeft());
+ _workerMatrix.applyPost(camera.getDirection(), _workerStoreA);
+ camera.setDirection(_workerStoreA);
+
+ // do we want to constrain our vertical angle?
+ if (isClampVerticalAngle()) {
+ // check if we went out of bounds and back up
+ final double angleV = MathUtils.HALF_PI - _workerStoreA.smallestAngleBetween(_upAxis);
+ if (angleV > getMaxVerticalAngle() || angleV < getMinVerticalAngle()) {
+ // clamp the angle to our range
+ final double newAngle = MathUtils.clamp(angleV, getMinVerticalAngle(), getMaxVerticalAngle());
+ // take the difference in angles and back up the direction vector
+ _workerMatrix.fromAngleNormalAxis(-(newAngle - angleV), camera.getLeft());
+ _workerMatrix.applyPost(camera.getDirection(), _workerStoreA);
+ camera.setDirection(_workerStoreA);
+ // figure out new up vector by crossing direction and left.
+ camera.getDirection().cross(camera.getLeft(), _workerStoreA);
+ camera.setUp(_workerStoreA);
+ return;
+ }
+ }
+
+ // just apply to up vector
+ _workerMatrix.applyPost(camera.getUp(), _workerStoreA);
+ camera.setUp(_workerStoreA);
+ }
+
+ /**
+ * @param layer
+ * the logical layer to register with
+ * @param upAxis
+ * the up axis of the camera
+ * @param dragOnly
+ * if true, mouse input will only rotate the camera if one of the mouse buttons (left, center or right)
+ * is down.
+ * @return a new FirstPersonControl object
+ */
+ public static FirstPersonControl setupTriggers(final LogicalLayer layer, final ReadOnlyVector3 upAxis,
+ final boolean dragOnly) {
+
+ final FirstPersonControl control = new FirstPersonControl(upAxis);
+ control.setupKeyboardTriggers(layer);
+ control.setupMouseTriggers(layer, dragOnly);
+ return control;
+ }
+
+ /**
+ * Deregister the triggers of the given FirstPersonControl from the given LogicalLayer.
+ *
+ * @param layer
+ * @param control
+ */
+ public static void removeTriggers(final LogicalLayer layer, final FirstPersonControl control) {
+ if (control._mouseTrigger != null) {
+ layer.deregisterTrigger(control._mouseTrigger);
+ }
+ if (control._keyTrigger != null) {
+ layer.deregisterTrigger(control._keyTrigger);
+ }
+ }
+
+ public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) {
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(),
+ Predicates.or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown()));
+ final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown);
+ final TriggerAction dragAction = new TriggerAction() {
+
+ // Test boolean to allow us to ignore first mouse event. First event can wildly vary based on platform.
+ private boolean firstPing = true;
+
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ final MouseState mouse = inputStates.getCurrent().getMouseState();
+ if (mouse.getDx() != 0 || mouse.getDy() != 0) {
+ if (!firstPing) {
+ FirstPersonControl.this.rotate(source.getCanvasRenderer().getCamera(), -mouse.getDx(),
+ -mouse.getDy());
+ } else {
+ firstPing = false;
+ }
+ }
+ }
+ };
+
+ _mouseTrigger = new InputTrigger(dragOnly ? dragged : TriggerConditions.mouseMoved(), dragAction);
+ layer.registerTrigger(_mouseTrigger);
+ }
+
+ public Predicate<TwoInputStates> setupKeyboardTriggers(final LogicalLayer layer) {
+ // WASD control
+ final Predicate<TwoInputStates> keysHeld = new Predicate<TwoInputStates>() {
+ Key[] keys = new Key[] { Key.W, Key.A, Key.S, Key.D, Key.LEFT, Key.RIGHT, Key.UP, Key.DOWN };
+
+ public boolean apply(final TwoInputStates states) {
+ for (final Key k : keys) {
+ if (states.getCurrent() != null && states.getCurrent().getKeyboardState().isDown(k)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ final TriggerAction moveAction = new TriggerAction() {
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ FirstPersonControl.this.move(source.getCanvasRenderer().getCamera(), inputStates.getCurrent()
+ .getKeyboardState(), tpf);
+ }
+ };
+ _keyTrigger = new InputTrigger(keysHeld, moveAction);
+ layer.registerTrigger(_keyTrigger);
+ return keysHeld;
+ }
+
+ public InputTrigger getKeyTrigger() {
+ return _keyTrigger;
+ }
+
+ public InputTrigger getMouseTrigger() {
+ return _mouseTrigger;
+ }
+
+ public boolean isClampVerticalAngle() {
+ return _clampVerticalAngle;
+ }
+
+ /**
+ * @param clampVerticalAngle
+ * if true, the vertical angle of the camera is locked between the minimum and maximum angles (default is
+ * [-60, 60])
+ */
+ public void setClampVerticalAngle(final boolean clampVerticalAngle) {
+ _clampVerticalAngle = clampVerticalAngle;
+ }
+
+ public double getMinVerticalAngle() {
+ return _minVerticalAngle;
+ }
+
+ /**
+ * @param minVerticalAngle
+ * the new minimum angle, in radians, to clamp our vertical angle to. Defaults to -60 degrees (in
+ * radians). Must be less than the max angle. Has no effect unless clampVerticalAngle is true.
+ * @see #setClampVerticalAngle(boolean)
+ */
+ public void setMinVerticalAngle(final double minVerticalAngle) {
+ _minVerticalAngle = minVerticalAngle;
+ }
+
+ public double getMaxVerticalAngle() {
+ return _maxVerticalAngle;
+ }
+
+ /**
+ *
+ * @param maxVerticalAngle
+ * the new maximum angle, in radians, to clamp our vertical angle to. Defaults to +60 degrees (in
+ * radians). Must be less than the max angle. Has no effect unless clampVerticalAngle is true.
+ * @see #setClampVerticalAngle(boolean)
+ */
+ public void setMaxVerticalAngle(final double maxVerticalAngle) {
+ _maxVerticalAngle = maxVerticalAngle;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java
new file mode 100644
index 0000000..2be9c8b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/control/OrbitCamControl.java
@@ -0,0 +1,382 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.control;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.MouseWheelMovedCondition;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.scenegraph.Spatial;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+
+/**
+ * <p>
+ * Orbital type Camera controller. Basically, this class references a camera and provides methods for moving that camera
+ * around a target position or spatial using spherical polar coordinates.
+ * </p>
+ * <p>
+ * To use, create a new instance of OrbitCamController. Update the controller in your update loop.
+ * </p>
+ * <p>
+ * Example: Creates a new control, adds mouse triggers to a Logical Layer, and sets the default location (15 units away,
+ * 0 degrees ascent, 0 degrees azimuth).
+ * </p>
+ *
+ * <pre>
+ * // ... in init
+ * control = new OrbitCamControl(myCamera, targetLocation);
+ * control.setupMouseTriggers(myLogicalLayer, true);
+ * control.setSphereCoords(15, 0, 0);
+ *
+ * // ...in update loop
+ * control.update(timer.getTimePerFrame());
+ * </pre>
+ */
+public class OrbitCamControl {
+
+ /**
+ * Our absolute min/max ascent (pitch) angle, in radians. This is set at 89.95 degrees to prevent the camera's
+ * direction from becoming parallel to the world up vector.
+ */
+ public static final double ABSOLUTE_MAXASCENT = 89.95 * MathUtils.DEG_TO_RAD;
+
+ /**
+ * The camera we are modifying.
+ */
+ protected Camera _camera;
+ protected Vector3 _worldUpVec = new Vector3(Vector3.UNIT_Y);
+
+ protected Vector3 _sphereCoords = new Vector3();
+ protected Vector3 _camPosition = new Vector3();
+
+ protected Vector3 _lookAtPoint = new Vector3();
+ protected Spatial _lookAtSpatial = null;
+ protected TargetType _targetType;
+
+ protected boolean _invertedX = false;
+ protected boolean _invertedY = false;
+ protected boolean _invertedWheel = true;
+
+ protected double _zoomSpeed = 0.01;
+ protected double _baseDistance = 15;
+ protected double _minZoomDistance = 1;
+ protected double _maxZoomDistance = 100;
+
+ protected double _minAscent = -ABSOLUTE_MAXASCENT;
+ protected double _maxAscent = ABSOLUTE_MAXASCENT;
+ protected double _xSpeed = 0.01;
+ protected double _ySpeed = 0.01;
+
+ protected boolean _dirty = true;
+
+ protected InputTrigger _mouseTrigger;
+
+ public enum TargetType {
+ Point, Spatial
+ }
+
+ /**
+ * Construct a new orbit controller
+ *
+ * @param cam
+ * the camera to control
+ * @param target
+ * a world location to lock our sights on.
+ */
+ public OrbitCamControl(final Camera cam, final ReadOnlyVector3 target) {
+ _camera = cam;
+ _targetType = TargetType.Point;
+ _lookAtPoint.set(target);
+ }
+
+ /**
+ * Construct a new orbit controller
+ *
+ * @param cam
+ * the camera to control
+ * @param target
+ * a spatial whose world location we'll lock our sights on.
+ */
+ public OrbitCamControl(final Camera cam, final Spatial target) {
+ _camera = cam;
+ _targetType = TargetType.Spatial;
+ _lookAtSpatial = target;
+ }
+
+ public Camera getCamera() {
+ return _camera;
+ }
+
+ public void setCamera(final Camera camera) {
+ _camera = camera;
+ }
+
+ public ReadOnlyVector3 getWorldUpVec() {
+ return _worldUpVec;
+ }
+
+ public void setWorldUpVec(final ReadOnlyVector3 worldUpVec) {
+ _worldUpVec.set(worldUpVec);
+ _dirty = true;
+ }
+
+ public void setInvertedWheel(final boolean invertedWheel) {
+ _invertedWheel = invertedWheel;
+ }
+
+ public boolean isInvertedWheel() {
+ return _invertedWheel;
+ }
+
+ public void setInvertedX(final boolean invertedX) {
+ _invertedX = invertedX;
+ }
+
+ public boolean isInvertedX() {
+ return _invertedX;
+ }
+
+ public void setInvertedY(final boolean invertedY) {
+ _invertedY = invertedY;
+ }
+
+ public boolean isInvertedY() {
+ return _invertedY;
+ }
+
+ public Vector3 getLookAtPoint() {
+ return _lookAtPoint;
+ }
+
+ /**
+ * Sets a specific world location for the camera to point at and circle around.
+ *
+ * @param point
+ */
+ public void setLookAtPoint(final Vector3 point) {
+ _dirty = !point.equals(_lookAtPoint);
+ _lookAtPoint = point;
+ _targetType = TargetType.Point;
+ }
+
+ public Spatial getLookAtSpatial() {
+ return _lookAtSpatial;
+ }
+
+ /**
+ * Sets a spatial to look at. We'll use the world transform of the spatial, so its transform needs to be up to date.
+ *
+ * @param spatial
+ */
+ public void setLookAtSpatial(final Spatial spatial) {
+ _dirty = spatial != _lookAtSpatial; // identity equality
+ _lookAtSpatial = spatial;
+ _targetType = TargetType.Spatial;
+ }
+
+ public TargetType getTargetType() {
+ return _targetType;
+ }
+
+ public double getZoomSpeed() {
+ return _zoomSpeed;
+ }
+
+ public void setZoomSpeed(final double zoomSpeed) {
+ _zoomSpeed = zoomSpeed;
+ }
+
+ public double getBaseDistance() {
+ return _baseDistance;
+ }
+
+ public void setBaseDistance(final double baseDistance) {
+ _baseDistance = baseDistance;
+ zoom(0);
+ }
+
+ public double getMaxAscent() {
+ return _maxAscent;
+ }
+
+ public void setMaxAscent(final double maxAscent) {
+ _maxAscent = Math.min(maxAscent, ABSOLUTE_MAXASCENT);
+ move(0, 0);
+ }
+
+ public double getMinAscent() {
+ return _minAscent;
+ }
+
+ public void setMinAscent(final double minAscent) {
+ _minAscent = Math.max(minAscent, -ABSOLUTE_MAXASCENT);
+ move(0, 0);
+ }
+
+ public double getMaxZoomDistance() {
+ return _maxZoomDistance;
+ }
+
+ public void setMaxZoomDistance(final double maxZoomDistance) {
+ _maxZoomDistance = maxZoomDistance;
+ zoom(0);
+ }
+
+ public double getMinZoomDistance() {
+ return _minZoomDistance;
+ }
+
+ public void setMinZoomDistance(final double minZoomDistance) {
+ _minZoomDistance = minZoomDistance;
+ zoom(0);
+ }
+
+ public double getXSpeed() {
+ return _xSpeed;
+ }
+
+ public void setXSpeed(final double speed) {
+ _xSpeed = speed;
+ }
+
+ public double getYSpeed() {
+ return _ySpeed;
+ }
+
+ public void setYSpeed(final double speed) {
+ _ySpeed = speed;
+ }
+
+ public void setSphereCoords(final ReadOnlyVector3 sphereCoords) {
+ _sphereCoords.set(sphereCoords);
+ makeDirty();
+ }
+
+ public void setSphereCoords(final double x, final double y, final double z) {
+ _sphereCoords.set(x, y, z);
+ makeDirty();
+ }
+
+ protected void updateTargetPos() {
+ if (_targetType == TargetType.Spatial) {
+ final double x = _lookAtPoint.getX();
+ final double y = _lookAtPoint.getY();
+ final double z = _lookAtPoint.getZ();
+ _lookAtSpatial.getWorldTransform().applyForward(Vector3.ZERO, _lookAtPoint);
+ if (x != _lookAtPoint.getX() || y != _lookAtPoint.getY() || z != _lookAtPoint.getZ()) {
+ makeDirty();
+ }
+ }
+ }
+
+ public void makeDirty() {
+ _dirty = true;
+ }
+
+ /**
+ * Zoom camera in/out from the target point.
+ *
+ * @param percent
+ * a value applied to the baseDistance to determine how far in/out to zoom. Inverted if
+ * {@link #isInvertedWheel()} is true.
+ */
+ public void zoom(final double percent) {
+ final double amount = (_invertedWheel ? -1 : 1) * percent * _baseDistance;
+ _sphereCoords.setX(MathUtils.clamp(_sphereCoords.getX() + amount, _minZoomDistance, _maxZoomDistance));
+ makeDirty();
+ }
+
+ /**
+ *
+ * @param xDif
+ * a value applied to the azimuth value of our spherical coordinates. Inverted if {@link #isInvertedX()}
+ * is true.
+ * @param yDif
+ * a value applied to the theta value of our spherical coordinates. Inverted if {@link #isInvertedY()} is
+ * true.
+ */
+ public void move(final double xDif, final double yDif) {
+ final double azimuthAccel = _invertedX ? -xDif : xDif;
+ final double thetaAccel = _invertedY ? -yDif : yDif;
+
+ // update our master spherical coords, using x and y movement
+ _sphereCoords.setY(MathUtils.moduloPositive(_sphereCoords.getY() - azimuthAccel, MathUtils.TWO_PI));
+ _sphereCoords.setZ(MathUtils.clamp(_sphereCoords.getZ() + thetaAccel, _minAscent, _maxAscent));
+ makeDirty();
+ }
+
+ /**
+ * Update the position of the Camera controlled by this object.
+ *
+ * @param time
+ * a delta time, in seconds. Not used currently, but might be useful for doing "ease-in" of camera
+ * movements.
+ */
+ public void update(final double time) {
+ updateTargetPos();
+
+ if (!_dirty) {
+ return;
+ }
+ if (_worldUpVec.getY() == 1) {
+ MathUtils.sphericalToCartesian(_sphereCoords, _camPosition);
+ } else if (_worldUpVec.getZ() == 1) {
+ MathUtils.sphericalToCartesianZ(_sphereCoords, _camPosition);
+ }
+
+ _camera.setLocation(_camPosition.addLocal(_lookAtPoint));
+
+ _camera.lookAt(_lookAtPoint, _worldUpVec);
+ _dirty = false;
+ }
+
+ public void setupMouseTriggers(final LogicalLayer layer, final boolean dragOnly) {
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown = Predicates.or(TriggerConditions.leftButtonDown(), Predicates
+ .or(TriggerConditions.rightButtonDown(), TriggerConditions.middleButtonDown()));
+ final Predicate<TwoInputStates> scrollWheelMoved = new MouseWheelMovedCondition();
+ final Predicate<TwoInputStates> dragged = Predicates.and(TriggerConditions.mouseMoved(), someMouseDown);
+ final TriggerAction mouseAction = new TriggerAction() {
+
+ // Test boolean to allow us to ignore first mouse event. First event can wildly vary based on platform.
+ private boolean firstPing = true;
+
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ final MouseState mouse = inputStates.getCurrent().getMouseState();
+ if (mouse.getDx() != 0 || mouse.getDy() != 0) {
+ if (!firstPing) {
+ move(_xSpeed * mouse.getDx(), _ySpeed * mouse.getDy());
+ } else {
+ firstPing = false;
+ }
+ }
+
+ if (mouse.getDwheel() != 0) {
+ zoom(_zoomSpeed * mouse.getDwheel());
+ }
+ }
+ };
+
+ final Predicate<TwoInputStates> predicate = Predicates.or(scrollWheelMoved, dragOnly ? dragged
+ : TriggerConditions.mouseMoved());
+ _mouseTrigger = new InputTrigger(predicate, mouseAction);
+ layer.registerTrigger(_mouseTrigger);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java
new file mode 100644
index 0000000..ae3dcc1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyControllerCondition.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class AnyControllerCondition implements Predicate<TwoInputStates> {
+
+ public boolean apply(final TwoInputStates states) {
+ final ControllerState oldState = states.getPrevious().getControllerState();
+ final ControllerState currentState = states.getCurrent().getControllerState();
+
+ final boolean apply = !oldState.equals(currentState);
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java
new file mode 100644
index 0000000..5ab407f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/AnyKeyCondition.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.InputState;
+import com.google.common.base.Predicate;
+
+/**
+ * Applicable whenever 'any' key has been pressed.
+ */
+public class AnyKeyCondition implements Predicate<TwoInputStates> {
+ public boolean apply(final TwoInputStates twoInputStates) {
+ final InputState currentState = twoInputStates.getCurrent();
+ final InputState previousState = twoInputStates.getPrevious();
+
+ return !currentState.getKeyboardState().getKeysPressedSince(previousState.getKeyboardState()).isEmpty();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java
new file mode 100644
index 0000000..5b77235
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/BasicTriggersApplier.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.Set;
+
+import com.ardor3d.framework.Canvas;
+
+public class BasicTriggersApplier implements LogicalTriggersApplier {
+
+ public void checkAndPerformTriggers(final Set<InputTrigger> triggers, final Canvas source,
+ final TwoInputStates states, final double tpf) {
+ for (final InputTrigger trigger : triggers) {
+ trigger.performIfValid(source, states, tpf);
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java
new file mode 100644
index 0000000..9f76c6a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerComponentCondition.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.List;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class ControllerComponentCondition implements Predicate<TwoInputStates> {
+
+ private int controllerIndex = -1;
+ private int componentIndex = -1;
+ private String controllerName = null;
+ private String componentName = null;
+
+ public ControllerComponentCondition(final int controller, final int component) {
+ controllerIndex = controller;
+ componentIndex = component;
+ }
+
+ public ControllerComponentCondition(final String controller, final String component) {
+ controllerName = controller;
+ componentName = component;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ boolean apply = false;
+ final ControllerState currentState = states.getCurrent().getControllerState();
+ final ControllerState previousState = states.getPrevious().getControllerState();
+
+ if (!previousState.equals(currentState)) {
+
+ if (controllerName == null) {
+ controllerName = currentState.getControllerNames().get(controllerIndex);
+ }
+ if (componentName == null) {
+ componentName = currentState.getControllerComponentNames(controllerName).get(componentIndex);
+ }
+
+ final List<ControllerEvent> events = currentState.getEvents();
+ for (final ControllerEvent event : events) {
+ if (event.getControllerName().equals(controllerName) && event.getComponentName().equals(componentName)) {
+ apply = true;
+ }
+ }
+ }
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java
new file mode 100644
index 0000000..79a4a02
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/ControllerCondition.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.List;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.google.common.base.Predicate;
+
+public final class ControllerCondition implements Predicate<TwoInputStates> {
+
+ private int controllerIndex = -1;
+ private String controllerName = null;
+
+ public ControllerCondition(final int controller) {
+ controllerIndex = controller;
+ }
+
+ public ControllerCondition(final String controller) {
+ controllerName = controller;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ boolean apply = false;
+ final ControllerState currentState = states.getCurrent().getControllerState();
+ final ControllerState previousState = states.getPrevious().getControllerState();
+
+ if (!previousState.equals(currentState)) {
+ if (controllerName == null) {
+ controllerName = currentState.getControllerNames().get(controllerIndex);
+ }
+ final List<ControllerEvent> events = currentState.getEvents();
+ for (final ControllerEvent event : events) {
+ if (event.getControllerName().equals(controllerName)) {
+ apply = true;
+ }
+ }
+ }
+ return apply;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java
new file mode 100644
index 0000000..aba7f9d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyControllerWrapper.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.ControllerEvent;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.ControllerWrapper;
+import com.google.common.collect.PeekingIterator;
+
+public class DummyControllerWrapper implements ControllerWrapper {
+ public static final DummyControllerWrapper INSTANCE = new DummyControllerWrapper();
+
+ PeekingIterator<ControllerEvent> empty = new PeekingIterator<ControllerEvent>() {
+ public boolean hasNext() {
+ return false;
+ }
+
+ public void remove() {}
+
+ public ControllerEvent peek() {
+ return null;
+ }
+
+ public ControllerEvent next() {
+ return null;
+ }
+ };
+
+ public ControllerState getBlankState() {
+ return new ControllerState();
+ }
+
+ public PeekingIterator<ControllerEvent> getEvents() {
+ return empty;
+ }
+
+ public void init() {
+ ; // ignore, does nothing
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java
new file mode 100644
index 0000000..7ff0c3e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyFocusWrapper.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.FocusWrapper;
+
+/**
+ * A "do-nothing" implementation of FocusWrapper useful when you want to ignore (or do not need) focus events.
+ */
+public class DummyFocusWrapper implements FocusWrapper {
+ public static final DummyFocusWrapper INSTANCE = new DummyFocusWrapper();
+
+ public void init() {
+ ; // ignore, does nothing
+ }
+
+ public boolean getAndClearFocusLost() {
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java
new file mode 100644
index 0000000..3de004e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyKeyboardWrapper.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.KeyEvent;
+import com.ardor3d.input.KeyboardWrapper;
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * A "do-nothing" implementation of KeyboardWrapper useful when you want to ignore (or do not need) key events.
+ */
+public class DummyKeyboardWrapper implements KeyboardWrapper {
+ public static final DummyKeyboardWrapper INSTANCE = new DummyKeyboardWrapper();
+
+ PeekingIterator<KeyEvent> empty = new PeekingIterator<KeyEvent>() {
+
+ public boolean hasNext() {
+ return false;
+ }
+
+ public void remove() {}
+
+ public KeyEvent peek() {
+ return null;
+ }
+
+ public KeyEvent next() {
+ return null;
+ }
+ };
+
+ public PeekingIterator<KeyEvent> getEvents() {
+ return empty;
+ }
+
+ public void init() {
+ ; // ignore, does nothing.
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java
new file mode 100644
index 0000000..dc47f24
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/DummyMouseWrapper.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.MouseWrapper;
+import com.google.common.collect.PeekingIterator;
+
+/**
+ * A "do-nothing" implementation of MouseWrapper useful when you want to ignore (or do not need) mouse events.
+ */
+public class DummyMouseWrapper implements MouseWrapper {
+ public static final DummyMouseWrapper INSTANCE = new DummyMouseWrapper();
+
+ PeekingIterator<MouseState> empty = new PeekingIterator<MouseState>() {
+
+ public boolean hasNext() {
+ return false;
+ }
+
+ public void remove() {}
+
+ public MouseState peek() {
+ return null;
+ }
+
+ public MouseState next() {
+ return null;
+ }
+ };
+
+ public PeekingIterator<MouseState> getEvents() {
+ return empty;
+ }
+
+ public void init() {
+ ; // ignore, does nothing
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java
new file mode 100644
index 0000000..fb5119f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/InputTrigger.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.framework.Canvas;
+import com.google.common.base.Predicate;
+
+/**
+ * Defines an action to be performed when a specific input condition is met.
+ */
+@Immutable
+public final class InputTrigger {
+ private final Predicate<TwoInputStates> _condition;
+ private final TriggerAction _action;
+ private String _id;
+
+ /**
+ * Construct a new InputTrigger with the given condition and action.
+ *
+ * @param condition
+ * the predicate to test for this trigger
+ * @param action
+ * the action to take if the predicate returns true.
+ */
+ public InputTrigger(final Predicate<TwoInputStates> condition, final TriggerAction action) {
+ _condition = condition;
+ _action = action;
+ }
+
+ /**
+ * Construct a new InputTrigger with the given condition and action.
+ *
+ * @param condition
+ * the predicate to test for this trigger
+ * @param action
+ * the action to take if the predicate returns true.
+ * @param id
+ * an id, useful for identifying this trigger for deregistration, etc.
+ */
+ public InputTrigger(final Predicate<TwoInputStates> condition, final TriggerAction action, final String id) {
+ _condition = condition;
+ _action = action;
+ _id = id;
+ }
+
+ /**
+ * Checks if the condition is applicable, and if so, performs the action.
+ *
+ * @param source
+ * the Canvas that was the source of the current input
+ * @param states
+ * the input states to check
+ * @param tpf
+ * the time per frame in seconds
+ */
+ void performIfValid(final Canvas source, final TwoInputStates states, final double tpf) {
+ if (_condition.apply(states)) {
+ _action.perform(source, states, tpf);
+ }
+ }
+
+ /**
+ * @param id
+ * the id to set. This id can be used to uniquely identify a trigger.
+ */
+ public void setId(final String id) {
+ _id = id;
+ }
+
+ /**
+ * @return the id set, or null if none was set.
+ */
+ public String getId() {
+ return _id;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java
new file mode 100644
index 0000000..e913b22
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyHeldCondition.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true when a key is down in the current input state.
+ */
+@Immutable
+public final class KeyHeldCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyHeldCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyHeldCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ return states.getCurrent().getKeyboardState().isDown(key);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java
new file mode 100644
index 0000000..fd56bb3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyPressedCondition.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given key was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class KeyPressedCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyPressedCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyPressedCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ return currentState.getKeyboardState().getKeysPressedSince(previousState.getKeyboardState()).contains(key);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java
new file mode 100644
index 0000000..d7e313f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/KeyReleasedCondition.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true when a key was released from the previous to the current input state.
+ */
+@Immutable
+public final class KeyReleasedCondition implements Predicate<TwoInputStates> {
+ private final Key key;
+
+ /**
+ * Construct a new KeyReleasedCondition.
+ *
+ * @param key
+ * the key that should be held
+ * @throws NullPointerException
+ * if the key is null
+ */
+ public KeyReleasedCondition(final Key key) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+
+ this.key = key;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ return currentState.getKeyboardState().getKeysReleasedSince(previousState.getKeyboardState()).contains(key);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java
new file mode 100644
index 0000000..ed57f8c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalLayer.java
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import com.ardor3d.annotation.GuardedBy;
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.annotation.ThreadSafe;
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.PhysicalLayer;
+
+/**
+ * Implementation of a logical layer on top of the physical one, to be able to more easily trigger certain commands for
+ * certain combination of user input.
+ */
+@ThreadSafe
+public final class LogicalLayer {
+ private final Set<InputSource> _inputs = new CopyOnWriteArraySet<InputSource>();
+ private final Set<InputTrigger> _triggers = new CopyOnWriteArraySet<InputTrigger>();
+ private LogicalTriggersApplier _applier = new BasicTriggersApplier();
+
+ public LogicalLayer() {}
+
+ public void registerInput(final Canvas source, final PhysicalLayer physicalLayer) {
+ _inputs.add(new InputSource(source, physicalLayer));
+ }
+
+ /**
+ * Register a trigger for evaluation when the {@link #checkTriggers(double)} method is called.
+ *
+ * @param inputTrigger
+ * the trigger to check
+ */
+ public void registerTrigger(final InputTrigger inputTrigger) {
+ _triggers.add(inputTrigger);
+ }
+
+ /**
+ * Deregister a trigger for evaluation when the {@link #checkTriggers(double)} method is called.
+ *
+ * @param inputTrigger
+ * the trigger to stop checking
+ */
+ public void deregisterTrigger(final InputTrigger inputTrigger) {
+ _triggers.remove(inputTrigger);
+ }
+
+ /**
+ * Check all registered triggers to see if their respective conditions are met. For every trigger whose condition is
+ * true, perform the associated action.
+ *
+ * @param tpf
+ * time per frame in seconds
+ */
+ @MainThread
+ public synchronized void checkTriggers(final double tpf) {
+ for (final InputSource is : _inputs) {
+ is.physicalLayer.readState();
+
+ final List<InputState> newStates = is.physicalLayer.drainAvailableStates();
+
+ if (newStates.isEmpty()) {
+ _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState, is.lastState),
+ tpf);
+ } else {
+ // used to spread tpf evenly among triggered actions
+ final double time = newStates.size() > 1 ? tpf / newStates.size() : tpf;
+ for (final InputState inputState : newStates) {
+ // no trigger is valid in the LOST_FOCUS state, so don't bother checking them
+ if (inputState != InputState.LOST_FOCUS) {
+ _applier.checkAndPerformTriggers(_triggers, is.source, new TwoInputStates(is.lastState,
+ inputState), time);
+ }
+
+ is.lastState = inputState;
+ }
+ }
+ }
+ }
+
+ public void setApplier(final LogicalTriggersApplier applier) {
+ _applier = applier;
+ }
+
+ public LogicalTriggersApplier getApplier() {
+ return _applier;
+ }
+
+ private static class InputSource {
+ private final Canvas source;
+ private final PhysicalLayer physicalLayer;
+ @GuardedBy("LogicalLayer.this")
+ private InputState lastState;
+
+ public InputSource(final Canvas source, final PhysicalLayer physicalLayer) {
+ this.source = source;
+ this.physicalLayer = physicalLayer;
+ lastState = InputState.EMPTY;
+ }
+ }
+
+ public Set<InputTrigger> getTriggers() {
+ return _triggers;
+ }
+
+ public InputTrigger findTriggerById(final String id) {
+ for (final InputTrigger trigger : _triggers) {
+ if (id.equals(trigger.getId())) {
+ return trigger;
+ }
+ }
+ return null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java
new file mode 100644
index 0000000..d034ce8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/LogicalTriggersApplier.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.Set;
+
+import com.ardor3d.framework.Canvas;
+
+/**
+ * Defines a class the handles applying the triggers of a LogicalLayer.
+ */
+public interface LogicalTriggersApplier {
+
+ void checkAndPerformTriggers(Set<InputTrigger> triggers, Canvas source, TwoInputStates states, double tpf);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java
new file mode 100644
index 0000000..3fb0153
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonClickedCondition.java
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was clicked (has a click count) when going from the previous input state
+ * to the current one.
+ */
+@Immutable
+public final class MouseButtonClickedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonClickedCondition.
+ *
+ * @param button
+ * the button that should be "clicked" to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonClickedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+
+ return currentState.getMouseState().getButtonsClicked().contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java
new file mode 100644
index 0000000..ecb03cb
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonCondition.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.EnumMap;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Maps;
+
+/**
+ * A condition that checks the state of the two most commonly used mouse buttons.
+ */
+@Immutable
+public final class MouseButtonCondition implements Predicate<TwoInputStates> {
+ private final EnumMap<MouseButton, ButtonState> _states = Maps.newEnumMap(MouseButton.class);
+
+ public MouseButtonCondition(final EnumMap<MouseButton, ButtonState> states) {
+ _states.putAll(states);
+ }
+
+ public MouseButtonCondition(final ButtonState left, final ButtonState right, final ButtonState middle) {
+ if (left != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.LEFT, left);
+ }
+ if (right != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.RIGHT, right);
+ }
+ if (middle != ButtonState.UNDEFINED) {
+ _states.put(MouseButton.MIDDLE, middle);
+ }
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ for (final MouseButton button : _states.keySet()) {
+ final ButtonState required = _states.get(button);
+ if (required != ButtonState.UNDEFINED) {
+ if (currentState.getMouseState().getButtonState(button) != required) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java
new file mode 100644
index 0000000..b804246
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonPressedCondition.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class MouseButtonPressedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonPressedCondition.
+ *
+ * @param button
+ * the button that should be pressed to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonPressedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null || previousState == null
+ || !currentState.getMouseState().hasButtonState(ButtonState.DOWN)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getButtonsPressedSince(previousState.getMouseState()).contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java
new file mode 100644
index 0000000..461d889
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseButtonReleasedCondition.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if a given button was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class MouseButtonReleasedCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonPressedCondition.
+ *
+ * @param button
+ * the button that should be pressed to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonReleasedCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null || previousState == null
+ || !previousState.getMouseState().hasButtonState(ButtonState.DOWN)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getButtonsReleasedSince(previousState.getMouseState()).contains(_button);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java
new file mode 100644
index 0000000..e8643a0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseMovedCondition.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if the mouse has moved between the two input states.
+ */
+@Immutable
+public final class MouseMovedCondition implements Predicate<TwoInputStates> {
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ if (currentState.equals(previousState)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getDx() != 0 || currentState.getMouseState().getDy() != 0;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java
new file mode 100644
index 0000000..f0377f9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/MouseWheelMovedCondition.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+import com.google.common.base.Predicate;
+
+/**
+ * A condition that is true if the mouse wheel has moved between the two input states.
+ */
+@Immutable
+public final class MouseWheelMovedCondition implements Predicate<TwoInputStates> {
+ public boolean apply(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+ final InputState previousState = states.getPrevious();
+
+ if (currentState == null) {
+ return false;
+ }
+
+ if (currentState.equals(previousState)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getDwheel() != 0;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java
new file mode 100644
index 0000000..48b5ac1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerAction.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.framework.Canvas;
+
+/**
+ * Defines an action to be performed when a given input condition is true.
+ */
+public interface TriggerAction {
+ /**
+ * Implementing classes should implementing this method to take whatever action is desired. This method will always
+ * be called on the main GL thread.
+ *
+ * @param source
+ * the Canvas that was the source of the current input
+ * @param inputState
+ * the current and previous states of the input system when the action was triggered
+ * @param tpf
+ * the time per frame in seconds
+ */
+ @MainThread
+ public void perform(Canvas source, TwoInputStates inputStates, double tpf);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java
new file mode 100644
index 0000000..1f686a5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TriggerConditions.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import java.util.EnumMap;
+
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.util.Timer;
+import com.google.common.base.Predicate;
+
+/**
+ * Utility methods for getting standard TriggerConditions. To reduce object creation, it may be a good idea to use
+ * utility methods here to get immutable conditions that meet common criteria.
+ */
+public final class TriggerConditions {
+ private static final MouseMovedCondition MOUSE_MOVED_CONDITION = new MouseMovedCondition();
+ private static final MouseButtonCondition LEFT_DOWN_CONDITION = makeCondition(MouseButton.LEFT, ButtonState.DOWN);
+ private static final MouseButtonCondition RIGHT_DOWN_CONDITION = makeCondition(MouseButton.RIGHT, ButtonState.DOWN);
+ private static final MouseButtonCondition MIDDLE_DOWN_CONDITION = makeCondition(MouseButton.MIDDLE,
+ ButtonState.DOWN);
+
+ private static final Predicate<TwoInputStates> ALWAYS_TRUE = new Predicate<TwoInputStates>() {
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ return true;
+ }
+ };
+
+ private static final Predicate<TwoInputStates> ALWAYS_FALSE = new Predicate<TwoInputStates>() {
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ return true;
+ }
+ };
+
+ private static MouseButtonCondition makeCondition(final MouseButton button, final ButtonState state) {
+ final EnumMap<MouseButton, ButtonState> map = new EnumMap<MouseButton, ButtonState>(MouseButton.class);
+ for (final MouseButton b : MouseButton.values()) {
+ map.put(b, button != b ? ButtonState.UNDEFINED : state);
+ }
+ return new MouseButtonCondition(map);
+ }
+
+ // prevent instantiation
+ private TriggerConditions() {
+
+ }
+
+ /**
+ * @return a condition that evaluates to true if the mouse has moved
+ */
+ public static MouseMovedCondition mouseMoved() {
+ return MOUSE_MOVED_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the left button is down
+ */
+ public static MouseButtonCondition leftButtonDown() {
+ return LEFT_DOWN_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the right button is down
+ */
+ public static MouseButtonCondition rightButtonDown() {
+ return RIGHT_DOWN_CONDITION;
+ }
+
+ /**
+ *
+ * @return a condition that is true if the middle button is down
+ */
+ public static MouseButtonCondition middleButtonDown() {
+ return MIDDLE_DOWN_CONDITION;
+ }
+
+ /**
+ * @return a condition that is always true.
+ */
+ public static Predicate<TwoInputStates> alwaysTrue() {
+ return ALWAYS_TRUE;
+ }
+
+ /**
+ * @return a condition that is always false.
+ */
+ public static Predicate<TwoInputStates> alwaysFalse() {
+ return ALWAYS_FALSE;
+ }
+
+ /**
+ * @return a condition that is always false.
+ */
+ public static Predicate<TwoInputStates> passedThrottle(final double throttleTime, final Timer timer) {
+ return new Predicate<TwoInputStates>() {
+ private double lastPass = 0;
+
+ @Override
+ public boolean apply(final TwoInputStates arg0) {
+ final double now = timer.getTimeInSeconds();
+ if (now - lastPass >= throttleTime) {
+ lastPass = now;
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java
new file mode 100644
index 0000000..2e058e1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/input/logical/TwoInputStates.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.InputState;
+
+/**
+ * Wrapper class to make it possible to use {@link com.google.common.base.Predicate}-based conditions for triggering
+ * actions based on user input.
+ */
+@Immutable
+public final class TwoInputStates {
+ private final InputState _previous;
+ private final InputState _current;
+
+ /**
+ * Instantiates a new TwoInputStates. It is safe for both parameters to point to the same instance, but they cannot
+ * be null.
+ *
+ * @param previous
+ * the previous input state
+ * @param current
+ * the current input state
+ *
+ * @throws NullPointerException
+ * if either parameter is null
+ */
+ public TwoInputStates(final InputState previous, final InputState current) {
+ _previous = checkNotNull(previous, "previous");
+ _current = checkNotNull(current, "current");
+ }
+
+ public InputState getPrevious() {
+ return _previous;
+ }
+
+ public InputState getCurrent() {
+ return _current;
+ }
+
+ @Override
+ public int hashCode() {
+ // we don't expect this to be used in a map.
+ assert false : "hashCode not designed";
+ return 42;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TwoInputStates)) {
+ return false;
+ }
+ final TwoInputStates comp = (TwoInputStates) o;
+ return _previous == comp._previous && _current == comp._current;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java
new file mode 100644
index 0000000..b90a7eb
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingCollisionResults.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * BoundingCollisionResults creates a CollisionResults object that only cares about bounding volume accuracy.
+ * CollisionData objects are added to the collision list as they happen, these data objects only refer to the two
+ * meshes, not their triangle lists. While BoundingCollisionResults defines a processCollisions method, it is empty and
+ * should be further defined by the user if so desired.
+ */
+public class BoundingCollisionResults extends CollisionResults {
+
+ /**
+ * adds a CollisionData object to this results list, the objects only refer to the collision meshes, not the
+ * triangles.
+ *
+ * @see com.ardor3d.intersection.CollisionResults#addCollision(com.ardor3d.scene.Geometry,
+ * com.ardor3d.scene.Geometry)
+ */
+ @Override
+ public void addCollision(final Mesh s, final Mesh t) {
+ final CollisionData data = new CollisionData(s, t);
+ addCollisionData(data);
+ }
+
+ /**
+ * empty implementation, it is highly recommended that you override this method to handle any collisions as needed.
+ *
+ * @see com.ardor3d.intersection.CollisionResults#processCollisions()
+ */
+ @Override
+ public void processCollisions() {
+
+ }
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java
new file mode 100644
index 0000000..ac3b284
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/BoundingPickResults.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * BoundingPickResults implements the addPick of PickResults to use PickData objects that calculate bounding volume
+ * level accurate ray picks.
+ */
+public class BoundingPickResults extends PickResults {
+ @Override
+ public void addPick(final Ray3 ray, final Pickable p) {
+ if (p.intersectsWorldBound(ray)) {
+ addPickData(new PickData(ray, p, willCheckDistance()));
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java
new file mode 100644
index 0000000..bd616a8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionData.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.List;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * CollisionData contains information about a collision between two Mesh objects. The mesh that was hit by the relevant
+ * Mesh (the one making the collision check) is referenced as well as an ArrayList for the triangles that collided.
+ */
+public class CollisionData {
+
+ private final Mesh _targetMesh;
+ private final Mesh _sourceMesh;
+
+ private final List<PrimitiveKey> _sourcePrimitives;
+ private final List<PrimitiveKey> _targetPrimitives;
+
+ /**
+ * instantiates a new CollisionData object.
+ *
+ * @param sourceMesh
+ * the relevant Geometry
+ * @param targetMesh
+ * the mesh the source Mesh collided with.
+ */
+ public CollisionData(final Mesh sourceMesh, final Mesh targetMesh) {
+ this(sourceMesh, targetMesh, null, null);
+ }
+
+ /**
+ * instantiates a new CollisionData object.
+ *
+ * @param sourceMesh
+ * the relevant Mesh
+ * @param targetMesh
+ * the mesh the source Mesh collided with.
+ * @param sourcePrimitives
+ * the primitives of the source Mesh that made contact.
+ * @param targetPrimitives
+ * the primitives of the second mesh that made contact.
+ */
+ public CollisionData(final Mesh sourceMesh, final Mesh targetMesh, final List<PrimitiveKey> sourcePrimitives,
+ final List<PrimitiveKey> targetPrimitives) {
+ _targetMesh = targetMesh;
+ _sourceMesh = sourceMesh;
+ _targetPrimitives = targetPrimitives;
+ _sourcePrimitives = sourcePrimitives;
+ }
+
+ public Mesh getTargetMesh() {
+ return _targetMesh;
+ }
+
+ public Mesh getSourceMesh() {
+ return _sourceMesh;
+ }
+
+ public List<PrimitiveKey> getSourcePrimitives() {
+ return _sourcePrimitives;
+ }
+
+ public List<PrimitiveKey> getTargetPrimitives() {
+ return _targetPrimitives;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java
new file mode 100644
index 0000000..0d4d693
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/CollisionResults.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * <code>CollisionResults</code> stores the results of a collision test by storing an ArrayList of CollisionData.
+ */
+public abstract class CollisionResults {
+
+ private final List<CollisionData> _nodeList;
+
+ /**
+ * Constructor instantiates a new <code>PickResults</code> object.
+ */
+ public CollisionResults() {
+ _nodeList = new ArrayList<CollisionData>();
+ }
+
+ /**
+ * <code>addCollisionData</code> places a new <code>CollisionData</code> object into the results list.
+ *
+ * @param col
+ * The collision data to be placed in the results list.
+ */
+ public void addCollisionData(final CollisionData col) {
+ _nodeList.add(col);
+ }
+
+ /**
+ * <code>getNumber</code> retrieves the number of collisions that have been placed in the results.
+ *
+ * @return the number of collisions in the list.
+ */
+ public int getNumber() {
+ return _nodeList.size();
+ }
+
+ /**
+ * <code>getCollisionData</code> retrieves a CollisionData from a specific index.
+ *
+ * @param i
+ * the index requested.
+ * @return the CollisionData at the specified index.
+ */
+ public CollisionData getCollisionData(final int i) {
+ return _nodeList.get(i);
+ }
+
+ /**
+ * <code>clear</code> clears the list of all CollisionData.
+ */
+ public void clear() {
+ _nodeList.clear();
+ }
+
+ /**
+ *
+ * <code>addCollision</code> is an abstract method whose intent is the subclass determines what to do when two Mesh
+ * object's bounding volumes are determined to intersect.
+ *
+ * @param s
+ * the first Mesh that intersects.
+ * @param t
+ * the second Mesh that intersects.
+ */
+ public abstract void addCollision(Mesh s, Mesh t);
+
+ /**
+ *
+ * <code>processCollisions</code> is an abstract method whose intent is the subclass defines how to process the
+ * collision data that has been collected since the last clear.
+ *
+ *
+ */
+ public abstract void processCollisions();
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java
new file mode 100644
index 0000000..8245aa4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Intersection.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Vector3;
+
+/**
+ * <code>Intersection</code> provides functional methods for calculating the intersection of some objects. All the
+ * methods are static to allow for quick and easy calls. <code>Intersection</code> relays requests to specific classes
+ * to handle the actual work. By providing checks to just <code>BoundingVolume</code> the client application need not
+ * worry about what type of bounding volume is being used.
+ */
+public abstract class Intersection {
+
+ public static boolean intersection(final Vector3[] verticesA, final Vector3[] verticesB) {
+ switch (verticesA.length) {
+ case 3:
+ switch (verticesB.length) {
+ case 3:
+ // Triangle on Triangle
+ return TriangleTriangleIntersect.intersectTriTri(verticesA[0], verticesA[1], verticesA[2],
+ verticesB[0], verticesB[1], verticesB[2]);
+ case 4:
+ // TODO: Triangle on Quad
+ return false;
+ case 2:
+ // TODO: Triangle on Line
+ return false;
+ case 1:
+ // TODO: Triangle on Point
+ return false;
+ }
+ case 4:
+ switch (verticesB.length) {
+ case 3:
+ // TODO: Quad on Triangle
+ return false;
+ case 4:
+ // TODO: Quad on Quad
+ return false;
+ case 2:
+ // TODO: Quad on Line
+ return false;
+ case 1:
+ // TODO: Quad on Point
+ return false;
+ }
+ case 2:
+ switch (verticesB.length) {
+ case 3:
+ // TODO: Line on Triangle
+ return false;
+ case 4:
+ // TODO: Line on Quad
+ return false;
+ case 2:
+ // TODO: Line on Line
+ return false;
+ case 1:
+ // TODO: Line on Point
+ return false;
+ }
+ case 1:
+ switch (verticesB.length) {
+ case 3:
+ // TODO: Point on Triangle
+ return false;
+ case 4:
+ // TODO: Point on Quad
+ return false;
+ case 2:
+ // TODO: Point on Line
+ return false;
+ case 1:
+ // Point on Point
+ return verticesA[0].equals(verticesB[0]);
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java
new file mode 100644
index 0000000..eb5fb3f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/IntersectionRecord.java
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.Ardor3dException;
+
+public class IntersectionRecord {
+
+ private static final class Intersection implements Comparable<Intersection> {
+ private final double _distance;
+ private final Vector3 _point;
+ private final Vector3 _normal;
+ private final PrimitiveKey _primitiveKey;
+
+ private Intersection(final double distance, final Vector3 point, final Vector3 normal,
+ final PrimitiveKey primitiveKey) {
+ _distance = distance;
+ _point = point;
+ _normal = normal;
+ _primitiveKey = primitiveKey;
+ }
+
+ @Override
+ public int compareTo(final Intersection other) {
+ return _distance == other._distance ? 0 : (_distance < other._distance ? -1 : 1);
+ }
+ }
+
+ private final Intersection[] _intersections;
+
+ private boolean _isSorted = true;
+
+ /**
+ * Instantiates a new IntersectionRecord defining the distances and points.
+ *
+ * @param distances
+ * the distances of this intersection.
+ * @param points
+ * the points of this intersection.
+ * @throws Ardor3dException
+ * if distances.length != points.length
+ */
+ public IntersectionRecord(final double[] distances, final Vector3[] points) {
+ this(distances, points, null);
+ }
+
+ /**
+ * Instantiates a new IntersectionRecord defining the distances and points.
+ *
+ * @param distances
+ * the distances of this intersection.
+ * @param points
+ * the points of this intersection.
+ * @param primitives
+ * the primitives at each index. May be null.
+ * @throws Ardor3dException
+ * if distances.length != points.length or points.length != primitives.size() (if primitives is not
+ * null)
+ */
+ public IntersectionRecord(final double[] distances, final Vector3[] points, final List<PrimitiveKey> primitives) {
+ this(distances, points, null, primitives);
+ }
+
+ /**
+ * Instantiates a new IntersectionRecord defining the distances and points.
+ *
+ * @param distances
+ * the distances of this intersection.
+ * @param points
+ * the points of this intersection.
+ * @param points
+ * the normals of this intersection.
+ * @param primitives
+ * the primitives at each index. May be null.
+ * @throws Ardor3dException
+ * if distances.length != points.length or points.length != primitives.size() (if primitives is not
+ * null)
+ */
+ public IntersectionRecord(final double[] distances, final Vector3[] points, final Vector3[] normals,
+ final List<PrimitiveKey> primitives) {
+ if (distances.length != points.length || (primitives != null && points.length != primitives.size())
+ || (normals != null && points.length != normals.length)) {
+ throw new Ardor3dException("All arguments must have an equal number of elements.");
+ }
+ _isSorted = distances.length < 2;
+ _intersections = new Intersection[distances.length];
+ for (int i = 0; i < distances.length; i++) {
+ _intersections[i] = new Intersection(distances[i], points[i], normals != null ? normals[i] : null,
+ primitives != null ? primitives.get(i) : null);
+ }
+ }
+
+ /**
+ * Sorts intersections from near to far
+ */
+ public void sortIntersections() {
+ if (!_isSorted) {
+ Arrays.sort(_intersections);
+ _isSorted = true;
+ }
+ }
+
+ /**
+ * @return the number of intersections that occurred.
+ */
+ public int getNumberOfIntersections() {
+ return _intersections.length;
+ }
+
+ /**
+ * Returns an intersection point at a provided index.
+ *
+ * @param index
+ * the index of the point to obtain.
+ * @return the point at the index of the array.
+ */
+ public Vector3 getIntersectionPoint(final int index) {
+ return _intersections[index]._point;
+ }
+
+ /**
+ * Returns an intersection normal at a provided index.
+ *
+ * @param index
+ * the index of the point to obtain.
+ * @return the normal at the index of the array.
+ */
+ public Vector3 getIntersectionNormal(final int index) {
+ return _intersections[index]._normal;
+ }
+
+ /**
+ * Returns an intersection distance at a provided index.
+ *
+ * @param index
+ * the index of the distance to obtain.
+ * @return the distance at the index of the array.
+ */
+ public double getIntersectionDistance(final int index) {
+ return _intersections[index]._distance;
+ }
+
+ /**
+ * @param index
+ * the index of the primitive to obtain.
+ * @return the primitive at the given index.
+ */
+ public PrimitiveKey getIntersectionPrimitive(final int index) {
+ return _intersections[index]._primitiveKey;
+ }
+
+ /**
+ * @return the smallest distance in the distance array or -1 if there are no distances in this.
+ */
+ public double getClosestDistance() {
+ final int i = getClosestIntersection();
+ return i != -1 ? _intersections[i]._distance : -1.0;
+ }
+
+ /**
+ * @return the largest distance in the distance array or -1 if there are no distances in this.
+ */
+ public double getFurthestDistance() {
+ final int i = getFurthestIntersection();
+ return i != -1 ? _intersections[i]._distance : -1.0;
+ }
+
+ /**
+ * @return the index in this record with the smallest relative distance or -1 if there are no distances in this
+ * record.
+ */
+ public int getClosestIntersection() {
+ int index = -1;
+ if (_isSorted) {
+ index = _intersections.length > 0 ? 0 : -1;
+ } else {
+ double min = Double.MAX_VALUE;
+ for (int i = _intersections.length; --i >= 0;) {
+ final double val = _intersections[i]._distance;
+ if (val < min) {
+ min = val;
+ index = i;
+ }
+ }
+ }
+ return index;
+ }
+
+ /**
+ * @return the index in this record with the largest relative distance or -1 if there are no distances in this
+ * record.
+ */
+ public int getFurthestIntersection() {
+ int index = -1;
+ if (_isSorted) {
+ index = _intersections.length > 0 ? _intersections.length - 1 : -1;
+ } else {
+ double max = -Double.MAX_VALUE;
+ for (int i = _intersections.length; --i >= 0;) {
+ final double val = _intersections[i]._distance;
+ if (val > max) {
+ max = val;
+ index = i;
+ }
+ }
+ }
+ return index;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java
new file mode 100644
index 0000000..4e2de16
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickData.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * PickData contains information about a picking operation (or Ray/Volume intersection). This data contains the mesh the
+ * ray hit, the triangles it hit, and the ray itself.
+ */
+public class PickData {
+
+ private final Ray3 _ray;
+ private final Pickable _target;
+ protected IntersectionRecord _intersectionRecord;
+
+ /**
+ * instantiates a new PickData object. Note: subclasses may want to make calc points false to prevent this extra
+ * work.
+ */
+ public PickData(final Ray3 ray, final Pickable target, final boolean calcPoints) {
+ _ray = ray;
+ _target = target;
+
+ if (calcPoints) {
+ _intersectionRecord = target.intersectsWorldBoundsWhere(ray);
+ }
+ }
+
+ /**
+ * @return the pickable hit by the ray.
+ */
+ public Pickable getTarget() {
+ return _target;
+ }
+
+ /**
+ * @return the ray used in the test.
+ */
+ public Ray3 getRay() {
+ return _ray;
+ }
+
+ /**
+ * @return the intersection record generated for this pick. Will be null if calcPoints was false.
+ */
+ public IntersectionRecord getIntersectionRecord() {
+ return _intersectionRecord;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java
new file mode 100644
index 0000000..402b934
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickResults.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * PickResults stores information created during ray intersection tests. The results will contain a list of every
+ * {@link Pickable} element encountered in a pick test. Distance can be used to order the results. If checkDistance is
+ * set to true, objects will be ordered with the first element in the list being the closest picked object.
+ */
+public abstract class PickResults {
+
+ private final List<PickData> _nodeList;
+ private boolean _checkDistance;
+ private DistanceComparator _distanceCompare;
+
+ /**
+ * Constructor instantiates a new <code>PickResults</code> object.
+ */
+ public PickResults() {
+ _nodeList = new ArrayList<PickData>();
+ }
+
+ /**
+ * remember modification of the list to allow sorting after all picks have been added - not each time.
+ */
+ private boolean modified = false;
+
+ /**
+ * Places a new geometry (enclosed in PickData) into the results list.
+ *
+ * @param data
+ * the PickData to be placed in the results list.
+ */
+ public void addPickData(final PickData data) {
+ _nodeList.add(data);
+ modified = true;
+ }
+
+ /**
+ * <code>getNumber</code> retrieves the number of geometries that have been placed in the results.
+ *
+ * @return the number of Mesh objects in the list.
+ */
+ public int getNumber() {
+ return _nodeList.size();
+ }
+
+ /**
+ * Retrieves a geometry (enclosed in PickData) from a specific index.
+ *
+ * @param i
+ * the index requested.
+ * @return the data at the specified index.
+ */
+ public PickData getPickData(final int i) {
+ if (modified) {
+ if (_checkDistance) {
+ Collections.sort(_nodeList, _distanceCompare);
+ }
+ modified = false;
+ }
+ return _nodeList.get(i);
+ }
+
+ /**
+ * <code>clear</code> clears the list of all Mesh objects.
+ */
+ public void clear() {
+ _nodeList.clear();
+ }
+
+ /**
+ * <code>addPick</code> generates an entry to be added to the list of picked objects. If checkDistance is true, the
+ * implementing class should order the object.
+ *
+ * @param ray
+ * the ray that was cast for the pick calculation.
+ * @param p
+ * the pickable object to add to the pick data.
+ */
+ public abstract void addPick(Ray3 ray, Pickable p);
+
+ /**
+ * Optional method that can be implemented by sub classes to define methods for handling picked objects. After
+ * calculating all pick results this method is called.
+ *
+ */
+ public void processPick() {}
+
+ /**
+ * Reports if these pick results will order the data by distance from the origin of the Ray.
+ *
+ * @return true if objects will be ordered by distance, false otherwise.
+ */
+ public boolean willCheckDistance() {
+ return _checkDistance;
+ }
+
+ /**
+ * Sets if these pick results will order the data by distance from the origin of the Ray.
+ *
+ * @param checkDistance
+ * true if objects will be ordered by distance, false otherwise.
+ */
+ public void setCheckDistance(final boolean checkDistance) {
+ _checkDistance = checkDistance;
+ if (checkDistance) {
+ _distanceCompare = new DistanceComparator();
+ }
+ }
+
+ /**
+ * Implementation of comparator that uses the distance set in the pick data to order the objects.
+ */
+ private static class DistanceComparator implements Comparator<PickData> {
+
+ public int compare(final PickData o1, final PickData o2) {
+ if (o1.getIntersectionRecord().getClosestDistance() <= o2.getIntersectionRecord().getClosestDistance()) {
+ return -1;
+ }
+
+ return 1;
+ }
+ }
+
+ public PickData findFirstIntersectingPickData() {
+ int i = 0;
+ while (getNumber() > 0 && getPickData(i).getIntersectionRecord().getNumberOfIntersections() == 0
+ && ++i < getNumber()) {
+ }
+ return getNumber() > i ? getPickData(i) : null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java
new file mode 100644
index 0000000..832162b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/Pickable.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * An interface describing objects that can be used with our PickingUtil class.
+ */
+public interface Pickable {
+
+ /**
+ * @return true if this pickable supports intersectsWorldBoundsWhere. False if this method will always return null.
+ */
+ boolean supportsBoundsIntersectionRecord();
+
+ /**
+ * @return true if this pickable supports intersectsPrimitivesWhere. False if this method will always return null.
+ */
+ boolean supportsPrimitivesIntersectionRecord();
+
+ /**
+ * @param ray
+ * a ray, in world coordinates.
+ * @return true if the given ray intersects our world bounding volume. false if it does not.
+ * @throws NullPointerException
+ * if there is no bound to check.
+ */
+ boolean intersectsWorldBound(Ray3 ray);
+
+ /**
+ * @param ray
+ * a ray, in world coordinates.
+ * @return an intersection record containing information about where the ray intersected our bounding volume, or
+ * null if it does not.
+ * @throws NullPointerException
+ * if there is no bound to check.
+ */
+ IntersectionRecord intersectsWorldBoundsWhere(Ray3 ray);
+
+ /**
+ * @param ray
+ * a ray, in world coordinates.
+ * @return an intersection record containing information about where the ray intersected our primitives, or null if
+ * it does not.
+ */
+ IntersectionRecord intersectsPrimitivesWhere(Ray3 ray);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java
new file mode 100644
index 0000000..0c3c066
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PickingUtil.java
@@ -0,0 +1,205 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.List;
+
+import com.ardor3d.bounding.CollisionTree;
+import com.ardor3d.bounding.CollisionTreeManager;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.PickingHint;
+
+public abstract class PickingUtil {
+ /**
+ * Finds a pick using the given ray starting at the scenegraph given as spatial. Results are stored in the given
+ * results value.
+ *
+ * NB: Spatials with CullHint ALWAYS will be skipped.
+ *
+ * @param spatial
+ * @param ray
+ * @param results
+ */
+ public static void findPick(final Spatial spatial, final Ray3 ray, final PickResults results) {
+ findPick(spatial, ray, results, true);
+ }
+
+ /**
+ * Finds a pick using the given ray starting at the scenegraph given as spatial. Results are stored in the given
+ * results value.
+ *
+ * @param spatial
+ * @param ray
+ * @param results
+ * @param ignoreCulled
+ * if true, Spatials with CullHint ALWAYS will be skipped.
+ */
+ public static void findPick(final Spatial spatial, final Ray3 ray, final PickResults results,
+ final boolean ignoreCulled) {
+ if (spatial == null || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Pickable)
+ || (ignoreCulled && spatial.getSceneHints().getCullHint() == CullHint.Always)
+ || spatial.getWorldBound() == null || !spatial.getWorldBound().intersects(ray)) {
+ return;
+ }
+
+ if (spatial instanceof Pickable) {
+ results.addPick(ray, (Pickable) spatial);
+ } else if (spatial instanceof Node) {
+ final Node node = (Node) spatial;
+ for (int i = node.getNumberOfChildren() - 1; i >= 0; i--) {
+ findPick(node.getChild(i), ray, results, ignoreCulled);
+ }
+ }
+ }
+
+ public static void findCollisions(final Spatial spatial, final Spatial scene, final CollisionResults results) {
+ if (spatial == scene || spatial.getWorldBound() == null
+ || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)
+ || !scene.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) {
+ return;
+ }
+
+ if (spatial instanceof Node) {
+ final Node node = (Node) spatial;
+
+ if (node.getWorldBound().intersects(scene.getWorldBound())) {
+ // further checking needed.
+ for (int i = 0; i < node.getNumberOfChildren(); i++) {
+ PickingUtil.findCollisions(node.getChild(i), scene, results);
+ }
+ }
+ } else if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+
+ if (mesh.getWorldBound().intersects(scene.getWorldBound())) {
+ if (scene instanceof Node) {
+ final Node parent = (Node) scene;
+ for (int i = 0; i < parent.getNumberOfChildren(); i++) {
+ PickingUtil.findCollisions(mesh, parent.getChild(i), results);
+ }
+ } else {
+ results.addCollision(mesh, (Mesh) scene);
+ }
+ }
+ }
+ }
+
+ /**
+ * This function checks for intersection between this mesh and the given one. On the first intersection, true is
+ * returned.
+ *
+ * @param toCheck
+ * The intersection testing mesh.
+ * @return True if they intersect.
+ */
+ public static boolean hasPrimitiveCollision(final Mesh testMesh, final Mesh toCheck) {
+ if (!testMesh.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)
+ || !toCheck.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) {
+ return false;
+ }
+
+ final CollisionTree thisCT = CollisionTreeManager.getInstance().getCollisionTree(testMesh);
+ final CollisionTree checkCT = CollisionTreeManager.getInstance().getCollisionTree(toCheck);
+
+ if (thisCT == null || checkCT == null) {
+ return false;
+ }
+
+ final ReadOnlyTransform worldTransform = testMesh.getWorldTransform();
+ thisCT.getBounds().transform(worldTransform, thisCT.getWorldBounds());
+ return thisCT.intersect(checkCT);
+ }
+
+ /**
+ * This function finds all intersections between this mesh and the checking one. The intersections are stored as
+ * PrimitiveKeys.
+ *
+ * @param toCheck
+ * The Mesh to check.
+ * @param testIndex
+ * The array of PrimitiveKeys intersecting in this mesh.
+ * @param otherIndex
+ * The array of PrimitiveKeys intersecting in the given mesh.
+ */
+ public static void findPrimitiveCollision(final Mesh testMesh, final Mesh toCheck,
+ final List<PrimitiveKey> testIndex, final List<PrimitiveKey> otherIndex) {
+ if (!testMesh.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)
+ || !toCheck.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) {
+ return;
+ }
+
+ final CollisionTree myTree = CollisionTreeManager.getInstance().getCollisionTree(testMesh);
+ final CollisionTree otherTree = CollisionTreeManager.getInstance().getCollisionTree(toCheck);
+
+ if (myTree == null || otherTree == null) {
+ return;
+ }
+
+ myTree.getBounds().transform(testMesh.getWorldTransform(), myTree.getWorldBounds());
+
+ myTree.intersect(otherTree, testIndex, otherIndex);
+ }
+
+ public static boolean hasCollision(final Spatial spatial, final Spatial scene, final boolean checkPrimitives) {
+ if (spatial == scene || spatial.getWorldBound() == null
+ || !spatial.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)
+ || !scene.getSceneHints().isPickingHintEnabled(PickingHint.Collidable)) {
+ return false;
+ }
+
+ if (spatial instanceof Node) {
+ final Node node = (Node) spatial;
+
+ if (node.getWorldBound().intersects(scene.getWorldBound())) {
+ if (node.getNumberOfChildren() == 0 && !checkPrimitives) {
+ return true;
+ }
+ // further checking needed.
+ for (int i = 0; i < node.getNumberOfChildren(); i++) {
+ if (PickingUtil.hasCollision(node.getChild(i), scene, checkPrimitives)) {
+ return true;
+ }
+ }
+ }
+ } else if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+
+ if (mesh.getWorldBound().intersects(scene.getWorldBound())) {
+ if (scene instanceof Node) {
+ final Node parent = (Node) scene;
+ for (int i = 0; i < parent.getNumberOfChildren(); i++) {
+ if (PickingUtil.hasCollision(mesh, parent.getChild(i), checkPrimitives)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ if (!checkPrimitives) {
+ return true;
+ }
+
+ return PickingUtil.hasPrimitiveCollision(mesh, (Mesh) scene);
+ }
+
+ return false;
+
+ }
+
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java
new file mode 100644
index 0000000..133312d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveCollisionResults.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * PrimitiveCollisionResults creates a CollisionResults object that calculates collisions to the primitive (quad,
+ * triangle, etc.) accuracy. CollisionData objects are added to the collision list as they happen, these data objects
+ * only refer to the two meshes, not their primitive lists. While PrimitiveCollisionResults defines a processCollisions
+ * method, it is empty and should be further defined by the user if so desired.
+ *
+ * NOTE: Only Mesh objects may obtain primitive accuracy, all others will result in Bounding accuracy.
+ */
+public class PrimitiveCollisionResults extends CollisionResults {
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.ardor3d.intersection.CollisionResults#addCollision(com.ardor3d.scene.Geometry,
+ * com.ardor3d.scene.Geometry)
+ */
+ @Override
+ public void addCollision(final Mesh s, final Mesh t) {
+ // find the triangle that is being hit.
+ // add this node and the triangle to the CollisionResults list.
+ final List<PrimitiveKey> a = new ArrayList<PrimitiveKey>();
+ final List<PrimitiveKey> b = new ArrayList<PrimitiveKey>();
+ PickingUtil.findPrimitiveCollision(s, t, a, b);
+ final CollisionData data = new CollisionData(s, t, a, b);
+ addCollisionData(data);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.ardor3d.intersection.CollisionResults#processCollisions()
+ */
+ @Override
+ public void processCollisions() {
+
+ }
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java
new file mode 100644
index 0000000..c23d085
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitiveKey.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+public class PrimitiveKey {
+ private final int _primitiveIndex;
+ private final int _section;
+
+ public PrimitiveKey(final int primitiveIndex, final int section) {
+ _primitiveIndex = primitiveIndex;
+ _section = section;
+ }
+
+ /**
+ * @return the primitiveIndex
+ */
+ public int getPrimitiveIndex() {
+ return _primitiveIndex;
+ }
+
+ /**
+ * @return the section
+ */
+ public int getSection() {
+ return _section;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+
+ result += 31 * result + _primitiveIndex;
+ result += 31 * result + _section;
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof PrimitiveKey)) {
+ return false;
+ }
+ final PrimitiveKey comp = (PrimitiveKey) o;
+ return _primitiveIndex == comp._primitiveIndex && _section == comp._section;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java
new file mode 100644
index 0000000..33fd1f5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickData.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * Pick data for primitive accurate picking including sort by distance to intersection point.
+ */
+public class PrimitivePickData extends PickData {
+ public PrimitivePickData(final Ray3 ray, final Pickable target) {
+ super(ray, target, false); // hard coded to false
+
+ _intersectionRecord = target.intersectsPrimitivesWhere(ray);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java
new file mode 100644
index 0000000..76fb345
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/PrimitivePickResults.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Ray3;
+
+/**
+ * PrimitivePickResults implements the addPick of PickResults to use PickData objects that calculate primitive accurate
+ * ray picks.
+ */
+public class PrimitivePickResults extends PickResults {
+ protected float _maxPickableDistance = Float.MAX_VALUE;
+
+ @Override
+ public void addPick(final Ray3 ray, final Pickable pickable) {
+ final IntersectionRecord record = pickable.intersectsWorldBoundsWhere(ray);
+ if (record != null && record.getClosestDistance() > _maxPickableDistance) {
+ return;
+ }
+
+ final PrimitivePickData data = new PrimitivePickData(ray, pickable);
+ if (data.getIntersectionRecord() != null && data.getIntersectionRecord().getNumberOfIntersections() > 0) {
+ addPickData(data);
+ }
+ }
+
+ public float getMaxPickableDistance() {
+ return _maxPickableDistance;
+ }
+
+ public void setMaxPickableDistance(final float distance) {
+ _maxPickableDistance = distance;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java
new file mode 100644
index 0000000..c718914
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/intersection/TriangleTriangleIntersect.java
@@ -0,0 +1,393 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.intersection;
+
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+
+public class TriangleTriangleIntersect {
+
+ /**
+ * EPSILON represents the error buffer used to denote a hit.
+ */
+ public static final double EPSILON = 1e-12;
+
+ /**
+ * This method tests for the intersection between two triangles defined by their vertexes. Converted to java from C
+ * code found at http://jgt.akpeters.com/papers/Moller97/
+ *
+ * @param v0
+ * First triangle's first vertex.
+ * @param v1
+ * First triangle's second vertex.
+ * @param v2
+ * First triangle's third vertex.
+ * @param u0
+ * Second triangle's first vertex.
+ * @param u1
+ * Second triangle's second vertex.
+ * @param u2
+ * Second triangle's third vertex.
+ * @return True if the two triangles intersect, false otherwise.
+ */
+ public static boolean intersectTriTri(final Vector3 v0, final Vector3 v1, final Vector3 v2, final Vector3 u0,
+ final Vector3 u1, final Vector3 u2) {
+ final Vector3 e1 = Vector3.fetchTempInstance();
+ final Vector3 e2 = Vector3.fetchTempInstance();
+ final Vector3 n1 = Vector3.fetchTempInstance();
+ final Vector3 n2 = Vector3.fetchTempInstance();
+ final Vector3 d = Vector3.fetchTempInstance();
+ try {
+
+ double d1, d2;
+ double du0, du1, du2, dv0, dv1, dv2;
+ final double[] isect1 = new double[2];
+ final double[] isect2 = new double[2];
+ double du0du1, du0du2, dv0dv1, dv0dv2;
+ short index;
+ double vp0, vp1, vp2;
+ double up0, up1, up2;
+ double bb, cc, max;
+ double xx, yy, xxyy, tmp;
+
+ /* compute plane equation of triangle(v0,v1,v2) */
+ v1.subtract(v0, e1);
+ v2.subtract(v0, e2);
+ e1.cross(e2, n1);
+ d1 = -n1.dot(v0);
+ /* plane equation 1: n1.X+d1=0 */
+
+ /*
+ * put u0,u1,u2 into plane equation 1 to compute signed distances to the plane
+ */
+ du0 = n1.dot(u0) + d1;
+ du1 = n1.dot(u1) + d1;
+ du2 = n1.dot(u2) + d1;
+
+ /* coplanarity robustness check */
+ if (Math.abs(du0) < EPSILON) {
+ du0 = 0.0f;
+ }
+ if (Math.abs(du1) < EPSILON) {
+ du1 = 0.0f;
+ }
+ if (Math.abs(du2) < EPSILON) {
+ du2 = 0.0f;
+ }
+ du0du1 = du0 * du1;
+ du0du2 = du0 * du2;
+
+ if (du0du1 > 0.0f && du0du2 > 0.0f) {
+ return false;
+ }
+
+ /* compute plane of triangle (u0,u1,u2) */
+ u1.subtract(u0, e1);
+ u2.subtract(u0, e2);
+ e1.cross(e2, n2);
+ d2 = -n2.dot(u0);
+ /* plane equation 2: n2.X+d2=0 */
+
+ /* put v0,v1,v2 into plane equation 2 */
+ dv0 = n2.dot(v0) + d2;
+ dv1 = n2.dot(v1) + d2;
+ dv2 = n2.dot(v2) + d2;
+
+ if (Math.abs(dv0) < EPSILON) {
+ dv0 = 0.0f;
+ }
+ if (Math.abs(dv1) < EPSILON) {
+ dv1 = 0.0f;
+ }
+ if (Math.abs(dv2) < EPSILON) {
+ dv2 = 0.0f;
+ }
+
+ dv0dv1 = dv0 * dv1;
+ dv0dv2 = dv0 * dv2;
+
+ if (dv0dv1 > 0.0f && dv0dv2 > 0.0f) { /*
+ * same sign on all of them + not equal 0 ?
+ */
+ return false; /* no intersection occurs */
+ }
+
+ /* compute direction of intersection line */
+ n1.cross(n2, d);
+
+ /* compute and index to the largest component of d */
+ max = Math.abs(d.getX());
+ index = 0;
+ bb = Math.abs(d.getY());
+ cc = Math.abs(d.getZ());
+ if (bb > max) {
+ max = bb;
+ index = 1;
+ }
+ if (cc > max) {
+ vp0 = v0.getZ();
+ vp1 = v1.getZ();
+ vp2 = v2.getZ();
+
+ up0 = u0.getZ();
+ up1 = u1.getZ();
+ up2 = u2.getZ();
+
+ } else if (index == 1) {
+ vp0 = v0.getY();
+ vp1 = v1.getY();
+ vp2 = v2.getY();
+
+ up0 = u0.getY();
+ up1 = u1.getY();
+ up2 = u2.getY();
+ } else {
+ vp0 = v0.getX();
+ vp1 = v1.getX();
+ vp2 = v2.getX();
+
+ up0 = u0.getX();
+ up1 = u1.getX();
+ up2 = u2.getX();
+ }
+
+ /* compute interval for triangle 1 */
+ {
+ final Vector3 abc = Vector3.fetchTempInstance();
+ final Vector2 x0x1 = Vector2.fetchTempInstance();
+ if (newComputeIntervals(vp0, vp1, vp2, dv0, dv1, dv2, dv0dv1, dv0dv2, abc, x0x1)) {
+ return coplanarTriTri(n1, v0, v1, v2, u0, u1, u2);
+ }
+
+ /* compute interval for triangle 2 */
+ final Vector3 def = Vector3.fetchTempInstance();
+ final Vector2 y0y1 = Vector2.fetchTempInstance();
+ if (newComputeIntervals(up0, up1, up2, du0, du1, du2, du0du1, du0du2, def, y0y1)) {
+ return coplanarTriTri(n1, v0, v1, v2, u0, u1, u2);
+ }
+
+ xx = x0x1.getX() * x0x1.getY();
+ yy = y0y1.getX() * y0y1.getY();
+ xxyy = xx * yy;
+
+ tmp = abc.getX() * xxyy;
+ isect1[0] = tmp + abc.getY() * x0x1.getY() * yy;
+ isect1[1] = tmp + abc.getZ() * x0x1.getX() * yy;
+
+ tmp = def.getX() * xxyy;
+ isect2[0] = tmp + def.getY() * xx * y0y1.getY();
+ isect2[1] = tmp + def.getZ() * xx * y0y1.getX();
+
+ Vector3.releaseTempInstance(abc);
+ Vector3.releaseTempInstance(def);
+
+ Vector2.releaseTempInstance(x0x1);
+ Vector2.releaseTempInstance(y0y1);
+
+ sort(isect1);
+ sort(isect2);
+ }
+ if (isect1[1] < isect2[0] || isect2[1] < isect1[0]) {
+ return false;
+ }
+
+ return true;
+ } finally {
+ Vector3.releaseTempInstance(e1);
+ Vector3.releaseTempInstance(e2);
+ Vector3.releaseTempInstance(n1);
+ Vector3.releaseTempInstance(n2);
+ Vector3.releaseTempInstance(d);
+ }
+ }
+
+ private static void sort(final double[] f) {
+ if (f[0] > f[1]) {
+ final double c = f[0];
+ f[0] = f[1];
+ f[1] = c;
+ }
+ }
+
+ private static boolean newComputeIntervals(final double vv0, final double vv1, final double vv2, final double d0,
+ final double d1, final double d2, final double d0d1, final double d0d2, final Vector3 abc,
+ final Vector2 x0x1) {
+ if (d0d1 > 0.0f) {
+ /* here we know that d0d2 <=0.0 */
+ /*
+ * that is d0, d1 are on the same side, d2 on the other or on the plane
+ */
+ abc.setX(vv2);
+ abc.setY((vv0 - vv2) * d2);
+ abc.setZ((vv1 - vv2) * d2);
+ x0x1.setX(d2 - d0);
+ x0x1.setY(d2 - d1);
+ } else if (d0d2 > 0.0f) {
+ /* here we know that d0d1 <=0.0 */
+ abc.setX(vv1);
+ abc.setY((vv0 - vv1) * d1);
+ abc.setZ((vv2 - vv1) * d1);
+ x0x1.setX(d1 - d0);
+ x0x1.setY(d1 - d2);
+ } else if (d1 * d2 > 0.0f || d0 != 0.0f) {
+ /* here we know that d0d1 <=0.0 or that d0!=0.0 */
+ abc.setX(vv0);
+ abc.setY((vv1 - vv0) * d0);
+ abc.setZ((vv2 - vv0) * d0);
+ x0x1.setX(d0 - d1);
+ x0x1.setY(d0 - d2);
+ } else if (d1 != 0.0f) {
+ abc.setX(vv1);
+ abc.setY((vv0 - vv1) * d1);
+ abc.setZ((vv2 - vv1) * d1);
+ x0x1.setX(d1 - d0);
+ x0x1.setY(d1 - d2);
+ } else if (d2 != 0.0f) {
+ abc.setX(vv2);
+ abc.setY((vv0 - vv2) * d2);
+ abc.setZ((vv1 - vv2) * d2);
+ x0x1.setX(d2 - d0);
+ x0x1.setY(d2 - d1);
+ } else {
+ /* triangles are coplanar */
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean coplanarTriTri(final Vector3 n, final Vector3 v0, final Vector3 v1, final Vector3 v2,
+ final Vector3 u0, final Vector3 u1, final Vector3 u2) {
+ final Vector3 a = new Vector3();
+ short i0, i1;
+ a.setX(Math.abs(n.getX()));
+ a.setY(Math.abs(n.getY()));
+ a.setZ(Math.abs(n.getZ()));
+
+ if (a.getX() > a.getY()) {
+ if (a.getX() > a.getZ()) {
+ i0 = 1; /* a[0] is greatest */
+ i1 = 2;
+ } else {
+ i0 = 0; /* a[2] is greatest */
+ i1 = 1;
+ }
+ } else /* a[0] <=a[1] */{
+ if (a.getZ() > a.getY()) {
+ i0 = 0; /* a[2] is greatest */
+ i1 = 1;
+ } else {
+ i0 = 0; /* a[1] is greatest */
+ i1 = 2;
+ }
+ }
+
+ /* test all edges of triangle 1 against the edges of triangle 2 */
+ final double[] v0f = new double[3];
+ v0.toArray(v0f);
+ final double[] v1f = new double[3];
+ v1.toArray(v1f);
+ final double[] v2f = new double[3];
+ v2.toArray(v2f);
+ final double[] u0f = new double[3];
+ u0.toArray(u0f);
+ final double[] u1f = new double[3];
+ u1.toArray(u1f);
+ final double[] u2f = new double[3];
+ u2.toArray(u2f);
+ if (edgeAgainstTriEdges(v0f, v1f, u0f, u1f, u2f, i0, i1)) {
+ return true;
+ }
+
+ if (edgeAgainstTriEdges(v1f, v2f, u0f, u1f, u2f, i0, i1)) {
+ return true;
+ }
+
+ if (edgeAgainstTriEdges(v2f, v0f, u0f, u1f, u2f, i0, i1)) {
+ return true;
+ }
+
+ /* finally, test if tri1 is totally contained in tri2 or vice versa */
+ pointInTri(v0f, u0f, u1f, u2f, i0, i1);
+ pointInTri(u0f, v0f, v1f, v2f, i0, i1);
+
+ return false;
+ }
+
+ private static boolean pointInTri(final double[] V0, final double[] U0, final double[] U1, final double[] U2,
+ final int i0, final int i1) {
+ double a, b, c, d0, d1, d2;
+ /* is T1 completly inside T2? */
+ /* check if V0 is inside tri(U0,U1,U2) */
+ a = U1[i1] - U0[i1];
+ b = -(U1[i0] - U0[i0]);
+ c = -a * U0[i0] - b * U0[i1];
+ d0 = a * V0[i0] + b * V0[i1] + c;
+
+ a = U2[i1] - U1[i1];
+ b = -(U2[i0] - U1[i0]);
+ c = -a * U1[i0] - b * U1[i1];
+ d1 = a * V0[i0] + b * V0[i1] + c;
+
+ a = U0[i1] - U2[i1];
+ b = -(U0[i0] - U2[i0]);
+ c = -a * U2[i0] - b * U2[i1];
+ d2 = a * V0[i0] + b * V0[i1] + c;
+ if (d0 * d1 > 0.0 && d0 * d2 > 0.0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean edgeAgainstTriEdges(final double[] v0, final double[] v1, final double[] u0,
+ final double[] u1, final double[] u2, final int i0, final int i1) {
+ double aX, aY;
+ aX = v1[i0] - v0[i0];
+ aY = v1[i1] - v0[i1];
+ /* test edge u0,u1 against v0,v1 */
+ if (edgeEdgeTest(v0, u0, u1, i0, i1, aX, aY)) {
+ return true;
+ }
+ /* test edge u1,u2 against v0,v1 */
+ if (edgeEdgeTest(v0, u1, u2, i0, i1, aX, aY)) {
+ return true;
+ }
+ /* test edge u2,u1 against v0,v1 */
+ if (edgeEdgeTest(v0, u2, u0, i0, i1, aX, aY)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean edgeEdgeTest(final double[] v0, final double[] u0, final double[] u1, final int i0,
+ final int i1, final double aX, final double Ay) {
+ final double Bx = u0[i0] - u1[i0];
+ final double By = u0[i1] - u1[i1];
+ final double Cx = v0[i0] - u0[i0];
+ final double Cy = v0[i1] - u0[i1];
+ final double f = Ay * Bx - aX * By;
+ final double d = By * Cx - Bx * Cy;
+ if ((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) {
+ final double e = aX * Cy - Ay * Cx;
+ if (f > 0) {
+ if (e >= 0 && e <= f) {
+ return true;
+ }
+ } else {
+ if (e <= 0 && e >= f) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java
new file mode 100644
index 0000000..6bd3c58
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/DirectionalLight.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.light;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>DirectionalLight</code> defines a light that is assumed to be infinitely far away (something similar to the
+ * sun). This means the direction of the light rays are all parallel. The direction field of this class identifies the
+ * direction in which the light is traveling, which is opposite how jME works.
+ */
+public class DirectionalLight extends Light {
+ private static final long serialVersionUID = 1L;
+
+ private final Vector3 _direction = new Vector3(Vector3.UNIT_Z);
+
+ /**
+ * Constructor instantiates a new <code>DirectionalLight</code> object. The initial light colors are white and the
+ * direction the light travels is along the positive z axis (0,0,1).
+ *
+ */
+ public DirectionalLight() {}
+
+ /**
+ * @return the direction the light traveling in.
+ */
+ public ReadOnlyVector3 getDirection() {
+ return _direction;
+ }
+
+ /**
+ * @param direction
+ * the direction the light is traveling in.
+ */
+ public void setDirection(final ReadOnlyVector3 direction) {
+ _direction.set(direction);
+ }
+
+ /**
+ * @param x
+ * the direction the light is traveling in on the x axis.
+ * @param y
+ * the direction the light is traveling in on the y axis.
+ * @param z
+ * the direction the light is traveling in on the z axis.
+ */
+ public void setDirection(final double x, final double y, final double z) {
+ _direction.set(x, y, z);
+ }
+
+ /**
+ * <code>getType</code> returns this light's type (Type.Directional).
+ *
+ * @see com.ardor3d.light.Light#getType()
+ */
+ @Override
+ public Type getType() {
+ return Type.Directional;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z));
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z)));
+
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java
new file mode 100644
index 0000000..cf1a9cd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/Light.java
@@ -0,0 +1,355 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.light;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * <code>Light</code> defines the attributes of a light element. This class is abstract and intended to be sub-classed
+ * by specific lighting types. A light will illuminate portions of the scene by assigning its properties to the objects
+ * in the scene. This will affect the objects color values, depending on the color of the ambient, diffuse and specular
+ * light components.
+ *
+ * Ambient light defines the general light of the scene, that is the intensity and color of lighting if no particular
+ * lights are affecting it.
+ *
+ * Diffuse lighting defines the reflection of light on matte surfaces.
+ *
+ * Specular lighting defines the reflection of light on shiny surfaces.
+ */
+public abstract class Light implements Serializable, Savable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * dark grey (.4, .4, .4, 1)
+ */
+ public static final ReadOnlyColorRGBA DEFAULT_AMBIENT = new ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f);
+
+ /**
+ * white (1, 1, 1, 1)
+ */
+ public static final ReadOnlyColorRGBA DEFAULT_DIFFUSE = new ColorRGBA(1, 1, 1, 1);
+
+ /**
+ * white (1, 1, 1, 1)
+ */
+ public static final ReadOnlyColorRGBA DEFAULT_SPECULAR = new ColorRGBA(1, 1, 1, 1);
+
+ public enum Type {
+ Directional, Point, Spot
+ }
+
+ // light attributes.
+ private final ColorRGBA _ambient = new ColorRGBA(DEFAULT_AMBIENT);
+ private final ColorRGBA _diffuse = new ColorRGBA(DEFAULT_DIFFUSE);
+ private final ColorRGBA _specular = new ColorRGBA(DEFAULT_SPECULAR);
+
+ private boolean _attenuate;
+ private float _constant = 1;
+ private float _linear;
+ private float _quadratic;
+
+ private int _lightMask = 0;
+ private int _backLightMask = 0;
+
+ private boolean _enabled;
+
+ private String _name;
+
+ /** when true, indicates the lights in this lightState will cast shadows. */
+ protected boolean _shadowCaster;
+
+ /**
+ * Constructor instantiates a new <code>Light</code> object. All light color values are set to white.
+ *
+ */
+ public Light() {}
+
+ /**
+ *
+ * <code>getType</code> returns the type of the light that has been created.
+ *
+ * @return the type of light that has been created.
+ */
+ public abstract Type getType();
+
+ /**
+ * <code>getConstant</code> returns the value for the constant attenuation.
+ *
+ * @return the value for the constant attenuation.
+ */
+ public float getConstant() {
+ return _constant;
+ }
+
+ /**
+ * <code>setConstant</code> sets the value for the constant attentuation.
+ *
+ * @param constant
+ * the value for the constant attenuation.
+ */
+ public void setConstant(final float constant) {
+ _constant = constant;
+ }
+
+ /**
+ * <code>getLinear</code> returns the value for the linear attenuation.
+ *
+ * @return the value for the linear attenuation.
+ */
+ public float getLinear() {
+ return _linear;
+ }
+
+ /**
+ * <code>setLinear</code> sets the value for the linear attentuation.
+ *
+ * @param linear
+ * the value for the linear attenuation.
+ */
+ public void setLinear(final float linear) {
+ _linear = linear;
+ }
+
+ /**
+ * <code>getQuadratic</code> returns the value for the quadratic attentuation.
+ *
+ * @return the value for the quadratic attenuation.
+ */
+ public float getQuadratic() {
+ return _quadratic;
+ }
+
+ /**
+ * <code>setQuadratic</code> sets the value for the quadratic attenuation.
+ *
+ * @param quadratic
+ * the value for the quadratic attenuation.
+ */
+ public void setQuadratic(final float quadratic) {
+ _quadratic = quadratic;
+ }
+
+ /**
+ * <code>isAttenuate</code> returns true if attenuation is to be used for this light.
+ *
+ * @return true if attenuation is to be used, false otherwise.
+ */
+ public boolean isAttenuate() {
+ return _attenuate;
+ }
+
+ /**
+ * <code>setAttenuate</code> sets if attenuation is to be used. True sets it on, false otherwise.
+ *
+ * @param attenuate
+ * true to use attenuation, false not to.
+ */
+ public void setAttenuate(final boolean attenuate) {
+ _attenuate = attenuate;
+ }
+
+ /**
+ *
+ * <code>isEnabled</code> returns true if the light is enabled, false otherwise.
+ *
+ * @return true if the light is enabled, false if it is not.
+ */
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ /**
+ *
+ * <code>setEnabled</code> sets the light on or off. True turns it on, false turns it off.
+ *
+ * @param value
+ * true to turn the light on, false to turn it off.
+ */
+ public void setEnabled(final boolean value) {
+ _enabled = value;
+ }
+
+ /**
+ * <code>getSpecular</code> returns the specular color value for this light.
+ *
+ * @return the specular color value of the light.
+ */
+ public ReadOnlyColorRGBA getSpecular() {
+ return _specular;
+ }
+
+ /**
+ * <code>setSpecular</code> sets the specular color value for this light.
+ *
+ * @param specular
+ * the specular color value of the light.
+ */
+ public void setSpecular(final ReadOnlyColorRGBA specular) {
+ this._specular.set(specular);
+ }
+
+ /**
+ * <code>getDiffuse</code> returns the diffuse color value for this light.
+ *
+ * @return the diffuse color value for this light.
+ */
+ public ReadOnlyColorRGBA getDiffuse() {
+ return _diffuse;
+ }
+
+ /**
+ * <code>setDiffuse</code> sets the diffuse color value for this light.
+ *
+ * @param diffuse
+ * the diffuse color value for this light.
+ */
+ public void setDiffuse(final ReadOnlyColorRGBA diffuse) {
+ this._diffuse.set(diffuse);
+ }
+
+ /**
+ * <code>getAmbient</code> returns the ambient color value for this light.
+ *
+ * @return the ambient color value for this light.
+ */
+ public ReadOnlyColorRGBA getAmbient() {
+ return _ambient;
+ }
+
+ /**
+ * <code>setAmbient</code> sets the ambient color value for this light.
+ *
+ * @param ambient
+ * the ambient color value for this light.
+ */
+ public void setAmbient(final ReadOnlyColorRGBA ambient) {
+ this._ambient.set(ambient);
+ }
+
+ /**
+ * @return Returns the lightMask - default is 0 or not masked.
+ */
+ public int getLightMask() {
+ return _lightMask;
+ }
+
+ /**
+ * <code>setLightMask</code> sets what attributes of this light to apply as an int comprised of bitwise |'ed values
+ * from LightState.Mask_XXXX. LightMask.MASK_GLOBALAMBIENT is ignored.
+ *
+ * @param lightMask
+ * The lightMask to set.
+ */
+ public void setLightMask(final int lightMask) {
+ _lightMask = lightMask;
+ }
+
+ /**
+ * Saves the light mask to a back store. That backstore is recalled with popLightMask. Despite the name, this is not
+ * a stack and additional pushes will simply overwrite the backstored value.
+ */
+ public void pushLightMask() {
+ _backLightMask = _lightMask;
+ }
+
+ /**
+ * Recalls the light mask from a back store or 0 if none was pushed.
+ *
+ * @see com.ardor3d.light.Light#pushLightMask()
+ */
+ public void popLightMask() {
+ _lightMask = _backLightMask;
+ }
+
+ /**
+ * @return Returns whether this light is able to cast shadows.
+ */
+ public boolean isShadowCaster() {
+ return _shadowCaster;
+ }
+
+ /**
+ * @param mayCastShadows
+ * true if this light can be used to derive shadows (when used in conjunction with a shadow pass.)
+ */
+ public void setShadowCaster(final boolean mayCastShadows) {
+ _shadowCaster = mayCastShadows;
+ }
+
+ /**
+ * Copies the light values from the given light into this Light.
+ *
+ * @param light
+ * the Light to copy from.
+ */
+ public void copyFrom(final Light light) {
+ _ambient.set(light._ambient);
+ _attenuate = light._attenuate;
+ _constant = light._constant;
+ _diffuse.set(light._diffuse);
+ _enabled = light._enabled;
+ _linear = light._linear;
+ _quadratic = light._quadratic;
+ _shadowCaster = light._shadowCaster;
+ _specular.set(light._specular);
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public void setName(final String name) {
+ this._name = name;
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_ambient, "ambient", new ColorRGBA(DEFAULT_AMBIENT));
+ capsule.write(_diffuse, "diffuse", new ColorRGBA(DEFAULT_DIFFUSE));
+ capsule.write(_specular, "specular", new ColorRGBA(DEFAULT_SPECULAR));
+ capsule.write(_attenuate, "attenuate", false);
+ capsule.write(_constant, "constant", 1);
+ capsule.write(_linear, "linear", 0);
+ capsule.write(_quadratic, "quadratic", 0);
+ capsule.write(_lightMask, "lightMask", 0);
+ capsule.write(_backLightMask, "backLightMask", 0);
+ capsule.write(_enabled, "enabled", false);
+ capsule.write(_shadowCaster, "shadowCaster", false);
+ capsule.write(_name, "name", null);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _ambient.set((ColorRGBA) capsule.readSavable("ambient", new ColorRGBA(DEFAULT_AMBIENT)));
+ _diffuse.set((ColorRGBA) capsule.readSavable("diffuse", new ColorRGBA(DEFAULT_DIFFUSE)));
+ _specular.set((ColorRGBA) capsule.readSavable("specular", new ColorRGBA(DEFAULT_SPECULAR)));
+ _attenuate = capsule.readBoolean("attenuate", false);
+ _constant = capsule.readFloat("constant", 1);
+ _linear = capsule.readFloat("linear", 0);
+ _quadratic = capsule.readFloat("quadratic", 0);
+ _lightMask = capsule.readInt("lightMask", 0);
+ _backLightMask = capsule.readInt("backLightMask", 0);
+ _enabled = capsule.readBoolean("enabled", false);
+ _shadowCaster = capsule.readBoolean("shadowCaster", false);
+ _name = capsule.readString("name", null);
+ }
+
+ public Class<? extends Light> getClassTag() {
+ return this.getClass();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java
new file mode 100644
index 0000000..beace3f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/PointLight.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.light;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>PointLight</code> defines a light that has a location in space and emits light in all directions evenly. This
+ * would be something similar to a light bulb. Typically this light's values are attenuated based on the distance of the
+ * point light and the object it illuminates.
+ */
+public class PointLight extends Light {
+ private static final long serialVersionUID = 1L;
+
+ // Position of the light.
+ private Vector3 _location;
+
+ /**
+ * Constructor instantiates a new <code>PointLight</code> object. The initial position of the light is (0,0,0) and
+ * it's colors are white.
+ *
+ */
+ public PointLight() {
+ super();
+ _location = new Vector3();
+ }
+
+ /**
+ * <code>getLocation</code> returns the position of this light.
+ *
+ * @return the position of the light.
+ */
+ public ReadOnlyVector3 getLocation() {
+ return _location;
+ }
+
+ /**
+ * <code>setLocation</code> sets the position of the light.
+ *
+ * @param location
+ * the position of the light.
+ */
+ public void setLocation(final ReadOnlyVector3 location) {
+ _location.set(location);
+ }
+
+ /**
+ * <code>setLocation</code> sets the position of the light.
+ *
+ * @param x
+ * the x position of the light.
+ * @param y
+ * the y position of the light.
+ * @param z
+ * the z position of the light.
+ */
+ public void setLocation(final double x, final double y, final double z) {
+ _location.set(x, y, z);
+ }
+
+ /**
+ * <code>getType</code> returns the type of this light (Type.Point).
+ *
+ * @see com.ardor3d.light.Light#getType()
+ */
+ @Override
+ public Type getType() {
+ return Type.Point;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_location, "location", new Vector3(Vector3.ZERO));
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _location = (Vector3) capsule.readSavable("location", new Vector3(Vector3.ZERO));
+
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java
new file mode 100644
index 0000000..f44e2a3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/light/SpotLight.java
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.light;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * SpotLight defines a light that has a location in space and emits light within a cone. This cone is defined by an
+ * angle and exponent. Typically this light's values are attenuated based on the distance of the point light and the
+ * object it illuminates.
+ */
+public class SpotLight extends PointLight {
+ private static final long serialVersionUID = 1L;
+
+ private float _angle;
+ private float _exponent;
+ private final Vector3 _direction = new Vector3(Vector3.UNIT_Z);
+
+ /**
+ * Constructor instantiates a new <code>SpotLight</code> object. The initial position of the light is (0,0,0) with
+ * angle 0, and colors white.
+ *
+ */
+ public SpotLight() {
+ super();
+ setAmbient(new ColorRGBA(0, 0, 0, 1));
+ }
+
+ /**
+ * @return the direction the spot light is pointing.
+ */
+ public ReadOnlyVector3 getDirection() {
+ return _direction;
+ }
+
+ /**
+ * @param direction
+ * the direction the spot light is pointing.
+ */
+ public void setDirection(final ReadOnlyVector3 direction) {
+ _direction.set(direction);
+ }
+
+ /**
+ * <code>getAngle</code> returns the angle of the spot light.
+ *
+ * @see #setAngle(float) for more info
+ * @return the angle (in degrees)
+ */
+ public float getAngle() {
+ return _angle;
+ }
+
+ /**
+ * <code>setAngle</code> sets the angle of focus of the spot light measured from the direction vector. Think of this
+ * as the angle of a cone. Therefore, if you specify 10 degrees, you will get a 20 degree cone (10 degrees off
+ * either side of the direction vector.) 180 degrees means radiate in all directions.
+ *
+ * @param angle
+ * the angle (in degrees) which must be between 0 and 90 (inclusive) or the special case 180.
+ */
+ public void setAngle(final float angle) {
+ if (angle < 0f || (angle > 90f && angle != 180f)) {
+ throw new Ardor3dException("invalid angle. Angle must be between 0 and 90, or 180");
+ }
+ _angle = angle;
+ }
+
+ /**
+ * <code>getExponent</code> gets the spot exponent of this light.
+ *
+ * @see #setExponent(float) for more info
+ * @return the spot exponent of this light.
+ */
+ public float getExponent() {
+ return _exponent;
+ }
+
+ /**
+ * <code>setExponent</code> sets the spot exponent of this light. This value represents how focused the light beam
+ * is.
+ *
+ * @param exponent
+ * the spot exponent of this light. Should be between 0-128
+ */
+ public void setExponent(final float exponent) {
+ if (exponent < 0f || exponent > 128f) {
+ throw new Ardor3dException("invalid exponent. Exponent must be between 0 and 128");
+ }
+ _exponent = exponent;
+ }
+
+ /**
+ * <code>getType</code> returns the type of this light (Type.Spot).
+ *
+ * @see com.ardor3d.light.Light#getType()
+ */
+ @Override
+ public Type getType() {
+ return Type.Spot;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z));
+ capsule.write(_angle, "angle", 0);
+ capsule.write(_exponent, "exponent", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z)));
+ _angle = capsule.readFloat("angle", 0);
+ _exponent = capsule.readFloat("exponent", 0);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java
new file mode 100644
index 0000000..f7a8c2e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractFBOTextureRenderer.java
@@ -0,0 +1,291 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.nio.IntBuffer;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.framework.Scene;
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+public abstract class AbstractFBOTextureRenderer implements TextureRenderer {
+ private static final Logger logger = Logger.getLogger(AbstractFBOTextureRenderer.class.getName());
+
+ /** List of states that override any set states on a spatial if not null. */
+ protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ protected final Camera _camera = new Camera(1, 1);
+
+ protected final ColorRGBA _backgroundColor = new ColorRGBA(1, 1, 1, 1);
+
+ protected int _active;
+
+ protected int _fboID = 0, _depthRBID = 0;
+ protected int _msfboID = 0, _msdepthRBID = 0, _mscolorRBID = 0;
+ protected int _width = 0, _height = 0, _samples = 0, _depthBits = 0;
+
+ protected IntBuffer _attachBuffer = null;
+ protected boolean _usingDepthRB = false;
+ protected final boolean _supportsDepthTexture;
+ protected final boolean _supportsMultisample;
+ protected boolean _neededClip;
+
+ protected final Renderer _parentRenderer;
+
+ public AbstractFBOTextureRenderer(final int width, final int height, final int depthBits, final int samples,
+ final Renderer parentRenderer, final ContextCapabilities caps) {
+ _parentRenderer = parentRenderer;
+ _samples = Math.min(samples, caps.getMaxFBOSamples());
+ _depthBits = depthBits;
+ _supportsDepthTexture = caps.isDepthTextureSupported();
+ _supportsMultisample = caps.getMaxFBOSamples() != 0;
+
+ int w = width;
+ int h = height;
+ if (!caps.isNonPowerOfTwoTextureSupported()) {
+ // Check if we have non-power of two sizes. If so, find the smallest power of two size that is greater than
+ // the provided size.
+ if (!MathUtils.isPowerOfTwo(w)) {
+ int newWidth = 2;
+ do {
+ newWidth <<= 1;
+
+ } while (newWidth < w);
+ w = newWidth;
+ }
+
+ if (!MathUtils.isPowerOfTwo(h)) {
+ int newHeight = 2;
+ do {
+ newHeight <<= 1;
+
+ } while (newHeight < h);
+ h = newHeight;
+ }
+ }
+
+ logger.fine("Creating FBO sized: " + w + " x " + h);
+
+ _width = w;
+ _height = h;
+
+ _camera.resize(_width, _height);
+ _camera.setFrustum(1.0f, 1000.0f, -0.50f, 0.50f, 0.50f, -0.50f);
+ final Vector3 loc = new Vector3(0.0f, 0.0f, 0.0f);
+ final Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f);
+ final Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
+ final Vector3 dir = new Vector3(0.0f, 0f, -1.0f);
+ _camera.setFrame(loc, left, up, dir);
+ }
+
+ /**
+ * <code>getCamera</code> retrieves the camera this renderer is using.
+ *
+ * @return the camera this renderer is using.
+ */
+ public Camera getCamera() {
+ return _camera;
+ }
+
+ public void setBackgroundColor(final ReadOnlyColorRGBA c) {
+ _backgroundColor.set(c);
+ }
+
+ public ReadOnlyColorRGBA getBackgroundColor() {
+ return _backgroundColor;
+ }
+
+ public void render(final Spatial toDraw, final Texture tex, final int clear) {
+ try {
+ ContextManager.getCurrentContext().pushFBOTextureRenderer(this);
+
+ setupForSingleTexDraw(tex);
+
+ if (_samples > 0 && _supportsMultisample) {
+ setMSFBO();
+ }
+
+ switchCameraIn(clear);
+ doDraw(toDraw);
+ switchCameraOut();
+
+ if (_samples > 0 && _supportsMultisample) {
+ blitMSFBO();
+ }
+
+ takedownForSingleTexDraw(tex);
+
+ ContextManager.getCurrentContext().popFBOTextureRenderer();
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture, boolean)", "Exception", e);
+ }
+ }
+
+ public void render(final Scene toDraw, final Texture tex, final int clear) {
+ try {
+ ContextManager.getCurrentContext().pushFBOTextureRenderer(this);
+
+ setupForSingleTexDraw(tex);
+
+ if (_samples > 0 && _supportsMultisample) {
+ setMSFBO();
+ }
+
+ switchCameraIn(clear);
+ doDraw(toDraw);
+ switchCameraOut();
+
+ if (_samples > 0 && _supportsMultisample) {
+ blitMSFBO();
+ }
+
+ takedownForSingleTexDraw(tex);
+
+ ContextManager.getCurrentContext().popFBOTextureRenderer();
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "render(Spatial, Texture, boolean)", "Exception", e);
+ }
+ }
+
+ public void render(final List<? extends Spatial> toDraw, final Texture tex, final int clear) {
+ try {
+ ContextManager.getCurrentContext().pushFBOTextureRenderer(this);
+
+ setupForSingleTexDraw(tex);
+
+ if (_samples > 0 && _supportsMultisample) {
+ setMSFBO();
+ }
+
+ switchCameraIn(clear);
+ doDraw(toDraw);
+ switchCameraOut();
+
+ if (_samples > 0 && _supportsMultisample) {
+ blitMSFBO();
+ }
+
+ takedownForSingleTexDraw(tex);
+
+ ContextManager.getCurrentContext().popFBOTextureRenderer();
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "render(List<Spatial>, Texture, boolean)",
+ "Exception", e);
+ }
+ }
+
+ protected abstract void activate();
+
+ protected abstract void setupForSingleTexDraw(Texture tex);
+
+ protected abstract void takedownForSingleTexDraw(Texture tex);
+
+ protected abstract void setMSFBO();
+
+ protected abstract void blitMSFBO();
+
+ protected abstract void deactivate();
+
+ private Camera _oldCamera;
+
+ protected void switchCameraIn(final int clear) {
+ // grab non-rtt settings
+ _oldCamera = Camera.getCurrentCamera();
+
+ // swap to rtt settings
+ _parentRenderer.getQueue().pushBuckets();
+
+ // clear the scene
+ if (clear != 0) {
+ clearBuffers(clear);
+ }
+
+ getCamera().update();
+ getCamera().apply(_parentRenderer);
+ }
+
+ protected abstract void clearBuffers(int clear);
+
+ protected void switchCameraOut() {
+ _parentRenderer.flushFrame(false);
+
+ // reset previous camera
+ _oldCamera.update();
+ _oldCamera.apply(_parentRenderer);
+
+ // back to the non rtt settings
+ _parentRenderer.getQueue().popBuckets();
+ }
+
+ protected void doDraw(final Spatial spat) {
+ // Override parent's last frustum test to avoid accidental incorrect cull
+ if (spat.getParent() != null) {
+ spat.getParent().setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+ }
+
+ // do rtt scene render
+ spat.onDraw(_parentRenderer);
+ }
+
+ protected void doDraw(final List<? extends Spatial> toDraw) {
+ for (int x = 0, max = toDraw.size(); x < max; x++) {
+ final Spatial spat = toDraw.get(x);
+ doDraw(spat);
+ }
+ }
+
+ protected void doDraw(final Scene toDraw) {
+ toDraw.renderUnto(_parentRenderer);
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+
+ public int getHeight() {
+ return _height;
+ }
+
+ public Renderer getParentRenderer() {
+ return _parentRenderer;
+ }
+
+ public void setMultipleTargets(final boolean multi) {
+ // ignore. Does not matter to FBO.
+ }
+
+ public void enforceState(final RenderState state) {
+ _enforcedStates.put(state.getType(), state);
+ }
+
+ public void enforceStates(final EnumMap<StateType, RenderState> states) {
+ _enforcedStates.putAll(states);
+ }
+
+ public void clearEnforcedState(final StateType type) {
+ _enforcedStates.remove(type);
+ }
+
+ public void clearEnforcedStates() {
+ _enforcedStates.clear();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java
new file mode 100644
index 0000000..a428526
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractPbufferTextureRenderer.java
@@ -0,0 +1,178 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.framework.Scene;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+public abstract class AbstractPbufferTextureRenderer implements TextureRenderer {
+ private static final Logger logger = Logger.getLogger(AbstractPbufferTextureRenderer.class.getName());
+
+ /** List of states that override any set states on a spatial if not null. */
+ protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ protected final Camera _camera = new Camera(1, 1);
+
+ protected final ColorRGBA _backgroundColor = new ColorRGBA(1, 1, 1, 1);
+ protected boolean _bgColorDirty = true;
+
+ protected boolean _useDirectRender = false;
+
+ protected int _active;
+
+ protected int _width = 0, _height = 0;
+
+ protected final Renderer _parentRenderer;
+ protected final DisplaySettings _settings;
+
+ public AbstractPbufferTextureRenderer(final DisplaySettings settings, final Renderer parentRenderer,
+ final ContextCapabilities caps) {
+ _parentRenderer = parentRenderer;
+ _settings = settings;
+
+ int width = settings.getWidth();
+ int height = settings.getHeight();
+ if (!caps.isNonPowerOfTwoTextureSupported()) {
+ // Check if we have non-power of two sizes. If so, find the smallest power of two size that is greater than
+ // the provided size.
+ if (!MathUtils.isPowerOfTwo(width)) {
+ int newWidth = 2;
+ do {
+ newWidth <<= 1;
+
+ } while (newWidth < width);
+ width = newWidth;
+ }
+
+ if (!MathUtils.isPowerOfTwo(height)) {
+ int newHeight = 2;
+ do {
+ newHeight <<= 1;
+
+ } while (newHeight < height);
+ height = newHeight;
+ }
+ }
+
+ _width = width;
+ _height = height;
+
+ logger.fine("Created Pbuffer sized: " + _width + " x " + _height);
+
+ _camera.resize(_width, _height);
+ _camera.setFrustum(1.0f, 1000.0f, -0.50f, 0.50f, 0.50f, -0.50f);
+ final Vector3 loc = new Vector3(0.0f, 0.0f, 0.0f);
+ final Vector3 left = new Vector3(-1.0f, 0.0f, 0.0f);
+ final Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
+ final Vector3 dir = new Vector3(0.0f, 0f, -1.0f);
+ _camera.setFrame(loc, left, up, dir);
+ }
+
+ protected RenderContext _oldContext;
+
+ protected void switchCameraIn(final int clear) {
+ // Note: no need for storing and replacing old camera since pbuffer is a separate context.
+
+ // swap to rtt settings
+ _parentRenderer.getQueue().pushBuckets();
+
+ // clear the scene
+ if (clear != 0) {
+ clearBuffers(clear);
+ }
+
+ getCamera().update();
+ getCamera().apply(_parentRenderer);
+ }
+
+ protected abstract void clearBuffers(int clear);
+
+ protected void switchCameraOut() {
+ _parentRenderer.flushFrame(false);
+ // back to the non rtt settings
+ _parentRenderer.getQueue().popBuckets();
+ }
+
+ protected void doDraw(final Spatial spat) {
+ // Override parent's last frustum test to avoid accidental incorrect cull
+ if (spat.getParent() != null) {
+ spat.getParent().setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+ }
+
+ // do rtt scene render
+ spat.onDraw(_parentRenderer);
+ }
+
+ protected void doDraw(final List<? extends Spatial> toDraw) {
+ for (int x = 0, max = toDraw.size(); x < max; x++) {
+ final Spatial spat = toDraw.get(x);
+ doDraw(spat);
+ }
+ }
+
+ protected void doDraw(final Scene toDraw) {
+ toDraw.renderUnto(_parentRenderer);
+ }
+
+ /**
+ * <code>getCamera</code> retrieves the camera this renderer is using.
+ *
+ * @return the camera this renderer is using.
+ */
+ public Camera getCamera() {
+ return _camera;
+ }
+
+ public void setBackgroundColor(final ReadOnlyColorRGBA c) {
+ _backgroundColor.set(c);
+ _bgColorDirty = true;
+ }
+
+ public ReadOnlyColorRGBA getBackgroundColor() {
+ return _backgroundColor;
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+
+ public int getHeight() {
+ return _height;
+ }
+
+ public void enforceState(final RenderState state) {
+ _enforcedStates.put(state.getType(), state);
+ }
+
+ public void enforceStates(final EnumMap<StateType, RenderState> states) {
+ _enforcedStates.putAll(states);
+ }
+
+ public void clearEnforcedState(final StateType type) {
+ _enforcedStates.remove(type);
+ }
+
+ public void clearEnforcedStates() {
+ _enforcedStates.clear();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java
new file mode 100644
index 0000000..6384842
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/AbstractRenderer.java
@@ -0,0 +1,191 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.queue.RenderQueue;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.record.RendererRecord;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardor3d.util.stat.StatType;
+
+/**
+ * Provides some common base level method implementations for Renderers.
+ */
+public abstract class AbstractRenderer implements Renderer {
+ // clear color
+ protected final ColorRGBA _backgroundColor = new ColorRGBA(ColorRGBA.BLACK);
+
+ protected boolean _processingQueue;
+
+ protected RenderQueue _queue = new RenderQueue();
+
+ protected boolean _inOrthoMode;
+
+ protected int _stencilClearValue;
+
+ protected RenderLogic renderLogic;
+
+ /** List of default rendering states for this specific renderer type */
+ protected final EnumMap<RenderState.StateType, RenderState> defaultStateList = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ public AbstractRenderer() {
+ for (final RenderState.StateType type : RenderState.StateType.values()) {
+ final RenderState state = RenderState.createState(type);
+ state.setEnabled(false);
+ defaultStateList.put(type, state);
+ }
+ }
+
+ public boolean isInOrthoMode() {
+ return _inOrthoMode;
+ }
+
+ public ReadOnlyColorRGBA getBackgroundColor() {
+ return _backgroundColor;
+ }
+
+ public RenderQueue getQueue() {
+ return _queue;
+ }
+
+ public boolean isProcessingQueue() {
+ return _processingQueue;
+ }
+
+ public void applyState(final StateType type, final RenderState state) {
+ if (Constants.stats) {
+ StatCollector.startStat(StatType.STAT_STATES_TIMER);
+ }
+
+ final RenderState tempState = getProperRenderState(type, state);
+ final RenderContext context = ContextManager.getCurrentContext();
+ if (!RenderState._quickCompare.contains(type) || tempState.needsRefresh()
+ || tempState != context.getCurrentState(type)) {
+ doApplyState(tempState);
+ tempState.setNeedsRefresh(false);
+ }
+
+ if (Constants.stats) {
+ StatCollector.endStat(StatType.STAT_STATES_TIMER);
+ }
+ }
+
+ protected abstract void doApplyState(RenderState state);
+
+ protected void addStats(final IndexMode indexMode, final int vertCount) {
+ final int primCount = IndexMode.getPrimitiveCount(indexMode, vertCount);
+ switch (indexMode) {
+ case Triangles:
+ case TriangleFan:
+ case TriangleStrip:
+ StatCollector.addStat(StatType.STAT_TRIANGLE_COUNT, primCount);
+ break;
+ case Lines:
+ case LineLoop:
+ case LineStrip:
+ StatCollector.addStat(StatType.STAT_LINE_COUNT, primCount);
+ break;
+ case Points:
+ StatCollector.addStat(StatType.STAT_POINT_COUNT, primCount);
+ break;
+ case Quads:
+ case QuadStrip:
+ StatCollector.addStat(StatType.STAT_QUAD_COUNT, primCount);
+ break;
+ }
+ }
+
+ protected int getTotalInterleavedSize(final RenderContext context, final FloatBufferData vertexCoords,
+ final FloatBufferData normalCoords, final FloatBufferData colorCoords,
+ final List<FloatBufferData> textureCoords) {
+ final ContextCapabilities caps = context.getCapabilities();
+
+ int bufferSizeBytes = 0;
+ if (normalCoords != null) {
+ bufferSizeBytes += normalCoords.getBufferLimit() * 4;
+ }
+ if (colorCoords != null) {
+ bufferSizeBytes += colorCoords.getBufferLimit() * 4;
+ }
+ if (textureCoords != null) {
+ final TextureState ts = (TextureState) context.getCurrentState(RenderState.StateType.Texture);
+ if (ts != null) {
+ final int max = caps.isMultitextureSupported() ? Math.min(caps.getNumberOfFragmentTexCoordUnits(),
+ TextureState.MAX_TEXTURES) : 1;
+ boolean exists;
+ for (int i = 0; i < max; i++) {
+ exists = i < textureCoords.size() && textureCoords.get(i) != null
+ && i <= ts.getMaxTextureIndexUsed();
+
+ if (!exists) {
+ continue;
+ }
+
+ final FloatBufferData textureBufferData = textureCoords.get(i);
+ if (textureBufferData != null) {
+ bufferSizeBytes += textureBufferData.getBufferLimit() * 4;
+ }
+ }
+ }
+ }
+ if (vertexCoords != null) {
+ bufferSizeBytes += vertexCoords.getBufferLimit() * 4;
+ }
+
+ return bufferSizeBytes;
+ }
+
+ public int getStencilClearValue() {
+ return _stencilClearValue;
+ }
+
+ public void setStencilClearValue(final int stencilClearValue) {
+ _stencilClearValue = stencilClearValue;
+ }
+
+ public boolean isClipTestEnabled() {
+ final RenderContext context = ContextManager.getCurrentContext();
+ final RendererRecord record = context.getRendererRecord();
+ return record.isClippingTestEnabled();
+ }
+
+ public RenderState getProperRenderState(final StateType type, final RenderState current) {
+ final RenderContext context = ContextManager.getCurrentContext();
+
+ // first look up in enforced states
+ final RenderState state = context.hasEnforcedStates() ? context.getEnforcedState(type) : null;
+
+ // Not there? Use the state we received
+ if (state == null) {
+ if (current != null) {
+ return current;
+ } else {
+ return defaultStateList.get(type);
+ }
+ } else {
+ return state;
+ }
+ }
+
+ public void setRenderLogic(final RenderLogic renderLogic) {
+ this.renderLogic = renderLogic;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java
new file mode 100644
index 0000000..f281735
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Camera.java
@@ -0,0 +1,1634 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.nio.FloatBuffer;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * This class represents a view into a 3d scene and how that view should map to a 2D rendering surface.
+ */
+public class Camera implements Savable, Externalizable {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger _logger = Logger.getLogger(Camera.class.getName());
+
+ public enum FrustumIntersect {
+ /**
+ * Object being compared to the frustum is completely outside of the frustum.
+ */
+ Outside,
+
+ /**
+ * Object being compared to the frustum is completely inside of the frustum.
+ */
+ Inside,
+
+ /**
+ * Object being compared to the frustum intersects one of the frustum planes and is thus both inside and outside
+ * of the frustum.
+ */
+ Intersects;
+ }
+
+ // planes of the frustum
+ /**
+ * LEFT_PLANE represents the left plane of the camera frustum.
+ */
+ public static final int LEFT_PLANE = 0;
+
+ /**
+ * RIGHT_PLANE represents the right plane of the camera frustum.
+ */
+ public static final int RIGHT_PLANE = 1;
+
+ /**
+ * BOTTOM_PLANE represents the bottom plane of the camera frustum.
+ */
+ public static final int BOTTOM_PLANE = 2;
+
+ /**
+ * TOP_PLANE represents the top plane of the camera frustum.
+ */
+ public static final int TOP_PLANE = 3;
+
+ /**
+ * FAR_PLANE represents the far plane of the camera frustum.
+ */
+ public static final int FAR_PLANE = 4;
+
+ /**
+ * NEAR_PLANE represents the near plane of the camera frustum.
+ */
+ public static final int NEAR_PLANE = 5;
+
+ /**
+ * FRUSTUM_PLANES represents the number of planes of the camera frustum.
+ */
+ public static final int FRUSTUM_PLANES = 6;
+
+ /**
+ * MAX_WORLD_PLANES holds the maximum planes allowed by the system.
+ */
+ public static final int MAX_WORLD_PLANES = 32;
+
+ // the location and orientation of the camera.
+ /**
+ * Camera's location
+ */
+ protected final Vector3 _location = new Vector3();
+
+ /**
+ * Direction of camera's 'left'
+ */
+ protected final Vector3 _left = new Vector3();
+
+ /**
+ * Direction of 'up' for camera.
+ */
+ protected final Vector3 _up = new Vector3();
+
+ /**
+ * Direction the camera is facing.
+ */
+ protected final Vector3 _direction = new Vector3();
+
+ /**
+ * The near range for mapping depth values from normalized device coordinates to window coordinates.
+ */
+ protected double _depthRangeNear;
+
+ /**
+ * The far range for mapping depth values from normalized device coordinates to window coordinates.
+ */
+ protected double _depthRangeFar;
+
+ /**
+ * Distance from camera to near frustum plane.
+ */
+ protected double _frustumNear;
+
+ /**
+ * Distance from camera to far frustum plane.
+ */
+ protected double _frustumFar;
+
+ /**
+ * Distance from camera to left frustum plane.
+ */
+ protected double _frustumLeft;
+
+ /**
+ * Distance from camera to right frustum plane.
+ */
+ protected double _frustumRight;
+
+ /**
+ * Distance from camera to top frustum plane.
+ */
+ protected double _frustumTop;
+
+ /**
+ * Distance from camera to bottom frustum plane.
+ */
+ protected double _frustumBottom;
+
+ /**
+ * Convenience store for fovY. Only set during setFrustumPerspective and never used. Retrieve by getFovY(). Default
+ * is NaN.
+ */
+ protected double _fovY = Double.NaN;
+
+ // Temporary values computed in onFrustumChange that are needed if a
+ // call is made to onFrameChange.
+ protected double _coeffLeft[];
+ protected double _coeffRight[];
+ protected double _coeffBottom[];
+ protected double _coeffTop[];
+
+ /** Number of camera planes used by this camera. Default is 6. */
+ protected int _planeQuantity;
+
+ // view port coordinates
+ /**
+ * Percent value on display where horizontal viewing starts for this camera. Default is 0.
+ */
+ protected double _viewPortLeft;
+
+ /**
+ * Percent value on display where horizontal viewing ends for this camera. Default is 1.
+ */
+ protected double _viewPortRight;
+
+ /**
+ * Percent value on display where vertical viewing ends for this camera. Default is 1.
+ */
+ protected double _viewPortTop;
+
+ /**
+ * Percent value on display where vertical viewing begins for this camera. Default is 0.
+ */
+ protected double _viewPortBottom;
+
+ /**
+ * Array holding the planes that this camera will check for culling.
+ */
+ protected Plane[] _worldPlane;
+
+ protected final FloatBuffer _matrixBuffer = BufferUtils.createFloatBuffer(16);
+
+ /**
+ * Computation vector used in various operations.
+ */
+ protected final Vector3 _tempVector = new Vector3();
+
+ /**
+ * Projection mode used by the camera.
+ */
+ public enum ProjectionMode {
+ Perspective, Parallel, Custom
+ }
+
+ /**
+ * Current projection mode.
+ */
+ private ProjectionMode _projectionMode = ProjectionMode.Perspective;
+
+ private boolean _updateMVMatrix = true;
+ private boolean _updatePMatrix = true;
+ private boolean _updateMVPMatrix = true;
+ private boolean _updateInverseMVPMatrix = true;
+
+ // NB: These matrices are column-major.
+ protected final Matrix4 _modelView = new Matrix4();
+ protected final Matrix4 _projection = new Matrix4();
+ private final Matrix4 _modelViewProjection = new Matrix4();
+ private final Matrix4 _modelViewProjectionInverse = new Matrix4();
+
+ protected boolean _depthRangeDirty;
+ protected boolean _frustumDirty;
+ protected boolean _viewPortDirty;
+ protected boolean _frameDirty;
+
+ /**
+ * A mask value set during contains() that allows fast culling of a Node's children.
+ */
+ private int _planeState;
+
+ protected int _width;
+ protected int _height;
+
+ /**
+ * Construct a new Camera with a width and height of 100.
+ */
+ public Camera() {
+ this(100, 100);
+ }
+
+ /**
+ * Construct a new Camera with the given frame width and height.
+ *
+ * @param width
+ * @param height
+ */
+ public Camera(final int width, final int height) {
+ _width = width;
+ _height = height;
+
+ _location.set(0, 0, 0);
+ _left.set(-1, 0, 0);
+ _up.set(0, 1, 0);
+ _direction.set(0, 0, -1);
+
+ _depthRangeNear = 0.0;
+ _depthRangeFar = 1.0;
+ _depthRangeDirty = true;
+
+ _frustumNear = 1.0;
+ _frustumFar = 2.0;
+ _frustumLeft = -0.5;
+ _frustumRight = 0.5;
+ _frustumTop = 0.5;
+ _frustumBottom = -0.5;
+
+ _coeffLeft = new double[2];
+ _coeffRight = new double[2];
+ _coeffBottom = new double[2];
+ _coeffTop = new double[2];
+
+ _viewPortLeft = 0.0;
+ _viewPortRight = 1.0;
+ _viewPortTop = 1.0;
+ _viewPortBottom = 0.0;
+
+ _planeQuantity = 6;
+
+ _worldPlane = new Plane[MAX_WORLD_PLANES];
+ for (int i = 0; i < MAX_WORLD_PLANES; i++) {
+ _worldPlane[i] = new Plane();
+ }
+
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+
+ _logger.fine("Camera created. W: " + width + " H: " + height);
+ }
+
+ /**
+ * Construct a new camera, using the given source camera's values.
+ *
+ * @param source
+ */
+ public Camera(final Camera source) {
+
+ _coeffLeft = new double[2];
+ _coeffRight = new double[2];
+ _coeffBottom = new double[2];
+ _coeffTop = new double[2];
+
+ _worldPlane = new Plane[MAX_WORLD_PLANES];
+ for (int i = 0; i < MAX_WORLD_PLANES; i++) {
+ _worldPlane[i] = new Plane();
+ }
+
+ set(source);
+
+ _logger.fine("Camera created. W: " + getWidth() + " H: " + getHeight());
+ }
+
+ /**
+ * Copy the source camera's fields to this camera
+ *
+ * @param source
+ * the camera to copy from
+ */
+ public void set(final Camera source) {
+ _width = source.getWidth();
+ _height = source.getHeight();
+
+ _location.set(source.getLocation());
+ _left.set(source.getLeft());
+ _up.set(source.getUp());
+ _direction.set(source.getDirection());
+ _fovY = source.getFovY();
+
+ _depthRangeNear = source.getDepthRangeNear();
+ _depthRangeFar = source.getDepthRangeFar();
+ _depthRangeDirty = true;
+
+ _frustumNear = source.getFrustumNear();
+ _frustumFar = source.getFrustumFar();
+ _frustumLeft = source.getFrustumLeft();
+ _frustumRight = source.getFrustumRight();
+ _frustumTop = source.getFrustumTop();
+ _frustumBottom = source.getFrustumBottom();
+
+ _viewPortLeft = source.getViewPortLeft();
+ _viewPortRight = source.getViewPortRight();
+ _viewPortTop = source.getViewPortTop();
+ _viewPortBottom = source.getViewPortBottom();
+
+ _planeQuantity = 6;
+
+ _projectionMode = source.getProjectionMode();
+
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+ }
+
+ public double getDepthRangeFar() {
+ return _depthRangeFar;
+ }
+
+ /**
+ * @param depthRangeNear
+ * the far clipping plane for window coordinates. Should be in the range [0, 1]. Default is 1.
+ */
+ public void setDepthRangeFar(final double depthRangeFar) {
+ _depthRangeFar = depthRangeFar;
+ _depthRangeDirty = true;
+ }
+
+ public double getDepthRangeNear() {
+ return _depthRangeNear;
+ }
+
+ /**
+ * @param depthRangeNear
+ * the near clipping plane for window coordinates. Should be in the range [0, 1]. Default is 0.
+ */
+ public void setDepthRangeNear(final double depthRangeNear) {
+ _depthRangeNear = depthRangeNear;
+ _depthRangeDirty = true;
+ }
+
+ /**
+ * @return the value of the bottom frustum plane.
+ */
+ public double getFrustumBottom() {
+ return _frustumBottom;
+ }
+
+ /**
+ * @param frustumBottom
+ * the new value of the bottom frustum plane.
+ */
+ public void setFrustumBottom(final double frustumBottom) {
+ _frustumBottom = frustumBottom;
+ onFrustumChange();
+ }
+
+ /**
+ * = * @return the value of the far frustum plane.
+ */
+ public double getFrustumFar() {
+ return _frustumFar;
+ }
+
+ /**
+ * @param frustumFar
+ * the new value of the far frustum plane.
+ */
+ public void setFrustumFar(final double frustumFar) {
+ _frustumFar = frustumFar;
+ onFrustumChange();
+ }
+
+ /**
+ * @return the value of the left frustum plane.
+ */
+ public double getFrustumLeft() {
+ return _frustumLeft;
+ }
+
+ /**
+ * @param frustumLeft
+ * the new value of the left frustum plane.
+ */
+ public void setFrustumLeft(final double frustumLeft) {
+ _frustumLeft = frustumLeft;
+ onFrustumChange();
+ }
+
+ /**
+ * @return the value of the near frustum plane.
+ */
+ public double getFrustumNear() {
+ return _frustumNear;
+ }
+
+ /**
+ * @param frustumNear
+ * the new value of the near frustum plane.
+ */
+ public void setFrustumNear(final double frustumNear) {
+ _frustumNear = frustumNear;
+ onFrustumChange();
+ }
+
+ /**
+ * @return frustumRight the value of the right frustum plane.
+ */
+ public double getFrustumRight() {
+ return _frustumRight;
+ }
+
+ /**
+ * @param frustumRight
+ * the new value of the right frustum plane.
+ */
+ public void setFrustumRight(final double frustumRight) {
+ _frustumRight = frustumRight;
+ onFrustumChange();
+ }
+
+ /**
+ * @return the value of the top frustum plane.
+ */
+ public double getFrustumTop() {
+ return _frustumTop;
+ }
+
+ /**
+ * @param frustumTop
+ * the new value of the top frustum plane.
+ */
+ public void setFrustumTop(final double frustumTop) {
+ _frustumTop = frustumTop;
+ onFrustumChange();
+ }
+
+ /**
+ * @return the current position of the camera.
+ */
+ public ReadOnlyVector3 getLocation() {
+ return _location;
+ }
+
+ /**
+ * @return the current direction the camera is facing.
+ */
+ public ReadOnlyVector3 getDirection() {
+ return _direction;
+ }
+
+ /**
+ * @return the left axis of the camera.
+ */
+ public ReadOnlyVector3 getLeft() {
+ return _left;
+ }
+
+ /**
+ * @return the up axis of the camera.
+ */
+ public ReadOnlyVector3 getUp() {
+ return _up;
+ }
+
+ /**
+ * @param location
+ * the new location or position of the camera.
+ */
+ public void setLocation(final ReadOnlyVector3 location) {
+ _location.set(location);
+ onFrameChange();
+ }
+
+ /**
+ * @param location
+ * the new location or position of the camera.
+ */
+ public void setLocation(final double x, final double y, final double z) {
+ _location.set(x, y, z);
+ onFrameChange();
+ }
+
+ /**
+ * Sets the new direction this camera is facing. This does not change left or up axes, so make sure those vectors
+ * are properly set as well.
+ *
+ * @param direction
+ * the new direction this camera is facing.
+ */
+ public void setDirection(final ReadOnlyVector3 direction) {
+ _direction.set(direction);
+ onFrameChange();
+ }
+
+ /**
+ * Sets the new left axis of this camera. This does not change direction or up axis, so make sure those vectors are
+ * properly set as well.
+ *
+ * @param left
+ * the new left axis of this camera.
+ */
+ public void setLeft(final ReadOnlyVector3 left) {
+ _left.set(left);
+ onFrameChange();
+ }
+
+ /**
+ * Sets the new up axis of this camera. This does not change direction or left axis, so make sure those vectors are
+ * properly set as well.
+ *
+ * @param up
+ * the new up axis of this camera.
+ */
+ public void setUp(final ReadOnlyVector3 up) {
+ _up.set(up);
+ onFrameChange();
+ }
+
+ /**
+ * @param left
+ * the new left axis of the camera.
+ * @param up
+ * the new up axis of the camera.
+ * @param direction
+ * the new direction the camera is facing.
+ */
+ public void setAxes(final ReadOnlyVector3 left, final ReadOnlyVector3 up, final ReadOnlyVector3 direction) {
+ _left.set(left);
+ _up.set(up);
+ _direction.set(direction);
+ onFrameChange();
+ }
+
+ /**
+ * Sets our left, up and direction values from the given rotation matrix.
+ *
+ * @param axes
+ * the matrix that defines the orientation of the camera.
+ */
+ public void setAxes(final ReadOnlyMatrix3 axes) {
+ axes.getColumn(0, _left);
+ axes.getColumn(1, _up);
+ axes.getColumn(2, _direction);
+ onFrameChange();
+ }
+
+ /**
+ * Ensure our up, left and direction are unit-length vectors.
+ */
+ public void normalize() {
+ _left.normalizeLocal();
+ _up.normalizeLocal();
+ _direction.normalizeLocal();
+ onFrameChange();
+ }
+
+ /**
+ * Sets the frustum plane values of this camera using the given values.
+ *
+ * @param near
+ * @param far
+ * @param left
+ * @param right
+ * @param top
+ * @param bottom
+ */
+ public void setFrustum(final double near, final double far, final double left, final double right,
+ final double top, final double bottom) {
+ _frustumNear = near;
+ _frustumFar = far;
+ _frustumLeft = left;
+ _frustumRight = right;
+ _frustumTop = top;
+ _frustumBottom = bottom;
+ onFrustumChange();
+ }
+
+ /**
+ * Sets the frustum plane values of this camera using those of a given source camera
+ *
+ * @param source
+ * a source camera.
+ */
+ public void setFrustum(final Camera source) {
+ _frustumNear = source.getFrustumNear();
+ _frustumFar = source.getFrustumFar();
+ _frustumLeft = source.getFrustumLeft();
+ _frustumRight = source.getFrustumRight();
+ _frustumTop = source.getFrustumTop();
+ _frustumBottom = source.getFrustumBottom();
+ onFrustumChange();
+ }
+
+ /**
+ * Sets the frustum plane values of this camera using the given perspective values.
+ *
+ * @param fovY
+ * the full angle of view on the Y axis, in degrees.
+ * @param aspect
+ * the aspect ratio of our view (generally in [0,1]). Often this is canvas width / canvas height.
+ * @param near
+ * our near plane value
+ * @param far
+ * our far plane value
+ */
+ public void setFrustumPerspective(final double fovY, final double aspect, final double near, final double far) {
+ if (Double.isNaN(aspect) || Double.isInfinite(aspect)) {
+ // ignore.
+ _logger.warning("Invalid aspect given to setFrustumPerspective: " + aspect);
+ return;
+ }
+ _fovY = fovY;
+ final double h = Math.tan(_fovY * MathUtils.DEG_TO_RAD * .5) * near;
+ final double w = h * aspect;
+ _frustumLeft = -w;
+ _frustumRight = w;
+ _frustumBottom = -h;
+ _frustumTop = h;
+ _frustumNear = near;
+ _frustumFar = far;
+ onFrustumChange();
+ }
+
+ /**
+ * Accessor for the fovY value. Note that this value is only present if setFrustumPerspective was previously called.
+ *
+ * @return the fovY value
+ */
+ public double getFovY() {
+ return _fovY;
+ }
+
+ /**
+ * Sets the axes and location of the camera. Similar to
+ * {@link #setAxes(ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3)}, but sets camera location as well.
+ *
+ * @param location
+ * @param left
+ * @param up
+ * @param direction
+ */
+ public void setFrame(final ReadOnlyVector3 location, final ReadOnlyVector3 left, final ReadOnlyVector3 up,
+ final ReadOnlyVector3 direction) {
+ _left.set(left);
+ _up.set(up);
+ _direction.set(direction);
+ _location.set(location);
+ onFrameChange();
+ }
+
+ /**
+ * Sets the axes and location of the camera. Similar to {@link #setAxes(ReadOnlyMatrix3)}, but sets camera location
+ * as well.
+ *
+ * @param location
+ * the point position of the camera.
+ * @param axes
+ * the orientation of the camera.
+ */
+ public void setFrame(final ReadOnlyVector3 location, final ReadOnlyMatrix3 axes) {
+ axes.getColumn(0, _left);
+ axes.getColumn(1, _up);
+ axes.getColumn(2, _direction);
+ _location.set(location);
+ onFrameChange();
+ }
+
+ /**
+ * Sets the axes and location of the camera using those of a given source camera
+ *
+ * @param source
+ * a source camera.
+ */
+ public void setFrame(final Camera source) {
+ _left.set(source.getLeft());
+ _up.set(source.getUp());
+ _direction.set(source.getDirection());
+ _location.set(source.getLocation());
+ onFrameChange();
+ }
+
+ /**
+ * A convenience method for auto-setting the frame based on a world position the user desires the camera to look at.
+ * It points the camera towards the given position using the difference between that position and the current camera
+ * location as a direction vector and the general worldUpVector to compute up and left camera vectors.
+ *
+ * @param pos
+ * where to look at in terms of world coordinates
+ * @param worldUpVector
+ * a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or
+ * {@link Vector3#UNIT_Z})
+ */
+ public void lookAt(final ReadOnlyVector3 pos, final ReadOnlyVector3 worldUpVector) {
+ lookAt(pos.getX(), pos.getY(), pos.getZ(), worldUpVector);
+ }
+
+ /**
+ * A convenience method for auto-setting the frame based on a world position the user desires the camera to look at.
+ * It points the camera towards the given position using the difference between that position and the current camera
+ * location as a direction vector and the general worldUpVector to compute up and left camera vectors.
+ *
+ * @param x
+ * where to look at in terms of world coordinates (x)
+ * @param y
+ * where to look at in terms of world coordinates (y)
+ * @param z
+ * where to look at in terms of world coordinates (z)
+ * @param worldUpVector
+ * a normalized vector indicating the up direction of the world. (often {@link Vector3#UNIT_Y} or
+ * {@link Vector3#UNIT_Z})
+ */
+ public void lookAt(final double x, final double y, final double z, final ReadOnlyVector3 worldUpVector) {
+ final Vector3 newDirection = _tempVector;
+ newDirection.set(x, y, z).subtractLocal(_location).normalizeLocal();
+
+ // check to see if we haven't really updated camera -- no need to call sets.
+ if (newDirection.equals(_direction)) {
+ return;
+ }
+ _direction.set(newDirection);
+
+ _up.set(worldUpVector).normalizeLocal();
+ if (_up.equals(Vector3.ZERO)) {
+ _up.set(Vector3.UNIT_Y);
+ }
+ _left.set(_up).crossLocal(_direction).normalizeLocal();
+ if (_left.equals(Vector3.ZERO)) {
+ if (_direction.getX() != 0.0) {
+ _left.set(_direction.getY(), -_direction.getX(), 0);
+ } else {
+ _left.set(0, _direction.getZ(), -_direction.getY());
+ }
+ }
+ _up.set(_direction).crossLocal(_left).normalizeLocal();
+ onFrameChange();
+ }
+
+ /**
+ * Forces all aspect of the camera to be updated from internal values, and sets all dirty flags to true so that the
+ * next apply() call will fully set this camera to the render context.
+ */
+ public void update() {
+ _depthRangeDirty = true;
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+ }
+
+ /**
+ * @return an internally used bitmask describing what frustum planes have been examined for culling thus far.
+ */
+ public int getPlaneState() {
+ return _planeState;
+ }
+
+ /**
+ * @param planeState
+ * a new value for planeState.
+ * @see #getPlaneState()
+ */
+ public void setPlaneState(final int planeState) {
+ _planeState = planeState;
+ }
+
+ /**
+ * @return the left boundary of the viewport
+ */
+ public double getViewPortLeft() {
+ return _viewPortLeft;
+ }
+
+ /**
+ * @param left
+ * the new left boundary of the viewport
+ */
+ public void setViewPortLeft(final double left) {
+ _viewPortLeft = left;
+ onViewPortChange();
+ }
+
+ /**
+ * @return the right boundary of the viewport
+ */
+ public double getViewPortRight() {
+ return _viewPortRight;
+ }
+
+ /**
+ * @param right
+ * the new right boundary of the viewport
+ */
+ public void setViewPortRight(final double right) {
+ _viewPortRight = right;
+ onViewPortChange();
+ }
+
+ /**
+ * @return the top boundary of the viewport
+ */
+ public double getViewPortTop() {
+ return _viewPortTop;
+ }
+
+ /**
+ * @param top
+ * the new top boundary of the viewport
+ */
+ public void setViewPortTop(final double top) {
+ _viewPortTop = top;
+ onViewPortChange();
+ }
+
+ /**
+ * @return the bottom boundary of the viewport
+ */
+ public double getViewPortBottom() {
+ return _viewPortBottom;
+ }
+
+ /**
+ * @param bottom
+ * the new bottom boundary of the viewport
+ */
+ public void setViewPortBottom(final double bottom) {
+ _viewPortBottom = bottom;
+ onViewPortChange();
+ }
+
+ /**
+ * Sets the boundaries of this camera's viewport to the given values
+ *
+ * @param left
+ * @param right
+ * @param bottom
+ * @param top
+ */
+ public void setViewPort(final double left, final double right, final double bottom, final double top) {
+ setViewPortLeft(left);
+ setViewPortRight(right);
+ setViewPortBottom(bottom);
+ setViewPortTop(top);
+ }
+
+ /**
+ * Checks a bounding volume against the planes of this camera's frustum and returns if it is completely inside of,
+ * outside of, or intersecting.
+ *
+ * @param bound
+ * the bound to check for culling
+ * @return intersection type
+ */
+ public Camera.FrustumIntersect contains(final BoundingVolume bound) {
+ if (bound == null) {
+ return FrustumIntersect.Inside;
+ }
+
+ int mask;
+ FrustumIntersect rVal = FrustumIntersect.Inside;
+
+ for (int planeCounter = FRUSTUM_PLANES; planeCounter >= 0; planeCounter--) {
+ if (planeCounter == bound.getCheckPlane()) {
+ continue; // we have already checked this plane at first iteration
+ }
+ final int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
+
+ mask = 1 << (planeId);
+ if ((_planeState & mask) == 0) {
+ switch (bound.whichSide(_worldPlane[planeId])) {
+ case Inside:
+ // object is outside of frustum
+ bound.setCheckPlane(planeId);
+ return FrustumIntersect.Outside;
+ case Outside:
+ // object is visible on *this* plane, so mark this plane
+ // so that we don't check it for sub nodes.
+ _planeState |= mask;
+ break;
+ case Neither:
+ rVal = FrustumIntersect.Intersects;
+ break;
+ }
+ }
+ }
+
+ return rVal;
+ }
+
+ /**
+ * Resizes this camera's view with the given width and height.
+ *
+ * @param width
+ * the view width
+ * @param height
+ * the view height
+ */
+ public void resize(final int width, final int height) {
+ _width = width;
+ _height = height;
+ onViewPortChange();
+ }
+
+ /**
+ * Updates internal frustum coefficient values to reflect the current frustum plane values.
+ */
+ public void onFrustumChange() {
+ if (getProjectionMode() == ProjectionMode.Perspective) {
+ final double nearSquared = _frustumNear * _frustumNear;
+ final double leftSquared = _frustumLeft * _frustumLeft;
+ final double rightSquared = _frustumRight * _frustumRight;
+ final double bottomSquared = _frustumBottom * _frustumBottom;
+ final double topSquared = _frustumTop * _frustumTop;
+
+ double inverseLength = 1.0 / Math.sqrt(nearSquared + leftSquared);
+ _coeffLeft[0] = _frustumNear * inverseLength;
+ _coeffLeft[1] = -_frustumLeft * inverseLength;
+
+ inverseLength = 1.0 / Math.sqrt(nearSquared + rightSquared);
+ _coeffRight[0] = -_frustumNear * inverseLength;
+ _coeffRight[1] = _frustumRight * inverseLength;
+
+ inverseLength = 1.0 / Math.sqrt(nearSquared + bottomSquared);
+ _coeffBottom[0] = _frustumNear * inverseLength;
+ _coeffBottom[1] = -_frustumBottom * inverseLength;
+
+ inverseLength = 1.0 / Math.sqrt(nearSquared + topSquared);
+ _coeffTop[0] = -_frustumNear * inverseLength;
+ _coeffTop[1] = _frustumTop * inverseLength;
+ } else if (getProjectionMode() == ProjectionMode.Parallel) {
+ if (_frustumRight > _frustumLeft) {
+ _coeffLeft[0] = -1;
+ _coeffLeft[1] = 0;
+
+ _coeffRight[0] = 1;
+ _coeffRight[1] = 0;
+ } else {
+ _coeffLeft[0] = 1;
+ _coeffLeft[1] = 0;
+
+ _coeffRight[0] = -1;
+ _coeffRight[1] = 0;
+ }
+
+ if (_frustumTop > _frustumBottom) {
+ _coeffBottom[0] = -1;
+ _coeffBottom[1] = 0;
+
+ _coeffTop[0] = 1;
+ _coeffTop[1] = 0;
+ } else {
+ _coeffBottom[0] = 1;
+ _coeffBottom[1] = 0;
+
+ _coeffTop[0] = -1;
+ _coeffTop[1] = 0;
+ }
+ }
+
+ _updatePMatrix = true;
+ _updateMVPMatrix = true;
+ _updateInverseMVPMatrix = true;
+
+ _frustumDirty = true;
+ }
+
+ /**
+ * Updates the values of the world planes associated with this camera.
+ */
+ public void onFrameChange() {
+ final double dirDotLocation = _direction.dot(_location);
+
+ final Vector3 planeNormal = Vector3.fetchTempInstance();
+
+ // left plane
+ planeNormal.setX(_left.getX() * _coeffLeft[0]);
+ planeNormal.setY(_left.getY() * _coeffLeft[0]);
+ planeNormal.setZ(_left.getZ() * _coeffLeft[0]);
+ planeNormal.addLocal(_direction.getX() * _coeffLeft[1], _direction.getY() * _coeffLeft[1], _direction.getZ()
+ * _coeffLeft[1]);
+ _worldPlane[LEFT_PLANE].setNormal(planeNormal);
+ _worldPlane[LEFT_PLANE].setConstant(_location.dot(planeNormal));
+
+ // right plane
+ planeNormal.setX(_left.getX() * _coeffRight[0]);
+ planeNormal.setY(_left.getY() * _coeffRight[0]);
+ planeNormal.setZ(_left.getZ() * _coeffRight[0]);
+ planeNormal.addLocal(_direction.getX() * _coeffRight[1], _direction.getY() * _coeffRight[1], _direction.getZ()
+ * _coeffRight[1]);
+ _worldPlane[RIGHT_PLANE].setNormal(planeNormal);
+ _worldPlane[RIGHT_PLANE].setConstant(_location.dot(planeNormal));
+
+ // bottom plane
+ planeNormal.setX(_up.getX() * _coeffBottom[0]);
+ planeNormal.setY(_up.getY() * _coeffBottom[0]);
+ planeNormal.setZ(_up.getZ() * _coeffBottom[0]);
+ planeNormal.addLocal(_direction.getX() * _coeffBottom[1], _direction.getY() * _coeffBottom[1],
+ _direction.getZ() * _coeffBottom[1]);
+ _worldPlane[BOTTOM_PLANE].setNormal(planeNormal);
+ _worldPlane[BOTTOM_PLANE].setConstant(_location.dot(planeNormal));
+
+ // top plane
+ planeNormal.setX(_up.getX() * _coeffTop[0]);
+ planeNormal.setY(_up.getY() * _coeffTop[0]);
+ planeNormal.setZ(_up.getZ() * _coeffTop[0]);
+ planeNormal.addLocal(_direction.getX() * _coeffTop[1], _direction.getY() * _coeffTop[1], _direction.getZ()
+ * _coeffTop[1]);
+ _worldPlane[TOP_PLANE].setNormal(planeNormal);
+ _worldPlane[TOP_PLANE].setConstant(_location.dot(planeNormal));
+
+ if (getProjectionMode() == ProjectionMode.Parallel) {
+ if (_frustumRight > _frustumLeft) {
+ _worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() + _frustumLeft);
+ _worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() - _frustumRight);
+ } else {
+ _worldPlane[LEFT_PLANE].setConstant(_worldPlane[LEFT_PLANE].getConstant() - _frustumLeft);
+ _worldPlane[RIGHT_PLANE].setConstant(_worldPlane[RIGHT_PLANE].getConstant() + _frustumRight);
+ }
+
+ if (_frustumBottom > _frustumTop) {
+ _worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() + _frustumTop);
+ _worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() - _frustumBottom);
+ } else {
+ _worldPlane[TOP_PLANE].setConstant(_worldPlane[TOP_PLANE].getConstant() - _frustumTop);
+ _worldPlane[BOTTOM_PLANE].setConstant(_worldPlane[BOTTOM_PLANE].getConstant() + _frustumBottom);
+ }
+ }
+
+ // far plane
+ planeNormal.set(_direction).negateLocal();
+ _worldPlane[FAR_PLANE].setNormal(planeNormal);
+ _worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + _frustumFar));
+
+ // near plane
+ _worldPlane[NEAR_PLANE].setNormal(_direction);
+ _worldPlane[NEAR_PLANE].setConstant(dirDotLocation + _frustumNear);
+
+ Vector3.releaseTempInstance(planeNormal);
+
+ _updateMVMatrix = true;
+ _updateMVPMatrix = true;
+ _updateInverseMVPMatrix = true;
+
+ _frameDirty = true;
+ }
+
+ /**
+ * Updates the value of our projection matrix.
+ */
+ protected void updateProjectionMatrix() {
+ if (getProjectionMode() == ProjectionMode.Parallel) {
+ _projection.setIdentity();
+ _projection.setM00(2.0 / (_frustumRight - _frustumLeft));
+ _projection.setM11(2.0 / (_frustumTop - _frustumBottom));
+ _projection.setM22(-2.0 / (_frustumFar - _frustumNear));
+ _projection.setM30(-(_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft));
+ _projection.setM31(-(_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom));
+ _projection.setM32(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear));
+ } else if (getProjectionMode() == ProjectionMode.Perspective) {
+ _projection.setIdentity();
+ _projection.setM00((2.0 * _frustumNear) / (_frustumRight - _frustumLeft));
+ _projection.setM11((2.0 * _frustumNear) / (_frustumTop - _frustumBottom));
+ _projection.setM20((_frustumRight + _frustumLeft) / (_frustumRight - _frustumLeft));
+ _projection.setM21((_frustumTop + _frustumBottom) / (_frustumTop - _frustumBottom));
+ _projection.setM22(-(_frustumFar + _frustumNear) / (_frustumFar - _frustumNear));
+ _projection.setM23(-1.0);
+ _projection.setM32(-(2.0 * _frustumFar * _frustumNear) / (_frustumFar - _frustumNear));
+ _projection.setM33(-0.0);
+ }
+
+ _updatePMatrix = false;
+ }
+
+ public void setProjectionMatrix(final ReadOnlyMatrix4 projection) {
+ _projection.set(projection);
+ _frustumDirty = true;
+ }
+
+ /**
+ * @return this camera's 4x4 projection matrix.
+ */
+ public ReadOnlyMatrix4 getProjectionMatrix() {
+ checkProjection();
+
+ return _projection;
+ }
+
+ /**
+ * Updates the value of our model view matrix.
+ */
+ protected void updateModelViewMatrix() {
+ _modelView.setIdentity();
+ _modelView.setM00(-_left.getX());
+ _modelView.setM10(-_left.getY());
+ _modelView.setM20(-_left.getZ());
+
+ _modelView.setM01(_up.getX());
+ _modelView.setM11(_up.getY());
+ _modelView.setM21(_up.getZ());
+
+ _modelView.setM02(-_direction.getX());
+ _modelView.setM12(-_direction.getY());
+ _modelView.setM22(-_direction.getZ());
+
+ _modelView.setM30(_left.dot(_location));
+ _modelView.setM31(-_up.dot(_location));
+ _modelView.setM32(_direction.dot(_location));
+ }
+
+ /**
+ * @return this camera's 4x4 model view matrix.
+ */
+ public ReadOnlyMatrix4 getModelViewMatrix() {
+ checkModelView();
+
+ return _modelView;
+ }
+
+ /**
+ * @return this camera's 4x4 model view X projection matrix.
+ */
+ public ReadOnlyMatrix4 getModelViewProjectionMatrix() {
+ checkModelViewProjection();
+
+ return _modelViewProjection;
+ }
+
+ /**
+ * @return the inverse of this camera's 4x4 model view X projection matrix.
+ */
+ public ReadOnlyMatrix4 getModelViewProjectionInverseMatrix() {
+ checkInverseModelViewProjection();
+
+ return _modelViewProjectionInverse;
+ }
+
+ /**
+ * Calculate a Pick Ray using the given screen position at the near plane of this camera and the camera's position
+ * in space.
+ *
+ * @param screenPosition
+ * the x, y position on the near space to pass the ray through.
+ * @param flipVertical
+ * if true, we'll flip the screenPosition on the y axis. This is useful when you are dealing with
+ * non-opengl coordinate systems.
+ * @param store
+ * the Ray to store the result in. If false, a new Ray is created and returned.
+ * @return the resulting Ray.
+ */
+ public Ray3 getPickRay(final ReadOnlyVector2 screenPosition, final boolean flipVertical, final Ray3 store) {
+ final Vector2 pos = Vector2.fetchTempInstance().set(screenPosition);
+ if (flipVertical) {
+ pos.setY(getHeight() - screenPosition.getY());
+ }
+
+ Ray3 result = store;
+ if (result == null) {
+ result = new Ray3();
+ }
+ final Vector3 origin = Vector3.fetchTempInstance();
+ final Vector3 direction = Vector3.fetchTempInstance();
+ getWorldCoordinates(pos, 0, origin);
+ getWorldCoordinates(pos, 0.3, direction).subtractLocal(origin).normalizeLocal();
+ result.setOrigin(origin);
+ result.setDirection(direction);
+ Vector2.releaseTempInstance(pos);
+ Vector3.releaseTempInstance(origin);
+ Vector3.releaseTempInstance(direction);
+ return result;
+ }
+
+ /**
+ * Converts a local x,y screen position and depth value to world coordinates based on the current settings of this
+ * camera.
+ *
+ * @param screenPosition
+ * the x,y coordinates of the screen position
+ * @param zDepth
+ * the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1
+ * indicates the far plane.
+ * @return a new vector containing the world coordinates.
+ */
+ public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth) {
+ return getWorldCoordinates(screenPosition, zDepth, null);
+ }
+
+ /**
+ * Converts a local x,y screen position and depth value to world coordinates based on the current settings of this
+ * camera.
+ *
+ * @param screenPosition
+ * the x,y coordinates of the screen position
+ * @param zDepth
+ * the depth into the camera view to take our point. 0 indicates the near plane of the camera and 1
+ * indicates the far plane.
+ * @param store
+ * Use to avoid object creation. if not null, the results are stored in the given vector and returned.
+ * Otherwise, a new vector is created.
+ * @return a vector containing the world coordinates.
+ */
+ public Vector3 getWorldCoordinates(final ReadOnlyVector2 screenPosition, final double zDepth, Vector3 store) {
+ if (store == null) {
+ store = new Vector3();
+ }
+ checkInverseModelViewProjection();
+ final Vector4 position = Vector4.fetchTempInstance();
+ position.set((screenPosition.getX() / getWidth() - _viewPortLeft) / (_viewPortRight - _viewPortLeft) * 2 - 1,
+ (screenPosition.getY() / getHeight() - _viewPortBottom) / (_viewPortTop - _viewPortBottom) * 2 - 1,
+ zDepth * 2 - 1, 1);
+ _modelViewProjectionInverse.applyPre(position, position);
+ position.multiplyLocal(1.0 / position.getW());
+ store.setX(position.getX());
+ store.setY(position.getY());
+ store.setZ(position.getZ());
+
+ Vector4.releaseTempInstance(position);
+ return store;
+ }
+
+ /**
+ * Converts a position in world coordinate space to an x,y screen position and depth value using the current
+ * settings of this camera.
+ *
+ * @param worldPos
+ * the position in space to retrieve screen coordinates for.
+ * @return a new vector containing the screen coordinates as x and y and the distance as a percent between near and
+ * far planes.
+ */
+ public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPos) {
+ return getScreenCoordinates(worldPos, null);
+ }
+
+ /**
+ * Converts a position in world coordinate space to an x,y screen position and depth value using the current
+ * settings of this camera.
+ *
+ * @param worldPos
+ * the position in space to retrieve screen coordinates for.
+ * @param store
+ * Use to avoid object creation. if not null, the results are stored in the given vector and returned.
+ * Otherwise, a new vector is created.
+ * @return a vector containing the screen coordinates as x and y and the distance as a percent between near and far
+ * planes.
+ */
+ public Vector3 getScreenCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
+ store = getNormalizedDeviceCoordinates(worldPosition, store);
+
+ store.setX(((store.getX() + 1) * (_viewPortRight - _viewPortLeft) / 2) * getWidth());
+ store.setY(((store.getY() + 1) * (_viewPortTop - _viewPortBottom) / 2) * getHeight());
+ store.setZ((store.getZ() + 1) / 2);
+
+ return store;
+ }
+
+ /**
+ * Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this
+ * camera.
+ *
+ * @param worldPos
+ * the position in space to retrieve frustum coordinates for.
+ * @return a new vector containing the x,y,z frustum position
+ */
+ public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPos) {
+ return getFrustumCoordinates(worldPos, null);
+ }
+
+ /**
+ * Converts a position in world coordinate space to a x,y,z frustum position using the current settings of this
+ * camera.
+ *
+ * @param worldPos
+ * the position in space to retrieve frustum coordinates for.
+ * @param store
+ * Use to avoid object creation. if not null, the results are stored in the given vector and returned.
+ * Otherwise, a new vector is created.
+ * @return a vector containing the x,y,z frustum position
+ */
+ public Vector3 getFrustumCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
+ store = getNormalizedDeviceCoordinates(worldPosition, store);
+
+ store.setX(((store.getX() + 1) * (_frustumRight - _frustumLeft) / 2) + _frustumLeft);
+ store.setY(((store.getY() + 1) * (_frustumTop - _frustumBottom) / 2) + _frustumBottom);
+ store.setZ(((store.getZ() + 1) * (_frustumFar - _frustumNear) / 2) + _frustumNear);
+
+ return store;
+ }
+
+ public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPos) {
+ return getNormalizedDeviceCoordinates(worldPos, null);
+ }
+
+ public Vector3 getNormalizedDeviceCoordinates(final ReadOnlyVector3 worldPosition, Vector3 store) {
+ if (store == null) {
+ store = new Vector3();
+ }
+ checkModelViewProjection();
+ final Vector4 position = Vector4.fetchTempInstance();
+ position.set(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ(), 1);
+ _modelViewProjection.applyPre(position, position);
+ position.multiplyLocal(1.0 / position.getW());
+ store.setX(position.getX());
+ store.setY(position.getY());
+ store.setZ(position.getZ());
+ Vector4.releaseTempInstance(position);
+
+ return store;
+ }
+
+ /**
+ * update modelView if necessary.
+ */
+ private void checkModelView() {
+ if (_updateMVMatrix) {
+ updateModelViewMatrix();
+ _updateMVMatrix = false;
+ }
+ }
+
+ /**
+ * update projection if necessary.
+ */
+ private void checkProjection() {
+ if (_updatePMatrix) {
+ updateProjectionMatrix();
+ _updatePMatrix = false;
+ }
+ }
+
+ /**
+ * update modelViewProjection if necessary.
+ */
+ private void checkModelViewProjection() {
+ if (_updateMVPMatrix) {
+ checkModelView();
+ checkProjection();
+ _modelViewProjection.set(getModelViewMatrix()).multiplyLocal(getProjectionMatrix());
+ _updateMVPMatrix = false;
+ }
+ }
+
+ /**
+ * update inverse modelViewProjection if necessary.
+ */
+ private void checkInverseModelViewProjection() {
+ if (_updateInverseMVPMatrix) {
+ checkModelViewProjection();
+ _modelViewProjection.invert(_modelViewProjectionInverse);
+ _updateInverseMVPMatrix = false;
+ }
+ }
+
+ /**
+ * @return the height of the display.
+ */
+ public int getHeight() {
+ return _height;
+ }
+
+ public ProjectionMode getProjectionMode() {
+ return _projectionMode;
+ }
+
+ public void setProjectionMode(final ProjectionMode projectionMode) {
+ _projectionMode = projectionMode;
+ }
+
+ /**
+ * @return the width of the display.
+ */
+ public int getWidth() {
+ return _width;
+ }
+
+ /**
+ * Apply this camera's values to the given Renderer. Only values determined to be dirty (via updates, setters, etc.)
+ * will be applied. This method must be run in the same thread as a valid OpenGL context.
+ *
+ * @param renderer
+ * the Renderer to use.
+ */
+ public void apply(final Renderer renderer) {
+ ContextManager.getCurrentContext().setCurrentCamera(this);
+ if (_depthRangeDirty) {
+ renderer.setDepthRange(_depthRangeNear, _depthRangeFar);
+ _depthRangeDirty = false;
+ }
+ if (_frustumDirty) {
+ applyProjectionMatrix(renderer);
+ _frustumDirty = false;
+ }
+ if (_viewPortDirty) {
+ applyViewport(renderer);
+ _viewPortDirty = false;
+ }
+ if (_frameDirty) {
+ applyModelViewMatrix(renderer);
+ _frameDirty = false;
+ }
+ }
+
+ /**
+ * Mark view port dirty.
+ */
+ protected void onViewPortChange() {
+ _viewPortDirty = true;
+ }
+
+ /**
+ * Apply the camera's projection matrix using the given Renderer.
+ *
+ * @param renderer
+ * the Renderer to use.
+ */
+ protected void applyProjectionMatrix(final Renderer renderer) {
+ _matrixBuffer.rewind();
+ getProjectionMatrix().toFloatBuffer(_matrixBuffer);
+ _matrixBuffer.rewind();
+ renderer.setProjectionMatrix(_matrixBuffer);
+ }
+
+ /**
+ * Apply the camera's viewport using the given Renderer.
+ *
+ * @param renderer
+ * the Renderer to use.
+ */
+ protected void applyViewport(final Renderer renderer) {
+ final int x = (int) (_viewPortLeft * _width);
+ final int y = (int) (_viewPortBottom * _height);
+ final int w = (int) ((_viewPortRight - _viewPortLeft) * _width);
+ final int h = (int) ((_viewPortTop - _viewPortBottom) * _height);
+ renderer.setViewport(x, y, w, h);
+ }
+
+ /**
+ * Apply the camera's modelview matrix using the given Renderer.
+ *
+ * @param renderer
+ * the Renderer to use.
+ */
+ protected void applyModelViewMatrix(final Renderer renderer) {
+ _matrixBuffer.rewind();
+ getModelViewMatrix().toFloatBuffer(_matrixBuffer);
+ _matrixBuffer.rewind();
+ renderer.setModelViewMatrix(_matrixBuffer);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_location, "location", new Vector3(Vector3.ZERO));
+ capsule.write(_left, "left", new Vector3(Vector3.UNIT_X));
+ capsule.write(_up, "up", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_direction, "direction", new Vector3(Vector3.UNIT_Z));
+ capsule.write(_frustumNear, "frustumNear", 1);
+ capsule.write(_frustumFar, "frustumFar", 2);
+ capsule.write(_frustumLeft, "frustumLeft", -0.5);
+ capsule.write(_frustumRight, "frustumRight", 0.5);
+ capsule.write(_frustumTop, "frustumTop", 0.5);
+ capsule.write(_frustumBottom, "frustumBottom", -0.5);
+ capsule.write(_coeffLeft, "coeffLeft", new double[2]);
+ capsule.write(_coeffRight, "coeffRight", new double[2]);
+ capsule.write(_coeffBottom, "coeffBottom", new double[2]);
+ capsule.write(_coeffTop, "coeffTop", new double[2]);
+ capsule.write(_planeQuantity, "planeQuantity", 6);
+ capsule.write(_viewPortLeft, "viewPortLeft", 0);
+ capsule.write(_viewPortRight, "viewPortRight", 1);
+ capsule.write(_viewPortTop, "viewPortTop", 1);
+ capsule.write(_viewPortBottom, "viewPortBottom", 0);
+ capsule.write(_width, "width", 0);
+ capsule.write(_height, "height", 0);
+ capsule.write(_depthRangeNear, "depthRangeNear", 0.0);
+ capsule.write(_depthRangeFar, "depthRangeFar", 1.0);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _location.set((Vector3) capsule.readSavable("location", new Vector3(Vector3.ZERO)));
+ _left.set((Vector3) capsule.readSavable("left", new Vector3(Vector3.UNIT_X)));
+ _up.set((Vector3) capsule.readSavable("up", new Vector3(Vector3.UNIT_Y)));
+ _direction.set((Vector3) capsule.readSavable("direction", new Vector3(Vector3.UNIT_Z)));
+ _frustumNear = capsule.readDouble("frustumNear", 1);
+ _frustumFar = capsule.readDouble("frustumFar", 2);
+ _frustumLeft = capsule.readDouble("frustumLeft", -0.5);
+ _frustumRight = capsule.readDouble("frustumRight", 0.5);
+ _frustumTop = capsule.readDouble("frustumTop", 0.5);
+ _frustumBottom = capsule.readDouble("frustumBottom", -0.5);
+ _coeffLeft = capsule.readDoubleArray("coeffLeft", new double[2]);
+ _coeffRight = capsule.readDoubleArray("coeffRight", new double[2]);
+ _coeffBottom = capsule.readDoubleArray("coeffBottom", new double[2]);
+ _coeffTop = capsule.readDoubleArray("coeffTop", new double[2]);
+ _planeQuantity = capsule.readInt("planeQuantity", 6);
+ _viewPortLeft = capsule.readDouble("viewPortLeft", 0);
+ _viewPortRight = capsule.readDouble("viewPortRight", 1);
+ _viewPortTop = capsule.readDouble("viewPortTop", 1);
+ _viewPortBottom = capsule.readDouble("viewPortBottom", 0);
+ _width = capsule.readInt("width", 0);
+ _height = capsule.readInt("height", 0);
+ _depthRangeNear = capsule.readDouble("depthRangeNear", 0.0);
+ _depthRangeFar = capsule.readDouble("depthRangeFar", 1.0);
+ }
+
+ public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
+ _location.set((Vector3) in.readObject());
+ _left.set((Vector3) in.readObject());
+ _up.set((Vector3) in.readObject());
+ _direction.set((Vector3) in.readObject());
+ _frustumNear = in.readDouble();
+ _frustumFar = in.readDouble();
+ _frustumLeft = in.readDouble();
+ _frustumRight = in.readDouble();
+ _frustumTop = in.readDouble();
+ _frustumBottom = in.readDouble();
+ _coeffLeft = (double[]) in.readObject();
+ _coeffRight = (double[]) in.readObject();
+ _coeffBottom = (double[]) in.readObject();
+ _coeffTop = (double[]) in.readObject();
+ _planeQuantity = in.readInt();
+ _viewPortLeft = in.readDouble();
+ _viewPortRight = in.readDouble();
+ _viewPortTop = in.readDouble();
+ _viewPortBottom = in.readDouble();
+ _width = in.readInt();
+ _height = in.readInt();
+ _depthRangeNear = in.readDouble();
+ _depthRangeFar = in.readDouble();
+ }
+
+ public void writeExternal(final ObjectOutput out) throws IOException {
+ out.writeObject(_location);
+ out.writeObject(_left);
+ out.writeObject(_up);
+ out.writeObject(_direction);
+ out.writeDouble(_frustumNear);
+ out.writeDouble(_frustumFar);
+ out.writeDouble(_frustumLeft);
+ out.writeDouble(_frustumRight);
+ out.writeDouble(_frustumTop);
+ out.writeDouble(_frustumBottom);
+ out.writeObject(_coeffLeft);
+ out.writeObject(_coeffRight);
+ out.writeObject(_coeffBottom);
+ out.writeObject(_coeffTop);
+ out.writeInt(_planeQuantity);
+ out.writeDouble(_viewPortLeft);
+ out.writeDouble(_viewPortRight);
+ out.writeDouble(_viewPortTop);
+ out.writeDouble(_viewPortBottom);
+ out.writeInt(_width);
+ out.writeInt(_height);
+ out.writeDouble(_depthRangeNear);
+ out.writeDouble(_depthRangeFar);
+ }
+
+ @Override
+ public String toString() {
+ return "com.ardor3d.renderer.Camera: loc - " + Arrays.toString(getLocation().toArray(null)) + " dir - "
+ + Arrays.toString(getDirection().toArray(null)) + " up - " + Arrays.toString(getUp().toArray(null))
+ + " left - " + Arrays.toString(getLeft().toArray(null));
+ }
+
+ public Class<? extends Camera> getClassTag() {
+ return getClass();
+ }
+
+ /**
+ * Convenience method for retrieving the Camera set on the current RenderContext. Similar to
+ * ContextManager.getCurrentContext().getCurrentCamera() but with null checks for current context.
+ *
+ * @return the Camera on the current RenderContext.
+ */
+ public static Camera getCurrentCamera() {
+ if (ContextManager.getCurrentContext() == null) {
+ return null;
+ }
+ return ContextManager.getCurrentContext().getCurrentCamera();
+ }
+
+ public boolean isFrameDirty() {
+ return _frameDirty;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java
new file mode 100644
index 0000000..f2ac794
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCapabilities.java
@@ -0,0 +1,662 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+public class ContextCapabilities {
+
+ protected boolean _supportsVBO = false;
+ protected boolean _supportsGL1_2 = false;
+ protected boolean _supportsMultisample = false;
+
+ protected boolean _supportsConstantColor = false;
+ protected boolean _supportsEq = false;
+ protected boolean _supportsSeparateEq = false;
+ protected boolean _supportsSeparateFunc = false;
+ protected boolean _supportsMinMax = false;
+ protected boolean _supportsSubtract = false;
+
+ protected boolean _supportsFogCoords = false;
+
+ protected boolean _supportsPointSprites = false;
+ protected boolean _supportsPointParameters = false;
+
+ protected boolean _supportsTextureLodBias = false;
+ protected float _maxTextureLodBias = 0f;
+
+ protected boolean _supportsFragmentProgram = false;
+ protected boolean _supportsVertexProgram = false;
+
+ protected boolean _glslSupported = false;
+ protected boolean _geometryShader4Supported = false;
+ protected boolean _geometryInstancingSupported = false;
+ protected boolean _tessellationShadersSupported = false;
+ protected int _maxGLSLVertexAttribs;
+
+ protected boolean _pbufferSupported = false;
+
+ protected boolean _fboSupported = false;
+ protected int _maxFBOColorAttachments = 1;
+ protected int _maxFBOSamples = 0;
+
+ protected int _maxUserClipPlanes = 0;
+
+ protected boolean _twoSidedStencilSupport = false;
+ protected boolean _stencilWrapSupport = false;
+
+ /** The total number of available auxiliary draw buffers. */
+ protected int _numAuxDrawBuffers = -1;
+
+ /** The total number of supported texture units. */
+ protected int _numTotalTexUnits = -1;
+
+ /** The number of texture units availible for fixed functionality */
+ protected int _numFixedTexUnits = -1;
+
+ /** The number of texture units availible to vertex shader */
+ protected int _numVertexTexUnits = -1;
+
+ /** The number of texture units availible to fragment shader */
+ protected int _numFragmentTexUnits = -1;
+
+ /** The number of texture coordinate sets available */
+ protected int _numFragmentTexCoordUnits = -1;
+
+ /** The max side of a texture supported. */
+ protected int _maxTextureSize = -1;
+
+ protected float _maxAnisotropic = -1.0f;
+
+ /** True if multitexturing is supported. */
+ protected boolean _supportsMultiTexture = false;
+
+ /** True if floating point textures are supported. */
+ protected boolean _supportsFloatTextures = false;
+
+ /** True if integer textures are supported. */
+ protected boolean _supportsIntegerTextures = false;
+
+ /** True if Red and RedGreen only textures are supported. */
+ protected boolean _supportsOneTwoComponentTextures = false;
+
+ /** True if combine dot3 is supported. */
+ protected boolean _supportsEnvDot3 = false;
+
+ /** True if combine dot3 is supported. */
+ protected boolean _supportsEnvCombine = false;
+
+ /** True if anisofiltering is supported. */
+ protected boolean _supportsAniso = false;
+
+ /** True if non pow 2 texture sizes are supported. */
+ protected boolean _supportsNonPowerTwo = false;
+
+ /** True if rectangular textures are supported (vs. only square textures) */
+ protected boolean _supportsRectangular = false;
+
+ /** True if S3TC compression is supported. */
+ protected boolean _supportsS3TCCompression = false;
+
+ /** True if LATC compression is supported. */
+ protected boolean _supportsLATCCompression = false;
+
+ /** True if generic (non-specific) texture compression is supported. */
+ protected boolean _supportsGenericCompression = false;
+
+ /** True if Texture3D is supported. */
+ protected boolean _supportsTexture3D = false;
+
+ /** True if TextureCubeMap is supported. */
+ protected boolean _supportsTextureCubeMap = false;
+
+ /** True if non-GLU mipmap generation (part of FBO) is supported. */
+ protected boolean _automaticMipMaps = false;
+
+ /** True if depth textures are supported */
+ protected boolean _supportsDepthTexture = false;
+
+ /** True if shadow mapping supported */
+ protected boolean _supportsShadow = false;
+
+ protected boolean _supportsMirroredRepeat;
+ protected boolean _supportsMirrorClamp;
+ protected boolean _supportsMirrorBorderClamp;
+ protected boolean _supportsMirrorEdgeClamp;
+ protected boolean _supportsBorderClamp;
+ protected boolean _supportsEdgeClamp;
+
+ protected String _displayVendor;
+ protected String _displayRenderer;
+ protected String _displayVersion;
+ protected String _shadingLanguageVersion;
+
+ public ContextCapabilities() {}
+
+ public ContextCapabilities(final ContextCapabilities source) {
+ _automaticMipMaps = source._automaticMipMaps;
+ _displayRenderer = source._displayRenderer;
+ _displayVendor = source._displayVendor;
+ _displayVersion = source._displayVersion;
+ _fboSupported = source._fboSupported;
+ _geometryShader4Supported = source._geometryShader4Supported;
+ _glslSupported = source._glslSupported;
+ _geometryInstancingSupported = source._geometryInstancingSupported;
+ _tessellationShadersSupported = source._tessellationShadersSupported;
+ _maxAnisotropic = source._maxAnisotropic;
+ _maxFBOColorAttachments = source._maxFBOColorAttachments;
+ _maxFBOSamples = source._maxFBOSamples;
+ _maxGLSLVertexAttribs = source._maxGLSLVertexAttribs;
+ _maxTextureLodBias = source._maxTextureLodBias;
+ _maxTextureSize = source._maxTextureSize;
+ _maxUserClipPlanes = source._maxUserClipPlanes;
+ _numAuxDrawBuffers = source._numAuxDrawBuffers;
+ _numFixedTexUnits = source._numFixedTexUnits;
+ _numFragmentTexCoordUnits = source._numFragmentTexCoordUnits;
+ _numFragmentTexUnits = source._numFragmentTexUnits;
+ _numTotalTexUnits = source._numTotalTexUnits;
+ _numVertexTexUnits = source._numVertexTexUnits;
+ _pbufferSupported = source._pbufferSupported;
+ _shadingLanguageVersion = source._shadingLanguageVersion;
+ _stencilWrapSupport = source._stencilWrapSupport;
+ _supportsAniso = source._supportsAniso;
+ _supportsBorderClamp = source._supportsBorderClamp;
+ _supportsConstantColor = source._supportsConstantColor;
+ _supportsDepthTexture = source._supportsDepthTexture;
+ _supportsEdgeClamp = source._supportsEdgeClamp;
+ _supportsEnvCombine = source._supportsEnvCombine;
+ _supportsEnvDot3 = source._supportsEnvDot3;
+ _supportsEq = source._supportsEq;
+ _supportsFogCoords = source._supportsFogCoords;
+ _supportsFragmentProgram = source._supportsFragmentProgram;
+ _supportsGenericCompression = source._supportsGenericCompression;
+ _supportsGL1_2 = source._supportsGL1_2;
+ _supportsLATCCompression = source._supportsLATCCompression;
+ _supportsMinMax = source._supportsMinMax;
+ _supportsMirrorBorderClamp = source._supportsMirrorBorderClamp;
+ _supportsMirrorClamp = source._supportsMirrorClamp;
+ _supportsMirrorEdgeClamp = source._supportsMirrorEdgeClamp;
+ _supportsMirroredRepeat = source._supportsMirroredRepeat;
+ _supportsMultisample = source._supportsMultisample;
+ _supportsMultiTexture = source._supportsMultiTexture;
+ _supportsNonPowerTwo = source._supportsNonPowerTwo;
+ _supportsPointParameters = source._supportsPointParameters;
+ _supportsPointSprites = source._supportsPointSprites;
+ _supportsRectangular = source._supportsRectangular;
+ _supportsS3TCCompression = source._supportsS3TCCompression;
+ _supportsSeparateEq = source._supportsSeparateEq;
+ _supportsSeparateFunc = source._supportsSeparateFunc;
+ _supportsShadow = source._supportsShadow;
+ _supportsSubtract = source._supportsSubtract;
+ _supportsTexture3D = source._supportsTexture3D;
+ _supportsTextureCubeMap = source._supportsTextureCubeMap;
+ _supportsTextureLodBias = source._supportsTextureLodBias;
+ _supportsVBO = source._supportsVBO;
+ _supportsVertexProgram = source._supportsVertexProgram;
+ _twoSidedStencilSupport = source._twoSidedStencilSupport;
+ _supportsFloatTextures = source._supportsFloatTextures;
+ _supportsIntegerTextures = source._supportsIntegerTextures;
+ _supportsOneTwoComponentTextures = source._supportsOneTwoComponentTextures;
+ }
+
+ /**
+ * @return true if we support Vertex Buffer Objects.
+ */
+ public boolean isVBOSupported() {
+ return _supportsVBO;
+ }
+
+ /**
+ * @return true if we support all of OpenGL 1.2
+ */
+ public boolean isOpenGL1_2Supported() {
+ return _supportsGL1_2;
+ }
+
+ /**
+ * @return true if we support multisampling (antialiasing)
+ */
+ public boolean isMultisampleSupported() {
+ return _supportsMultisample;
+ }
+
+ /**
+ * @return true if we support setting a constant color for use with *Constant* type BlendFunctions.
+ */
+ public boolean isConstantBlendColorSupported() {
+ return _supportsConstantColor;
+ }
+
+ /**
+ * @return true if we support setting rgb and alpha functions separately for source and destination.
+ */
+ public boolean isSeparateBlendFunctionsSupported() {
+ return _supportsSeparateFunc;
+ }
+
+ /**
+ * @return true if we support setting the blend equation
+ */
+ public boolean isBlendEquationSupported() {
+ return _supportsEq;
+ }
+
+ /**
+ * @return true if we support setting the blend equation for alpha and rgb separately
+ */
+ public boolean isSeparateBlendEquationsSupported() {
+ return _supportsSeparateEq;
+ }
+
+ /**
+ * @return true if we support using min and max blend equations
+ */
+ public boolean isMinMaxBlendEquationsSupported() {
+ return _supportsMinMax;
+ }
+
+ /**
+ * @return true if we support using subtract blend equations
+ */
+ public boolean isSubtractBlendEquationsSupported() {
+ return _supportsSubtract;
+ }
+
+ /**
+ * @return true if mesh based fog coords are supported
+ */
+ public boolean isFogCoordinatesSupported() {
+ return _supportsFogCoords;
+ }
+
+ /**
+ * @return true if point sprites are supported
+ */
+ public boolean isPointSpritesSupported() {
+ return _supportsPointSprites;
+ }
+
+ /**
+ * @return true if point parameters are supported
+ */
+ public boolean isPointParametersSupported() {
+ return _supportsPointParameters;
+ }
+
+ /**
+ * @return true if texture lod bias is supported
+ */
+ public boolean isTextureLodBiasSupported() {
+ return _supportsTextureLodBias;
+ }
+
+ /**
+ * @return the max amount of texture lod bias that this context supports.
+ */
+ public float getMaxLodBias() {
+ return _maxTextureLodBias;
+ }
+
+ /**
+ * @return <code>true</code> if the GLSL is supported and GL_ARB_tessellation_shader is supported by current
+ * graphics configuration
+ */
+ public boolean isTessellationShadersSupported() {
+ return _tessellationShadersSupported;
+ }
+
+ /**
+ * @return true if the ARB_shader_objects extension is supported by current graphics configuration.
+ */
+ public boolean isGLSLSupported() {
+ return _glslSupported;
+ }
+
+ /**
+ * @return true if the GLSL is supported and GL_EXT_draw_instanced is supported by the current graphics
+ * configuration configuration.
+ */
+ public boolean isGeometryInstancingSupported() {
+ return _geometryInstancingSupported;
+ }
+
+ /**
+ * @return true if the GLSL is supported and ARB_geometry_shader4 extension is supported by current graphics
+ * configuration.
+ */
+ public boolean isGeometryShader4Supported() {
+ return _geometryShader4Supported;
+ }
+
+ /**
+ * @return true if the ARB_shader_objects extension is supported by current graphics configuration.
+ */
+ public boolean isPbufferSupported() {
+ return _pbufferSupported;
+ }
+
+ /**
+ * @return true if the EXT_framebuffer_object extension is supported by current graphics configuration.
+ */
+ public boolean isFBOSupported() {
+ return _fboSupported;
+ }
+
+ /**
+ * @return true if we can handle doing separate stencil operations for front and back facing polys in a single pass.
+ */
+ public boolean isTwoSidedStencilSupported() {
+ return _twoSidedStencilSupport;
+ }
+
+ /**
+ * @return true if we can handle wrapping increment/decrement operations.
+ */
+ public boolean isStencilWrapSupported() {
+ return _stencilWrapSupport;
+ }
+
+ /**
+ * <code>getNumberOfAuxiliaryDrawBuffers</code> returns the total number of available auxiliary draw buffers this
+ * context supports.
+ *
+ * @return the number of available auxiliary draw buffers supported by the context.
+ */
+ public int getNumberOfAuxiliaryDrawBuffers() {
+ return _numAuxDrawBuffers;
+ }
+
+ /**
+ * <code>getTotalNumberOfUnits</code> returns the total number of texture units this context supports.
+ *
+ * @return the total number of texture units supported by the context.
+ */
+ public int getTotalNumberOfUnits() {
+ return _numTotalTexUnits;
+ }
+
+ /**
+ * <code>getNumberOfFixedUnits</code> returns the number of texture units this context supports, for use in the
+ * fixed pipeline.
+ *
+ * @return the number units.
+ */
+ public int getNumberOfFixedTextureUnits() {
+ return _numFixedTexUnits;
+ }
+
+ /**
+ * <code>getNumberOfVertexUnits</code> returns the number of texture units available to a vertex shader that this
+ * context supports.
+ *
+ * @return the number of units.
+ */
+ public int getNumberOfVertexUnits() {
+ return _numVertexTexUnits;
+ }
+
+ /**
+ * <code>getNumberOfFragmentUnits</code> returns the number of texture units available to a fragment shader that
+ * this context supports.
+ *
+ * @return the number of units.
+ */
+ public int getNumberOfFragmentTextureUnits() {
+ return _numFragmentTexUnits;
+ }
+
+ /**
+ * <code>getNumberOfFragmentTexCoordUnits</code> returns the number of texture coordinate sets available that this
+ * context supports.
+ *
+ * @return the number of units.
+ */
+ public int getNumberOfFragmentTexCoordUnits() {
+ return _numFragmentTexCoordUnits;
+ }
+
+ /**
+ * @return the max size of texture (in terms of # pixels wide) that this context supports.
+ */
+ public int getMaxTextureSize() {
+ return _maxTextureSize;
+ }
+
+ /**
+ * <code>getNumberOfTotalUnits</code> returns the number of texture units this context supports.
+ *
+ * @return the number of units.
+ */
+ public int getNumberOfTotalTextureUnits() {
+ return _numTotalTexUnits;
+ }
+
+ /**
+ * <code>getMaxFBOColorAttachments</code> returns the MAX_COLOR_ATTACHMENTS for FBOs that this context supports.
+ *
+ * @return the number of buffers.
+ */
+ public int getMaxFBOColorAttachments() {
+ return _maxFBOColorAttachments;
+ }
+
+ /**
+ * Returns the maximum anisotropic filter.
+ *
+ * @return The maximum anisotropic filter.
+ */
+ public float getMaxAnisotropic() {
+ return _maxAnisotropic;
+ }
+
+ /**
+ * @return true if multi-texturing is supported in fixed function
+ */
+ public boolean isMultitextureSupported() {
+ return _supportsMultiTexture;
+ }
+
+ /**
+ * @return true if floating point textures are supported by this context.
+ */
+ public boolean isFloatingPointTexturesSupported() {
+ return _supportsFloatTextures;
+ }
+
+ /**
+ * @return true if integer textures are supported by this context.
+ */
+ public boolean isIntegerTexturesSupported() {
+ return _supportsIntegerTextures;
+ }
+
+ /**
+ * @return true if one- and two-component textures are supported by this context.
+ */
+ public boolean isOneTwoComponentTexturesSupported() {
+ return _supportsOneTwoComponentTextures;
+ }
+
+ /**
+ * @return true we support dot3 environment texture settings
+ */
+ public boolean isEnvDot3TextureCombineSupported() {
+ return _supportsEnvDot3;
+ }
+
+ /**
+ * @return true we support combine environment texture settings
+ */
+ public boolean isEnvCombineSupported() {
+ return _supportsEnvCombine;
+ }
+
+ /**
+ * Returns if S3TC compression is available for textures.
+ *
+ * @return true if S3TC is available.
+ */
+ public boolean isS3TCSupported() {
+ return _supportsS3TCCompression;
+ }
+
+ /**
+ * Returns if LATC compression is available for textures.
+ *
+ * @return true if LATC is available.
+ */
+ public boolean isLATCSupported() {
+ return _supportsLATCCompression;
+ }
+
+ /**
+ * Returns if generic (non-specific) compression is available for textures.
+ *
+ * @return true if available.
+ */
+ public boolean isGenericTCSupported() {
+ return _supportsGenericCompression;
+ }
+
+ /**
+ * Returns if Texture3D is available for textures.
+ *
+ * @return true if Texture3D is available.
+ */
+ public boolean isTexture3DSupported() {
+ return _supportsTexture3D;
+ }
+
+ /**
+ * Returns if TextureCubeMap is available for textures.
+ *
+ * @return true if TextureCubeMap is available.
+ */
+ public boolean isTextureCubeMapSupported() {
+ return _supportsTextureCubeMap;
+ }
+
+ /**
+ * Returns if AutomaticMipmap generation is available for textures.
+ *
+ * @return true if AutomaticMipmap generation is available.
+ */
+ public boolean isAutomaticMipmapsSupported() {
+ return _automaticMipMaps;
+ }
+
+ /**
+ * @return if Anisotropic texture filtering is supported
+ */
+ public boolean isAnisoSupported() {
+ return _supportsAniso;
+ }
+
+ /**
+ * @return true if non pow 2 texture sizes are supported
+ */
+ public boolean isNonPowerOfTwoTextureSupported() {
+ return _supportsNonPowerTwo;
+ }
+
+ /**
+ * @return true if rectangular texture sizes are supported (width != height)
+ */
+ public boolean isRectangularTextureSupported() {
+ return _supportsRectangular;
+ }
+
+ public boolean isFragmentProgramSupported() {
+ return _supportsFragmentProgram;
+ }
+
+ public boolean isVertexProgramSupported() {
+ return _supportsVertexProgram;
+ }
+
+ public int getMaxGLSLVertexAttributes() {
+ return _maxGLSLVertexAttribs;
+ }
+
+ public boolean isDepthTextureSupported() {
+ return _supportsDepthTexture;
+ }
+
+ public boolean isARBShadowSupported() {
+ return _supportsShadow;
+ }
+
+ public boolean isTextureMirroredRepeatSupported() {
+ return _supportsMirroredRepeat;
+ }
+
+ public boolean isTextureMirrorClampSupported() {
+ return _supportsMirrorClamp;
+ }
+
+ public boolean isTextureMirrorEdgeClampSupported() {
+ return _supportsMirrorEdgeClamp;
+ }
+
+ public boolean isTextureMirrorBorderClampSupported() {
+ return _supportsMirrorBorderClamp;
+ }
+
+ public boolean isTextureBorderClampSupported() {
+ return _supportsBorderClamp;
+ }
+
+ public boolean isTextureEdgeClampSupported() {
+ return _supportsEdgeClamp;
+ }
+
+ public int getMaxFBOSamples() {
+ return _maxFBOSamples;
+ }
+
+ public int getMaxUserClipPlanes() {
+ return _maxUserClipPlanes;
+ }
+
+ /**
+ * Returns the vendor of the graphics adapter
+ *
+ * @return The vendor of the graphics adapter
+ */
+ public String getDisplayVendor() {
+ return _displayVendor;
+ }
+
+ /**
+ * Returns renderer details of the adapter
+ *
+ * @return The adapter details
+ */
+ public String getDisplayRenderer() {
+ return _displayRenderer;
+ }
+
+ /**
+ * Returns the version supported
+ *
+ * @return The version supported
+ */
+ public String getDisplayVersion() {
+ return _displayVersion;
+ }
+
+ /**
+ * Returns the supported shading language version. Needs OpenGL 2.0 support to query.
+ *
+ * @return The shading language version supported
+ */
+ public String getShadingLanguageVersion() {
+ return _shadingLanguageVersion;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java
new file mode 100644
index 0000000..839ac23
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextCleanListener.java
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+public interface ContextCleanListener {
+
+ void cleanForContext(RenderContext renderContext);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java
new file mode 100644
index 0000000..8f973d9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/ContextManager.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+public class ContextManager {
+
+ protected static RenderContext currentContext = null;
+
+ private static List<ContextCleanListener> _cleanListeners = Lists.newArrayList();
+
+ protected static final Map<Object, RenderContext> contextStore = new MapMaker().weakKeys().makeMap();
+
+ /**
+ * @return a RenderContext object representing the current OpenGL context.
+ */
+ public static RenderContext getCurrentContext() {
+ return currentContext;
+ }
+
+ public static RenderContext switchContext(final Object contextKey) {
+ currentContext = contextStore.get(contextKey);
+ if (currentContext == null) {
+ throw new IllegalArgumentException("contextKey not found in context store.");
+ }
+ return currentContext;
+ }
+
+ public static void removeContext(final Object contextKey) {
+ contextStore.remove(contextKey);
+ }
+
+ public static void addContext(final Object contextKey, final RenderContext context) {
+ contextStore.put(contextKey, context);
+ }
+
+ public static RenderContext getContextForKey(final Object key) {
+ return contextStore.get(key);
+ }
+
+ /**
+ * Find the first context we manage that uses the given shared opengl context.
+ *
+ * @param glref
+ * @return
+ */
+ public static RenderContext getContextForRef(final Object glref) {
+ if (glref == null) {
+ return null;
+ }
+ for (final RenderContext context : contextStore.values()) {
+ if (glref.equals(context.getGlContextRep())) {
+ return context;
+ }
+ }
+ return null;
+ }
+
+ public static void fireCleanContextEvent(final RenderContext renderContext) {
+ for (final ContextCleanListener listener : _cleanListeners) {
+ listener.cleanForContext(renderContext);
+ }
+ }
+
+ public static void addContextCleanListener(final ContextCleanListener listener) {
+ _cleanListeners.add(listener);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java
new file mode 100644
index 0000000..0dba3b5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/DrawBufferTarget.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+public enum DrawBufferTarget {
+
+ /**
+ * No color buffers
+ */
+ None,
+
+ /**
+ * The front left color buffer
+ */
+ FrontLeft,
+
+ /**
+ * The front right color buffer
+ */
+ FrontRight,
+
+ /**
+ * The back left color buffer
+ */
+ BackLeft,
+
+ /**
+ * The back right color buffer
+ */
+ BackRight,
+
+ /**
+ * The front left and front right (if exists) color buffers.
+ */
+ Front,
+
+ /**
+ * The back left and front right (if exists) color buffers.
+ */
+ Back,
+
+ /**
+ * The front left and back left (if exists) color buffers.
+ */
+ Left,
+
+ /**
+ * The front right and back right (if exists) color buffers.
+ */
+ Right,
+
+ /**
+ * All of FrontLeft, FrontRight, BackLeft, BackRight, if exists.
+ */
+ FrontAndBack,
+
+ /**
+ * Auxiliary color buffer 0. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()}
+ * >= 1.
+ */
+ Aux0,
+
+ /**
+ * Auxiliary color buffer 1. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()}
+ * >= 2.
+ */
+ Aux1,
+
+ /**
+ * Auxiliary color buffer 2. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()}
+ * >= 3.
+ */
+ Aux2,
+
+ /**
+ * Auxiliary color buffer 3. Should check first that {@link ContextCapabilities#getNumberOfAuxiliaryDrawBuffers()}
+ * >= 4.
+ */
+ Aux3;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java
new file mode 100644
index 0000000..b952d4a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/IndexMode.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+public enum IndexMode {
+ // TRIMESH
+ /**
+ * Every three vertices referenced by the indexbuffer will be considered a stand-alone triangle.
+ */
+ Triangles(true),
+ /**
+ * The first three vertices referenced by the indexbuffer create a triangle, from there, every additional vertex is
+ * paired with the two preceding vertices to make a new triangle.
+ */
+ TriangleStrip(true),
+ /**
+ * The first three vertices (V0, V1, V2) referenced by the indexbuffer create a triangle, from there, every
+ * additional vertex is paired with the preceding vertex and the initial vertex (V0) to make a new triangle.
+ */
+ TriangleFan(true),
+
+ // QUADMESH
+ /**
+ * Every four vertices referenced by the indexbuffer will be considered a stand-alone quad.
+ */
+ Quads(true),
+ /**
+ * The first four vertices referenced by the indexbuffer create a triangle, from there, every two additional
+ * vertices are paired with the two preceding vertices to make a new quad.
+ */
+ QuadStrip(true),
+
+ // LINE
+ /**
+ * Every two vertices referenced by the indexbuffer will be considered a stand-alone line segment.
+ */
+ Lines(false),
+ /**
+ * The first two vertices referenced by the indexbuffer create a line, from there, every additional vertex is paired
+ * with the preceding vertex to make a new, connected line.
+ */
+ LineStrip(false),
+ /**
+ * Identical to <i>LineStrip</i> except the final indexed vertex is then connected back to the initial vertex to
+ * form a loop.
+ */
+ LineLoop(false),
+
+ // POINT
+ /**
+ * Identical to <i>Connected</i> except the final indexed vertex is then connected back to the initial vertex to
+ * form a loop.
+ */
+ Points(false);
+
+ private final boolean _hasPolygons;
+
+ private IndexMode(final boolean hasPolygons) {
+ _hasPolygons = hasPolygons;
+ }
+
+ public boolean hasPolygons() {
+ return _hasPolygons;
+ }
+
+ public int getVertexCount() {
+ switch (this) {
+ case Triangles:
+ case TriangleStrip:
+ case TriangleFan:
+ return 3;
+ case Quads:
+ case QuadStrip:
+ return 4;
+ case Lines:
+ case LineStrip:
+ case LineLoop:
+ return 2;
+ case Points:
+ return 1;
+ }
+ throw new IllegalArgumentException("Unhandled type: " + this);
+ }
+
+ /**
+ * @param indexMode
+ * @param size
+ * @return the number of primitives you would have if you connected an array of points of the given size using the
+ * given index mode.
+ */
+ public static int getPrimitiveCount(final IndexMode indexMode, final int size) {
+ switch (indexMode) {
+ case Triangles:
+ return size / 3;
+ case TriangleFan:
+ case TriangleStrip:
+ return size - 2;
+ case Quads:
+ return size / 4;
+ case QuadStrip:
+ return size / 2 - 1;
+ case Lines:
+ return size / 2;
+ case LineStrip:
+ return size - 1;
+ case LineLoop:
+ return size;
+ case Points:
+ return size;
+ }
+
+ throw new IllegalArgumentException("unimplemented index mode: " + indexMode);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java
new file mode 100644
index 0000000..83f3392
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderContext.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.EmptyStackException;
+import java.util.EnumMap;
+import java.util.Stack;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.record.LineRecord;
+import com.ardor3d.renderer.state.record.RendererRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+
+/**
+ * Represents the state of an individual context in OpenGL.
+ */
+public class RenderContext {
+
+ /** List of states that override any set states on a spatial if not null. */
+ protected final EnumMap<RenderState.StateType, RenderState> _enforcedStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ protected final Stack<EnumMap<StateType, RenderState>> _enforcedBackStack = new Stack<EnumMap<StateType, RenderState>>();
+
+ protected final Stack<AbstractFBOTextureRenderer> _textureRenderers = new Stack<AbstractFBOTextureRenderer>();
+
+ /** RenderStates a Spatial contains during rendering. */
+ protected final EnumMap<RenderState.StateType, RenderState> _currentStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ protected final EnumMap<RenderState.StateType, StateRecord> _stateRecords = new EnumMap<RenderState.StateType, StateRecord>(
+ RenderState.StateType.class);
+
+ protected final LineRecord _lineRecord = new LineRecord();
+ protected final RendererRecord _rendererRecord = new RendererRecord();
+
+ /** Basically this object represents the sharable portion of a GL context... Textures, displayLists, etc. */
+ protected final Object _glContextRep;
+
+ protected final ContextCapabilities _capabilities;
+
+ /** The object tied to this RenderContext, such as the Canvas, etc. */
+ protected final Object _contextKey;
+
+ protected Camera _currentCamera = null;
+
+ public RenderContext(final Object key, final ContextCapabilities caps) {
+ this(key, caps, null);
+ }
+
+ public RenderContext(final Object key, final ContextCapabilities caps, final RenderContext shared) {
+ _contextKey = key;
+ _capabilities = caps;
+ setupRecords();
+ _glContextRep = (shared == null) ? new Object() : shared._glContextRep;
+ }
+
+ protected void setupRecords() {
+ for (final RenderState.StateType type : RenderState.StateType.values()) {
+ _stateRecords.put(type, RenderState.createState(type).createStateRecord());
+ }
+ }
+
+ public void invalidateStates() {
+ for (final RenderState.StateType type : RenderState.StateType.values()) {
+ _stateRecords.get(type).invalidate();
+ }
+ _lineRecord.invalidate();
+ _rendererRecord.invalidate();
+
+ clearCurrentStates();
+ }
+
+ public ContextCapabilities getCapabilities() {
+ return _capabilities;
+ }
+
+ public StateRecord getStateRecord(final RenderState.StateType type) {
+ return _stateRecords.get(type);
+ }
+
+ public LineRecord getLineRecord() {
+ return _lineRecord;
+ }
+
+ public RendererRecord getRendererRecord() {
+ return _rendererRecord;
+ }
+
+ /**
+ * Enforce a particular state. In other words, the given state will override any state of the same type set on a
+ * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where
+ * multiple sets of states need to be applied to a scenegraph drawn multiple times.
+ *
+ * @param state
+ * state to enforce
+ */
+ public void enforceState(final RenderState state) {
+ _enforcedStates.put(state.getType(), state);
+ }
+
+ /**
+ * Enforces the states referenced in the given EnumMap.
+ */
+ public void enforceStates(final EnumMap<StateType, RenderState> states) {
+ _enforcedStates.putAll(states);
+ }
+
+ /**
+ * Clears an enforced render state index by setting it to null. This allows object specific states to be used.
+ *
+ * @param type
+ * The type of RenderState to clear enforcement on.
+ */
+ public void clearEnforcedState(final RenderState.StateType type) {
+ _enforcedStates.remove(type);
+ }
+
+ /**
+ * sets all enforced states to null.
+ */
+ public void clearEnforcedStates() {
+ _enforcedStates.clear();
+ }
+
+ /**
+ * sets all current states to null, and therefore forces the use of the default states.
+ */
+ public void clearCurrentStates() {
+ _currentStates.clear();
+ }
+
+ /**
+ * @param type
+ * the state type to clear.
+ */
+ public void clearCurrentState(final RenderState.StateType type) {
+ _currentStates.remove(type);
+ }
+
+ public boolean hasEnforcedStates() {
+ return !_enforcedStates.isEmpty();
+ }
+
+ public RenderState getEnforcedState(final RenderState.StateType type) {
+ return _enforcedStates.get(type);
+ }
+
+ public RenderState getCurrentState(final RenderState.StateType type) {
+ return _currentStates.get(type);
+ }
+
+ public Object getContextKey() {
+ return _contextKey;
+ }
+
+ public void setCurrentState(final StateType type, final RenderState state) {
+ _currentStates.put(type, state);
+ }
+
+ public Camera getCurrentCamera() {
+ return _currentCamera;
+ }
+
+ public void setCurrentCamera(final Camera cam) {
+ _currentCamera = cam;
+ }
+
+ public Object getGlContextRep() {
+ return _glContextRep;
+ }
+
+ /**
+ * Saves the currently set states to a stack. Does not changes the currently enforced states.
+ */
+ public void pushEnforcedStates() {
+ _enforcedBackStack.push(new EnumMap<StateType, RenderState>(_enforcedStates));
+ }
+
+ /**
+ * Restores the enforced states from the stack. Any states enforced or cleared since the last push are reverted.
+ *
+ * @throws EmptyStackException
+ * if this method is called without first calling {@link #pushEnforcedStates()}
+ */
+ public void popEnforcedStates() {
+ _enforcedStates.clear();
+ _enforcedStates.putAll(_enforcedBackStack.pop());
+ }
+
+ public void pushFBOTextureRenderer(final AbstractFBOTextureRenderer top) {
+ if (_textureRenderers.size() > 0) {
+ _textureRenderers.peek().deactivate();
+ }
+ _textureRenderers.push(top);
+ top.activate();
+ }
+
+ public void popFBOTextureRenderer() {
+ AbstractFBOTextureRenderer top = _textureRenderers.pop();
+ top.deactivate();
+ if (_textureRenderers.size() > 0) {
+ top = _textureRenderers.peek();
+ top.activate();
+ }
+ }
+
+ /**
+ * Should only be called on a thread with an active context.
+ */
+ public void contextLost() {
+ // Notify any interested parties of the deletion.
+ ContextManager.fireCleanContextEvent(this);
+
+ // invalidate our render states
+ invalidateStates();
+
+ // force camera update
+ if (_currentCamera != null) {
+ _currentCamera.update();
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java
new file mode 100644
index 0000000..1cd4869
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RenderLogic.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.scenegraph.Renderable;
+
+public interface RenderLogic {
+ void apply(Renderable renderable);
+
+ void restore(Renderable renderable);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java
new file mode 100644
index 0000000..4d14424
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/Renderer.java
@@ -0,0 +1,591 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.Collection;
+import java.util.List;
+
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture1D;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.Texture3D;
+import com.ardor3d.image.TextureCubeMap;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyRectangle2;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.renderer.queue.RenderQueue;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.AbstractBufferData;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Renderable;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.NormalsMode;
+import com.ardor3d.util.Ardor3dException;
+
+/**
+ * <code>Renderer</code> defines an interface for classes that handle displaying of graphics data to a render context.
+ *
+ * All rendering state and tasks should be handled through this class.
+ */
+public interface Renderer {
+
+ /**
+ * No buffer.
+ */
+ public static int BUFFER_NONE = 0x00;
+ /**
+ * A buffer storing color information generally for display to the user.
+ */
+ public static int BUFFER_COLOR = 0x01;
+ /**
+ * A depth buffer allows sorting of pixels by depth or distance from the view port.
+ */
+ public static int BUFFER_DEPTH = 0x02;
+ /**
+ * Often a higher precision buffer used to gather rendering results over time.
+ */
+ public static int BUFFER_ACCUMULATION = 0x04;
+ /**
+ * A buffer used for masking out areas of the screen to prevent drawing.
+ */
+ public static int BUFFER_STENCIL = 0x08;
+
+ /**
+ * Convenience for those that find it too hard to do bitwise or. :)
+ */
+ public static int BUFFER_COLOR_AND_DEPTH = BUFFER_COLOR | BUFFER_DEPTH;
+
+ /**
+ * <code>setBackgroundColor</code> sets the color of window. This color will be shown for any pixel that is not set
+ * via typical rendering operations.
+ *
+ * @param c
+ * the color to set the background to.
+ */
+ void setBackgroundColor(ReadOnlyColorRGBA c);
+
+ /**
+ * <code>getBackgroundColor</code> retrieves the clear color of the current OpenGL context.
+ *
+ * @return the current clear color.
+ */
+ ReadOnlyColorRGBA getBackgroundColor();
+
+ /**
+ * <code>clearBuffers</code> clears the given buffers (specified as a bitwise &).
+ *
+ * @param buffers
+ * the buffers to clear.
+ * @see #BUFFER_COLOR
+ * @see #BUFFER_DEPTH
+ * @see #BUFFER_ACCUMULATION
+ * @see #BUFFER_STENCIL
+ */
+ void clearBuffers(int buffers);
+
+ /**
+ * <code>clearBuffers</code> clears the given buffers (specified as a bitwise &).
+ *
+ * @param buffers
+ * the buffers to clear.
+ * @param strict
+ * if true, we'll limit the clearing to just the viewport area specified by the current camera.
+ * @see #BUFFER_COLOR
+ * @see #BUFFER_DEPTH
+ * @see #BUFFER_ACCUMULATION
+ * @see #BUFFER_STENCIL
+ */
+ void clearBuffers(int buffers, boolean strict);
+
+ /**
+ * <code>flushFrame</code> handles rendering any items still remaining in the render buckets and optionally swaps
+ * the back buffer with the currently displayed buffer.
+ *
+ * @param doSwap
+ * if true, will ask the underlying implementation to blit the back buffer contents to the display
+ * buffer. Usually this will be true, unless you are in a windowing toolkit that handles doing this for
+ * you.
+ */
+ void flushFrame(boolean doSwap);
+
+ /**
+ *
+ * <code>setOrtho</code> sets the display system to be in orthographic mode. If the system has already been set to
+ * orthographic mode a <code>Ardor3dException</code> is thrown. The origin (0,0) is the bottom left of the screen.
+ *
+ */
+ void setOrtho();
+
+ /**
+ *
+ * <code>unsetOrhto</code> unsets the display system from orthographic mode back into regular projection mode. If
+ * the system is not in orthographic mode a <code>Ardor3dException</code> is thrown.
+ *
+ *
+ */
+ void unsetOrtho();
+
+ /**
+ * @return true if the renderer is currently in ortho mode.
+ */
+ boolean isInOrthoMode();
+
+ /**
+ * render queues - will first sort, then render, then finally clear the queue
+ */
+ void renderBuckets();
+
+ /**
+ * render queues
+ *
+ * @param doSort
+ * if true, the queues will be sorted prior to rendering.
+ * @param doClear
+ * if true, the queues will be emptied after rendering.
+ */
+ void renderBuckets(boolean doSort, boolean doClear);
+
+ /**
+ * clear the render queue
+ */
+ void clearQueue();
+
+ /**
+ * <code>grabScreenContents</code> reads a block of data as bytes from the current framebuffer. The format
+ * determines how many bytes per pixel are read and thus how big the buffer must be that you pass in.
+ *
+ * @param store
+ * a buffer to store contents in.
+ * @param format
+ * the format to read in bytes for.
+ * @param x
+ * - x starting point of block
+ * @param y
+ * - y starting point of block
+ * @param w
+ * - width of block
+ * @param h
+ * - height of block
+ */
+ void grabScreenContents(ByteBuffer store, ImageDataFormat format, int x, int y, int w, int h);
+
+ /**
+ * <code>draw</code> renders a scene. As it receives a base class of <code>Spatial</code> the renderer hands off
+ * management of the scene to spatial for it to determine when a <code>Geometry</code> leaf is reached.
+ *
+ * @param s
+ * the scene to render.
+ */
+ void draw(Spatial s);
+
+ /**
+ * <code>flush</code> tells the graphics hardware to send through all currently waiting commands in the buffer.
+ */
+ void flushGraphics();
+
+ /**
+ * <code>finish</code> is similar to flush, however it blocks until all waiting hardware graphics commands have been
+ * finished.
+ */
+ void finishGraphics();
+
+ /**
+ * Get the render queue associated with this Renderer.
+ *
+ * @return RenderQueue
+ */
+ RenderQueue getQueue();
+
+ /**
+ * Return true if this renderer is in the middle of processing its RenderQueue.
+ *
+ * @return boolean
+ */
+ boolean isProcessingQueue();
+
+ /**
+ * Check a given Spatial to see if it should be queued. return true if it was queued.
+ *
+ * @param s
+ * Spatial to check
+ * @return true if it was queued.
+ */
+ boolean checkAndAdd(Spatial s);
+
+ /**
+ * Attempts to delete the VBOs with the given id. Ignores null ids or ids < 1.
+ *
+ * @param ids
+ */
+ void deleteVBOs(Collection<Integer> ids);
+
+ /**
+ * Attempts to delete any VBOs associated with this buffer that are relevant to the current RenderContext.
+ *
+ * @param ids
+ */
+ void deleteVBOs(AbstractBufferData<?> buffer);
+
+ /**
+ * Unbind the current VBO elements.
+ */
+ void unbindVBO();
+
+ /**
+ * Update all or a portion of an existing one dimensional texture object.
+ *
+ * @param destination
+ * the texture to update. Should already have been sent to the card (have a valid texture id.)
+ * @param dstOffsetX
+ * the offset into the destination to start our update.
+ * @param dstWidth
+ * the width of the region to update.
+ * @param source
+ * the data to update from.
+ * @param srcOffsetX
+ * the optional offset into our source data.
+ */
+ void updateTexture1DSubImage(Texture1D destination, int dstOffsetX, int dstWidth, ByteBuffer source, int srcOffsetX);
+
+ /**
+ * Update all or a portion of an existing two dimensional texture object.
+ *
+ * @param destination
+ * the texture to update. Should already have been sent to the card (have a valid texture id.)
+ * @param dstOffsetX
+ * the x offset into the destination to start our update.
+ * @param dstOffsetY
+ * the y offset into the destination to start our update.
+ * @param dstWidth
+ * the width of the region to update.
+ * @param dstHeight
+ * the height of the region to update.
+ * @param source
+ * the data to update from.
+ * @param srcOffsetX
+ * the optional X offset into our source data.
+ * @param srcOffsetY
+ * the optional Y offset into our source data.
+ * @param srcTotalWidth
+ * the total width of our source data, so we can properly walk through it.
+ */
+ void updateTexture2DSubImage(Texture2D destination, int dstOffsetX, int dstOffsetY, int dstWidth, int dstHeight,
+ ByteBuffer source, int srcOffsetX, int srcOffsetY, int srcTotalWidth);
+
+ /**
+ * Update all or a portion of an existing one dimensional texture object.
+ *
+ * @param destination
+ * the texture to update. Should already have been sent to the card (have a valid texture id.)
+ * @param dstOffsetX
+ * the x offset into the destination to start our update.
+ * @param dstOffsetY
+ * the y offset into the destination to start our update.
+ * @param dstOffsetZ
+ * the z offset into the destination to start our update.
+ * @param dstWidth
+ * the width of the region to update.
+ * @param dstHeight
+ * the height of the region to update.
+ * @param dstDepth
+ * the depth of the region to update. eg. 1 == one slice
+ * @param source
+ * the data to update from.
+ * @param srcOffsetX
+ * the optional X offset into our source data.
+ * @param srcOffsetY
+ * the optional Y offset into our source data.
+ * @param srcOffsetZ
+ * the optional Z offset into our source data.
+ * @param srcTotalWidth
+ * the total width of our source data, so we can properly walk through it.
+ * @param srcTotalHeight
+ * the total height of our source data, so we can properly walk through it.
+ */
+ void updateTexture3DSubImage(Texture3D destination, int dstOffsetX, int dstOffsetY, int dstOffsetZ, int dstWidth,
+ int dstHeight, int dstDepth, ByteBuffer source, int srcOffsetX, int srcOffsetY, int srcOffsetZ,
+ int srcTotalWidth, int srcTotalHeight);
+
+ /**
+ * Update all or a portion of a single two dimensional face on an existing cubemap texture object.
+ *
+ * @param destination
+ * the texture to update. Should already have been sent to the card (have a valid texture id.)
+ * @param dstFace
+ * the face to update.
+ * @param dstOffsetX
+ * the x offset into the destination to start our update.
+ * @param dstOffsetY
+ * the y offset into the destination to start our update.
+ * @param dstWidth
+ * the width of the region to update.
+ * @param dstHeight
+ * the height of the region to update.
+ * @param source
+ * the data to update from.
+ * @param srcOffsetX
+ * the optional X offset into our source data.
+ * @param srcOffsetY
+ * the optional Y offset into our source data.
+ * @param srcTotalWidth
+ * the total width of our source data, so we can properly walk through it.
+ */
+ void updateTextureCubeMapSubImage(TextureCubeMap destination, TextureCubeMap.Face dstFace, int dstOffsetX,
+ int dstOffsetY, int dstWidth, int dstHeight, ByteBuffer source, int srcOffsetX, int srcOffsetY,
+ int srcTotalWidth);
+
+ /**
+ * Check the underlying rendering system (opengl, etc.) for exceptions.
+ *
+ * @throws Ardor3dException
+ * if an error is found.
+ */
+ void checkCardError() throws Ardor3dException;
+
+ /**
+ * <code>draw</code> renders the renderable to the back buffer.
+ *
+ * @param renderable
+ * the text object to be rendered.
+ */
+ void draw(Renderable renderable);
+
+ /**
+ * <code>doTransforms</code> sets the current transform.
+ *
+ * @param transform
+ * transform to apply.
+ */
+ boolean doTransforms(ReadOnlyTransform transform);
+
+ /**
+ * <code>undoTransforms</code> reverts the latest transform.
+ *
+ * @param transform
+ * transform to revert.
+ */
+ void undoTransforms(ReadOnlyTransform transform);
+
+ // TODO: Arrays
+ void setupVertexData(FloatBufferData vertexCoords);
+
+ void setupNormalData(FloatBufferData normalCoords);
+
+ void setupColorData(FloatBufferData colorCoords);
+
+ void setupFogData(FloatBufferData fogCoords);
+
+ void setupTextureData(List<FloatBufferData> textureCoords);
+
+ void drawElements(IndexBufferData<?> indices, int[] indexLengths, IndexMode[] indexModes, int primcount);
+
+ void drawArrays(FloatBufferData vertexBuffer, int[] indexLengths, IndexMode[] indexModes, int primcount);
+
+ void drawElementsVBO(IndexBufferData<?> indices, int[] indexLengths, IndexMode[] indexModes, int primcount);
+
+ void applyNormalsMode(NormalsMode normMode, ReadOnlyTransform worldTransform);
+
+ void applyDefaultColor(ReadOnlyColorRGBA defaultColor);
+
+ // TODO: VBO
+ void setupVertexDataVBO(FloatBufferData vertexCoords);
+
+ void setupNormalDataVBO(FloatBufferData normalCoords);
+
+ void setupColorDataVBO(FloatBufferData colorCoords);
+
+ void setupFogDataVBO(FloatBufferData fogCoords);
+
+ void setupTextureDataVBO(List<FloatBufferData> textureCoords);
+
+ void setupInterleavedDataVBO(FloatBufferData interleaved, FloatBufferData vertexCoords,
+ FloatBufferData normalCoords, FloatBufferData colorCoords, List<FloatBufferData> textureCoords);
+
+ // TODO: Display List
+ void renderDisplayList(int displayListID);
+
+ void setProjectionMatrix(FloatBuffer matrix);
+
+ /**
+ * Gets the current projection matrix in row major order
+ *
+ * @param store
+ * The buffer to store in. If null or remaining is < 16, a new FloatBuffer will be created and returned.
+ * @return
+ */
+ FloatBuffer getProjectionMatrix(FloatBuffer store);
+
+ void setModelViewMatrix(FloatBuffer matrix);
+
+ /**
+ * Gets the current modelview matrix in row major order
+ *
+ * @param store
+ * The buffer to store in. If null or remaining is < 16, a new FloatBuffer will be created and returned.
+ * @return
+ */
+ FloatBuffer getModelViewMatrix(FloatBuffer store);
+
+ void setViewport(int x, int y, int width, int height);
+
+ void setDepthRange(double depthRangeNear, double depthRangeFar);
+
+ /**
+ * Specify which color buffers are to be drawn into.
+ *
+ * @param target
+ */
+ void setDrawBuffer(DrawBufferTarget target);
+
+ /**
+ * This is a workaround until we make shared Record classes, or open up lower level opengl calls abstracted from
+ * lwjgl/jogl.
+ *
+ * @param lineWidth
+ * @param stippleFactor
+ * @param stipplePattern
+ * @param antialiased
+ */
+ void setupLineParameters(float lineWidth, int stippleFactor, short stipplePattern, boolean antialiased);
+
+ /**
+ * This is a workaround until we make shared Record classes, or open up lower level opengl calls abstracted from
+ * lwjgl/jogl.
+ *
+ * @param pointSize
+ * @param antialiased
+ * @param isSprite
+ * @param useDistanceAttenuation
+ * @param attenuationCoefficients
+ * @param minPointSize
+ * @param maxPointSize
+ */
+ void setupPointParameters(float pointSize, boolean antialiased, boolean isSprite, boolean useDistanceAttenuation,
+ FloatBuffer attenuationCoefficients, float minPointSize, float maxPointSize);
+
+ /**
+ * Apply the given state to the current RenderContext using this Renderer.
+ *
+ * @param type
+ * the state type
+ * @param state
+ * the render state. If null, the renderer's default is applied instead.
+ */
+ void applyState(StateType type, RenderState state);
+
+ /**
+ * Start a new display list. All further renderer commands that can be stored in a display list are part of this new
+ * list until {@link #endDisplayList()} is called.
+ *
+ * @return id of new display list
+ */
+ int startDisplayList();
+
+ /**
+ * Ends a display list. Will likely cause an OpenGL exception if a display list is not currently being generated.
+ */
+ void endDisplayList();
+
+ /**
+ * Loads a texture onto the card for the current OpenGL context.
+ *
+ * @param texture
+ * the texture obejct to load.
+ * @param unit
+ * the texture unit to load into
+ */
+ void loadTexture(Texture texture, int unit);
+
+ /**
+ * Explicitly remove this Texture from the graphics card. Note, the texture is only removed for the current context.
+ * If the texture is used in other contexts, those uses are not touched. If the texture is not used in this context,
+ * this is a no-op.
+ *
+ * @param texture
+ * the Texture object to remove.
+ */
+ void deleteTexture(Texture texture);
+
+ /**
+ * Removes the given texture ids from the current OpenGL context.
+ *
+ * @param ids
+ * a list/set of ids to remove.
+ */
+ void deleteTextureIds(Collection<Integer> ids);
+
+ /**
+ * Removes the given display lists by id from the current OpenGL context.
+ *
+ * @param ids
+ * a list/set of ids to remove.
+ */
+ void deleteDisplayLists(Collection<Integer> collection);
+
+ /**
+ * Add the given rectangle to the clip stack, clipping the rendering area by the given rectangle intersected with
+ * any existing scissor entries. Enable clipping if this is the first rectangle in the stack.
+ *
+ * @param rectangle
+ */
+ void pushClip(ReadOnlyRectangle2 rectangle);
+
+ /**
+ * Push a clip onto the stack indicating that future clips should not intersect with clips prior to this one.
+ * Basically this allows you to ignore prior clips for nested drawing. Make sure you pop this using
+ * {@link #popClip()}.
+ */
+ void pushEmptyClip();
+
+ /**
+ * Pop the most recent rectangle from the stack and adjust the rendering area accordingly.
+ */
+ void popClip();
+
+ /**
+ * Clear all rectangles from the clip stack and disable clipping.
+ */
+ void clearClips();
+
+ /**
+ * @param enabled
+ * toggle clipping without effecting the current clip stack.
+ */
+ void setClipTestEnabled(boolean enabled);
+
+ /**
+ * @return true if the renderer believes clipping is enabled
+ */
+ boolean isClipTestEnabled();
+
+ /**
+ * @param type
+ * the state type to grab
+ * @return the appropriate render state for the current context for the current type. This is the enforced state if
+ * one exists or the given current state if not null. Otherwise, the Renderer's default state is returned.
+ */
+ RenderState getProperRenderState(StateType type, RenderState current);
+
+ /**
+ * Set rendering logic that will be called during drawing of renderables
+ *
+ * @param logic
+ * logic to use in rendering. call with null to reset rendering.
+ * @see RenderLogic
+ */
+ void setRenderLogic(RenderLogic logic);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java
new file mode 100644
index 0000000..665317d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/RendererCallable.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.concurrent.Callable;
+
+public abstract class RendererCallable<V> implements Callable<V> {
+
+ private Renderer _renderer;
+
+ public void setRenderer(final Renderer renderer) {
+ _renderer = renderer;
+ }
+
+ public Renderer getRenderer() {
+ return _renderer;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java
new file mode 100644
index 0000000..cf3eb10
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/StereoCamera.java
@@ -0,0 +1,194 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+
+/**
+ * Extension of Camera useful for tracking and updating a Stereo view camera. See
+ * http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/stereorender/ for a useful discussion on stereo viewing in
+ * OpenGL.
+ */
+public class StereoCamera extends Camera {
+
+ private boolean _sideBySideMode = false;
+
+ private final Camera _leftCamera;
+ private final Camera _rightCamera;
+
+ private double _focalDistance = 100;
+ private double _eyeSeparation = _focalDistance / 30;
+ private double _aperture = 45 * MathUtils.DEG_TO_RAD;
+
+ public StereoCamera() {
+ this(100, 100);
+ }
+
+ /**
+ *
+ * @param width
+ * @param height
+ * @param sideBySideMode
+ */
+ public StereoCamera(final int width, final int height) {
+ super(width, height);
+ _leftCamera = new Camera(width, height);
+ _rightCamera = new Camera(width, height);
+ }
+
+ public StereoCamera(final Camera camera) {
+ super(camera);
+ _leftCamera = new Camera(camera);
+ _rightCamera = new Camera(camera);
+ }
+
+ @Override
+ public void resize(final int width, final int height) {
+ super.resize(width, height);
+ _leftCamera.resize(width, height);
+ _rightCamera.resize(width, height);
+ }
+
+ /**
+ * @return the sideBySideMode
+ * @see #setSideBySideMode(boolean)
+ */
+ public boolean isSideBySideMode() {
+ return _sideBySideMode;
+ }
+
+ /**
+ * @param sideBySideMode
+ * If true, left camera will be set up to the left half of the render context and right camera to the
+ * right half. If false, the views are set up to be the full size of the render context.
+ */
+ public void setSideBySideMode(final boolean sideBySideMode) {
+ _sideBySideMode = sideBySideMode;
+ setupLeftRightCameras();
+ }
+
+ public void setupLeftRightCameras() {
+ // Set viewport:
+ // XXX: Could maybe make use of our current viewport?
+ if (_sideBySideMode) {
+ _leftCamera.setViewPort(0, .5, 0, 1);
+ _rightCamera.setViewPort(.5, 1, 0, 1);
+ } else {
+ _leftCamera.setViewPort(0, 1, 0, 1);
+ _rightCamera.setViewPort(0, 1, 0, 1);
+ }
+
+ // Set frustum:
+ final double aspectRatio = (getWidth() / (double) getHeight() / (_sideBySideMode ? 2.0 : 1.0));
+ final double halfView = getFrustumNear() * MathUtils.tan(_aperture / 2);
+
+ final double top = halfView;
+ final double bottom = -halfView;
+ final double horizontalShift = 0.5 * _eyeSeparation * getFrustumNear() / _focalDistance;
+
+ // LEFT:
+ {
+ final double left = -aspectRatio * halfView + horizontalShift;
+ final double right = aspectRatio * halfView + horizontalShift;
+
+ _leftCamera.setFrustum(getFrustumNear(), getFrustumFar(), left, right, top, bottom);
+ }
+
+ // RIGHT:
+ {
+ final double left = -aspectRatio * halfView - horizontalShift;
+ final double right = aspectRatio * halfView - horizontalShift;
+
+ _rightCamera.setFrustum(getFrustumNear(), getFrustumFar(), left, right, top, bottom);
+ }
+ }
+
+ public void updateLeftRightCameraFrames() {
+ // update camera frame
+ final Vector3 rightDir = Vector3.fetchTempInstance();
+ final Vector3 work = Vector3.fetchTempInstance();
+ rightDir.set(getDirection()).crossLocal(getUp()).multiplyLocal(_eyeSeparation / 2.0);
+ _leftCamera.setFrame(getLocation().subtract(rightDir, work), getLeft(), getUp(), getDirection());
+ _rightCamera.setFrame(getLocation().add(rightDir, work), getLeft(), getUp(), getDirection());
+ Vector3.releaseTempInstance(work);
+ Vector3.releaseTempInstance(rightDir);
+ }
+
+ public void switchToLeftCamera(final Renderer r) {
+ _leftCamera.update();
+ _leftCamera.apply(r);
+ }
+
+ public void switchToRightCamera(final Renderer r) {
+ _rightCamera.update();
+ _rightCamera.apply(r);
+ }
+
+ /**
+ * @return the leftCamera
+ */
+ public Camera getLeftCamera() {
+ return _leftCamera;
+ }
+
+ /**
+ * @return the rightCamera
+ */
+ public Camera getRightCamera() {
+ return _rightCamera;
+ }
+
+ /**
+ * @return the focalDistance
+ */
+ public double getFocalDistance() {
+ return _focalDistance;
+ }
+
+ /**
+ * @param focalDistance
+ * the focalDistance to set
+ */
+ public void setFocalDistance(final double focalDistance) {
+ _focalDistance = focalDistance;
+ }
+
+ /**
+ * @return the eyeSeparation
+ */
+ public double getEyeSeparation() {
+ return _eyeSeparation;
+ }
+
+ /**
+ * @param eyeSeparation
+ * the eyeSeparation to set
+ */
+ public void setEyeSeparation(final double eyeSeparation) {
+ _eyeSeparation = eyeSeparation;
+ }
+
+ /**
+ * @return the aperture
+ */
+ public double getAperture() {
+ return _aperture;
+ }
+
+ /**
+ * @param radians
+ * the horizontal field of view, in radians
+ */
+ public void setAperture(final double radians) {
+ _aperture = radians;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java
new file mode 100644
index 0000000..7972f98
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRenderer.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.framework.Scene;
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * <code>TextureRenderer</code> defines an abstract class that handles rendering a scene to a buffer and copying it to a
+ * texture. Creation of this object is usually handled via a TextureRendererFactory. Note that currently, only Texture2D
+ * is supported by Pbuffer versions of this class. Texture2D and TextureCubeMap are supported in FBO mode.
+ */
+public interface TextureRenderer {
+
+ /**
+ * <code>getCamera</code> retrieves the camera this renderer is using.
+ *
+ * @return the camera this renderer is using.
+ */
+ Camera getCamera();
+
+ /**
+ *
+ * @param scene
+ * the scene to render.
+ * @param tex
+ * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its
+ * currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(Scene scene, Texture tex, int clear);
+
+ /**
+ * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render
+ * time.
+ *
+ * @param scene
+ * the scene to render.
+ * @param texs
+ * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter,
+ * its currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(Scene scene, List<Texture> texs, int clear);
+
+ /**
+ *
+ * @param spat
+ * the scene to render.
+ * @param tex
+ * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its
+ * currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(Spatial spat, Texture tex, int clear);
+
+ /**
+ * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render
+ * time.
+ *
+ * @param spat
+ * the scene to render.
+ * @param texs
+ * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter,
+ * its currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(Spatial spat, List<Texture> texs, int clear);
+
+ /**
+ * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render
+ * time.
+ *
+ * @param spats
+ * an array of Spatials to render.
+ * @param tex
+ * the Texture to render to. This should be a Texture2D or TextureCubeMap. If the latter, its
+ * currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(List<? extends Spatial> spats, Texture tex, int clear);
+
+ /**
+ * NOTE: If more than one texture is given, copy-texture is used regardless of card capabilities to decrease render
+ * time.
+ *
+ * @param spats
+ * an array of Spatials to render.
+ * @param texs
+ * a list of Textures to render to. These should be of type Texture2D or TextureCubeMap. If the latter,
+ * its currentRTTFace will determine which cube face is drawn to.
+ * @param clear
+ * which buffers to clear before rendering, if any.
+ * @see Renderer#BUFFER_COLOR et al
+ */
+ void render(List<? extends Spatial> spats, List<Texture> texs, int clear);
+
+ /**
+ * <code>setBackgroundColor</code> sets the color of window. This color will be shown for any pixel that is not set
+ * via typical rendering operations.
+ *
+ * @param c
+ * the color to set the background to.
+ */
+ void setBackgroundColor(ReadOnlyColorRGBA c);
+
+ /**
+ * <code>getBackgroundColor</code> retrieves the color used for the window background.
+ *
+ * @return the background color that is currently set to the background.
+ */
+ ReadOnlyColorRGBA getBackgroundColor();
+
+ /**
+ * <code>setupTexture</code> initializes a Texture object for use with TextureRenderer. Generates a valid gl texture
+ * id for this texture and sets up data storage for it. The texture will be equal to the texture renderer's size.
+ *
+ * Note that the texture renderer's size is not necessarily what is specified in the constructor.
+ *
+ * @param tex
+ * The texture to setup for use in Texture Rendering. This should be of type Texture2D or TextureCubeMap.
+ */
+ void setupTexture(Texture tex);
+
+ /**
+ * <code>copyToTexture</code> copies the current frame buffer contents to the given Texture. What is copied is based
+ * on the rttFormat of the texture object when it was setup. Note that the contents are copied with no scaling
+ * applied, so the texture must be big enough such that xoffset + width <= texture's width and yoffset + height <=
+ * texture's height.
+ *
+ * @param tex
+ * The Texture to copy into. This should be a Texture2D or TextureCubeMap. If the latter, its
+ * currentRTTFace will determine which cube face is drawn to.
+ * @param x
+ * the x offset into the framebuffer
+ * @param y
+ * the y offset into the framebuffer
+ * @param width
+ * the width of the rectangle to read from the framebuffer and copy 1:1 to the texture
+ * @param height
+ * the width of the rectangle to read from the framebuffer and copy 1:1 to the texture
+ * @param xoffset
+ * the x offset into the texture to draw at
+ * @param yoffset
+ * the y offset into the texture to draw at
+ */
+ void copyToTexture(Texture tex, int x, int y, int width, int height, int xoffset, int yoffset);
+
+ /**
+ * Any wrapping up and cleaning up of TextureRenderer information is performed here.
+ */
+ void cleanup();
+
+ /**
+ * Set up this textureRenderer for use with multiple targets. If you are going to use this texture renderer to
+ * render to more than one texture, call this with true.
+ *
+ * @param multi
+ * true if you plan to use this texture renderer to render different content to more than one texture.
+ */
+ void setMultipleTargets(boolean multi);
+
+ int getWidth();
+
+ int getHeight();
+
+ /**
+ * Enforce a particular state whenever this texture renderer is used. In other words, the given state will override
+ * any state of the same type set on a scene object rendered with this texture renderer.
+ *
+ * @param state
+ * state to enforce
+ */
+ void enforceState(RenderState state);
+
+ void enforceStates(EnumMap<StateType, RenderState> states);
+
+ /**
+ * @param type
+ * state type to clear
+ */
+ void clearEnforcedState(StateType type);
+
+ /**
+ * Clear all enforced states on this texture renderer.
+ */
+ void clearEnforcedStates();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java
new file mode 100644
index 0000000..f6f8a8c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererFactory.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.framework.DisplaySettings;
+
+public enum TextureRendererFactory {
+
+ INSTANCE;
+
+ private TextureRendererProvider _provider = null;
+
+ public void setProvider(final TextureRendererProvider provider) {
+ _provider = provider;
+ }
+
+ /**
+ * Create a TextureRenderer of the given width and height. All other params are considered undefined. We will
+ * attempt to make an FBO based renderer if supported, or a Pbuffer based renderer if supported, or null if neither
+ * are supported.
+ *
+ * @param width
+ * the width of our off screen rendering target
+ * @param height
+ * the height of our off screen rendering target
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final int width, final int height, final Renderer renderer,
+ final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(width, height, renderer, caps);
+ }
+
+ /**
+ * Create a TextureRenderer using params that are meaningful regardless of whether a Pbuffer or FBO renderer are
+ * used. We will attempt to make an FBO based renderer if supported, or a Pbuffer based renderer if supported, or
+ * null if neither are supported.
+ *
+ * @param width
+ * the width of our off screen rendering target
+ * @param height
+ * the height of our off screen rendering target
+ * @param depthBits
+ * the desired depth buffer size of our off screen rendering target
+ * @param samples
+ * the number of samples for our off screen rendering target
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final int width, final int height, final int depthBits,
+ final int samples, final Renderer renderer, final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(width, height, depthBits, samples, renderer, caps);
+ }
+
+ /**
+ * Create a TextureRenderer using as many of the given DisplaySettings that are meaningful for the chosen type.
+ * Unless forcePbuffer is true, we will attempt to make an FBO based renderer if supported, or a Pbuffer based
+ * renderer if supported, or null if neither are supported.
+ *
+ * @param settings
+ * a complete set of possible display settings to use. Some will only be valid if Pbuffer is used.
+ * @param forcePbuffer
+ * if true, we will return a pbuffer or null if pbuffers are not supported.
+ * @param renderer
+ * the renderer to use when rendering to this off screen target.
+ * @param caps
+ * the context capabilities, used for testing.
+ * @return a TextureRenderer
+ * @throws IllegalStateException
+ * if provider has not been set prior to calling this method.
+ */
+ public TextureRenderer createTextureRenderer(final DisplaySettings settings, final boolean forcePbuffer,
+ final Renderer renderer, final ContextCapabilities caps) {
+ if (_provider == null) {
+ throw new IllegalStateException("No provider has been set on TextureRendererFactory.");
+ }
+ return _provider.createTextureRenderer(settings, forcePbuffer, renderer, caps);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java
new file mode 100644
index 0000000..6416824
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/TextureRendererProvider.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer;
+
+import com.ardor3d.framework.DisplaySettings;
+
+public interface TextureRendererProvider {
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(int, int, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(int width, int height, Renderer renderer, ContextCapabilities caps);
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(int, int, int, int, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(int width, int height, int depthBits, int samples, Renderer renderer,
+ ContextCapabilities caps);
+
+ /**
+ * @see TextureRendererFactory#createTextureRenderer(DisplaySettings, boolean, Renderer, ContextCapabilities)
+ */
+ TextureRenderer createTextureRenderer(DisplaySettings settings, boolean forcePbuffer, Renderer renderer,
+ ContextCapabilities caps);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java
new file mode 100644
index 0000000..8299c14
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectManager.java
@@ -0,0 +1,167 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class EffectManager {
+
+ protected final DisplaySettings _canvasSettings;
+ protected final List<RenderEffect> _effects = Lists.newArrayList();
+ protected final Map<String, RenderTarget> _renderTargetMap = Maps.newHashMap();
+ protected Renderer _currentRenderer = null;
+ protected RenderTarget _currentRenderTarget = null;
+ protected Camera _fsqCamera, _sceneCamera;
+ protected Mesh _fsq;
+ protected RenderTarget _inOutTargetA, _inOutTargetB;
+ protected boolean _swapTargets = false;
+ protected final TextureStoreFormat _outputFormat;
+
+ public EffectManager(final DisplaySettings settings, final TextureStoreFormat outputformat) {
+ _canvasSettings = settings;
+ _fsqCamera = new Camera(settings.getWidth(), settings.getHeight());
+ _fsqCamera.setFrustum(-1, 1, -1, 1, 1, -1);
+ _fsqCamera.setProjectionMode(ProjectionMode.Parallel);
+ _fsqCamera.setAxes(Vector3.NEG_UNIT_X, Vector3.UNIT_Y, Vector3.NEG_UNIT_Z);
+
+ _outputFormat = outputformat;
+ setupDefaultTargets(outputformat);
+ }
+
+ public void setupEffects() {
+ for (final RenderEffect effect : _effects) {
+ effect.prepare(this);
+ }
+ }
+
+ protected void setupDefaultTargets(final TextureStoreFormat outputformat) {
+ _renderTargetMap.put("*Framebuffer", new RenderTarget_Framebuffer());
+ _inOutTargetA = new RenderTarget_Texture2D(_canvasSettings.getWidth(), _canvasSettings.getHeight(),
+ outputformat);
+ _inOutTargetB = new RenderTarget_Texture2D(_canvasSettings.getWidth(), _canvasSettings.getHeight(),
+ outputformat);
+ }
+
+ public void renderEffects(final Renderer renderer) {
+ _currentRenderer = renderer;
+ for (final RenderEffect effect : _effects) {
+ if (effect.isEnabled()) {
+ effect.render(this);
+ _swapTargets = !_swapTargets;
+ }
+ }
+ }
+
+ public DisplaySettings getCanvasSettings() {
+ return _canvasSettings;
+ }
+
+ public List<RenderEffect> getEffects() {
+ return _effects;
+ }
+
+ public Map<String, RenderTarget> getRenderTargetMap() {
+ return _renderTargetMap;
+ }
+
+ public Renderer getCurrentRenderer() {
+ return _currentRenderer;
+ }
+
+ public RenderTarget getCurrentRenderTarget() {
+ return _currentRenderTarget;
+ }
+
+ public void setCurrentRenderTarget(final RenderTarget target) {
+ _currentRenderTarget = target;
+ }
+
+ public void addEffect(final RenderEffect effect) {
+ _effects.add(effect);
+ }
+
+ public RenderTarget getRenderTarget(final String name) {
+ // Check for reserved words
+ if ("*Previous".equals(name)) {
+ return _swapTargets ? _inOutTargetB : _inOutTargetA;
+ } else if ("*Next".equals(name)) {
+ return _swapTargets ? _inOutTargetA : _inOutTargetB;
+ } else {
+ return _renderTargetMap.get(name);
+ }
+ }
+
+ public boolean setCurrentRenderTarget(final String name) {
+ final RenderTarget target = getRenderTarget(name);
+ if (target != null) {
+ _currentRenderTarget = target;
+ return true;
+ }
+ return false;
+ }
+
+ public void renderFullScreenQuad(final EnumMap<StateType, RenderState> enforcedStates) {
+ // render our -1,1 quad
+ _currentRenderTarget.render(this, _fsqCamera, getFullScreenQuad(), enforcedStates);
+ }
+
+ protected Mesh getFullScreenQuad() {
+ if (_fsq != null) {
+ return _fsq;
+ }
+
+ _fsq = new Mesh("fsq");
+ _fsq.getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(-1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1));
+ _fsq.getMeshData().setTextureBuffer(BufferUtils.createFloatBuffer(0, 0, 1, 0, 1, 1, 0, 1), 0);
+ _fsq.getMeshData().setIndexBuffer(BufferUtils.createIntBuffer(0, 1, 3, 1, 2, 3));
+
+ _fsq.getSceneHints().setCullHint(CullHint.Never);
+ _fsq.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ final ZBufferState zState = new ZBufferState();
+ zState.setEnabled(false);
+ _fsq.setRenderState(zState);
+
+ _fsq.updateGeometricState(0);
+
+ return _fsq;
+ }
+
+ public TextureStoreFormat getOutputFormat() {
+ return _outputFormat;
+ }
+
+ public Camera getSceneCamera() {
+ return _sceneCamera;
+ }
+
+ public void setSceneCamera(final Camera sceneCamera) {
+ _sceneCamera = sceneCamera;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java
new file mode 100644
index 0000000..4971d58
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+/**
+ * A specific instruction in a RenderEffect.
+ */
+public interface EffectStep {
+
+ /**
+ * Apply this step.
+ *
+ * @param manager
+ */
+ public void apply(final EffectManager manager);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java
new file mode 100644
index 0000000..777daee
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderScreenOverlay.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.google.common.collect.Maps;
+
+public class EffectStep_RenderScreenOverlay implements EffectStep {
+
+ private final EnumMap<StateType, RenderState> _states = Maps.newEnumMap(StateType.class);
+ private final TextureState _texState = new TextureState();
+ private final Map<String, Integer> _targetMap = Maps.newHashMap();
+
+ public EffectStep_RenderScreenOverlay() {
+ _states.put(StateType.Texture, _texState);
+ }
+
+ @Override
+ public void apply(final EffectManager manager) {
+ // prepare our texture state
+ for (final String key : _targetMap.keySet()) {
+ final RenderTarget target = manager.getRenderTarget(key);
+ final Integer unit = _targetMap.get(key);
+ _texState.setTexture(target.getTexture(), unit.intValue());
+ }
+
+ // render a quad to the screen using our states.
+ manager.renderFullScreenQuad(_states);
+ }
+
+ public TextureState getTextureState() {
+ return _texState;
+ }
+
+ public EnumMap<StateType, RenderState> getEnforcedStates() {
+ return _states;
+ }
+
+ public Map<String, Integer> getTargetMap() {
+ return _targetMap;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java
new file mode 100644
index 0000000..ee03266
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_RenderSpatials.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+public class EffectStep_RenderSpatials implements EffectStep {
+ private final EnumMap<StateType, RenderState> _states = Maps.newEnumMap(StateType.class);
+ private final List<Spatial> _spatials = Lists.newArrayList();
+ private final Camera _trackedCamera;
+
+ public EffectStep_RenderSpatials(final Camera trackedCamera) {
+ _trackedCamera = trackedCamera;
+ }
+
+ @Override
+ public void apply(final EffectManager manager) {
+ manager.getCurrentRenderTarget().render(manager,
+ _trackedCamera != null ? _trackedCamera : manager.getSceneCamera(), _spatials, _states);
+ }
+
+ public List<Spatial> getSpatials() {
+ return _spatials;
+ }
+
+ public EnumMap<StateType, RenderState> getEnforcedStates() {
+ return _states;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java
new file mode 100644
index 0000000..8b36d29
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/EffectStep_SetRenderTarget.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+public class EffectStep_SetRenderTarget implements EffectStep {
+
+ private final String _target;
+
+ public EffectStep_SetRenderTarget(final String target) {
+ _target = target;
+ }
+
+ @Override
+ public void apply(final EffectManager manager) {
+ manager.setCurrentRenderTarget(_target);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java
new file mode 100644
index 0000000..76a9338
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/FrameBufferOutputEffect.java
@@ -0,0 +1,31 @@
+
+package com.ardor3d.renderer.effect;
+
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+
+public class FrameBufferOutputEffect extends RenderEffect {
+
+ private BlendState _blend = null;
+
+ @Override
+ public void prepare(final EffectManager manager) {
+ _steps.clear();
+ _steps.add(new EffectStep_SetRenderTarget("*Framebuffer"));
+
+ final EffectStep_RenderScreenOverlay drawStep = new EffectStep_RenderScreenOverlay();
+ drawStep.getTargetMap().put("*Previous", 0);
+ drawStep.getEnforcedStates().put(StateType.Blend, _blend);
+ _steps.add(drawStep);
+
+ super.prepare(manager);
+ }
+
+ public BlendState getBlend() {
+ return _blend;
+ }
+
+ public void setBlend(final BlendState blend) {
+ _blend = blend;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java
new file mode 100644
index 0000000..6fda5a9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderEffect.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+/**
+ * A RenderEffect object represents a complete set of instructions necessary for applying a specific effect to our
+ * render output. Each effect is comprised of a set of 1 or more steps (EffectStep).
+ */
+public abstract class RenderEffect {
+
+ /** A list of logical steps that comprise our effect. */
+ protected final List<EffectStep> _steps = Lists.newArrayList();
+
+ /** Is this render effect active? */
+ protected boolean _enabled = true;
+
+ /**
+ * Do any setup necessary for our effect prior. This should be called only once, or on changes to the effect chain.
+ *
+ * @param manager
+ */
+ public void prepare(final EffectManager manager) {}
+
+ /**
+ * Render this effect.
+ *
+ * @param manager
+ */
+ public void render(final EffectManager manager) {
+ for (final EffectStep step : _steps) {
+ step.apply(manager);
+ }
+ }
+
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ public void setEnabled(final boolean enabled) {
+ _enabled = enabled;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java
new file mode 100644
index 0000000..7a4a55f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+public interface RenderTarget {
+
+ void render(final EffectManager effectManager, final Camera camera, final Spatial spatial,
+ EnumMap<StateType, RenderState> enforcedStates);
+
+ void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials,
+ EnumMap<StateType, RenderState> enforcedStates);
+
+ Texture getTexture();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java
new file mode 100644
index 0000000..54eec7f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Framebuffer.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+public class RenderTarget_Framebuffer implements RenderTarget {
+
+ @Override
+ public void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials,
+ final EnumMap<StateType, RenderState> enforcedStates) {
+ render(effectManager.getCurrentRenderer(), camera, spatials, null, enforcedStates);
+ }
+
+ @Override
+ public void render(final EffectManager effectManager, final Camera camera, final Spatial spatial,
+ final EnumMap<StateType, RenderState> enforcedStates) {
+ render(effectManager.getCurrentRenderer(), camera, null, spatial, enforcedStates);
+ }
+
+ public void render(final Renderer renderer, final Camera camera, final List<Spatial> spatials,
+ final Spatial spatial, final EnumMap<StateType, RenderState> enforcedStates) {
+ if (camera != Camera.getCurrentCamera()) {
+ camera.update();
+ }
+ camera.apply(renderer);
+
+ final RenderContext context = ContextManager.getCurrentContext();
+
+ context.enforceStates(enforcedStates);
+
+ if (spatial != null) {
+ spatial.onDraw(renderer);
+ } else {
+ for (final Spatial spat : spatials) {
+ spat.onDraw(renderer);
+ }
+ }
+
+ renderer.renderBuckets();
+ context.clearEnforcedStates();
+ }
+
+ @Override
+ public Texture getTexture() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java
new file mode 100644
index 0000000..338bda8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/RenderTarget_Texture2D.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Spatial;
+
+public class RenderTarget_Texture2D implements RenderTarget {
+
+ private final Texture2D _texture = new Texture2D();
+ private final int _width, _height;
+ private boolean _texSetup = false;
+ private final ColorRGBA _backgroundColor = new ColorRGBA(ColorRGBA.BLACK_NO_ALPHA);
+
+ public RenderTarget_Texture2D(final int width, final int height) {
+ this(width, height, TextureStoreFormat.RGB8);
+ }
+
+ public RenderTarget_Texture2D(final int width, final int height, final TextureStoreFormat format) {
+ _width = width;
+ _height = height;
+ _texture.setTextureStoreFormat(format);
+ }
+
+ @Override
+ public void render(final EffectManager effectManager, final Camera camera, final List<Spatial> spatials,
+ final EnumMap<StateType, RenderState> enforcedStates) {
+ render(effectManager.getCurrentRenderer(), camera, spatials, null, enforcedStates);
+ }
+
+ @Override
+ public void render(final EffectManager effectManager, final Camera camera, final Spatial spatial,
+ final EnumMap<StateType, RenderState> enforcedStates) {
+ render(effectManager.getCurrentRenderer(), camera, null, spatial, enforcedStates);
+ }
+
+ protected void render(final Renderer renderer, final Camera camera, final List<Spatial> spatials,
+ final Spatial spatial, final EnumMap<StateType, RenderState> enforcedStates) {
+ final TextureRenderer texRend = TextureRendererPool.fetch(_width, _height, renderer);
+ if (!_texSetup) {
+ texRend.setupTexture(_texture);
+ _texSetup = true;
+ }
+
+ // set desired bg color
+ texRend.setBackgroundColor(_backgroundColor);
+
+ // setup camera
+ if (camera != null) {
+ texRend.getCamera().setFrame(camera);
+ texRend.getCamera().setFrustum(camera);
+ texRend.getCamera().setProjectionMode(camera.getProjectionMode());
+ }
+
+ texRend.enforceStates(enforcedStates);
+
+ // draw to texture
+ if (spatial != null) {
+ texRend.render(spatial, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ } else {
+ texRend.render(spatials, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ }
+
+ texRend.clearEnforcedStates();
+ TextureRendererPool.release(texRend);
+ }
+
+ @Override
+ public Texture2D getTexture() {
+ return _texture;
+ }
+
+ public int getWidth() {
+ return _width;
+ }
+
+ public int getHeight() {
+ return _height;
+ }
+
+ public ReadOnlyColorRGBA getBackgroundColor() {
+ return _backgroundColor;
+ }
+
+ public void setBackgroundColor(final ReadOnlyColorRGBA color) {
+ _backgroundColor.set(color);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java
new file mode 100644
index 0000000..a49dfa2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/SpatialRTTEffect.java
@@ -0,0 +1,29 @@
+
+package com.ardor3d.renderer.effect;
+
+import java.util.Arrays;
+
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.scenegraph.Spatial;
+
+public class SpatialRTTEffect extends RenderEffect {
+
+ private final EffectStep_SetRenderTarget _targetStep;
+ private final EffectStep_RenderSpatials _drawStep;
+
+ public SpatialRTTEffect(final String targetName, final Camera trackedCamera, final Spatial... spatials) {
+ _targetStep = new EffectStep_SetRenderTarget(targetName);
+ _drawStep = new EffectStep_RenderSpatials(trackedCamera);
+ _drawStep.getSpatials().addAll(Arrays.asList(spatials));
+ }
+
+ @Override
+ public void prepare(final EffectManager manager) {
+ _steps.clear();
+ _steps.add(_targetStep);
+ _steps.add(_drawStep);
+
+ super.prepare(manager);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java
new file mode 100644
index 0000000..4f42df3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/effect/TextureRendererPool.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.effect;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.google.common.collect.Lists;
+
+public enum TextureRendererPool {
+ INSTANCE;
+
+ private final List<TextureRenderer> renderers = Lists.newLinkedList();
+
+ public static TextureRenderer fetch(final int width, final int height, final Renderer renderer) {
+ for (final Iterator<TextureRenderer> it = INSTANCE.renderers.iterator(); it.hasNext();) {
+ final TextureRenderer texRend = it.next();
+ if (texRend.getWidth() == width && texRend.getHeight() == height) {
+ it.remove();
+ return texRend;
+ }
+ }
+
+ // none found, make one
+ return TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, renderer, ContextManager
+ .getCurrentContext().getCapabilities());
+ }
+
+ public static void release(final TextureRenderer texRend) {
+ INSTANCE.renderers.add(texRend);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java
new file mode 100644
index 0000000..6597e59
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/BasicPassManager.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.pass;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+
+/**
+ * <code>BasicPassManager</code> controls a set of passes and sends through calls to render and update.
+ */
+public class BasicPassManager {
+
+ protected List<Pass> _passes = new ArrayList<Pass>();
+
+ public void add(final Pass toAdd) {
+ if (toAdd != null) {
+ _passes.add(toAdd);
+ }
+ }
+
+ public void insert(final Pass toAdd, final int index) {
+ _passes.add(index, toAdd);
+ }
+
+ public boolean contains(final Pass s) {
+ return _passes.contains(s);
+ }
+
+ public boolean remove(final Pass toRemove) {
+ return _passes.remove(toRemove);
+ }
+
+ public Pass get(final int index) {
+ return _passes.get(index);
+ }
+
+ public int passes() {
+ return _passes.size();
+ }
+
+ public void clearAll() {
+ cleanUp();
+ _passes.clear();
+ }
+
+ public void cleanUp() {
+ for (int i = 0, sSize = _passes.size(); i < sSize; i++) {
+ final Pass p = _passes.get(i);
+ p.cleanUp();
+ }
+ }
+
+ public void renderPasses(final Renderer r) {
+ for (int i = 0, sSize = _passes.size(); i < sSize; i++) {
+ final Pass p = _passes.get(i);
+ p.renderPass(r);
+ }
+ }
+
+ public void renderPasses(final TextureRenderer r, final int clear, final List<Texture> texs) {
+ for (int i = 0, sSize = _passes.size(); i < sSize; i++) {
+ final Pass p = _passes.get(i);
+ p.renderPass(r, clear, texs);
+ }
+ }
+
+ public void updatePasses(final double tpf) {
+ for (int i = 0, sSize = _passes.size(); i < sSize; i++) {
+ final Pass p = _passes.get(i);
+ p.updatePass(tpf);
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java
new file mode 100644
index 0000000..e4896fc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/OutlinePass.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.pass;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.LightState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.renderer.state.CullState.Face;
+
+/**
+ * This Pass can be used for drawing an outline around geometry objects. It does this by first drawing the geometry as
+ * normal, and then drawing an outline using the geometry's wireframe.
+ */
+public class OutlinePass extends RenderPass {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final float DEFAULT_LINE_WIDTH = 3f;
+ public static final ReadOnlyColorRGBA DEFAULT_OUTLINE_COLOR = new ColorRGBA(ColorRGBA.BLACK);
+
+ // render states needed to draw the outline
+ private final CullState _frontCull;
+ private final CullState _backCull;
+ private final WireframeState _wireframeState;
+ private final LightState _noLights;
+ private final TextureState _noTexture;
+ private BlendState _blendState;
+
+ public OutlinePass(final boolean antialiased) {
+ _wireframeState = new WireframeState();
+ _wireframeState.setFace(WireframeState.Face.FrontAndBack);
+ _wireframeState.setLineWidth(DEFAULT_LINE_WIDTH);
+ _wireframeState.setEnabled(true);
+
+ _frontCull = new CullState();
+ _frontCull.setCullFace(Face.Front);
+
+ _backCull = new CullState();
+ _backCull.setCullFace(Face.Back);
+
+ _wireframeState.setAntialiased(antialiased);
+
+ _noLights = new LightState();
+ _noLights.setGlobalAmbient(DEFAULT_OUTLINE_COLOR);
+ _noLights.setEnabled(true);
+
+ _noTexture = new TextureState();
+ _noTexture.setEnabled(true);
+
+ _blendState = new BlendState();
+ _blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ _blendState.setBlendEnabled(true);
+ _blendState.setEnabled(true);
+
+ }
+
+ @Override
+ public void doRender(final Renderer renderer) {
+ // if there's nothing to do
+ if (_spatials.size() == 0) {
+ return;
+ }
+
+ // normal render
+ _context.enforceState(_frontCull);
+ super.doRender(renderer);
+
+ // set up the render states
+ // CullState.setFlippedCulling(true);
+ _context.enforceState(_backCull);
+ _context.enforceState(_wireframeState);
+ _context.enforceState(_noLights);
+ _context.enforceState(_noTexture);
+ _context.enforceState(_blendState);
+
+ // this will draw the wireframe
+ super.doRender(renderer);
+
+ // revert state changes
+ // CullState.setFlippedCulling(false);
+ _context.clearEnforcedStates();
+ }
+
+ public void setOutlineWidth(final float width) {
+ _wireframeState.setLineWidth(width);
+ }
+
+ public float getOutlineWidth() {
+ return _wireframeState.getLineWidth();
+ }
+
+ public void setOutlineColor(final ReadOnlyColorRGBA outlineColor) {
+ _noLights.setGlobalAmbient(outlineColor);
+ }
+
+ public ReadOnlyColorRGBA getOutlineColor() {
+ return _noLights.getGlobalAmbient();
+ }
+
+ public BlendState getBlendState() {
+ return _blendState;
+ }
+
+ public void setBlendState(final BlendState alphaState) {
+ _blendState = alphaState;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java
new file mode 100644
index 0000000..12fadf3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/Pass.java
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.pass;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * <code>Pass</code> encapsulates logic necessary for rendering one or more steps in a multipass technique.
+ *
+ * Rendering:
+ *
+ * When renderPass is called, a check is first made to see if the pass isEnabled(). Then any states set on this pass are
+ * enforced via Spatial.enforceState(RenderState). This is useful for doing things such as causing this pass to be
+ * blended to a previous pass via enforcing an BlendState, etc. Next, doRender(Renderer) is called to do the actual
+ * rendering work. Finally, any enforced states set before this pass was run are restored.
+ */
+public abstract class Pass implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /** list of Spatial objects registered with this pass. */
+ protected List<Spatial> _spatials = new ArrayList<Spatial>();
+
+ /** if false, pass will not be updated or rendered. */
+ protected boolean _enabled = true;
+
+ /**
+ * RenderStates registered with this pass - if a given state is not null it overrides the corresponding state set
+ * during rendering.
+ */
+ protected final EnumMap<RenderState.StateType, RenderState> _passStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ protected RenderContext _context = null;
+
+ /** if enabled, set the states for this pass and then render. */
+ public final void renderPass(final Renderer r) {
+ if (!_enabled) {
+ return;
+ }
+ _context = ContextManager.getCurrentContext();
+ _context.pushEnforcedStates();
+ _context.enforceStates(_passStates);
+ doRender(r);
+ _context.popEnforcedStates();
+ _context = null;
+ }
+
+ /** if enabled, set the states for this pass and then render. */
+ public final void renderPass(final TextureRenderer r, final int clear, final List<Texture> texs) {
+ if (!_enabled) {
+ return;
+ }
+ _context = ContextManager.getCurrentContext();
+ _context.pushEnforcedStates();
+ _context.enforceStates(_passStates);
+ doRender(r, clear, texs);
+ _context.popEnforcedStates();
+ _context = null;
+ }
+
+ /**
+ * Enforce a particular state. In other words, the given state will override any state of the same type set on a
+ * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where
+ * multiple sets of states need to be applied to a scenegraph drawn multiple times.
+ *
+ * @param state
+ * state to enforce
+ */
+ public void setPassState(final RenderState state) {
+ _passStates.put(state.getType(), state);
+ }
+
+ /**
+ * Clears an enforced render state index by setting it to null. This allows object specific states to be used.
+ *
+ * @param type
+ * The type of RenderState to clear enforcement on.
+ */
+ public void clearPassState(final RenderState.StateType type) {
+ _passStates.remove(type);
+ }
+
+ /**
+ * sets all enforced states to null.
+ *
+ * @see RenderContext#clearEnforcedState(int)
+ */
+ public void clearPassStates() {
+ _passStates.clear();
+ }
+
+ protected abstract void doRender(Renderer r);
+
+ protected void doRender(final TextureRenderer r, final int clear, final List<Texture> texs) {
+ throw new UnsupportedOperationException("This pass type does not support RTT use.");
+ }
+
+ /** if enabled, call doUpdate to update information for this pass. */
+ public final void updatePass(final double tpf) {
+ if (!_enabled) {
+ return;
+ }
+ doUpdate(tpf);
+ }
+
+ protected void doUpdate(final double tpf) {}
+
+ public void add(final Spatial toAdd) {
+ _spatials.add(toAdd);
+ }
+
+ public Spatial get(final int index) {
+ return _spatials.get(index);
+ }
+
+ public boolean contains(final Spatial s) {
+ return _spatials.contains(s);
+ }
+
+ public boolean remove(final Spatial toRemove) {
+ return _spatials.remove(toRemove);
+ }
+
+ public int size() {
+ return _spatials.size();
+ }
+
+ /**
+ * @return Returns the enabled.
+ */
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ /**
+ * @param enabled
+ * The enabled to set.
+ */
+ public void setEnabled(final boolean enabled) {
+ _enabled = enabled;
+ }
+
+ public void cleanUp() {}
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java
new file mode 100644
index 0000000..e814a54
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/pass/RenderPass.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.pass;
+
+import java.util.List;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * <code>RenderPass</code> renders the spatials attached to it as normal, including rendering the renderqueue at the end
+ * of the pass.
+ */
+public class RenderPass extends Pass {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void doRender(final Renderer r) {
+ for (int i = 0, sSize = _spatials.size(); i < sSize; i++) {
+ final Spatial s = _spatials.get(i);
+ r.draw(s);
+ }
+ r.renderBuckets();
+ }
+
+ @Override
+ public void doRender(final TextureRenderer r, final int clear, final List<Texture> texs) {
+ r.render(_spatials, texs, clear);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java
new file mode 100644
index 0000000..3350df3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/AbstractRenderBucket.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Stack;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.SortUtil;
+
+public class AbstractRenderBucket implements RenderBucket {
+
+ protected Comparator<Spatial> _comparator;
+
+ protected Spatial[] _currentList, _tempList;
+ protected int _currentListSize;
+
+ protected Stack<Spatial[]> _listStack = new Stack<Spatial[]>();
+ protected Stack<Spatial[]> _listStackPool = new Stack<Spatial[]>();
+ protected Stack<Integer> _listSizeStack = new Stack<Integer>();
+
+ public AbstractRenderBucket() {
+ _currentList = new Spatial[32];
+ _tempList = new Spatial[_currentList.length];
+ }
+
+ public void add(final Spatial spatial) {
+ spatial._queueDistance = Double.NEGATIVE_INFINITY;
+ if (_currentListSize >= _currentList.length) {
+ // grow if necessary
+ final Spatial[] temp = new Spatial[_currentListSize * 2];
+ System.arraycopy(_currentList, 0, temp, 0, _currentListSize);
+ _currentList = temp;
+ if (_tempList.length < temp.length) {
+ _tempList = new Spatial[temp.length];
+ }
+ }
+ _currentList[_currentListSize++] = spatial;
+ }
+
+ public void remove(final Spatial spatial) {
+ int index = 0;
+ for (int i = 0; i < _currentListSize; i++) {
+ if (_currentList[index] == spatial) {
+ break;
+ }
+ index++;
+ }
+ for (int i = index; i < _currentListSize - 1; i++) {
+ _currentList[i] = _currentList[i + 1];
+ }
+
+ _currentListSize--;
+ }
+
+ public void clear() {
+ if (_currentListSize > 0) {
+ Arrays.fill(_currentList, 0, _currentListSize, null);
+ _currentListSize = 0;
+ }
+ }
+
+ public void render(final Renderer renderer) {
+ for (int i = 0; i < _currentListSize; i++) {
+ _currentList[i].draw(renderer);
+ }
+ }
+
+ public void sort() {
+ // only sort if we have more than one item in our bucket.
+ if (_currentListSize > 1) {
+ if (_currentListSize <= SortUtil.SHELL_SORT_THRESHOLD) {
+ // shell sort
+ SortUtil.shellSort(_currentList, 0, _currentListSize - 1, _comparator);
+ } else {
+ // copy in our list for use in the merge sort.
+ System.arraycopy(_currentList, 0, _tempList, 0, _currentListSize);
+
+ // merge sort
+ SortUtil.msort(_tempList, _currentList, 0, _currentListSize - 1, _comparator);
+
+ // null fill to remove references
+ Arrays.fill(_tempList, 0, _currentListSize, null);
+ }
+ }
+ }
+
+ public void pushBucket() {
+ _listStack.push(_currentList);
+ if (_listStackPool.isEmpty()) {
+ _currentList = new Spatial[32];
+ } else {
+ _currentList = _listStackPool.pop();
+ }
+
+ _listSizeStack.push(_currentListSize);
+ _currentListSize = 0;
+ }
+
+ public void popBucket() {
+ if (_currentList != null) {
+ _listStackPool.push(_currentList);
+ }
+ _currentList = _listStack.pop();
+ _currentListSize = _listSizeStack.pop();
+ }
+
+ /**
+ * Calculates the distance from a spatial to the camera. Distance is a squared distance.
+ *
+ * @param spat
+ * Spatial to check distance.
+ * @return Distance from Spatial to current context's camera.
+ */
+ protected double distanceToCam(final Spatial spat) {
+ if (spat._queueDistance != Double.NEGATIVE_INFINITY) {
+ return spat._queueDistance;
+ }
+
+ final Camera cam = Camera.getCurrentCamera();
+
+ if (spat.getWorldBound() != null && Vector3.isValid(spat.getWorldBound().getCenter())) {
+ spat._queueDistance = spat.getWorldBound().distanceToEdge(cam.getLocation());
+ } else {
+ final ReadOnlyVector3 spatPosition = spat.getWorldTranslation();
+ if (!Vector3.isValid(spatPosition)) {
+ spat._queueDistance = Double.POSITIVE_INFINITY;
+ } else {
+ spat._queueDistance = cam.getLocation().distance(spatPosition);
+ }
+ }
+
+ return spat._queueDistance;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java
new file mode 100644
index 0000000..6ced7e2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OpaqueRenderBucket.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Comparator;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.TextureKey;
+
+public class OpaqueRenderBucket extends AbstractRenderBucket {
+
+ public OpaqueRenderBucket() {
+ super();
+
+ _comparator = new OpaqueComparator();
+ }
+
+ private class OpaqueComparator implements Comparator<Spatial> {
+ public int compare(final Spatial o1, final Spatial o2) {
+ if (o1 instanceof Mesh && o2 instanceof Mesh) {
+ return compareByStates((Mesh) o1, (Mesh) o2);
+ }
+
+ final double d1 = distanceToCam(o1);
+ final double d2 = distanceToCam(o2);
+ if (d1 > d2) {
+ return 1;
+ } else if (d1 < d2) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Compare opaque items by their texture states - generally the most expensive switch. Later this might expand
+ * to comparisons by other states as well, such as lighting or material.
+ */
+ private int compareByStates(final Mesh mesh1, final Mesh mesh2) {
+ final TextureState ts1 = (TextureState) mesh1.getWorldRenderState(RenderState.StateType.Texture);
+ final TextureState ts2 = (TextureState) mesh2.getWorldRenderState(RenderState.StateType.Texture);
+ if (ts1 == ts2) {
+ return 0;
+ } else if (ts1 == null && ts2 != null) {
+ return -1;
+ } else if (ts2 == null && ts1 != null) {
+ return 1;
+ }
+
+ for (int x = 0, maxIndex = Math.min(ts1.getMaxTextureIndexUsed(), ts2.getMaxTextureIndexUsed()); x <= maxIndex; x++) {
+
+ final TextureKey key1 = ts1.getTextureKey(x);
+ final TextureKey key2 = ts2.getTextureKey(x);
+
+ if (key1 == null) {
+ if (key2 == null) {
+ continue;
+ } else {
+ return -1;
+ }
+ } else if (key2 == null) {
+ return 1;
+ }
+
+ final int tid1 = key1.hashCode();
+ final int tid2 = key2.hashCode();
+
+ if (tid1 == tid2) {
+ continue;
+ } else if (tid1 < tid2) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ if (ts1.getMaxTextureIndexUsed() != ts2.getMaxTextureIndexUsed()) {
+ return ts2.getMaxTextureIndexUsed() - ts1.getMaxTextureIndexUsed();
+ }
+
+ return 0;
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java
new file mode 100644
index 0000000..afc7d70
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/OrthoRenderBucket.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Comparator;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+
+public class OrthoRenderBucket extends AbstractRenderBucket {
+
+ public OrthoRenderBucket() {
+ super();
+
+ _comparator = new OrthoComparator();
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ if (_currentListSize > 0) {
+ try {
+ renderer.setOrtho();
+ for (int i = 0; i < _currentListSize; i++) {
+ _currentList[i].draw(renderer);
+ }
+ } finally {
+ renderer.unsetOrtho();
+ }
+ }
+ }
+
+ private static class OrthoComparator implements Comparator<Spatial> {
+ public int compare(final Spatial o1, final Spatial o2) {
+ if (o2.getSceneHints().getOrthoOrder() == o1.getSceneHints().getOrthoOrder()) {
+ return 0;
+ } else if (o2.getSceneHints().getOrthoOrder() < o1.getSceneHints().getOrthoOrder()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java
new file mode 100644
index 0000000..abc888e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucket.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+
+public interface RenderBucket {
+ void add(Spatial spatial);
+
+ void remove(Spatial spatial);
+
+ void clear();
+
+ void sort();
+
+ void render(Renderer renderer);
+
+ void pushBucket();
+
+ void popBucket();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java
new file mode 100644
index 0000000..bd48c03
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderBucketType.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.HashMap;
+
+public final class RenderBucketType {
+
+ public static final RenderBucketType getRenderBucketType(final String name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name must not be null!");
+ }
+
+ RenderBucketType bucketType = bucketTypeMap.get(name);
+ if (bucketType == null) {
+ bucketType = new RenderBucketType(name);
+ }
+ return bucketType;
+ }
+
+ private static final HashMap<String, RenderBucketType> bucketTypeMap = new HashMap<String, RenderBucketType>();
+
+ private final String name;
+
+ private RenderBucketType(final String name) {
+ this.name = name;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return name();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ /**
+ * Use your parent's RenderBucketType. If you do not have a parent, {@link #Opaque} will be used instead.
+ */
+ public static final RenderBucketType Inherit = getRenderBucketType("Inherit");
+
+ /**
+ * Used for objects that we want to guarantee will be rendered first.
+ */
+ public static final RenderBucketType PreBucket = getRenderBucketType("PreBucket");
+
+ /**
+ * TODO: Add definition.
+ */
+ public static final RenderBucketType Shadow = getRenderBucketType("Shadow");
+
+ /**
+ * Used for surfaces that are fully opaque - can not be seen through. Drawn from front to back.
+ */
+ public static final RenderBucketType Opaque = getRenderBucketType("Opaque");
+
+ /**
+ * Used for surfaces that are partially transparent or translucent - can be seen through. Drawn from back to front.
+ * See also the flag {@link com.ardor3d.renderer.queue.TransparentRenderBucket#setTwoPassTransparency(boolean)
+ * TransparentRenderBucket.setTwoPassTransparency(boolean)} allowing you to enable two pass transparency for more
+ * accurate results.
+ */
+ public static final RenderBucketType Transparent = getRenderBucketType("Transparent");
+
+ /**
+ * Draw in orthographic mode where the x and y coordinates are in screen space with the origin in the lower left
+ * corner. Uses {@link com.ardor3d.scenegraph.hint.SceneHints#getOrthoOrder() SceneHints.getOrthoOrder()} to
+ * determine draw order.
+ */
+ public static final RenderBucketType Ortho = getRenderBucketType("Ortho");
+
+ /**
+ * Used for objects that we want to guarantee will be rendered last.
+ */
+ public static final RenderBucketType PostBucket = getRenderBucketType("PostBucket");
+
+ /**
+ * Do not use bucket system. Instead, draw the spatial immediately to the back buffer.
+ */
+ public static final RenderBucketType Skip = getRenderBucketType("Skip");
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java
new file mode 100644
index 0000000..facd51c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/RenderQueue.java
@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Map;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.InstancingManager;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.Constants;
+import com.google.common.collect.Maps;
+
+public class RenderQueue {
+
+ private final Map<RenderBucketType, RenderBucket> renderBuckets = Maps.newLinkedHashMap();
+
+ public RenderQueue() {
+ setupDefaultBuckets();
+ }
+
+ public void setupBuckets(final RenderBucketType[] renderBucketTypes, final RenderBucket[] buckets) {
+ if (renderBucketTypes.length != buckets.length) {
+ throw new Ardor3dException("Can't setup buckets, RenderBucketType and RenderBucket counts don't match.");
+ }
+ removeRenderBuckets();
+ for (int i = 0; i < renderBucketTypes.length; i++) {
+ setRenderBucket(renderBucketTypes[i], buckets[i]);
+ }
+ }
+
+ private void setupDefaultBuckets() {
+ setRenderBucket(RenderBucketType.PreBucket, new OpaqueRenderBucket());
+ setRenderBucket(RenderBucketType.Shadow, new OpaqueRenderBucket());
+ setRenderBucket(RenderBucketType.Opaque, new OpaqueRenderBucket());
+ setRenderBucket(RenderBucketType.Transparent, new TransparentRenderBucket());
+ setRenderBucket(RenderBucketType.Ortho, new OrthoRenderBucket());
+ setRenderBucket(RenderBucketType.PostBucket, new OpaqueRenderBucket());
+ }
+
+ public void removeRenderBuckets() {
+ renderBuckets.clear();
+ }
+
+ public void removeRenderBucket(final RenderBucketType type) {
+ renderBuckets.remove(type);
+ }
+
+ public void setRenderBucket(final RenderBucketType type, final RenderBucket renderBucket) {
+ renderBuckets.put(type, renderBucket);
+ }
+
+ public RenderBucket getRenderBucket(final RenderBucketType type) {
+ return renderBuckets.get(type);
+ }
+
+ public void addToQueue(final Spatial spatial, final RenderBucketType type) {
+ if (type == RenderBucketType.Inherit || type == RenderBucketType.Skip) {
+ throw new Ardor3dException("Can't add spatial to bucket of type: " + type);
+ }
+
+ if (Constants.enableInstancedGeometrySupport && prepareForInstancing(spatial)) {
+ return;
+ }
+
+ final RenderBucket renderBucket = getRenderBucket(type);
+ if (renderBucket != null) {
+ renderBucket.add(spatial);
+ } else {
+ throw new Ardor3dException("No bucket exists of type: " + type);
+ }
+ }
+
+ private final boolean prepareForInstancing(final Spatial spatial) {
+ boolean skipRenderQueue = false;
+
+ if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+ final InstancingManager instancing = mesh.getMeshData().getInstancingManager();
+ // Only one instance needs to be added to the render queue
+ if (instancing != null) {
+ skipRenderQueue = instancing.isAddedToRenderQueue();
+ instancing.registerMesh(mesh);
+ }
+ }
+ return skipRenderQueue;
+ }
+
+ public void removeFromQueue(final Spatial spatial, final RenderBucketType type) {
+ if (type == RenderBucketType.Inherit || type == RenderBucketType.Skip) {
+ throw new Ardor3dException("Can't remove spatial from bucket of type: " + type);
+ }
+
+ final RenderBucket renderBucket = getRenderBucket(type);
+ if (renderBucket != null) {
+ renderBucket.remove(spatial);
+ } else {
+ throw new Ardor3dException("No bucket exists of type: " + type);
+ }
+ }
+
+ public void clearBuckets() {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.clear();
+ }
+ }
+
+ public void sortBuckets() {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.sort();
+ }
+ }
+
+ public void renderOnly(final Renderer renderer) {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.render(renderer);
+ }
+ }
+
+ public void renderBuckets(final Renderer renderer) {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.sort();
+ renderBucket.render(renderer);
+ renderBucket.clear();
+ }
+ }
+
+ public void pushBuckets() {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.pushBucket();
+ }
+ }
+
+ public void popBuckets() {
+ for (final RenderBucket renderBucket : renderBuckets.values()) {
+ renderBucket.popBucket();
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java
new file mode 100644
index 0000000..fe3e73d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/queue/TransparentRenderBucket.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.queue;
+
+import java.util.Comparator;
+
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.renderer.state.CullState.Face;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.TransparencyType;
+
+public class TransparentRenderBucket extends AbstractRenderBucket {
+ /** CullState for two pass transparency rendering. */
+ private final CullState _tranparentCull;
+
+ /** ZBufferState for two pass transparency rendering. */
+ private final ZBufferState _transparentZBuff;
+
+ public TransparentRenderBucket() {
+ super();
+
+ _tranparentCull = new CullState();
+ _transparentZBuff = new ZBufferState();
+ _transparentZBuff.setWritable(false);
+ _transparentZBuff.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
+
+ _comparator = new TransparentComparator();
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ // Grab our render context - used to enforce renderstates
+ final RenderContext context = ContextManager.getCurrentContext();
+
+ // go through our bucket contents
+ Spatial spatial;
+ for (int i = 0; i < _currentListSize; i++) {
+ spatial = _currentList[i];
+
+ // make sure we have a real spatial
+ if (spatial == null) {
+ continue;
+ }
+
+ // we only care about altering the Mesh... perhaps could be altered later to some interface shared by Mesh
+ // and other leaf nodes.
+ if (spatial instanceof Mesh) {
+
+ // get our transparency rendering type.
+ final TransparencyType renderType = spatial.getSceneHints().getTransparencyType();
+
+ // check for one of the two pass types...
+ if (renderType != TransparencyType.OnePass) {
+
+ // get handle to Mesh
+ final Mesh mesh = (Mesh) spatial;
+
+ // check if we have a Cull state set or enforced. If one is explicitly set and is not Face.None,
+ // we'll not do two-pass transparency.
+ RenderState setState = context.hasEnforcedStates() ? context.getEnforcedState(StateType.Cull)
+ : null;
+ if (setState == null) {
+ setState = mesh.getWorldRenderState(RenderState.StateType.Cull);
+ }
+
+ // Do the described check.
+ if (setState == null || ((CullState) setState).getCullFace() == Face.None) {
+
+ // pull any currently enforced cull or zstate. We'll put them back afterwards
+ final RenderState oldCullState = context.getEnforcedState(StateType.Cull);
+ final RenderState oldZState = context.getEnforcedState(StateType.ZBuffer);
+
+ // enforce our cull and zstate. The zstate is setup to respect depth, but not write to it.
+ context.enforceState(_tranparentCull);
+ context.enforceState(_transparentZBuff);
+
+ // first render back-facing tris only
+ _tranparentCull.setCullFace(CullState.Face.Front);
+ mesh.draw(renderer);
+
+ // revert z state
+ context.clearEnforcedState(StateType.ZBuffer);
+ if (oldZState != null) {
+ context.enforceState(oldZState);
+ }
+
+ // render front-facing tris
+ _tranparentCull.setCullFace(CullState.Face.Back);
+ mesh.draw(renderer);
+
+ // revert cull state
+ if (oldCullState != null) {
+ context.enforceState(oldCullState);
+ } else {
+ context.clearEnforcedState(StateType.Cull);
+ }
+ continue;
+ }
+ }
+ }
+
+ // go ahead and draw as usual
+ spatial.draw(renderer);
+ }
+ }
+
+ private class TransparentComparator implements Comparator<Spatial> {
+ public int compare(final Spatial o1, final Spatial o2) {
+ final double d1 = distanceToCam(o1);
+ final double d2 = distanceToCam(o2);
+ if (d1 > d2) {
+ return -1;
+ } else if (d1 < d2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java
new file mode 100644
index 0000000..5b2ed39
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/BlendState.java
@@ -0,0 +1,636 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.record.BlendStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>BlendState</code> maintains the state of the blending values of a particular node and its children. The blend
+ * state provides a method for blending a source pixel with a destination pixel. The blend value provides a transparent
+ * or translucent surfaces. For example, this would allow for the rendering of green glass. Where you could see all
+ * objects behind this green glass but they would be tinted green.
+ */
+public class BlendState extends RenderState {
+
+ public enum SourceFunction {
+ /**
+ * The source value of the blend function is all zeros.
+ */
+ Zero(false),
+ /**
+ * The source value of the blend function is all ones.
+ */
+ One(false),
+ /**
+ * The source value of the blend function is the destination color.
+ */
+ DestinationColor(false),
+ /**
+ * The source value of the blend function is 1 - the destination color.
+ */
+ OneMinusDestinationColor(false),
+ /**
+ * The source value of the blend function is the source alpha value.
+ */
+ SourceAlpha(false),
+ /**
+ * The source value of the blend function is 1 - the source alpha value.
+ */
+ OneMinusSourceAlpha(false),
+ /**
+ * The source value of the blend function is the destination alpha.
+ */
+ DestinationAlpha(false),
+ /**
+ * The source value of the blend function is 1 - the destination alpha.
+ */
+ OneMinusDestinationAlpha(false),
+ /**
+ * The source value of the blend function is the minimum of alpha or 1 - alpha.
+ */
+ SourceAlphaSaturate(false),
+ /**
+ * The source value of the blend function is the value of the constant color. (Rc, Gc, Bc, Ac) If not set, black
+ * with alpha = 0 is used. If not supported, falls back to One.
+ */
+ ConstantColor(true),
+ /**
+ * The source value of the blend function is 1 minus the value of the constant color. (1-Rc, 1-Gc, 1-Bc, 1-Ac)
+ * If color is not set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ OneMinusConstantColor(true),
+ /**
+ * The source value of the blend function is the value of the constant color's alpha. (Ac, Ac, Ac, Ac) If not
+ * set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ ConstantAlpha(true),
+ /**
+ * The source value of the blend function is 1 minus the value of the constant color's alpha. (1-Ac, 1-Ac, 1-Ac,
+ * 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ OneMinusConstantAlpha(true);
+
+ private boolean usesConstantColor;
+
+ private SourceFunction(final boolean usesConstantColor) {
+ this.usesConstantColor = usesConstantColor;
+ }
+
+ public boolean usesConstantColor() {
+ return usesConstantColor;
+ }
+ }
+
+ public enum DestinationFunction {
+ /**
+ * The destination value of the blend function is all zeros.
+ */
+ Zero(false),
+ /**
+ * The destination value of the blend function is all ones.
+ */
+ One(false),
+ /**
+ * The destination value of the blend function is the source color.
+ */
+ SourceColor(false),
+ /**
+ * The destination value of the blend function is 1 - the source color.
+ */
+ OneMinusSourceColor(false),
+ /**
+ * The destination value of the blend function is the source alpha value.
+ */
+ SourceAlpha(false),
+ /**
+ * The destination value of the blend function is 1 - the source alpha value.
+ */
+ OneMinusSourceAlpha(false),
+ /**
+ * The destination value of the blend function is the destination alpha value.
+ */
+ DestinationAlpha(false),
+ /**
+ * The destination value of the blend function is 1 - the destination alpha value.
+ */
+ OneMinusDestinationAlpha(false),
+ /**
+ * The destination value of the blend function is the value of the constant color. (Rc, Gc, Bc, Ac) If not set,
+ * black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ ConstantColor(true),
+ /**
+ * The destination value of the blend function is 1 minus the value of the constant color. (1-Rc, 1-Gc, 1-Bc,
+ * 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ OneMinusConstantColor(true),
+ /**
+ * The destination value of the blend function is the value of the constant color's alpha. (Ac, Ac, Ac, Ac) If
+ * not set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ ConstantAlpha(true),
+ /**
+ * The destination value of the blend function is 1 minus the value of the constant color's alpha. (1-Ac, 1-Ac,
+ * 1-Ac, 1-Ac) If color is not set, black with alpha = 0 is used. If not supported, falls back to One.
+ */
+ OneMinusConstantAlpha(true);
+
+ private boolean usesConstantColor;
+
+ private DestinationFunction(final boolean usesConstantColor) {
+ this.usesConstantColor = usesConstantColor;
+ }
+
+ public boolean usesConstantColor() {
+ return usesConstantColor;
+ }
+ }
+
+ public enum TestFunction {
+ /**
+ * Never passes the depth test.
+ */
+ Never,
+ /**
+ * Always passes the depth test.
+ */
+ Always,
+ /**
+ * Pass the test if this alpha is equal to the reference alpha.
+ */
+ EqualTo,
+ /**
+ * Pass the test if this alpha is not equal to the reference alpha.
+ */
+ NotEqualTo,
+ /**
+ * Pass the test if this alpha is less than the reference alpha.
+ */
+ LessThan,
+ /**
+ * Pass the test if this alpha is less than or equal to the reference alpha.
+ */
+ LessThanOrEqualTo,
+ /**
+ * Pass the test if this alpha is greater than the reference alpha.
+ */
+ GreaterThan,
+ /**
+ * Pass the test if this alpha is greater than or equal to the reference alpha.
+ */
+ GreaterThanOrEqualTo;
+
+ }
+
+ public enum BlendEquation {
+ /**
+ * Sets the blend equation so that the source and destination data are added. (Default) Clamps to [0,1] Useful
+ * for things like antialiasing and transparency.
+ */
+ Add,
+ /**
+ * Sets the blend equation so that the source and destination data are subtracted (Src - Dest). Clamps to [0,1]
+ * Falls back to Add if supportsSubtract is false.
+ */
+ Subtract,
+ /**
+ * Same as Subtract, but the order is reversed (Dst - Src). Clamps to [0,1] Falls back to Add if
+ * supportsSubtract is false.
+ */
+ ReverseSubtract,
+ /**
+ * sets the blend equation so that each component of the result color is the minimum of the corresponding
+ * components of the source and destination colors. This and Max are useful for applications that analyze image
+ * data (image thresholding against a constant color, for example). Falls back to Add if supportsMinMax is
+ * false.
+ */
+ Min,
+ /**
+ * sets the blend equation so that each component of the result color is the maximum of the corresponding
+ * components of the source and destination colors. This and Min are useful for applications that analyze image
+ * data (image thresholding against a constant color, for example). Falls back to Add if supportsMinMax is
+ * false.
+ */
+ Max;
+ }
+
+ // attributes
+ /** The current value of if blend is enabled. */
+ private boolean _blendEnabled = false;
+
+ /** The blend color used in constant blend operations. */
+ private ColorRGBA _constantColor = new ColorRGBA(0, 0, 0, 0);
+
+ /** The current source blend function. */
+ private SourceFunction _sourceFunctionRGB = SourceFunction.SourceAlpha;
+ /** The current destination blend function. */
+ private DestinationFunction _destinationFunctionRGB = DestinationFunction.OneMinusSourceAlpha;
+ /** The current blend equation. */
+ private BlendEquation _blendEquationRGB = BlendEquation.Add;
+
+ /** The current source blend function. */
+ private SourceFunction _sourceFunctionAlpha = SourceFunction.SourceAlpha;
+ /** The current destination blend function. */
+ private DestinationFunction _destinationFunctionAlpha = DestinationFunction.OneMinusSourceAlpha;
+ /** The current blend equation. */
+ private BlendEquation _blendEquationAlpha = BlendEquation.Add;
+
+ /** If enabled, alpha testing done. */
+ private boolean _testEnabled = false;
+ /** Alpha test value. */
+ private TestFunction _testFunction = TestFunction.GreaterThan;
+ /** The reference value to which incoming alpha values are compared. */
+ private float _reference = 0;
+
+ /** Enables conversion of alpha values to masks - a form of dithering. */
+ private boolean _sampleAlphaToCoverageEnabled = false;
+ /** Replaces alpha sample with max value. */
+ private boolean _sampleAlphaToOneEnabled = false;
+ /** Enables fragment mask modification. */
+ private boolean _sampleCoverageEnabled = false;
+ /** a mask that modifies the coverage of multi-sampled pixel fragments. Must be [0, 1] */
+ private float _sampleCoverage = 1.0f;
+ /** If enabled, sample coverage mask is inverted. */
+ private boolean _sampleCoverageInverted = false;
+
+ /**
+ * Constructor instantiates a new <code>BlendState</code> object with default values.
+ */
+ public BlendState() {}
+
+ @Override
+ public StateType getType() {
+ return StateType.Blend;
+ }
+
+ /**
+ * <code>isBlendEnabled</code> returns true if blending is turned on, otherwise false is returned.
+ *
+ * @return true if blending is enabled, false otherwise.
+ */
+ public boolean isBlendEnabled() {
+ return _blendEnabled;
+ }
+
+ /**
+ * <code>setBlendEnabled</code> sets whether or not blending is enabled.
+ *
+ * @param value
+ * true to enable the blending, false to disable it.
+ */
+ public void setBlendEnabled(final boolean value) {
+ _blendEnabled = value;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setSrcFunction</code> sets the source function for the blending equation for both rgb and alpha values.
+ *
+ * @param function
+ * the source function for the blending equation.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setSourceFunction(final SourceFunction function) {
+ setSourceFunctionRGB(function);
+ setSourceFunctionAlpha(function);
+ }
+
+ /**
+ * <code>setSrcFunction</code> sets the source function for the blending equation. If supportsSeparateFunc is false,
+ * this value will be used for RGB and Alpha.
+ *
+ * @param function
+ * the source function for the blending equation.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setSourceFunctionRGB(final SourceFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _sourceFunctionRGB = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setSourceFunctionAlpha</code> sets the source function for the blending equation used with alpha values.
+ *
+ * @param function
+ * the source function for the blending equation for alpha values.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setSourceFunctionAlpha(final SourceFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _sourceFunctionAlpha = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getSourceFunction</code> returns the source function for the blending function.
+ *
+ * @return the source function for the blending function.
+ */
+ public SourceFunction getSourceFunctionRGB() {
+ return _sourceFunctionRGB;
+ }
+
+ /**
+ * <code>getSourceFunction</code> returns the source function for the blending function.
+ *
+ * @return the source function for the blending function.
+ */
+ public SourceFunction getSourceFunctionAlpha() {
+ return _sourceFunctionAlpha;
+ }
+
+ /**
+ * <code>setDestinationFunction</code> sets the destination function for the blending equation for both Alpha and
+ * RGB values.
+ *
+ * @param function
+ * the destination function for the blending equation.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setDestinationFunction(final DestinationFunction function) {
+ setDestinationFunctionRGB(function);
+ setDestinationFunctionAlpha(function);
+ }
+
+ /**
+ * <code>setDestinationFunctionRGB</code> sets the destination function for the blending equation. If
+ * supportsSeparateFunc is false, this value will be used for RGB and Alpha.
+ *
+ * @param function
+ * the destination function for the blending equation for RGB values.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setDestinationFunctionRGB(final DestinationFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _destinationFunctionRGB = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setDestinationFunctionAlpha</code> sets the destination function for the blending equation.
+ *
+ * @param function
+ * the destination function for the blending equation for Alpha values.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setDestinationFunctionAlpha(final DestinationFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _destinationFunctionAlpha = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getDestinationFunction</code> returns the destination function for the blending function.
+ *
+ * @return the destination function for the blending function.
+ */
+ public DestinationFunction getDestinationFunctionRGB() {
+ return _destinationFunctionRGB;
+ }
+
+ /**
+ * <code>getDestinationFunction</code> returns the destination function for the blending function.
+ *
+ * @return the destination function for the blending function.
+ */
+ public DestinationFunction getDestinationFunctionAlpha() {
+ return _destinationFunctionAlpha;
+ }
+
+ public void setBlendEquation(final BlendEquation blendEquation) {
+ setBlendEquationRGB(blendEquation);
+ setBlendEquationAlpha(blendEquation);
+ }
+
+ public void setBlendEquationRGB(final BlendEquation blendEquation) {
+ if (blendEquation == null) {
+ throw new IllegalArgumentException("blendEquation can not be null.");
+ }
+ _blendEquationRGB = blendEquation;
+ }
+
+ public void setBlendEquationAlpha(final BlendEquation blendEquation) {
+ if (blendEquation == null) {
+ throw new IllegalArgumentException("blendEquation can not be null.");
+ }
+ _blendEquationAlpha = blendEquation;
+ }
+
+ public BlendEquation getBlendEquationRGB() {
+ return _blendEquationRGB;
+ }
+
+ public BlendEquation getBlendEquationAlpha() {
+ return _blendEquationAlpha;
+ }
+
+ /**
+ * <code>isTestEnabled</code> returns true if alpha testing is enabled, false otherwise.
+ *
+ * @return true if alpha testing is enabled, false otherwise.
+ */
+ public boolean isTestEnabled() {
+ return _testEnabled;
+ }
+
+ /**
+ * <code>setTestEnabled</code> turns alpha testing on and off. True turns on the testing, while false diables it.
+ *
+ * @param value
+ * true to enabled alpha testing, false to disable it.
+ */
+ public void setTestEnabled(final boolean value) {
+ _testEnabled = value;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setTestFunction</code> sets the testing function used for the alpha testing. If an invalid value is passed,
+ * the default TF_ALWAYS is used.
+ *
+ * @param function
+ * the testing function used for the alpha testing.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setTestFunction(final TestFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _testFunction = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getTestFunction</code> returns the testing function used for the alpha testing.
+ *
+ * @return the testing function used for the alpha testing.
+ */
+ public TestFunction getTestFunction() {
+ return _testFunction;
+ }
+
+ /**
+ * <code>setReference</code> sets the reference value that incoming alpha values are compared to when doing alpha
+ * testing. This is clamped to [0, 1].
+ *
+ * @param reference
+ * the reference value that alpha values are compared to.
+ */
+ public void setReference(float reference) {
+ if (reference < 0) {
+ reference = 0;
+ }
+
+ if (reference > 1) {
+ reference = 1;
+ }
+ _reference = reference;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getReference</code> returns the reference value that incoming alpha values are compared to.
+ *
+ * @return the reference value that alpha values are compared to.
+ */
+ public float getReference() {
+ return _reference;
+ }
+
+ /**
+ * @return the color used in constant blending functions. (0,0,0,0) is the default.
+ */
+ public ReadOnlyColorRGBA getConstantColor() {
+ return _constantColor;
+ }
+
+ public void setConstantColor(final ReadOnlyColorRGBA constantColor) {
+ _constantColor.set(constantColor);
+ }
+
+ public boolean isSampleAlphaToCoverageEnabled() {
+ return _sampleAlphaToCoverageEnabled;
+ }
+
+ public void setSampleAlphaToCoverageEnabled(final boolean sampleAlphaToCoverageEnabled) {
+ _sampleAlphaToCoverageEnabled = sampleAlphaToCoverageEnabled;
+ }
+
+ public boolean isSampleAlphaToOneEnabled() {
+ return _sampleAlphaToOneEnabled;
+ }
+
+ public void setSampleAlphaToOneEnabled(final boolean sampleAlphaToOneEnabled) {
+ _sampleAlphaToOneEnabled = sampleAlphaToOneEnabled;
+ }
+
+ public boolean isSampleCoverageEnabled() {
+ return _sampleCoverageEnabled;
+ }
+
+ public void setSampleCoverageEnabled(final boolean sampleCoverageEnabled) {
+ _sampleCoverageEnabled = sampleCoverageEnabled;
+ }
+
+ public float getSampleCoverage() {
+ return _sampleCoverage;
+ }
+
+ /**
+ * @param value
+ * new sample coverage value - must be in range [0f, 1f]
+ * @throws IllegalArgumentException
+ * if value is not in correct range.
+ */
+ public void setSampleCoverage(final float value) {
+ if (value > 1.0f || value < 0.0f) {
+ throw new IllegalArgumentException("value must be in range [0f, 1f]");
+ }
+ _sampleCoverage = value;
+ }
+
+ public boolean isSampleCoverageInverted() {
+ return _sampleCoverageInverted;
+ }
+
+ public void setSampleCoverageInverted(final boolean sampleCoverageInverted) {
+ _sampleCoverageInverted = sampleCoverageInverted;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_blendEnabled, "blendEnabled", false);
+ capsule.write(_sourceFunctionRGB, "sourceFunctionRGB", SourceFunction.SourceAlpha);
+ capsule.write(_destinationFunctionRGB, "destinationFunctionRGB", DestinationFunction.OneMinusSourceAlpha);
+ capsule.write(_blendEquationRGB, "blendEquationRGB", BlendEquation.Add);
+ capsule.write(_sourceFunctionAlpha, "sourceFunctionAlpha", SourceFunction.SourceAlpha);
+ capsule.write(_destinationFunctionAlpha, "destinationFunctionAlpha", DestinationFunction.OneMinusSourceAlpha);
+ capsule.write(_blendEquationAlpha, "blendEquationAlpha", BlendEquation.Add);
+ capsule.write(_testEnabled, "testEnabled", false);
+ capsule.write(_testFunction, "test", TestFunction.GreaterThan);
+ capsule.write(_reference, "reference", 0);
+ capsule.write(_constantColor, "constantColor", null);
+ capsule.write(_sampleAlphaToCoverageEnabled, "sampleAlphaToCoverageEnabled", false);
+ capsule.write(_sampleAlphaToOneEnabled, "sampleAlphaToOneEnabled", false);
+ capsule.write(_sampleCoverageEnabled, "sampleCoverageEnabled", false);
+ capsule.write(_sampleCoverageInverted, "sampleCoverageInverted", false);
+ capsule.write(_sampleCoverage, "sampleCoverage", 1.0f);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _blendEnabled = capsule.readBoolean("blendEnabled", false);
+ _sourceFunctionRGB = capsule.readEnum("sourceFunctionRGB", SourceFunction.class, SourceFunction.SourceAlpha);
+ _destinationFunctionRGB = capsule.readEnum("destinationFunctionRGB", DestinationFunction.class,
+ DestinationFunction.OneMinusSourceAlpha);
+ _blendEquationRGB = capsule.readEnum("blendEquationRGB", BlendEquation.class, BlendEquation.Add);
+ _sourceFunctionAlpha = capsule
+ .readEnum("sourceFunctionAlpha", SourceFunction.class, SourceFunction.SourceAlpha);
+ _destinationFunctionAlpha = capsule.readEnum("destinationFunctionAlpha", DestinationFunction.class,
+ DestinationFunction.OneMinusSourceAlpha);
+ _blendEquationAlpha = capsule.readEnum("blendEquationAlpha", BlendEquation.class, BlendEquation.Add);
+ _testEnabled = capsule.readBoolean("testEnabled", false);
+ _testFunction = capsule.readEnum("test", TestFunction.class, TestFunction.GreaterThan);
+ _reference = capsule.readFloat("reference", 0);
+ _constantColor = (ColorRGBA) capsule.readSavable("constantColor", null);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new BlendStateRecord();
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java
new file mode 100644
index 0000000..0d809e9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ClipState.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.ClipStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>ClipState</code> specifies a plane to test for clipping of the nodes. This can be used to take "slices" out of
+ * geometric objects. ClipPlane can add an additional (to the normal frustum planes) six planes to clip against.
+ */
+public class ClipState extends RenderState {
+
+ /**
+ * Max supported number of user-defined clip planes in Ardor3D. Note that a user may or may not have access to all 6
+ * (or even any!) or their particular platform. Check ContextCapabilities to confirm as necessary.
+ */
+ public static final int MAX_CLIP_PLANES = 6;
+
+ protected boolean[] enabledClipPlanes = new boolean[MAX_CLIP_PLANES];
+
+ protected double[][] planeEquations = new double[MAX_CLIP_PLANES][4];
+
+ @Override
+ public StateType getType() {
+ return StateType.Clip;
+ }
+
+ /**
+ * Enables/disables a specific clip plane
+ *
+ * @param planeIndex
+ * Plane to enable/disable (CLIP_PLANE0-CLIP_PLANE5)
+ * @param enabled
+ * true/false
+ */
+ public void setEnableClipPlane(final int planeIndex, final boolean enabled) {
+ if (planeIndex < 0 || planeIndex >= MAX_CLIP_PLANES) {
+ return;
+ }
+
+ enabledClipPlanes[planeIndex] = enabled;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Sets plane equation for a specific clip plane
+ *
+ * @param planeIndex
+ * Plane to set equation for (CLIP_PLANE0-CLIP_PLANE5)
+ * @param clipX
+ * plane x variable
+ * @param clipY
+ * plane y variable
+ * @param clipZ
+ * plane z variable
+ * @param clipW
+ * plane w variable
+ */
+ public void setClipPlaneEquation(final int planeIndex, final double clipX, final double clipY, final double clipZ,
+ final double clipW) {
+ if (planeIndex < 0 || planeIndex >= MAX_CLIP_PLANES) {
+ return;
+ }
+
+ planeEquations[planeIndex][0] = clipX;
+ planeEquations[planeIndex][1] = clipY;
+ planeEquations[planeIndex][2] = clipZ;
+ planeEquations[planeIndex][3] = clipW;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @param index
+ * plane to check
+ * @return true if given clip plane is enabled
+ */
+ public boolean getPlaneEnabled(final int index) {
+ return enabledClipPlanes[index];
+ }
+
+ public double[] getPlaneEquations(final int plane) {
+ return planeEquations[plane];
+ }
+
+ public double getPlaneEquation(final int plane, final int eqIndex) {
+ return planeEquations[plane][eqIndex];
+ }
+
+ public void setPlaneEq(final int plane, final int eqIndex, final double value) {
+ planeEquations[plane][eqIndex] = value;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(enabledClipPlanes, "enabledClipPlanes", new boolean[MAX_CLIP_PLANES]);
+ capsule.write(planeEquations, "planeEquations", new double[MAX_CLIP_PLANES][4]);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ enabledClipPlanes = capsule.readBooleanArray("enabledClipPlanes", new boolean[MAX_CLIP_PLANES]);
+ planeEquations = capsule.readDoubleArray2D("planeEquations", new double[MAX_CLIP_PLANES][4]);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new ClipStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java
new file mode 100644
index 0000000..b07fb0c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ColorMaskState.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.ColorMaskStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>ColorMaskState</code>
+ */
+public class ColorMaskState extends RenderState {
+
+ protected boolean blue = true;
+ protected boolean green = true;
+ protected boolean red = true;
+ protected boolean alpha = true;
+
+ @Override
+ public StateType getType() {
+ return StateType.ColorMask;
+ }
+
+ public void setAll(final boolean on) {
+ blue = on;
+ green = on;
+ red = on;
+ alpha = on;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return Returns the alpha.
+ */
+ public boolean getAlpha() {
+ return alpha;
+ }
+
+ /**
+ * @param alpha
+ * The alpha to set.
+ */
+ public void setAlpha(final boolean alpha) {
+ this.alpha = alpha;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return Returns the blue.
+ */
+ public boolean getBlue() {
+ return blue;
+ }
+
+ /**
+ * @param blue
+ * The blue to set.
+ */
+ public void setBlue(final boolean blue) {
+ this.blue = blue;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return Returns the green.
+ */
+ public boolean getGreen() {
+ return green;
+ }
+
+ /**
+ * @param green
+ * The green to set.
+ */
+ public void setGreen(final boolean green) {
+ this.green = green;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return Returns the red.
+ */
+ public boolean getRed() {
+ return red;
+ }
+
+ /**
+ * @param red
+ * The red to set.
+ */
+ public void setRed(final boolean red) {
+ this.red = red;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(blue, "blue", true);
+ capsule.write(green, "green", true);
+ capsule.write(red, "red", true);
+ capsule.write(alpha, "alpha", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ blue = capsule.readBoolean("blue", true);
+ green = capsule.readBoolean("green", true);
+ red = capsule.readBoolean("red", true);
+ alpha = capsule.readBoolean("alpha", true);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new ColorMaskStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java
new file mode 100644
index 0000000..3ad5cc4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/CullState.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.CullStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>CullState</code> determines which side of a model will be visible when it is rendered. By default, both sides
+ * are visible. Define front as the side that traces its vertexes counter clockwise and back as the side that traces its
+ * vertexes clockwise, a side (front or back) can be culled, or not shown when the model is rendered. Instead, the side
+ * will be transparent. <br>
+ * <b>NOTE:</b> Any object that is placed in the transparent queue with two sided transparency will not use the
+ * cullstate that is attached to it. Instead, using the CullStates necessary for rendering two sided transparency.
+ */
+public class CullState extends RenderState {
+
+ public enum Face {
+ /** Neither front or back face is culled. This is default. */
+ None,
+ /** Cull the front faces. */
+ Front,
+ /** Cull the back faces. */
+ Back,
+ /** Cull both the front and back faces. */
+ FrontAndBack;
+ }
+
+ public enum PolygonWind {
+ /** Polygons whose vertices are specified in CCW order are front facing. This is default. */
+ CounterClockWise,
+ /** Polygons whose vertices are specified in CW order are front facing. */
+ ClockWise;
+ }
+
+ /** The cull face set for this CullState. */
+ private Face cullFace = Face.None;
+
+ /** The polygonWind order set for this CullState. */
+ private PolygonWind polygonWind = PolygonWind.CounterClockWise;
+
+ @Override
+ public StateType getType() {
+ return StateType.Cull;
+ }
+
+ /**
+ * @param face
+ * The new face to cull.
+ */
+ public void setCullFace(final Face face) {
+ cullFace = face;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the currently set face to cull.
+ */
+ public Face getCullFace() {
+ return cullFace;
+ }
+
+ /**
+ * @param windOrder
+ * The new polygonWind order.
+ */
+ public void setPolygonWind(final PolygonWind windOrder) {
+ polygonWind = windOrder;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the currently set polygonWind order.
+ */
+ public PolygonWind getPolygonWind() {
+ return polygonWind;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(cullFace, "cullFace", Face.None);
+ capsule.write(polygonWind, "polygonWind", PolygonWind.CounterClockWise);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ cullFace = capsule.readEnum("cullFace", Face.class, Face.None);
+ polygonWind = capsule.readEnum("polygonWind", PolygonWind.class, PolygonWind.CounterClockWise);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new CullStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java
new file mode 100644
index 0000000..eae1444
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FogState.java
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.record.FogStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>FogState</code> maintains the fog qualities for a node and it's children. The fogging function, color, start,
+ * end and density are all set and maintained. Please note that fog does not affect alpha.
+ */
+public class FogState extends RenderState {
+
+ public enum DensityFunction {
+ /**
+ * The fog blending function defined as: (end - z) / (end - start).
+ */
+ Linear,
+ /**
+ * The fog blending function defined as: e^-(density*z)
+ */
+ Exponential,
+ /**
+ * The fog blending function defined as: e^((-density*z)^2)
+ */
+ ExponentialSquared;
+ }
+
+ public enum CoordinateSource {
+ /** The source of the fogging value is based on the depth buffer */
+ Depth,
+ /** The source of the fogging value is based on the specified fog coordinates */
+ FogCoords
+ }
+
+ public enum Quality {
+ /**
+ * Each vertex color is altered by the fogging function.
+ */
+ PerVertex,
+ /**
+ * Each pixel color is altered by the fogging function.
+ */
+ PerPixel;
+ }
+
+ // fogging attributes.
+ protected float start = 0;
+ protected float end = 1;
+ protected float density = 1.0f;
+ protected final ColorRGBA color = new ColorRGBA();
+ protected DensityFunction densityFunction = DensityFunction.Exponential;
+ protected Quality quality = Quality.PerVertex;
+ protected CoordinateSource source = CoordinateSource.Depth;
+
+ /**
+ * Constructor instantiates a new <code>FogState</code> with default fog values.
+ */
+ public FogState() {}
+
+ /**
+ * <code>setQuality</code> sets the quality used for the fog attributes.
+ *
+ * @param quality
+ * the quality used for the fog application.
+ * @throws IllegalArgumentException
+ * if quality is null
+ */
+ public void setQuality(final Quality quality) {
+ if (quality == null) {
+ throw new IllegalArgumentException("quality can not be null.");
+ }
+ this.quality = quality;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setDensityFunction</code> sets the density function used for the fog blending.
+ *
+ * @param function
+ * the function used for the fog density.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setDensityFunction(final DensityFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ densityFunction = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setColor</code> sets the color of the fog.
+ *
+ * @param color
+ * the color of the fog. This value is COPIED into the state. Further changes to the object after calling
+ * this method will have no affect on this state.
+ */
+ public void setColor(final ReadOnlyColorRGBA color) {
+ this.color.set(color);
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setDensity</code> sets the density of the fog. This value is clamped to [0, 1].
+ *
+ * @param density
+ * the density of the fog.
+ */
+ public void setDensity(float density) {
+ if (density < 0) {
+ density = 0;
+ }
+
+ if (density > 1) {
+ density = 1;
+ }
+ this.density = density;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setEnd</code> sets the end distance, or the distance where fog is at it's thickest.
+ *
+ * @param end
+ * the distance where the fog is the thickest.
+ */
+ public void setEnd(final float end) {
+ this.end = end;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>setStart</code> sets the start distance, or where fog begins to be applied.
+ *
+ * @param start
+ * the start distance of the fog.
+ */
+ public void setStart(final float start) {
+ this.start = start;
+ setNeedsRefresh(true);
+ }
+
+ public void setSource(final CoordinateSource source) {
+ this.source = source;
+ }
+
+ public CoordinateSource getSource() {
+ return source;
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Fog;
+ }
+
+ public Quality getQuality() {
+ return quality;
+ }
+
+ public ReadOnlyColorRGBA getColor() {
+ return color;
+ }
+
+ public float getDensity() {
+ return density;
+ }
+
+ public DensityFunction getDensityFunction() {
+ return densityFunction;
+ }
+
+ public float getEnd() {
+ return end;
+ }
+
+ public float getStart() {
+ return start;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(start, "start", 0);
+ capsule.write(end, "end", 0);
+ capsule.write(density, "density", 0);
+ capsule.write(color, "color", new ColorRGBA(ColorRGBA.WHITE));
+ capsule.write(densityFunction, "densityFunction", DensityFunction.Exponential);
+ capsule.write(quality, "applyFunction", Quality.PerPixel);
+ capsule.write(source, "source", CoordinateSource.Depth);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ start = capsule.readFloat("start", 0);
+ end = capsule.readFloat("end", 0);
+ density = capsule.readFloat("density", 0);
+ color.set((ColorRGBA) capsule.readSavable("color", new ColorRGBA(ColorRGBA.WHITE)));
+ densityFunction = capsule.readEnum("densityFunction", DensityFunction.class, DensityFunction.Exponential);
+ quality = capsule.readEnum("applyFunction", Quality.class, Quality.PerPixel);
+ source = capsule.readEnum("source", CoordinateSource.class, CoordinateSource.Depth);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new FogStateRecord();
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java
new file mode 100644
index 0000000..7646547
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/FragmentProgramState.java
@@ -0,0 +1,231 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.renderer.state.record.FragmentProgramStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class FragmentProgramState extends RenderState {
+ private static final Logger logger = Logger.getLogger(FragmentProgramState.class.getName());
+
+ /** If any local parameters for this FP state are set */
+ protected boolean usingParameters = false;
+
+ /** Parameters local to this fragment program */
+ protected float[][] parameters;
+ protected ByteBuffer program;
+ protected int _programID = -1;
+
+ /**
+ * <code>setEnvParameter</code> sets an environmental fragment program parameter that is accessable by all fragment
+ * programs in memory.
+ *
+ * @param param
+ * four-element array of floating point numbers
+ * @param paramID
+ * identity number of the parameter, ranging from 0 to 95
+ */
+ // TODO: Reevaluate how this is done.
+ /*
+ * public static void setEnvParameter(float[] param, int paramID){ if (paramID < 0 || paramID > 95) throw new
+ * IllegalArgumentException("Invalid parameter ID"); if (param != null && param.length != 4) throw new
+ * IllegalArgumentException("Vertex program parameters must be of type float[4]");
+ *
+ * envparameters[paramID] = param; }
+ */
+
+ public FragmentProgramState() {
+ parameters = new float[24][];
+ }
+
+ /**
+ * <code>setParameter</code> sets a parameter for this fragment program.
+ *
+ * @param paramID
+ * identity number of the parameter, ranging from 0 to 23
+ * @param param
+ * four-element array of floating point numbers
+ */
+ public void setParameter(final float[] param, final int paramID) {
+ if (paramID < 0 || paramID > 23) {
+ throw new IllegalArgumentException("Invalid parameter ID");
+ }
+ if (param != null && param.length != 4) {
+ throw new IllegalArgumentException("Fragment program parameters must be of type float[4]");
+ }
+
+ usingParameters = true;
+ parameters[paramID] = param;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.FragmentProgram;
+ }
+
+ /**
+ * Loads the fragment program into a byte array.
+ *
+ * @see com.ardor3d.renderer.state.FragmentProgramState#load(java.net.URL)
+ */
+ public void load(final java.net.URL file) {
+ InputStream inputStream = null;
+ try {
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16 * 1024);
+ inputStream = new BufferedInputStream(file.openStream());
+ final byte[] buffer = new byte[1024];
+ int byteCount = -1;
+
+ // Read the byte content into the output stream first
+ while ((byteCount = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, byteCount);
+ }
+
+ // Set data with byte content from stream
+ final byte data[] = outputStream.toByteArray();
+
+ // Release resources
+ inputStream.close();
+ outputStream.close();
+
+ program = BufferUtils.createByteBuffer(data.length);
+ program.put(data);
+ program.rewind();
+ _programID = -1;
+ setNeedsRefresh(true);
+ } catch (final Exception e) {
+ logger.severe("Could not load fragment program: " + e);
+ logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e);
+ } finally {
+ // Ensure that the stream is closed, even if there is an exception.
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException closeFailure) {
+ logger.log(Level.WARNING, "Failed to close the fragment program", closeFailure);
+ }
+ }
+ }
+ }
+
+ /**
+ * Loads the fragment program into a byte array.
+ *
+ * @see com.ardor3d.renderer.state.FragmentProgramState#load(java.net.URL)
+ */
+ public void load(final String programContents) {
+ try {
+ final byte[] bytes = programContents.getBytes();
+ program = BufferUtils.createByteBuffer(bytes.length);
+ program.put(bytes);
+ program.rewind();
+ _programID = -1;
+ setNeedsRefresh(true);
+ } catch (final Exception e) {
+ logger.severe("Could not load fragment program: " + e);
+ logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e);
+ }
+ }
+
+ public ByteBuffer getProgramAsBuffer() {
+ return program;
+ }
+
+ public int _getProgramID() {
+ return _programID;
+ }
+
+ public void _setProgramID(final int id) {
+ _programID = id;
+ }
+
+ public boolean isUsingParameters() {
+ return usingParameters;
+ }
+
+ public float[][] _getParameters() {
+ return parameters;
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @see java.io.Serializable
+ */
+ private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
+ s.defaultWriteObject();
+ if (program == null) {
+ s.writeInt(0);
+ } else {
+ s.writeInt(program.capacity());
+ program.rewind();
+ for (int x = 0, len = program.capacity(); x < len; x++) {
+ s.writeByte(program.get());
+ }
+ }
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @see java.io.Serializable
+ */
+ private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ final int len = s.readInt();
+ if (len == 0) {
+ program = null;
+ } else {
+ program = BufferUtils.createByteBuffer(len);
+ for (int x = 0; x < len; x++) {
+ program.put(s.readByte());
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(usingParameters, "usingParameters", false);
+ capsule.write(parameters, "parameters", new float[24][]);
+ capsule.write(program, "program", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ usingParameters = capsule.readBoolean("usingParameters", false);
+ parameters = capsule.readFloatArray2D("parameters", new float[24][]);
+ program = capsule.readByteBuffer("program", null);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new FragmentProgramStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java
new file mode 100644
index 0000000..a2a5036
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderDataLogic.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * Logic responsible for transferring data from a geometry to a shader before rendering
+ */
+public interface GLSLShaderDataLogic {
+ /**
+ * Responsible for transferring data from a Mesh object to a shader before rendering
+ *
+ * @param shader
+ * Shader to update with new data(setUniform/setAttribute)
+ * @param meshData
+ * MeshData to retrieve data from
+ * @param renderer
+ * Current renderer
+ */
+ void applyData(GLSLShaderObjectsState shader, Mesh mesh, Renderer renderer);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java
new file mode 100644
index 0000000..dcbacf4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/GLSLShaderObjectsState.java
@@ -0,0 +1,1333 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyQuaternion;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.math.type.ReadOnlyVector4;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.state.record.ShaderObjectsStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.scenegraph.ByteBufferData;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.IntBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.ShortBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.shader.ShaderVariable;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat2;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat3;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloat4;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableFloatArray;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt2;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt3;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableInt4;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableIntArray;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix3;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix4;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariableMatrix4Array;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerByte;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerFloat;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerFloatMatrix;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerInt;
+import com.ardor3d.util.shader.uniformtypes.ShaderVariablePointerShort;
+
+/**
+ * Implementation of the GL_ARB_shader_objects extension.
+ */
+public class GLSLShaderObjectsState extends RenderState {
+ private static final Logger logger = Logger.getLogger(GLSLShaderObjectsState.class.getName());
+
+ /** Storage for shader uniform values */
+ protected List<ShaderVariable> _shaderUniforms = new ArrayList<ShaderVariable>();
+ /** Storage for shader attribute values */
+ protected List<ShaderVariable> _shaderAttributes = new ArrayList<ShaderVariable>();
+
+ protected ByteBuffer _vertShader, _fragShader, _geomShader, _tessControlShader, _tessEvalShader;
+
+ // XXX: The below fields are public for brevity mostly as a way to remember that this class needs revisiting.
+
+ /**
+ * Optional logic for setting shadervariables based on the current geom. Note: If this object does not implement
+ * Savable, it will be ignored during write.
+ */
+ public GLSLShaderDataLogic _shaderDataLogic;
+
+ /** The Mesh this shader currently operates on during rendering */
+ public Mesh _mesh;
+
+ public boolean _needSendShader = true;
+
+ /** OpenGL id for this program. * */
+ public int _programID = -1;
+
+ /** OpenGL id for the attached vertex shader. */
+ public int _vertexShaderID = -1;
+
+ /** OpenGL id for the attached fragment shader. */
+ public int _fragmentShaderID = -1;
+
+ /** OpenGL id for the attached geometry shader. */
+ public int _geometryShaderID = -1;
+
+ /** OpenGL id for the attached tessellation control shader. */
+ public int _tessellationControlShaderID = -1;
+
+ /** OpenGL id for the attached tessellation evaluation shader. */
+ public int _tessellationEvaluationShaderID = -1;
+
+ /** if true, we'll send our vertex attributes to the shader via vbo */
+ private boolean _useAttributeVBO;
+
+ /** optional name for our vertex shader, used for debugging details. */
+ public String _vertexShaderName;
+
+ /** optional name for our fragment shader, used for debugging details. */
+ public String _fragmentShaderName;
+
+ /** optional name for our geometry shader, used for debugging details. */
+ public String _geometryShaderName;
+
+ /** optional name for our tessellation control shader, used for debugging details. */
+ public String _tessellationControlShaderName;
+
+ /** optional name for our tessellation evaluation shader, used for debugging details. */
+ public String _tessellationEvaluationShaderName;
+
+ /**
+ * Gets the currently loaded vertex shader.
+ *
+ * @return
+ */
+ public ByteBuffer getVertexShader() {
+ return _vertShader;
+ }
+
+ /**
+ * Gets the currently loaded fragment shader.
+ *
+ * @return
+ */
+ public ByteBuffer getFragmentShader() {
+ return _fragShader;
+ }
+
+ /**
+ * Gets the currently loaded geometry shader.
+ *
+ * @return
+ */
+ public ByteBuffer getGeometryShader() {
+ return _geomShader;
+ }
+
+ /**
+ * Gets the currently loaded tessellation control shader.
+ *
+ * @return
+ */
+ public ByteBuffer getTessellationControlShader() {
+ return _tessControlShader;
+ }
+
+ /**
+ * Gets the currently loaded tessellation evaluation shader.
+ *
+ * @return
+ */
+ public ByteBuffer getTessellationEvaluationShader() {
+ return _tessEvalShader;
+ }
+
+ public void setVertexShader(final InputStream stream) throws IOException {
+ setVertexShader(stream, "");
+ }
+
+ public void setVertexShader(final InputStream stream, final String name) throws IOException {
+ setVertexShader(load(stream));
+ _vertexShaderName = name;
+ }
+
+ public void setFragmentShader(final InputStream stream) throws IOException {
+ setFragmentShader(stream, "");
+ }
+
+ public void setFragmentShader(final InputStream stream, final String name) throws IOException {
+ setFragmentShader(load(stream));
+ _fragmentShaderName = name;
+ }
+
+ public void setGeometryShader(final InputStream stream) throws IOException {
+ setGeometryShader(stream, "");
+ }
+
+ public void setGeometryShader(final InputStream stream, final String name) throws IOException {
+ setGeometryShader(load(stream));
+ _geometryShaderName = name;
+ }
+
+ public void setTessellationControlShader(final InputStream stream) throws IOException {
+ setTessellationControlShader(stream, "");
+ }
+
+ public void setTessellationControlShader(final InputStream stream, final String name) throws IOException {
+ setTessellationControlShader(load(stream));
+ _tessellationControlShaderName = name;
+ }
+
+ public void setTessellationEvaluationShader(final InputStream stream) throws IOException {
+ setTessellationEvaluationShader(stream, "");
+ }
+
+ public void setTessellationEvaluationShader(final InputStream stream, final String name) throws IOException {
+ setTessellationEvaluationShader(load(stream));
+ _tessellationEvaluationShaderName = name;
+ }
+
+ protected ByteBuffer load(final InputStream in) throws IOException {
+ DataInputStream dataStream = null;
+ try {
+ final BufferedInputStream bufferedInputStream = new BufferedInputStream(in);
+ dataStream = new DataInputStream(bufferedInputStream);
+ final byte shaderCode[] = new byte[bufferedInputStream.available()];
+ dataStream.readFully(shaderCode);
+ bufferedInputStream.close();
+ dataStream.close();
+ final ByteBuffer shaderByteBuffer = BufferUtils.createByteBuffer(shaderCode.length);
+ shaderByteBuffer.put(shaderCode);
+ shaderByteBuffer.rewind();
+
+ return shaderByteBuffer;
+ } finally {
+ // Ensure that the stream is closed, even if there is an exception.
+ if (dataStream != null) {
+ try {
+ dataStream.close();
+ } catch (final IOException closeFailure) {
+ logger.log(Level.WARNING, "Failed to close the shader object", closeFailure);
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the contents for our vertex shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setVertexShader(final ByteBuffer shader) {
+ setVertexShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our vertex shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setVertexShader(final ByteBuffer shader, final String name) {
+ _vertShader = shader;
+ _vertexShaderName = name;
+ }
+
+ /**
+ * Set the contents for our fragment shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setFragmentShader(final ByteBuffer shader) {
+ setFragmentShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our fragment shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setFragmentShader(final ByteBuffer shader, final String name) {
+ _fragShader = shader;
+ _fragmentShaderName = name;
+ }
+
+ /**
+ * Set the contents for our geometry shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setGeometryShader(final ByteBuffer shader) {
+ setGeometryShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our geometry shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setGeometryShader(final ByteBuffer shader, final String name) {
+ _geomShader = shader;
+ _geometryShaderName = name;
+ }
+
+ /**
+ * Set the contents for our tessellation control shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setTessellationControlShader(final ByteBuffer shader) {
+ setTessellationControlShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our tessellation control shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setTessellationControlShader(final ByteBuffer shader, final String name) {
+ _tessControlShader = shader;
+ _tessellationControlShaderName = name;
+ }
+
+ /**
+ * Set the contents for our tessellation evaluation shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setTessellationEvaluationShader(final ByteBuffer shader) {
+ setTessellationEvaluationShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our tessellation evaluation shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setTessellationEvaluationShader(final ByteBuffer shader, final String name) {
+ _tessEvalShader = shader;
+ _tessellationEvaluationShaderName = name;
+ }
+
+ /**
+ * Set the contents for our vertex shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setVertexShader(final String shader) {
+ setVertexShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our vertex shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setVertexShader(final String shader, final String name) {
+ _vertShader = stringToByteBuffer(shader);
+ _vertexShaderName = name;
+ }
+
+ /**
+ * Set the contents for our fragment shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setFragmentShader(final String shader) {
+ setFragmentShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our fragment shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setFragmentShader(final String shader, final String name) {
+ _fragShader = stringToByteBuffer(shader);
+ _fragmentShaderName = name;
+ }
+
+ /**
+ * Set the contents for our geometry shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setGeometryShader(final String shader) {
+ setGeometryShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our geometry shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setGeometryShader(final String shader, final String name) {
+ _geomShader = stringToByteBuffer(shader);
+ _geometryShaderName = name;
+ }
+
+ /**
+ * Set the contents for our tessellation control shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setTessellationControlShader(final String shader) {
+ setTessellationControlShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our tessellation control shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setTessellationControlShader(final String shader, final String name) {
+ _tessControlShader = stringToByteBuffer(shader);
+ _tessellationControlShaderName = name;
+ }
+
+ /**
+ * Set the contents for our tessellation evaluation shader
+ *
+ * @param shader
+ * the shader contents.
+ */
+ public void setTessellationEvaluationShader(final String shader) {
+ setTessellationEvaluationShader(shader, "");
+ }
+
+ /**
+ * Set the contents for our tessellation evaluation shader
+ *
+ * @param shader
+ * the shader contents.
+ * @param name
+ * a label for this shader, displayer upon shader errors.
+ */
+ public void setTessellationEvaluationShader(final String shader, final String name) {
+ _tessEvalShader = stringToByteBuffer(shader);
+ _tessellationEvaluationShaderName = name;
+ }
+
+ private ByteBuffer stringToByteBuffer(final String str) {
+ final byte[] bytes = str.getBytes();
+ final ByteBuffer buf = BufferUtils.createByteBuffer(bytes.length);
+ buf.put(bytes);
+ buf.rewind();
+ return buf;
+ }
+
+ /**
+ * Gets all shader uniforms variables.
+ *
+ * @return
+ */
+ public List<ShaderVariable> getShaderUniforms() {
+ return _shaderUniforms;
+ }
+
+ /**
+ * Retrieves a shader uniform by name.
+ *
+ * @param uniformName
+ * @return
+ */
+ public ShaderVariable getUniformByName(final String uniformName) {
+ for (final ShaderVariable shaderVar : _shaderUniforms) {
+ if (shaderVar.name.equals(uniformName)) {
+ return shaderVar;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets all shader attribute variables.
+ *
+ * @return
+ */
+ public List<ShaderVariable> getShaderAttributes() {
+ return _shaderAttributes;
+ }
+
+ /**
+ * Retrieves a shader attribute by name.
+ *
+ * @param uniformName
+ * @return
+ */
+ public ShaderVariable getAttributeByName(final String attributeName) {
+ for (final ShaderVariable shaderVar : _shaderAttributes) {
+ if (shaderVar.name.equals(attributeName)) {
+ return shaderVar;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ *
+ * @param meshData
+ */
+ public void setMesh(final Mesh mesh) {
+ _mesh = mesh;
+ }
+
+ /**
+ * Logic to handle setting mesh-specific data to a shader before rendering
+ *
+ * @param shaderDataLogic
+ */
+ public void setShaderDataLogic(final GLSLShaderDataLogic shaderDataLogic) {
+ _shaderDataLogic = shaderDataLogic;
+ }
+
+ public GLSLShaderDataLogic getShaderDataLogic() {
+ return _shaderDataLogic;
+ }
+
+ public boolean isUseAttributeVBO() {
+ return _useAttributeVBO;
+ }
+
+ /**
+ * @param useAttributeVBO
+ * if true, and we support VBO, we'll use VBO for shader attributes.
+ */
+ public void setUseAttributeVBO(final boolean useAttributeVBO) {
+ _useAttributeVBO = useAttributeVBO;
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final boolean value) {
+ final ShaderVariableInt shaderUniform = getShaderUniform(name, ShaderVariableInt.class);
+ shaderUniform.value1 = value ? 1 : 0;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final int value) {
+ final ShaderVariableInt shaderUniform = getShaderUniform(name, ShaderVariableInt.class);
+ shaderUniform.value1 = value;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final float value) {
+ final ShaderVariableFloat shaderUniform = getShaderUniform(name, ShaderVariableFloat.class);
+ shaderUniform.value1 = value;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ */
+ public void setUniform(final String name, final boolean value1, final boolean value2) {
+ final ShaderVariableInt2 shaderUniform = getShaderUniform(name, ShaderVariableInt2.class);
+ shaderUniform.value1 = value1 ? 1 : 0;
+ shaderUniform.value2 = value2 ? 1 : 0;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ */
+ public void setUniform(final String name, final int value1, final int value2) {
+ final ShaderVariableInt2 shaderUniform = getShaderUniform(name, ShaderVariableInt2.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ */
+ public void setUniform(final String name, final float value1, final float value2) {
+ final ShaderVariableFloat2 shaderUniform = getShaderUniform(name, ShaderVariableFloat2.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ */
+ public void setUniform(final String name, final boolean value1, final boolean value2, final boolean value3) {
+ final ShaderVariableInt3 shaderUniform = getShaderUniform(name, ShaderVariableInt3.class);
+ shaderUniform.value1 = value1 ? 1 : 0;
+ shaderUniform.value2 = value2 ? 1 : 0;
+ shaderUniform.value3 = value3 ? 1 : 0;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ */
+ public void setUniform(final String name, final int value1, final int value2, final int value3) {
+ final ShaderVariableInt3 shaderUniform = getShaderUniform(name, ShaderVariableInt3.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+ shaderUniform.value3 = value3;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ */
+ public void setUniform(final String name, final float value1, final float value2, final float value3) {
+ final ShaderVariableFloat3 shaderUniform = getShaderUniform(name, ShaderVariableFloat3.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+ shaderUniform.value3 = value3;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ * @param value4
+ * the new value
+ */
+ public void setUniform(final String name, final boolean value1, final boolean value2, final boolean value3,
+ final boolean value4) {
+ final ShaderVariableInt4 shaderUniform = getShaderUniform(name, ShaderVariableInt4.class);
+ shaderUniform.value1 = value1 ? 1 : 0;
+ shaderUniform.value2 = value2 ? 1 : 0;
+ shaderUniform.value3 = value3 ? 1 : 0;
+ shaderUniform.value4 = value4 ? 1 : 0;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ * @param value4
+ * the new value
+ */
+ public void setUniform(final String name, final int value1, final int value2, final int value3, final int value4) {
+ final ShaderVariableInt4 shaderUniform = getShaderUniform(name, ShaderVariableInt4.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+ shaderUniform.value3 = value3;
+ shaderUniform.value4 = value4;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value1
+ * the new value
+ * @param value2
+ * the new value
+ * @param value3
+ * the new value
+ * @param value4
+ * the new value
+ */
+ public void setUniform(final String name, final float value1, final float value2, final float value3,
+ final float value4) {
+ final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class);
+ shaderUniform.value1 = value1;
+ shaderUniform.value2 = value2;
+ shaderUniform.value3 = value3;
+ shaderUniform.value4 = value4;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new float data
+ * @param size
+ * the number of components per entry (must be 1, 2, 3, or 4)
+ */
+ public void setUniform(final String name, final FloatBuffer value, final int size) {
+ assert (size >= 1 && size <= 4) : "Size must be 1, 2, 3 or 4";
+ final ShaderVariableFloatArray shaderUniform = getShaderUniform(name, ShaderVariableFloatArray.class);
+ shaderUniform.value = value;
+ shaderUniform.size = size;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final float[] value) {
+ final ShaderVariableFloatArray shaderUniform = getShaderUniform(name, ShaderVariableFloatArray.class);
+ shaderUniform.value = BufferUtils.createFloatBuffer(value);
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final IntBuffer value) {
+ final ShaderVariableIntArray shaderUniform = getShaderUniform(name, ShaderVariableIntArray.class);
+ shaderUniform.value = value;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final int[] value) {
+ final ShaderVariableIntArray shaderUniform = getShaderUniform(name, ShaderVariableIntArray.class);
+ shaderUniform.value = BufferUtils.createIntBuffer(value);
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final ReadOnlyVector2 value) {
+ final ShaderVariableFloat2 shaderUniform = getShaderUniform(name, ShaderVariableFloat2.class);
+ shaderUniform.value1 = (float) value.getX();
+ shaderUniform.value2 = (float) value.getY();
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final ReadOnlyVector3 value) {
+ final ShaderVariableFloat3 shaderUniform = getShaderUniform(name, ShaderVariableFloat3.class);
+ shaderUniform.value1 = (float) value.getX();
+ shaderUniform.value2 = (float) value.getY();
+ shaderUniform.value3 = (float) value.getZ();
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final ReadOnlyVector4 value) {
+ final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class);
+ shaderUniform.value1 = (float) value.getX();
+ shaderUniform.value2 = (float) value.getY();
+ shaderUniform.value3 = (float) value.getZ();
+ shaderUniform.value4 = (float) value.getW();
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final ReadOnlyColorRGBA value) {
+ final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class);
+ shaderUniform.value1 = value.getRed();
+ shaderUniform.value2 = value.getGreen();
+ shaderUniform.value3 = value.getBlue();
+ shaderUniform.value4 = value.getAlpha();
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ */
+ public void setUniform(final String name, final ReadOnlyQuaternion value) {
+ final ShaderVariableFloat4 shaderUniform = getShaderUniform(name, ShaderVariableFloat4.class);
+ shaderUniform.value1 = (float) value.getX();
+ shaderUniform.value2 = (float) value.getY();
+ shaderUniform.value3 = (float) value.getZ();
+ shaderUniform.value4 = (float) value.getW();
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ * @param rowMajor
+ * true if is this in row major order
+ */
+ public void setUniform(final String name, final ReadOnlyMatrix3 value, final boolean rowMajor) {
+ final ShaderVariableMatrix3 shaderUniform = getShaderUniform(name, ShaderVariableMatrix3.class);
+ // prepare buffer for writing
+ shaderUniform.matrixBuffer.rewind();
+ value.toFloatBuffer(shaderUniform.matrixBuffer);
+ // prepare buffer for reading
+ shaderUniform.matrixBuffer.rewind();
+ shaderUniform.rowMajor = rowMajor;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ * @param rowMajor
+ * true if is this in row major order
+ */
+ public void setUniform(final String name, final ReadOnlyMatrix4 value, final boolean rowMajor) {
+ final ShaderVariableMatrix4 shaderUniform = getShaderUniform(name, ShaderVariableMatrix4.class);
+ // prepare buffer for writing
+ shaderUniform.matrixBuffer.rewind();
+ value.toFloatBuffer(shaderUniform.matrixBuffer);
+ // prepare buffer for reading
+ shaderUniform.matrixBuffer.rewind();
+ shaderUniform.rowMajor = rowMajor;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform Matrix4 variable to change
+ * @param value
+ * the new value, assumed row major
+ */
+ public void setUniformMatrix4(final String name, final FloatBuffer value) {
+ final ShaderVariableMatrix4 shaderUniform = getShaderUniform(name, ShaderVariableMatrix4.class);
+ // prepare buffer for writing
+ shaderUniform.matrixBuffer.rewind();
+ shaderUniform.matrixBuffer.put(value);
+ // prepare buffer for reading
+ shaderUniform.matrixBuffer.rewind();
+ value.rewind();
+ shaderUniform.rowMajor = true;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an uniform value for this shader object.
+ *
+ * @param name
+ * uniform variable to change
+ * @param value
+ * the new value
+ * @param rowMajor
+ * true if is this in row major order
+ */
+ public void setUniform(final String name, final ReadOnlyMatrix4[] values, final boolean rowMajor) {
+ final ShaderVariableMatrix4Array shaderUniform = getShaderUniform(name, ShaderVariableMatrix4Array.class);
+ // prepare buffer for writing
+ FloatBuffer matrixBuffer = shaderUniform.matrixBuffer;
+ if (matrixBuffer == null || matrixBuffer.capacity() < values.length * 16) {
+ matrixBuffer = BufferUtils.createFloatBuffer(values.length * 16);
+ shaderUniform.matrixBuffer = matrixBuffer;
+ }
+
+ matrixBuffer.clear();
+ for (final ReadOnlyMatrix4 value : values) {
+ value.toFloatBuffer(matrixBuffer);
+ }
+ matrixBuffer.flip();
+
+ // prepare buffer for reading
+ shaderUniform.rowMajor = rowMajor;
+
+ setNeedsRefresh(true);
+ }
+
+ /** <code>clearUniforms</code> clears all uniform values from this state. */
+ public void clearUniforms() {
+ _shaderUniforms.clear();
+ }
+
+ /**
+ * Set an attribute pointer value for this shader object.
+ *
+ * @param name
+ * attribute variable to change
+ * @param size
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2,
+ * 3, or 4.
+ * @param normalized
+ * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point
+ * values when they are accessed.
+ * @param stride
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value),
+ * the attribute values are understood to be tightly packed in the array.
+ * @param data
+ * The actual data to use as attribute pointer
+ */
+ public void setAttributePointer(final String name, final int size, final boolean normalized, final int stride,
+ final FloatBufferData data) {
+ final ShaderVariablePointerFloat shaderUniform = getShaderAttribute(name, ShaderVariablePointerFloat.class);
+ shaderUniform.size = size;
+ shaderUniform.normalized = normalized;
+ shaderUniform.stride = stride;
+ shaderUniform.data = data;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an attribute pointer value for this shader object.
+ *
+ * @param name
+ * attribute variable to change
+ * @param size
+ * the number of rows and cols in the matrix. Must be 2, 3, or 4.
+ * @param normalized
+ * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point
+ * values when they are accessed.
+ * @param data
+ * The actual data to use as attribute pointer
+ */
+ public void setAttributePointerMatrix(final String name, final int size, final boolean normalized,
+ final FloatBufferData data) {
+ final ShaderVariablePointerFloatMatrix shaderUniform = getShaderAttribute(name,
+ ShaderVariablePointerFloatMatrix.class);
+ shaderUniform.size = size;
+ shaderUniform.normalized = normalized;
+ shaderUniform.data = data;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an attribute pointer value for this shader object.
+ *
+ * @param name
+ * attribute variable to change
+ * @param size
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2,
+ * 3, or 4.
+ * @param normalized
+ * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point
+ * values when they are accessed.
+ * @param unsigned
+ * Specifies wheter the data is signed or unsigned
+ * @param stride
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value),
+ * the attribute values are understood to be tightly packed in the array.
+ * @param data
+ * The actual data to use as attribute pointer
+ */
+ public void setAttributePointer(final String name, final int size, final boolean normalized,
+ final boolean unsigned, final int stride, final ByteBufferData data) {
+ final ShaderVariablePointerByte shaderUniform = getShaderAttribute(name, ShaderVariablePointerByte.class);
+ shaderUniform.size = size;
+ shaderUniform.normalized = normalized;
+ shaderUniform.unsigned = unsigned;
+ shaderUniform.stride = stride;
+ shaderUniform.data = data;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an attribute pointer value for this shader object.
+ *
+ * @param name
+ * attribute variable to change
+ * @param size
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2,
+ * 3, or 4.
+ * @param normalized
+ * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point
+ * values when they are accessed.
+ * @param unsigned
+ * Specifies wheter the data is signed or unsigned
+ * @param stride
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value),
+ * the attribute values are understood to be tightly packed in the array.
+ * @param data
+ * The actual data to use as attribute pointer
+ */
+ public void setAttributePointer(final String name, final int size, final boolean normalized,
+ final boolean unsigned, final int stride, final IntBufferData data) {
+ final ShaderVariablePointerInt shaderUniform = getShaderAttribute(name, ShaderVariablePointerInt.class);
+ shaderUniform.size = size;
+ shaderUniform.normalized = normalized;
+ shaderUniform.unsigned = unsigned;
+ shaderUniform.stride = stride;
+ shaderUniform.data = data;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Set an attribute pointer value for this shader object.
+ *
+ * @param name
+ * attribute variable to change
+ * @param size
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2,
+ * 3, or 4.
+ * @param normalized
+ * Specifies whether fixed-point data values should be normalized or converted directly as fixed-point
+ * values when they are accessed.
+ * @param unsigned
+ * Specifies wheter the data is signed or unsigned
+ * @param stride
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value),
+ * the attribute values are understood to be tightly packed in the array.
+ * @param data
+ * The actual data to use as attribute pointer
+ */
+ public void setAttributePointer(final String name, final int size, final boolean normalized,
+ final boolean unsigned, final int stride, final ShortBufferData data) {
+ final ShaderVariablePointerShort shaderUniform = getShaderAttribute(name, ShaderVariablePointerShort.class);
+ shaderUniform.size = size;
+ shaderUniform.normalized = normalized;
+ shaderUniform.unsigned = unsigned;
+ shaderUniform.stride = stride;
+ shaderUniform.data = data;
+
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>clearAttributes</code> clears all attribute values from this state.
+ */
+ public void clearAttributes() {
+ _shaderAttributes.clear();
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.GLSLShader;
+ }
+
+ /**
+ * Creates or retrieves a uniform shadervariable.
+ *
+ * @param name
+ * Name of the uniform shadervariable to retrieve or create
+ * @param classz
+ * Class type of the shadervariable
+ * @return
+ */
+ private <T extends ShaderVariable> T getShaderUniform(final String name, final Class<T> classz) {
+ final T shaderVariable = getShaderVariable(name, classz, _shaderUniforms);
+ return shaderVariable;
+ }
+
+ /**
+ * Creates or retrieves a attribute shadervariable.
+ *
+ * @param name
+ * Name of the attribute shadervariable to retrieve or create
+ * @param classz
+ * Class type of the shadervariable
+ * @return
+ */
+ private <T extends ShaderVariable> T getShaderAttribute(final String name, final Class<T> classz) {
+ final T shaderVariable = getShaderVariable(name, classz, _shaderAttributes);
+ checkAttributeSizeLimits();
+ return shaderVariable;
+ }
+
+ /**
+ * @param name
+ * Name of the shadervariable to retrieve or create
+ * @param classz
+ * Class type of the shadervariable
+ * @param shaderVariableList
+ * List retrieve shadervariable from
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ private <T extends ShaderVariable> T getShaderVariable(final String name, final Class<T> classz,
+ final List<ShaderVariable> shaderVariableList) {
+ for (int i = shaderVariableList.size(); --i >= 0;) {
+ final ShaderVariable temp = shaderVariableList.get(i);
+ if (name.equals(temp.name)) {
+ temp.needsRefresh = true;
+ return (T) temp;
+ }
+ }
+
+ try {
+ final T shaderUniform = classz.newInstance();
+ shaderUniform.name = name;
+ shaderVariableList.add(shaderUniform);
+
+ return shaderUniform;
+ } catch (final InstantiationException e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(),
+ "getShaderVariable(name, classz, shaderVariableList)", "Exception", e);
+ } catch (final IllegalAccessException e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(),
+ "getShaderVariable(name, classz, shaderVariableList)", "Exception", e);
+ }
+
+ return null;
+ }
+
+ /**
+ * Check if we are keeping the size limits in terms of attribute locations on the card.
+ */
+ public void checkAttributeSizeLimits() {
+ final RenderContext context = ContextManager.getCurrentContext();
+ final ContextCapabilities caps = context.getCapabilities();
+ if (_shaderAttributes.size() > caps.getMaxGLSLVertexAttributes()) {
+ logger.severe("Too many shader attributes(standard+defined): " + _shaderAttributes.size() + " maximum: "
+ + caps.getMaxGLSLVertexAttributes());
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.writeSavableList(_shaderUniforms, "shaderUniforms", new ArrayList<ShaderVariable>());
+ capsule.writeSavableList(_shaderAttributes, "shaderAttributes", new ArrayList<ShaderVariable>());
+ capsule.write(_vertShader, "vertShader", null);
+ capsule.write(_fragShader, "fragShader", null);
+ capsule.write(_geomShader, "geomShader", null);
+ capsule.write(_geomShader, "geomShader", null);
+ capsule.write(_tessControlShader, "tessControlShader", null);
+ capsule.write(_tessEvalShader, "tessEvalShader", null);
+ capsule.write(_useAttributeVBO, "useAttributeVBO", false);
+
+ if (_shaderDataLogic instanceof Savable) {
+ capsule.write((Savable) _shaderDataLogic, "shaderDataLogic", null);
+ }
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _shaderUniforms = capsule.readSavableList("shaderUniforms", new ArrayList<ShaderVariable>());
+ _shaderAttributes = capsule.readSavableList("shaderAttributes", new ArrayList<ShaderVariable>());
+ _vertShader = capsule.readByteBuffer("vertShader", null);
+ _fragShader = capsule.readByteBuffer("fragShader", null);
+ _geomShader = capsule.readByteBuffer("geomShader", null);
+ _tessControlShader = capsule.readByteBuffer("tessControlShader", null);
+ _tessEvalShader = capsule.readByteBuffer("tessEvalShader", null);
+ _useAttributeVBO = capsule.readBoolean("useAttributeVBO", false);
+
+ final Savable shaderDataLogic = capsule.readSavable("shaderDataLogic", null);
+ // only override set _shaderDataLogic if we have something in the capsule.
+ if (shaderDataLogic != null) {
+ if (shaderDataLogic instanceof GLSLShaderDataLogic) {
+ _shaderDataLogic = (GLSLShaderDataLogic) shaderDataLogic;
+ } else {
+ logger.warning("Deserialized shaderDataLogic is not of type GLSLShaderDataLogic. "
+ + shaderDataLogic.getClass().getName());
+ }
+ }
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new ShaderObjectsStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java
new file mode 100644
index 0000000..6f1648a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightState.java
@@ -0,0 +1,397 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import com.ardor3d.light.Light;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.record.LightStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>LightState</code> maintains a collection of lights up to the set number of maximum lights allowed. Any subclass
+ * of <code>Light</code> can be added to the light state. Each light is processed and used to modify the color of the
+ * scene.
+ */
+public class LightState extends RenderState {
+
+ /**
+ * Debug flag for turning off all lighting.
+ */
+ public static boolean LIGHTS_ENABLED = true;
+
+ /**
+ * defines the maximum number of lights that are allowed to be maintained at one time.
+ */
+ public static final int MAX_LIGHTS_ALLOWED = 8;
+
+ /**
+ * When applied to lightMask, implies ambient light should be set to 0 for this lightstate
+ */
+ public static final int MASK_AMBIENT = 1;
+
+ /**
+ * When applied to lightMask, implies diffuse light should be set to 0 for this lightstate
+ */
+ public static final int MASK_DIFFUSE = 2;
+
+ /**
+ * When applied to lightMask, implies specular light should be set to 0 for this lightstate
+ */
+ public static final int MASK_SPECULAR = 4;
+
+ /**
+ * When applied to lightMask, implies global ambient light should be set to 0 for this lightstate
+ */
+ public static final int MASK_GLOBALAMBIENT = 8;
+
+ // holds the lights
+ private List<Light> lightList;
+
+ // mask value - default is no masking
+ protected int lightMask = 0;
+
+ // mask value stored by pushLightMask, retrieved by popLightMask
+ protected int backLightMask = 0;
+
+ /** When true, both sides of the model will be lighted. */
+ protected boolean twoSidedOn = true;
+
+ protected ColorRGBA _globalAmbient = new ColorRGBA(DEFAULT_GLOBAL_AMBIENT);
+
+ public static final ReadOnlyColorRGBA DEFAULT_GLOBAL_AMBIENT = new ColorRGBA(0, 0, 0, 1);
+
+ /**
+ * When true, the eye position (as opposed to just the view direction) will be taken into account when computing
+ * specular reflections.
+ */
+ protected boolean localViewerOn;
+
+ /**
+ * When true, specular highlights will be computed separately and added to fragments after texturing.
+ */
+ protected boolean separateSpecularOn;
+
+ /**
+ * Constructor instantiates a new <code>LightState</code> object. Initially there are no lights set.
+ */
+ public LightState() {
+ lightList = new ArrayList<Light>();
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Light;
+ }
+
+ /**
+ *
+ * <code>attach</code> places a light in the queue to be processed. If there are already eight lights placed in the
+ * queue, the light is ignored and false is returned. Otherwise, true is returned to indicate success.
+ *
+ * @param light
+ * the light to add to the queue.
+ * @return true if the light was added successfully, false if there are already eight lights in the queue.
+ */
+ public boolean attach(final Light light) {
+ if (!lightList.contains(light)) {
+ lightList.add(light);
+ setNeedsRefresh(true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ * <code>detach</code> removes a light from the queue for processing.
+ *
+ * @param light
+ * the light to be removed.
+ */
+ public void detach(final Light light) {
+ lightList.remove(light);
+ setNeedsRefresh(true);
+ }
+
+ /**
+ *
+ * <code>detachAll</code> clears the queue of all lights to be processed.
+ *
+ */
+ public void detachAll() {
+ lightList.clear();
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Retrieves all lights handled by this LightState
+ *
+ * @return List of lights handled
+ */
+ public List<Light> getLightList() {
+ return lightList;
+ }
+
+ /**
+ *
+ * <code>get</code> retrieves a particular light defined by an index. If there exists no light at a particular
+ * index, null is returned.
+ *
+ * @param i
+ * the index to retrieve the light from the queue.
+ * @return the light at the given index, null if no light exists at this index.
+ */
+ public Light get(final int i) {
+ return lightList.get(i);
+ }
+
+ /**
+ *
+ * <code>getNumberOfChildren</code> returns the number of lights currently in the queue.
+ *
+ * @return the number of lights currently in the queue.
+ */
+ public int getNumberOfChildren() {
+ return lightList.size() > MAX_LIGHTS_ALLOWED ? MAX_LIGHTS_ALLOWED : lightList.size();
+ }
+
+ /**
+ * Sets if two sided lighting should be enabled for this LightState. Two sided lighting will cause the back of
+ * surfaces to be colored using the inverse of the surface normal as well as the Material properties set for
+ * MaterialFace.Back.
+ *
+ * @param twoSidedOn
+ * If true, two sided lighting is enabled.
+ */
+ public void setTwoSidedLighting(final boolean twoSidedOn) {
+ this.twoSidedOn = twoSidedOn;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Returns the current state of two sided lighting for this LightState. By default, it is off.
+ *
+ * @return True if two sided lighting is enabled.
+ */
+ public boolean getTwoSidedLighting() {
+ return twoSidedOn;
+ }
+
+ /**
+ * Sets if local viewer mode should be enabled for this LightState.
+ *
+ * @param localViewerOn
+ * If true, local viewer mode is enabled.
+ */
+ public void setLocalViewer(final boolean localViewerOn) {
+ this.localViewerOn = localViewerOn;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Returns the current state of local viewer mode for this LightState. By default, it is off.
+ *
+ * @return True if local viewer mode is enabled.
+ */
+ public boolean getLocalViewer() {
+ return localViewerOn;
+ }
+
+ /**
+ * Sets if separate specular mode should be enabled for this LightState.
+ *
+ * @param separateSpecularOn
+ * If true, separate specular mode is enabled.
+ */
+ public void setSeparateSpecular(final boolean separateSpecularOn) {
+ this.separateSpecularOn = separateSpecularOn;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Returns the current state of separate specular mode for this LightState. By default, it is off.
+ *
+ * @return True if separate specular mode is enabled.
+ */
+ public boolean getSeparateSpecular() {
+ return separateSpecularOn;
+ }
+
+ public void setGlobalAmbient(final ReadOnlyColorRGBA color) {
+ _globalAmbient.set(color);
+ setNeedsRefresh(true);
+ }
+
+ /**
+ *
+ * @param store
+ * @return
+ */
+ public ReadOnlyColorRGBA getGlobalAmbient() {
+ return _globalAmbient;
+ }
+
+ /**
+ * @return Returns the lightMask - default is 0 or not masked.
+ */
+ public int getLightMask() {
+ return lightMask;
+ }
+
+ /**
+ * <code>setLightMask</code> sets what attributes of this lightstate to apply as an int comprised of bitwise or'ed
+ * values.
+ *
+ * @param lightMask
+ * The lightMask to set.
+ */
+ public void setLightMask(final int lightMask) {
+ this.lightMask = lightMask;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Saves the light mask to a back store. That backstore is recalled with popLightMask. Despite the name, this is not
+ * a stack and additional pushes will simply overwrite the backstored value.
+ */
+ public void pushLightMask() {
+ backLightMask = lightMask;
+ }
+
+ /**
+ * Recalls the light mask from a back store or 0 if none was pushed.
+ *
+ * @see com.ardor3d.renderer.state.LightState#pushLightMask()
+ */
+ public void popLightMask() {
+ lightMask = backLightMask;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.writeSavableList(lightList, "lightList", new ArrayList<Light>());
+ capsule.write(lightMask, "lightMask", 0);
+ capsule.write(backLightMask, "backLightMask", 0);
+ capsule.write(twoSidedOn, "twoSidedOn", false);
+ capsule.write(_globalAmbient, "globalAmbient", new ColorRGBA(DEFAULT_GLOBAL_AMBIENT));
+ capsule.write(localViewerOn, "localViewerOn", false);
+ capsule.write(separateSpecularOn, "separateSpecularOn", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ lightList = capsule.readSavableList("lightList", new ArrayList<Light>());
+ lightMask = capsule.readInt("lightMask", 0);
+ backLightMask = capsule.readInt("backLightMask", 0);
+ twoSidedOn = capsule.readBoolean("twoSidedOn", false);
+ _globalAmbient = (ColorRGBA) capsule.readSavable("globalAmbient", new ColorRGBA(DEFAULT_GLOBAL_AMBIENT));
+ localViewerOn = capsule.readBoolean("localViewerOn", false);
+ separateSpecularOn = capsule.readBoolean("separateSpecularOn", false);
+ }
+
+ @Override
+ public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) {
+ if (spat == null) {
+ return stack.peek();
+ }
+
+ final LightCombineMode mode = spat.getSceneHints().getLightCombineMode();
+
+ final Mesh mesh = (Mesh) spat;
+ LightState lightState = mesh.getLightState();
+ if (lightState == null) {
+ lightState = new LightState();
+ mesh.setLightState(lightState);
+ }
+
+ lightState.detachAll();
+
+ if (mode == LightCombineMode.Replace || (mode != LightCombineMode.Off && stack.size() == 1)) {
+ // todo: use dummy state if off?
+
+ final LightState copyLightState = (LightState) stack.peek();
+ copyLightState(copyLightState, lightState);
+ } else {
+ // accumulate the lights in the stack into a single LightState object
+ final Object states[] = stack.toArray();
+ boolean foundEnabled = false;
+ switch (mode) {
+ case CombineClosest:
+ case CombineClosestEnabled:
+ for (int iIndex = states.length - 1; iIndex >= 0; iIndex--) {
+ final LightState pkLState = (LightState) states[iIndex];
+ if (!pkLState.isEnabled()) {
+ if (mode == LightCombineMode.CombineClosestEnabled) {
+ break;
+ }
+
+ continue;
+ }
+
+ foundEnabled = true;
+ copyLightState(pkLState, lightState);
+ }
+ break;
+ case CombineFirst:
+ for (int iIndex = 0, max = states.length; iIndex < max; iIndex++) {
+ final LightState pkLState = (LightState) states[iIndex];
+ if (!pkLState.isEnabled()) {
+ continue;
+ }
+
+ foundEnabled = true;
+ copyLightState(pkLState, lightState);
+ }
+ break;
+ case Off:
+ break;
+ }
+ lightState.setEnabled(foundEnabled);
+ }
+
+ return lightState;
+ }
+
+ private static void copyLightState(final LightState source, final LightState destination) {
+ destination.setTwoSidedLighting(source.getTwoSidedLighting());
+ destination.setLocalViewer(source.getLocalViewer());
+ destination.setSeparateSpecular(source.getSeparateSpecular());
+ destination.setEnabled(source.isEnabled());
+ destination.setGlobalAmbient(source.getGlobalAmbient());
+ destination.setLightMask(source.getLightMask());
+ destination.setNeedsRefresh(true);
+
+ for (int i = 0, maxL = source.getLightList().size(); i < maxL; i++) {
+ final Light pkLight = source.get(i);
+ if (pkLight != null) {
+ destination.attach(pkLight);
+ }
+ }
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new LightStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java
new file mode 100644
index 0000000..112a955
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/LightUtil.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.light.Light;
+import com.ardor3d.light.PointLight;
+import com.ardor3d.light.SpotLight;
+import com.ardor3d.math.Plane;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public abstract class LightUtil {
+ private static class LightComparator implements Comparator<Light> {
+ private Spatial _sp;
+
+ public void setSpatial(final Spatial sp) {
+ _sp = sp;
+ }
+
+ public int compare(final Light l1, final Light l2) {
+ final double v1 = getValueFor(l1, _sp.getWorldBound());
+ final double v2 = getValueFor(l2, _sp.getWorldBound());
+ final double cmp = v1 - v2;
+ if (0 > cmp) {
+ return 1;
+ } else if (0 < cmp) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ private static LightComparator lightComparator = new LightComparator();
+
+ public static void sort(final Mesh geometry, final List<Light> lights) {
+ lightComparator.setSpatial(geometry);
+ Collections.sort(lights, lightComparator);
+ }
+
+ protected static double getValueFor(final Light l, final BoundingVolume val) {
+ if (l == null || !l.isEnabled()) {
+ return 0;
+ } else if (l.getType() == Light.Type.Directional) {
+ return getColorValue(l);
+ } else if (l.getType() == Light.Type.Point) {
+ return getValueFor((PointLight) l, val);
+ } else if (l.getType() == Light.Type.Spot) {
+ return getValueFor((SpotLight) l, val);
+ }
+ // If a new type of light was added and this was not updated return .3
+ return .3;
+ }
+
+ protected static double getValueFor(final PointLight l, final BoundingVolume val) {
+ if (val == null) {
+ return 0;
+ }
+ if (l.isAttenuate()) {
+ final ReadOnlyVector3 location = l.getLocation();
+ final double dist = val.distanceTo(location);
+
+ final double color = getColorValue(l);
+ final double amlat = l.getConstant() + l.getLinear() * dist + l.getQuadratic() * dist * dist;
+
+ return color / amlat;
+ }
+
+ return getColorValue(l);
+ }
+
+ protected static double getValueFor(final SpotLight l, final BoundingVolume val) {
+ if (val == null) {
+ return 0;
+ }
+ final ReadOnlyVector3 direction = l.getDirection();
+ final ReadOnlyVector3 location = l.getLocation();
+ // direction is copied into Plane, not reused.
+ final Plane p = new Plane(direction, direction.dot(location));
+ if (val.whichSide(p) != Plane.Side.Inside) {
+ return getValueFor((PointLight) l, val);
+ }
+
+ return 0;
+ }
+
+ protected static double getColorValue(final Light l) {
+ return strength(l.getAmbient()) + strength(l.getDiffuse());
+ }
+
+ protected static double strength(final ReadOnlyColorRGBA color) {
+ return Math.sqrt(color.getRed() * color.getRed() + color.getGreen() * color.getGreen() + color.getBlue()
+ * color.getBlue());
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java
new file mode 100644
index 0000000..15eb46c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/MaterialState.java
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.record.MaterialStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * MaterialState defines parameters used in conjunction with the lighting model to produce a surface color. Please note
+ * therefore that this state has no effect if lighting is disabled. It is also worth noting that material properties set
+ * for Front face will in fact affect both front and back unless two sided lighting is enabled.
+ *
+ * @see LightState
+ * @see LightState#setTwoSidedLighting(boolean)
+ */
+public class MaterialState extends RenderState {
+
+ public enum ColorMaterial {
+ /** Mesh colors are ignored. This is default. */
+ None,
+
+ /** Mesh colors determine material ambient color. */
+ Ambient,
+
+ /** Mesh colors determine material diffuse color. */
+ Diffuse,
+
+ /** Mesh colors determine material ambient and diffuse colors. */
+ AmbientAndDiffuse,
+
+ /** Mesh colors determine material specular colors. */
+ Specular,
+
+ /** Mesh colors determine material emissive color. */
+ Emissive;
+ }
+
+ public enum MaterialFace {
+ /** Apply material property to front face only. */
+ Front,
+
+ /**
+ * Apply material property to back face only. Note that this only has an affect if two sided lighting is
+ * enabled.
+ */
+ Back,
+
+ /** Apply material property to front and back faces. */
+ FrontAndBack;
+ }
+
+ /** Default ambient color for all material states. (.2, .2, .2, 1) */
+ public static final ReadOnlyColorRGBA DEFAULT_AMBIENT = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f);
+
+ /** Default diffuse color for all material states. (.8, .8, .8, 1) */
+ public static final ReadOnlyColorRGBA DEFAULT_DIFFUSE = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f);
+
+ /** Default specular color for all material states. (0, 0, 0, 1) */
+ public static final ReadOnlyColorRGBA DEFAULT_SPECULAR = new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f);
+
+ /** Default emissive color for all material states. (0, 0, 0, 1) */
+ public static final ReadOnlyColorRGBA DEFAULT_EMISSIVE = new ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f);
+
+ /** Default shininess for all material states. */
+ public static final float DEFAULT_SHININESS = 0.0f;
+
+ /** Default color material mode for all material states. */
+ public static final ColorMaterial DEFAULT_COLOR_MATERIAL = ColorMaterial.None;
+
+ /** Default color material face for all material states. */
+ public static final MaterialFace DEFAULT_COLOR_MATERIAL_FACE = MaterialFace.FrontAndBack;
+
+ // front face attributes of the material (used for back face if lighting is not two sided)
+ protected final ColorRGBA _frontAmbient = new ColorRGBA(DEFAULT_AMBIENT);
+ protected final ColorRGBA _frontDiffuse = new ColorRGBA(DEFAULT_DIFFUSE);
+ protected final ColorRGBA _frontSpecular = new ColorRGBA(DEFAULT_SPECULAR);
+ protected final ColorRGBA _frontEmissive = new ColorRGBA(DEFAULT_EMISSIVE);
+ protected float _frontShininess = DEFAULT_SHININESS;
+
+ // back face attributes of the material (only used if lighting is two sided)
+ protected final ColorRGBA _backAmbient = new ColorRGBA(DEFAULT_AMBIENT);
+ protected final ColorRGBA _backDiffuse = new ColorRGBA(DEFAULT_DIFFUSE);
+ protected final ColorRGBA _backSpecular = new ColorRGBA(DEFAULT_SPECULAR);
+ protected final ColorRGBA _backEmissive = new ColorRGBA(DEFAULT_EMISSIVE);
+ protected float _backShininess = DEFAULT_SHININESS;
+
+ protected ColorMaterial _colorMaterial = DEFAULT_COLOR_MATERIAL;
+ protected MaterialFace _colorMaterialFace = DEFAULT_COLOR_MATERIAL_FACE;
+
+ /**
+ * Constructor instantiates a new <code>MaterialState</code> object.
+ */
+ public MaterialState() {}
+
+ /**
+ * @return the ambient color (or front face color, if two sided lighting is used) of this material.
+ */
+ public ReadOnlyColorRGBA getAmbient() {
+ return _frontAmbient;
+ }
+
+ /**
+ * @return the ambient back face color of this material. This is only used if two sided lighting is used.
+ */
+ public ReadOnlyColorRGBA getBackAmbient() {
+ return _backAmbient;
+ }
+
+ /**
+ * Sets the ambient color for front and back to the given value.
+ *
+ * @param ambient
+ * the new ambient color
+ */
+ public void setAmbient(final ReadOnlyColorRGBA ambient) {
+ setAmbient(MaterialFace.FrontAndBack, ambient);
+ }
+
+ /**
+ * @param face
+ * the face to apply the ambient color to
+ * @param ambient
+ * the new ambient color
+ */
+ public void setAmbient(final MaterialFace face, final ReadOnlyColorRGBA ambient) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ _frontAmbient.set(ambient);
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ _backAmbient.set(ambient);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the diffuse color (or front face color, if two sided lighting is used) of this material.
+ */
+ public ReadOnlyColorRGBA getDiffuse() {
+ return _frontDiffuse;
+ }
+
+ /**
+ * @return the diffuse back face color of this material. This is only used if two sided lighting is used.
+ */
+ public ReadOnlyColorRGBA getBackDiffuse() {
+ return _backDiffuse;
+ }
+
+ /**
+ * Sets the diffuse color for front and back to the given value.
+ *
+ * @param diffuse
+ * the new diffuse color
+ */
+ public void setDiffuse(final ReadOnlyColorRGBA diffuse) {
+ setDiffuse(MaterialFace.FrontAndBack, diffuse);
+ }
+
+ /**
+ * @param face
+ * the face to apply the diffuse color to
+ * @param diffuse
+ * the new diffuse color
+ */
+ public void setDiffuse(final MaterialFace face, final ReadOnlyColorRGBA diffuse) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ _frontDiffuse.set(diffuse);
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ _backDiffuse.set(diffuse);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the emissive color (or front face color, if two sided lighting is used) of this material.
+ */
+ public ReadOnlyColorRGBA getEmissive() {
+ return _frontEmissive;
+ }
+
+ /**
+ * @return the emissive back face color of this material. This is only used if two sided lighting is used.
+ */
+ public ReadOnlyColorRGBA getBackEmissive() {
+ return _backEmissive;
+ }
+
+ /**
+ * Sets the emissive color for front and back to the given value.
+ *
+ * @param emissive
+ * the new emissive color
+ */
+ public void setEmissive(final ReadOnlyColorRGBA emissive) {
+ setEmissive(MaterialFace.FrontAndBack, emissive);
+ }
+
+ /**
+ * @param face
+ * the face to apply the emissive color to
+ * @param emissive
+ * the new emissive color
+ */
+ public void setEmissive(final MaterialFace face, final ReadOnlyColorRGBA emissive) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ _frontEmissive.set(emissive);
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ _backEmissive.set(emissive);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the specular color (or front face color, if two sided lighting is used) of this material.
+ */
+ public ReadOnlyColorRGBA getSpecular() {
+ return _frontSpecular;
+ }
+
+ /**
+ * @return the specular back face color of this material. This is only used if two sided lighting is used.
+ */
+ public ReadOnlyColorRGBA getBackSpecular() {
+ return _backSpecular;
+ }
+
+ /**
+ * Sets the specular color for front and back to the given value.
+ *
+ * @param specular
+ * the new specular color
+ */
+ public void setSpecular(final ReadOnlyColorRGBA specular) {
+ setSpecular(MaterialFace.FrontAndBack, specular);
+ }
+
+ /**
+ * @param face
+ * the face to apply the specular color to
+ * @param specular
+ * the new specular color
+ */
+ public void setSpecular(final MaterialFace face, final ReadOnlyColorRGBA specular) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ _frontSpecular.set(specular);
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ _backSpecular.set(specular);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the shininess value (or front face shininess value, if two sided lighting is used) of the material.
+ */
+ public float getShininess() {
+ return _frontShininess;
+ }
+
+ /**
+ * @return the shininess value of the back face of this material. This is only used if two sided lighting is used.
+ */
+ public float getBackShininess() {
+ return _backShininess;
+ }
+
+ /**
+ * Sets the shininess value for front and back to the given value.
+ *
+ * @param shininess
+ * the new shininess for this material. Must be between 0 and 128. Higher numbers result in "tighter"
+ * specular reflections.
+ */
+ public void setShininess(final float shininess) {
+ setShininess(MaterialFace.FrontAndBack, shininess);
+ }
+
+ /**
+ * @param face
+ * the face to apply the shininess color to
+ * @param shininess
+ * the new shininess for this material. Must be between 0 and 128. Higher numbers result in "tighter"
+ * specular reflections.
+ */
+ public void setShininess(final MaterialFace face, final float shininess) {
+ if (shininess < 0 || shininess > 128) {
+ throw new IllegalArgumentException("Shininess must be between 0 and 128.");
+ }
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ _frontShininess = shininess;
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ _backShininess = shininess;
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the color material mode of this material, which determines how geometry colors affect the material.
+ * @see ColorMaterial
+ */
+ public ColorMaterial getColorMaterial() {
+ return _colorMaterial;
+ }
+
+ /**
+ * @param material
+ * the new color material mode
+ * @throws IllegalArgumentException
+ * if material is null
+ */
+ public void setColorMaterial(final ColorMaterial material) {
+ if (material == null) {
+ throw new IllegalArgumentException("material can not be null.");
+ }
+ _colorMaterial = material;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the color material face of this material, which determines how geometry colors affect the material.
+ * @see ColorMaterial
+ */
+ public MaterialFace getColorMaterialFace() {
+ return _colorMaterialFace;
+ }
+
+ /**
+ * @param face
+ * the new color material face
+ * @throws IllegalArgumentException
+ * if face is null
+ */
+ public void setColorMaterialFace(final MaterialFace face) {
+ if (face == null) {
+ throw new IllegalArgumentException("face can not be null.");
+ }
+ _colorMaterialFace = face;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Material;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_frontAmbient, "frontAmbient", (ColorRGBA) DEFAULT_AMBIENT);
+ capsule.write(_frontDiffuse, "frontDiffuse", (ColorRGBA) DEFAULT_DIFFUSE);
+ capsule.write(_frontSpecular, "frontSpecular", (ColorRGBA) DEFAULT_SPECULAR);
+ capsule.write(_frontEmissive, "frontEmissive", (ColorRGBA) DEFAULT_EMISSIVE);
+ capsule.write(_frontShininess, "frontShininess", DEFAULT_SHININESS);
+ capsule.write(_backAmbient, "backAmbient", (ColorRGBA) DEFAULT_AMBIENT);
+ capsule.write(_backDiffuse, "backDiffuse", (ColorRGBA) DEFAULT_DIFFUSE);
+ capsule.write(_backSpecular, "backSpecular", (ColorRGBA) DEFAULT_SPECULAR);
+ capsule.write(_backEmissive, "backEmissive", (ColorRGBA) DEFAULT_EMISSIVE);
+ capsule.write(_backShininess, "backShininess", DEFAULT_SHININESS);
+ capsule.write(_colorMaterial, "colorMaterial", DEFAULT_COLOR_MATERIAL);
+ capsule.write(_colorMaterialFace, "colorMaterialFace", DEFAULT_COLOR_MATERIAL_FACE);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _frontAmbient.set((ColorRGBA) capsule.readSavable("frontAmbient", (ColorRGBA) DEFAULT_AMBIENT));
+ _frontDiffuse.set((ColorRGBA) capsule.readSavable("frontDiffuse", (ColorRGBA) DEFAULT_DIFFUSE));
+ _frontSpecular.set((ColorRGBA) capsule.readSavable("frontSpecular", (ColorRGBA) DEFAULT_SPECULAR));
+ _frontEmissive.set((ColorRGBA) capsule.readSavable("frontEmissive", (ColorRGBA) DEFAULT_EMISSIVE));
+ _frontShininess = capsule.readFloat("frontShininess", DEFAULT_SHININESS);
+ _backAmbient.set((ColorRGBA) capsule.readSavable("backAmbient", (ColorRGBA) DEFAULT_AMBIENT));
+ _backDiffuse.set((ColorRGBA) capsule.readSavable("backDiffuse", (ColorRGBA) DEFAULT_DIFFUSE));
+ _backSpecular.set((ColorRGBA) capsule.readSavable("backSpecular", (ColorRGBA) DEFAULT_SPECULAR));
+ _backEmissive.set((ColorRGBA) capsule.readSavable("backEmissive", (ColorRGBA) DEFAULT_EMISSIVE));
+ _backShininess = capsule.readFloat("backShininess", DEFAULT_SHININESS);
+ _colorMaterial = capsule.readEnum("colorMaterial", ColorMaterial.class, DEFAULT_COLOR_MATERIAL);
+ _colorMaterialFace = capsule.readEnum("colorMaterialFace", MaterialFace.class, DEFAULT_COLOR_MATERIAL_FACE);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new MaterialStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java
new file mode 100644
index 0000000..8f42100
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/OffsetState.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import com.ardor3d.renderer.state.record.OffsetStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>OffsetState</code> controls depth offset for rendering.
+ */
+public class OffsetState extends RenderState {
+
+ public enum OffsetType {
+ /** Apply offset to filled polygons. */
+ Fill,
+
+ /** Apply offset to lines. */
+ Line,
+
+ /** Apply offset to points. */
+ Point;
+ }
+
+ private final EnumSet<OffsetType> _enabledOffsets = EnumSet.noneOf(OffsetType.class);
+
+ private float _factor;
+
+ private float _units;
+
+ /**
+ * Constructor instantiates a new <code>OffsetState</code> object.
+ */
+ public OffsetState() {}
+
+ /**
+ * Sets an offset param to the zbuffer to be used when comparing an incoming fragment for depth buffer pass/fail.
+ *
+ * @param offset
+ * Is multiplied by an implementation-specific value to create a constant depth offset. The initial value
+ * is 0.
+ */
+ public void setFactor(final float factor) {
+ _factor = factor;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the currently set offset factor.
+ */
+ public float getFactor() {
+ return _factor;
+ }
+
+ /**
+ * Sets an offset param to the zbuffer to be used when comparing an incoming fragment for depth buffer pass/fail.
+ *
+ * @param units
+ * Is multiplied by an implementation-specific value to create a constant depth offset. The initial value
+ * is 0.
+ */
+ public void setUnits(final float units) {
+ _units = units;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return the currently set offset units.
+ */
+ public float getUnits() {
+ return _units;
+ }
+
+ /**
+ * Enable or disable depth offset for a particular type.
+ *
+ * @param type
+ * @param enabled
+ */
+ public void setTypeEnabled(final OffsetType type, final boolean enabled) {
+ if (enabled) {
+ _enabledOffsets.add(type);
+ } else {
+ _enabledOffsets.remove(type);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ *
+ * @param type
+ * the type to check
+ * @return true if offset is enabled for that type. (default is false for all types.)
+ */
+ public boolean isTypeEnabled(final OffsetType type) {
+ return _enabledOffsets.contains(type);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Offset;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_factor, "factor", 0);
+ capsule.write(_units, "units", 0);
+ capsule.write(_enabledOffsets.contains(OffsetType.Fill), "typeFill", false);
+ capsule.write(_enabledOffsets.contains(OffsetType.Line), "typeLine", false);
+ capsule.write(_enabledOffsets.contains(OffsetType.Point), "typePoint", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _factor = capsule.readFloat("factor", 0);
+ _units = capsule.readFloat("units", 0);
+ _enabledOffsets.clear();
+ if (capsule.readBoolean("typeFill", false)) {
+ _enabledOffsets.add(OffsetType.Fill);
+ }
+ if (capsule.readBoolean("typeLine", false)) {
+ _enabledOffsets.add(OffsetType.Line);
+ }
+ if (capsule.readBoolean("typePoint", false)) {
+ _enabledOffsets.add(OffsetType.Point);
+ }
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new OffsetStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java
new file mode 100644
index 0000000..a09e152
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/RenderState.java
@@ -0,0 +1,279 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Stack;
+
+import com.ardor3d.math.ObjectPool;
+import com.ardor3d.math.Poolable;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Maps;
+
+/**
+ * <code>RenderState</code> is the base class for all states that affect the rendering of a piece of geometry. They
+ * aren't created directly, but are created for users from the renderer. The renderstate of a parent can affect its
+ * children and it is OK to assign to more than one Spatial the same render state.
+ */
+public abstract class RenderState implements Savable {
+
+ // XXX: This enum may change, particularly the shader portions
+ public enum StateType {
+ Blend, Fog, Light, Material, Shading, Texture, Wireframe, ZBuffer, Cull, VertexProgram, FragmentProgram, Stencil, GLSLShader, ColorMask, Clip, Offset;
+
+ // cached
+ public static StateType[] values = values();
+ }
+
+ /**
+ * <p>
+ * If false, each renderstate of that type is always applied in the renderer and only field by field checks are done
+ * to minimize jni overhead. This is slower than setting to true, but relieves the programmer from situations where
+ * he has to remember to update the needsRefresh field of a state.
+ * </p>
+ * <p>
+ * If true, each renderstate of that type is checked for == with the last applied renderstate of the same type. If
+ * same and the state's needsRefresh method returns false, then application of the renderstate is skipped. This can
+ * be much faster than setting false, but in certain circumstances, the programmer must manually set needsRefresh
+ * (for example, in a FogState, if you call getFogColor().set(....) to change the color, the fogstate will not set
+ * the needsRefresh field. In non-quick compare mode, this is not a problem because it will go into the apply method
+ * and do an actual check of the current fog color in opengl vs. the color in the state being applied.)
+ * </p>
+ * <p>
+ * DEFAULTS:
+ * <ul>
+ * <li>Blend: true</li>
+ * <li>Fog: true</li>
+ * <li>Light: false - because you can change a light object directly without telling the state</li>
+ * <li>Material: true</li>
+ * <li>Shading: true</li>
+ * <li>Texture: false - because you can change a texture object directly without telling the state</li>
+ * <li>Wireframe: false - because line attributes can change when drawing regular lines, affecting wireframe lines</li>
+ * <li>ZBuffer: true</li>
+ * <li>Cull: true</li>
+ * <li>VertexShader1: true</li>
+ * <li>FragmentShader1: true</li>
+ * <li>Stencil: false</li>
+ * <li>GLSLShader: true</li>
+ * <li>ColorMask: true</li>
+ * <li>Clip: true</li>
+ * <li>Offset: true</li>
+ * </ul>
+ */
+ public static final EnumSet<StateType> _quickCompare = EnumSet.noneOf(StateType.class);
+ static {
+ _quickCompare.add(StateType.Blend);
+ _quickCompare.add(StateType.Fog);
+ _quickCompare.add(StateType.Material);
+ _quickCompare.add(StateType.Shading);
+ _quickCompare.add(StateType.ZBuffer);
+ _quickCompare.add(StateType.Cull);
+ _quickCompare.add(StateType.VertexProgram);
+ _quickCompare.add(StateType.FragmentProgram);
+ _quickCompare.add(StateType.GLSLShader);
+ _quickCompare.add(StateType.ColorMask);
+ _quickCompare.add(StateType.Offset);
+ }
+
+ private static final ObjectPool<StateStack> STATESTACKS_POOL = ObjectPool.create(StateStack.class,
+ Constants.maxStatePoolSize);
+
+ static public class StateStack implements Poolable {
+
+ private final EnumMap<RenderState.StateType, Stack<RenderState>> stacks = Maps
+ .newEnumMap(RenderState.StateType.class);
+
+ public StateStack() {}
+
+ public final static StateStack fetchTempInstance() {
+ if (Constants.useStatePools) {
+ final StateStack s = STATESTACKS_POOL.fetch();
+ // re-use already allocated stacks
+ for (final Stack<RenderState> stack : s.stacks.values()) {
+ stack.clear();
+ }
+ return s;
+ } else {
+ return new StateStack();
+ }
+ }
+
+ public final static void releaseTempInstance(final StateStack s) {
+ if (Constants.useStatePools) {
+ STATESTACKS_POOL.release(s);
+ }
+ }
+
+ public void push(final RenderState state) {
+ Stack<RenderState> stack = stacks.get(state.getType());
+ if (stack == null) {
+ stack = new Stack<RenderState>();
+ stacks.put(state.getType(), stack);
+ }
+ stack.push(state);
+ }
+
+ public void pop(final RenderState state) {
+ final Stack<RenderState> stack = stacks.get(state.getType());
+ stack.pop();
+ }
+
+ public void extract(final EnumMap<StateType, RenderState> states, final Spatial caller) {
+ RenderState state;
+ for (final Stack<RenderState> stack : stacks.values()) {
+ if (!stack.isEmpty()) {
+ state = stack.peek().extract(stack, caller);
+ states.put(state.getType(), state);
+ }
+ }
+ }
+ };
+
+ private boolean _enabled = true;
+ private boolean _needsRefresh = false;
+
+ /**
+ * Constructs a new RenderState. The state is enabled by default.
+ */
+ public RenderState() {}
+
+ /**
+ * @return An statetype enum value for the subclass.
+ * @see StateType
+ */
+ public abstract StateType getType();
+
+ /**
+ * Returns if this render state is enabled during rendering. Disabled states are ignored.
+ *
+ * @return True if this state is enabled.
+ */
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ /**
+ * Sets if this render state is enabled during rendering. Disabled states are ignored.
+ *
+ * @param value
+ * False if the state is to be disabled, true otherwise.
+ */
+ public void setEnabled(final boolean value) {
+ _enabled = value;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Extracts from the stack the correct renderstate that should apply to the given spatial. This is mainly used for
+ * RenderStates that can be cumulitive such as TextureState or LightState. By default, the top of the static is
+ * returned. This function should not be called by users directly.
+ *
+ * @param stack
+ * The stack to extract render states from.
+ * @param spat
+ * The spatial to apply the render states too.
+ * @return The render state to use.
+ */
+ public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) {
+ // The default behavior is to return the top of the stack, the last item
+ // pushed during the recursive traversal.
+ return stack.peek();
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_enabled, "enabled", true);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _enabled = capsule.readBoolean("enabled", true);
+ }
+
+ public Class<? extends RenderState> getClassTag() {
+ return this.getClass();
+ }
+
+ public abstract StateRecord createStateRecord();
+
+ /**
+ * @return true if we should apply this state even if we think it is the current state of its type in the current
+ * context. Is reset to false after apply is finished.
+ */
+ public boolean needsRefresh() {
+ return _needsRefresh;
+ }
+
+ /**
+ * This should be called by states when it knows internal data has been altered.
+ *
+ * @param refresh
+ * true if we should apply this state even if we think it is the current state of its type in the current
+ * context.
+ */
+ public void setNeedsRefresh(final boolean refresh) {
+ _needsRefresh = refresh;
+ }
+
+ /**
+ * @see #_quickCompare
+ * @param enabled
+ */
+ public static void setQuickCompares(final boolean enabled) {
+ _quickCompare.clear();
+ if (enabled) {
+ _quickCompare.addAll(EnumSet.allOf(StateType.class));
+ }
+ }
+
+ public static RenderState createState(final StateType type) {
+ switch (type) {
+ case Blend:
+ return new BlendState();
+ case Clip:
+ return new ClipState();
+ case ColorMask:
+ return new ColorMaskState();
+ case Cull:
+ return new CullState();
+ case Fog:
+ return new FogState();
+ case FragmentProgram:
+ return new FragmentProgramState();
+ case GLSLShader:
+ return new GLSLShaderObjectsState();
+ case Light:
+ return new LightState();
+ case Material:
+ return new MaterialState();
+ case Offset:
+ return new OffsetState();
+ case Shading:
+ return new ShadingState();
+ case Stencil:
+ return new StencilState();
+ case Texture:
+ return new TextureState();
+ case VertexProgram:
+ return new VertexProgramState();
+ case Wireframe:
+ return new WireframeState();
+ case ZBuffer:
+ return new ZBufferState();
+ }
+ throw new IllegalArgumentException("Unknown state type: " + type);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java
new file mode 100644
index 0000000..1ca1b72
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ShadingState.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.ShadingStateRecord;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>ShadeState</code> maintains the interpolation of color between vertices. Smooth shades the colors with proper
+ * linear interpolation, while flat provides no smoothing. If this state is not enabled, Smooth is used.
+ */
+public class ShadingState extends RenderState {
+
+ public enum ShadingMode {
+ /**
+ * Pick the color of just one vertex of a triangle and rasterize all pixels of the triangle with this color.
+ */
+ Flat,
+ /**
+ * Smoothly interpolate the color values between the three colors of the three vertices. (Default)
+ */
+ Smooth;
+ }
+
+ // shade mode.
+ protected ShadingMode _shadeMode = ShadingMode.Smooth;
+
+ /**
+ * Constructor instantiates a new <code>ShadeState</code> object with the default mode being smooth.
+ */
+ public ShadingState() {}
+
+ /**
+ * <code>getShade</code> returns the current shading mode.
+ *
+ * @return the current shading mode.
+ */
+ public ShadingMode getShadingMode() {
+ return _shadeMode;
+ }
+
+ /**
+ * <code>setShadeMode</code> sets the current shading mode.
+ *
+ * @param shadeMode
+ * the new shading mode.
+ * @throws IllegalArgumentException
+ * if shadeMode is null
+ */
+ public void setShadingMode(final ShadingMode shadeMode) {
+ if (shadeMode == null) {
+ throw new IllegalArgumentException("shadeMode can not be null.");
+ }
+ _shadeMode = shadeMode;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Shading;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_shadeMode, "shadeMode", ShadingMode.Smooth);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _shadeMode = capsule.readEnum("shadeMode", ShadingMode.class, ShadingMode.Smooth);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new ShadingStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java
new file mode 100644
index 0000000..56b5745
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/StencilState.java
@@ -0,0 +1,580 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.renderer.state.record.StencilStateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * The StencilState RenderState allows the user to set the attributes of the stencil buffer of the renderer. The
+ * Stenciling is similar to Z-Buffering in that it allows enabling and disabling drawing on a per pixel basis. You can
+ * use the stencil plane to mask out portions of the rendering to create special effects, such as outlining or planar
+ * shadows. Our stencil state supports setting operations for front and back facing polygons separately. If your card
+ * does not support setting faces independently, the front face values will be used for both sides.
+ */
+public class StencilState extends RenderState {
+
+ public enum StencilFunction {
+ /** A stencil function that never passes. */
+ Never,
+ /** A stencil function that passes if (ref & mask) < (stencil & mask). */
+ LessThan,
+ /** A stencil function that passes if (ref & mask) <= (stencil & mask). */
+ LessThanOrEqualTo,
+ /** A stencil function that passes if (ref & mask) > (stencil & mask). */
+ GreaterThan,
+ /** A stencil function that passes if (ref & mask) >= (stencil & mask). */
+ GreaterThanOrEqualTo,
+ /** A stencil function that passes if (ref & mask) == (stencil & mask). */
+ EqualTo,
+ /** A stencil function that passes if (ref & mask) != (stencil & mask). */
+ NotEqualTo,
+ /** A stencil function that always passes. (Default) */
+ Always;
+ }
+
+ public enum StencilOperation {
+ /** A stencil function result that keeps the current value. */
+ Keep,
+ /** A stencil function result that sets the stencil buffer value to 0. */
+ Zero,
+ /**
+ * A stencil function result that sets the stencil buffer value to ref, as specified by stencil function.
+ */
+ Replace,
+ /**
+ * A stencil function result that increments the current stencil buffer value.
+ */
+ Increment,
+ /**
+ * A stencil function result that decrements the current stencil buffer value.
+ */
+ Decrement,
+ /**
+ * A stencil function result that increments the current stencil buffer value and wraps around to the lowest
+ * stencil value if it reaches the max. (if the renderer does not support stencil wrap, we'll fall back to
+ * Increment)
+ */
+ IncrementWrap,
+ /**
+ * A stencil function result that decrements the current stencil buffer and wraps around to the highest stencil
+ * value if it reaches the min. value. (if the renderer does not support stencil wrap, we'll fall back to
+ * Decrement)
+ */
+ DecrementWrap,
+ /**
+ * A stencil function result that bitwise inverts the current stencil buffer value.
+ */
+ Invert;
+ }
+
+ private boolean _useTwoSided = false;
+
+ // Front
+ private StencilFunction _stencilFunctionFront = StencilFunction.Always;
+
+ private int _stencilReferenceFront = 0;
+ private int _stencilFuncMaskFront = ~0;
+ private int _stencilWriteMaskFront = ~0;
+
+ private StencilOperation _stencilOpFailFront = StencilOperation.Keep;
+ private StencilOperation _stencilOpZFailFront = StencilOperation.Keep;
+ private StencilOperation _stencilOpZPassFront = StencilOperation.Keep;
+
+ // Back
+ private StencilFunction _stencilFunctionBack = StencilFunction.Always;
+
+ private int _stencilReferenceBack = 0;
+ private int _stencilFuncMaskBack = ~0;
+ private int _stencilWriteMaskBack = ~0;
+
+ private StencilOperation _stencilOpFailBack = StencilOperation.Keep;
+ private StencilOperation _stencilOpZFailBack = StencilOperation.Keep;
+ private StencilOperation _stencilOpZPassBack = StencilOperation.Keep;
+
+ @Override
+ public StateType getType() {
+ return StateType.Stencil;
+ }
+
+ /**
+ * Sets the function that defines if a stencil test passes or not for both faces.
+ *
+ * @param function
+ * The new stencil function for both faces.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setStencilFunction(final StencilFunction function) {
+ setStencilFunctionFront(function);
+ setStencilFunctionBack(function);
+ }
+
+ /**
+ * Sets the stencil reference to be used during the stencil function for both faces.
+ *
+ * @param reference
+ * The new stencil reference for both faces.
+ */
+ public void setStencilReference(final int reference) {
+ setStencilReferenceFront(reference);
+ setStencilReferenceBack(reference);
+ }
+
+ /**
+ * Convienence method for setting both types of stencil masks at once for both faces.
+ *
+ * @param mask
+ * The new stencil write and func mask for both faces.
+ */
+ public void setStencilMask(final int mask) {
+ setStencilMaskFront(mask);
+ setStencilMaskBack(mask);
+ }
+
+ /**
+ * Controls which stencil bitplanes are written for both faces.
+ *
+ * @param mask
+ * The new stencil write mask for both faces.
+ */
+ public void setStencilWriteMask(final int mask) {
+ setStencilWriteMaskFront(mask);
+ setStencilWriteMaskBack(mask);
+ }
+
+ /**
+ * Sets the stencil mask to be used during stencil functions for both faces.
+ *
+ * @param mask
+ * The new stencil function mask for both faces.
+ */
+ public void setStencilFuncMask(final int mask) {
+ setStencilFuncMaskFront(mask);
+ setStencilFuncMaskBack(mask);
+ }
+
+ /**
+ * Specifies the aciton to take when the stencil test fails for both faces.
+ *
+ * @param operation
+ * The new stencil operation for both faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpFail(final StencilOperation operation) {
+ setStencilOpFailFront(operation);
+ setStencilOpFailBack(operation);
+ }
+
+ /**
+ * Specifies stencil action when the stencil test passes, but the depth test fails for both faces.
+ *
+ * @param operation
+ * The Z test operation to set for both faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZFail(final StencilOperation operation) {
+ setStencilOpZFailFront(operation);
+ setStencilOpZFailBack(operation);
+ }
+
+ /**
+ * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and
+ * either there is no depth buffer or depth testing is not enabled.
+ *
+ * @param operation
+ * The new Z test pass operation to set for both faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZPass(final StencilOperation operation) {
+ setStencilOpZPassFront(operation);
+ setStencilOpZPassBack(operation);
+ }
+
+ /**
+ * Sets the function that defines if a stencil test passes or not for front faces.
+ *
+ * @param function
+ * The new stencil function for front faces.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setStencilFunctionFront(final StencilFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _stencilFunctionFront = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil function for front faces. Default is StencilFunction.Always
+ */
+ public StencilFunction getStencilFunctionFront() {
+ return _stencilFunctionFront;
+ }
+
+ /**
+ * Sets the stencil reference to be used during the stencil function for front faces.
+ *
+ * @param reference
+ * The new stencil reference for front faces.
+ */
+ public void setStencilReferenceFront(final int reference) {
+ _stencilReferenceFront = reference;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil reference for front faces. Default is 0
+ */
+ public int getStencilReferenceFront() {
+ return _stencilReferenceFront;
+ }
+
+ /**
+ * Convienence method for setting both types of stencil masks at once for front faces.
+ *
+ * @param mask
+ * The new stencil write and func mask for front faces.
+ */
+ public void setStencilMaskFront(final int mask) {
+ setStencilWriteMaskFront(mask);
+ setStencilFuncMaskFront(mask);
+ }
+
+ /**
+ * Controls which stencil bitplanes are written for front faces.
+ *
+ * @param mask
+ * The new stencil write mask for front faces.
+ */
+ public void setStencilWriteMaskFront(final int mask) {
+ _stencilWriteMaskFront = mask;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil write mask for front faces. Default is all 1's (~0)
+ */
+ public int getStencilWriteMaskFront() {
+ return _stencilWriteMaskFront;
+ }
+
+ /**
+ * Sets the stencil mask to be used during stencil functions for front faces.
+ *
+ * @param mask
+ * The new stencil function mask for front faces.
+ */
+ public void setStencilFuncMaskFront(final int mask) {
+ _stencilFuncMaskFront = mask;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil function mask for front faces. Default is all 1's (~0)
+ */
+ public int getStencilFuncMaskFront() {
+ return _stencilFuncMaskFront;
+ }
+
+ /**
+ * Specifies the aciton to take when the stencil test fails for front faces.
+ *
+ * @param operation
+ * The new stencil operation for front faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpFailFront(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpFailFront = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil operation for front faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpFailFront() {
+ return _stencilOpFailFront;
+ }
+
+ /**
+ * Specifies stencil action when the stencil test passes, but the depth test fails for front faces.
+ *
+ * @param operation
+ * The Z test operation to set for front faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZFailFront(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpZFailFront = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current Z op fail function for front faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpZFailFront() {
+ return _stencilOpZFailFront;
+ }
+
+ /**
+ * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and
+ * either there is no depth buffer or depth testing is not enabled.
+ *
+ * @param operation
+ * The new Z test pass operation to set for front faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZPassFront(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpZPassFront = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current Z op pass function for front faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpZPassFront() {
+ return _stencilOpZPassFront;
+ }
+
+ /**
+ * Sets the function that defines if a stencil test passes or not for back faces.
+ *
+ * @param function
+ * The new stencil function for back faces.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setStencilFunctionBack(final StencilFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _stencilFunctionBack = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil function for back faces. Default is StencilFunction.Always
+ */
+ public StencilFunction getStencilFunctionBack() {
+ return _stencilFunctionBack;
+ }
+
+ /**
+ * Sets the stencil reference to be used during the stencil function for back faces.
+ *
+ * @param reference
+ * The new stencil reference for back faces.
+ */
+ public void setStencilReferenceBack(final int reference) {
+ _stencilReferenceBack = reference;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil reference for back faces. Default is 0
+ */
+ public int getStencilReferenceBack() {
+ return _stencilReferenceBack;
+ }
+
+ /**
+ * Convienence method for setting both types of stencil masks at once for back faces.
+ *
+ * @param mask
+ * The new stencil write and func mask for back faces.
+ */
+ public void setStencilMaskBack(final int mask) {
+ setStencilWriteMaskBack(mask);
+ setStencilFuncMaskBack(mask);
+ }
+
+ /**
+ * Controls which stencil bitplanes are written for back faces.
+ *
+ * @param mask
+ * The new stencil write mask for back faces.
+ */
+ public void setStencilWriteMaskBack(final int mask) {
+ _stencilWriteMaskBack = mask;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil write mask for back faces. Default is all 1's (~0)
+ */
+ public int getStencilWriteMaskBack() {
+ return _stencilWriteMaskBack;
+ }
+
+ /**
+ * Sets the stencil mask to be used during stencil functions for back faces.
+ *
+ * @param mask
+ * The new stencil function mask for back faces.
+ */
+ public void setStencilFuncMaskBack(final int mask) {
+ _stencilFuncMaskBack = mask;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil function mask for back faces. Default is all 1's (~0)
+ */
+ public int getStencilFuncMaskBack() {
+ return _stencilFuncMaskBack;
+ }
+
+ /**
+ * Specifies the aciton to take when the stencil test fails for back faces.
+ *
+ * @param operation
+ * The new stencil operation for back faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpFailBack(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpFailBack = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current stencil operation for back faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpFailBack() {
+ return _stencilOpFailBack;
+ }
+
+ /**
+ * Specifies stencil action when the stencil test passes, but the depth test fails.
+ *
+ * @param operation
+ * The Z test operation to set for back faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZFailBack(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpZFailBack = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current Z op fail function for back faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpZFailBack() {
+ return _stencilOpZFailBack;
+ }
+
+ /**
+ * Specifies stencil action when both the stencil test and the depth test pass, or when the stencil test passes and
+ * either there is no depth buffer or depth testing is not enabled.
+ *
+ * @param operation
+ * The new Z test pass operation to set for back faces.
+ * @throws IllegalArgumentException
+ * if operation is null
+ */
+ public void setStencilOpZPassBack(final StencilOperation operation) {
+ if (operation == null) {
+ throw new IllegalArgumentException("operation can not be null.");
+ }
+ _stencilOpZPassBack = operation;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return The current Z op pass function for back faces. Default is StencilOperation.Keep
+ */
+ public StencilOperation getStencilOpZPassBack() {
+ return _stencilOpZPassBack;
+ }
+
+ public boolean isUseTwoSided() {
+ return _useTwoSided;
+ }
+
+ public void setUseTwoSided(final boolean useTwoSided) {
+ _useTwoSided = useTwoSided;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_useTwoSided, "useTwoSided", false);
+ capsule.write(_stencilFunctionFront, "stencilFuncFront", StencilFunction.Always);
+ capsule.write(_stencilReferenceFront, "stencilRefFront", 0);
+ capsule.write(_stencilWriteMaskFront, "stencilWriteMaskFront", ~0);
+ capsule.write(_stencilFuncMaskFront, "stencilFuncMaskFront", ~0);
+ capsule.write(_stencilOpFailFront, "stencilOpFailFront", StencilOperation.Keep);
+ capsule.write(_stencilOpZFailFront, "stencilOpZFailFront", StencilOperation.Keep);
+ capsule.write(_stencilOpZPassFront, "stencilOpZPassFront", StencilOperation.Keep);
+
+ capsule.write(_stencilFunctionBack, "stencilFuncBack", StencilFunction.Always);
+ capsule.write(_stencilReferenceBack, "stencilRefBack", 0);
+ capsule.write(_stencilWriteMaskBack, "stencilWriteMaskBack", ~0);
+ capsule.write(_stencilFuncMaskBack, "stencilFuncMaskBack", ~0);
+ capsule.write(_stencilOpFailBack, "stencilOpFailBack", StencilOperation.Keep);
+ capsule.write(_stencilOpZFailBack, "stencilOpZFailBack", StencilOperation.Keep);
+ capsule.write(_stencilOpZPassBack, "stencilOpZPassBack", StencilOperation.Keep);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _useTwoSided = capsule.readBoolean("useTwoSided", false);
+ _stencilFunctionFront = capsule.readEnum("stencilFuncFront", StencilFunction.class, StencilFunction.Always);
+ _stencilReferenceFront = capsule.readInt("stencilRefFront", 0);
+ _stencilWriteMaskFront = capsule.readInt("stencilWriteMaskFront", ~0);
+ _stencilFuncMaskFront = capsule.readInt("stencilFuncMaskFront", ~0);
+ _stencilOpFailFront = capsule.readEnum("stencilOpFailFront", StencilOperation.class, StencilOperation.Keep);
+ _stencilOpZFailFront = capsule.readEnum("stencilOpZFailFront", StencilOperation.class, StencilOperation.Keep);
+ _stencilOpZPassFront = capsule.readEnum("stencilOpZPassFront", StencilOperation.class, StencilOperation.Keep);
+
+ _stencilFunctionBack = capsule.readEnum("stencilFuncBack", StencilFunction.class, StencilFunction.Always);
+ _stencilReferenceBack = capsule.readInt("stencilRefBack", 0);
+ _stencilWriteMaskBack = capsule.readInt("stencilWriteMaskBack", ~0);
+ _stencilFuncMaskBack = capsule.readInt("stencilFuncMaskBack", ~0);
+ _stencilOpFailBack = capsule.readEnum("stencilOpFailBack", StencilOperation.class, StencilOperation.Keep);
+ _stencilOpZFailBack = capsule.readEnum("stencilOpZFailBack", StencilOperation.class, StencilOperation.Keep);
+ _stencilOpZPassBack = capsule.readEnum("stencilOpZPassBack", StencilOperation.class, StencilOperation.Keep);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new StencilStateRecord();
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java
new file mode 100644
index 0000000..d895eb7
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/TextureState.java
@@ -0,0 +1,376 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.image.Image;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.renderer.state.record.TextureStateRecord;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.TextureKey;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.ResourceSource;
+import com.ardor3d.util.resource.URLResourceSource;
+
+/**
+ * <code>TextureState</code> maintains a texture state for a given node and it's children. The number of states that a
+ * TextureState can maintain at one time is equal to the number of texture units available on the GPU. It is not within
+ * the scope of this class to generate the texture, and is recommended that <code>TextureManager</code> be used to
+ * create the Texture objects.
+ *
+ * @see com.ardor3d.util.TextureManager
+ */
+public class TextureState extends RenderState {
+ private static final Logger logger = Logger.getLogger(TextureState.class.getName());
+
+ public static final int MAX_TEXTURES = 32;
+
+ protected static Texture _defaultTexture = null;
+ protected static boolean defaultTextureLoaded = false;
+
+ public enum CorrectionType {
+ /**
+ * Correction modifier makes no color corrections, and is the fastest.
+ */
+ Affine,
+
+ /**
+ * Correction modifier makes color corrections based on perspective and is slower than CM_AFFINE. (Default)
+ */
+ Perspective;
+ }
+
+ /** The texture(s). */
+ protected List<Texture> _texture = new ArrayList<Texture>(1);
+
+ /**
+ * Perspective correction to use for the object rendered with this texture state. Default is
+ * CorrectionType.Perspective.
+ */
+ private CorrectionType _correctionType = CorrectionType.Perspective;
+
+ public transient TextureKey[] _keyCache = new TextureKey[MAX_TEXTURES];
+
+ public static ResourceSource DEFAULT_TEXTURE_SOURCE;
+ static {
+ try {
+ DEFAULT_TEXTURE_SOURCE = new URLResourceSource(ResourceLocatorTool.getClassPathResource(TextureState.class,
+ "com/ardor3d/renderer/state/notloaded.tga"));
+ } catch (final Exception e) {
+ // ignore.
+ DEFAULT_TEXTURE_SOURCE = null;
+ }
+ }
+
+ /**
+ * Constructor instantiates a new <code>TextureState</code> object.
+ */
+ public TextureState() {
+ if (!defaultTextureLoaded) {
+ loadDefaultTexture();
+ }
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.Texture;
+ }
+
+ /**
+ * <code>setTexture</code> sets a single texture to the first texture unit.
+ *
+ * @param texture
+ * the texture to set.
+ */
+ public void setTexture(final Texture texture) {
+ if (_texture.size() == 0) {
+ _texture.add(texture);
+ } else {
+ _texture.set(0, texture);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getTexture</code> gets the texture that is assigned to the first texture unit.
+ *
+ * @return the texture in the first texture unit.
+ */
+ public Texture getTexture() {
+ if (_texture.size() > 0) {
+ return _texture.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * <code>setTexture</code> sets the texture object to be used by the state. The texture unit that this texture uses
+ * is set, if the unit is not valid, i.e. less than zero or greater than the number of texture units supported by
+ * the graphics card, it is ignored.
+ *
+ * @param texture
+ * the texture to be used by the state.
+ * @param textureUnit
+ * the texture unit this texture will fill.
+ */
+ public void setTexture(final Texture texture, final int textureUnit) {
+ if (textureUnit >= 0 && textureUnit < MAX_TEXTURES) {
+ while (textureUnit >= _texture.size()) {
+ _texture.add(null);
+ }
+ _texture.set(textureUnit, texture);
+ }
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getTexture</code> retrieves the texture being used by the state in a particular texture unit.
+ *
+ * @param textureUnit
+ * the texture unit to retrieve the texture from.
+ * @return the texture being used by the state. If the texture unit is invalid, null is returned.
+ */
+ public Texture getTexture(final int textureUnit) {
+ if (textureUnit < _texture.size() && textureUnit >= 0) {
+ return _texture.get(textureUnit);
+ }
+
+ return null;
+ }
+
+ public boolean removeTexture(final Texture tex) {
+
+ final int index = _texture.indexOf(tex);
+ if (index == -1) {
+ return false;
+ }
+
+ _texture.set(index, null);
+ _keyCache[index] = null;
+ return true;
+ }
+
+ public boolean removeTexture(final int textureUnit) {
+ if (textureUnit < 0 || textureUnit >= MAX_TEXTURES || textureUnit >= _texture.size()) {
+ return false;
+ }
+
+ final Texture t = _texture.get(textureUnit);
+ if (t == null) {
+ return false;
+ }
+
+ _texture.set(textureUnit, null);
+ _keyCache[textureUnit] = null;
+ return true;
+
+ }
+
+ /**
+ * Removes all textures in this texture state. Does not delete them from the graphics card.
+ */
+ public void clearTextures() {
+ for (int i = _texture.size(); --i >= 0;) {
+ removeTexture(i);
+ }
+ }
+
+ /**
+ * <code>setCorrectionType</code> sets the image correction type for this texture state.
+ *
+ * @param type
+ * the correction type for this texture.
+ * @throws IllegalArgumentException
+ * if type is null
+ */
+ public void setCorrectionType(final CorrectionType type) {
+ if (type == null) {
+ throw new IllegalArgumentException("type can not be null.");
+ }
+ _correctionType = type;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>getCorrectionType</code> returns the correction mode for the texture state.
+ *
+ * @return the correction type for the texture state.
+ */
+ public CorrectionType getCorrectionType() {
+ return _correctionType;
+ }
+
+ /**
+ * Returns the number of textures this texture manager is maintaining.
+ *
+ * @return the number of textures.
+ */
+ public int getNumberOfSetTextures() {
+ int set = 0;
+ for (int i = 0; i < _texture.size(); i++) {
+ if (_texture.get(i) != null) {
+ set++;
+ }
+ }
+ return set;
+ }
+
+ /**
+ * Returns the max index in this TextureState that contains a non-null Texture.
+ *
+ * @return the max index, or -1 if no textures are contained by this state.
+ */
+ public int getMaxTextureIndexUsed() {
+ int max = _texture.size() - 1;
+ while (max > 0 && _texture.get(max) == null) {
+ max--;
+ }
+ return max;
+ }
+
+ /**
+ * Fast access for retrieving a TextureKey. A return is guaranteed when <code>textureUnit</code> is any number under
+ * or equal to the highest texture unit currently in use. This value can be retrieved with
+ * <code>getNumberOfSetTextures</code>. A higher value might result in unexpected behavior such as an exception
+ * being thrown.
+ *
+ * @param textureUnit
+ * The texture unit from which to retrieve the TextureKey.
+ * @return the TextureKey, or null if there is none.
+ */
+ public final TextureKey getTextureKey(final int textureUnit) {
+ if (textureUnit < _keyCache.length && textureUnit >= 0) {
+ return _keyCache[textureUnit];
+ }
+
+ return null;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.writeSavableList(_texture, "texture", new ArrayList<Texture>(1));
+ capsule.write(_correctionType, "correctionType", CorrectionType.Perspective);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _texture = capsule.readSavableList("texture", new ArrayList<Texture>(1));
+ _correctionType = capsule.readEnum("correctionType", CorrectionType.class, CorrectionType.Perspective);
+ }
+
+ public static Image getDefaultTextureImage() {
+ return _defaultTexture != null ? _defaultTexture.getImage() : null;
+ }
+
+ public static Texture getDefaultTexture() {
+ if (!defaultTextureLoaded) {
+ loadDefaultTexture();
+ }
+ return _defaultTexture.createSimpleClone();
+ }
+
+ private static void loadDefaultTexture() {
+ synchronized (logger) {
+ if (!defaultTextureLoaded) {
+ defaultTextureLoaded = true;
+ _defaultTexture = new Texture2D();
+ try {
+ _defaultTexture = TextureManager.load(DEFAULT_TEXTURE_SOURCE, Texture.MinificationFilter.Trilinear,
+ true);
+ } catch (final Exception e) {
+ logger.log(Level.WARNING, "Failed to load default texture: notloaded.tga", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new TextureStateRecord();
+ }
+
+ @Override
+ public RenderState extract(final Stack<? extends RenderState> stack, final Spatial spat) {
+ if (spat == null) {
+ return stack.peek();
+ }
+
+ final TextureCombineMode mode = spat.getSceneHints().getTextureCombineMode();
+ if (mode == TextureCombineMode.Replace || (mode != TextureCombineMode.Off && stack.size() == 1)) {
+ // todo: use dummy state if off?
+ return stack.peek();
+ }
+
+ // accumulate the textures in the stack into a single TextureState object
+ final TextureState newTState = new TextureState();
+ boolean foundEnabled = false;
+ final Object states[] = stack.toArray();
+ switch (mode) {
+ case CombineClosest:
+ case CombineClosestEnabled:
+ for (int iIndex = states.length - 1; iIndex >= 0; iIndex--) {
+ final TextureState pkTState = (TextureState) states[iIndex];
+ if (!pkTState.isEnabled()) {
+ if (mode == TextureCombineMode.CombineClosestEnabled) {
+ break;
+ }
+
+ continue;
+ }
+
+ foundEnabled = true;
+ for (int i = 0, max = pkTState.getMaxTextureIndexUsed(); i <= max; i++) {
+ final Texture pkText = pkTState.getTexture(i);
+ if (newTState.getTexture(i) == null) {
+ newTState.setTexture(pkText, i);
+ }
+ }
+ }
+ break;
+ case CombineFirst:
+ for (int iIndex = 0, max = states.length; iIndex < max; iIndex++) {
+ final TextureState pkTState = (TextureState) states[iIndex];
+ if (!pkTState.isEnabled()) {
+ continue;
+ }
+
+ foundEnabled = true;
+ for (int i = 0; i < TextureState.MAX_TEXTURES; i++) {
+ final Texture pkText = pkTState.getTexture(i);
+ if (newTState.getTexture(i) == null) {
+ newTState.setTexture(pkText, i);
+ }
+ }
+ }
+ break;
+ case Off:
+ break;
+ }
+ newTState.setEnabled(foundEnabled);
+ return newTState;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java
new file mode 100644
index 0000000..ea8564b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/VertexProgramState.java
@@ -0,0 +1,246 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.renderer.state.record.VertexProgramStateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Implementation of the GL_ARB_vertex_program extension.
+ */
+public class VertexProgramState extends RenderState {
+ private static final Logger logger = Logger.getLogger(VertexProgramState.class.getName());
+
+ /** Environmental parameters applied to all vertex programs */
+ protected static float[][] _envparameters = new float[96][];
+
+ /** If any local parameters for this VP state are set */
+ protected boolean _usingParameters = false;
+
+ /** Parameters local to this vertex program */
+ protected float[][] _parameters;
+ protected ByteBuffer _program;
+
+ protected int _programID = -1;
+
+ /**
+ * <code>setEnvParameter</code> sets an environmental vertex program parameter that is accessible by all vertex
+ * programs in memory.
+ *
+ * @param param
+ * four-element array of floating point numbers
+ * @param paramID
+ * identity number of the parameter, ranging from 0 to 95
+ */
+ public static void setEnvParameter(final float[] param, final int paramID) {
+ if (paramID < 0 || paramID > 95) {
+ throw new IllegalArgumentException("Invalid parameter ID");
+ }
+ if (param != null && param.length != 4) {
+ throw new IllegalArgumentException("Vertex program parameters must be of type float[4]");
+ }
+
+ _envparameters[paramID] = param;
+ }
+
+ /**
+ * Creates a new VertexProgramState. <code>load(URL)</code> must be called before the state can be used.
+ */
+ public VertexProgramState() {
+ _parameters = new float[96][];
+ }
+
+ /**
+ * <code>setParameter</code> sets a parameter for this vertex program.
+ *
+ * @param paramID
+ * identity number of the parameter, ranging from 0 to 95
+ * @param param
+ * four-element array of floating point numbers
+ */
+ public void setParameter(final float[] param, final int paramID) {
+ if (paramID < 0 || paramID > 95) {
+ throw new IllegalArgumentException("Invalid parameter ID");
+ }
+ if (param != null && param.length != 4) {
+ throw new IllegalArgumentException("Vertex program parameters must be of type float[4]");
+ }
+
+ _usingParameters = true;
+ _parameters[paramID] = param;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.VertexProgram;
+ }
+
+ public void load(final java.net.URL file) {
+ InputStream inputStream = null;
+ try {
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(16 * 1024);
+ inputStream = new BufferedInputStream(file.openStream());
+ final byte[] buffer = new byte[1024];
+ int byteCount = -1;
+
+ // Read the byte content into the output stream first
+ while ((byteCount = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, byteCount);
+ }
+
+ // Set data with byte content from stream
+ final byte[] data = outputStream.toByteArray();
+
+ // Release resources
+ inputStream.close();
+ outputStream.close();
+
+ _program = BufferUtils.createByteBuffer(data.length);
+ _program.put(data);
+ _program.rewind();
+ _programID = -1;
+ setNeedsRefresh(true);
+
+ } catch (final Exception e) {
+ logger.severe("Could not load vertex program: " + e);
+ logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e);
+ } finally {
+ // Ensure that the stream is closed, even if there is an exception.
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (final IOException closeFailure) {
+ logger.log(Level.WARNING, "Failed to close the vertex program", closeFailure);
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Loads the vertex program into a byte array.
+ *
+ * @see com.ardor3d.renderer.state.VertexProgramState#load(java.net.URL)
+ */
+ public void load(final String programContents) {
+ try {
+ final byte[] bytes = programContents.getBytes();
+ _program = BufferUtils.createByteBuffer(bytes.length);
+ _program.put(bytes);
+ _program.rewind();
+ _programID = -1;
+ setNeedsRefresh(true);
+
+ } catch (final Exception e) {
+ logger.severe("Could not load vertex program: " + e);
+ logger.logp(Level.SEVERE, getClass().getName(), "load(URL)", "Exception", e);
+ }
+ }
+
+ public ByteBuffer getProgramAsBuffer() {
+ return _program;
+ }
+
+ public int _getProgramID() {
+ return _programID;
+ }
+
+ public void _setProgramID(final int id) {
+ _programID = id;
+ }
+
+ public boolean isUsingParameters() {
+ return _usingParameters;
+ }
+
+ public float[][] _getParameters() {
+ return _parameters;
+ }
+
+ public static float[][] _getEnvParameters() {
+ return _envparameters;
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @see java.io.Serializable
+ */
+ private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
+ s.defaultWriteObject();
+ if (_program == null) {
+ s.writeInt(0);
+ } else {
+ s.writeInt(_program.capacity());
+ _program.rewind();
+ for (int x = 0, len = _program.capacity(); x < len; x++) {
+ s.writeByte(_program.get());
+ }
+ }
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @see java.io.Serializable
+ */
+ private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ final int len = s.readInt();
+ if (len == 0) {
+ _program = null;
+ } else {
+ _program = BufferUtils.createByteBuffer(len);
+ for (int x = 0; x < len; x++) {
+ _program.put(s.readByte());
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_usingParameters, "usingParameters", false);
+ capsule.write(_parameters, "parameters", new float[96][]);
+ capsule.write(_program, "program", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _usingParameters = capsule.readBoolean("usingParameters", false);
+ _parameters = capsule.readFloatArray2D("parameters", new float[96][]);
+ _program = capsule.readByteBuffer("program", null);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new VertexProgramStateRecord();
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java
new file mode 100644
index 0000000..dbb099c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/WireframeState.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.renderer.state.record.WireframeStateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>WireframeState</code> maintains whether a node and it's children should be drawn in wireframe or solid fill. By
+ * default all nodes are rendered solid.
+ */
+public class WireframeState extends RenderState {
+
+ public enum Face {
+ /** The front will be wireframed, but the back will be solid. */
+ Front,
+ /** The back will be wireframed, but the front will be solid. */
+ Back,
+ /** Both sides of the model are wireframed. */
+ FrontAndBack;
+ }
+
+ /** Default wireframe of front and back. */
+ protected Face _face = Face.FrontAndBack;
+ /** Default line width of 1 pixel. */
+ protected float _lineWidth = 1.0f;
+ /** Default line style */
+ protected boolean _antialiased = false;
+
+ @Override
+ public StateType getType() {
+ return StateType.Wireframe;
+ }
+
+ /**
+ * <code>setLineWidth</code> sets the width of lines the wireframe is drawn in. Attempting to set a line width
+ * smaller than 0.0 throws an <code>IllegalArgumentException</code>.
+ *
+ * @param width
+ * the line width, in pixels
+ */
+ public void setLineWidth(final float width) {
+ if (width < 0.0f) {
+ throw new IllegalArgumentException("Line width must be positive");
+ }
+
+ _lineWidth = width;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Returns the current lineWidth.
+ *
+ * @return the current LineWidth
+ */
+ public float getLineWidth() {
+ return _lineWidth;
+ }
+
+ /**
+ * <code>setFace</code> sets which face will recieve the wireframe.
+ *
+ * @param face
+ * which face will be rendered in wireframe.
+ * @throws IllegalArgumentException
+ * if face is null
+ */
+ public void setFace(final Face face) {
+ if (face == null) {
+ throw new IllegalArgumentException("face can not be null.");
+ }
+ _face = face;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * Returns the face state of this wireframe state.
+ *
+ * @return The face state (one of WS_FRONT, WS_BACK, or WS_FRONT_AND_BACK)
+ */
+ public Face getFace() {
+ return _face;
+ }
+
+ /**
+ * Set whether this wireframe should use antialiasing when drawing lines. May decrease performance. If you want to
+ * enabled antialiasing, you should also use an alphastate with a source of SourceFunction.SourceAlpha and a
+ * destination of DB_ONE_MINUS_SRC_ALPHA or DB_ONE.
+ *
+ * @param antialiased
+ * true for using smoothed antialiased lines.
+ */
+ public void setAntialiased(final boolean antialiased) {
+ _antialiased = antialiased;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * @return whether this wireframe uses antialiasing for drawing lines.
+ */
+ public boolean isAntialiased() {
+ return _antialiased;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_face, "face", Face.FrontAndBack);
+ capsule.write(_lineWidth, "lineWidth", 1);
+ capsule.write(_antialiased, "antialiased", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _face = capsule.readEnum("face", Face.class, Face.FrontAndBack);
+ _lineWidth = capsule.readFloat("lineWidth", 1);
+ _antialiased = capsule.readBoolean("antialiased", false);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new WireframeStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java
new file mode 100644
index 0000000..ca79425
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/ZBufferState.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state;
+
+import java.io.IOException;
+
+import com.ardor3d.renderer.state.record.StateRecord;
+import com.ardor3d.renderer.state.record.ZBufferStateRecord;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>ZBufferState</code> maintains how the use of the depth buffer is to occur. Depth buffer comparisons are used to
+ * evaluate what incoming fragment will be used. This buffer is based on z depth, or distance between the pixel source
+ * and the eye.
+ */
+public class ZBufferState extends RenderState {
+
+ public enum TestFunction {
+ /**
+ * Depth comparison never passes.
+ */
+ Never,
+ /**
+ * Depth comparison always passes.
+ */
+ Always,
+ /**
+ * Passes if the incoming value is the same as the stored value.
+ */
+ EqualTo,
+ /**
+ * Passes if the incoming value is not equal to the stored value.
+ */
+ NotEqualTo,
+ /**
+ * Passes if the incoming value is less than the stored value.
+ */
+ LessThan,
+ /**
+ * Passes if the incoming value is less than or equal to the stored value.
+ */
+ LessThanOrEqualTo,
+ /**
+ * Passes if the incoming value is greater than the stored value.
+ */
+ GreaterThan,
+ /**
+ * Passes if the incoming value is greater than or equal to the stored value.
+ */
+ GreaterThanOrEqualTo;
+
+ }
+
+ /** Depth function. */
+ protected TestFunction _function = TestFunction.LessThan;
+ /** Depth mask is writable or not. */
+ protected boolean _writable = true;
+
+ /**
+ * Constructor instantiates a new <code>ZBufferState</code> object. The initial values are TestFunction.LessThan and
+ * depth writing on.
+ */
+ public ZBufferState() {}
+
+ /**
+ * <code>getFunction</code> returns the current depth function.
+ *
+ * @return the depth function currently used.
+ */
+ public TestFunction getFunction() {
+ return _function;
+ }
+
+ /**
+ * <code>setFunction</code> sets the depth function.
+ *
+ * @param function
+ * the depth function.
+ * @throws IllegalArgumentException
+ * if function is null
+ */
+ public void setFunction(final TestFunction function) {
+ if (function == null) {
+ throw new IllegalArgumentException("function can not be null.");
+ }
+ _function = function;
+ setNeedsRefresh(true);
+ }
+
+ /**
+ * <code>isWritable</code> returns if the depth mask is writable or not.
+ *
+ * @return true if the depth mask is writable, false otherwise.
+ */
+ public boolean isWritable() {
+ return _writable;
+ }
+
+ /**
+ * <code>setWritable</code> sets the depth mask writable or not.
+ *
+ * @param writable
+ * true to turn on depth writing, false otherwise.
+ */
+ public void setWritable(final boolean writable) {
+ _writable = writable;
+ setNeedsRefresh(true);
+ }
+
+ @Override
+ public StateType getType() {
+ return StateType.ZBuffer;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_function, "function", TestFunction.LessThan);
+ capsule.write(_writable, "writable", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _function = capsule.readEnum("function", TestFunction.class, TestFunction.LessThan);
+ _writable = capsule.readBoolean("writable", true);
+ }
+
+ @Override
+ public StateRecord createStateRecord() {
+ return new ZBufferStateRecord();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java
new file mode 100644
index 0000000..4687bba
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/BlendStateRecord.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import com.ardor3d.math.ColorRGBA;
+
+public class BlendStateRecord extends StateRecord {
+ public boolean blendEnabled = false;
+ public boolean testEnabled = false;
+
+ // RGB or primary
+ public int srcFactorRGB = -1;
+ public int dstFactorRGB = -1;
+ public int blendEqRGB = -1;
+
+ // Alpha (if supported)
+ public int srcFactorAlpha = -1;
+ public int dstFactorAlpha = -1;
+ public int blendEqAlpha = -1;
+
+ public int alphaFunc = -1;
+ public float alphaRef = -1;
+
+ public ColorRGBA blendColor = new ColorRGBA(-1, -1, -1, -1);
+
+ // sample coverage
+ public boolean sampleAlphaToCoverageEnabled = false;
+ public boolean sampleAlphaToOneEnabled = false;
+ public boolean sampleCoverageEnabled = false;
+ public boolean sampleCoverageInverted = false;
+ public float sampleCoverage = 1f;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ blendEnabled = false;
+ testEnabled = false;
+
+ srcFactorRGB = -1;
+ dstFactorRGB = -1;
+ blendEqRGB = -1;
+
+ srcFactorAlpha = -1;
+ dstFactorAlpha = -1;
+ blendEqAlpha = -1;
+
+ alphaFunc = -1;
+ alphaRef = -1;
+
+ blendColor.set(-1, -1, -1, -1);
+
+ sampleAlphaToCoverageEnabled = false;
+ sampleAlphaToOneEnabled = false;
+ sampleCoverageEnabled = false;
+ sampleCoverageInverted = false;
+ sampleCoverage = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java
new file mode 100644
index 0000000..7dba025
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ClipStateRecord.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.DoubleBuffer;
+import java.util.Arrays;
+
+import com.ardor3d.renderer.state.ClipState;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class ClipStateRecord extends StateRecord {
+
+ public final boolean[] planeEnabled = new boolean[ClipState.MAX_CLIP_PLANES];
+ public final DoubleBuffer buf = BufferUtils.createDoubleBuffer(4);
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ Arrays.fill(planeEnabled, false);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java
new file mode 100644
index 0000000..559050a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ColorMaskStateRecord.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class ColorMaskStateRecord extends StateRecord {
+ public boolean red = true;
+ public boolean green = true;
+ public boolean blue = true;
+ public boolean alpha = true;
+
+ public boolean is(final boolean red, final boolean green, final boolean blue, final boolean alpha) {
+ if (this.alpha != alpha) {
+ return false;
+ } else if (this.red != red) {
+ return false;
+ } else if (this.green != green) {
+ return false;
+ } else if (this.blue != blue) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ public void set(final boolean red, final boolean green, final boolean blue, final boolean alpha) {
+ this.red = red;
+ this.green = green;
+ this.blue = blue;
+ this.alpha = alpha;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ red = green = blue = alpha = true;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java
new file mode 100644
index 0000000..55a39f8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/CullStateRecord.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import com.ardor3d.renderer.state.CullState.PolygonWind;
+
+public class CullStateRecord extends StateRecord {
+ public boolean enabled = false;
+ public int face = -1;
+ public PolygonWind windOrder = null;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ enabled = false;
+ face = -1;
+ windOrder = null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java
new file mode 100644
index 0000000..7217f88
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FogStateRecord.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class FogStateRecord extends StateRecord {
+
+ public boolean enabled = false;
+ public float fogStart = -1;
+ public float fogEnd = -1;
+ public float density = -1;
+ public int fogMode = -1;
+ public int fogHint = -1;
+ public ColorRGBA fogColor = null;
+ public FloatBuffer colorBuff = null;
+ public FogState.CoordinateSource source = null;
+
+ public FogStateRecord() {
+ fogColor = new ColorRGBA(0, 0, 0, -1);
+ colorBuff = BufferUtils.createColorBuffer(1);
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ enabled = false;
+ fogStart = -1;
+ fogEnd = -1;
+ density = -1;
+ fogMode = -1;
+ fogHint = -1;
+ fogColor.set(0, 0, 0, -1);
+ source = null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java
new file mode 100644
index 0000000..3c5e6bc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/FragmentProgramStateRecord.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import com.ardor3d.renderer.state.FragmentProgramState;
+
+public class FragmentProgramStateRecord extends StateRecord {
+ private FragmentProgramState reference = null;
+
+ public FragmentProgramState getReference() {
+ return reference;
+ }
+
+ public void setReference(final FragmentProgramState reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ reference = null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java
new file mode 100644
index 0000000..cee7c73
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightRecord.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector4;
+
+public class LightRecord extends StateRecord {
+ public ColorRGBA ambient = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA diffuse = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA specular = new ColorRGBA(-1, -1, -1, -1);
+ private float constant = -1;
+ private float linear = -1;
+ private float quadratic = -1;
+ private float spotExponent = -1;
+ private float spotCutoff = -1;
+ private boolean enabled = false;
+
+ public Vector4 position = new Vector4();
+ public Matrix4 modelViewMatrix = new Matrix4();
+
+ private boolean attenuate;
+
+ public boolean isAttenuate() {
+ return attenuate;
+ }
+
+ public void setAttenuate(final boolean attenuate) {
+ this.attenuate = attenuate;
+ }
+
+ public float getConstant() {
+ return constant;
+ }
+
+ public void setConstant(final float constant) {
+ this.constant = constant;
+ }
+
+ public float getLinear() {
+ return linear;
+ }
+
+ public void setLinear(final float linear) {
+ this.linear = linear;
+ }
+
+ public float getQuadratic() {
+ return quadratic;
+ }
+
+ public void setQuadratic(final float quadratic) {
+ this.quadratic = quadratic;
+ }
+
+ public float getSpotExponent() {
+ return spotExponent;
+ }
+
+ public void setSpotExponent(final float exponent) {
+ spotExponent = exponent;
+ }
+
+ public float getSpotCutoff() {
+ return spotCutoff;
+ }
+
+ public void setSpotCutoff(final float spotCutoff) {
+ this.spotCutoff = spotCutoff;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(final boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ ambient.set(-1, -1, -1, -1);
+ diffuse.set(-1, -1, -1, -1);
+ specular.set(-1, -1, -1, -1);
+ constant = -1;
+ linear = -1;
+ quadratic = -1;
+ spotExponent = -1;
+ spotCutoff = -1;
+ enabled = false;
+
+ position.set(-1, -1, -1, -1);
+ modelViewMatrix.setIdentity();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java
new file mode 100644
index 0000000..3a04e45
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LightStateRecord.java
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class LightStateRecord extends StateRecord {
+ private final List<LightRecord> lightList = new ArrayList<LightRecord>();
+ private int lightMask;
+ private int backLightMask;
+ private boolean twoSidedOn;
+ public ColorRGBA globalAmbient = new ColorRGBA(-1, -1, -1, -1);
+ private boolean enabled;
+ private boolean localViewer;
+ private boolean separateSpecular;
+
+ // buffer for light colors.
+ public FloatBuffer lightBuffer = BufferUtils.createColorBuffer(1);
+
+ public int getBackLightMask() {
+ return backLightMask;
+ }
+
+ public void setBackLightMask(final int backLightMask) {
+ this.backLightMask = backLightMask;
+ }
+
+ public LightRecord getLightRecord(final int index) {
+ if (lightList.size() <= index) {
+ return null;
+ }
+
+ return lightList.get(index);
+ }
+
+ public void setLightRecord(final LightRecord lr, final int index) {
+ while (lightList.size() <= index) {
+ lightList.add(null);
+ }
+
+ lightList.set(index, lr);
+ }
+
+ public int getLightMask() {
+ return lightMask;
+ }
+
+ public void setLightMask(final int lightMask) {
+ this.lightMask = lightMask;
+ }
+
+ public boolean isTwoSidedOn() {
+ return twoSidedOn;
+ }
+
+ public void setTwoSidedOn(final boolean twoSidedOn) {
+ this.twoSidedOn = twoSidedOn;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(final boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isLocalViewer() {
+ return localViewer;
+ }
+
+ public void setLocalViewer(final boolean localViewer) {
+ this.localViewer = localViewer;
+ }
+
+ public boolean isSeparateSpecular() {
+ return separateSpecular;
+ }
+
+ public void setSeparateSpecular(final boolean seperateSpecular) {
+ separateSpecular = seperateSpecular;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ for (final LightRecord record : lightList) {
+ record.invalidate();
+ }
+
+ lightMask = -1;
+ backLightMask = -1;
+ twoSidedOn = false;
+ enabled = false;
+ localViewer = false;
+ separateSpecular = false;
+ globalAmbient.set(-1, -1, -1, -1);
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ for (final LightRecord record : lightList) {
+ record.validate();
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java
new file mode 100644
index 0000000..64a1c1b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/LineRecord.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class LineRecord extends StateRecord {
+ public boolean smoothed = false;
+ public boolean stippled = false;
+ public int smoothHint = -1;
+ public float width = -1;
+ public int stippleFactor = -1;
+ public short stipplePattern = -1;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ smoothed = false;
+ stippled = false;
+ smoothHint = -1;
+ width = -1;
+ stippleFactor = -1;
+ stipplePattern = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java
new file mode 100644
index 0000000..543649f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/MaterialStateRecord.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.FloatBuffer;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
+import com.ardor3d.renderer.state.MaterialState.MaterialFace;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class MaterialStateRecord extends StateRecord {
+ private static final Logger logger = Logger.getLogger(MaterialStateRecord.class.getName());
+
+ public ColorRGBA frontAmbient = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA frontDiffuse = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA frontSpecular = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA frontEmissive = new ColorRGBA(-1, -1, -1, -1);
+ public float frontShininess = Float.NEGATIVE_INFINITY;
+
+ public ColorRGBA backAmbient = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA backDiffuse = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA backSpecular = new ColorRGBA(-1, -1, -1, -1);
+ public ColorRGBA backEmissive = new ColorRGBA(-1, -1, -1, -1);
+ public float backShininess = Float.NEGATIVE_INFINITY;
+
+ public ColorMaterial colorMaterial = null;
+ public MaterialFace colorMaterialFace = null;
+
+ public FloatBuffer tempColorBuff = BufferUtils.createColorBuffer(1);
+
+ public boolean isSetColor(final MaterialFace face, final ColorMaterial glMatColor, final ReadOnlyColorRGBA color,
+ final MaterialStateRecord record) {
+ if (face == MaterialFace.Front) {
+ switch (glMatColor) {
+ case Ambient:
+ return color.equals(frontAmbient);
+ case Diffuse:
+ return color.equals(frontDiffuse);
+ case Specular:
+ return color.equals(frontSpecular);
+ case Emissive:
+ return color.equals(frontEmissive);
+ default:
+ logger.warning("bad isSetColor");
+ }
+ } else if (face == MaterialFace.FrontAndBack) {
+ switch (glMatColor) {
+ case Ambient:
+ return color.equals(frontAmbient) && color.equals(backAmbient);
+ case Diffuse:
+ return color.equals(frontDiffuse) && color.equals(backDiffuse);
+ case Specular:
+ return color.equals(frontSpecular) && color.equals(backSpecular);
+ case Emissive:
+ return color.equals(frontEmissive) && color.equals(backEmissive);
+ default:
+ logger.warning("bad isSetColor");
+ }
+ } else if (face == MaterialFace.Back) {
+ switch (glMatColor) {
+ case Ambient:
+ return color.equals(backAmbient);
+ case Diffuse:
+ return color.equals(backDiffuse);
+ case Specular:
+ return color.equals(backSpecular);
+ case Emissive:
+ return color.equals(backEmissive);
+ default:
+ logger.warning("bad isSetColor");
+ }
+ }
+ return false;
+ }
+
+ public boolean isSetShininess(final MaterialFace face, final float shininess, final MaterialStateRecord record) {
+ if (face == MaterialFace.Front) {
+ return shininess == frontShininess;
+ } else if (face == MaterialFace.FrontAndBack) {
+ return shininess == frontShininess && shininess == backShininess;
+ } else if (face == MaterialFace.Back) {
+ return shininess == backShininess;
+ }
+ return false;
+ }
+
+ public void setColor(final MaterialFace face, final ColorMaterial glMatColor, final ReadOnlyColorRGBA color) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ switch (glMatColor) {
+ case Ambient:
+ frontAmbient.set(color);
+ break;
+ case Diffuse:
+ frontDiffuse.set(color);
+ break;
+ case Specular:
+ frontSpecular.set(color);
+ break;
+ case Emissive:
+ frontEmissive.set(color);
+ break;
+ default:
+ logger.warning("bad setColor");
+ }
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ switch (glMatColor) {
+ case Ambient:
+ backAmbient.set(color);
+ break;
+ case Diffuse:
+ backDiffuse.set(color);
+ break;
+ case Specular:
+ backSpecular.set(color);
+ break;
+ case Emissive:
+ backEmissive.set(color);
+ break;
+ default:
+ logger.warning("bad setColor");
+ }
+ }
+ }
+
+ public void resetColorsForCM(final MaterialFace face, final ColorMaterial glMatColor) {
+ if (face == MaterialFace.Front || face == MaterialFace.FrontAndBack) {
+ switch (glMatColor) {
+ case Ambient:
+ frontAmbient.set(-1, -1, -1, -1);
+ break;
+ case Diffuse:
+ frontDiffuse.set(-1, -1, -1, -1);
+ break;
+ case AmbientAndDiffuse:
+ frontAmbient.set(-1, -1, -1, -1);
+ frontDiffuse.set(-1, -1, -1, -1);
+ break;
+ case Emissive:
+ frontEmissive.set(-1, -1, -1, -1);
+ break;
+ case Specular:
+ frontSpecular.set(-1, -1, -1, -1);
+ break;
+ }
+ }
+ if (face == MaterialFace.Back || face == MaterialFace.FrontAndBack) {
+ switch (glMatColor) {
+ case Ambient:
+ backAmbient.set(-1, -1, -1, -1);
+ break;
+ case Diffuse:
+ backDiffuse.set(-1, -1, -1, -1);
+ break;
+ case AmbientAndDiffuse:
+ backAmbient.set(-1, -1, -1, -1);
+ backDiffuse.set(-1, -1, -1, -1);
+ break;
+ case Emissive:
+ backEmissive.set(-1, -1, -1, -1);
+ break;
+ case Specular:
+ backSpecular.set(-1, -1, -1, -1);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ frontAmbient.set(-1, -1, -1, -1);
+ frontDiffuse.set(-1, -1, -1, -1);
+ frontSpecular.set(-1, -1, -1, -1);
+ frontEmissive.set(-1, -1, -1, -1);
+ frontShininess = Float.NEGATIVE_INFINITY;
+
+ backAmbient.set(-1, -1, -1, -1);
+ backDiffuse.set(-1, -1, -1, -1);
+ backSpecular.set(-1, -1, -1, -1);
+ backEmissive.set(-1, -1, -1, -1);
+ backShininess = Float.NEGATIVE_INFINITY;
+
+ colorMaterial = null;
+ colorMaterialFace = null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java
new file mode 100644
index 0000000..b982024
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/OffsetStateRecord.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.util.EnumSet;
+
+import com.ardor3d.renderer.state.OffsetState.OffsetType;
+
+public class OffsetStateRecord extends StateRecord {
+ public boolean enabled = false;
+ public float factor = 0;
+ public float units = 0;
+ public EnumSet<OffsetType> enabledOffsets = EnumSet.noneOf(OffsetType.class);
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ enabled = false;
+ factor = 0;
+ units = 0;
+ enabledOffsets.clear();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java
new file mode 100644
index 0000000..9f51cbc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/RendererRecord.java
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.util.Stack;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyRectangle2;
+import com.ardor3d.renderer.DrawBufferTarget;
+
+public class RendererRecord extends StateRecord {
+ private int _matrixMode = -1;
+ private int _currentElementVboId = -1, _currentVboId = -1;
+ private boolean _matrixValid;
+ private boolean _vboValid;
+ private boolean _elementVboValid;
+ private boolean _clippingTestValid;
+ private boolean _clippingTestEnabled;
+ private transient final ColorRGBA _tempColor = new ColorRGBA();
+ private DrawBufferTarget _drawBufferTarget = null;
+ private final Stack<ReadOnlyRectangle2> _clips = new Stack<ReadOnlyRectangle2>();
+ private int _normalMode = -1; // signifies disabled
+ private int _enabledTextures = 0;
+ private boolean _texturesValid = false;
+ private int _currentTextureArraysUnit = 0;
+
+ @Override
+ public void invalidate() {
+ invalidateMatrix();
+ invalidateVBO();
+ _drawBufferTarget = null;
+ _clippingTestValid = false;
+ _texturesValid = false;
+ _normalMode = -1;
+ _currentTextureArraysUnit = -1;
+ }
+
+ @Override
+ public void validate() {
+ // ignore - validate per item or locally
+ }
+
+ public void invalidateMatrix() {
+ _matrixValid = false;
+ _matrixMode = -1;
+ }
+
+ public void invalidateVBO() {
+ _vboValid = false;
+ _elementVboValid = false;
+ _currentElementVboId = _currentVboId = -1;
+ }
+
+ public int getMatrixMode() {
+ return _matrixMode;
+ }
+
+ public void setMatrixMode(final int matrixMode) {
+ _matrixMode = matrixMode;
+ }
+
+ public int getCurrentElementVboId() {
+ return _currentElementVboId;
+ }
+
+ public void setCurrentElementVboId(final int currentElementVboId) {
+ _currentElementVboId = currentElementVboId;
+ }
+
+ public int getCurrentVboId() {
+ return _currentVboId;
+ }
+
+ public void setCurrentVboId(final int currentVboId) {
+ _currentVboId = currentVboId;
+ }
+
+ public boolean isMatrixValid() {
+ return _matrixValid;
+ }
+
+ public void setMatrixValid(final boolean matrixValid) {
+ _matrixValid = matrixValid;
+ }
+
+ public boolean isVboValid() {
+ return _vboValid;
+ }
+
+ public void setVboValid(final boolean vboValid) {
+ _vboValid = vboValid;
+ }
+
+ public boolean isElementVboValid() {
+ return _elementVboValid;
+ }
+
+ public void setElementVboValid(final boolean elementVboValid) {
+ _elementVboValid = elementVboValid;
+ }
+
+ public ColorRGBA getTempColor() {
+ return _tempColor;
+ }
+
+ public DrawBufferTarget getDrawBufferTarget() {
+ return _drawBufferTarget;
+ }
+
+ public void setDrawBufferTarget(final DrawBufferTarget target) {
+ _drawBufferTarget = target;
+ }
+
+ public Stack<ReadOnlyRectangle2> getScissorClips() {
+ return _clips;
+ }
+
+ public boolean isClippingTestEnabled() {
+ return _clippingTestEnabled;
+ }
+
+ public void setClippingTestEnabled(final boolean enabled) {
+ _clippingTestEnabled = enabled;
+ }
+
+ public boolean isClippingTestValid() {
+ return _clippingTestValid;
+ }
+
+ public void setClippingTestValid(final boolean valid) {
+ _clippingTestValid = valid;
+ }
+
+ public int getEnabledTextures() {
+ return _enabledTextures;
+ }
+
+ public void setEnabledTextures(final int enabledTextures) {
+ _enabledTextures = enabledTextures;
+ }
+
+ public int getNormalMode() {
+ return _normalMode;
+ }
+
+ public void setNormalMode(final int normalMode) {
+ _normalMode = normalMode;
+ }
+
+ public boolean isTexturesValid() {
+ return _texturesValid;
+ }
+
+ public void setTexturesValid(final boolean texturesValid) {
+ _texturesValid = texturesValid;
+ }
+
+ public int getCurrentTextureArraysUnit() {
+ return _currentTextureArraysUnit;
+ }
+
+ public void setCurrentTextureArraysUnit(final int currentTextureArraysUnit) {
+ _currentTextureArraysUnit = currentTextureArraysUnit;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java
new file mode 100644
index 0000000..b542051
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShaderObjectsStateRecord.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.util.List;
+
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.util.shader.ShaderVariable;
+import com.google.common.collect.Lists;
+
+public class ShaderObjectsStateRecord extends StateRecord {
+ // XXX NOTE: This is non-standard. Due to the fact that shader implementations
+ // XXX will be changed this record simply makes use of the old reference
+ // XXX checking system.
+ GLSLShaderObjectsState reference = null;
+
+ public List<ShaderVariable> enabledAttributes = Lists.newArrayList();
+
+ public int shaderId = -1;
+
+ public GLSLShaderObjectsState getReference() {
+ return reference;
+ }
+
+ public void setReference(final GLSLShaderObjectsState reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ reference = null;
+ shaderId = -1;
+ enabledAttributes.clear();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java
new file mode 100644
index 0000000..a561eed
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ShadingStateRecord.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class ShadingStateRecord extends StateRecord {
+ public int lastShade = -1;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ lastShade = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java
new file mode 100644
index 0000000..1c6aead
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StateRecord.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public abstract class StateRecord {
+
+ // If false, don't trust any of the values in this record.
+ protected boolean valid = false;
+
+ /**
+ * @return true if ardor3d thinks this state holds trusted information about the opengl state machine.
+ */
+ public boolean isValid() {
+ return valid;
+ }
+
+ /**
+ * Invalidate this record - iow, we don't trust this record's information about the opengl state machine.
+ */
+ public void invalidate() {
+ valid = false;
+ }
+
+ /**
+ * Validate this record - iow, we trust this record's information about the opengl state machine.
+ */
+ public void validate() {
+ valid = true;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java
new file mode 100644
index 0000000..de76d39
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/StencilStateRecord.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class StencilStateRecord extends StateRecord {
+ public boolean enabled = false;
+
+ // public int[] func = { -1, -1, -1 };
+ // public int[] ref = { -1, -1, -1 };
+ // public int[] writeMask = { -1, -1, -1 };
+ // public int[] funcMask = { -1, -1, -1 };
+ // public int[] fail = { -1, -1, -1 };
+ // public int[] zfail = { -1, -1, -1 };
+ // public int[] zpass = { -1, -1, -1 };
+
+ public boolean useTwoSided = false;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ enabled = false;
+ useTwoSided = false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java
new file mode 100644
index 0000000..3baf5d5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureRecord.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class TextureRecord extends StateRecord {
+
+ public int wrapS, wrapT, wrapR;
+ public int magFilter, minFilter;
+ public int depthTextureMode, depthTextureCompareFunc, depthTextureCompareMode;
+ public float anisoLevel = -1;
+ public static FloatBuffer colorBuffer = BufferUtils.createColorBuffer(1);
+ public ColorRGBA borderColor = new ColorRGBA(-1, -1, -1, -1);
+
+ public TextureRecord() {}
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ wrapS = wrapT = wrapR = 0;
+ magFilter = minFilter = 0;
+ depthTextureMode = depthTextureCompareFunc = depthTextureCompareMode = 0;
+ anisoLevel = -1;
+ borderColor.set(-1, -1, -1, -1);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java
new file mode 100644
index 0000000..062c555
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureStateRecord.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.util.Collection;
+import java.util.HashMap;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector4;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Maps;
+
+public class TextureStateRecord extends StateRecord {
+
+ public FloatBuffer plane = BufferUtils.createFloatBuffer(4);
+
+ public static final float[] DEFAULT_S_PLANE = { 1f, 0f, 0f, 0f };
+ public static final float[] DEFAULT_T_PLANE = { 0f, 1f, 0f, 0f };
+ public static final float[] DEFAULT_R_PLANE = { 0f, 0f, 1f, 0f };
+ public static final float[] DEFAULT_Q_PLANE = { 0f, 0f, 0f, 1f };
+
+ public HashMap<Integer, TextureRecord> textures;
+ public TextureUnitRecord[] units;
+ public int hint = -1;
+ public int currentUnit = -1;
+
+ /**
+ * temporary rotation axis vector to flatline memory usage.
+ */
+ public final Vector3 tmp_rotation1 = new Vector3();
+
+ /**
+ * temporary matrix buffer to flatline memory usage.
+ */
+ public final DoubleBuffer tmp_matrixBuffer = BufferUtils.createDoubleBuffer(16);
+
+ public TextureStateRecord() {
+ textures = Maps.newHashMap();
+ units = new TextureUnitRecord[TextureState.MAX_TEXTURES];
+ for (int i = 0; i < units.length; i++) {
+ units[i] = new TextureUnitRecord();
+ }
+ }
+
+ public TextureRecord getTextureRecord(final Integer textureId, final Texture.Type type) {
+ TextureRecord tr = textures.get(textureId);
+ if (tr == null) {
+ tr = new TextureRecord();
+ textures.put(textureId, tr);
+ }
+ return tr;
+ }
+
+ public void removeTextureRecord(final Integer textureId) {
+ if (textureId == null) {
+ return;
+ }
+ textures.remove(textureId);
+ for (int i = 0; i < units.length; i++) {
+ if (units[i].boundTexture == textureId.intValue()) {
+ units[i].boundTexture = -1;
+ }
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ currentUnit = -1;
+ hint = -1;
+ final Collection<TextureRecord> texs = textures.values();
+ for (final TextureRecord tr : texs) {
+ tr.invalidate();
+ }
+ for (int i = 0; i < units.length; i++) {
+ units[i].invalidate();
+ }
+ }
+
+ @Override
+ public void validate() {
+ super.validate();
+ final Collection<TextureRecord> texs = textures.values();
+ for (final TextureRecord tr : texs) {
+ tr.validate();
+ }
+ for (int i = 0; i < units.length; i++) {
+ units[i].validate();
+ }
+ }
+
+ public void prepPlane(final ReadOnlyVector4 planeEq, final float[] defaultVal) {
+ if (planeEq == null) {
+ plane.put(defaultVal);
+ } else {
+ plane.put(planeEq.getXf());
+ plane.put(planeEq.getYf());
+ plane.put(planeEq.getZf());
+ plane.put(planeEq.getWf());
+ }
+ plane.rewind();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java
new file mode 100644
index 0000000..64a0133
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/TextureUnitRecord.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import java.util.Arrays;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.ApplyMode;
+import com.ardor3d.image.Texture.CombinerFunctionAlpha;
+import com.ardor3d.image.Texture.CombinerFunctionRGB;
+import com.ardor3d.image.Texture.CombinerOperandAlpha;
+import com.ardor3d.image.Texture.CombinerOperandRGB;
+import com.ardor3d.image.Texture.CombinerScale;
+import com.ardor3d.image.Texture.CombinerSource;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.math.Vector3;
+
+/**
+ * Represents a texture unit in opengl
+ */
+public class TextureUnitRecord extends StateRecord {
+ public boolean enabled[] = new boolean[Texture.Type.values().length];
+ public Matrix4 texMatrix = new Matrix4();
+ public Vector3 texScale = new Vector3();
+ public int boundTexture = -1;
+ public ApplyMode envMode = null;
+ public CombinerScale envRGBScale = null;
+ public CombinerScale envAlphaScale = null;
+ public ColorRGBA blendColor = new ColorRGBA(-1, -1, -1, -1);
+ public CombinerFunctionRGB rgbCombineFunc = null;
+ public CombinerFunctionAlpha alphaCombineFunc = null;
+ public CombinerSource combSrcRGB0 = null, combSrcRGB1 = null, combSrcRGB2 = null;
+ public CombinerOperandRGB combOpRGB0 = null, combOpRGB1 = null, combOpRGB2 = null;
+ public CombinerSource combSrcAlpha0 = null, combSrcAlpha1 = null, combSrcAlpha2 = null;
+ public CombinerOperandAlpha combOpAlpha0 = null, combOpAlpha1 = null, combOpAlpha2 = null;
+ public boolean identityMatrix = true;
+ public float lodBias = 0f;
+
+ public boolean textureGenQ = false, textureGenR = false, textureGenS = false, textureGenT = false;
+ public int textureGenQMode = -1, textureGenRMode = -1, textureGenSMode = -1, textureGenTMode = -1;
+
+ public TextureUnitRecord() {}
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ Arrays.fill(enabled, false);
+ texMatrix.setIdentity();
+ texScale.zero();
+ boundTexture = -1;
+ lodBias = 0;
+ envMode = null;
+ envRGBScale = null;
+ envAlphaScale = null;
+ blendColor.set(-1, -1, -1, -1);
+ rgbCombineFunc = null;
+ alphaCombineFunc = null;
+ combSrcRGB0 = null;
+ combSrcRGB1 = null;
+ combSrcRGB2 = null;
+ combOpRGB0 = null;
+ combOpRGB1 = null;
+ combOpRGB2 = null;
+ combSrcAlpha0 = null;
+ combSrcAlpha1 = null;
+ combSrcAlpha2 = null;
+ combOpAlpha0 = null;
+ combOpAlpha1 = null;
+ combOpAlpha2 = null;
+ identityMatrix = false;
+
+ textureGenQ = false;
+ textureGenR = false;
+ textureGenS = false;
+ textureGenT = false;
+ textureGenQMode = -1;
+ textureGenRMode = -1;
+ textureGenSMode = -1;
+ textureGenTMode = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java
new file mode 100644
index 0000000..f630739
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/VertexProgramStateRecord.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+import com.ardor3d.renderer.state.VertexProgramState;
+
+public class VertexProgramStateRecord extends StateRecord {
+ // XXX NOTE: This is non-standard. Due to the fact that shader
+ // implementations
+ // XXX will be changed this record simply makes use of the old reference
+ // XXX checking system.
+ VertexProgramState reference = null;
+
+ public VertexProgramState getReference() {
+ return reference;
+ }
+
+ public void setReference(final VertexProgramState reference) {
+ this.reference = reference;
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ reference = null;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java
new file mode 100644
index 0000000..1f27367
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/WireframeStateRecord.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class WireframeStateRecord extends StateRecord {
+
+ public int frontMode = -1;
+ public int backMode = -1;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+
+ frontMode = -1;
+ backMode = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java
new file mode 100644
index 0000000..88c63c1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/renderer/state/record/ZBufferStateRecord.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.renderer.state.record;
+
+public class ZBufferStateRecord extends StateRecord {
+ public boolean depthTest = false;
+ public boolean writable = false;
+ public int depthFunc = -1;
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ depthTest = false;
+ writable = false;
+ depthFunc = -1;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java
new file mode 100644
index 0000000..9274fb0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/AbstractBufferData.java
@@ -0,0 +1,320 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.nio.Buffer;
+import java.util.Map;
+import java.util.Set;
+
+import com.ardor3d.renderer.ContextCleanListener;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.RendererCallable;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.ContextIdReference;
+import com.ardor3d.util.GameTaskQueueManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.MapMaker;
+import com.google.common.collect.Multimap;
+
+public abstract class AbstractBufferData<T extends Buffer> {
+
+ private static Map<AbstractBufferData<?>, Object> _identityCache = new MapMaker().weakKeys().makeMap();
+ private static final Object STATIC_REF = new Object();
+
+ private static ReferenceQueue<AbstractBufferData<?>> _vboRefQueue = new ReferenceQueue<AbstractBufferData<?>>();
+
+ static {
+ ContextManager.addContextCleanListener(new ContextCleanListener() {
+ public void cleanForContext(final RenderContext renderContext) {
+ AbstractBufferData.cleanAllVBOs(null, renderContext);
+ }
+ });
+ }
+
+ protected transient ContextIdReference<AbstractBufferData<T>> _vboIdCache;
+
+ /** Buffer holding the data. */
+ protected T _buffer;
+
+ /** Access mode of the buffer when using Vertex Buffer Objects. */
+ public enum VBOAccessMode {
+ StaticDraw, StaticCopy, StaticRead, StreamDraw, StreamCopy, StreamRead, DynamicDraw, DynamicCopy, DynamicRead
+ }
+
+ /** VBO Access mode for this buffer. */
+ protected VBOAccessMode _vboAccessMode = VBOAccessMode.StaticDraw;
+
+ /** Flag for notifying the renderer that the VBO buffer needs to be updated. */
+ protected boolean _needsRefresh = false;
+
+ AbstractBufferData() {
+ _identityCache.put(this, STATIC_REF);
+ }
+
+ /**
+ * @return the number of bytes per entry in the buffer. For example, an IntBuffer would return 4.
+ */
+ public abstract int getByteCount();
+
+ /**
+ * Gets the count.
+ *
+ * @return the count
+ */
+ public int getBufferLimit() {
+ if (_buffer != null) {
+ return _buffer.limit();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Gets the count.
+ *
+ * @return the count
+ */
+ public int getBufferCapacity() {
+ if (_buffer != null) {
+ return _buffer.capacity();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Get the buffer holding the data.
+ *
+ * @return the buffer
+ */
+ public T getBuffer() {
+ return _buffer;
+ }
+
+ /**
+ * Set the buffer holding the data.
+ *
+ * @param buffer
+ * the buffer to set
+ */
+ public void setBuffer(final T buffer) {
+ _buffer = buffer;
+ }
+
+ /**
+ * @param glContext
+ * the object representing the OpenGL context a vbo belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the vbo id of a vbo in the given context. If the vbo is not found in the given context, 0 is returned.
+ */
+ public int getVBOID(final Object glContext) {
+ if (_vboIdCache != null) {
+ final Integer id = _vboIdCache.getValue(glContext);
+ if (id != null) {
+ return id.intValue();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Removes any vbo id from this buffer's data for the given OpenGL context.
+ *
+ * @param glContext
+ * the object representing the OpenGL context a vbo would belong to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the id removed or 0 if not found.
+ */
+ public int removeVBOID(final Object glContext) {
+ if (_vboIdCache != null) {
+ return _vboIdCache.removeValue(glContext);
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Sets the id for a vbo based on this buffer's data in regards to the given OpenGL context.
+ *
+ * @param glContextRep
+ * the object representing the OpenGL context a vbo belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @param vboId
+ * the vbo id of a vbo. To be valid, this must be not equals to 0.
+ * @throws IllegalArgumentException
+ * if vboId is less than or equal to 0.
+ */
+ public void setVBOID(final Object glContextRep, final int vboId) {
+ if (vboId == 0) {
+ throw new IllegalArgumentException("vboId must != 0");
+ }
+
+ if (_vboIdCache == null) {
+ _vboIdCache = new ContextIdReference<AbstractBufferData<T>>(this, _vboRefQueue);
+ }
+ _vboIdCache.put(glContextRep, vboId);
+ }
+
+ public VBOAccessMode getVboAccessMode() {
+ return _vboAccessMode;
+ }
+
+ public void setVboAccessMode(final VBOAccessMode vboAccessMode) {
+ this._vboAccessMode = vboAccessMode;
+ }
+
+ public boolean isNeedsRefresh() {
+ return _needsRefresh;
+ }
+
+ public void setNeedsRefresh(final boolean needsRefresh) {
+ this._needsRefresh = needsRefresh;
+ }
+
+ public static void cleanAllVBOs(final Renderer deleter) {
+ final Multimap<Object, Integer> idMap = ArrayListMultimap.create();
+
+ // gather up expired vbos... these don't exist in our cache
+ gatherGCdIds(idMap);
+
+ // Walk through the cached items and delete those too.
+ for (final AbstractBufferData<?> buf : _identityCache.keySet()) {
+ if (buf._vboIdCache != null) {
+ if (Constants.useMultipleContexts) {
+ final Set<Object> contextObjects = buf._vboIdCache.getContextObjects();
+ for (final Object o : contextObjects) {
+ // Add id to map
+ idMap.put(o, buf.getVBOID(o));
+ }
+ } else {
+ idMap.put(ContextManager.getCurrentContext().getGlContextRep(), buf.getVBOID(null));
+ }
+ buf._vboIdCache.clear();
+ }
+ }
+
+ handleVBODelete(deleter, idMap);
+ }
+
+ public static void cleanAllVBOs(final Renderer deleter, final RenderContext context) {
+ final Multimap<Object, Integer> idMap = ArrayListMultimap.create();
+
+ // gather up expired vbos... these don't exist in our cache
+ gatherGCdIds(idMap);
+
+ final Object glRep = context.getGlContextRep();
+ // Walk through the cached items and delete those too.
+ for (final AbstractBufferData<?> buf : _identityCache.keySet()) {
+ // only worry about buffers that have received ids.
+ if (buf._vboIdCache != null) {
+ final Integer id = buf._vboIdCache.removeValue(glRep);
+ if (id != null && id.intValue() != 0) {
+ idMap.put(context.getGlContextRep(), id);
+ }
+ }
+ }
+
+ handleVBODelete(deleter, idMap);
+ }
+
+ /**
+ * Clean any VBO ids from the hardware, using the given Renderer object to do the work immediately, if given. If
+ * not, we will delete in the next execution of the appropriate context's game task render queue.
+ *
+ * @param deleter
+ * the Renderer to use. If null, execution will not occur immediately.
+ */
+ public static void cleanExpiredVBOs(final Renderer deleter) {
+ // gather up expired vbos...
+ final Multimap<Object, Integer> idMap = gatherGCdIds(null);
+
+ if (idMap != null) {
+ // send to be deleted (perhaps on next render.)
+ handleVBODelete(deleter, idMap);
+ }
+ }
+
+ /**
+ * @return a deep copy of this buffer data object
+ */
+ public abstract AbstractBufferData<T> makeCopy();
+
+ @SuppressWarnings("unchecked")
+ private static final Multimap<Object, Integer> gatherGCdIds(Multimap<Object, Integer> store) {
+ // Pull all expired vbos from ref queue and add to an id multimap.
+ ContextIdReference<AbstractBufferData<?>> ref;
+ while ((ref = (ContextIdReference<AbstractBufferData<?>>) _vboRefQueue.poll()) != null) {
+ if (Constants.useMultipleContexts) {
+ final Set<Object> contextObjects = ref.getContextObjects();
+ for (final Object o : contextObjects) {
+ // Add id to map
+ final Integer id = ref.getValue(o);
+ if (id != null) {
+ if (store == null) { // lazy init
+ store = ArrayListMultimap.create();
+ }
+ store.put(o, id);
+ }
+ }
+ } else {
+ final Integer id = ref.getValue(null);
+ if (id != null) {
+ if (store == null) { // lazy init
+ store = ArrayListMultimap.create();
+ }
+ store.put(ContextManager.getCurrentContext().getGlContextRep(), id);
+ }
+ }
+ ref.clear();
+ }
+
+ return store;
+ }
+
+ private static void handleVBODelete(final Renderer deleter, final Multimap<Object, Integer> idMap) {
+ Object currentGLRef = null;
+ // Grab the current context, if any.
+ if (deleter != null && ContextManager.getCurrentContext() != null) {
+ currentGLRef = ContextManager.getCurrentContext().getGlContextRep();
+ }
+ // For each affected context...
+ for (final Object glref : idMap.keySet()) {
+ // If we have a deleter and the context is current, immediately delete
+ if (deleter != null && glref.equals(currentGLRef)) {
+ deleter.deleteVBOs(idMap.get(glref));
+ }
+ // Otherwise, add a delete request to that context's render task queue.
+ else {
+ GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref)).render(
+ new RendererCallable<Void>() {
+ public Void call() throws Exception {
+ getRenderer().deleteVBOs(idMap.get(glref));
+ return null;
+ }
+ });
+ }
+ }
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _vboAccessMode = capsule.readEnum("vboAccessMode", VBOAccessMode.class, VBOAccessMode.StaticDraw);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_vboAccessMode, "vboAccessMode", VBOAccessMode.StaticDraw);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java
new file mode 100644
index 0000000..89d516e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ByteBufferData.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Simple data class storing a buffer of bytes
+ */
+public class ByteBufferData extends IndexBufferData<ByteBuffer> implements Savable {
+
+ /**
+ * Instantiates a new ByteBufferData.
+ */
+ public ByteBufferData() {}
+
+ /**
+ * Instantiates a new ByteBufferData with a buffer of the given size.
+ */
+ public ByteBufferData(final int size) {
+ this(BufferUtils.createByteBuffer(size));
+ }
+
+ /**
+ * Creates a new ByteBufferData.
+ *
+ * @param buffer
+ * Buffer holding the data. Must not be null.
+ */
+ public ByteBufferData(final ByteBuffer buffer) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("Buffer can not be null!");
+ }
+
+ _buffer = buffer;
+ }
+
+ public Class<? extends ByteBufferData> getClassTag() {
+ return getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _buffer = capsule.readByteBuffer("buffer", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_buffer, "buffer", null);
+ }
+
+ @Override
+ public int get() {
+ return _buffer.get() & 0xFF;
+ }
+
+ @Override
+ public int get(final int index) {
+ return _buffer.get(index) & 0xFF;
+ }
+
+ @Override
+ public ByteBufferData put(final int value) {
+ if (value < 0 || value >= 256) {
+ throw new IllegalArgumentException("Invalid value passed to byte buffer: " + value);
+ }
+ _buffer.put((byte) value);
+ return this;
+ }
+
+ @Override
+ public ByteBufferData put(final int index, final int value) {
+ if (value < 0 || value >= 256) {
+ throw new IllegalArgumentException("Invalid value passed to byte buffer: " + value);
+ }
+ _buffer.put(index, (byte) value);
+ return this;
+ }
+
+ @Override
+ public void put(final IndexBufferData<?> buf) {
+ if (buf instanceof ByteBufferData) {
+ _buffer.put((ByteBuffer) buf.getBuffer());
+ } else {
+ while (buf.getBuffer().hasRemaining()) {
+ put(buf.get());
+ }
+ }
+ }
+
+ @Override
+ public int getByteCount() {
+ return 1;
+ }
+
+ @Override
+ public ByteBuffer getBuffer() {
+ return _buffer;
+ }
+
+ @Override
+ public IntBuffer asIntBuffer() {
+ final ByteBuffer source = getBuffer().duplicate();
+ source.rewind();
+ final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit());
+ for (int i = 0, max = source.limit(); i < max; i++) {
+ buff.put(source.get() & 0xFF);
+ }
+ buff.flip();
+ return buff;
+ }
+
+ @Override
+ public ByteBufferData makeCopy() {
+ final ByteBufferData copy = new ByteBufferData();
+ copy._buffer = BufferUtils.clone(_buffer);
+ copy._vboAccessMode = _vboAccessMode;
+ return copy;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java
new file mode 100644
index 0000000..19d0a16
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferData.java
@@ -0,0 +1,146 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Simple data class storing a buffer of floats and a number that indicates how many floats to group together to make up
+ * a "tuple"
+ */
+public class FloatBufferData extends AbstractBufferData<FloatBuffer> implements Savable {
+
+ /** Specifies the number of coordinates per vertex. Must be 1 - 4. */
+ private int _valuesPerTuple;
+
+ /**
+ * Instantiates a new FloatBufferData.
+ */
+ public FloatBufferData() {}
+
+ /**
+ * Instantiates a new FloatBufferData with a buffer of the given size.
+ */
+ public FloatBufferData(final int size, final int valuesPerTuple) {
+ this(BufferUtils.createFloatBuffer(size), valuesPerTuple);
+ }
+
+ /**
+ * Creates a new FloatBufferData.
+ *
+ * @param buffer
+ * Buffer holding the data. Must not be null.
+ * @param valuesPerTuple
+ * Specifies the number of values per tuple. Can not be < 1.
+ */
+ public FloatBufferData(final FloatBuffer buffer, final int valuesPerTuple) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("Buffer can not be null!");
+ }
+
+ if (valuesPerTuple < 1) {
+ throw new IllegalArgumentException("valuesPerTuple must be greater than 1.");
+ }
+
+ _buffer = buffer;
+ _valuesPerTuple = valuesPerTuple;
+ }
+
+ @Override
+ public int getByteCount() {
+ return 4;
+ }
+
+ public int getTupleCount() {
+ return getBufferLimit() / _valuesPerTuple;
+ }
+
+ /**
+ * @return number of values per tuple
+ */
+ public int getValuesPerTuple() {
+ return _valuesPerTuple;
+ }
+
+ /**
+ * Set number of values per tuple. This method should only be used internally.
+ *
+ * @param valuesPerTuple
+ * number of values per tuple
+ */
+ void setValuesPerTuple(final int valuesPerTuple) {
+ _valuesPerTuple = valuesPerTuple;
+ }
+
+ /**
+ * Scale the data in this buffer by the given value(s)
+ *
+ * @param scales
+ * the scale values to use. The Nth buffer element is scaled by the (N % scales.length) scales element.
+ */
+ public void scaleData(final float... scales) {
+ _buffer.rewind();
+ for (int i = 0; i < _buffer.limit();) {
+ _buffer.put(_buffer.get(i) * scales[i % scales.length]);
+ i++;
+ }
+ _buffer.rewind();
+ }
+
+ /**
+ * Translate the data in this buffer by the given value(s)
+ *
+ * @param translates
+ * the translation values to use. The Nth buffer element is translated by the (N % translates.length)
+ * translates element.
+ */
+ public void translateData(final float... translates) {
+ _buffer.rewind();
+ for (int i = 0; i < _buffer.limit();) {
+ _buffer.put(_buffer.get(i) + translates[i % translates.length]);
+ i++;
+ }
+ _buffer.rewind();
+ }
+
+ @Override
+ public FloatBufferData makeCopy() {
+ final FloatBufferData copy = new FloatBufferData();
+ copy._buffer = BufferUtils.clone(_buffer);
+ copy._valuesPerTuple = _valuesPerTuple;
+ copy._vboAccessMode = _vboAccessMode;
+ return copy;
+ }
+
+ public Class<? extends FloatBufferData> getClassTag() {
+ return getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _buffer = capsule.readFloatBuffer("buffer", null);
+ _valuesPerTuple = capsule.readInt("valuesPerTuple", 0);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_buffer, "buffer", null);
+ capsule.write(_valuesPerTuple, "valuesPerTuple", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java
new file mode 100644
index 0000000..422dabe
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/FloatBufferDataUtil.java
@@ -0,0 +1,66 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class FloatBufferDataUtil {
+ public static FloatBufferData makeNew(final ReadOnlyVector2[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 2);
+ }
+
+ public static FloatBufferData makeNew(final ReadOnlyVector3[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 3);
+ }
+
+ public static FloatBufferData makeNew(final float[] coords) {
+ if (coords == null) {
+ return null;
+ }
+
+ return new FloatBufferData(BufferUtils.createFloatBuffer(coords), 1);
+ }
+
+ /**
+ * Check an incoming TexCoords object for null and correct size.
+ *
+ * @param tc
+ * @param vertexCount
+ * @param perVert
+ * @return tc if it is not null and the right size, otherwise it will be a new TexCoords object.
+ */
+ public static FloatBufferData ensureSize(final FloatBufferData tc, final int vertexCount, final int coordsPerVertex) {
+ if (tc == null) {
+ return new FloatBufferData(BufferUtils.createFloatBuffer(vertexCount * coordsPerVertex), coordsPerVertex);
+ }
+
+ if (tc.getBuffer().limit() == coordsPerVertex * vertexCount && tc.getValuesPerTuple() == coordsPerVertex) {
+ tc.getBuffer().rewind();
+ return tc;
+ } else if (tc.getBuffer().limit() == coordsPerVertex * vertexCount) {
+ tc.setValuesPerTuple(coordsPerVertex);
+ } else {
+ return new FloatBufferData(BufferUtils.createFloatBuffer(vertexCount * coordsPerVertex), coordsPerVertex);
+ }
+
+ return tc;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java
new file mode 100644
index 0000000..9c39fae
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IndexBufferData.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+public abstract class IndexBufferData<T extends Buffer> extends AbstractBufferData<T> {
+
+ /**
+ * @return the next value from this object, as an int, incrementing our position by 1 entry. Buffer types smaller
+ * than an int should return unsigned values.
+ */
+ public abstract int get();
+
+ /**
+ * @param index
+ * the absolute position to get our value from. This is in entries, not bytes, and is 0 based. So for a
+ * ShortBuffer, 2 would be the 3rd short from the beginning, etc.
+ * @return the value from this object, as an int, at the given absolute entry position. Buffer types smaller than an
+ * int should return unsigned values.
+ */
+ public abstract int get(int index);
+
+ /**
+ * @return a new, non-direct IntBuffer containing a snapshot of the contents of this buffer.
+ */
+ public abstract IntBuffer asIntBuffer();
+
+ /**
+ * Sets the value of this buffer at the current position, incrementing our position by 1 entry.
+ *
+ * @param value
+ * the value to place into this object at the current position.
+ * @return this object, for chaining.
+ */
+ public abstract IndexBufferData<T> put(int value);
+
+ /**
+ * Sets the value of this buffer at the given index.
+ *
+ * @param index
+ * the absolute position to put our value at. This is in entries, not bytes, and is 0 based. So for a
+ * ShortBuffer, 2 would be the 3rd short from the beginning, etc.
+ * @param value
+ * the value to place into this object
+ * @return
+ */
+ public abstract IndexBufferData<T> put(int index, int value);
+
+ /**
+ * Write the contents of the given IndexBufferData into this one. Note that data conversion is handled using the
+ * get/put methods in IndexBufferData.
+ *
+ * @param buf
+ * the source buffer object.
+ */
+ public abstract void put(IndexBufferData<?> buf);
+
+ /**
+ * Get the underlying nio buffer.
+ */
+ @Override
+ public abstract T getBuffer();
+
+ /**
+ * @see Buffer#remaining();
+ */
+ public int remaining() {
+ return getBuffer().remaining();
+ }
+
+ /**
+ * @see Buffer#position();
+ */
+ public int position() {
+ return getBuffer().position();
+ }
+
+ /**
+ * @see Buffer#position(int);
+ */
+ public void position(final int position) {
+ getBuffer().position(position);
+ }
+
+ /**
+ * @see Buffer#limit();
+ */
+ public int limit() {
+ return getBuffer().limit();
+ }
+
+ /**
+ * @see Buffer#limit(int);
+ */
+ public void limit(final int limit) {
+ getBuffer().limit(limit);
+ }
+
+ /**
+ * @see Buffer#capacity();
+ */
+ public int capacity() {
+ return getBuffer().capacity();
+ }
+
+ /**
+ * @see Buffer#rewind();
+ */
+ public void rewind() {
+ getBuffer().rewind();
+ }
+
+ /**
+ * @see Buffer#reset();
+ */
+ public void reset() {
+ getBuffer().reset();
+ }
+
+ @Override
+ public abstract IndexBufferData<T> makeCopy();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java
new file mode 100644
index 0000000..e6673a5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/InstancingManager.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.nio.FloatBuffer;
+import java.util.List;
+
+import com.ardor3d.math.Matrix4;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+
+public class InstancingManager {
+
+ private int _maxBatchSize = 30;
+ private final List<Mesh> _visibleMeshes = Lists.newArrayListWithCapacity(_maxBatchSize);
+ private FloatBuffer _transformBuffer;
+ private int _primCount;
+ private int _meshesToDraw = 0;
+
+ /**
+ * Register a mesh for instancing for this current frame (internal use only)
+ *
+ * @param mesh
+ */
+ public void registerMesh(final Mesh mesh) {
+ _visibleMeshes.add(mesh);
+ _meshesToDraw++;
+ }
+
+ /**
+ * Fill the buffer with the transforms and return it
+ */
+ protected FloatBuffer fillTransformBuffer() {
+
+ _primCount = Math.min(_visibleMeshes.size(), _maxBatchSize);
+
+ final int nrOfFloats = _primCount * 16; /* 16 floats per matrix */
+
+ // re-init buffer when it is too small of more than twice the required size
+ if (_transformBuffer == null || nrOfFloats > _transformBuffer.capacity()) {
+ _transformBuffer = BufferUtils.createFloatBuffer(nrOfFloats);
+ }
+
+ _transformBuffer.rewind();
+ _transformBuffer.limit(nrOfFloats);
+
+ final Matrix4 mat = Matrix4.fetchTempInstance();
+
+ for (int i = 0; i < _maxBatchSize && _meshesToDraw > 0; i++) {
+ final Mesh mesh = _visibleMeshes.get(--_meshesToDraw);
+ final Matrix4 transform = mesh.getWorldTransform().getHomogeneousMatrix(mat);
+ transform.toFloatBuffer(_transformBuffer, false);
+ }
+
+ Matrix4.releaseTempInstance(mat);
+ _transformBuffer.rewind();
+
+ return _transformBuffer;
+ }
+
+ /**
+ * Returns the number of meshes to be drawn this batch. This function is only valid after the apply call (internal
+ * use only)
+ */
+ public int getPrimitiveCount() {
+ return _primCount;
+ }
+
+ /**
+ * Split the batch in multiple batches if number of visible meshes exceeds this amount. Using larger batches will
+ * lead to better performance, although you might overflow the uniform space of the shader/videocard (crashes)
+ *
+ * @return maximum batch size
+ */
+ public int getMaxBatchSize() {
+ return _maxBatchSize;
+ }
+
+ /**
+ * Split the batch in multiple batches if number of visible meshes exceeds this amount. Using larger batches will
+ * lead to better performance, although you might overflow the uniform space of the shader/videocard (crashes)
+ *
+ * @param maxBatchSize
+ * maximum batch size
+ */
+ public void setMaxBatchSize(final int maxBatchSize) {
+ _maxBatchSize = maxBatchSize;
+ }
+
+ public boolean isAddedToRenderQueue() {
+ return _meshesToDraw > 0;
+ }
+
+ /**
+ * Applies all instancing info to the mesh and returns if the current render call is allowed to continue
+ *
+ * @param mesh
+ * @param renderer
+ * @param shader
+ * @return continue rendering or skip rendering all together
+ */
+ public boolean apply(final Mesh mesh, final Renderer renderer, final GLSLShaderObjectsState shader) {
+ final RenderContext context = ContextManager.getCurrentContext();
+ final ContextCapabilities caps = context.getCapabilities();
+
+ if (!caps.isGeometryInstancingSupported()) {
+ throw new Ardor3dException("Geometry instancing not supported for current graphics configuration");
+ }
+
+ if (_meshesToDraw <= 0) {
+ // reset for next draw call
+ _primCount = -1;
+ shader.setUniform("nrOfInstances", -1);
+ _visibleMeshes.clear();
+ return false;
+ }
+
+ shader.setUniform("transforms", fillTransformBuffer(), 4);
+ shader.setUniform("nrOfInstances", getPrimitiveCount());
+ return true;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java
new file mode 100644
index 0000000..4e4cf04
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/IntBufferData.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.IntBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Simple data class storing a buffer of ints
+ */
+public class IntBufferData extends IndexBufferData<IntBuffer> implements Savable {
+
+ /**
+ * Instantiates a new IntBufferData.
+ */
+ public IntBufferData() {}
+
+ /**
+ * Instantiates a new IntBufferData with a buffer of the given size.
+ */
+ public IntBufferData(final int size) {
+ this(BufferUtils.createIntBuffer(size));
+ }
+
+ /**
+ * Creates a new IntBufferData.
+ *
+ * @param buffer
+ * Buffer holding the data. Must not be null.
+ */
+ public IntBufferData(final IntBuffer buffer) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("Buffer can not be null!");
+ }
+
+ _buffer = buffer;
+ }
+
+ public Class<? extends IntBufferData> getClassTag() {
+ return getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _buffer = capsule.readIntBuffer("buffer", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_buffer, "buffer", null);
+ }
+
+ @Override
+ public int get() {
+ return _buffer.get();
+ }
+
+ @Override
+ public int get(final int index) {
+ return _buffer.get(index);
+ }
+
+ @Override
+ public IntBufferData put(final int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("Invalid value passed to int buffer: " + value);
+ }
+ _buffer.put(value);
+ return this;
+ }
+
+ @Override
+ public IntBufferData put(final int index, final int value) {
+ if (value < 0) {
+ throw new IllegalArgumentException("Invalid value passed to int buffer: " + value);
+ }
+ _buffer.put(index, value);
+ return this;
+ }
+
+ @Override
+ public void put(final IndexBufferData<?> buf) {
+ if (buf instanceof IntBufferData) {
+ _buffer.put((IntBuffer) buf.getBuffer());
+ } else {
+ while (buf.getBuffer().hasRemaining()) {
+ put(buf.get());
+ }
+ }
+ }
+
+ @Override
+ public int getByteCount() {
+ return 4;
+ }
+
+ @Override
+ public IntBuffer getBuffer() {
+ return _buffer;
+ }
+
+ @Override
+ public IntBuffer asIntBuffer() {
+ final IntBuffer source = getBuffer().duplicate();
+ source.rewind();
+ final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit());
+ buff.put(source);
+ buff.flip();
+ return buff;
+ }
+
+ @Override
+ public IntBufferData makeCopy() {
+ final IntBufferData copy = new IntBufferData();
+ copy._buffer = BufferUtils.clone(_buffer);
+ copy._vboAccessMode = _vboAccessMode;
+ return copy;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java
new file mode 100644
index 0000000..b1cfaae
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Line.java
@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class Line extends Mesh {
+
+ private float _lineWidth = 1.0f;
+ private short _stipplePattern = (short) 0xFFFF;
+ private int _stippleFactor = 1;
+ private boolean _antialiased = false;
+
+ public Line() {
+ this("line");
+ }
+
+ /**
+ * Constructs a new line with the given name. By default, the line has no information.
+ *
+ * @param name
+ * The name of the line.
+ */
+ public Line(final String name) {
+ super(name);
+
+ _meshData.setIndexMode(IndexMode.Lines);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Line</code> object with a given set of data. Any data can be null except for
+ * the vertex list. If vertices are null an exception will be thrown.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param vertex
+ * the vertices that make up the lines.
+ * @param normal
+ * the normals of the lines.
+ * @param color
+ * the color of each point of the lines.
+ * @param coords
+ * the texture coordinates of the lines.
+ */
+ public Line(final String name, final FloatBuffer vertex, final FloatBuffer normal, final FloatBuffer color,
+ final FloatBufferData coords) {
+ super(name);
+ setupData(vertex, normal, color, coords);
+ _meshData.setIndexMode(IndexMode.Lines);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Line</code> object with a given set of data. Any data can be null except for
+ * the vertex list. If vertices are null an exception will be thrown.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param vertex
+ * the vertices that make up the lines.
+ * @param normal
+ * the normals of the lines.
+ * @param color
+ * the color of each point of the lines.
+ * @param texture
+ * the texture coordinates of the lines.
+ */
+ public Line(final String name, final ReadOnlyVector3[] vertex, final ReadOnlyVector3[] normal,
+ final ReadOnlyColorRGBA[] color, final ReadOnlyVector2[] texture) {
+ super(name);
+ setupData(BufferUtils.createFloatBuffer(vertex), BufferUtils.createFloatBuffer(normal),
+ BufferUtils.createFloatBuffer(color), FloatBufferDataUtil.makeNew(texture));
+ _meshData.setIndexMode(IndexMode.Lines);
+ }
+
+ /**
+ * Initialize the meshdata object with data.
+ *
+ * @param vertices
+ * @param normals
+ * @param colors
+ * @param coords
+ */
+ private void setupData(final FloatBuffer vertices, final FloatBuffer normals, final FloatBuffer colors,
+ final FloatBufferData coords) {
+ _meshData.setVertexBuffer(vertices);
+ _meshData.setNormalBuffer(normals);
+ _meshData.setColorBuffer(colors);
+ _meshData.setTextureCoords(coords, 0);
+ _meshData.setIndices(null);
+ }
+
+ /**
+ * Puts a circle into vertex and normal buffer at the current buffer position. The buffers are enlarged and copied
+ * if they are too small.
+ *
+ * @param radius
+ * radius of the circle
+ * @param x
+ * x coordinate of circle center
+ * @param y
+ * y coordinate of circle center
+ * @param segments
+ * number of line segments the circle is built from
+ * @param insideOut
+ * false for normal winding (ccw), true for clockwise winding
+ */
+ public void appendCircle(final double radius, final double x, final double y, final int segments,
+ final boolean insideOut) {
+ final int requiredFloats = segments * 2 * 3;
+ final FloatBuffer verts = BufferUtils.ensureLargeEnough(_meshData.getVertexBuffer(), requiredFloats);
+ _meshData.setVertexBuffer(verts);
+ final FloatBuffer normals = BufferUtils.ensureLargeEnough(_meshData.getNormalBuffer(), requiredFloats);
+ _meshData.setNormalBuffer(normals);
+ double angle = 0;
+ final double step = MathUtils.PI * 2 / segments;
+ for (int i = 0; i < segments; i++) {
+ final double dx = MathUtils.cos(insideOut ? -angle : angle) * radius;
+ final double dy = MathUtils.sin(insideOut ? -angle : angle) * radius;
+ if (i > 0) {
+ verts.put((float) (dx + x)).put((float) (dy + y)).put(0);
+ normals.put((float) dx).put((float) dy).put(0);
+ }
+ verts.put((float) (dx + x)).put((float) (dy + y)).put(0);
+ normals.put((float) dx).put((float) dy).put(0);
+ angle += step;
+ }
+ verts.put((float) (radius + x)).put((float) y).put(0);
+ normals.put((float) radius).put(0).put(0);
+ }
+
+ /**
+ * @return true if points are to be drawn antialiased
+ */
+ public boolean isAntialiased() {
+ return _antialiased;
+ }
+
+ /**
+ * Sets whether the point should be antialiased. May decrease performance. If you want to enabled antialiasing, you
+ * should also use an alphastate with a source of SourceFunction.SourceAlpha and a destination of
+ * DB_ONE_MINUS_SRC_ALPHA or DB_ONE.
+ *
+ * @param antialiased
+ * true if the line should be antialiased.
+ */
+ public void setAntialiased(final boolean antialiased) {
+ _antialiased = antialiased;
+ }
+
+ /**
+ * @return the width of this line.
+ */
+ public float getLineWidth() {
+ return _lineWidth;
+ }
+
+ /**
+ * Sets the width of the line when drawn. Non anti-aliased line widths are rounded to the nearest whole number by
+ * opengl.
+ *
+ * @param lineWidth
+ * The lineWidth to set.
+ */
+ public void setLineWidth(final float lineWidth) {
+ _lineWidth = lineWidth;
+ }
+
+ /**
+ * @return the set stipplePattern. 0xFFFF means no stipple.
+ */
+ public short getStipplePattern() {
+ return _stipplePattern;
+ }
+
+ /**
+ * The stipple or pattern to use when drawing this line. 0xFFFF is a solid line.
+ *
+ * @param stipplePattern
+ * a 16bit short whose bits describe the pattern to use when drawing this line
+ */
+ public void setStipplePattern(final short stipplePattern) {
+ _stipplePattern = stipplePattern;
+ }
+
+ /**
+ * @return the set stippleFactor.
+ */
+ public int getStippleFactor() {
+ return _stippleFactor;
+ }
+
+ /**
+ * @param stippleFactor
+ * magnification factor to apply to the stipple pattern.
+ */
+ public void setStippleFactor(final int stippleFactor) {
+ _stippleFactor = stippleFactor;
+ }
+
+ @Override
+ public Line makeCopy(final boolean shareGeometricData) {
+ final Line lineCopy = (Line) super.makeCopy(shareGeometricData);
+ lineCopy.setAntialiased(_antialiased);
+ lineCopy.setLineWidth(_lineWidth);
+ lineCopy.setStippleFactor(_stippleFactor);
+ lineCopy.setStipplePattern(_stipplePattern);
+ return lineCopy;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_lineWidth, "lineWidth", 1);
+ capsule.write(_stipplePattern, "stipplePattern", (short) 0xFFFF);
+ capsule.write(_antialiased, "antialiased", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _lineWidth = capsule.readFloat("lineWidth", 1);
+ _stipplePattern = capsule.readShort("stipplePattern", (short) 0xFFFF);
+ _antialiased = capsule.readBoolean("antialiased", false);
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ renderer.setupLineParameters(getLineWidth(), getStippleFactor(), getStipplePattern(), isAntialiased());
+
+ super.render(renderer);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java
new file mode 100644
index 0000000..d97c844
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Mesh.java
@@ -0,0 +1,777 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.bounding.CollisionTree;
+import com.ardor3d.bounding.CollisionTreeManager;
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.intersection.Pickable;
+import com.ardor3d.intersection.PrimitiveKey;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.GLSLShaderObjectsState;
+import com.ardor3d.renderer.state.LightState;
+import com.ardor3d.renderer.state.LightUtil;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.scenegraph.hint.DataMode;
+import com.ardor3d.scenegraph.hint.NormalsMode;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.scenegraph.RenderDelegate;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardor3d.util.stat.StatType;
+import com.google.common.collect.Lists;
+
+/**
+ * A Mesh is a spatial describing a renderable geometric object. Data about the mesh is stored locally using MeshData.
+ */
+public class Mesh extends Spatial implements Renderable, Pickable {
+
+ public static boolean RENDER_VERTEX_ONLY = false;
+
+ /** Actual buffer representation of the mesh */
+ protected MeshData _meshData = new MeshData();
+
+ /** Local model bounding volume */
+ protected BoundingVolume _modelBound = new BoundingSphere(Double.POSITIVE_INFINITY, Vector3.ZERO);
+
+ /**
+ * The compiled list of renderstates for this mesh, taking into account ancestors states - updated with
+ * updateRenderStates()
+ */
+ protected final EnumMap<RenderState.StateType, RenderState> _states = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ /** The compiled lightState for this mesh */
+ protected transient LightState _lightState;
+
+ /** Default color to use when no per vertex colors are set */
+ protected ColorRGBA _defaultColor = new ColorRGBA(ColorRGBA.WHITE);
+
+ /** Visibility setting that can be used after the scenegraph hierarchical culling */
+ protected boolean _isVisible = true;
+
+ /**
+ * Constructs a new Mesh.
+ */
+ public Mesh() {
+ super();
+ }
+
+ /**
+ * Constructs a new <code>Mesh</code> with a given name.
+ *
+ * @param name
+ * the name of the mesh. This is required for identification purposes.
+ */
+ public Mesh(final String name) {
+ super(name);
+ }
+
+ /**
+ * Retrieves the mesh data object used by this mesh.
+ *
+ * @return the mesh data object
+ */
+ public MeshData getMeshData() {
+ return _meshData;
+ }
+
+ /**
+ * Sets the mesh data object for this mesh.
+ *
+ * @param meshData
+ * the mesh data object
+ */
+ public void setMeshData(final MeshData meshData) {
+ // invalidate collision tree cache
+ CollisionTreeManager.INSTANCE.removeCollisionTree(this);
+ _meshData = meshData;
+ }
+
+ /**
+ * Retrieves the local bounding volume for this mesh.
+ *
+ * @param store
+ * the bounding volume
+ */
+ public BoundingVolume getModelBound() {
+ return _modelBound;
+ }
+
+ /**
+ * Retrieves a copy of the local bounding volume for this mesh.
+ *
+ * @param store
+ * the bounding volume
+ */
+ public BoundingVolume getModelBound(final BoundingVolume store) {
+ if (_modelBound == null) {
+ return null;
+ }
+ return _modelBound.clone(store);
+ }
+
+ /**
+ * Sets the local bounding volume for this mesh to the given bounds, updated to fit the vertices of this Mesh. Marks
+ * the spatial as having dirty world bounds.
+ *
+ * @param modelBound
+ * the bounding volume - only type is used, actual values are replaced.
+ */
+ public void setModelBound(final BoundingVolume modelBound) {
+ setModelBound(modelBound, true);
+ }
+
+ /**
+ * Sets the local bounding volume for this mesh to the given bounding volume. If autoCompute is true (default, if
+ * not given) then we will modify the given modelBound to fit the current vertices of this mesh. This will also mark
+ * the spatial as having dirty world bounds.
+ *
+ * @param modelBound
+ * the bounding volume
+ * @param autoCompute
+ * if true, update the given modelBound to fit the vertices of this Mesh.
+ */
+ public void setModelBound(final BoundingVolume modelBound, final boolean autoCompute) {
+ _modelBound = modelBound != null ? modelBound.clone(_modelBound) : null;
+ if (autoCompute) {
+ updateModelBound();
+ }
+ markDirty(DirtyType.Bounding);
+ }
+
+ /**
+ * Recalculate the local bounding volume of this Mesh to fit its vertices.
+ */
+ public void updateModelBound() {
+ if (_modelBound != null && _meshData.getVertexBuffer() != null) {
+ // using duplicate to allow walking through buffer without altering current position, etc.
+ // NB: this maintains a measure of thread safety when using shared meshdata.
+ _modelBound.computeFromPoints(_meshData.getVertexBuffer().duplicate());
+ markDirty(DirtyType.Bounding);
+ }
+ }
+
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ if (_modelBound != null) {
+ _worldBound = _modelBound.transform(_worldTransform, _worldBound);
+ } else {
+ _worldBound = null;
+ }
+ clearDirty(DirtyType.Bounding);
+ }
+
+ /**
+ * translates/rotates and scales the vectors of this Mesh to world coordinates based on its world settings. The
+ * results are stored in the given FloatBuffer. If given FloatBuffer is null, one is created.
+ *
+ * @param store
+ * the FloatBuffer to store the results in, or null if you want one created.
+ * @return store or new FloatBuffer if store == null.
+ */
+ public FloatBuffer getWorldVectors(FloatBuffer store) {
+ final FloatBuffer vertBuf = _meshData.getVertexBuffer();
+ if (store == null || store.capacity() != vertBuf.limit()) {
+ store = BufferUtils.createFloatBuffer(vertBuf.limit());
+ }
+
+ final Vector3 compVect = Vector3.fetchTempInstance();
+ for (int v = 0, vSize = store.capacity() / 3; v < vSize; v++) {
+ BufferUtils.populateFromBuffer(compVect, vertBuf, v);
+ _worldTransform.applyForward(compVect);
+ BufferUtils.setInBuffer(compVect, store, v);
+ }
+ Vector3.releaseTempInstance(compVect);
+ return store;
+ }
+
+ /**
+ * rotates the normals of this Mesh to world normals based on its world settings. The results are stored in the
+ * given FloatBuffer. If given FloatBuffer is null, one is created.
+ *
+ * @param store
+ * the FloatBuffer to store the results in, or null if you want one created.
+ * @return store or new FloatBuffer if store == null.
+ */
+ public FloatBuffer getWorldNormals(FloatBuffer store) {
+ final FloatBuffer normBuf = _meshData.getNormalBuffer();
+ if (store == null || store.capacity() != normBuf.limit()) {
+ store = BufferUtils.createFloatBuffer(normBuf.limit());
+ }
+
+ final Vector3 compVect = Vector3.fetchTempInstance();
+ for (int v = 0, vSize = store.capacity() / 3; v < vSize; v++) {
+ BufferUtils.populateFromBuffer(compVect, normBuf, v);
+ _worldTransform.applyForwardVector(compVect);
+ BufferUtils.setInBuffer(compVect, store, v);
+ }
+ Vector3.releaseTempInstance(compVect);
+ return store;
+ }
+
+ public void render(final Renderer renderer) {
+ if (isVisible()) {
+ render(renderer, getMeshData());
+ }
+ }
+
+ public void render(final Renderer renderer, final MeshData meshData) {
+ // Set up MeshData in GLSLShaderObjectsState if necessary
+ // XXX: considered a hack until we settle on our shader model.
+ final GLSLShaderObjectsState glsl = (GLSLShaderObjectsState) renderer.getProperRenderState(
+ RenderState.StateType.GLSLShader, _states.get(RenderState.StateType.GLSLShader));
+
+ if (glsl != null && glsl.getShaderDataLogic() != null) {
+ glsl.setMesh(this);
+ glsl.setNeedsRefresh(true);
+ }
+
+ final InstancingManager instancing = glsl != null ? meshData.getInstancingManager() : null;
+
+ final RenderContext context = ContextManager.getCurrentContext();
+ final ContextCapabilities caps = context.getCapabilities();
+
+ // Apply fixed function states before mesh transforms for proper function
+ for (final StateType type : StateType.values) {
+ if (type != StateType.GLSLShader && type != StateType.FragmentProgram && type != StateType.VertexProgram) {
+ renderer.applyState(type, _states.get(type));
+ }
+ }
+
+ final boolean useVBO = (getSceneHints().getDataMode() == DataMode.VBO || getSceneHints().getDataMode() == DataMode.VBOInterleaved)
+ && caps.isVBOSupported();
+
+ if (instancing == null) {
+ final boolean transformed = renderer.doTransforms(_worldTransform);
+
+ // Apply shader states here for the ability to retrieve mesh matrices
+ renderer.applyState(StateType.GLSLShader, _states.get(StateType.GLSLShader));
+ renderer.applyState(StateType.FragmentProgram, _states.get(StateType.FragmentProgram));
+ renderer.applyState(StateType.VertexProgram, _states.get(StateType.VertexProgram));
+
+ if (useVBO) {
+ renderVBO(renderer, meshData, -1);
+ } else {
+ renderArrays(renderer, meshData, -1, caps);
+ }
+
+ if (transformed) {
+ renderer.undoTransforms(_worldTransform);
+ }
+
+ } else {
+ while (instancing.apply(this, renderer, glsl)) {
+ // Apply shader states here for the ability to retrieve mesh matrices
+ renderer.applyState(StateType.GLSLShader, _states.get(StateType.GLSLShader));
+ renderer.applyState(StateType.FragmentProgram, _states.get(StateType.FragmentProgram));
+ renderer.applyState(StateType.VertexProgram, _states.get(StateType.VertexProgram));
+
+ if (useVBO) {
+ renderVBO(renderer, meshData, instancing.getPrimitiveCount());
+ } else {
+ renderArrays(renderer, meshData, instancing.getPrimitiveCount(), caps);
+ }
+ }
+ }
+ }
+
+ protected void renderArrays(final Renderer renderer, final MeshData meshData, final int primcount,
+ final ContextCapabilities caps) {
+ // Use arrays
+ if (caps.isVBOSupported()) {
+ renderer.unbindVBO();
+ }
+
+ if (RENDER_VERTEX_ONLY) {
+ renderer.applyNormalsMode(NormalsMode.Off, null);
+ renderer.setupNormalData(null);
+ renderer.applyDefaultColor(null);
+ renderer.setupColorData(null);
+ renderer.setupTextureData(null);
+ } else {
+ renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform);
+ if (getSceneHints().getNormalsMode() != NormalsMode.Off) {
+ renderer.setupNormalData(meshData.getNormalCoords());
+ } else {
+ renderer.setupNormalData(null);
+ }
+
+ if (meshData.getColorCoords() != null) {
+ renderer.setupColorData(meshData.getColorCoords());
+ } else {
+ renderer.applyDefaultColor(_defaultColor);
+ renderer.setupColorData(null);
+ }
+
+ renderer.setupTextureData(meshData.getTextureCoords());
+ }
+ renderer.setupVertexData(meshData.getVertexCoords());
+
+ if (meshData.getIndexBuffer() != null) {
+ renderer.drawElements(meshData.getIndices(), meshData.getIndexLengths(), meshData.getIndexModes(),
+ primcount);
+ } else {
+ renderer.drawArrays(meshData.getVertexCoords(), meshData.getIndexLengths(), meshData.getIndexModes(),
+ primcount);
+ }
+
+ if (Constants.stats) {
+ StatCollector.addStat(StatType.STAT_VERTEX_COUNT, meshData.getVertexCount());
+ StatCollector.addStat(StatType.STAT_MESH_COUNT, 1);
+ }
+ }
+
+ protected void renderVBO(final Renderer renderer, final MeshData meshData, final int primcount) {
+ if (getSceneHints().getDataMode() == DataMode.VBOInterleaved) {
+ if (meshData.getColorCoords() == null) {
+ renderer.applyDefaultColor(_defaultColor);
+ }
+ renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform);
+ // Make sure we have a FBD to hold our id.
+ if (meshData.getInterleavedData() == null) {
+ final FloatBufferData interleaved = new FloatBufferData(FloatBuffer.allocate(0), 1);
+ meshData.setInterleavedData(interleaved);
+ }
+ renderer.setupInterleavedDataVBO(meshData.getInterleavedData(), meshData.getVertexCoords(),
+ meshData.getNormalCoords(), meshData.getColorCoords(), meshData.getTextureCoords());
+ } else {
+ if (RENDER_VERTEX_ONLY) {
+ renderer.applyNormalsMode(NormalsMode.Off, null);
+ renderer.setupNormalDataVBO(null);
+ renderer.applyDefaultColor(null);
+ renderer.setupColorDataVBO(null);
+ renderer.setupTextureDataVBO(null);
+ } else {
+ renderer.applyNormalsMode(getSceneHints().getNormalsMode(), _worldTransform);
+ if (getSceneHints().getNormalsMode() != NormalsMode.Off) {
+ renderer.setupNormalDataVBO(meshData.getNormalCoords());
+ } else {
+ renderer.setupNormalDataVBO(null);
+ }
+
+ if (meshData.getColorCoords() != null) {
+ renderer.setupColorDataVBO(meshData.getColorCoords());
+ } else {
+ renderer.applyDefaultColor(_defaultColor);
+ renderer.setupColorDataVBO(null);
+ }
+
+ renderer.setupTextureDataVBO(meshData.getTextureCoords());
+ }
+ renderer.setupVertexDataVBO(meshData.getVertexCoords());
+ }
+
+ if (meshData.getIndexBuffer() != null) {
+ // TODO: Maybe ask for the IndexBuffer's dynamic/static type and fall back to arrays for indices?
+ renderer.drawElementsVBO(meshData.getIndices(), meshData.getIndexLengths(), meshData.getIndexModes(),
+ primcount);
+ } else {
+ renderer.drawArrays(meshData.getVertexCoords(), meshData.getIndexLengths(), meshData.getIndexModes(),
+ primcount);
+ }
+
+ if (Constants.stats) {
+ StatCollector.addStat(StatType.STAT_VERTEX_COUNT, meshData.getVertexCount());
+ StatCollector.addStat(StatType.STAT_MESH_COUNT, 1);
+ }
+ }
+
+ @Override
+ protected void applyWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) {
+ // start with a blank slate
+ _states.clear();
+
+ // Go through each state stack and apply to our states list.
+ stack.extract(_states, this);
+ }
+
+ public boolean isVisible() {
+ return _isVisible;
+ }
+
+ public void setVisible(final boolean isVisible) {
+ _isVisible = isVisible;
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void draw(final Renderer r) {
+ if (!r.isProcessingQueue()) {
+ if (r.checkAndAdd(this)) {
+ return;
+ }
+ }
+
+ final RenderDelegate delegate = getCurrentRenderDelegate();
+ if (delegate == null) {
+ r.draw((Renderable) this);
+ } else {
+ delegate.render(this, r);
+ }
+ }
+
+ /**
+ * Sorts the lights based on distance to mesh bounding volume
+ */
+ @Override
+ public void sortLights() {
+ if (_lightState != null && _lightState.getLightList().size() > LightState.MAX_LIGHTS_ALLOWED) {
+ LightUtil.sort(this, _lightState.getLightList());
+ }
+ }
+
+ public LightState getLightState() {
+ return _lightState;
+ }
+
+ public void setLightState(final LightState lightState) {
+ _lightState = lightState;
+ }
+
+ /**
+ * <code>setDefaultColor</code> sets the color to be used if no per vertex color buffer is set.
+ *
+ * @param color
+ */
+ public void setDefaultColor(final ReadOnlyColorRGBA color) {
+ _defaultColor.set(color);
+ }
+
+ /**
+ *
+ * @param store
+ * @return
+ */
+ public ReadOnlyColorRGBA getDefaultColor() {
+ return _defaultColor;
+ }
+
+ /**
+ * @param type
+ * StateType of RenderState we want to grab
+ * @return the compiled RenderState for this Mesh, either from RenderStates applied locally or those inherited from
+ * this Mesh's ancestors. May be null if a state of the given type was never applied in either place.
+ */
+ public RenderState getWorldRenderState(final StateType type) {
+ return _states.get(type);
+ }
+
+ /**
+ * <code>setSolidColor</code> sets the color array of this geometry to a single color. For greater efficiency, try
+ * setting the the ColorBuffer to null and using DefaultColor instead.
+ *
+ * @param color
+ * the color to set.
+ */
+ public void setSolidColor(final ReadOnlyColorRGBA color) {
+ FloatBuffer colorBuf = _meshData.getColorBuffer();
+ if (colorBuf == null) {
+ colorBuf = BufferUtils.createColorBuffer(_meshData.getVertexCount());
+ _meshData.setColorBuffer(colorBuf);
+ }
+
+ colorBuf.rewind();
+ for (int x = 0, cLength = colorBuf.remaining(); x < cLength; x += 4) {
+ colorBuf.put(color.getRed());
+ colorBuf.put(color.getGreen());
+ colorBuf.put(color.getBlue());
+ colorBuf.put(color.getAlpha());
+ }
+ colorBuf.flip();
+ }
+
+ /**
+ * Sets every color of this geometry's color array to a random color.
+ */
+ public void setRandomColors() {
+ FloatBuffer colorBuf = _meshData.getColorBuffer();
+ if (colorBuf == null) {
+ colorBuf = BufferUtils.createColorBuffer(_meshData.getVertexCount());
+ _meshData.setColorBuffer(colorBuf);
+ } else {
+ colorBuf.rewind();
+ }
+
+ for (int x = 0, cLength = colorBuf.limit(); x < cLength; x += 4) {
+ colorBuf.put(MathUtils.nextRandomFloat());
+ colorBuf.put(MathUtils.nextRandomFloat());
+ colorBuf.put(MathUtils.nextRandomFloat());
+ colorBuf.put(1);
+ }
+ colorBuf.flip();
+ }
+
+ // PICKABLE INTERFACE
+
+ @Override
+ public boolean supportsBoundsIntersectionRecord() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsPrimitivesIntersectionRecord() {
+ return true;
+ }
+
+ @Override
+ public boolean intersectsWorldBound(final Ray3 ray) {
+ // should throw NPE if no bound.
+ return getWorldBound().intersects(ray);
+ }
+
+ @Override
+ public IntersectionRecord intersectsWorldBoundsWhere(final Ray3 ray) {
+ // should throw NPE if no bound.
+ return getWorldBound().intersectsWhere(ray);
+ }
+
+ @Override
+ public IntersectionRecord intersectsPrimitivesWhere(final Ray3 ray) {
+ final List<PrimitiveKey> primitives = Lists.newArrayList();
+
+ // What about Lines and Points?
+ final CollisionTree ct = CollisionTreeManager.getInstance().getCollisionTree(this);
+ if (ct != null) {
+ ct.getBounds().transform(getWorldTransform(), ct.getWorldBounds());
+ ct.intersect(ray, primitives);
+ }
+
+ if (primitives.isEmpty()) {
+ return null;
+ }
+
+ Vector3[] vertices = null;
+ final double[] distances = new double[primitives.size()];
+ for (int i = 0; i < primitives.size(); i++) {
+ final PrimitiveKey key = primitives.get(i);
+ vertices = getMeshData().getPrimitiveVertices(key.getPrimitiveIndex(), key.getSection(), vertices);
+ // convert to world coord space
+ final int max = getMeshData().getIndexMode(key.getSection()).getVertexCount();
+ for (int j = 0; j < max; j++) {
+ if (vertices[j] != null) {
+ getWorldTransform().applyForward(vertices[j]);
+ }
+ }
+ final double triDistanceSq = ray.getDistanceToPrimitive(vertices);
+ distances[i] = triDistanceSq;
+ }
+
+ // FIXME: optimize! ugly bubble sort for now
+ boolean sorted = false;
+ while (!sorted) {
+ sorted = true;
+ for (int sort = 0; sort < distances.length - 1; sort++) {
+ if (distances[sort] > distances[sort + 1]) {
+ // swap
+ sorted = false;
+ final double temp = distances[sort + 1];
+ distances[sort + 1] = distances[sort];
+ distances[sort] = temp;
+
+ // swap primitives too
+ final PrimitiveKey temp2 = primitives.get(sort + 1);
+ primitives.set(sort + 1, primitives.get(sort));
+ primitives.set(sort, temp2);
+ }
+ }
+ }
+
+ final Vector3[] positions = new Vector3[distances.length];
+ for (int i = 0; i < distances.length; i++) {
+ positions[i] = ray.getDirection().multiply(distances[i], new Vector3()).addLocal(ray.getOrigin());
+ }
+ return new IntersectionRecord(distances, positions, primitives);
+ }
+
+ @Override
+ public Mesh makeCopy(final boolean shareGeometricData) {
+ // get copy of basic spatial info
+ final Mesh mesh = (Mesh) super.makeCopy(shareGeometricData);
+
+ // if we are sharing, just reuse meshdata
+ if (shareGeometricData) {
+ mesh.setMeshData(_meshData);
+ } else {
+ // make a copy of our data
+ mesh.setMeshData(_meshData.makeCopy());
+ }
+
+ // copy our basic properties
+ mesh.setModelBound(_modelBound != null ? _modelBound.clone(null) : null);
+ mesh.setDefaultColor(_defaultColor);
+ mesh.setVisible(_isVisible);
+
+ // return
+ return mesh;
+ }
+
+ @Override
+ public Mesh makeInstanced() {
+ final Mesh mesh = (Mesh) super.makeInstanced();
+ if (_meshData.getInstancingManager() == null) {
+ _meshData.setInstancingManager(new InstancingManager());
+ }
+ mesh.setMeshData(_meshData);
+ mesh.setModelBound(_modelBound != null ? _modelBound.clone(null) : null);
+ mesh._defaultColor = _defaultColor;
+ mesh.setVisible(_isVisible);
+ return mesh;
+ }
+
+ /**
+ * Let this mesh know we want to change its indices to the provided new order. Override this to provide extra
+ * functionality for sub types as needed.
+ *
+ * @param newIndices
+ * the IntBufferData to switch to.
+ * @param modes
+ * the new segment modes to use.
+ * @param lengths
+ * the new lengths to use.
+ */
+ public void reorderIndices(final IndexBufferData<?> newIndices, final IndexMode[] modes, final int[] lengths) {
+ _meshData.setIndices(newIndices);
+ _meshData.setIndexModes(modes);
+ _meshData.setIndexLengths(lengths);
+ }
+
+ /**
+ * Swap around the order of the vertex data in this Mesh. This is usually called by a tool that has attempted to
+ * determine a more optimal order for vertex data.
+ *
+ * @param newVertexOrder
+ * a mapping to the desired new order, where the current location of a vertex is the index into this
+ * array and the value at that location in the array is the new location to store the vertex data.
+ */
+ public void reorderVertexData(final int[] newVertexOrder) {
+ reorderVertexData(newVertexOrder, _meshData);
+ }
+
+ /**
+ * Swap around the order of the vertex data in the given MeshData. Override to provide specific behavior to the Mesh
+ * object.
+ *
+ * @param newVertexOrder
+ * a mapping to the desired new order, where the current location of a vertex is the index into this
+ * array and the value at that location in the array is the new location to store the vertex data.
+ * @param meshData
+ * the meshData object to work against.
+ */
+ protected void reorderVertexData(final int[] newVertexOrder, final MeshData meshData) {
+ // must be non-null
+ final FloatBufferData verts = meshData.getVertexCoords().makeCopy();
+
+ final FloatBufferData norms = meshData.getNormalBuffer() != null ? meshData.getVertexCoords().makeCopy() : null;
+ final FloatBufferData colors = meshData.getColorBuffer() != null ? meshData.getColorCoords().makeCopy() : null;
+ final FloatBufferData fogs = meshData.getFogBuffer() != null ? meshData.getFogCoords().makeCopy() : null;
+ final FloatBufferData tangents = meshData.getTangentBuffer() != null ? meshData.getTangentCoords().makeCopy()
+ : null;
+ final FloatBufferData[] uvs = new FloatBufferData[meshData.getNumberOfUnits()];
+ for (int k = 0; k < uvs.length; k++) {
+ final FloatBufferData tex = meshData.getTextureCoords(k);
+ if (tex != null) {
+ uvs[k] = tex.makeCopy();
+ }
+ }
+
+ int vert;
+ for (int i = 0; i < meshData.getVertexCount(); i++) {
+ vert = newVertexOrder[i];
+ if (vert == -1) {
+ vert = i;
+ }
+ BufferUtils.copy(meshData.getVertexBuffer(), i * verts.getValuesPerTuple(), verts.getBuffer(),
+ vert * verts.getValuesPerTuple(), verts.getValuesPerTuple());
+ if (norms != null) {
+ BufferUtils.copy(meshData.getNormalBuffer(), i * norms.getValuesPerTuple(), norms.getBuffer(), vert
+ * norms.getValuesPerTuple(), norms.getValuesPerTuple());
+ }
+ if (colors != null) {
+ BufferUtils.copy(meshData.getColorBuffer(), i * colors.getValuesPerTuple(), colors.getBuffer(), vert
+ * colors.getValuesPerTuple(), colors.getValuesPerTuple());
+ }
+ if (fogs != null) {
+ BufferUtils.copy(meshData.getFogBuffer(), i * fogs.getValuesPerTuple(), fogs.getBuffer(),
+ vert * fogs.getValuesPerTuple(), fogs.getValuesPerTuple());
+ }
+ if (tangents != null) {
+ BufferUtils.copy(meshData.getTangentBuffer(), i * tangents.getValuesPerTuple(), tangents.getBuffer(),
+ vert * tangents.getValuesPerTuple(), tangents.getValuesPerTuple());
+ }
+ for (int k = 0; k < uvs.length; k++) {
+ if (uvs[k] != null) {
+ BufferUtils.copy(meshData.getTextureBuffer(k), i * uvs[k].getValuesPerTuple(), uvs[k].getBuffer(),
+ vert * uvs[k].getValuesPerTuple(), uvs[k].getValuesPerTuple());
+ }
+ }
+ }
+ meshData.setVertexCoords(verts);
+ meshData.setNormalCoords(norms);
+ meshData.setColorCoords(colors);
+ meshData.setFogCoords(fogs);
+ meshData.setTangentCoords(tangents);
+ for (int k = 0; k < uvs.length; k++) {
+ if (uvs[k] != null) {
+ meshData.setTextureCoords(uvs[k], k);
+ }
+ }
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends Mesh> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_meshData, "meshData", null);
+ capsule.write(_modelBound, "modelBound", null);
+ capsule.write(_defaultColor, "defaultColor", new ColorRGBA(ColorRGBA.WHITE));
+ capsule.write(_isVisible, "visible", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _meshData = (MeshData) capsule.readSavable("meshData", null);
+ _modelBound = (BoundingVolume) capsule.readSavable("modelBound", null);
+ _defaultColor = (ColorRGBA) capsule.readSavable("defaultColor", new ColorRGBA(ColorRGBA.WHITE));
+ _isVisible = capsule.readBoolean("visible", true);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java
new file mode 100644
index 0000000..b98c7ec
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/MeshData.java
@@ -0,0 +1,1253 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+/**
+ * MeshData contains all the commonly used buffers for rendering a mesh.
+ */
+public class MeshData implements Savable {
+
+ /** The Constant logger. */
+ private static final Logger logger = Logger.getLogger(MeshData.class.getName());
+
+ /** A cache of ids for interleaved use. */
+ private transient Map<Object, Integer> _vboIdCache = null;
+
+ /** Number of vertices represented by this data. */
+ protected int _vertexCount;
+
+ /** Number of primitives represented by this data. */
+ protected transient int[] _primitiveCounts = new int[1];
+
+ /** Buffer data holding buffers and number of coordinates per vertex */
+ protected FloatBufferData _vertexCoords;
+ protected FloatBufferData _normalCoords;
+ protected FloatBufferData _colorCoords;
+ protected FloatBufferData _fogCoords;
+ protected FloatBufferData _tangentCoords;
+ protected List<FloatBufferData> _textureCoords = Lists.newArrayListWithCapacity(1);
+
+ /** Interleaved data (for VBO id use). */
+ protected FloatBufferData _interleaved;
+
+ /** Index data. */
+ protected IndexBufferData<?> _indexBuffer;
+ protected int[] _indexLengths;
+ protected IndexMode[] _indexModes = new IndexMode[] { IndexMode.Triangles };
+
+ private InstancingManager _instancingManager;
+
+ /**
+ * Gets the vertex count.
+ *
+ * @return the vertex count
+ */
+ public int getVertexCount() {
+ return _vertexCount;
+ }
+
+ /**
+ * Gets the vertex buffer.
+ *
+ * @return the vertex buffer
+ */
+ public FloatBuffer getVertexBuffer() {
+ if (_vertexCoords == null) {
+ return null;
+ }
+ return _vertexCoords.getBuffer();
+ }
+
+ /**
+ * Sets the vertex buffer.
+ *
+ * @param vertexBuffer
+ * the new vertex buffer
+ */
+ public void setVertexBuffer(final FloatBuffer vertexBuffer) {
+ if (vertexBuffer == null) {
+ setVertexCoords(null);
+ } else {
+ setVertexCoords(new FloatBufferData(vertexBuffer, 3));
+ }
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the vertex coords.
+ *
+ * @return the vertex coords
+ */
+ public FloatBufferData getVertexCoords() {
+ return _vertexCoords;
+ }
+
+ private void refreshInterleaved() {
+ if (_interleaved != null) {
+ _interleaved.setNeedsRefresh(true);
+ }
+ }
+
+ /**
+ * Sets the vertex coords.
+ *
+ * @param bufferData
+ * the new vertex coords
+ */
+ public void setVertexCoords(final FloatBufferData bufferData) {
+ _vertexCoords = bufferData;
+ updateVertexCount();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the normal buffer.
+ *
+ * @return the normal buffer
+ */
+ public FloatBuffer getNormalBuffer() {
+ if (_normalCoords == null) {
+ return null;
+ }
+ return _normalCoords.getBuffer();
+ }
+
+ /**
+ * Sets the normal buffer.
+ *
+ * @param normalBuffer
+ * the new normal buffer
+ */
+ public void setNormalBuffer(final FloatBuffer normalBuffer) {
+ if (normalBuffer == null) {
+ _normalCoords = null;
+ } else {
+ _normalCoords = new FloatBufferData(normalBuffer, 3);
+ }
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the normal coords.
+ *
+ * @return the normal coords
+ */
+ public FloatBufferData getNormalCoords() {
+ return _normalCoords;
+ }
+
+ /**
+ * Sets the normal coords.
+ *
+ * @param bufferData
+ * the new normal coords
+ */
+ public void setNormalCoords(final FloatBufferData bufferData) {
+ _normalCoords = bufferData;
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the color buffer.
+ *
+ * @return the color buffer
+ */
+ public FloatBuffer getColorBuffer() {
+ if (_colorCoords == null) {
+ return null;
+ }
+ return _colorCoords.getBuffer();
+ }
+
+ /**
+ * Sets the color buffer.
+ *
+ * @param colorBuffer
+ * the new color buffer
+ */
+ public void setColorBuffer(final FloatBuffer colorBuffer) {
+ if (colorBuffer == null) {
+ _colorCoords = null;
+ } else {
+ _colorCoords = new FloatBufferData(colorBuffer, 4);
+ }
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the color coords.
+ *
+ * @return the color coords
+ */
+ public FloatBufferData getColorCoords() {
+ return _colorCoords;
+ }
+
+ /**
+ * Sets the color coords.
+ *
+ * @param bufferData
+ * the new color coords
+ */
+ public void setColorCoords(final FloatBufferData bufferData) {
+ _colorCoords = bufferData;
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the fog buffer.
+ *
+ * @return the fog buffer
+ */
+ public FloatBuffer getFogBuffer() {
+ if (_fogCoords == null) {
+ return null;
+ }
+ return _fogCoords.getBuffer();
+ }
+
+ /**
+ * Sets the fog buffer.
+ *
+ * @param fogBuffer
+ * the new fog buffer
+ */
+ public void setFogBuffer(final FloatBuffer fogBuffer) {
+ if (fogBuffer == null) {
+ _fogCoords = null;
+ } else {
+ _fogCoords = new FloatBufferData(fogBuffer, 3);
+ }
+ }
+
+ /**
+ * Gets the fog coords.
+ *
+ * @return the fog coords
+ */
+ public FloatBufferData getFogCoords() {
+ return _fogCoords;
+ }
+
+ /**
+ * Sets the fog coords.
+ *
+ * @param bufferData
+ * the new fog coords
+ */
+ public void setFogCoords(final FloatBufferData bufferData) {
+ _fogCoords = bufferData;
+ }
+
+ /**
+ * Gets the tangent buffer.
+ *
+ * @return the tangent buffer
+ */
+ public FloatBuffer getTangentBuffer() {
+ if (_tangentCoords == null) {
+ return null;
+ }
+ return _tangentCoords.getBuffer();
+ }
+
+ /**
+ * Sets the tangent buffer.
+ *
+ * @param tangentBuffer
+ * the new tangent buffer
+ */
+ public void setTangentBuffer(final FloatBuffer tangentBuffer) {
+ if (tangentBuffer == null) {
+ _tangentCoords = null;
+ } else {
+ _tangentCoords = new FloatBufferData(tangentBuffer, 3);
+ }
+ }
+
+ /**
+ * Gets the tangent coords.
+ *
+ * @return the tangent coords
+ */
+ public FloatBufferData getTangentCoords() {
+ return _tangentCoords;
+ }
+
+ /**
+ * Sets the tangent coords.
+ *
+ * @param bufferData
+ * the new tangent coords
+ */
+ public void setTangentCoords(final FloatBufferData bufferData) {
+ _tangentCoords = bufferData;
+ }
+
+ /**
+ * Gets the FloatBuffer of the FloatBufferData set on a given texture unit.
+ *
+ * @param index
+ * the unit index
+ *
+ * @return the texture buffer for the given index, or null if none was set.
+ */
+ public FloatBuffer getTextureBuffer(final int index) {
+ if (_textureCoords.size() <= index) {
+ return null;
+ }
+ final FloatBufferData textureCoord = _textureCoords.get(index);
+ if (textureCoord == null) {
+ return null;
+ }
+ return textureCoord.getBuffer();
+ }
+
+ /**
+ * Sets the texture buffer for a given texture unit index. Interprets it as a 2 component float buffer data. If you
+ * need other sizes, use setTextureCoords instead.
+ *
+ * @param textureBuffer
+ * the texture buffer
+ * @param index
+ * the unit index
+ * @see #setTextureCoords(FloatBufferData, int)
+ */
+ public void setTextureBuffer(final FloatBuffer textureBuffer, final int index) {
+ while (_textureCoords.size() <= index) {
+ _textureCoords.add(null);
+ }
+ _textureCoords.set(index, new FloatBufferData(textureBuffer, 2));
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the texture coords.
+ *
+ * @return the texture coords
+ */
+ public List<FloatBufferData> getTextureCoords() {
+ return _textureCoords;
+ }
+
+ /**
+ * Gets the texture coords assigned to a specific texture unit index of this MeshData.
+ *
+ * @param index
+ * the texture unit index
+ *
+ * @return the texture coords
+ */
+ public FloatBufferData getTextureCoords(final int index) {
+ if (_textureCoords.size() <= index) {
+ return null;
+ }
+ return _textureCoords.get(index);
+ }
+
+ /**
+ * Sets all texture coords on this MeshData.
+ *
+ * @param textureCoords
+ * the new texture coords
+ */
+ public void setTextureCoords(final List<FloatBufferData> textureCoords) {
+ _textureCoords = textureCoords;
+ refreshInterleaved();
+ }
+
+ /**
+ * Sets the texture coords of a specific texture unit index to the given FloatBufferData.
+ *
+ * @param textureCoords
+ * the texture coords
+ * @param index
+ * the unit index
+ */
+ public void setTextureCoords(final FloatBufferData textureCoords, final int index) {
+ while (_textureCoords.size() <= index) {
+ _textureCoords.add(null);
+ }
+ _textureCoords.set(index, textureCoords);
+ refreshInterleaved();
+ }
+
+ /**
+ * Retrieves the interleaved buffer, if set or created through packInterleaved.
+ *
+ * @return the interleaved buffer
+ */
+ public FloatBuffer getInterleavedBuffer() {
+ if (_interleaved == null) {
+ return null;
+ }
+ return _interleaved.getBuffer();
+ }
+
+ /**
+ * Gets the interleaved data.
+ *
+ * @return the interleaved data
+ */
+ public FloatBufferData getInterleavedData() {
+ return _interleaved;
+ }
+
+ /**
+ * Sets the interleaved buffer.
+ *
+ * @param interleavedBuffer
+ * the interleaved buffer
+ */
+ public void setInterleavedData(final FloatBufferData interleavedData) {
+ _interleaved = interleavedData;
+ refreshInterleaved();
+ }
+
+ /**
+ * Update the vertex count based on the current limit of the vertex buffer.
+ */
+ public void updateVertexCount() {
+ if (_vertexCoords == null) {
+ _vertexCount = 0;
+ } else {
+ _vertexCount = _vertexCoords.getTupleCount();
+ }
+ // update primitive count if we are using arrays
+ if (_indexBuffer == null) {
+ updatePrimitiveCounts();
+ }
+ }
+
+ /**
+ * <code>copyTextureCoords</code> copies the texture coordinates of a given texture unit to another location. If the
+ * texture unit is not valid, then the coordinates are ignored. Coords are multiplied by the given factor.
+ *
+ * @param fromIndex
+ * the coordinates to copy.
+ * @param toIndex
+ * the texture unit to set them to. Must not be the same as the fromIndex.
+ * @param factor
+ * a multiple to apply when copying
+ */
+ public void copyTextureCoordinates(final int fromIndex, final int toIndex, final float factor) {
+ if (_textureCoords == null) {
+ return;
+ }
+
+ if (fromIndex < 0 || fromIndex >= _textureCoords.size() || _textureCoords.get(fromIndex) == null) {
+ return;
+ }
+
+ if (toIndex < 0 || toIndex == fromIndex) {
+ return;
+ }
+
+ // make sure we are big enough
+ while (toIndex >= _textureCoords.size()) {
+ _textureCoords.add(null);
+ }
+
+ FloatBufferData dest = _textureCoords.get(toIndex);
+ final FloatBufferData src = _textureCoords.get(fromIndex);
+ if (dest == null || dest.getBuffer().capacity() != src.getBuffer().limit()) {
+ dest = new FloatBufferData(BufferUtils.createFloatBuffer(src.getBuffer().capacity()),
+ src.getValuesPerTuple());
+ _textureCoords.set(toIndex, dest);
+ }
+ dest.getBuffer().clear();
+ final int oldLimit = src.getBuffer().limit();
+ src.getBuffer().clear();
+ for (int i = 0, len = dest.getBuffer().capacity(); i < len; i++) {
+ dest.getBuffer().put(factor * src.getBuffer().get());
+ }
+ src.getBuffer().limit(oldLimit);
+ dest.getBuffer().limit(oldLimit);
+ }
+
+ /**
+ * <code>copyTextureCoords</code> copies the texture coordinates of a given texture unit to another location. If the
+ * texture unit is not valid, then the coordinates are ignored. Coords are multiplied by the given S and T factors.
+ *
+ * @param fromIndex
+ * the coordinates to copy.
+ * @param toIndex
+ * the texture unit to set them to. Must not be the same as the fromIndex.
+ * @param factorS
+ * a multiple to apply to the S channel when copying
+ * @param factorT
+ * a multiple to apply to the T channel when copying
+ */
+ public void copyTextureCoordinates(final int fromIndex, final int toIndex, final float factorS, final float factorT) {
+ if (_textureCoords == null) {
+ return;
+ }
+
+ if (fromIndex < 0 || fromIndex >= _textureCoords.size() || _textureCoords.get(fromIndex) == null) {
+ return;
+ }
+
+ if (toIndex < 0 || toIndex == fromIndex) {
+ return;
+ }
+
+ // make sure we are big enough
+ while (toIndex >= _textureCoords.size()) {
+ _textureCoords.add(null);
+ }
+
+ FloatBufferData dest = _textureCoords.get(toIndex);
+ final FloatBufferData src = _textureCoords.get(fromIndex);
+ if (dest == null || dest.getBuffer().capacity() != src.getBuffer().limit()) {
+ dest = new FloatBufferData(BufferUtils.createFloatBuffer(src.getBuffer().capacity()),
+ src.getValuesPerTuple());
+ _textureCoords.set(toIndex, dest);
+ }
+ dest.getBuffer().clear();
+ final int oldLimit = src.getBuffer().limit();
+ src.getBuffer().clear();
+ for (int i = 0, len = dest.getBuffer().capacity(); i < len; i++) {
+ if (i % 2 == 0) {
+ dest.getBuffer().put(factorS * src.getBuffer().get());
+ } else {
+ dest.getBuffer().put(factorT * src.getBuffer().get());
+ }
+ }
+ src.getBuffer().limit(oldLimit);
+ dest.getBuffer().limit(oldLimit);
+ }
+
+ /**
+ * <code>getNumberOfUnits</code> returns the number of texture units this geometry is currently using.
+ *
+ * @return the number of texture units in use.
+ */
+ public int getNumberOfUnits() {
+ if (_textureCoords == null) {
+ return 0;
+ }
+ return _textureCoords.size();
+ }
+
+ /**
+ * Gets the index buffer.
+ *
+ * @return the index buffer
+ */
+ public Buffer getIndexBuffer() {
+ if (_indexBuffer == null) {
+ return null;
+ }
+ return _indexBuffer.getBuffer();
+ }
+
+ /**
+ * Sets the index buffer.
+ *
+ * @param indices
+ * the new index buffer
+ */
+ public void setIndexBuffer(final IntBuffer indices) {
+ if (indices == null) {
+ _indexBuffer = null;
+ } else {
+ _indexBuffer = new IntBufferData(indices);
+ }
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Sets the index buffer.
+ *
+ * @param indices
+ * the new index buffer
+ */
+ public void setIndexBuffer(final ShortBuffer indices) {
+ if (indices == null) {
+ _indexBuffer = null;
+ } else {
+ _indexBuffer = new ShortBufferData(indices);
+ }
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Sets the index buffer.
+ *
+ * @param indices
+ * the new index buffer
+ */
+ public void setIndexBuffer(final ByteBuffer indices) {
+ if (indices == null) {
+ _indexBuffer = null;
+ } else {
+ _indexBuffer = new ByteBufferData(indices);
+ }
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the indices.
+ *
+ * @return the indices
+ */
+ public IndexBufferData<?> getIndices() {
+ return _indexBuffer;
+ }
+
+ /**
+ * Sets the indices
+ *
+ * @param bufferData
+ * the new indices
+ */
+ public void setIndices(final IndexBufferData<?> bufferData) {
+ _indexBuffer = bufferData;
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the index mode.
+ *
+ * @return the IndexMode of the first section of this MeshData.
+ * @deprecated Please switch to {@link #getIndexMode(int)}
+ */
+ @Deprecated
+ public IndexMode getIndexMode() {
+ return getIndexMode(0);
+ }
+
+ /**
+ * Sets the index mode.
+ *
+ * @param indexMode
+ * the new IndexMode to use for the first section of this MeshData.
+ */
+ public void setIndexMode(final IndexMode indexMode) {
+ _indexModes[0] = indexMode;
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the index lengths.
+ *
+ * @return the index lengths
+ */
+ public int[] getIndexLengths() {
+ return _indexLengths;
+ }
+
+ /**
+ * Sets the index lengths.
+ *
+ * @param indexLengths
+ * the new index lengths
+ */
+ public void setIndexLengths(final int[] indexLengths) {
+ _indexLengths = indexLengths;
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the index modes.
+ *
+ * @return the index modes
+ */
+ public IndexMode[] getIndexModes() {
+ return _indexModes;
+ }
+
+ /**
+ * Gets the index mode.
+ *
+ * @param sectionIndex
+ * the section index
+ *
+ * @return the index mode
+ */
+ public IndexMode getIndexMode(final int sectionIndex) {
+ if (sectionIndex < 0 || sectionIndex >= getSectionCount()) {
+ throw new IllegalArgumentException("invalid section index: " + sectionIndex);
+ }
+ return _indexModes.length > sectionIndex ? _indexModes[sectionIndex] : _indexModes[_indexModes.length - 1];
+ }
+
+ /**
+ * Note: Also updates primitive counts.
+ *
+ * @param indexModes
+ * the index modes to use for this MeshData.
+ */
+ public void setIndexModes(final IndexMode[] indexModes) {
+ _indexModes = indexModes;
+ updatePrimitiveCounts();
+ refreshInterleaved();
+ }
+
+ /**
+ * Gets the section count.
+ *
+ * @return the number of sections (lengths, indexModes, etc.) this MeshData contains.
+ */
+ public int getSectionCount() {
+ return _indexLengths != null ? _indexLengths.length : 1;
+ }
+
+ /**
+ * Gets the total primitive count.
+ *
+ * @return the sum of the primitive counts on all sections of this mesh data.
+ */
+ public int getTotalPrimitiveCount() {
+ int count = 0;
+ for (int i = 0; i < _primitiveCounts.length; i++) {
+ count += _primitiveCounts[i];
+ }
+ return count;
+ }
+
+ /**
+ * Gets the primitive count.
+ *
+ * @param section
+ * the section
+ *
+ * @return the number of primitives (triangles, quads, lines, points, etc.) on a given section of this mesh data.
+ */
+ public int getPrimitiveCount(final int section) {
+ return _primitiveCounts[section];
+ }
+
+ /**
+ * Returns the vertex indices of a specified primitive.
+ *
+ * @param primitiveIndex
+ * which triangle, quad, etc
+ * @param section
+ * which section to pull from (corresponds to array position in indexmodes and lengths)
+ * @param store
+ * an int array to store the results in. if null, or the length < the size of the primitive, a new array
+ * is created and returned.
+ *
+ * @return the primitive's vertex indices as an array
+ *
+ * @throws IndexOutOfBoundsException
+ * if primitiveIndex is outside of range [0, count-1] where count is the number of primitives in the
+ * given section.
+ * @throws ArrayIndexOutOfBoundsException
+ * if section is out of range [0, N-1] where N is the number of sections in this MeshData object.
+ */
+ public int[] getPrimitiveIndices(final int primitiveIndex, final int section, final int[] store) {
+ final int count = getPrimitiveCount(section);
+ if (primitiveIndex >= count || primitiveIndex < 0) {
+ throw new IndexOutOfBoundsException("Invalid primitiveIndex '" + primitiveIndex + "'. Count is " + count);
+ }
+
+ final IndexMode mode = getIndexMode(section);
+ final int rSize = mode.getVertexCount();
+
+ int[] result = store;
+ if (result == null || result.length < rSize) {
+ result = new int[rSize];
+ }
+
+ for (int i = 0; i < rSize; i++) {
+ if (getIndexBuffer() != null) {
+ result[i] = getIndices().get(getVertexIndex(primitiveIndex, i, section));
+ } else {
+ result[i] = getVertexIndex(primitiveIndex, i, section);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the vertices that make up the given primitive.
+ *
+ * @param primitiveIndex
+ * the primitive index
+ * @param section
+ * the section
+ * @param store
+ * the store. If null or the wrong size, we'll make a new array and return that instead.
+ *
+ * @return the primitive
+ */
+ public Vector3[] getPrimitiveVertices(final int primitiveIndex, final int section, final Vector3[] store) {
+ final int count = getPrimitiveCount(section);
+ if (primitiveIndex >= count || primitiveIndex < 0) {
+ throw new IndexOutOfBoundsException("Invalid primitiveIndex '" + primitiveIndex + "'. Count is " + count);
+ }
+
+ final IndexMode mode = getIndexMode(section);
+ final int rSize = mode.getVertexCount();
+ Vector3[] result = store;
+ if (result == null || result.length < rSize) {
+ result = new Vector3[rSize];
+ }
+
+ for (int i = 0; i < rSize; i++) {
+ if (result[i] == null) {
+ result[i] = new Vector3();
+ }
+ if (getIndexBuffer() != null) {
+ // indexed geometry
+ BufferUtils.populateFromBuffer(result[i], getVertexBuffer(),
+ getIndices().get(getVertexIndex(primitiveIndex, i, section)));
+ } else {
+ // non-indexed geometry
+ BufferUtils
+ .populateFromBuffer(result[i], getVertexBuffer(), getVertexIndex(primitiveIndex, i, section));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Gets the vertex index.
+ *
+ * @param primitiveIndex
+ * which triangle, quad, etc.
+ * @param point
+ * which point on the triangle, quad, etc. (triangle has three points, so this would be 0-2, etc.)
+ * @param section
+ * which section to pull from (corresponds to array position in indexmodes and lengths)
+ *
+ * @return the position you would expect to find the given point in the index buffer
+ */
+ public int getVertexIndex(final int primitiveIndex, final int point, final int section) {
+ int index = 0;
+ // move our offset up to the beginning of our section
+ for (int i = 0; i < section; i++) {
+ index += _indexLengths[i];
+ }
+
+ // Ok, now pull primitive index based on indexmode.
+ switch (getIndexMode(section)) {
+ case Triangles:
+ index += (primitiveIndex * 3) + point;
+ break;
+ case TriangleStrip:
+ // XXX: Do we need to flip point 0 and 1 on odd primitiveIndex values?
+ // if (point < 2 && primitiveIndex % 2 == 1) {
+ // index += primitiveIndex + (point == 0 ? 1 : 0);
+ // } else {
+ index += primitiveIndex + point;
+ // }
+ break;
+ case TriangleFan:
+ if (point == 0) {
+ index += 0;
+ } else {
+ index += primitiveIndex + point;
+ }
+ break;
+ case Quads:
+ index += (primitiveIndex * 4) + point;
+ break;
+ case QuadStrip:
+ index += (primitiveIndex * 2) + point;
+ break;
+ case Points:
+ index += primitiveIndex;
+ break;
+ case Lines:
+ index += (primitiveIndex * 2) + point;
+ break;
+ case LineStrip:
+ case LineLoop:
+ index += primitiveIndex + point;
+ break;
+ default:
+ logger.warning("unimplemented index mode: " + getIndexMode(0));
+ return -1;
+ }
+ return index;
+ }
+
+ /**
+ * Random vertex.
+ *
+ * @param store
+ * the vector object to store the result in. if null, a new one is created.
+ *
+ * @return a random vertex from the vertices stored in this MeshData. null is returned if there are no vertices.
+ */
+ public Vector3 randomVertex(final Vector3 store) {
+ if (_vertexCoords == null) {
+ return null;
+ }
+
+ Vector3 result = store;
+ if (result == null) {
+ result = new Vector3();
+ }
+
+ final int i = MathUtils.nextRandomInt(0, getVertexCount() - 1);
+ BufferUtils.populateFromBuffer(result, _vertexCoords.getBuffer(), i);
+
+ return result;
+ }
+
+ /**
+ * Random point on primitives.
+ *
+ * @param store
+ * the vector object to store the result in. if null, a new one is created.
+ *
+ * @return a random point from the surface of a primitive stored in this MeshData. null is returned if there are no
+ * vertices or indices.
+ */
+ public Vector3 randomPointOnPrimitives(final Vector3 store) {
+ if (_vertexCoords == null || _indexBuffer == null) {
+ return null;
+ }
+
+ Vector3 result = store;
+ if (result == null) {
+ result = new Vector3();
+ }
+
+ // randomly pick a section (if there are more than 1)
+ final int section = MathUtils.nextRandomInt(0, getSectionCount() - 1);
+
+ // randomly pick a primitive in that section
+ final int primitiveIndex = MathUtils.nextRandomInt(0, getPrimitiveCount(section) - 1);
+
+ // Now, based on IndexMode, pick a point on that primitive
+ final IndexMode mode = getIndexMode(section);
+ final boolean hasIndices = getIndexBuffer() != null;
+ switch (mode) {
+ case Triangles:
+ case TriangleFan:
+ case TriangleStrip:
+ case Quads:
+ case QuadStrip: {
+ int pntA = getVertexIndex(primitiveIndex, 0, section);
+ int pntB = getVertexIndex(primitiveIndex, 1, section);
+ int pntC = getVertexIndex(primitiveIndex, 2, section);
+
+ if (hasIndices) {
+ pntA = getIndices().get(pntA);
+ pntB = getIndices().get(pntB);
+ pntC = getIndices().get(pntC);
+ }
+
+ double b = MathUtils.nextRandomDouble();
+ double c = MathUtils.nextRandomDouble();
+
+ if (mode != IndexMode.Quads && mode != IndexMode.QuadStrip) {
+ // keep it in the triangle by reflecting it across the center diagonal BC
+ if (b + c > 1) {
+ b = 1 - b;
+ c = 1 - c;
+ }
+ }
+
+ final double a = 1 - b - c;
+
+ final Vector3 work = Vector3.fetchTempInstance();
+ BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntA);
+ work.multiplyLocal(a);
+ result.set(work);
+
+ BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntB);
+ work.multiplyLocal(b);
+ result.addLocal(work);
+
+ BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntC);
+ work.multiplyLocal(c);
+ result.addLocal(work);
+ Vector3.releaseTempInstance(work);
+ break;
+ }
+ case Points: {
+ int pnt = getVertexIndex(primitiveIndex, 0, section);
+ if (hasIndices) {
+ pnt = getIndices().get(pnt);
+ }
+ BufferUtils.populateFromBuffer(result, getVertexBuffer(), pnt);
+ break;
+ }
+ case Lines:
+ case LineLoop:
+ case LineStrip: {
+ int pntA = getVertexIndex(primitiveIndex, 0, section);
+ int pntB = getVertexIndex(primitiveIndex, 1, section);
+ if (hasIndices) {
+ pntA = getIndices().get(pntA);
+ pntB = getIndices().get(pntB);
+ }
+
+ final Vector3 work = Vector3.fetchTempInstance();
+ BufferUtils.populateFromBuffer(result, getVertexBuffer(), pntA);
+ BufferUtils.populateFromBuffer(work, getVertexBuffer(), pntB);
+ Vector3.lerp(result, work, MathUtils.nextRandomDouble(), result);
+ Vector3.releaseTempInstance(work);
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Translate points.
+ *
+ * @param x
+ * the x
+ * @param y
+ * the y
+ * @param z
+ * the z
+ */
+ public void translatePoints(final double x, final double y, final double z) {
+ translatePoints(new Vector3(x, y, z));
+ }
+
+ /**
+ * Translate points.
+ *
+ * @param amount
+ * the amount
+ */
+ public void translatePoints(final Vector3 amount) {
+ for (int x = 0; x < _vertexCount; x++) {
+ BufferUtils.addInBuffer(amount, _vertexCoords.getBuffer(), x);
+ }
+ }
+
+ public void transformVertices(final Transform transform) {
+ final Vector3 store = new Vector3();
+ for (int x = 0; x < _vertexCount; x++) {
+ BufferUtils.populateFromBuffer(store, _vertexCoords.getBuffer(), x);
+ transform.applyForward(store, store);
+ BufferUtils.setInBuffer(store, _vertexCoords.getBuffer(), x);
+ }
+ }
+
+ public void transformNormals(final Transform transform, final boolean normalize) {
+ final Vector3 store = new Vector3();
+ for (int x = 0; x < _vertexCount; x++) {
+ BufferUtils.populateFromBuffer(store, _normalCoords.getBuffer(), x);
+ transform.applyForwardVector(store, store);
+ if (normalize) {
+ store.normalizeLocal();
+ }
+ BufferUtils.setInBuffer(store, _normalCoords.getBuffer(), x);
+ }
+ }
+
+ /**
+ * Rotate points.
+ *
+ * @param rotate
+ * the rotate
+ */
+ public void rotatePoints(final Quaternion rotate) {
+ final Vector3 store = new Vector3();
+ for (int x = 0; x < _vertexCount; x++) {
+ BufferUtils.populateFromBuffer(store, _vertexCoords.getBuffer(), x);
+ rotate.apply(store, store);
+ BufferUtils.setInBuffer(store, _vertexCoords.getBuffer(), x);
+ }
+ }
+
+ /**
+ * Rotate normals.
+ *
+ * @param rotate
+ * the rotate
+ */
+ public void rotateNormals(final Quaternion rotate) {
+ final Vector3 store = new Vector3();
+ for (int x = 0; x < _vertexCount; x++) {
+ BufferUtils.populateFromBuffer(store, _normalCoords.getBuffer(), x);
+ rotate.apply(store, store);
+ BufferUtils.setInBuffer(store, _normalCoords.getBuffer(), x);
+ }
+ }
+
+ /**
+ * Update primitive counts.
+ */
+ private void updatePrimitiveCounts() {
+ final int maxIndex = _indexBuffer != null ? _indexBuffer.getBufferLimit() : _vertexCount;
+ final int maxSection = _indexLengths != null ? _indexLengths.length : 1;
+ if (_primitiveCounts.length != maxSection) {
+ _primitiveCounts = new int[maxSection];
+ }
+ for (int i = 0; i < maxSection; i++) {
+ final int size = _indexLengths != null ? _indexLengths[i] : maxIndex;
+ final int count = IndexMode.getPrimitiveCount(getIndexMode(i), size);
+ _primitiveCounts[i] = count;
+ }
+
+ }
+
+ /**
+ * @param glContext
+ * the object representing the OpenGL context a vbo belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the vbo id of a vbo in the given context. If the vbo is not found in the given context, 0 is returned.
+ */
+ public int getVBOInterleavedID(final Object glContext) {
+ if (_vboIdCache != null && _vboIdCache.containsKey(glContext)) {
+ return _vboIdCache.get(glContext);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets the id for a vbo based on interleaving this MeshData's buffer, in regards to the given OpenGL context.
+ *
+ * @param glContext
+ * the object representing the OpenGL context a vbo belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @param vboId
+ * the vbo id of a vbo. To be valid, this must be != 0.
+ * @throws IllegalArgumentException
+ * if vboId is equal to 0.
+ */
+ public void setVBOInterleavedID(final Object glContext, final int vboId) {
+ if (vboId == 0) {
+ throw new IllegalArgumentException("vboId must != 0");
+ }
+
+ if (_vboIdCache == null) {
+ _vboIdCache = new MapMaker().initialCapacity(1).weakKeys().makeMap();
+ }
+ _vboIdCache.put(glContext, vboId);
+ }
+
+ public MeshData makeCopy() {
+ final MeshData data = new MeshData();
+ data._vertexCount = _vertexCount;
+ data._primitiveCounts = new int[_primitiveCounts.length];
+ System.arraycopy(_primitiveCounts, 0, data._primitiveCounts, 0, _primitiveCounts.length);
+
+ if (_vertexCoords != null) {
+ data._vertexCoords = _vertexCoords.makeCopy();
+ }
+ if (_normalCoords != null) {
+ data._normalCoords = _normalCoords.makeCopy();
+ }
+ if (_colorCoords != null) {
+ data._colorCoords = _colorCoords.makeCopy();
+ }
+ if (_fogCoords != null) {
+ data._fogCoords = _fogCoords.makeCopy();
+ }
+ if (_tangentCoords != null) {
+ data._tangentCoords = _tangentCoords.makeCopy();
+ }
+
+ for (final FloatBufferData tCoord : _textureCoords) {
+ if (tCoord != null) {
+ data._textureCoords.add(tCoord.makeCopy());
+ } else {
+ data._textureCoords.add(null);
+ }
+ }
+
+ if (_indexBuffer != null) {
+ data._indexBuffer = _indexBuffer.makeCopy();
+ }
+
+ if (_indexLengths != null) {
+ data._indexLengths = new int[_indexLengths.length];
+ System.arraycopy(_indexLengths, 0, data._indexLengths, 0, _indexLengths.length);
+ }
+ data._indexModes = new IndexMode[_indexModes.length];
+ System.arraycopy(_indexModes, 0, data._indexModes, 0, _indexModes.length);
+
+ return data;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<? extends MeshData> getClassTag() {
+ return this.getClass();
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_vertexCount, "vertexCount", 0);
+ capsule.write(_vertexCoords, "vertexBuffer", null);
+ capsule.write(_normalCoords, "normalBuffer", null);
+ capsule.write(_colorCoords, "colorBuffer", null);
+ capsule.write(_fogCoords, "fogBuffer", null);
+ capsule.write(_tangentCoords, "tangentBuffer", null);
+ capsule.writeSavableList(_textureCoords, "textureCoords", new ArrayList<FloatBufferData>(1));
+ capsule.write((Savable) _indexBuffer, "indexBuffer", null);
+ capsule.write(_interleaved, "interleaved", null);
+ capsule.write(_indexLengths, "indexLengths", null);
+ capsule.write(_indexModes, "indexModes");
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _vertexCount = capsule.readInt("vertexCount", 0);
+ _vertexCoords = (FloatBufferData) capsule.readSavable("vertexBuffer", null);
+ _normalCoords = (FloatBufferData) capsule.readSavable("normalBuffer", null);
+ _colorCoords = (FloatBufferData) capsule.readSavable("colorBuffer", null);
+ _fogCoords = (FloatBufferData) capsule.readSavable("fogBuffer", null);
+ _tangentCoords = (FloatBufferData) capsule.readSavable("tangentBuffer", null);
+ _textureCoords = capsule.readSavableList("textureCoords", new ArrayList<FloatBufferData>(1));
+ _indexBuffer = (IndexBufferData<?>) capsule.readSavable("indexBuffer", null);
+ _interleaved = (FloatBufferData) capsule.readSavable("interleaved", null);
+ _indexLengths = capsule.readIntArray("indexLengths", null);
+ _indexModes = capsule.readEnumArray("indexModes", IndexMode.class, new IndexMode[] { IndexMode.Triangles });
+
+ updatePrimitiveCounts();
+ }
+
+ public InstancingManager getInstancingManager() {
+ return _instancingManager;
+ }
+
+ public void setInstancingManager(final InstancingManager info) {
+ _instancingManager = info;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java
new file mode 100644
index 0000000..f03c447
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Node.java
@@ -0,0 +1,515 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.scenegraph.visitor.Visitor;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.scenegraph.RenderDelegate;
+
+/**
+ * Node defines an internal node of a scene graph. The internal node maintains a collection of children and handles
+ * merging said children into a single bound to allow for very fast culling of multiple nodes. Node allows for any
+ * number of children to be attached.
+ */
+public class Node extends Spatial {
+ private static final Logger logger = Logger.getLogger(Node.class.getName());
+
+ /** This node's children. */
+ protected final List<Spatial> _children;
+
+ /**
+ * Constructs a new Spatial.
+ */
+ public Node() {
+ super();
+ _children = Collections.synchronizedList(new ArrayList<Spatial>(1));
+ }
+
+ /**
+ * Constructs a new <code>Node</code> with a given name.
+ *
+ * @param name
+ * the name of the node. This is required for identification purposes.
+ */
+ public Node(final String name) {
+ this(name, Collections.synchronizedList(new ArrayList<Spatial>(1)));
+ }
+
+ /**
+ * Constructs a new <code>Node</code> with a given name.
+ *
+ * @param name
+ * the name of the node. This is required for identification purposes.
+ * @param children
+ * the list to use for storing children. Defaults to a synchronized ArrayList, but using this
+ * constructor, you can select a different kind of list.
+ */
+ public Node(final String name, final List<Spatial> children) {
+ super(name);
+ _children = children;
+ }
+
+ /**
+ *
+ * <code>attachChild</code> attaches a child to this node. This node becomes the child's parent. The current number
+ * of children maintained is returned. <br>
+ * If the child already had a parent it is detached from that former parent.
+ *
+ * @param child
+ * the child to attach to this node.
+ * @return the number of children maintained by this node.
+ */
+ public int attachChild(final Spatial child) {
+ if (child != null) {
+ if (child == this || (child instanceof Node && hasAncestor((Node) child))) {
+ throw new IllegalArgumentException("Child is already part of this hierarchy.");
+ }
+ if (child.getParent() != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ _children.add(child);
+ child.markDirty(DirtyType.Attached);
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("Child (" + child.getName() + ") attached to this" + " node (" + getName() + ")");
+ }
+ }
+ }
+
+ return _children.size();
+ }
+
+ /**
+ *
+ * <code>attachChildAt</code> attaches a child to this node at an index. This node becomes the child's parent. The
+ * current number of children maintained is returned. <br>
+ * If the child already had a parent it is detached from that former parent.
+ *
+ * @param child
+ * the child to attach to this node.
+ * @return the number of children maintained by this node.
+ */
+ public int attachChildAt(final Spatial child, final int index) {
+ if (child != null) {
+ if (child == this || (child instanceof Node && hasAncestor((Node) child))) {
+ throw new IllegalArgumentException("Child is already part of this hierarchy.");
+ }
+ if (child.getParent() != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ _children.add(index, child);
+ child.markDirty(DirtyType.Attached);
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("Child (" + child.getName() + ") attached to this" + " node (" + getName() + ")");
+ }
+ }
+ }
+
+ return _children.size();
+ }
+
+ /**
+ * <code>detachChild</code> removes a given child from the node's list. This child will no longe be maintained.
+ *
+ * @param child
+ * the child to remove.
+ * @return the index the child was at. -1 if the child was not in the list.
+ */
+ public int detachChild(final Spatial child) {
+ if (child == null) {
+ return -1;
+ }
+ if (child.getParent() == this) {
+ final int index = _children.indexOf(child);
+ if (index != -1) {
+ detachChildAt(index);
+ }
+ return index;
+ }
+
+ return -1;
+ }
+
+ /**
+ * <code>detachChild</code> removes a given child from the node's list. This child will no longe be maintained. Only
+ * the first child with a matching name is removed.
+ *
+ * @param childName
+ * the child to remove.
+ * @return the index the child was at. -1 if the child was not in the list.
+ */
+ public int detachChildNamed(final String childName) {
+ if (childName == null) {
+ return -1;
+ }
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+ if (childName.equals(child.getName())) {
+ detachChildAt(i);
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ *
+ * <code>detachChildAt</code> removes a child at a given index. That child is returned for saving purposes.
+ *
+ * @param index
+ * the index of the child to be removed.
+ * @return the child at the supplied index.
+ */
+ public Spatial detachChildAt(final int index) {
+ final Spatial child = _children.remove(index);
+ if (child != null) {
+ child.setParent(null);
+ markDirty(child, DirtyType.Detached);
+ if (child.getListener() != null) {
+ child.setListener(null);
+ }
+ if (logger.isLoggable(Level.INFO)) {
+ logger.fine("Child removed.");
+ }
+ }
+ return child;
+ }
+
+ /**
+ *
+ * <code>detachAllChildren</code> removes all children attached to this node.
+ */
+ public void detachAllChildren() {
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ detachChildAt(i);
+ }
+ logger.fine("All children removed.");
+ }
+
+ /**
+ * Get the index of the specified spatial.
+ *
+ * @param sp
+ * spatial to retrieve index for.
+ * @return the index
+ */
+ public int getChildIndex(final Spatial sp) {
+ return _children.indexOf(sp);
+ }
+
+ /**
+ * Returns all children to this node.
+ *
+ * @return a list containing all children to this node
+ */
+ public List<Spatial> getChildren() {
+ return _children;
+ }
+
+ /**
+ * Swaps two children.
+ *
+ * @param index1
+ * @param index2
+ */
+ public void swapChildren(final int index1, final int index2) {
+ final Spatial c2 = _children.get(index2);
+ final Spatial c1 = _children.remove(index1);
+ _children.add(index1, c2);
+ _children.remove(index2);
+ _children.add(index2, c1);
+ }
+
+ @Override
+ protected void updateChildren(final double time) {
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial pkChild = getChild(i);
+ if (pkChild != null) {
+ pkChild.updateGeometricState(time, false);
+ }
+ }
+ }
+
+ /**
+ *
+ * <code>getChild</code> returns a child at a given index.
+ *
+ * @param i
+ * the index to retrieve the child from.
+ * @return the child at a specified index.
+ */
+ public Spatial getChild(final int i) {
+ return _children.get(i);
+ }
+
+ /**
+ * <code>getChild</code> returns the first child found with exactly the given name (case sensitive.) If our children
+ * are Nodes, we will search their children as well.
+ *
+ * @param name
+ * the name of the child to retrieve. If null, we'll return null.
+ * @return the child if found, or null.
+ */
+ public Spatial getChild(final String name) {
+ if (name == null) {
+ return null;
+ }
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+ if (name.equals(child.getName())) {
+ return child;
+ } else if (child instanceof Node) {
+ final Spatial out = ((Node) child).getChild(name);
+ if (out != null) {
+ return out;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * determines if the provided Spatial is contained in the children list of this node.
+ *
+ * @param spat
+ * the child object to look for.
+ * @return true if the object is contained, false otherwise.
+ */
+ public boolean hasChild(final Spatial spat) {
+ if (_children.contains(spat)) {
+ return true;
+ }
+
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+ if (child instanceof Node && ((Node) child).hasChild(spat)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ *
+ * <code>getNumberOfChildren</code> returns the number of children this node maintains.
+ *
+ * @return the number of children this node maintains.
+ */
+ public int getNumberOfChildren() {
+ return _children.size();
+ }
+
+ @Override
+ protected void propagateDirtyDown(final EnumSet<DirtyType> dirtyTypes) {
+ super.propagateDirtyDown(dirtyTypes);
+
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+
+ child.propagateDirtyDown(dirtyTypes);
+ }
+ }
+
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ super.updateWorldTransform(recurse);
+
+ if (recurse) {
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ _children.get(i).updateWorldTransform(true);
+ }
+ }
+ }
+
+ @Override
+ protected void updateWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) {
+ super.updateWorldRenderStates(recurse, stack);
+
+ if (recurse) {
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ _children.get(i).updateWorldRenderStates(true, stack);
+ }
+ }
+ }
+
+ /**
+ * <code>draw</code> calls the onDraw method for each child maintained by this node.
+ *
+ * @see com.ardor3d.scenegraph.Spatial#draw(com.ardor3d.renderer.Renderer)
+ * @param r
+ * the renderer to draw to.
+ */
+ @Override
+ public void draw(final Renderer r) {
+
+ final RenderDelegate delegate = getCurrentRenderDelegate();
+ if (delegate == null) {
+ Spatial child;
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ child = _children.get(i);
+ if (child != null) {
+ child.onDraw(r);
+ }
+ }
+ } else {
+ // Queue as needed
+ if (!r.isProcessingQueue()) {
+ if (r.checkAndAdd(this)) {
+ return;
+ }
+ }
+
+ delegate.render(this, r);
+ }
+ }
+
+ /**
+ * <code>updateWorldBound</code> merges the bounds of all the children maintained by this node. This will allow for
+ * faster culling operations.
+ *
+ * @see com.ardor3d.scenegraph.Spatial#updateWorldBound(boolean)
+ */
+ @Override
+ public void updateWorldBound(final boolean recurse) {
+ BoundingVolume worldBound = null;
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+ if (child != null) {
+ if (recurse) {
+ child.updateWorldBound(true);
+ }
+ if (worldBound != null) {
+ // merge current world bound with child world bound
+ worldBound.mergeLocal(child.getWorldBound());
+
+ // simple check to catch NaN issues
+ if (!Vector3.isValid(worldBound.getCenter())) {
+ throw new Ardor3dException("WorldBound center is invalid after merge between " + this + " and "
+ + child);
+ }
+ } else {
+ // set world bound to first non-null child world bound
+ if (child.getWorldBound() != null) {
+ worldBound = child.getWorldBound().clone(_worldBound);
+ }
+ }
+ }
+ }
+ _worldBound = worldBound;
+ clearDirty(DirtyType.Bounding);
+ }
+
+ @Override
+ public void acceptVisitor(final Visitor visitor, final boolean preexecute) {
+ if (preexecute) {
+ visitor.visit(this);
+ }
+
+ Spatial child;
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ child = _children.get(i);
+ if (child != null) {
+ child.acceptVisitor(visitor, preexecute);
+ }
+ }
+
+ if (!preexecute) {
+ visitor.visit(this);
+ }
+ }
+
+ @Override
+ public void sortLights() {
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial pkChild = getChild(i);
+ if (pkChild != null) {
+ pkChild.sortLights();
+ }
+ }
+ }
+
+ @Override
+ public Node makeCopy(final boolean shareGeometricData) {
+ // get copy of basic spatial info
+ final Node node = (Node) super.makeCopy(shareGeometricData);
+
+ // add copy of children
+ for (final Spatial child : getChildren()) {
+ final Spatial copy = child.makeCopy(shareGeometricData);
+ node.attachChild(copy);
+ }
+
+ // return
+ return node;
+ }
+
+ @Override
+ public Node makeInstanced() {
+ // get copy of basic spatial info
+ final Node node = (Node) super.makeInstanced();
+ // add copy of children
+ for (final Spatial child : getChildren()) {
+ final Spatial copy = child.makeInstanced();
+ node.attachChild(copy);
+ }
+ return node;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ @Override
+ public Class<? extends Node> getClassTag() {
+ return this.getClass();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.writeSavableList(new ArrayList<Spatial>(_children), "children", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ final List<Spatial> cList = capsule.readSavableList("children", null);
+ _children.clear();
+ if (cList != null) {
+ _children.addAll(cList);
+ }
+
+ // go through children and set parent to this node
+ for (int i = getNumberOfChildren() - 1; i >= 0; i--) {
+ final Spatial child = _children.get(i);
+ child._parent = this;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java
new file mode 100644
index 0000000..70d04ef
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Point.java
@@ -0,0 +1,320 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Point</code> defines a collection of vertices that are rendered as single points or textured sprites depending
+ * on PointType.
+ */
+public class Point extends Mesh {
+
+ public enum PointType {
+ Point, PointSprite;
+ };
+
+ private PointType _pointType;
+
+ private float _pointSize = 1.0f;
+ private boolean _antialiased = false;
+
+ /**
+ * Distance Attenuation fields.
+ */
+ // XXX: LWJGL requires 4 floats, but only 3 coefficients are mentioned in the specification?
+ // JOGL works fine with 3.
+ private final FloatBuffer _attenuationCoefficients = BufferUtils.createFloatBuffer(new float[] { 0.0f, 0f,
+ 0.000004f, 0f });
+ private float _minPointSize = 1.0f;
+ private float _maxPointSize = 64.0f;
+ private boolean _useDistanceAttenuation = false;
+
+ public Point() {
+ this("point", null, null, null, (FloatBufferData) null);
+ }
+
+ public Point(final PointType type) {
+ this();
+ _pointType = type;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Point</code> object with a given set of data. Any data may be null, except
+ * the vertex array. If this is null an exception is thrown.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param vertex
+ * the vertices or points.
+ * @param normal
+ * the normals of the points.
+ * @param color
+ * the color of the points.
+ * @param texture
+ * the texture coordinates of the points.
+ */
+ public Point(final String name, final ReadOnlyVector3[] vertex, final ReadOnlyVector3[] normal,
+ final ReadOnlyColorRGBA[] color, final ReadOnlyVector2[] texture) {
+ super(name);
+ setupData(BufferUtils.createFloatBuffer(vertex), BufferUtils.createFloatBuffer(normal),
+ BufferUtils.createFloatBuffer(color), FloatBufferDataUtil.makeNew(texture));
+ _meshData.setIndexMode(IndexMode.Points);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Point</code> object with a given set of data. Any data may be null, except
+ * the vertex array. If this is null an exception is thrown.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param vertex
+ * the vertices or points.
+ * @param normal
+ * the normals of the points.
+ * @param color
+ * the color of the points.
+ * @param coords
+ * the texture coordinates of the points.
+ */
+ public Point(final String name, final FloatBuffer vertex, final FloatBuffer normal, final FloatBuffer color,
+ final FloatBufferData coords) {
+ super(name);
+ setupData(vertex, normal, color, coords);
+ _meshData.setIndexMode(IndexMode.Points);
+ }
+
+ /**
+ * Initialize the meshdata object with data.
+ *
+ * @param vertices
+ * @param normals
+ * @param colors
+ * @param coords
+ */
+ private void setupData(final FloatBuffer vertices, final FloatBuffer normals, final FloatBuffer colors,
+ final FloatBufferData coords) {
+ _meshData.setVertexBuffer(vertices);
+ _meshData.setNormalBuffer(normals);
+ _meshData.setColorBuffer(colors);
+ _meshData.setTextureCoords(coords, 0);
+ }
+
+ public boolean isPointSprite() {
+ return _pointType == PointType.PointSprite;
+ }
+
+ /**
+ * Default attenuation coefficient is calculated to work best with pointSize = 1.
+ *
+ * @param bool
+ */
+ public void enableDistanceAttenuation(final boolean bool) {
+ _useDistanceAttenuation = bool;
+ }
+
+ /**
+ * Distance Attenuation Equation:<br>
+ * x = distance from the eye<br>
+ * Derived Size = clamp( pointSize * sqrt( attenuation(x) ) )<br>
+ * attenuation(x) = 1 / (a + b*x + c*x^2)
+ * <p>
+ * Default coefficients used are(they should work best with pointSize=1):<br>
+ * a = 0, b = 0, c = 0.000004f<br>
+ * This should give(without taking regards to the max,min pointSize clamping):<br>
+ * 1. A size of 1 pixel at distance of 500 units.<br>
+ * Derived Size = 1/(0.000004*500^2) = 1<br>
+ * 2. A size of 25 pixel at distance of 100 units.<br>
+ * 3. A size of 2500 at a distance of 10 units.<br>
+ *
+ * @see <a href="http://www.opengl.org/registry/specs/ARB/point_parameters.txt">OpenGL specification</a>
+ * @param a
+ * constant term in the attenuation equation
+ * @param b
+ * linear term in the attenuation equation
+ * @param c
+ * quadratic term in the attenuation equation
+ */
+ public void setDistanceAttenuationCoefficients(final float a, final float b, final float c) {
+ _attenuationCoefficients.put(0, a);
+ _attenuationCoefficients.put(1, b);
+ _attenuationCoefficients.put(2, c);
+ }
+
+ /**
+ * @return true if points are to be drawn antialiased
+ */
+ public boolean isAntialiased() {
+ return _antialiased;
+ }
+
+ /**
+ * Sets whether the point should be antialiased. May decrease performance. If you want to enabled antialiasing, you
+ * should also use an alphastate with a source of SourceFunction.SourceAlpha and a destination of
+ * DB_ONE_MINUS_SRC_ALPHA or DB_ONE.
+ *
+ * @param antialiased
+ * true if the line should be antialiased.
+ */
+ public void setAntialiased(final boolean antialiased) {
+ _antialiased = antialiased;
+ }
+
+ public PointType getPointType() {
+ return _pointType;
+ }
+
+ public void setPointType(final PointType pointType) {
+ _pointType = pointType;
+ }
+
+ /**
+ * @return the pixel size of each point.
+ */
+ public float getPointSize() {
+ return _pointSize;
+ }
+
+ /**
+ * Sets the pixel width of the point when drawn. Non anti-aliased point sizes are rounded to the nearest whole
+ * number by opengl.
+ *
+ * @param size
+ * The size to set.
+ */
+ public void setPointSize(final float size) {
+ _pointSize = size;
+ }
+
+ /**
+ * When DistanceAttenuation is enabled, the points maximum size will get clamped to this value.
+ *
+ * @param maxSize
+ */
+ public void setMaxPointSize(final float maxSize) {
+ _maxPointSize = maxSize;
+ }
+
+ /**
+ * When DistanceAttenuation is enabled, the points maximum size will get clamped to this value.
+ *
+ * @param maxSize
+ */
+ public float getMaxPointSize() {
+ return _maxPointSize;
+ }
+
+ /**
+ * When DistanceAttenuation is enabled, the points minimum size will get clamped to this value.
+ *
+ * @param maxSize
+ */
+ public void setMinPointSize(final float minSize) {
+ _minPointSize = minSize;
+ }
+
+ /**
+ * When DistanceAttenuation is enabled, the points minimum size will get clamped to this value.
+ *
+ * @param maxSize
+ */
+ public float getMinPointSize() {
+ return _minPointSize;
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @see java.io.Serializable
+ */
+ private void writeObject(final java.io.ObjectOutputStream s) throws IOException {
+ s.defaultWriteObject();
+ }
+
+ /**
+ * Used with Serialization. Do not call this directly.
+ *
+ * @param s
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @see java.io.Serializable
+ */
+ private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ }
+
+ @Override
+ public Point makeCopy(final boolean shareGeometricData) {
+ final Point pointCopy = (Point) super.makeCopy(shareGeometricData);
+ pointCopy.setAntialiased(_antialiased);
+ pointCopy.setDistanceAttenuationCoefficients(_attenuationCoefficients.get(0), _attenuationCoefficients.get(1),
+ _attenuationCoefficients.get(2));
+ pointCopy.setMaxPointSize(_maxPointSize);
+ pointCopy.setMinPointSize(_minPointSize);
+ pointCopy.setPointSize(_pointSize);
+ pointCopy.setPointType(_pointType);
+ return pointCopy;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_pointSize, "pointSize", 1);
+ capsule.write(_antialiased, "antialiased", false);
+ capsule.write(_pointType, "pointType", PointType.Point);
+ capsule.write(_useDistanceAttenuation, "useDistanceAttenuation", false);
+ capsule.write(_attenuationCoefficients, "attenuationCoefficients", null);
+ capsule.write(_minPointSize, "minPointSize", 1);
+ capsule.write(_maxPointSize, "maxPointSize", 64);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _pointSize = capsule.readFloat("pointSize", 1);
+ _antialiased = capsule.readBoolean("antialiased", false);
+ _pointType = capsule.readEnum("pointType", PointType.class, PointType.Point);
+ _useDistanceAttenuation = capsule.readBoolean("useDistanceAttenuation", false);
+ final FloatBuffer coef = capsule.readFloatBuffer("attenuationCoefficients", null);
+ if (coef == null) {
+ _attenuationCoefficients.clear();
+ _attenuationCoefficients.put(new float[] { 0.0f, 0f, 0.000004f, 0f });
+ } else {
+ _attenuationCoefficients.clear();
+ _attenuationCoefficients.put(coef);
+ }
+ _minPointSize = capsule.readFloat("minPointSize", 1);
+ _maxPointSize = capsule.readFloat("maxPointSize", 64);
+
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ renderer.setupPointParameters(_pointSize, isAntialiased(), isPointSprite(), _useDistanceAttenuation,
+ _attenuationCoefficients, _minPointSize, _maxPointSize);
+
+ super.render(renderer);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java
new file mode 100644
index 0000000..cfc8d8b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Renderable.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * Renderable is the interface for objects that can be rendered.
+ */
+public interface Renderable {
+ /**
+ * Render the object using the supplied renderer instance.
+ *
+ * @param renderer
+ */
+ void render(Renderer renderer);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java
new file mode 100644
index 0000000..95016bf
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/ShortBufferData.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Simple data class storing a buffer of shorts
+ */
+public class ShortBufferData extends IndexBufferData<ShortBuffer> implements Savable {
+
+ /**
+ * Instantiates a new ShortBufferData.
+ */
+ public ShortBufferData() {}
+
+ /**
+ * Instantiates a new ShortBufferData with a buffer of the given size.
+ */
+ public ShortBufferData(final int size) {
+ this(BufferUtils.createShortBuffer(size));
+ }
+
+ /**
+ * Creates a new ShortBufferData.
+ *
+ * @param buffer
+ * Buffer holding the data. Must not be null.
+ */
+ public ShortBufferData(final ShortBuffer buffer) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("Buffer can not be null!");
+ }
+
+ _buffer = buffer;
+ }
+
+ public Class<? extends ShortBufferData> getClassTag() {
+ return getClass();
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _buffer = capsule.readShortBuffer("buffer", null);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_buffer, "buffer", null);
+ }
+
+ @Override
+ public int get() {
+ return _buffer.get() & 0xFFFF;
+ }
+
+ @Override
+ public int get(final int index) {
+ return _buffer.get(index) & 0xFFFF;
+ }
+
+ @Override
+ public ShortBufferData put(final int value) {
+ if (value < 0 || value >= 65536) {
+ throw new IllegalArgumentException("Invalid value passed to short buffer: " + value);
+ }
+ _buffer.put((short) value);
+ return this;
+ }
+
+ @Override
+ public ShortBufferData put(final int index, final int value) {
+ if (value < 0 || value >= 65536) {
+ throw new IllegalArgumentException("Invalid value passed to short buffer: " + value);
+ }
+ _buffer.put(index, (short) value);
+ return this;
+ }
+
+ @Override
+ public void put(final IndexBufferData<?> buf) {
+ if (buf instanceof ShortBufferData) {
+ _buffer.put((ShortBuffer) buf.getBuffer());
+ } else {
+ while (buf.getBuffer().hasRemaining()) {
+ put(buf.get());
+ }
+ }
+ }
+
+ @Override
+ public int getByteCount() {
+ return 2;
+ }
+
+ @Override
+ public ShortBuffer getBuffer() {
+ return _buffer;
+ }
+
+ @Override
+ public IntBuffer asIntBuffer() {
+ final ShortBuffer source = getBuffer().duplicate();
+ source.rewind();
+ final IntBuffer buff = BufferUtils.createIntBufferOnHeap(source.limit());
+ for (int i = 0, max = source.limit(); i < max; i++) {
+ buff.put(source.get() & 0xFFFF);
+ }
+ buff.flip();
+ return buff;
+ }
+
+ @Override
+ public ShortBufferData makeCopy() {
+ final ShortBufferData copy = new ShortBufferData();
+ copy._buffer = BufferUtils.clone(_buffer);
+ copy._vboAccessMode = _vboAccessMode;
+ return copy;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java
new file mode 100644
index 0000000..14d8da4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/Spatial.java
@@ -0,0 +1,1423 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.ValidatingTransform;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyQuaternion;
+import com.ardor3d.math.type.ReadOnlyTransform;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateStack;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.controller.SpatialController;
+import com.ardor3d.scenegraph.event.DirtyEventListener;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.Hintable;
+import com.ardor3d.scenegraph.hint.SceneHints;
+import com.ardor3d.scenegraph.visitor.Visitor;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.export.CapsuleUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.scenegraph.RenderDelegate;
+import com.google.common.collect.MapMaker;
+
+/**
+ * Base class for all scenegraph objects.
+ */
+public abstract class Spatial implements Savable, Hintable {
+ private static final Logger logger = Logger.getLogger(Spatial.class.getName());
+
+ /** This spatial's name. */
+ protected String _name;
+
+ /** Spatial's transform relative to its parent. */
+ protected final Transform _localTransform;
+
+ /** Spatial's absolute transform. */
+ protected final Transform _worldTransform;
+
+ /** Spatial's world bounding volume. */
+ protected BoundingVolume _worldBound;
+
+ /** Spatial's parent, or null if it has none. */
+ protected Node _parent;
+
+ /** ArrayList of controllers for this spatial. */
+ protected List<SpatialController<?>> _controllers;
+
+ /** The render states of this spatial. */
+ protected EnumMap<RenderState.StateType, RenderState> _renderStateList = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ /** Listener for dirty events. */
+ protected DirtyEventListener _listener;
+
+ /** Field for accumulating dirty marks. */
+ protected EnumSet<DirtyType> _dirtyMark = EnumSet
+ .of(DirtyType.Bounding, DirtyType.RenderState, DirtyType.Transform);
+
+ /** Field for user data. Note: If this object is not explicitly of type Savable, it will be ignored during save. */
+ protected Object _userData = null;
+
+ /** Keeps track of the current frustum intersection state of this Spatial. */
+ protected Camera.FrustumIntersect _frustumIntersects = Camera.FrustumIntersect.Intersects;
+
+ /** The hints for Ardor3D's use when evaluating and rendering this spatial. */
+ protected SceneHints _sceneHints;
+
+ /** The render delegates to use for this Spatial, mapped by glContext reference. */
+ protected transient Map<Object, RenderDelegate> _delegateMap = null;
+
+ public transient double _queueDistance = Double.NEGATIVE_INFINITY;
+
+ /** The default delegate reference to use if none provided. */
+ private static final Object defaultDelegateRef = new Object();
+
+ protected static final EnumSet<DirtyType> ON_DIRTY_TRANSFORM = EnumSet.of(DirtyType.Bounding, DirtyType.Transform);
+ protected static final EnumSet<DirtyType> ON_DIRTY_RENDERSTATE = EnumSet.of(DirtyType.RenderState);
+ protected static final EnumSet<DirtyType> ON_DIRTY_BOUNDING = EnumSet.of(DirtyType.Bounding);
+ protected static final EnumSet<DirtyType> ON_DIRTY_ATTACHED = EnumSet.of(DirtyType.Transform,
+ DirtyType.RenderState, DirtyType.Bounding);
+
+ /**
+ * Constructs a new Spatial. Initializes the transform fields.
+ */
+ public Spatial() {
+ _localTransform = Constants.useValidatingTransform ? new ValidatingTransform() : new Transform();
+ _worldTransform = Constants.useValidatingTransform ? new ValidatingTransform() : new Transform();
+ _sceneHints = new SceneHints(this);
+ }
+
+ /**
+ * Constructs a new <code>Spatial</code> with a given name.
+ *
+ * @param name
+ * the name of the spatial. This is required for identification purposes.
+ */
+ public Spatial(final String name) {
+ this();
+ _name = name;
+ }
+
+ /**
+ * Returns the name of this spatial.
+ *
+ * @return This spatial's name.
+ */
+ public String getName() {
+ return _name;
+ }
+
+ /**
+ * Sets the name of this Spatial.
+ *
+ * @param name
+ * new name
+ */
+ public void setName(final String name) {
+ _name = name;
+ }
+
+ /**
+ * Sets the render delegate.
+ *
+ * @param delegate
+ * the new delegate, or null for default behavior
+ * @param glContextRef
+ * if null, the delegate is set as the default render delegate for this spatial. Otherwise, the delegate
+ * is used when this Spatial is rendered in a RenderContext tied to the given glContextRef.
+ */
+ public void setRenderDelegate(final RenderDelegate delegate, final Object glContextRef) {
+ if (_delegateMap == null) {
+ if (delegate == null) {
+ return;
+ } else {
+ _delegateMap = new MapMaker().weakKeys().makeMap();
+ }
+ }
+ if (delegate != null) {
+ if (glContextRef == null) {
+ _delegateMap.put(defaultDelegateRef, delegate);
+ } else {
+ _delegateMap.put(glContextRef, delegate);
+ }
+ } else {
+ if (glContextRef == null) {
+ _delegateMap.remove(defaultDelegateRef);
+ } else {
+ _delegateMap.remove(glContextRef);
+ }
+ if (_delegateMap.isEmpty()) {
+ _delegateMap = null;
+ }
+ }
+ }
+
+ /**
+ * Gets the render delegate.
+ *
+ * @param glContextRef
+ * if null, retrieve the default render delegate for this spatial. Otherwise, retrieve the delegate used
+ * when this Spatial is rendered in a RenderContext tied to the given glContextRef.
+ * @return delegate as described.
+ */
+ public RenderDelegate getRenderDelegate(final Object glContextRef) {
+ if (_delegateMap == null) {
+ return null;
+ }
+ if (glContextRef == null) {
+ return _delegateMap.get(defaultDelegateRef);
+ } else {
+ return _delegateMap.get(glContextRef);
+ }
+ }
+
+ /**
+ * <code>getParent</code> retrieve's this node's parent. If the parent is null this is the root node.
+ *
+ * @return the parent of this node.
+ */
+ public Node getParent() {
+ return _parent;
+ }
+
+ /**
+ * Called by {@link Node#attachChild(Spatial)} and {@link Node#detachChild(Spatial)} - don't call directly.
+ * <code>setParent</code> sets the parent of this node.
+ *
+ * @param parent
+ * the parent of this node.
+ */
+ protected void setParent(final Node parent) {
+ _parent = parent;
+ }
+
+ /**
+ * <code>removeFromParent</code> removes this Spatial from it's parent.
+ *
+ * @return true if it has a parent and performed the remove.
+ */
+ public boolean removeFromParent() {
+ if (_parent != null) {
+ _parent.detachChild(this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial.
+ *
+ * @param ancestor
+ * the ancestor object to look for.
+ * @return true if the ancestor is found, false otherwise.
+ */
+ public boolean hasAncestor(final Node ancestor) {
+ if (_parent == null) {
+ return false;
+ } else if (_parent.equals(ancestor)) {
+ return true;
+ } else {
+ return _parent.hasAncestor(ancestor);
+ }
+ }
+
+ /**
+ * @see Hintable#getParentHintable()
+ */
+ public Hintable getParentHintable() {
+ return _parent;
+ }
+
+ /**
+ * Gets the scene hints.
+ *
+ * @return the scene hints set on this Spatial
+ */
+ public SceneHints getSceneHints() {
+ return _sceneHints;
+ }
+
+ /**
+ * Returns the listener for dirty events on this node, if set.
+ *
+ * @return the listener
+ */
+ public DirtyEventListener getListener() {
+ return _listener;
+ }
+
+ /**
+ * Sets the listener for dirty events on this node.
+ *
+ * @param listener
+ * listener to use.
+ */
+ public void setListener(final DirtyEventListener listener) {
+ _listener = listener;
+ }
+
+ /**
+ * Mark this node as dirty. Can be marked as Transform, Bounding, Attached, Detached, Destroyed or RenderState
+ *
+ * @param dirtyType
+ * the dirty type
+ */
+ public void markDirty(final DirtyType dirtyType) {
+ markDirty(this, dirtyType);
+ }
+
+ /**
+ * Mark this node as dirty. Can be marked as Transform, Bounding, Attached, Detached, Destroyed or RenderState
+ *
+ * @param caller
+ * the spatial where the marking was initiated
+ * @param dirtyType
+ * the dirty type
+ */
+ protected void markDirty(final Spatial caller, final DirtyType dirtyType) {
+ switch (dirtyType) {
+ case Transform:
+ propagateDirtyDown(ON_DIRTY_TRANSFORM);
+ if (_parent != null) {
+ _parent.propagateDirtyUp(ON_DIRTY_BOUNDING);
+ }
+ break;
+ case RenderState:
+ propagateDirtyDown(ON_DIRTY_RENDERSTATE);
+ break;
+ case Bounding:
+ // not just _parent here, on purpose
+ propagateDirtyUp(ON_DIRTY_BOUNDING);
+ break;
+ case Attached:
+ propagateDirtyDown(ON_DIRTY_ATTACHED);
+ if (_parent != null) {
+ _parent.propagateDirtyUp(ON_DIRTY_BOUNDING);
+ }
+ break;
+ case Detached:
+ case Destroyed:
+ if (_parent != null) {
+ _parent.propagateDirtyUp(ON_DIRTY_BOUNDING);
+ }
+ break;
+ default:
+ break;
+ }
+
+ propageEventUp(caller, dirtyType, true);
+ }
+
+ /**
+ * Test if this spatial is marked as dirty in respect to the supplied DirtyType.
+ *
+ * @param dirtyType
+ * dirty type to test against
+ * @return true if spatial marked dirty against the supplied dirty type
+ */
+ public boolean isDirty(final DirtyType dirtyType) {
+ return _dirtyMark.contains(dirtyType);
+ }
+
+ /**
+ * Clears the dirty flag set at this spatial for the supplied dirty type.
+ *
+ * @param dirtyType
+ * dirty type to clear flag for
+ */
+ public void clearDirty(final DirtyType dirtyType) {
+ clearDirty(this, dirtyType);
+ }
+
+ /**
+ * Clears the dirty flag set at this spatial for the supplied dirty type.
+ *
+ * @param caller
+ * the spatial where the clearing was initiated
+ * @param dirtyType
+ * dirty type to clear flag for
+ */
+ public void clearDirty(final Spatial caller, final DirtyType dirtyType) {
+ _dirtyMark.remove(dirtyType);
+
+ propageEventUp(caller, dirtyType, false);
+ }
+
+ /**
+ * Propagate the dirty mark up the tree hierarchy.
+ *
+ * @param dirtyTypes
+ * the dirty types
+ */
+ protected void propagateDirtyUp(final EnumSet<DirtyType> dirtyTypes) {
+ _dirtyMark.addAll(dirtyTypes);
+
+ if (_parent != null) {
+ _parent.propagateDirtyUp(dirtyTypes);
+ }
+ }
+
+ /**
+ * Propagate the dirty mark down the tree hierarchy.
+ *
+ * @param dirtyTypes
+ * the dirty types
+ */
+ protected void propagateDirtyDown(final EnumSet<DirtyType> dirtyTypes) {
+ _dirtyMark.addAll(dirtyTypes);
+ }
+
+ /**
+ * Propagate the dirty event up the hierarchy. If a listener is found on the spatial the event is fired and the
+ * propagation is stopped.
+ *
+ * @param spatial
+ * the spatial
+ * @param dirtyType
+ * the dirty type
+ * @param dirty
+ * if true, propogate a dirty event, else propogate a clean event
+ */
+ protected void propageEventUp(final Spatial spatial, final DirtyType dirtyType, final boolean dirty) {
+ boolean consumed = false;
+ if (_listener != null) {
+ if (dirty) {
+ consumed = _listener.spatialDirty(spatial, dirtyType);
+ } else {
+ consumed = _listener.spatialClean(spatial, dirtyType);
+ }
+ }
+
+ if (!consumed && _parent != null) {
+ _parent.propageEventUp(spatial, dirtyType, dirty);
+ }
+ }
+
+ /**
+ * Gets the local rotation matrix.
+ *
+ * @return the rotation
+ */
+ public ReadOnlyMatrix3 getRotation() {
+ return _localTransform.getMatrix();
+ }
+
+ /**
+ * Gets the local scale vector.
+ *
+ * @return the scale
+ */
+ public ReadOnlyVector3 getScale() {
+ return _localTransform.getScale();
+ }
+
+ /**
+ * Gets the local translation vector.
+ *
+ * @return the translation
+ */
+ public ReadOnlyVector3 getTranslation() {
+ return _localTransform.getTranslation();
+ }
+
+ /**
+ * Gets the local transform.
+ *
+ * @return the transform
+ */
+ public ReadOnlyTransform getTransform() {
+ return _localTransform;
+ }
+
+ /**
+ * Sets the local transform.
+ *
+ * @param transform
+ * the new transform
+ */
+ public void setTransform(final ReadOnlyTransform transform) {
+ _localTransform.set(transform);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * Sets the world rotation matrix.
+ *
+ * @param rotation
+ * the new world rotation
+ */
+ public void setWorldRotation(final ReadOnlyMatrix3 rotation) {
+ _worldTransform.setRotation(rotation);
+ }
+
+ /**
+ * Sets the world rotation quaternion.
+ *
+ * @param rotation
+ * the new world rotation
+ */
+ public void setWorldRotation(final ReadOnlyQuaternion rotation) {
+ _worldTransform.setRotation(rotation);
+ }
+
+ /**
+ * Sets the world scale.
+ *
+ * @param scale
+ * the new world scale vector
+ */
+ public void setWorldScale(final ReadOnlyVector3 scale) {
+ _worldTransform.setScale(scale);
+ }
+
+ /**
+ * Sets the world scale.
+ *
+ * @param x
+ * the x coordinate
+ * @param y
+ * the y coordinate
+ * @param z
+ * the z coordinate
+ */
+ public void setWorldScale(final double x, final double y, final double z) {
+ _worldTransform.setScale(x, y, z);
+ }
+
+ /**
+ * Sets the world scale.
+ *
+ * @param scale
+ * the new world scale
+ */
+ public void setWorldScale(final double scale) {
+ _worldTransform.setScale(scale);
+ }
+
+ /**
+ * Sets the world translation vector.
+ *
+ * @param translation
+ * the new world translation
+ */
+ public void setWorldTranslation(final ReadOnlyVector3 translation) {
+ _worldTransform.setTranslation(translation);
+ }
+
+ /**
+ * Sets the world translation.
+ *
+ * @param x
+ * the x coordinate
+ * @param y
+ * the y coordinate
+ * @param z
+ * the z coordinate
+ */
+ public void setWorldTranslation(final double x, final double y, final double z) {
+ _worldTransform.setTranslation(x, y, z);
+ }
+
+ /**
+ * Sets the world transform.
+ *
+ * @param transform
+ * the new world transform
+ */
+ public void setWorldTransform(final ReadOnlyTransform transform) {
+ _worldTransform.set(transform);
+ }
+
+ /**
+ * Sets the rotation of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param rotation
+ * the new rotation of this spatial
+ * @see Transform#setRotation(Matrix3)
+ */
+ public void setRotation(final ReadOnlyMatrix3 rotation) {
+ _localTransform.setRotation(rotation);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * Sets the rotation of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param rotation
+ * the new rotation of this spatial
+ * @see Transform#setRotation(Quaternion)
+ */
+ public void setRotation(final ReadOnlyQuaternion rotation) {
+ _localTransform.setRotation(rotation);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * <code>setScale</code> sets the scale of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param scale
+ * the new scale of this spatial
+ */
+ public void setScale(final ReadOnlyVector3 scale) {
+ _localTransform.setScale(scale);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * <code>setScale</code> sets the scale of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param scale
+ * the new scale of this spatial
+ */
+ public void setScale(final double scale) {
+ _localTransform.setScale(scale);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * sets the scale of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param x
+ * the x scale factor
+ * @param y
+ * the y scale factor
+ * @param z
+ * the z scale factor
+ */
+ public void setScale(final double x, final double y, final double z) {
+ _localTransform.setScale(x, y, z);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * <code>setTranslation</code> sets the translation of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param translation
+ * the new translation of this spatial
+ */
+ public void setTranslation(final ReadOnlyVector3 translation) {
+ _localTransform.setTranslation(translation);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * sets the translation of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param x
+ * the x coordinate
+ * @param y
+ * the y coordinate
+ * @param z
+ * the z coordinate
+ */
+ public void setTranslation(final double x, final double y, final double z) {
+ _localTransform.setTranslation(x, y, z);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * <code>addTranslation</code> adds the given translation to the translation of this spatial. This marks the spatial
+ * as DirtyType.Transform.
+ *
+ * @param translation
+ * the translation vector
+ */
+ public void addTranslation(final ReadOnlyVector3 translation) {
+ addTranslation(translation.getX(), translation.getY(), translation.getZ());
+ }
+
+ /**
+ * adds to the current translation of this spatial. This marks the spatial as DirtyType.Transform.
+ *
+ * @param x
+ * the x amount
+ * @param y
+ * the y amount
+ * @param z
+ * the z amount
+ */
+ public void addTranslation(final double x, final double y, final double z) {
+ _localTransform.translate(x, y, z);
+ markDirty(DirtyType.Transform);
+ }
+
+ /**
+ * Gets the world rotation matrix.
+ *
+ * @return the world rotation
+ */
+ public ReadOnlyMatrix3 getWorldRotation() {
+ return _worldTransform.getMatrix();
+ }
+
+ /**
+ * Gets the world scale vector.
+ *
+ * @return the world scale
+ */
+ public ReadOnlyVector3 getWorldScale() {
+ return _worldTransform.getScale();
+ }
+
+ /**
+ * Gets the world translation vector.
+ *
+ * @return the world translation
+ */
+ public ReadOnlyVector3 getWorldTranslation() {
+ return _worldTransform.getTranslation();
+ }
+
+ /**
+ * Gets the world transform.
+ *
+ * @return the world transform
+ */
+ public ReadOnlyTransform getWorldTransform() {
+ return _worldTransform;
+ }
+
+ /**
+ * <code>getWorldBound</code> retrieves the world bound at this level.
+ *
+ * @return the world bound at this level.
+ */
+ public BoundingVolume getWorldBound() {
+ return _worldBound;
+ }
+
+ /**
+ * <code>onDraw</code> checks the spatial with the camera to see if it should be culled, if not, the node's draw
+ * method is called.
+ * <p>
+ * This method is called by the renderer. Usually it should not be called directly.
+ *
+ * @param r
+ * the renderer used for display.
+ */
+ public void onDraw(final Renderer r) {
+ final CullHint cm = _sceneHints.getCullHint();
+ if (cm == CullHint.Always) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
+ return;
+ } else if (cm == CullHint.Never) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+ draw(r);
+ return;
+ }
+
+ final Camera camera = Camera.getCurrentCamera();
+ final int state = camera.getPlaneState();
+
+ // check to see if we can cull this node
+ _frustumIntersects = ((_parent != null && _parent.getWorldBound() != null) ? _parent._frustumIntersects
+ : Camera.FrustumIntersect.Intersects);
+
+ if (cm == CullHint.Dynamic && _frustumIntersects == Camera.FrustumIntersect.Intersects) {
+ _frustumIntersects = camera.contains(_worldBound);
+ }
+
+ if (_frustumIntersects != Camera.FrustumIntersect.Outside) {
+ draw(r);
+ }
+ camera.setPlaneState(state);
+ }
+
+ /**
+ * <code>draw</code> abstract method that handles drawing data to the renderer if it is geometry and passing the
+ * call to it's children if it is a node.
+ *
+ * @param renderer
+ * the renderer used for display.
+ */
+ public abstract void draw(final Renderer renderer);
+
+ /**
+ * Grab the render delegate for this spatial based on the currently set RenderContext.
+ *
+ * @return the delegate or null if a delegate was not found.
+ */
+ protected RenderDelegate getCurrentRenderDelegate() {
+ // short circuit... ignore if no delegates at all.
+ if (_delegateMap == null || _delegateMap.isEmpty()) {
+ return null;
+ }
+
+ // otherwise... grab our current context
+ final RenderContext context = ContextManager.getCurrentContext();
+
+ // get the delegate for this context
+ RenderDelegate delegate = getRenderDelegate(context.getGlContextRep());
+ // if none, check for a default delegate.
+ if (delegate == null) {
+ delegate = getRenderDelegate(null);
+ }
+
+ return delegate;
+ }
+
+ /**
+ * Update geometric state.
+ *
+ * @param time
+ * The time in seconds between the last two consecutive frames (time per frame). See
+ * {@link ReadOnlyTimer#getTimePerFrame()}
+ * @see #updateGeometricState(double, boolean)
+ */
+ public void updateGeometricState(final double time) {
+ updateGeometricState(time, true);
+ }
+
+ /**
+ * <code>updateGeometricState</code> updates all the geometry information for the node.
+ *
+ * @param time
+ * The time in seconds between the last two consecutive frames (time per frame). See
+ * {@link ReadOnlyTimer#getTimePerFrame()}
+ * @param initiator
+ * true if this node started the update process.
+ */
+ public void updateGeometricState(final double time, final boolean initiator) {
+ updateControllers(time);
+
+ if (_dirtyMark.isEmpty()) {
+ updateChildren(time);
+ } else {
+ if (isDirty(DirtyType.Transform)) {
+ updateWorldTransform(false);
+ }
+
+ if (isDirty(DirtyType.RenderState)) {
+ updateWorldRenderStates(false);
+ clearDirty(DirtyType.RenderState);
+ }
+
+ updateChildren(time);
+
+ if (isDirty(DirtyType.Bounding)) {
+ updateWorldBound(false);
+ if (initiator) {
+ propagateBoundToRoot();
+ }
+ }
+ }
+ }
+
+ /**
+ * Override to allow objects like Node to update their children.
+ *
+ * @param time
+ * The time in seconds between the last two consecutive frames (time per frame). See
+ * {@link ReadOnlyTimer#getTimePerFrame()}
+ */
+ protected void updateChildren(final double time) {}
+
+ /**
+ * Update all controllers set on this spatial.
+ *
+ * @param time
+ * The time in seconds between the last two consecutive frames (time per frame). See
+ * {@link ReadOnlyTimer#getTimePerFrame()}
+ */
+ @SuppressWarnings("unchecked")
+ public void updateControllers(final double time) {
+ if (_controllers != null) {
+ for (int i = 0, gSize = _controllers.size(); i < gSize; i++) {
+ try {
+ final SpatialController<Spatial> controller = (SpatialController<Spatial>) _controllers.get(i);
+ if (controller != null) {
+ controller.update(time, this);
+ }
+ } catch (final IndexOutOfBoundsException e) {
+ // a controller was removed in SpatialController.update (note: this
+ // may skip one controller)
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates the worldTransform.
+ *
+ * @param recurse
+ * usually false when updating the tree. Set to true when you just want to update the world transforms
+ * for a branch without updating geometric state.
+ */
+ public void updateWorldTransform(final boolean recurse) {
+ if (_parent != null) {
+ _parent._worldTransform.multiply(_localTransform, _worldTransform);
+ } else {
+ _worldTransform.set(_localTransform);
+ }
+ clearDirty(DirtyType.Transform);
+ }
+
+ /**
+ * Convert a vector (in) from this spatial's local coordinate space to world coordinate space.
+ *
+ * @param in
+ * vector to read from
+ * @param store
+ * where to write the result (null to create a new vector, may be same as in)
+ * @return the result (store)
+ */
+ public Vector3 localToWorld(final ReadOnlyVector3 in, Vector3 store) {
+ if (store == null) {
+ store = new Vector3();
+ }
+
+ return _worldTransform.applyForward(in, store);
+ }
+
+ /**
+ * Convert a vector (in) from world coordinate space to this spatial's local coordinate space.
+ *
+ * @param in
+ * vector to read from
+ * @param store
+ * where to write the result (null to create a new vector, may be same as in)
+ * @return the result (store)
+ */
+ public Vector3 worldToLocal(final ReadOnlyVector3 in, Vector3 store) {
+ if (store == null) {
+ store = new Vector3();
+ }
+
+ return _worldTransform.applyInverse(in, store);
+ }
+
+ /**
+ * Updates the render state values of this Spatial and and children it has. Should be called whenever render states
+ * change.
+ *
+ * @param recurse
+ * true to recurse down the scenegraph tree
+ */
+ public void updateWorldRenderStates(final boolean recurse) {
+ updateWorldRenderStates(recurse, null);
+ }
+
+ /**
+ * Called internally. Updates the render states of this Spatial. The stack contains parent render states.
+ *
+ * @param recurse
+ * true to recurse down the scenegraph tree
+ * @param stateStack
+ * The parent render states, or null if we are starting at this point in the scenegraph.
+ */
+ protected void updateWorldRenderStates(final boolean recurse, final RenderState.StateStack stateStack) {
+ if (stateStack == null) {
+ // grab all states from root to here.
+ final RenderState.StateStack stack = RenderState.StateStack.fetchTempInstance();
+ propagateStatesFromRoot(stack);
+
+ applyWorldRenderStates(recurse, stack);
+
+ RenderState.StateStack.releaseTempInstance(stack);
+ } else {
+ for (final RenderState state : _renderStateList.values()) {
+ stateStack.push(state);
+ }
+
+ applyWorldRenderStates(recurse, stateStack);
+
+ for (final RenderState state : _renderStateList.values()) {
+ stateStack.pop(state);
+ }
+ }
+ }
+
+ /**
+ * The method actually implements how the render states are applied to this spatial and (if recurse is true) any
+ * children it may have. By default, this function does nothing.
+ *
+ * @param recurse
+ * true to recurse down the scenegraph tree
+ * @param stack
+ * The stack for each state
+ */
+ protected void applyWorldRenderStates(final boolean recurse, final RenderState.StateStack stack) {}
+
+ /**
+ * Sort the ligts on this spatial.
+ */
+ public void sortLights() {}
+
+ /**
+ * Retrieves the complete renderstate list.
+ *
+ * @return the list of renderstates
+ */
+ public EnumMap<StateType, RenderState> getLocalRenderStates() {
+ return _renderStateList;
+ }
+
+ /**
+ * <code>setRenderState</code> sets a render state for this node. Note, there can only be one render state per type
+ * per node. That is, there can only be a single BlendState a single TextureState, etc. If there is already a render
+ * state for a type set the old render state will be returned. Otherwise, null is returned.
+ *
+ * @param rs
+ * the render state to add.
+ * @return the old render state.
+ */
+ public RenderState setRenderState(final RenderState rs) {
+ if (rs == null) {
+ return null;
+ }
+
+ final RenderState.StateType type = rs.getType();
+ final RenderState oldState = _renderStateList.get(type);
+ _renderStateList.put(type, rs);
+
+ markDirty(DirtyType.RenderState);
+
+ return oldState;
+ }
+
+ /**
+ * Returns the requested RenderState that this Spatial currently has set or null if none is set.
+ *
+ * @param type
+ * the state type to retrieve
+ * @return a render state at the given position or null
+ */
+ public RenderState getLocalRenderState(final RenderState.StateType type) {
+ return _renderStateList.get(type);
+ }
+
+ /**
+ * Clears a given render state index by setting it to null.
+ *
+ * @param type
+ * The type of RenderState to clear
+ */
+ public void clearRenderState(final RenderState.StateType type) {
+ _renderStateList.remove(type);
+ markDirty(DirtyType.RenderState);
+ }
+
+ /**
+ * Called during updateRenderState(Stack[]), this function goes up the scene graph tree until the parent is null and
+ * pushes RenderStates onto the states Stack array.
+ *
+ * @param stack
+ * The stack to push any parent states onto.
+ */
+ public void propagateStatesFromRoot(final StateStack stack) {
+ // traverse to root to allow downward state propagation
+ if (_parent != null) {
+ _parent.propagateStatesFromRoot(stack);
+ }
+
+ // push states onto current render state stack
+ for (final RenderState state : _renderStateList.values()) {
+ stack.push(state);
+ }
+ }
+
+ /**
+ * updates the bounding volume of the world. Abstract, geometry transforms the bound while node merges the
+ * children's bound. In most cases, users will want to call updateModelBound() and let this function be called
+ * automatically during updateGeometricState().
+ *
+ * @param recurse
+ * true to recurse down the scenegraph tree
+ */
+ public abstract void updateWorldBound(boolean recurse);
+
+ /**
+ * passes the new world bound up the tree to the root.
+ */
+ public void propagateBoundToRoot() {
+ if (_parent != null) {
+ _parent.updateWorldBound(false);
+ _parent.propagateBoundToRoot();
+ }
+ }
+
+ /**
+ * Gets the Spatial specific user data.
+ *
+ * @return the user data
+ */
+ public Object getUserData() {
+ return _userData;
+ }
+
+ /**
+ * Sets the Spatial specific user data.
+ *
+ * @param userData
+ * Some Spatial specific user data. Note: If this object is not explicitly of type Savable, it will be
+ * ignored during read/write.
+ */
+ public void setUserData(final Object userData) {
+ _userData = userData;
+ }
+
+ /**
+ * Adds a SpatialController to this Spatial's list of controllers.
+ *
+ * @param controller
+ * The SpatialController to add
+ * @see com.ardor3d.scenegraph.controller.SpatialController
+ */
+ public void addController(final SpatialController<?> controller) {
+ if (_controllers == null) {
+ _controllers = new ArrayList<SpatialController<?>>(1);
+ }
+ _controllers.add(controller);
+ }
+
+ /**
+ * Removes a SpatialController from this Spatial's list of controllers, if it exist.
+ *
+ * @param controller
+ * The SpatialController to remove
+ * @return True if the SpatialController was in the list to remove.
+ * @see com.ardor3d.scenegraph.controller.SpatialController
+ */
+ public boolean removeController(final SpatialController<?> controller) {
+ if (_controllers == null) {
+ return false;
+ }
+ return _controllers.remove(controller);
+ }
+
+ /**
+ * Removes a SpatialController from this Spatial's list of controllers by index.
+ *
+ * @param index
+ * The index of the controller to remove
+ * @return The SpatialController removed or null if nothing was removed.
+ * @see com.ardor3d.scenegraph.controller.SpatialController
+ */
+ public SpatialController<?> removeController(final int index) {
+ if (_controllers == null) {
+ return null;
+ }
+ return _controllers.remove(index);
+ }
+
+ /**
+ * Removes all Controllers from this Spatial's list of controllers.
+ *
+ * @see com.ardor3d.scenegraph.controller.SpatialController
+ */
+ public void clearControllers() {
+ if (_controllers != null) {
+ _controllers.clear();
+ }
+ }
+
+ /**
+ * Returns the controller in this list of controllers at index i.
+ *
+ * @param i
+ * The index to get a controller from.
+ * @return The controller at index i.
+ * @see com.ardor3d.scenegraph.controller.SpatialController
+ */
+ public SpatialController<?> getController(final int i) {
+ if (_controllers == null) {
+ _controllers = new ArrayList<SpatialController<?>>(1);
+ }
+ return _controllers.get(i);
+ }
+
+ /**
+ * Returns the ArrayList that contains this spatial's SpatialControllers.
+ *
+ * @return This spatial's _controllers.
+ */
+ public List<SpatialController<?>> getControllers() {
+ if (_controllers == null) {
+ _controllers = new ArrayList<SpatialController<?>>(1);
+ }
+ return _controllers;
+ }
+
+ /**
+ * Gets the controller count.
+ *
+ * @return the number of controllers set on this Spatial.
+ */
+ public int getControllerCount() {
+ if (_controllers == null) {
+ return 0;
+ }
+ return _controllers.size();
+ }
+
+ /**
+ * Returns this spatial's last frustum intersection result. This int is set when a check is made to determine if the
+ * bounds of the object fall inside a camera's frustum. If a parent is found to fall outside the frustum, the value
+ * for this spatial will not be updated.
+ *
+ * @return The spatial's last frustum intersection result.
+ */
+ public Camera.FrustumIntersect getLocalLastFrustumIntersection() {
+ return _frustumIntersects;
+ }
+
+ /**
+ * Tries to find the most accurate last frustum intersection for this spatial by checking the parent for possible
+ * Outside value.
+ *
+ * @return Outside, if this, or any ancestor was Outside, otherwise the local intersect value.
+ */
+ public Camera.FrustumIntersect getLastFrustumIntersection() {
+ if (_parent != null && _frustumIntersects != Camera.FrustumIntersect.Outside) {
+ final Camera.FrustumIntersect parentIntersect = _parent.getLastFrustumIntersection();
+ if (parentIntersect == Camera.FrustumIntersect.Outside) {
+ return Camera.FrustumIntersect.Outside;
+ }
+ }
+ return _frustumIntersects;
+ }
+
+ /**
+ * Overrides the last intersection result. This is useful for operations that want to start rendering at the middle
+ * of a scene tree and don't want the parent of that node to influence culling. (See texture renderer code for
+ * example.)
+ *
+ * @param frustumIntersects
+ * the new frustum intersection value
+ */
+ public void setLastFrustumIntersection(final Camera.FrustumIntersect frustumIntersects) {
+ _frustumIntersects = frustumIntersects;
+ }
+
+ /**
+ * Execute the given Visitor on this Spatial, and any Spatials managed by this Spatial as appropriate.
+ *
+ * @param visitor
+ * the Visitor object to use.
+ * @param preexecute
+ * if true, we will visit <i>this</i> Spatial before any Spatials we manage (such as children of a Node.)
+ * If false, we will visit them first, then ourselves.
+ */
+ public void acceptVisitor(final Visitor visitor, final boolean preexecute) {
+ visitor.visit(this);
+ }
+
+ /**
+ * Returns the Spatial's name followed by the class of the spatial <br>
+ * Example: "MyNode (com.ardor3d.scene.Spatial)
+ *
+ * @return Spatial's name followed by the class of the Spatial
+ */
+ @Override
+ public String toString() {
+ return _name + " (" + this.getClass().getName() + ')';
+ }
+
+ /**
+ * Create a copy of this spatial.
+ *
+ * @param shareGeometricData
+ * if true, reuse any data fields describing the geometric shape of the spatial, as applicable.
+ * @return the copy as described.
+ */
+ public Spatial makeCopy(final boolean shareGeometricData) {
+ final Spatial spat = duplicate();
+
+ // copy basic spatial info
+ spat.setName(getName());
+ spat.getSceneHints().set(_sceneHints);
+ spat.setTransform(_localTransform);
+
+ // copy local render states
+ for (final StateType type : _renderStateList.keySet()) {
+ final RenderState state = _renderStateList.get(type);
+ if (state != null) {
+ spat.setRenderState(state);
+ }
+ }
+
+ // copy controllers
+ if (_controllers != null) {
+ for (final SpatialController<?> sc : _controllers) {
+ spat.addController(sc);
+ }
+ }
+
+ return spat;
+ }
+
+ private Spatial duplicate() {
+ Spatial spat = null;
+ final Class<? extends Spatial> clazz = getClass();
+ try {
+ final SavableFactory ann = clazz.getAnnotation(SavableFactory.class);
+ if (ann == null) {
+ spat = clazz.newInstance();
+ } else {
+ spat = (Spatial) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null, (Object[]) null);
+ }
+ } catch (final InstantiationException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final NoSuchMethodException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalArgumentException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final SecurityException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final InvocationTargetException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class " + clazz.getCanonicalName(), e);
+ throw new RuntimeException(e);
+ }
+ return spat;
+ }
+
+ /**
+ * Creates and returns a new instance of this spatial. Used for instanced rendering. All instances visible on the
+ * screen will be drawn in one draw call. The new instance will share all data (meshData and renderStates) with the
+ * current mesh and all other instances created from this spatial.
+ *
+ * @return an instanced copy of this node
+ */
+ public Spatial makeInstanced() {
+
+ final Spatial spat = duplicate();
+
+ // copy basic spatial info
+ spat.setName(getName());
+ spat._sceneHints = _sceneHints;
+ spat.setTransform(_localTransform);
+
+ // copy local render states
+ spat._renderStateList = _renderStateList;
+
+ // copy controllers
+ if (_controllers != null) {
+ for (final SpatialController<?> sc : _controllers) {
+ spat.addController(sc);
+ }
+ }
+
+ return spat;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ /**
+ * @see Savable#getClassTag()
+ */
+ public Class<? extends Spatial> getClassTag() {
+ return this.getClass();
+ }
+
+ /**
+ * @param capsule
+ * the input capsule
+ * @throws IOException
+ * Signals that an I/O exception has occurred.
+ * @see Savable#read(InputCapsule)
+ */
+ public void read(final InputCapsule capsule) throws IOException {
+ _name = capsule.readString("name", null);
+
+ final RenderState[] states = CapsuleUtils.asArray(capsule.readSavableArray("renderStateList", null),
+ RenderState.class);
+ _renderStateList.clear();
+ if (states != null) {
+ for (final RenderState state : states) {
+ _renderStateList.put(state.getType(), state);
+ }
+ }
+
+ _localTransform.set((Transform) capsule.readSavable("localTransform", new Transform(Transform.IDENTITY)));
+ _worldTransform.set((Transform) capsule.readSavable("worldTransform", new Transform(Transform.IDENTITY)));
+
+ final Savable userData = capsule.readSavable("userData", null);
+ // only override set userdata if we have something in the capsule.
+ if (userData != null) {
+ _userData = userData;
+ }
+
+ final List<Savable> list = capsule.readSavableList("controllers", null);
+ if (list != null) {
+ for (final Savable s : list) {
+ if (s instanceof SpatialController<?>) {
+ addController((SpatialController<?>) s);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param capsule
+ * the capsule
+ * @throws IOException
+ * Signals that an I/O exception has occurred.
+ * @see Savable#write(OutputCapsule)
+ */
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_name, "name", null);
+
+ capsule.write(_renderStateList.values().toArray(new RenderState[0]), "renderStateList", null);
+
+ capsule.write(_localTransform, "localTransform", new Transform(Transform.IDENTITY));
+ capsule.write(_worldTransform, "worldTransform", new Transform(Transform.IDENTITY));
+
+ if (_userData instanceof Savable) {
+ capsule.write((Savable) _userData, "userData", null);
+ }
+
+ if (_controllers != null) {
+ final List<Savable> list = new ArrayList<Savable>();
+ for (final SpatialController<?> sc : _controllers) {
+ if (sc instanceof Savable) {
+ list.add((Savable) sc);
+ }
+ }
+ capsule.writeSavableList(list, "controllers", null);
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java
new file mode 100644
index 0000000..909a004
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/ComplexSpatialController.java
@@ -0,0 +1,232 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.HashMap;
+
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * <code>ComplexSpatialController</code> provides a base class for creation of controllers to modify nodes and render
+ * states over time. The base controller provides a repeat type, min and max time, as well as speed. Subclasses of this
+ * will provide the update method that takes the time between the last call and the current one and modifies an object
+ * in a application specific way.
+ */
+public abstract class ComplexSpatialController<T extends Spatial> implements SpatialController<T>, Serializable,
+ Savable {
+
+ private static final long serialVersionUID = 1;
+
+ public enum RepeatType {
+ /**
+ * A clamped repeat type signals that the controller should look like its final state when it's done <br>
+ * Example: 0 1 5 8 9 10 10 10 10 10 10 10 10 10 10 10...
+ */
+ CLAMP,
+
+ /**
+ * A wrapped repeat type signals that the controller should start back at the begining when it's final state is
+ * reached <br>
+ * Example: 0 1 5 8 9 10 0 1 5 8 9 10 0 1 5 ....
+ */
+ WRAP,
+
+ /**
+ * A cycled repeat type signals that the controller should cycle it's states forwards and backwards <br>
+ * Example: 0 1 5 8 9 10 9 8 5 1 0 1 5 8 9 10 9 ....
+ */
+ CYCLE;
+ }
+
+ /**
+ * Defines how this controller should repeat itself. Default is {@link RepeatType#CLAMP}.
+ */
+ private RepeatType _repeatType = RepeatType.CLAMP;
+
+ /**
+ * The controller's minimum cycle time
+ */
+ private double _minTime;
+
+ /**
+ * The controller's maximum cycle time
+ */
+ private double _maxTime;
+
+ /**
+ * The 'speed' of this Controller. Generically speaking, less than 1 is slower, more than 1 is faster, and 1
+ * represents the base speed
+ */
+ private double _speed = 1;
+
+ /**
+ * True if this controller is active, false otherwise
+ */
+ private boolean _active = true;
+
+ /**
+ * @return The speed of this controller. Speed is 1 by default.
+ */
+ public double getSpeed() {
+ return _speed;
+ }
+
+ /**
+ * Sets the speed of this controller
+ *
+ * @param speed
+ * The new speed
+ */
+ public void setSpeed(final double speed) {
+ _speed = speed;
+ }
+
+ /**
+ * Returns the current maximum time for this controller.
+ *
+ * @return This controller's maximum time.
+ */
+ public double getMaxTime() {
+ return _maxTime;
+ }
+
+ /**
+ * Sets the maximum time for this controller
+ *
+ * @param maxTime
+ * The new maximum time
+ */
+ public void setMaxTime(final double maxTime) {
+ _maxTime = maxTime;
+ }
+
+ /**
+ * Returns the current minimum time of this controller
+ *
+ * @return This controller's minimum time
+ */
+ public double getMinTime() {
+ return _minTime;
+ }
+
+ /**
+ * Sets the minimum time of this controller
+ *
+ * @param minTime
+ * The new minimum time.
+ */
+ public void setMinTime(final double minTime) {
+ _minTime = minTime;
+ }
+
+ /**
+ * Returns the current repeat type of this controller.
+ *
+ * @return The current repeat type
+ */
+ public RepeatType getRepeatType() {
+ return _repeatType;
+ }
+
+ /**
+ * Sets the repeat type of this controller. The default is {@link RepeatType#CLAMP}.
+ *
+ * @param repeatType
+ * The new repeat type, can not be <code>null</code>.
+ */
+ public void setRepeatType(final RepeatType repeatType) {
+ if (null == repeatType) {
+ throw new IllegalArgumentException("repeatType can not be null!");
+ }
+
+ _repeatType = repeatType;
+ }
+
+ /**
+ * Sets the active flag of this controller. Note: updates on controllers are still called even if this flag is set
+ * to false. It is the responsibility of the extending class to check isActive if it wishes to be turn-off-able.
+ *
+ * @param active
+ * The new active state.
+ */
+ public void setActive(final boolean active) {
+ _active = active;
+ }
+
+ /**
+ * Returns if this Controller is active or not.
+ *
+ * @return True if this controller is set to active, false if not.
+ */
+ public boolean isActive() {
+ return _active;
+ }
+
+ /**
+ * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#CLAMP clamp},
+ * <code>false</code> otherwise.
+ */
+ public boolean isRepeatTypeClamp() {
+ return RepeatType.CLAMP.equals(getRepeatType());
+ }
+
+ /**
+ * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#WRAP wrap},
+ * <code>false</code> otherwise.
+ */
+ public boolean isRepeatTypeWrap() {
+ return RepeatType.WRAP.equals(getRepeatType());
+ }
+
+ /**
+ * @return <code>true</code> if the {@link #getRepeatType() repeat type} is {@link RepeatType#CYCLE cycle},
+ * <code>false</code> otherwise.
+ */
+ public boolean isRepeatTypeCycle() {
+ return RepeatType.CYCLE.equals(getRepeatType());
+ }
+
+ public abstract void update(double time, T caller);
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_repeatType, "repeatType", RepeatType.CLAMP);
+ capsule.write(_minTime, "minTime", 0);
+ capsule.write(_maxTime, "maxTime", 0);
+ capsule.write(_speed, "speed", 1);
+ capsule.write(_active, "active", true);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _repeatType = capsule.readEnum("repeatType", RepeatType.class, RepeatType.CLAMP);
+ _minTime = capsule.readDouble("minTime", 0);
+ _maxTime = capsule.readDouble("maxTime", 0);
+ _speed = capsule.readDouble("speed", 1);
+ _active = capsule.readBoolean("active", true);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Class<? extends ComplexSpatialController> getClassTag() {
+ return this.getClass();
+ }
+
+ public void getControllerValues(final HashMap<String, Object> store) {
+
+ }
+
+ public void setControllerValues(final HashMap<String, Object> values) {
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java
new file mode 100644
index 0000000..7a3881e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/SpatialController.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller;
+
+import com.ardor3d.scenegraph.Spatial;
+
+public interface SpatialController<T extends Spatial> {
+
+ /**
+ * @param time
+ * The time in seconds between the last call to update and the current one
+ * @param caller
+ * The spatial currently executing this controller.
+ */
+ public void update(double time, T caller);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java
new file mode 100644
index 0000000..cd04c05
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveInterpolationController.java
@@ -0,0 +1,286 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import java.util.logging.Logger;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController;
+import com.ardor3d.spline.ArcLengthTable;
+import com.ardor3d.spline.Curve;
+import com.ardor3d.spline.Spline;
+
+/**
+ * CurveInterpolationController class interpolates a {@link Spatial}s vectors using a {@link Curve}.
+ * <p>
+ * This class is stateful and can not be used by more than one controller at a time.
+ * </p>
+ */
+public class CurveInterpolationController extends Vector3InterpolationController {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** Classes logger */
+ private static final Logger LOGGER = Logger.getLogger(CurveInterpolationController.class.getName());
+
+ /** @see #setCurve(Curve) */
+ private Curve _curve;
+
+ /** Look up table of arc lengths (used for constant speed) */
+ private ArcLengthTable _arcLengths;
+
+ /** Look up table of arc lengths (used for constant speed when travelling in reverse (repeat type = cycle)) */
+ private ArcLengthTable _arcLengthsReverse;
+
+ /** Distance travelled between control points (used for constant speed) */
+ private double _distance = 0.0;
+
+ /*
+ * Overrides to handle constant speed updating
+ */
+ @Override
+ protected double incrementDelta(final double by) {
+ double delta;
+
+ /*
+ * If constant speed we need to also check we aren't clamped at max index before we call lookup in the arc
+ * length table because there would be no point
+ */
+ if (isConstantSpeed()) {
+ _distance += by;
+
+ if (isCycleForward()) {
+ assert (null != _arcLengths) : "You need to call generateArcLengths(x, false) to create the required arc length table!";
+
+ delta = _arcLengths.getDelta(getIndex(), _distance);
+ } else {
+ assert (null != _arcLengthsReverse) : "You need to call generateArcLengths(x, true) to create the required reverse arc length table!";
+
+ delta = _arcLengthsReverse.getDelta(getIndex(), _distance);
+ }
+
+ setDelta(delta);
+ } else {
+ delta = super.incrementDelta(by);
+ }
+
+ return delta;
+ }
+
+ /*
+ * Overrides to handle updating the travelled distance correctly (used during constant speed mode)
+ */
+ @Override
+ protected int decrementIndex() {
+ assert (null != _arcLengthsReverse) : "You need to call generateArcLengths() to create the required arc length tables!";
+
+ _distance -= _arcLengthsReverse.getLength(getIndex());
+
+ return super.decrementIndex();
+ }
+
+ /*
+ * Overrides to handle updating the travelled distance correctly (used during constant speed mode)
+ */
+ @Override
+ protected int incrementIndex() {
+ assert (null != _arcLengths) : "You need to call generateArcLengths() to create the required arc length tables!";
+
+ _distance -= _arcLengths.getLength(getIndex());
+
+ return super.incrementIndex();
+ }
+
+ @Override
+ protected Vector3 interpolateVectors(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta,
+ final Vector3 target) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+
+ final ReadOnlyVector3 p0 = getControlPointStart();
+ final ReadOnlyVector3 p3 = getCotnrolPointEnd();
+
+ final Spline spline = getCurve().getSpline();
+
+ return spline.interpolate(p0, from, to, p3, delta, target);
+ }
+
+ /**
+ * @return The initial control point, will not be <code>null</code>.
+ */
+ protected ReadOnlyVector3 getControlPointStart() {
+ ReadOnlyVector3 control = null;
+
+ final int fromIndex = getIndex();
+
+ switch (getRepeatType()) {
+ case CLAMP:
+ control = getControls().get(fromIndex - 1);
+ break;
+
+ case CYCLE:
+ if (isCycleForward()) {
+ control = getControls().get(fromIndex - 1);
+ } else {
+ control = getControls().get(fromIndex + 1);
+ }
+ break;
+
+ case WRAP:
+ control = getControls().get(fromIndex - 1);
+ break;
+ }
+
+ return control;
+ }
+
+ /**
+ * @return The final control point, will not be <code>null</code>.
+ */
+ protected ReadOnlyVector3 getCotnrolPointEnd() {
+ ReadOnlyVector3 control = null;
+
+ final int toIndex = getIndex();
+
+ switch (getRepeatType()) {
+ case CLAMP:
+ control = getControls().get(toIndex + 2);
+ break;
+
+ case CYCLE:
+ if (isCycleForward()) {
+ control = getControls().get(toIndex + 2);
+ } else {
+ control = getControls().get(toIndex - 2);
+ }
+ break;
+
+ case WRAP:
+ control = getControls().get(toIndex + 2);
+ break;
+ }
+
+ return control;
+ }
+
+ /**
+ * Setting a new curve will automatically update the control points.
+ *
+ * @param curve
+ * The new curve to follow, can not be <code>null</code>.
+ * @see #getCurve()
+ */
+ public void setCurve(final Curve curve) {
+ if (null == curve) {
+ throw new IllegalArgumentException("curve can not be null!");
+ }
+
+ _curve = curve;
+
+ setControls(_curve.getControlPoints());
+
+ if (isConstantSpeed()) {
+ LOGGER
+ .warning("Constant speed is set to true, you will need to call generateArcLengths() to avoid errors during update.");
+ }
+ }
+
+ /**
+ * @return The curve being followed, will not <code>null</code>.
+ * @see #setCurve(Curve)
+ */
+ public Curve getCurve() {
+ assert (null != _curve) : "curve was null, it must be set before use!";
+
+ return _curve;
+ }
+
+ /*
+ * Overrides to provide a warning about generating arc lengths if constant speed is set to true and they haven't
+ * been generated yet.
+ */
+ @Override
+ public void setConstantSpeed(final boolean constantSpeed) {
+ super.setConstantSpeed(constantSpeed);
+
+ if (isConstantSpeed() && null == _arcLengths) {
+ LOGGER
+ .warning("Constant speed was set to true, you will need to call generateArcLengths() to avoid errors during update.");
+ }
+ }
+
+ /**
+ * Generates the arc lengths, generates the reverse table if the
+ * {@link #setRepeatType(com.ardor3d.scenegraph.controller.ComplexSpatialController.RepeatType) repeat type} is set
+ * to {@link ComplexSpatialController.RepeatType#CYCLE cycle}
+ *
+ * @param step
+ * 'See Also:' method for more info.
+ * @see #generateArcLengths(int, boolean)
+ */
+ public void generateArcLengths(final int step) {
+ generateArcLengths(step, RepeatType.CYCLE.equals(getRepeatType()));
+ }
+
+ /**
+ * Generates arc lengths which are required if you wish to have {@link #setConstantSpeed(boolean) constant speed}
+ * interpolation.
+ *
+ * @param step
+ * 'See Also:' method for more info.
+ * @param reverse
+ * <code>true</code> to also generate a reverse look up table. This is only required if you plan to use
+ * the {@link ComplexSpatialController.RepeatType#CYCLE} repeat type.
+ * @see ArcLengthTable#generate(int, boolean)
+ */
+ public void generateArcLengths(final int step, final boolean reverse) {
+ _arcLengths = new ArcLengthTable(getCurve());
+ _arcLengths.generate(step, false);
+
+ if (reverse) {
+ _arcLengthsReverse = new ArcLengthTable(getCurve());
+ _arcLengthsReverse.generate(step, true);
+ }
+ }
+
+ /**
+ * Since splines require at least 4 points to interpolate correctly the default maximum value is overridden to 1
+ * less than normal.
+ */
+ @Override
+ protected int getMaximumIndex() {
+ return super.getMaximumIndex() - 1;
+ }
+
+ /**
+ * Since splines require at least 4 points to interpolate correctly the default minimum value is overridden to 1
+ * more than normal.
+ */
+ @Override
+ protected int getMinimumIndex() {
+ return super.getMinimumIndex() + 1;
+ }
+
+ /*
+ * Overrides to also reset the distance.
+ */
+ @Override
+ public void reset() {
+ super.reset();
+
+ _distance = 0.0;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java
new file mode 100644
index 0000000..30b595b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/CurveLookAtController.java
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import java.io.Serializable;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.SpatialController;
+
+/**
+ * CurveLookAtController class rotates a spatial to 'look at' a curve.
+ * <p>
+ * This class assumes the given delegate curve interpolation controller is already added to a spatial and is getting
+ * automatically updated as part of the main loop. Therefore this class doesn't call update on the delegate controller.
+ * </p>
+ */
+public class CurveLookAtController implements SpatialController<Spatial>, Serializable {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** The world up vector to use in matrix look at */
+ private ReadOnlyVector3 _worldUp;
+
+ /** The curve interpolation controller that does the work of finding the correct position to look at */
+ private final CurveInterpolationController _curveController;
+
+ /** The previous location of the spatial on the curve */
+ private final Vector3 _previous;
+
+ /** @see #setLocalRotation(boolean) */
+ private boolean _localRotation = true;
+
+ /**
+ * Creates a new instance of <code>CurveLookAtController</code>, with {@link Vector3#UNIT_Y} as the world up vector.
+ *
+ * @param curveController
+ * The curve interpolation controller that does the work of finding the correct position to look at, can
+ * not be <code>null</code>.
+ */
+ public CurveLookAtController(final CurveInterpolationController curveController) {
+ this(curveController, Vector3.UNIT_Y);
+ }
+
+ /**
+ * Creates a new instance of <code>CurveLookAtController</code>.
+ *
+ * @param curveController
+ * The curve interpolation controller that does the work of finding the correct position to look at, can
+ * not be <code>null</code>.
+ * @param worldUp
+ * The world up vector, can not be <code>null</code>.
+ */
+ public CurveLookAtController(final CurveInterpolationController curveController, final ReadOnlyVector3 worldUp) {
+ super();
+
+ if (null == curveController) {
+ throw new IllegalArgumentException("curveController can not be null!");
+ }
+
+ _curveController = curveController;
+
+ _previous = new Vector3(_curveController.getControlFrom());
+
+ setWorldUp(worldUp);
+ }
+
+ @Override
+ public void update(final double time, final Spatial caller) {
+ if (null == caller) {
+ throw new IllegalArgumentException("caller can not be null!");
+ }
+
+ final Vector3 interpolated = Vector3.fetchTempInstance();
+ final Matrix3 rotation = Matrix3.fetchTempInstance();
+
+ _curveController.interpolateVectors(_curveController.getControlFrom(), _curveController.getControlTo(),
+ _curveController.getDelta(), interpolated);
+
+ MathUtils.matrixLookAt(_previous, interpolated, _worldUp, rotation);
+
+ if (isLocalRotation()) {
+ caller.setRotation(rotation);
+ } else {
+ caller.setWorldRotation(rotation);
+ }
+
+ _previous.set(interpolated);
+
+ Matrix3.releaseTempInstance(rotation);
+ Vector3.releaseTempInstance(interpolated);
+ }
+
+ /**
+ * @param worldUp
+ * The world up vector, can not be <code>null</code>.
+ */
+ public void setWorldUp(final ReadOnlyVector3 worldUp) {
+ if (null == worldUp) {
+ throw new IllegalArgumentException("worldUp can not be null!");
+ }
+
+ _worldUp = worldUp;
+ }
+
+ /**
+ * @param localRotation
+ * <code>true</code> to update local rotation, <code>false</code> to update world rotation.
+ * @see #isLocalRotation()
+ */
+ public void setLocalRotation(final boolean localRotation) {
+ _localRotation = localRotation;
+ }
+
+ /**
+ * @return <code>true</code> if the local rotation is being updated, <code>false</code> if the world rotation is.
+ * @see #setLocalRotation(boolean)
+ */
+ public boolean isLocalRotation() {
+ return _localRotation;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java
new file mode 100644
index 0000000..378e5b2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/DefaultColorInterpolationController.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+
+/**
+ * ColorRGBAInterpolationController class interpolates the {@link Mesh#getDefaultColor() default colour} of a mesh using
+ * {@link ReadOnlyColorRGBA}s.
+ * <p>
+ * Note: The default colour only works if a {@link MeshData#getColorBuffer() colour buffer} has NOT been set on the
+ * mesh.
+ * </p>
+ */
+public class DefaultColorInterpolationController extends InterpolationController<ReadOnlyColorRGBA, Mesh> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Interpolates between the given colors using the
+ * {@link ColorRGBA#lerpLocal(ReadOnlyColorRGBA, ReadOnlyColorRGBA, float)} method.
+ */
+ @Override
+ protected void interpolate(final ReadOnlyColorRGBA from, final ReadOnlyColorRGBA to, final double delta,
+ final Mesh caller) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+ assert (null != caller) : "parameter 'caller' can not be null";
+
+ final ColorRGBA color = ColorRGBA.fetchTempInstance().set(caller.getDefaultColor());
+
+ color.lerpLocal(from, to, (float) delta);
+
+ caller.setDefaultColor(color);
+
+ ColorRGBA.releaseTempInstance(color);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java
new file mode 100644
index 0000000..0baf1d5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/InterpolationController.java
@@ -0,0 +1,420 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController;
+
+/**
+ * InterpolationController class is an abstract class containing all the stuff common to controllers interpolating
+ * things.
+ * <p>
+ * Implementation note: This class is comprised of quite a few protected methods, this is mainly to allow maximum
+ * flexibility for overriding classes.
+ * </p>
+ *
+ * @param <C>
+ * The control 'points' being interpolated, for example Vectors or Quaternions.
+ * @param <T>
+ * The object this controller will perform the interpolation on, for example Spatials.
+ */
+public abstract class InterpolationController<C, T extends Spatial> extends ComplexSpatialController<T> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** The minimum allowed delta */
+ public static final double DELTA_MIN = 0.0;
+
+ /** The maximum allowed delta */
+ public static final double DELTA_MAX = 1.0;
+
+ /** @see #setControls(List) */
+ private List<C> _controls = null;
+
+ /** @see #setIndex(int) */
+ private int _index = getMinimumIndex();
+
+ /** @see #setDelta(double) */
+ private double _delta = DELTA_MIN;
+
+ /** @see #setCycleForward(boolean) */
+ private boolean _cycleForward = true;
+
+ /**
+ * This method is automatically called by {@link #update(double, Spatial)} from this controller.
+ *
+ * @param from
+ * The control to interpolate from.
+ * @param to
+ * The control to interpolate to.
+ * @param delta
+ * The distance between <code>from</code> and <code>to</code>, will be between <code>0.0</code> and
+ * <code>1.0</code> (inclusive).
+ * @param caller
+ * The object to interpolate, will not be <code>null</code>.
+ */
+ protected abstract void interpolate(C from, C to, double delta, T caller);
+
+ /**
+ * Interpolates on the set {@link #getControls() controls}.
+ * <p>
+ * It will only update the given object if this controller is {@link #isActive() active}, caller isn't
+ * <code>null</code> and it's {@link #getSpeed() speed} is greater than zero.
+ * </p>
+ *
+ * @param time
+ * The passed since this controller was last called.
+ * @param caller
+ * The object to update, if this is <code>null</code> nothing will be updated.
+ */
+ @Override
+ public void update(final double time, final T caller) {
+ if (shouldUpdate(time, caller)) {
+
+ updateDeltaAndIndex(time);
+
+ assert (getDelta() >= DELTA_MIN) : "delta is less than " + DELTA_MIN
+ + ", updateDeltaAndIndex() has probably been overriden incorrectly";
+ assert (getDelta() <= DELTA_MAX) : "delta is greater than " + DELTA_MAX
+ + ", updateDeltaAndIndex() has probably been overriden incorrectly";
+
+ clampIndex();
+
+ assert (getIndex() < getControls().size()) : "_index was greater than the number of controls, clampIndex() has probably been overriden incorrectly";
+ assert (getIndex() >= 0) : "_index was negative, clampIndex() has probably been overriden incorrectly";
+
+ interpolate(getControlFrom(), getControlTo(), getDelta(), caller);
+ }
+ }
+
+ private boolean shouldUpdate(final double time, final T caller) {
+ return isActive() && null != caller && time > 0.0 && getSpeed() > 0.0 && !isClamped();
+ }
+
+ /**
+ * @return The minimum allowed index. By default it returns 0.
+ */
+ protected int getMinimumIndex() {
+ return 0;
+ }
+
+ /**
+ * @return The maximum allowed index. By default it returns the last index in the {@link #getControls() control}
+ * list.
+ */
+ protected int getMaximumIndex() {
+ return getControls().size() - 1;
+ }
+
+ /**
+ * @param controls
+ * The new controls to set, can not be <code>null</code> or empty.
+ * @see #getControls()
+ */
+ public void setControls(final List<C> controls) {
+ if (null == controls) {
+ throw new IllegalArgumentException("controls can not be null!");
+ }
+ if (controls.isEmpty()) {
+ throw new IllegalArgumentException("controls can not be empty!");
+ }
+
+ _controls = controls;
+ }
+
+ /**
+ * @param controlArray
+ * The new values to set, can not be <code>null</code> or size 0.
+ * @see #getControls()
+ */
+ public void setControls(final C... controlArray) {
+ if (null == controlArray) {
+ throw new IllegalArgumentException("controlArray can not be null!");
+ }
+
+ setControls(Arrays.<C> asList(controlArray));
+ }
+
+ /**
+ * @return The controls getting interpolated between, will not be <code>null</code> or empty.
+ * @see #setControls(List)
+ */
+ public List<C> getControls() {
+ assert (null != _controls) : "_controls was null, it must be set before use!";
+ assert (!_controls.isEmpty()) : "_controls was empty, it must contain at least 1 element for this class to work!";
+
+ return _controls;
+ }
+
+ /**
+ * Updates the {@link #getDelta() delta} and {@link #getIndex() index}.
+ *
+ * @param time
+ * The raw time since this was last called.
+ */
+ protected void updateDeltaAndIndex(final double time) {
+ incrementDelta(getSpeed() * time);
+
+ /* If >= DELTA_MAX then we need to start interpolating between next set of points */
+ while (getDelta() >= DELTA_MAX) {
+ /* Adjust delta for new set of points */
+ decrementDelta(DELTA_MAX);
+
+ /* Increment/decrement current index based on whether we are cycling forward or backwards */
+ if (isCycleForward()) {
+ incrementIndex();
+ } else {
+ decrementIndex();
+ }
+ }
+ }
+
+ /**
+ * Clamps the {@link #getIndex() index} to ensure its not out of bounds.
+ * <p>
+ * This is called automatically from {@link #update(double, Spatial)} and shouldn't be called manually. It only
+ * really exists as a separate method to allow sub classes maximum flexibility. Also of note is the fact that if
+ * this method is overridden then {@link #getControlFrom()} and {@link #getControlTo()} methods will also probably
+ * need to be overridden as they rely on this method clamping the index correctly before they get called.
+ * </p>
+ */
+ protected void clampIndex() {
+ switch (getRepeatType()) {
+ case CLAMP:
+ if (getIndex() >= getMaximumIndex()) {
+ /* Clamp these just to be on the safe side (overflow) */
+ setIndex(getMaximumIndex());
+ setDelta(DELTA_MAX);
+ }
+ break;
+
+ case CYCLE:
+ if (isCycleForward()) {
+ if (getIndex() >= getMaximumIndex()) {
+ setIndex(getMaximumIndex());
+ setCycleForward(false);
+ }
+ } else {
+ if (getIndex() <= getMinimumIndex()) {
+ setIndex(getMinimumIndex());
+ setCycleForward(true);
+ }
+ }
+ break;
+
+ case WRAP:
+ if (getIndex() >= getMaximumIndex()) {
+ setIndex(getMinimumIndex());
+ }
+ break;
+ }
+ }
+
+ /**
+ * This method assumes the {@link #getIndex() index} has already been {@link #clampIndex() clamped} correctly.
+ *
+ * @return The control to interpolate from, will not be <code>null</code>.
+ * @see #getControlTo()
+ */
+ protected C getControlFrom() {
+ C from = null;
+
+ switch (getRepeatType()) {
+ case CLAMP:
+ if (getIndex() > getMaximumIndex()) {
+ from = getControls().get(getMaximumIndex());
+ } else {
+ from = getControls().get(getIndex());
+ }
+ break;
+
+ case CYCLE:
+ from = getControls().get(getIndex());
+ break;
+
+ case WRAP:
+ from = getControls().get(getIndex());
+ break;
+ }
+
+ return from;
+ }
+
+ /**
+ * This method assumes the {@link #getIndex() index} has already been {@link #clampIndex() clamped} correctly.
+ *
+ * @return The control to interpolate to, will not be <code>null</code>.
+ * @see #getControlFrom()
+ */
+ protected C getControlTo() {
+ C to = null;
+
+ switch (getRepeatType()) {
+ case CLAMP:
+ if (getIndex() >= getMaximumIndex()) {
+ to = getControls().get(getMaximumIndex());
+ } else {
+ to = getControls().get(getIndex() + 1);
+ }
+ break;
+
+ case CYCLE:
+ if (isCycleForward()) {
+ to = getControls().get(getIndex() + 1);
+ } else {
+ to = getControls().get(getIndex() - 1);
+ }
+ break;
+
+ case WRAP:
+ to = getControls().get(getIndex() + 1);
+ break;
+ }
+
+ return to;
+ }
+
+ /**
+ * Increments the index by 1.
+ *
+ * @return The new index value as a convenience.
+ */
+ protected int incrementIndex() {
+ return ++_index;
+ }
+
+ /**
+ * Decrements the index by 1.
+ *
+ * @return The new index value as a convenience.
+ */
+ protected int decrementIndex() {
+ return --_index;
+ }
+
+ /**
+ * @param index
+ * The new index value.
+ * @see #getIndex()
+ */
+ protected void setIndex(final int index) {
+ _index = index;
+ }
+
+ /**
+ * @return The index of the {@link #getControls() control} to interpolate from.
+ * @see #setIndex(int)
+ */
+ protected int getIndex() {
+ return _index;
+ }
+
+ /**
+ * @param by
+ * The amount to increment by, if this is negative it will actually decrement the delta.
+ * @return The new delta value as a convenience.
+ * @see #decrementDelta(double)
+ */
+ protected double incrementDelta(final double by) {
+ _delta += by;
+
+ return _delta;
+ }
+
+ /**
+ * @param by
+ * The amount to decrement by, if this is negative it will actually increment the delta.
+ * @return The new delta value as a convenience.
+ * @see #incrementDelta(double)
+ */
+ protected double decrementDelta(final double by) {
+ _delta -= by;
+
+ return _delta;
+ }
+
+ /**
+ * @param delta
+ * The new distance between the {@link #getControlFrom() from control} and {@link #getControlTo() to
+ * control} , should be between <code>0.0</code> and <code>1.0</code> (inclusive).
+ * @see #getDelta()
+ */
+ protected void setDelta(final double delta) {
+ _delta = delta;
+ }
+
+ /**
+ * @return The distance between the {@link #getControlFrom() from control} and {@link #getControlTo() to control} ,
+ * will be between <code>0.0</code> and <code>1.0</code> (inclusive).
+ * @see #setDelta(double)
+ */
+ protected double getDelta() {
+ return _delta;
+ }
+
+ /**
+ * @param cycleForward
+ * <code>true</code> to interpolate the controls forwards (1, 2, 3 ...), <code>false</code> to
+ * interpolate the controls backwards (3, 2, 1 ...)
+ * @see #isCycleForward()
+ */
+ protected void setCycleForward(final boolean cycleForward) {
+ _cycleForward = cycleForward;
+ }
+
+ /**
+ * @return <code>true</code> if interpolating the controls forwards (1, 2, 3 ...), <code>false</code> if
+ * interpolating the controls backwards (3, 2, 1 ...)
+ * @see #setCycleForward(boolean)
+ */
+ protected boolean isCycleForward() {
+ return _cycleForward;
+ }
+
+ /**
+ * Also {@link #reset() resets} this controller for safety, because changing the repeat type part way through an
+ * interpolation can cause problems.
+ *
+ * @param repeatType
+ * The new repeat type to use.
+ */
+ @Override
+ public void setRepeatType(final RepeatType repeatType) {
+ if (getRepeatType() != repeatType) {
+ /* Reset for safety */
+ reset();
+ }
+
+ super.setRepeatType(repeatType);
+ }
+
+ /**
+ * Resets the internal state, namely the cycle direction, delta and index variables.
+ */
+ public void reset() {
+ setCycleForward(true);
+ setDelta(DELTA_MIN);
+ setIndex(getMinimumIndex());
+ }
+
+ /**
+ * @return <code>true</code> if this controllers {@link #getRepeatType() repeat type} is
+ * {@link ComplexSpatialController.RepeatType#CLAMP clamp} and its currently clamped at the maximum index.
+ */
+ public boolean isClamped() {
+ return isRepeatTypeClamp() && getIndex() == getMaximumIndex();
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java
new file mode 100644
index 0000000..176383a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/LinearVector3InterpolationController.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * LinearVector3InterpolationController class interpolates a {@link Spatial}s vectors using
+ * {@link Vector3#lerpLocal(ReadOnlyVector3, ReadOnlyVector3, double)}
+ */
+public class LinearVector3InterpolationController extends Vector3InterpolationController {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected Vector3 interpolateVectors(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta,
+ final Vector3 target) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+
+ return target.lerpLocal(from, to, delta);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java
new file mode 100644
index 0000000..dfc5c2c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/QuaternionInterpolationController.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.type.ReadOnlyQuaternion;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * QuaternionInterpolationController class interpolates a {@link Spatial}s rotation using {@link Quaternion}s.
+ */
+public class QuaternionInterpolationController extends InterpolationController<ReadOnlyQuaternion, Spatial> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** @see #setLocalRotation(boolean) */
+ private boolean _localRotation = true;
+
+ /**
+ * Interpolates between the given quaternions using the
+ * {@link Quaternion#slerpLocal(ReadOnlyQuaternion, ReadOnlyQuaternion, double)} method.
+ */
+ @Override
+ protected void interpolate(final ReadOnlyQuaternion from, final ReadOnlyQuaternion to, final double delta,
+ final Spatial caller) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+ assert (null != caller) : "parameter 'caller' can not be null";
+
+ final Quaternion tempQuat = Quaternion.fetchTempInstance();
+
+ tempQuat.slerpLocal(from, to, delta);
+
+ if (isLocalRotation()) {
+ caller.setRotation(tempQuat);
+ } else {
+ caller.setWorldRotation(tempQuat);
+ }
+
+ Quaternion.releaseTempInstance(tempQuat);
+ }
+
+ /**
+ * @param localRotation
+ * <code>true</code> to update local rotation, <code>false</code> to update world rotation.
+ * @see #isLocalRotation()
+ */
+ public void setLocalRotation(final boolean localRotation) {
+ _localRotation = localRotation;
+ }
+
+ /**
+ * @return <code>true</code> if the local rotation is being updated, <code>false</code> if the world rotation is.
+ * @see #setLocalRotation(boolean)
+ */
+ public boolean isLocalRotation() {
+ return _localRotation;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java
new file mode 100644
index 0000000..44ad32a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/controller/interpolation/Vector3InterpolationController.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.controller.interpolation;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * Vector3InterpolationController class is a base class for controllers that can interpolate on vectors.
+ */
+public abstract class Vector3InterpolationController extends InterpolationController<ReadOnlyVector3, Spatial> {
+
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ /** @see #setConstantSpeed(boolean) */
+ private boolean _constantSpeed;
+
+ /** @see #setUpdateField(UpdateField) */
+ private UpdateField _updateField = UpdateField.LOCAL_TRANSLATION;
+
+ /**
+ * Implemented by sub classes to perform the actual interpolation.
+ *
+ * @param from
+ * The vector to interpolate from.
+ * @param to
+ * The vector to interpolate to.
+ * @param delta
+ * The distance between <code>from</code> and <code>to</code>, will be between <code>0.0</code> and
+ * <code>1.0</code> (inclusive).
+ * @param target
+ * The vector to actually interpolate.
+ * @return The interpolated vector, should not be <code>null</code>.
+ */
+ protected abstract Vector3 interpolateVectors(ReadOnlyVector3 from, ReadOnlyVector3 to, double delta, Vector3 target);
+
+ /**
+ * Interpolates between the given vectors using the
+ * {@link #interpolateVectors(ReadOnlyVector3, ReadOnlyVector3, double, Vector3)} to perform the actual
+ * interpolation.
+ */
+ @Override
+ protected void interpolate(final ReadOnlyVector3 from, final ReadOnlyVector3 to, final double delta,
+ final Spatial caller) {
+
+ assert (null != from) : "parameter 'from' can not be null";
+ assert (null != to) : "parameter 'to' can not be null";
+ assert (null != caller) : "parameter 'caller' can not be null";
+
+ final Vector3 target = Vector3.fetchTempInstance();
+
+ final ReadOnlyVector3 interpolated = interpolateVectors(from, to, delta, target);
+
+ switch (getUpdateField()) {
+ case LOCAL_SCALE:
+ caller.setScale(interpolated);
+ break;
+ case LOCAL_TRANSLATION:
+ caller.setTranslation(interpolated);
+ break;
+ case WORLD_SCALE:
+ caller.setWorldScale(interpolated);
+ break;
+ case WORLD_TRANSLATION:
+ caller.setWorldTranslation(interpolated);
+ break;
+ default:
+ caller.setTranslation(interpolated);
+ }
+
+ Vector3.releaseTempInstance(target);
+
+ }
+
+ /**
+ * @param constantSpeed
+ * <code>true</code> to interpolate between vectors at a constant speed, <code>false</code> to
+ * interpolate at a constant time.
+ * @see #isConstantSpeed()
+ */
+ public void setConstantSpeed(final boolean constantSpeed) {
+ _constantSpeed = constantSpeed;
+ }
+
+ /**
+ * See the setters Javadoc for more information.
+ *
+ * @return <code>true</code> if interpolating at a constant speed, <code>false</code> otherwise.
+ * @see #setConstantSpeed(boolean)
+ */
+ public boolean isConstantSpeed() {
+ return _constantSpeed;
+ }
+
+ /**
+ * @param updateField
+ * The new field to update.
+ * @see #getUpdateField()
+ */
+ public void setUpdateField(final UpdateField updateField) {
+ _updateField = updateField;
+ }
+
+ /**
+ * @return The field being updated.
+ * @see #setUpdateField(UpdateField)
+ */
+ public UpdateField getUpdateField() {
+ return _updateField;
+ }
+
+ /**
+ * Specifies which field on the spatial to update.
+ */
+ public enum UpdateField {
+ /** @see Spatial#getTranslation() */
+ LOCAL_TRANSLATION,
+ /** @see Spatial#getWorldTranslation() */
+ WORLD_TRANSLATION,
+ /** @see Spatial#getScale() */
+ LOCAL_SCALE,
+ /** @see Spatial#getWorldScale() */
+ WORLD_SCALE;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java
new file mode 100644
index 0000000..004c7a5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyEventListener.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * DirtyEventListener is the interface for objects interested in updates when spatials get marked dirty / clean
+ * (updated).
+ */
+public interface DirtyEventListener {
+
+ /**
+ * spatialDirty is called when a spatial is changed in respect to transform, bounding, attach/dettach or renderstate
+ *
+ * @return true if the event should be consumed and not continue up the scenegraph.
+ */
+ boolean spatialDirty(Spatial spatial, DirtyType dirtyType);
+
+ /**
+ * spatialClean is called when a spatial is changed in respect to transform, bounding, attach/dettach or renderstate
+ *
+ * @return true if the event should be consumed and not continue up the scenegraph.
+ */
+ boolean spatialClean(Spatial spatial, DirtyType dirtyType);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java
new file mode 100644
index 0000000..3a4c91b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/DirtyType.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+/**
+ * DirtyType contains the types of update that can occur on a spatial.
+ */
+public enum DirtyType {
+ Transform, Bounding, Attached, Detached, Destroyed, RenderState
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java
new file mode 100644
index 0000000..9c9d95e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/event/SceneGraphManager.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * SceneGraphManager is a convenience class for use when you want to have multiple listeners on a particular node.
+ */
+public class SceneGraphManager implements DirtyEventListener {
+ private static SceneGraphManager sceneGraphManagerInstance;
+
+ private final List<DirtyEventListener> _listeners;
+
+ private SceneGraphManager() {
+ _listeners = new ArrayList<DirtyEventListener>();
+ }
+
+ public static SceneGraphManager getSceneGraphManager() {
+ if (sceneGraphManagerInstance == null) {
+ sceneGraphManagerInstance = new SceneGraphManager();
+ }
+
+ return sceneGraphManagerInstance;
+ }
+
+ public void listenOnSpatial(final Spatial spatial) {
+ spatial.setListener(this);
+ }
+
+ public void addDirtyEventListener(final DirtyEventListener listener) {
+ _listeners.add(listener);
+ }
+
+ public void removeDirtyEventListener(final DirtyEventListener listener) {
+ _listeners.remove(listener);
+ }
+
+ public boolean spatialDirty(final Spatial spatial, final DirtyType dirtyType) {
+ for (final DirtyEventListener listener : _listeners) {
+ listener.spatialDirty(spatial, dirtyType);
+ }
+ return false;
+ }
+
+ public boolean spatialClean(final Spatial spatial, final DirtyType dirtyType) {
+ for (final DirtyEventListener listener : _listeners) {
+ listener.spatialClean(spatial, dirtyType);
+ }
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java
new file mode 100644
index 0000000..8a3cad5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/BillboardNode.java
@@ -0,0 +1,277 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>BillboardNode</code> defines a node that always orients towards the camera. However, it does not tilt up/down
+ * as the camera rises. This keep geometry from appearing to fall over if the camera rises or lowers.
+ * <code>BillboardNode</code> is useful to contain a single quad that has a image applied to it for lowest detail
+ * models. This quad, with the texture, will appear to be a full model at great distances, and save on rendering and
+ * memory. It is important to note that for AXIAL mode, the billboards orientation will always be up (0,1,0). This means
+ * that a "standard" ardor3d camera with up (0,1,0) is the only camera setting compatible with AXIAL mode.
+ */
+public class BillboardNode extends Node {
+
+ private double _lastTime;
+
+ private final Matrix3 _orient = new Matrix3(Matrix3.IDENTITY);
+
+ private final Vector3 _look = new Vector3(Vector3.ZERO);
+
+ private final Vector3 _left = new Vector3(Vector3.ZERO);
+
+ public enum BillboardAlignment {
+ ScreenAligned, CameraAligned, AxialY, AxialZ
+ }
+
+ private BillboardAlignment _alignment;
+
+ public BillboardNode() {}
+
+ /**
+ * Constructor instantiates a new <code>BillboardNode</code>. The name of the node is supplied during construction.
+ *
+ * @param name
+ * the name of the node.
+ */
+ public BillboardNode(final String name) {
+ super(name);
+ _alignment = BillboardAlignment.ScreenAligned;
+ }
+
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ _lastTime = 0; // time
+ super.updateWorldTransform(recurse);
+ }
+
+ /**
+ * <code>draw</code> updates the billboards orientation then renders the billboard's children.
+ *
+ * @param r
+ * the renderer used to draw.
+ * @see com.ardor3d.scenegraph.Spatial#draw(com.ardor3d.renderer.Renderer)
+ */
+ @Override
+ public void draw(final Renderer r) {
+ rotateBillboard();
+
+ super.draw(r);
+ }
+
+ /**
+ * rotate the billboard based on the type set
+ *
+ * @param cam
+ * Camera
+ */
+ public void rotateBillboard() {
+ // get the scale, translation and rotation of the node in world space
+ updateWorldTransform(false);
+
+ switch (_alignment) {
+ case ScreenAligned:
+ rotateScreenAligned();
+ break;
+ case CameraAligned:
+ rotateCameraAligned();
+ break;
+ case AxialY:
+ rotateAxial(new Vector3(Vector3.UNIT_Y));
+ break;
+ case AxialZ:
+ rotateAxial(new Vector3(Vector3.UNIT_Z));
+ break;
+ }
+
+ if (_children == null) {
+ return;
+ }
+
+ propagateDirtyDown(ON_DIRTY_TRANSFORM);
+ for (int i = 0, cSize = getNumberOfChildren(); i < cSize; i++) {
+ final Spatial child = getChild(i);
+ if (child != null) {
+ child.updateGeometricState(_lastTime, false);
+ }
+ }
+ }
+
+ /**
+ * Aligns this Billboard Node so that it points to the camera position.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateCameraAligned() {
+ final Camera camera = Camera.getCurrentCamera();
+ _look.set(camera.getLocation()).subtractLocal(_worldTransform.getTranslation());
+ // coopt left for our own purposes.
+ final Vector3 xzp = _left;
+ // The xzp vector is the projection of the look vector on the xz plane
+ xzp.set(_look.getX(), 0, _look.getZ());
+
+ // check for undefined rotation...
+ if (xzp.equals(Vector3.ZERO)) {
+ return;
+ }
+
+ _look.normalizeLocal();
+ xzp.normalizeLocal();
+ final double cosp = _look.dot(xzp);
+
+ // compute the local orientation matrix for the billboard
+ _orient.setValue(0, 0, xzp.getZ());
+ _orient.setValue(0, 1, xzp.getX() * -_look.getY());
+ _orient.setValue(0, 2, xzp.getX() * cosp);
+ _orient.setValue(1, 0, 0);
+ _orient.setValue(1, 1, cosp);
+ _orient.setValue(1, 2, _look.getY());
+ _orient.setValue(2, 0, -xzp.getX());
+ _orient.setValue(2, 1, xzp.getZ() * -_look.getY());
+ _orient.setValue(2, 2, xzp.getZ() * cosp);
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ final Matrix3 mat = Matrix3.fetchTempInstance().set(_worldTransform.getMatrix()).multiplyLocal(_orient);
+ _worldTransform.setRotation(mat);
+ Matrix3.releaseTempInstance(mat);
+ }
+
+ /**
+ * Rotate the billboard so it points directly opposite the direction the camera's facing
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateScreenAligned() {
+ final Camera camera = Camera.getCurrentCamera();
+ // coopt diff for our in direction:
+ _look.set(camera.getDirection()).negateLocal();
+ // coopt loc for our left direction:
+ _left.set(camera.getLeft()).negateLocal();
+ _orient.fromAxes(_left, camera.getUp(), _look);
+ _worldTransform.setRotation(_orient);
+ }
+
+ /**
+ * Rotate the billboard towards the camera, but keeping a given axis fixed.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateAxial(final Vector3 axis) {
+ final Camera camera = Camera.getCurrentCamera();
+ // Compute the additional rotation required for the billboard to face
+ // the camera. To do this, the camera must be inverse-transformed into
+ // the model space of the billboard.
+ _look.set(camera.getLocation()).subtractLocal(_worldTransform.getTranslation());
+ final Matrix3 worldMatrix = Matrix3.fetchTempInstance().set(_worldTransform.getMatrix());
+ worldMatrix.applyPost(_look, _left); // coopt left for our own purposes.
+ final ReadOnlyVector3 scale = _worldTransform.getScale();
+ _left.divideLocal(scale);
+
+ // squared length of the camera projection in the xz-plane
+ final double lengthSquared = _left.getX() * _left.getX() + _left.getZ() * _left.getZ();
+ if (lengthSquared < MathUtils.EPSILON) {
+ // camera on the billboard axis, rotation not defined
+ return;
+ }
+
+ // unitize the projection
+ final double invLength = 1.0 / Math.sqrt(lengthSquared);
+ if (axis.getY() == 1) {
+ _left.setX(_left.getX() * invLength);
+ _left.setY(0.0);
+ _left.setZ(_left.getZ() * invLength);
+
+ // compute the local orientation matrix for the billboard
+ _orient.setValue(0, 0, _left.getZ());
+ _orient.setValue(0, 1, 0);
+ _orient.setValue(0, 2, _left.getX());
+ _orient.setValue(1, 0, 0);
+ _orient.setValue(1, 1, 1);
+ _orient.setValue(1, 2, 0);
+ _orient.setValue(2, 0, -_left.getX());
+ _orient.setValue(2, 1, 0);
+ _orient.setValue(2, 2, _left.getZ());
+ } else if (axis.getZ() == 1) {
+ _left.setX(_left.getX() * invLength);
+ _left.setY(_left.getY() * invLength);
+ _left.setZ(0.0);
+
+ // compute the local orientation matrix for the billboard
+ _orient.setValue(0, 0, _left.getY());
+ _orient.setValue(0, 1, _left.getX());
+ _orient.setValue(0, 2, 0);
+ _orient.setValue(1, 0, -_left.getY());
+ _orient.setValue(1, 1, _left.getX());
+ _orient.setValue(1, 2, 0);
+ _orient.setValue(2, 0, 0);
+ _orient.setValue(2, 1, 0);
+ _orient.setValue(2, 2, 1);
+ }
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ worldMatrix.multiplyLocal(_orient);
+ _worldTransform.setRotation(worldMatrix);
+ Matrix3.releaseTempInstance(worldMatrix);
+ }
+
+ /**
+ * Returns the alignment this BillboardNode is set too.
+ *
+ * @return The alignment of rotation, ScreenAligned, CameraAligned, AxialY or AxialZ.
+ */
+ public BillboardAlignment getAlignment() {
+ return _alignment;
+ }
+
+ /**
+ * Sets the type of rotation this BillboardNode will have. The alignment can be ScreenAligned, CameraAligned, AxialY
+ * or AxialZ. Invalid alignments will assume no billboard rotation.
+ */
+ public void setAlignment(final BillboardAlignment alignment) {
+ _alignment = alignment;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_orient, "orient", new Matrix3());
+ capsule.write(_look, "look", new Vector3(Vector3.ZERO));
+ capsule.write(_left, "left", new Vector3(Vector3.ZERO));
+ capsule.write(_alignment, "alignment", BillboardAlignment.ScreenAligned);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _orient.set((Matrix3) capsule.readSavable("orient", new Matrix3(Matrix3.IDENTITY)));
+ _look.set((Vector3) capsule.readSavable("look", new Vector3(Vector3.ZERO)));
+ _left.set((Vector3) capsule.readSavable("left", new Vector3(Vector3.ZERO)));
+ _alignment = capsule.readEnum("alignment", BillboardAlignment.class, BillboardAlignment.ScreenAligned);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java
new file mode 100644
index 0000000..76e1628
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/CameraNode.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.type.ReadOnlyMatrix3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * Defines a node that manages a {@link Camera} object, allowing it to be part of a scenegraph. The updateWorldTransform
+ * method is overridden to adjust the managed camera's location and orientation using this Node's world translation and
+ * the world rotation. The column 0 of the world rotation matrix is used for the camera left vector, column 1 is used
+ * for the camera up vector, column 2 is used for the camera direction vector.
+ */
+public class CameraNode extends Node {
+
+ private Camera _camera;
+
+ public CameraNode() {}
+
+ /**
+ * Constructor instantiates a new <code>CameraNode</code> object setting the camera to use for the frame reference.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param camera
+ * the camera this node controls.
+ */
+ public CameraNode(final String name, final Camera camera) {
+ super(name);
+ _camera = camera;
+ }
+
+ /**
+ * Forces rotation and translation of this node to be sync'd with the attached camera. (Assumes the node is in world
+ * space.)
+ */
+ public void updateFromCamera() {
+ final ReadOnlyVector3 camLeft = _camera.getLeft();
+ final ReadOnlyVector3 camUp = _camera.getUp();
+ final ReadOnlyVector3 camDir = _camera.getDirection();
+ final ReadOnlyVector3 camLoc = _camera.getLocation();
+
+ final Matrix3 rotation = Matrix3.fetchTempInstance();
+ rotation.fromAxes(camLeft, camUp, camDir);
+
+ setRotation(rotation);
+ setTranslation(camLoc);
+
+ Matrix3.releaseTempInstance(rotation);
+ }
+
+ /**
+ * <code>setCamera</code> sets the camera that this node controls.
+ *
+ * @param camera
+ * the camera that this node controls.
+ */
+ public void setCamera(final Camera camera) {
+ _camera = camera;
+ }
+
+ /**
+ * <code>getCamera</code> retrieves the camera object that this node controls.
+ *
+ * @return the camera this node controls.
+ */
+ public Camera getCamera() {
+ return _camera;
+ }
+
+ /**
+ * <code>updateWorldTransform</code> updates the rotation and translation of this node, and sets the camera's frame
+ * buffer to reflect the current view.
+ */
+ @Override
+ public void updateWorldTransform(final boolean recurse) {
+ super.updateWorldTransform(recurse);
+ if (_camera != null) {
+ final ReadOnlyVector3 worldTranslation = getWorldTranslation();
+ final ReadOnlyMatrix3 worldRotation = getWorldRotation();
+ _camera.setFrame(worldTranslation, worldRotation);
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_camera, "camera", null);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _camera = (Camera) capsule.readSavable("camera", null);
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java
new file mode 100644
index 0000000..8d2377d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNode.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class PassNode extends Node {
+
+ private List<PassNodeState> _passNodeStates = new ArrayList<PassNodeState>();
+
+ public PassNode(final String name) {
+ super(name);
+ }
+
+ public PassNode() {
+ super();
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ if (_children == null) {
+ return;
+ }
+
+ final RenderContext context = ContextManager.getCurrentContext();
+ r.getQueue().pushBuckets();
+ for (final PassNodeState pass : _passNodeStates) {
+ if (!pass.isEnabled()) {
+ continue;
+ }
+
+ pass.applyPassNodeStates(context);
+
+ Spatial child;
+ for (int i = 0, cSize = _children.size(); i < cSize; i++) {
+ child = _children.get(i);
+ if (child != null) {
+ child.onDraw(r);
+ }
+ }
+ r.renderBuckets();
+
+ context.popEnforcedStates();
+ }
+ r.getQueue().popBuckets();
+ }
+
+ public void addPass(final PassNodeState toAdd) {
+ _passNodeStates.add(toAdd);
+ }
+
+ public void insertPass(final PassNodeState toAdd, final int index) {
+ _passNodeStates.add(index, toAdd);
+ }
+
+ public boolean containsPass(final PassNodeState s) {
+ return _passNodeStates.contains(s);
+ }
+
+ public boolean removePass(final PassNodeState toRemove) {
+ return _passNodeStates.remove(toRemove);
+ }
+
+ public PassNodeState getPass(final int index) {
+ return _passNodeStates.get(index);
+ }
+
+ public int nrPasses() {
+ return _passNodeStates.size();
+ }
+
+ public void clearAll() {
+ _passNodeStates.clear();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.writeSavableList(_passNodeStates, "passNodeStates", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _passNodeStates = capsule.readSavableList("passNodeStates", null);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java
new file mode 100644
index 0000000..c34d4f9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/PassNodeState.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.EnumMap;
+
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.util.export.CapsuleUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public class PassNodeState implements Savable, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /** if false, pass will not be updated or rendered. */
+ protected boolean _enabled = true;
+
+ /**
+ * RenderStates registered with this pass - if a given state is not null it overrides the corresponding state set
+ * during rendering.
+ */
+ protected final EnumMap<RenderState.StateType, RenderState> _passStates = new EnumMap<RenderState.StateType, RenderState>(
+ RenderState.StateType.class);
+
+ /**
+ * Applies all currently set renderstates to the supplied context
+ *
+ * @param context
+ */
+ public void applyPassNodeStates(final RenderContext context) {
+ context.pushEnforcedStates();
+ context.enforceStates(_passStates);
+ }
+
+ /**
+ * Enforce a particular state. In other words, the given state will override any state of the same type set on a
+ * scene object. Remember to clear the state when done enforcing. Very useful for multipass techniques where
+ * multiple sets of states need to be applied to a scenegraph drawn multiple times.
+ *
+ * @param state
+ * state to enforce
+ */
+ public void setPassState(final RenderState state) {
+ _passStates.put(state.getType(), state);
+ }
+
+ /**
+ * @param type
+ * the type to query
+ * @return the state enforced for a give state type, or null if none.
+ */
+ public RenderState getPassState(final StateType type) {
+ return _passStates.get(type);
+ }
+
+ /**
+ * Clears an enforced render state index by setting it to null. This allows object specific states to be used.
+ *
+ * @param type
+ * The type of RenderState to clear enforcement on.
+ */
+ public void clearPassState(final StateType type) {
+ _passStates.remove(type);
+ }
+
+ /**
+ * sets all enforced states to null.
+ *
+ * @see RenderContext#clearEnforcedState(int)
+ */
+ public void clearPassStates() {
+ _passStates.clear();
+ }
+
+ /** @return Returns the enabled. */
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ /**
+ * @param enabled
+ * The enabled to set.
+ */
+ public void setEnabled(final boolean enabled) {
+ _enabled = enabled;
+ }
+
+ public Class<? extends PassNodeState> getClassTag() {
+ return this.getClass();
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ final OutputCapsule oc = capsule;
+ oc.write(_enabled, "enabled", true);
+ oc.write(_passStates.values().toArray(new RenderState[0]), "passStates", null);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ final InputCapsule ic = capsule;
+ _enabled = ic.readBoolean("enabled", true);
+ final RenderState[] states = CapsuleUtils.asArray(ic.readSavableArray("passStates", null), RenderState.class);
+ _passStates.clear();
+ if (states != null) {
+ for (final RenderState state : states) {
+ _passStates.put(state.getType(), state);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java
new file mode 100644
index 0000000..1500c5d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/QuadImposterNode.java
@@ -0,0 +1,406 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.Timer;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * QuadImposterNode
+ */
+public class QuadImposterNode extends Node {
+
+ protected TextureRenderer _tRenderer;
+
+ protected Texture2D _texture;
+
+ protected Node _targetScene;
+
+ protected Quad _imposterQuad;
+
+ protected double _redrawRate;
+ protected double _elapsed;
+ protected double _cameraAngleThreshold;
+ protected double _cameraDistanceThreshold = Double.MAX_VALUE;
+ protected boolean _haveDrawn;
+
+ protected Vector3 _worldUpVector = new Vector3(0, 1, 0);
+
+ protected boolean _doUpdate = true;
+
+ protected Camera _cam;
+
+ protected int _twidth, _theight;
+ protected int _depth, _samples;
+
+ protected final Vector3 _lastCamDir = new Vector3();
+ protected double _lastCamDist;
+
+ protected Vector3[] _corners = new Vector3[8];
+ protected final Vector3 _center = new Vector3();
+ protected final Vector3 _extents = new Vector3();
+ protected final Vector2 _minScreenPos = new Vector2();
+ protected final Vector2 _maxScreenPos = new Vector2();
+ protected final Vector2 _minMaxScreenPos = new Vector2();
+ protected final Vector2 _maxMinScreenPos = new Vector2();
+ protected final Vector3 _tempVec = new Vector3();
+ protected double _minZ;
+ protected double _nearPlane;
+ protected double _farPlane;
+ protected Timer _timer;
+
+ public QuadImposterNode() {
+ this(null, 64, 64);
+ }
+
+ public QuadImposterNode(final String name, final int twidth, final int theight) {
+ this(name, twidth, theight, null);
+ }
+
+ public QuadImposterNode(final String name, final int twidth, final int theight, final Timer timer) {
+ this(name, twidth, theight, 8, 0, timer);
+ }
+
+ public QuadImposterNode(final String name, final int twidth, final int theight, final int depth, final int samples,
+ final Timer timer) {
+ super(name);
+
+ _twidth = twidth;
+ _theight = theight;
+ _depth = depth;
+ _samples = samples;
+
+ _timer = timer;
+
+ _texture = new Texture2D();
+
+ _imposterQuad = new Quad("ImposterQuad");
+ _imposterQuad.resize(1, 1);
+ _imposterQuad.setModelBound(new BoundingBox());
+ _imposterQuad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ _imposterQuad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ super.attachChild(_imposterQuad);
+
+ getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+
+ _targetScene = new Node();
+ super.attachChild(_targetScene);
+
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i] = new Vector3();
+ }
+
+ if (timer != null) {
+ _redrawRate = _elapsed = 0.05; // 20x per sec
+ } else {
+ setCameraAngleThreshold(10.0);
+ setCameraDistanceThreshold(0.2);
+ }
+ _haveDrawn = false;
+ }
+
+ @Override
+ public int attachChild(final Spatial child) {
+ return _targetScene.attachChild(child);
+ }
+
+ @Override
+ public int attachChildAt(final Spatial child, final int index) {
+ return _targetScene.attachChildAt(child, index);
+ }
+
+ @Override
+ public void detachAllChildren() {
+ _targetScene.detachAllChildren();
+ }
+
+ @Override
+ public int detachChild(final Spatial child) {
+ return _targetScene.detachChild(child);
+ }
+
+ @Override
+ public Spatial detachChildAt(final int index) {
+ return _targetScene.detachChildAt(index);
+ }
+
+ @Override
+ public int detachChildNamed(final String childName) {
+ return _targetScene.detachChildNamed(childName);
+ }
+
+ private void init(final Renderer renderer) {
+ _tRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(_twidth, _theight, _depth, _samples,
+ renderer, ContextManager.getCurrentContext().getCapabilities());
+
+ _tRenderer.setBackgroundColor(new ColorRGBA(0, 0, 0, 0));
+ resetTexture();
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ if (_timer != null && _redrawRate > 0) {
+ _elapsed += _timer.getTimePerFrame();
+ }
+
+ if (_tRenderer == null) {
+ init(r);
+ }
+ if (_cam == null) {
+ _cam = Camera.getCurrentCamera();
+
+ _tRenderer.getCamera().setFrustum(_cam.getFrustumNear(), _cam.getFrustumFar(), _cam.getFrustumLeft(),
+ _cam.getFrustumRight(), _cam.getFrustumTop(), _cam.getFrustumBottom());
+ _tRenderer.getCamera().setFrame(_cam.getLocation(), _cam.getLeft(), _cam.getUp(), _cam.getDirection());
+ }
+
+ if (_doUpdate && (!_haveDrawn || shouldDoUpdate(_cam)) && _targetScene.getWorldBound() != null) {
+ final BoundingVolume b = _targetScene.getWorldBound();
+ _center.set(b.getCenter());
+
+ updateCameraLookat();
+
+ calculateImposter();
+
+ updateCameraLookat();
+ updateCameraFrustum();
+
+ renderImposter();
+
+ _haveDrawn = true;
+ }
+
+ _imposterQuad.draw(r);
+ }
+
+ @Override
+ protected void updateChildren(final double time) {
+ _imposterQuad.updateGeometricState(time, false);
+ if (_doUpdate && (!_haveDrawn || shouldDoUpdate(_cam))) {
+ _targetScene.updateGeometricState(time, false);
+ }
+ }
+
+ private void calculateImposter() {
+ final BoundingVolume worldBound = _targetScene.getWorldBound();
+ _center.set(worldBound.getCenter());
+
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i].set(_center);
+ }
+
+ if (worldBound instanceof BoundingBox) {
+ final BoundingBox bbox = (BoundingBox) worldBound;
+ bbox.getExtent(_extents);
+ } else if (worldBound instanceof BoundingSphere) {
+ final BoundingSphere bsphere = (BoundingSphere) worldBound;
+ _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius());
+ }
+
+ _corners[0].addLocal(_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[1].addLocal(-_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[2].addLocal(_extents.getX(), -_extents.getY(), -_extents.getZ());
+ _corners[3].addLocal(-_extents.getX(), -_extents.getY(), -_extents.getZ());
+ _corners[4].addLocal(_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[5].addLocal(-_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[6].addLocal(_extents.getX(), -_extents.getY(), _extents.getZ());
+ _corners[7].addLocal(-_extents.getX(), -_extents.getY(), _extents.getZ());
+
+ for (int i = 0; i < _corners.length; i++) {
+ _tRenderer.getCamera().getScreenCoordinates(_corners[i], _corners[i]);
+ }
+
+ _minScreenPos.set(Double.MAX_VALUE, Double.MAX_VALUE);
+ _maxScreenPos.set(-Double.MAX_VALUE, -Double.MAX_VALUE);
+ _minZ = Double.MAX_VALUE;
+ for (int i = 0; i < _corners.length; i++) {
+ _minScreenPos.setX(Math.min(_corners[i].getX(), _minScreenPos.getX()));
+ _minScreenPos.setY(Math.min(_corners[i].getY(), _minScreenPos.getY()));
+
+ _maxScreenPos.setX(Math.max(_corners[i].getX(), _maxScreenPos.getX()));
+ _maxScreenPos.setY(Math.max(_corners[i].getY(), _maxScreenPos.getY()));
+
+ _minZ = Math.min(_corners[i].getZ(), _minZ);
+ }
+ _maxMinScreenPos.set(_maxScreenPos.getX(), _minScreenPos.getY());
+ _minMaxScreenPos.set(_minScreenPos.getX(), _maxScreenPos.getY());
+
+ _tRenderer.getCamera().getWorldCoordinates(_maxScreenPos, _minZ, _corners[0]);
+ _tRenderer.getCamera().getWorldCoordinates(_maxMinScreenPos, _minZ, _corners[1]);
+ _tRenderer.getCamera().getWorldCoordinates(_minScreenPos, _minZ, _corners[2]);
+ _tRenderer.getCamera().getWorldCoordinates(_minMaxScreenPos, _minZ, _corners[3]);
+ _center.set(_corners[0]).addLocal(_corners[1]).addLocal(_corners[2]).addLocal(_corners[3]).multiplyLocal(0.25);
+
+ _lastCamDir.set(_center).subtractLocal(_tRenderer.getCamera().getLocation());
+ _lastCamDist = _nearPlane = _lastCamDir.length();
+ _farPlane = _nearPlane + _extents.length() * 2.0;
+ _lastCamDir.normalizeLocal();
+
+ final FloatBuffer vertexBuffer = _imposterQuad.getMeshData().getVertexBuffer();
+ BufferUtils.setInBuffer(_corners[0], vertexBuffer, 3);
+ BufferUtils.setInBuffer(_corners[1], vertexBuffer, 2);
+ BufferUtils.setInBuffer(_corners[2], vertexBuffer, 1);
+ BufferUtils.setInBuffer(_corners[3], vertexBuffer, 0);
+
+ _imposterQuad.updateModelBound();
+ }
+
+ private void updateCameraLookat() {
+ _tRenderer.getCamera().setLocation(_cam.getLocation());
+ _tRenderer.getCamera().lookAt(_center, _worldUpVector);
+ }
+
+ private void updateCameraFrustum() {
+ final double width = _corners[2].subtractLocal(_corners[1]).length() / 2.0;
+ final double height = _corners[1].subtractLocal(_corners[0]).length() / 2.0;
+
+ _tRenderer.getCamera().setFrustum(_nearPlane, _farPlane, -width, width, height, -height);
+ }
+
+ private boolean shouldDoUpdate(final Camera cam) {
+ if (_redrawRate > 0 && _elapsed >= _redrawRate) {
+ _elapsed = _elapsed % _redrawRate;
+ return true;
+ }
+
+ if (_cameraAngleThreshold > 0) {
+ _tempVec.set(_center).subtractLocal(cam.getLocation());
+
+ final double currentDist = _tempVec.length();
+ if (_lastCamDist != 0 && Math.abs(currentDist - _lastCamDist) / _lastCamDist > _cameraDistanceThreshold) {
+ return true;
+ }
+
+ _tempVec.normalizeLocal();
+ final double angle = _tempVec.smallestAngleBetween(_lastCamDir);
+ if (angle > _cameraAngleThreshold) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setRedrawRate(final double rate) {
+ _redrawRate = _elapsed = rate;
+ }
+
+ public double getCameraDistanceThreshold() {
+ return _cameraDistanceThreshold;
+ }
+
+ public void setCameraDistanceThreshold(final double cameraDistanceThreshold) {
+ _cameraDistanceThreshold = cameraDistanceThreshold;
+ }
+
+ public double getCameraAngleThreshold() {
+ return _cameraAngleThreshold;
+ }
+
+ public void setCameraAngleThreshold(final double cameraAngleThreshold) {
+ _cameraAngleThreshold = cameraAngleThreshold;
+ }
+
+ public void resetTexture() {
+ _texture.setWrap(Texture.WrapMode.EdgeClamp);
+ _texture.setMinificationFilter(Texture.MinificationFilter.BilinearNoMipMaps);
+ _texture.setMagnificationFilter(Texture.MagnificationFilter.Bilinear);
+ _texture.setTextureStoreFormat(TextureStoreFormat.RGBA8);
+ _tRenderer.setupTexture(_texture);
+ final TextureState ts = new TextureState();
+ ts.setEnabled(true);
+ ts.setTexture(_texture, 0);
+ _imposterQuad.setRenderState(ts);
+
+ // Add a blending mode... This is so the background of the texture is
+ // transparent.
+ final BlendState as1 = new BlendState();
+ as1.setBlendEnabled(true);
+ as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ as1.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ as1.setTestEnabled(true);
+ as1.setTestFunction(BlendState.TestFunction.GreaterThan);
+ as1.setEnabled(true);
+ _imposterQuad.setRenderState(as1);
+ }
+
+ public void renderImposter() {
+ _tRenderer.render(_targetScene, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ }
+
+ public Vector3 getWorldUpVector() {
+ return _worldUpVector;
+ }
+
+ public void setWorldUpVector(final Vector3 worldUpVector) {
+ _worldUpVector = worldUpVector;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_texture, "texture", null);
+ capsule.write(_targetScene, "targetScene", null);
+ capsule.write(_imposterQuad, "standIn", new Quad("ImposterQuad"));
+ capsule.write(_redrawRate, "redrawRate", 0.05f);
+ capsule.write(_cameraAngleThreshold, "cameraThreshold", 0);
+ capsule.write(_worldUpVector, "worldUpVector", new Vector3(Vector3.UNIT_Y));
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _texture = (Texture2D) capsule.readSavable("texture", null);
+ _targetScene = (Node) capsule.readSavable("targetScene", null);
+ _imposterQuad = (Quad) capsule.readSavable("standIn", new Quad("ImposterQuad"));
+ _redrawRate = capsule.readFloat("redrawRate", 0.05f);
+ _cameraAngleThreshold = capsule.readFloat("cameraThreshold", 0);
+ _worldUpVector = (Vector3) capsule.readSavable("worldUpVector", new Vector3(Vector3.UNIT_Y));
+ }
+
+ public Texture getTexture() {
+ return _texture;
+ }
+
+ public void setDoUpdate(final boolean doUpdate) {
+ _doUpdate = doUpdate;
+ }
+
+ public boolean isDoUpdate() {
+ return _doUpdate;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java
new file mode 100644
index 0000000..c396e0b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/Skybox.java
@@ -0,0 +1,257 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.io.IOException;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.export.CapsuleUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * A Box made of textured quads that simulate having a sky, horizon and so forth around your scene. Either attach to a
+ * camera node or update on each frame to set this skybox at the camera's position.
+ */
+public class Skybox extends Node {
+
+ public enum Face {
+ /** The +Z side of the skybox. */
+ North,
+ /** The -Z side of the skybox. */
+ South,
+ /** The -X side of the skybox. */
+ East,
+ /** The +X side of the skybox. */
+ West,
+ /** The +Y side of the skybox. */
+ Up,
+ /** The -Y side of the skybox. */
+ Down;
+ }
+
+ private float _xExtent;
+ private float _yExtent;
+ private float _zExtent;
+
+ private Quad[] _skyboxQuads;
+
+ public Skybox() {}
+
+ /**
+ * Creates a new skybox. The size of the skybox and name is specified here. By default, no textures are set.
+ *
+ * @param name
+ * The name of the skybox.
+ * @param xExtent
+ * The x size of the skybox in both directions from the center.
+ * @param yExtent
+ * The y size of the skybox in both directions from the center.
+ * @param zExtent
+ * The z size of the skybox in both directions from the center.
+ */
+ public Skybox(final String name, final float xExtent, final float yExtent, final float zExtent) {
+ super(name);
+
+ _xExtent = xExtent;
+ _yExtent = yExtent;
+ _zExtent = zExtent;
+
+ initialize();
+ }
+
+ /**
+ * Set the texture to be displayed on the given face of the skybox. Replaces any existing texture on that face.
+ *
+ * @param face
+ * the face to set
+ * @param texture
+ * The texture for that side to assume.
+ * @throws IllegalArgumentException
+ * if face is null.
+ */
+ public void setTexture(final Face face, final Texture texture) {
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+
+ _skyboxQuads[face.ordinal()].clearRenderState(RenderState.StateType.Texture);
+ setTexture(face, texture, 0);
+ }
+
+ /**
+ * Set the texture to be displayed on the given side of the skybox. Only replaces the texture at the index specified
+ * by textureUnit.
+ *
+ * @param face
+ * the face to set
+ * @param texture
+ * The texture for that side to assume.
+ * @param textureUnit
+ * The texture unite of the given side's TextureState the texture will assume.
+ */
+ public void setTexture(final Face face, final Texture texture, final int textureUnit) {
+ // Validate
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+
+ TextureState ts = (TextureState) _skyboxQuads[face.ordinal()]
+ .getLocalRenderState(RenderState.StateType.Texture);
+ if (ts == null) {
+ ts = new TextureState();
+ }
+
+ // Initialize the texture state
+ ts.setTexture(texture, textureUnit);
+ ts.setEnabled(true);
+
+ texture.setWrap(WrapMode.EdgeClamp);
+
+ // Set the texture to the quad
+ _skyboxQuads[face.ordinal()].setRenderState(ts);
+
+ return;
+ }
+
+ public Texture getTexture(final Face face) {
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+ return ((TextureState) _skyboxQuads[face.ordinal()].getLocalRenderState(RenderState.StateType.Texture))
+ .getTexture();
+ }
+
+ public void initialize() {
+
+ // Skybox consists of 6 sides
+ _skyboxQuads = new Quad[6];
+
+ // Create each of the quads
+ _skyboxQuads[Face.North.ordinal()] = new Quad("north", _xExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.North.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(180), 0));
+ _skyboxQuads[Face.North.ordinal()].setTranslation(new Vector3(0, 0, _zExtent));
+ _skyboxQuads[Face.South.ordinal()] = new Quad("south", _xExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.South.ordinal()].setTranslation(new Vector3(0, 0, -_zExtent));
+ _skyboxQuads[Face.East.ordinal()] = new Quad("east", _zExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.East.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(90), 0));
+ _skyboxQuads[Face.East.ordinal()].setTranslation(new Vector3(-_xExtent, 0, 0));
+ _skyboxQuads[Face.West.ordinal()] = new Quad("west", _zExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.West.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(270), 0));
+ _skyboxQuads[Face.West.ordinal()].setTranslation(new Vector3(_xExtent, 0, 0));
+ _skyboxQuads[Face.Up.ordinal()] = new Quad("up", _xExtent * 2, _zExtent * 2);
+ _skyboxQuads[Face.Up.ordinal()]
+ .setRotation(new Matrix3().fromAngles(Math.toRadians(90), Math.toRadians(270), 0));
+ _skyboxQuads[Face.Up.ordinal()].setTranslation(new Vector3(0, _yExtent, 0));
+ _skyboxQuads[Face.Down.ordinal()] = new Quad("down", _xExtent * 2, _zExtent * 2);
+ _skyboxQuads[Face.Down.ordinal()].setRotation(new Matrix3().fromAngles(Math.toRadians(270),
+ Math.toRadians(270), 0));
+ _skyboxQuads[Face.Down.ordinal()].setTranslation(new Vector3(0, -_yExtent, 0));
+
+ // We don't want the light to effect our skybox
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ final ZBufferState zbuff = new ZBufferState();
+ zbuff.setEnabled(false);
+ setRenderState(zbuff);
+
+ final FogState fs = new FogState();
+ fs.setEnabled(false);
+ setRenderState(fs);
+
+ // We don't want it making our skybox disapear, so force view
+ getSceneHints().setCullHint(CullHint.Never);
+
+ for (int i = 0; i < 6; i++) {
+ // Make sure texture is only what is set.
+ _skyboxQuads[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ // Make sure no lighting on the skybox
+ _skyboxQuads[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ // Make sure the quad is viewable
+ _skyboxQuads[i].getSceneHints().setCullHint(CullHint.Never);
+
+ // Add to the prebucket.
+ _skyboxQuads[i].getSceneHints().setRenderBucketType(RenderBucketType.PreBucket);
+
+ // And attach the skybox as a child
+ attachChild(_skyboxQuads[i]);
+ }
+ }
+
+ /**
+ * Retrieve the quad indicated by the given side.
+ *
+ * @param face
+ * One of Skybox.Face.North, Skybox.Face.South, and so on...
+ * @return The Quad that makes up that side of the Skybox.
+ */
+ public Quad getFace(final Face face) {
+ return _skyboxQuads[face.ordinal()];
+ }
+
+ public void preloadTexture(final Face face, final Renderer r) {
+ final TextureState ts = (TextureState) _skyboxQuads[face.ordinal()]
+ .getLocalRenderState(RenderState.StateType.Texture);
+ if (ts != null) {
+ r.applyState(StateType.Texture, ts);
+ }
+ }
+
+ /**
+ * Force all of the textures to load. This prevents pauses later during the application as you pan around the world.
+ */
+ public void preloadTextures(final Renderer r) {
+ for (int x = 0; x < 6; x++) {
+ final TextureState ts = (TextureState) _skyboxQuads[x].getLocalRenderState(RenderState.StateType.Texture);
+ if (ts != null) {
+ r.applyState(StateType.Texture, ts);
+ }
+ }
+
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xExtent, "xExtent", 0);
+ capsule.write(_yExtent, "yExtent", 0);
+ capsule.write(_zExtent, "zExtent", 0);
+ capsule.write(_skyboxQuads, "skyboxQuads", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xExtent = capsule.readFloat("xExtent", 0);
+ _yExtent = capsule.readFloat("yExtent", 0);
+ _zExtent = capsule.readFloat("zExtent", 0);
+ _skyboxQuads = CapsuleUtils.asArray(capsule.readSavableArray("skyboxQuads", null), Quad.class);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java
new file mode 100644
index 0000000..532dace
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/extension/SwitchNode.java
@@ -0,0 +1,142 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.extension;
+
+import java.util.BitSet;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+public class SwitchNode extends Node {
+
+ protected BitSet _childMask = new BitSet();
+
+ public SwitchNode() {
+ this("SwitchNode");
+ }
+
+ public SwitchNode(final String name) {
+ super(name);
+
+ _childMask.set(0);
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ if (_children == null) {
+ return;
+ }
+ for (int i = 0, max = Math.min(_childMask.length(), _children.size()); i < max; i++) {
+ if (_childMask.get(i)) {
+ final Spatial child = _children.get(i);
+ if (child != null) {
+ child.onDraw(r);
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void updateChildren(final double time) {
+ if (_children == null) {
+ return;
+ }
+ for (int i = 0, max = Math.min(_childMask.length(), _children.size()); i < max; i++) {
+ if (_childMask.get(i)) {
+ final Spatial child = _children.get(i);
+ if (child != null) {
+ child.updateGeometricState(time, false);
+ }
+ }
+ }
+ }
+
+ public void setAllNonVisible() {
+ _childMask.clear();
+ }
+
+ public void setAllVisible() {
+ _childMask.set(0, getNumberOfChildren());
+ }
+
+ public void flipAllVisible() {
+ _childMask.flip(0, getNumberOfChildren());
+ }
+
+ public boolean getVisible(final int bitIndex) {
+ return _childMask.get(bitIndex);
+ }
+
+ public BitSet getVisible() {
+ return _childMask;
+ }
+
+ public void setVisible(final BitSet set) {
+ _childMask = set;
+ }
+
+ public void setVisible(final int bitIndex, final boolean value) {
+ _childMask.set(bitIndex, value);
+ }
+
+ public void setVisible(final int fromIndex, final int toIndex, final boolean value) {
+ _childMask.set(fromIndex, toIndex, value);
+ }
+
+ public void setSingleVisible(final int bitIndex) {
+ _childMask.clear();
+ _childMask.set(bitIndex);
+ }
+
+ public int getNextNonVisible(final int fromIndex) {
+ return _childMask.nextClearBit(fromIndex);
+ }
+
+ public int getNextVisible(final int fromIndex) {
+ return _childMask.nextSetBit(fromIndex);
+ }
+
+ public void shiftVisibleRight() {
+ final int nrChildren = getNumberOfChildren();
+ if (nrChildren == 0) {
+ return;
+ }
+
+ final boolean lastVal = _childMask.get(nrChildren - 1);
+ for (int i = nrChildren - 1; i > 0; i--) {
+ _childMask.set(i, _childMask.get(i - 1));
+ }
+ _childMask.set(0, lastVal);
+ }
+
+ public void shiftVisibleLeft() {
+ final int nrChildren = getNumberOfChildren();
+ if (nrChildren == 0) {
+ return;
+ }
+
+ final boolean firstVal = _childMask.get(0);
+ for (int i = 0; i < nrChildren - 1; i++) {
+ _childMask.set(i, _childMask.get(i + 1));
+ }
+ _childMask.set(getNumberOfChildren() - 1, firstVal);
+ }
+
+ public void flipVisible(final int fromIndex, final int toIndex) {
+ _childMask.flip(fromIndex, toIndex);
+ }
+
+ public void flipVisible(final int bitIndex) {
+ _childMask.flip(bitIndex);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java
new file mode 100644
index 0000000..74a9399
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/CullHint.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+/**
+ * Describes how a scene object interacts with Ardor3D's frustum culling.
+ */
+public enum CullHint {
+
+ /**
+ * Do whatever our parent does. If no parent, we'll default to dynamic.
+ */
+ Inherit,
+
+ /**
+ * Do not draw if we are not at least partially within the view frustum of the renderer's camera.
+ */
+ Dynamic,
+
+ /**
+ * Always cull this from view.
+ */
+ Always,
+
+ /**
+ * Never cull this from view. Note that we will still get culled if our parent is culled.
+ */
+ Never;
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java
new file mode 100644
index 0000000..ba4673d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/DataMode.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+/**
+ * Enum used to describe how we prefer data to be sent to the card.
+ */
+public enum DataMode {
+ /**
+ * Use our parent's DataMode. If we do not have a parent, Arrays will be used.
+ */
+ Inherit,
+
+ /**
+ * Send each data buffer to the card using vertex arrays.
+ */
+ Arrays,
+
+ /**
+ * Send each data buffer to the card using vertex buffer objects. This is usually faster than Arrays, but may not be
+ * supported on older cards. If not supported, Arrays is used by the Renderer.
+ */
+ VBO,
+
+ /**
+ * Send each data buffer to the card using a combined vertex buffer object(s). Usually this is done by combining all
+ * FloatBufferData buffers in one buffer sequentially, but if their types are different, multiple buffers might be
+ * used instead. This is usually a bit faster than just VBO. If not supported, Arrays is used by the Renderer.
+ */
+ VBOInterleaved;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java
new file mode 100644
index 0000000..0df2795
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/Hintable.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+/**
+ * Interface used for describing objects that deal with the SceneHints class. This interface allows non-specific access
+ * to object hierarchy.
+ */
+public interface Hintable {
+
+ /**
+ * @return a hierarchical parent to be used for determining inheritance of certain SceneHint fields.
+ */
+ Hintable getParentHintable();
+
+ /**
+ * @return the SceneHints object used by this Hintable.
+ */
+ SceneHints getSceneHints();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java
new file mode 100644
index 0000000..a8e4f18
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/LightCombineMode.java
@@ -0,0 +1,34 @@
+
+package com.ardor3d.scenegraph.hint;
+
+/**
+ * Describes how to combine lights from ancestor LightStates.
+ */
+public enum LightCombineMode {
+ /** When updating render states, turn off lighting for this spatial. */
+ Off,
+
+ /**
+ * Combine lights starting from the root node and working towards the given Spatial. Ignore disabled states. Stop
+ * combining when lights == MAX_LIGHTS_ALLOWED
+ */
+ CombineFirst,
+
+ /**
+ * Combine lights starting from the given Spatial and working up towards the root. Ignore disabled states. Stop
+ * combining when lights == MAX_LIGHTS_ALLOWED
+ */
+ CombineClosest,
+
+ /**
+ * Similar to CombineClosest, but if a disabled state is encountered, it will stop combining at that point. Stop
+ * combining when lights == MAX_LIGHTS_ALLOWED
+ */
+ CombineClosestEnabled,
+
+ /** Inherit mode from parent. */
+ Inherit,
+
+ /** Do not combine lights, just use the most recent light state. */
+ Replace;
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java
new file mode 100644
index 0000000..a6873c0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/NormalsMode.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+public enum NormalsMode {
+
+ /**
+ * Do whatever our parent does. If no parent, we'll default to NormalizeIfScaled.
+ */
+ Inherit,
+
+ /**
+ * Send through the normals currently set as-is.
+ */
+ UseProvided,
+
+ /**
+ * Tell the card to normalize any normals data we might give it.
+ */
+ AlwaysNormalize,
+
+ /**
+ * If a scale other than 1,1,1 is being used then tell the card to normalize any normals data we might give it.
+ */
+ NormalizeIfScaled,
+
+ /**
+ * Do not send normal data to the card, even if we have some.
+ */
+ Off;
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java
new file mode 100644
index 0000000..54e4098
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/PickingHint.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+public enum PickingHint {
+ /**
+ * Scene object can be included in results from pick operations.
+ */
+ Pickable,
+
+ /**
+ * Scene object can be included in results from collision checks.
+ */
+ Collidable
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java
new file mode 100644
index 0000000..36a239c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/SceneHints.java
@@ -0,0 +1,493 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+import java.io.IOException;
+import java.util.EnumSet;
+
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * SceneHints encapsulates various rendering and interaction preferences for a scene object.
+ */
+public class SceneHints implements Savable {
+
+ /**
+ * How we want the data for a subset of the scenegraph to be sent to the card.
+ */
+ protected DataMode _dataMode = DataMode.Inherit;
+
+ /**
+ * A flag indicating how normals should be treated by the renderer.
+ */
+ protected NormalsMode _normalsMode = NormalsMode.Inherit;
+
+ /**
+ * A flag indicating if scene culling should be done on this object by inheritance, dynamically, never, or always.
+ */
+ protected CullHint _cullHint = CullHint.Inherit;
+
+ /**
+ * Flag signaling how lights are combined for this node. By default set to INHERIT.
+ */
+ protected LightCombineMode _lightCombineMode = LightCombineMode.Inherit;
+
+ /**
+ * Flag signaling how textures are combined for this node. By default set to INHERIT.
+ */
+ protected TextureCombineMode _textureCombineMode = TextureCombineMode.Inherit;
+
+ /**
+ * RenderBucketType for this spatial
+ */
+ protected RenderBucketType _renderBucketType = RenderBucketType.Inherit;
+
+ /**
+ * Draw order to use when drawing in ortho mode.
+ */
+ protected int _orthoOrder = 0;
+
+ /**
+ * Field for setting whether the Spatial is enabled for Picking or Collision. Both are enabled by default.
+ */
+ protected final EnumSet<PickingHint> _pickingHints = EnumSet.allOf(PickingHint.class);
+
+ /**
+ * The source for this SceneHints.
+ */
+ private final Hintable _source;
+
+ /**
+ * Type of transparency to do.
+ */
+ private TransparencyType _transpType = TransparencyType.Inherit;
+
+ /**
+ * Hint for shadow implementations
+ */
+ protected boolean _castsShadows = true;
+
+ public SceneHints(final Hintable source) {
+ _source = source;
+ }
+
+ public void set(final SceneHints sceneHints) {
+ _dataMode = sceneHints._dataMode;
+ _normalsMode = sceneHints._normalsMode;
+ _cullHint = sceneHints._cullHint;
+ _lightCombineMode = sceneHints._lightCombineMode;
+ _textureCombineMode = sceneHints._textureCombineMode;
+ _renderBucketType = sceneHints._renderBucketType;
+ _orthoOrder = sceneHints._orthoOrder;
+ _pickingHints.clear();
+ _pickingHints.addAll(sceneHints._pickingHints);
+ _castsShadows = sceneHints._castsShadows;
+ _transpType = sceneHints._transpType;
+ }
+
+ /**
+ * Returns the normals mode. If the mode is set to inherit, then we get its normals mode from the given source's
+ * hintable parent. If no parent, we'll default to NormalizeIfScaled.
+ *
+ * @return The normals mode to use.
+ */
+ public DataMode getDataMode() {
+ if (_dataMode != DataMode.Inherit) {
+ return _dataMode;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getDataMode();
+ }
+
+ return DataMode.Arrays;
+ }
+
+ /**
+ * @return the exact data mode set.
+ */
+ public DataMode getLocalDataMode() {
+ return _dataMode;
+ }
+
+ /**
+ * @param type
+ * the new data mode to set on this SceneHints
+ */
+ public void setDataMode(final DataMode type) {
+ _dataMode = type;
+ }
+
+ /**
+ * Returns the normals mode. If the mode is set to inherit, then we get its normals mode from the given source's
+ * hintable parent. If no parent, we'll default to NormalizeIfScaled.
+ *
+ * @return The normals mode to use.
+ */
+ public NormalsMode getNormalsMode() {
+ if (_normalsMode != NormalsMode.Inherit) {
+ return _normalsMode;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getNormalsMode();
+ }
+
+ return NormalsMode.NormalizeIfScaled;
+ }
+
+ /**
+ * @return the exact normals mode set.
+ */
+ public NormalsMode getLocalNormalsMode() {
+ return _normalsMode;
+ }
+
+ /**
+ * @param mode
+ * the new normals mode to set on this SceneHints
+ */
+ public void setNormalsMode(final NormalsMode mode) {
+ _normalsMode = mode;
+ }
+
+ /**
+ * @see #setCullHint(CullHint)
+ * @return the cull mode of this spatial, or if set to INHERIT, the cullmode of its parent.
+ */
+ public CullHint getCullHint() {
+ if (_cullHint != CullHint.Inherit) {
+ return _cullHint;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getCullHint();
+ }
+
+ return CullHint.Dynamic;
+ }
+
+ /**
+ * @return the cullmode set on this Spatial
+ */
+ public CullHint getLocalCullHint() {
+ return _cullHint;
+ }
+
+ /**
+ * <code>setCullHint</code> sets how scene culling should work on this spatial during drawing. CullHint.Dynamic:
+ * Determine via the defined Camera planes whether or not this Spatial should be culled. CullHint.Always: Always
+ * throw away this object and any children during draw commands. CullHint.Never: Never throw away this object
+ * (always draw it) CullHint.Inherit: Look for a non-inherit parent and use its cull mode. NOTE: You must set this
+ * AFTER attaching to a parent or it will be reset with the parent's cullMode value.
+ *
+ * @param hint
+ * one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or CullHint.Never
+ */
+ public void setCullHint(final CullHint hint) {
+ _cullHint = hint;
+ }
+
+ /**
+ * Returns this spatial's texture combine mode. If the mode is set to inherit, then the spatial gets its combine
+ * mode from its parent.
+ *
+ * @return The spatial's texture current combine mode.
+ */
+ public TextureCombineMode getTextureCombineMode() {
+ if (_textureCombineMode != TextureCombineMode.Inherit) {
+ return _textureCombineMode;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getTextureCombineMode();
+ }
+
+ return TextureCombineMode.CombineClosest;
+ }
+
+ /**
+ * @return the textureCombineMode set on this Spatial
+ */
+ public TextureCombineMode getLocalTextureCombineMode() {
+ return _textureCombineMode;
+ }
+
+ /**
+ * Sets how textures from parents should be combined for this Spatial.
+ *
+ * @param mode
+ * The new texture combine mode for this spatial.
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ public void setTextureCombineMode(final TextureCombineMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _textureCombineMode = mode;
+ }
+
+ /**
+ * Returns this spatial's light combine mode. If the mode is set to inherit, then the spatial gets its combine mode
+ * from its parent.
+ *
+ * @return The spatial's light current combine mode.
+ */
+ public LightCombineMode getLightCombineMode() {
+ if (_lightCombineMode != LightCombineMode.Inherit) {
+ return _lightCombineMode;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getLightCombineMode();
+ }
+
+ return LightCombineMode.CombineFirst;
+ }
+
+ /**
+ * @return the lightCombineMode set on this Spatial
+ */
+ public LightCombineMode getLocalLightCombineMode() {
+ return _lightCombineMode;
+ }
+
+ /**
+ * Sets how lights from parents should be combined for this spatial.
+ *
+ * @param mode
+ * The light combine mode for this spatial
+ * @throws IllegalArgumentException
+ * if mode is null
+ */
+ public void setLightCombineMode(final LightCombineMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ _lightCombineMode = mode;
+ }
+
+ /**
+ * Get the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered
+ * in.
+ * <p>
+ * This method returns the effective bucket type that is used for rendering. If the type is set to
+ * {@link com.ardor3d.renderer.queue.RenderBucketType#Inherit Inherit} then the bucket type from the spatial's
+ * parent will be used during rendering. If no parent, then
+ * {@link com.ardor3d.renderer.queue.RenderBucketType#Opaque Opaque} is used.
+ *
+ * @return the render queue mode used for this spatial.
+ * @see com.ardor3d.renderer.queue.RenderBucketType
+ */
+ public RenderBucketType getRenderBucketType() {
+ if (_renderBucketType != RenderBucketType.Inherit) {
+ return _renderBucketType;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getRenderBucketType();
+ }
+
+ return RenderBucketType.Opaque;
+ }
+
+ /**
+ * Get the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered
+ * in.
+ * <p>
+ * This method returns the actual bucket type that is set on this spatial, if the type is set to
+ * {@link com.ardor3d.renderer.queue.RenderBucketType#Inherit Inherit} then the bucket type from the spatial's
+ * parent will be used during rendering. If no parent, then
+ * {@link com.ardor3d.renderer.queue.RenderBucketType#Opaque Opaque} is used.
+ *
+ * @return the render queue mode set on this spatial.
+ * @see com.ardor3d.renderer.queue.RenderBucketType
+ */
+ public RenderBucketType getLocalRenderBucketType() {
+ return _renderBucketType;
+ }
+
+ /**
+ * Set the render bucket type used to determine which "phase" of the rendering process this Spatial will rendered
+ * in.
+ *
+ * @param renderBucketType
+ * the render bucket type to use for this spatial.
+ * @see com.ardor3d.renderer.queue.RenderBucketType
+ */
+ public void setRenderBucketType(final RenderBucketType renderBucketType) {
+ _renderBucketType = renderBucketType;
+ }
+
+ /**
+ * Returns whether a certain pick hint is set on this spatial.
+ *
+ * @param pickingHint
+ * Pick hint to test for
+ * @return Enabled or disabled
+ */
+ public boolean isPickingHintEnabled(final PickingHint pickingHint) {
+ return _pickingHints.contains(pickingHint);
+ }
+
+ /**
+ * Enable or disable a picking hint for this Spatial
+ *
+ * @param pickingHint
+ * PickingHint to set. Pickable or Collidable
+ * @param enabled
+ * Enable or disable
+ */
+ public void setPickingHint(final PickingHint pickingHint, final boolean enabled) {
+ if (enabled) {
+ _pickingHints.add(pickingHint);
+ } else {
+ _pickingHints.remove(pickingHint);
+ }
+ }
+
+ /**
+ * Enable or disable all picking hints for this Spatial
+ *
+ * @param enabled
+ * Enable or disable
+ */
+ public void setAllPickingHints(final boolean enabled) {
+ if (enabled) {
+ _pickingHints.addAll(EnumSet.allOf(PickingHint.class));
+ } else {
+ _pickingHints.clear();
+ }
+ }
+
+ /**
+ * @return a number representing z ordering when used in the Ortho bucket. Higher values are
+ * "further into the screen" and lower values are "closer". Or in other words, if you draw two quads, one
+ * with a zorder of 1 and the other with a zorder of 2, the quad with zorder of 2 will be "under" the other
+ * quad.
+ */
+ public int getOrthoOrder() {
+ return _orthoOrder;
+ }
+
+ /**
+ * @param orthoOrder
+ */
+ public void setOrthoOrder(final int orthoOrder) {
+ _orthoOrder = orthoOrder;
+ }
+
+ /**
+ * Returns the transparency rendering type. If the mode is set to inherit, then we get its type from the given
+ * source's hintable parent. If no parent, we'll default to OnePass.
+ *
+ * @return The transparency rendering type to use.
+ */
+ public TransparencyType getTransparencyType() {
+ if (_transpType != TransparencyType.Inherit) {
+ return _transpType;
+ }
+
+ final Hintable parent = _source.getParentHintable();
+ if (parent != null) {
+ return parent.getSceneHints().getTransparencyType();
+ }
+
+ return TransparencyType.OnePass;
+ }
+
+ /**
+ * @return the exact transparency rendering type set.
+ */
+ public TransparencyType getLocalTransparencyType() {
+ return _transpType;
+ }
+
+ /**
+ * @param type
+ * the new transparency rendering type to set on this SceneHints
+ */
+ public void setTransparencyType(final TransparencyType type) {
+ _transpType = type;
+ }
+
+ /**
+ * @return true if this object should cast shadows
+ */
+ public boolean isCastsShadows() {
+ return _castsShadows;
+ }
+
+ /**
+ * @param castsShadows
+ * set if this object should cast shadows
+ */
+ public void setCastsShadows(final boolean castsShadows) {
+ _castsShadows = castsShadows;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<? extends SceneHints> getClassTag() {
+ return this.getClass();
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _orthoOrder = capsule.readInt("orthoOrder", 0);
+ _cullHint = capsule.readEnum("cullMode", CullHint.class, CullHint.Inherit);
+ final String bucketTypeName = capsule.readString("renderBucketType", RenderBucketType.Inherit.name());
+ _renderBucketType = RenderBucketType.getRenderBucketType(bucketTypeName);
+ _lightCombineMode = capsule.readEnum("lightCombineMode", LightCombineMode.class, LightCombineMode.Inherit);
+ _textureCombineMode = capsule.readEnum("textureCombineMode", TextureCombineMode.class,
+ TextureCombineMode.Inherit);
+ _normalsMode = capsule.readEnum("normalsMode", NormalsMode.class, NormalsMode.Inherit);
+ _dataMode = capsule.readEnum("dataMode", DataMode.class, DataMode.Inherit);
+ _transpType = capsule.readEnum("transpType", TransparencyType.class, TransparencyType.Inherit);
+ _castsShadows = capsule.readBoolean("castsShadows", true);
+ final PickingHint[] pickHints = capsule.readEnumArray("pickingHints", PickingHint.class, null);
+ _pickingHints.clear();
+ if (pickHints != null) {
+ for (final PickingHint hint : pickHints) {
+ _pickingHints.add(hint);
+ }
+ } else {
+ // default is all values set.
+ for (final PickingHint hint : PickingHint.values()) {
+ _pickingHints.add(hint);
+ }
+ }
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_orthoOrder, "orthoOrder", 0);
+ capsule.write(_cullHint, "cullMode", CullHint.Inherit);
+ capsule.write(_renderBucketType.name(), "renderBucketType", RenderBucketType.Inherit.name());
+ capsule.write(_lightCombineMode, "lightCombineMode", LightCombineMode.Inherit);
+ capsule.write(_textureCombineMode, "textureCombineMode", TextureCombineMode.Inherit);
+ capsule.write(_normalsMode, "normalsMode", NormalsMode.Inherit);
+ capsule.write(_dataMode, "dataMode", DataMode.Inherit);
+ capsule.write(_pickingHints.toArray(new PickingHint[] {}), "pickingHints");
+ capsule.write(_transpType, "transpType", TransparencyType.Inherit);
+ capsule.write(_castsShadows, "castsShadows", true);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java
new file mode 100644
index 0000000..49e3fa9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TextureCombineMode.java
@@ -0,0 +1,31 @@
+
+package com.ardor3d.scenegraph.hint;
+
+/**
+ * Describes how to combine textures from ancestor TextureStates.
+ */
+public enum TextureCombineMode {
+ /** When updating render states, turn off texturing for this spatial. */
+ Off,
+
+ /**
+ * Combine textures starting from the root node and working towards the given Spatial. Ignore disabled states.
+ */
+ CombineFirst,
+
+ /**
+ * Combine textures starting from the given Spatial and working towards the root. Ignore disabled states. (Default)
+ */
+ CombineClosest,
+
+ /**
+ * Similar to CombineClosest, but if a disabled state is encountered, it will stop combining at that point.
+ */
+ CombineClosestEnabled,
+
+ /** Inherit mode from parent. */
+ Inherit,
+
+ /** Do not combine textures, just use the most recent texture state. */
+ Replace;
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java
new file mode 100644
index 0000000..71520a4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/hint/TransparencyType.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.hint;
+
+public enum TransparencyType {
+
+ /**
+ * Do whatever our parent does. If no parent, we'll default to OnePass.
+ */
+ Inherit,
+
+ /**
+ * Single pass. Best for most circumstances.
+ */
+ OnePass,
+
+ /**
+ * Two passes, one with CullState enforced to Front and another with it enforced to Back. The back face pass will
+ * not write to depth buffer. The front face will use the ZBufferState from the scene or enforced on the context.
+ */
+ TwoPass;
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java
new file mode 100644
index 0000000..e1e8e7d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Arrow.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>Arrow</code> is basically a cylinder with a pyramid on top.
+ */
+public class Arrow extends Node {
+
+ protected double _length = 1;
+ protected double _width = .25;
+
+ protected static final Quaternion rotator = new Quaternion().applyRotationX(MathUtils.HALF_PI);
+
+ public Arrow() {}
+
+ public Arrow(final String name) {
+ super(name);
+ }
+
+ public Arrow(final String name, final double length, final double width) {
+ super(name);
+ _length = length;
+ _width = width;
+
+ buildArrow();
+ }
+
+ public void buildArrow() {
+ // Start with cylinders:
+ final Cylinder base = new Cylinder("base", 4, 16, _width * .75, _length);
+ base.getMeshData().rotatePoints(rotator);
+ base.getMeshData().rotateNormals(rotator);
+ attachChild(base);
+ base.updateModelBound();
+
+ final Pyramid tip = new Pyramid("tip", 2 * _width, _length / 2f);
+ tip.getMeshData().translatePoints(0, _length * .75, 0);
+ attachChild(tip);
+ tip.updateModelBound();
+ }
+
+ public double getLength() {
+ return _length;
+ }
+
+ public void setLength(final double length) {
+ _length = length;
+ }
+
+ public double getWidth() {
+ return _width;
+ }
+
+ public void setWidth(final double width) {
+ _width = width;
+ }
+
+ public void setSolidColor(final ReadOnlyColorRGBA color) {
+ for (int x = 0; x < getNumberOfChildren(); x++) {
+ if (getChild(x) instanceof Mesh) {
+ ((Mesh) getChild(x)).setSolidColor(color);
+ }
+ }
+ }
+
+ public void setDefaultColor(final ReadOnlyColorRGBA color) {
+ for (int x = 0; x < getNumberOfChildren(); x++) {
+ if (getChild(x) instanceof Mesh) {
+ ((Mesh) getChild(x)).setDefaultColor(color);
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_length, "length", 1);
+ capsule.write(_width, "width", .25);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _length = capsule.readDouble("length", 1);
+ _width = capsule.readDouble("width", .25);
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java
new file mode 100644
index 0000000..3d2914b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/AxisRods.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * <code>AxisRods</code> is a convenience shape representing three axes in space.
+ */
+public class AxisRods extends Node {
+
+ protected static final ColorRGBA xAxisColor = new ColorRGBA(1, 0, 0, .4f);
+ protected static final ColorRGBA yAxisColor = new ColorRGBA(0, 1, 0, .25f);
+ protected static final ColorRGBA zAxisColor = new ColorRGBA(0, 0, 1, .4f);
+
+ protected double length;
+ protected double width;
+ protected boolean rightHanded;
+
+ protected Arrow xAxis;
+ protected Arrow yAxis;
+ protected Arrow zAxis;
+
+ public AxisRods() {}
+
+ public AxisRods(final String name) {
+ this(name, true, 1);
+ }
+
+ public AxisRods(final String name, final boolean rightHanded, final double baseScale) {
+ this(name, rightHanded, baseScale, baseScale * 0.125);
+ }
+
+ public AxisRods(final String name, final boolean rightHanded, final double length, final double width) {
+ super(name);
+ this.length = length;
+ this.width = width;
+ this.rightHanded = rightHanded;
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Off);
+
+ buildAxis();
+ }
+
+ protected void buildAxis() {
+ xAxis = new Arrow("_xAxis", length, width);
+ xAxis.setDefaultColor(xAxisColor);
+ xAxis.setRotation(new Matrix3().fromAngles(0, 0, -90 * MathUtils.DEG_TO_RAD));
+ xAxis.setTranslation(length * .5, 0, 0);
+ attachChild(xAxis);
+
+ yAxis = new Arrow("yAxis", length, width);
+ yAxis.setDefaultColor(yAxisColor);
+ yAxis.setTranslation(0, length * .5, 0);
+ attachChild(yAxis);
+
+ zAxis = new Arrow("zAxis", length, width);
+ zAxis.setDefaultColor(zAxisColor);
+ if (rightHanded) {
+ zAxis.setRotation(new Matrix3().fromAngles(90 * MathUtils.DEG_TO_RAD, 0, 0));
+ zAxis.setTranslation(0, 0, length * .5);
+ } else {
+ zAxis.setRotation(new Matrix3().fromAngles(-90 * MathUtils.DEG_TO_RAD, 0, 0));
+ zAxis.setTranslation(0, 0, -length * .5);
+ }
+ attachChild(zAxis);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(length, "length", 1);
+ capsule.write(width, "width", 0.125);
+ capsule.write(rightHanded, "rightHanded", true);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ length = capsule.readDouble("length", 1);
+ width = capsule.readDouble("width", 0.125);
+ rightHanded = capsule.readBoolean("rightHanded", true);
+ buildAxis();
+ }
+
+ public double getLength() {
+ return length;
+ }
+
+ public void setLength(final double length) {
+ this.length = length;
+ }
+
+ public double getWidth() {
+ return width;
+ }
+
+ public void setWidth(final double width) {
+ this.width = width;
+ }
+
+ public Arrow getxAxis() {
+ return xAxis;
+ }
+
+ public Arrow getyAxis() {
+ return yAxis;
+ }
+
+ public Arrow getzAxis() {
+ return zAxis;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java
new file mode 100644
index 0000000..4b4bfdd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Box.java
@@ -0,0 +1,329 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Box</code> is an axis-aligned rectangular prism defined by a center point and x, y, and z extents from that
+ * center (essentially radii.)
+ */
+public class Box extends Mesh {
+
+ private double _xExtent, _yExtent, _zExtent;
+
+ private final Vector3 _center = new Vector3(0, 0, 0);
+
+ /**
+ * Constructs a new 1x1x1 <code>Box</code>.
+ */
+ public Box() {
+ this("unnamed Box");
+ }
+
+ /**
+ * Constructs a new 1x1x1 <code>Box</code> with the given name.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ */
+ public Box(final String name) {
+ super(name);
+ setData(Vector3.ZERO, 0.5, 0.5, 0.5);
+ }
+
+ /**
+ * Constructs a new <code>Box</code> object using the given two points as opposite corners of the box. These two
+ * points may be in any order.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ * @param pntA
+ * the first point
+ * @param pntB
+ * the second point.
+ */
+ public Box(final String name, final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) {
+ super(name);
+ setData(pntA, pntB);
+ }
+
+ /**
+ * Constructs a new <code>Box</code> object using the given center and extents. Since the extents represent the
+ * distance from the center of the box to the edge, the full length of a side is actually 2 * extent.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ * @param center
+ * Center of the box.
+ * @param xExtent
+ * x extent of the box
+ * @param yExtent
+ * y extent of the box
+ * @param zExtent
+ * z extent of the box
+ */
+ public Box(final String name, final ReadOnlyVector3 center, final double xExtent, final double yExtent,
+ final double zExtent) {
+ super(name);
+ setData(center, xExtent, yExtent, zExtent);
+ }
+
+ /**
+ * @return the current center of this box.
+ */
+ public ReadOnlyVector3 getCenter() {
+ return _center;
+ }
+
+ /**
+ * @return the current X extent of this box.
+ */
+ public double getXExtent() {
+ return _xExtent;
+ }
+
+ /**
+ * @return the current Y extent of this box.
+ */
+ public double getYExtent() {
+ return _yExtent;
+ }
+
+ /**
+ * @return the current Z extent of this box.
+ */
+ public double getZExtent() {
+ return _zExtent;
+ }
+
+ /**
+ * Updates the center point and extents of this box to match an axis-aligned box defined by the two given opposite
+ * corners.
+ *
+ * @param pntA
+ * the first point
+ * @param pntB
+ * the second point.
+ */
+ public void setData(final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) {
+ _center.set(pntB).addLocal(pntA).multiplyLocal(0.5);
+
+ final double x = Math.abs(pntB.getX() - _center.getX());
+ final double y = Math.abs(pntB.getY() - _center.getY());
+ final double z = Math.abs(pntB.getZ() - _center.getZ());
+ setData(_center, x, y, z);
+ }
+
+ /**
+ * Updates the center point and extents of this box using the defined values.
+ *
+ * @param center
+ * The center of the box.
+ * @param xExtent
+ * x extent of the box
+ * @param yExtent
+ * y extent of the box
+ * @param zExtent
+ * z extent of the box
+ */
+ public void setData(final ReadOnlyVector3 center, final double xExtent, final double yExtent, final double zExtent) {
+ if (center != null) {
+ _center.set(center);
+ }
+
+ _xExtent = xExtent;
+ _yExtent = yExtent;
+ _zExtent = zExtent;
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ /**
+ * <code>setVertexData</code> sets the vertex positions that define the box using the center point and defined
+ * extents.
+ */
+ protected void setVertexData() {
+ if (_meshData.getVertexBuffer() == null) {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24));
+ }
+
+ final Vector3[] vert = computeVertices(); // returns 8
+
+ // Back
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 0);
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 1);
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 2);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 3);
+
+ // Right
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 4);
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 5);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 6);
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 7);
+
+ // Front
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 8);
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 9);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 10);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 11);
+
+ // Left
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 12);
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 13);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 14);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 15);
+
+ // Top
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 16);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 17);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 18);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 19);
+
+ // Bottom
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 20);
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 21);
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 22);
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 23);
+ }
+
+ /**
+ * <code>setNormalData</code> sets the normals of each of the box's planes.
+ */
+ private void setNormalData() {
+ if (_meshData.getNormalBuffer() == null) {
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24));
+
+ // back
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(0).put(-1);
+ }
+
+ // right
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(1).put(0).put(0);
+ }
+
+ // front
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ }
+
+ // left
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(-1).put(0).put(0);
+ }
+
+ // top
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ }
+
+ // bottom
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ }
+ }
+ }
+
+ /**
+ * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where
+ * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six
+ * faces.
+ */
+ private void setTextureData() {
+ if (_meshData.getTextureCoords(0) == null) {
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0);
+ final FloatBuffer tex = _meshData.getTextureBuffer(0);
+
+ for (int i = 0; i < 6; i++) {
+ tex.put(1).put(0);
+ tex.put(0).put(0);
+ tex.put(0).put(1);
+ tex.put(1).put(1);
+ }
+ }
+ }
+
+ /**
+ * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the
+ * box.
+ */
+ private void setIndexData() {
+ if (_meshData.getIndexBuffer() == null) {
+ final byte[] indices = { 2, 1, 0, 3, 2, 0, 6, 5, 4, 7, 6, 4, 10, 9, 8, 11, 10, 8, 14, 13, 12, 15, 14, 12,
+ 18, 17, 16, 19, 18, 16, 22, 21, 20, 23, 22, 20 };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+ }
+ }
+
+ /**
+ * <code>clone</code> creates a new Box object containing the same data as this one.
+ *
+ * @return the new Box
+ */
+ @Override
+ public Box clone() {
+ return new Box(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent);
+ }
+
+ /**
+ * @return a size 8 array of Vectors representing the 8 points of the box.
+ */
+ public Vector3[] computeVertices() {
+
+ final Vector3 rVal[] = new Vector3[8];
+ rVal[0] = _center.add(-_xExtent, -_yExtent, -_zExtent, null);
+ rVal[1] = _center.add(_xExtent, -_yExtent, -_zExtent, null);
+ rVal[2] = _center.add(_xExtent, _yExtent, -_zExtent, null);
+ rVal[3] = _center.add(-_xExtent, _yExtent, -_zExtent, null);
+ rVal[4] = _center.add(_xExtent, -_yExtent, _zExtent, null);
+ rVal[5] = _center.add(-_xExtent, -_yExtent, _zExtent, null);
+ rVal[6] = _center.add(_xExtent, _yExtent, _zExtent, null);
+ rVal[7] = _center.add(-_xExtent, _yExtent, _zExtent, null);
+ return rVal;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xExtent, "xExtent", 0);
+ capsule.write(_yExtent, "yExtent", 0);
+ capsule.write(_zExtent, "zExtent", 0);
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xExtent = capsule.readDouble("xExtent", 0);
+ _yExtent = capsule.readDouble("yExtent", 0);
+ _zExtent = capsule.readDouble("zExtent", 0);
+ _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)));
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java
new file mode 100644
index 0000000..fb184d4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Capsule.java
@@ -0,0 +1,333 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Capsule</code> provides an extension of <code>Mesh</code>. A <code>Capsule</code> is defined by a height and a
+ * radius. The center of the Cylinder is the origin.
+ */
+public class Capsule extends Mesh {
+
+ private int axisSamples, radialSamples, sphereSamples;
+ private double radius, height;
+
+ public Capsule() {}
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better
+ * looking cylinder, but at the cost of more vertex information. <br>
+ * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for
+ * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to
+ * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture.
+ *
+ * @param name
+ * The name of this Cylinder.
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ */
+ public Capsule(final String name, final int axisSamples, final int radialSamples, final int sphereSamples,
+ final double radius, final double height) {
+
+ super(name);
+
+ this.axisSamples = axisSamples;
+ this.sphereSamples = sphereSamples;
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+ this.height = height;
+
+ recreateBuffers();
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public double getHeight() {
+ return height;
+ }
+
+ /**
+ * @param height
+ * The height to set.
+ */
+ public void setHeight(final double height) {
+ this.height = height;
+ recreateBuffers();
+ }
+
+ /**
+ * @return Returns the radius.
+ */
+ public double getRadius() {
+ return radius;
+ }
+
+ /**
+ * Change the radius of this cylinder.
+ *
+ * @param radius
+ * The radius to set.
+ */
+ public void setRadius(final double radius) {
+ this.radius = radius;
+ setGeometryData();
+ }
+
+ private void recreateBuffers() {
+ // determine vert quantity - first the sphere caps
+ final int sampleLines = (2 * sphereSamples - 1 + axisSamples);
+ final int verts = (radialSamples + 1) * sampleLines + 2;
+
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts));
+
+ // allocate normals
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts));
+
+ // allocate texture coordinates
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ // determine tri quantity
+ final int tris = 2 * radialSamples * sampleLines;
+
+ if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * tris) {
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+ }
+
+ setGeometryData();
+ setIndexData();
+ }
+
+ private void setGeometryData() {
+ final FloatBuffer verts = _meshData.getVertexBuffer();
+ final FloatBuffer norms = _meshData.getNormalBuffer();
+ final FloatBuffer texs = _meshData.getTextureBuffer(0);
+ verts.rewind();
+ norms.rewind();
+ texs.rewind();
+
+ // generate geometry
+ final double inverseRadial = 1.0 / radialSamples;
+ final double inverseSphere = 1.0 / sphereSamples;
+ final double halfHeight = 0.5 * height;
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a cylinder slice.
+ final double[] sin = new double[radialSamples + 1];
+ final double[] cos = new double[radialSamples + 1];
+
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ final double angle = MathUtils.TWO_PI * inverseRadial * radialCount;
+ cos[radialCount] = MathUtils.cos(angle);
+ sin[radialCount] = MathUtils.sin(angle);
+ }
+ sin[radialSamples] = sin[0];
+ cos[radialSamples] = cos[0];
+
+ final Vector3 tempA = new Vector3();
+
+ // top point.
+ verts.put(0).put((float) (radius + halfHeight)).put(0);
+ norms.put(0).put(1).put(0);
+ texs.put(1).put(1);
+
+ // generating the top dome.
+ for (int i = 0; i < sphereSamples; i++) {
+ final double center = radius * (1 - (i + 1) * (inverseSphere));
+ final double lengthFraction = (center + height + radius) / (height + 2 * radius);
+
+ // compute radius of slice
+ final double fSliceRadius = Math.sqrt(Math.abs(radius * radius - center * center));
+
+ for (int j = 0; j <= radialSamples; j++) {
+ final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]);
+ kRadial.multiplyLocal(fSliceRadius);
+ verts.put(kRadial.getXf()).put((float) (center + halfHeight)).put(kRadial.getZf());
+ kRadial.setY(center);
+ kRadial.normalizeLocal();
+ norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf());
+ final double radialFraction = 1 - (j * inverseRadial); // in [0,1)
+ texs.put((float) radialFraction).put((float) lengthFraction);
+ }
+ }
+
+ // generate cylinder... but no need to add points for first and last
+ // samples as they are already part of domes.
+ for (int i = 1; i < axisSamples; i++) {
+ final double center = halfHeight - (i * height / axisSamples);
+ final double lengthFraction = (center + halfHeight + radius) / (height + 2 * radius);
+
+ for (int j = 0; j <= radialSamples; j++) {
+ final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]);
+ kRadial.multiplyLocal(radius);
+ verts.put(kRadial.getXf()).put((float) center).put(kRadial.getZf());
+ kRadial.normalizeLocal();
+ norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf());
+ final double radialFraction = 1 - (j * inverseRadial); // in [0,1)
+ texs.put((float) radialFraction).put((float) lengthFraction);
+ }
+
+ }
+
+ // generating the bottom dome.
+ for (int i = 0; i < sphereSamples; i++) {
+ final double center = i * (radius / sphereSamples);
+ final double lengthFraction = (radius - center) / (height + 2 * radius);
+
+ // compute radius of slice
+ final double fSliceRadius = Math.sqrt(Math.abs(radius * radius - center * center));
+
+ for (int j = 0; j <= radialSamples; j++) {
+ final Vector3 kRadial = tempA.set(cos[j], 0, sin[j]);
+ kRadial.multiplyLocal(fSliceRadius);
+ verts.put(kRadial.getXf()).put((float) (-center - halfHeight)).put(kRadial.getZf());
+ kRadial.setY(-center);
+ kRadial.normalizeLocal();
+ norms.put(kRadial.getXf()).put(kRadial.getYf()).put(kRadial.getZf());
+ final double radialFraction = 1 - (j * inverseRadial); // in [0,1)
+ texs.put((float) radialFraction).put((float) lengthFraction);
+ }
+ }
+
+ // bottom point.
+ verts.put(0).put((float) (-radius - halfHeight)).put(0);
+ norms.put(0).put(-1).put(0);
+ texs.put(0).put(0);
+
+ }
+
+ private void setIndexData() {
+ _meshData.getIndexBuffer().rewind();
+
+ // start with top of top dome.
+ for (int samples = 1; samples <= radialSamples; samples++) {
+ _meshData.getIndices().put(samples + 1);
+ _meshData.getIndices().put(samples);
+ _meshData.getIndices().put(0);
+ }
+
+ for (int plane = 1; plane < (sphereSamples); plane++) {
+ final int topPlaneStart = plane * (radialSamples + 1);
+ final int bottomPlaneStart = (plane - 1) * (radialSamples + 1);
+ for (int sample = 1; sample <= radialSamples; sample++) {
+ _meshData.getIndices().put(bottomPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ }
+ }
+
+ int start = sphereSamples * (radialSamples + 1);
+
+ // add cylinder
+ for (int plane = 0; plane < (axisSamples); plane++) {
+ final int topPlaneStart = start + plane * (radialSamples + 1);
+ final int bottomPlaneStart = start + (plane - 1) * (radialSamples + 1);
+ for (int sample = 1; sample <= radialSamples; sample++) {
+ _meshData.getIndices().put(bottomPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ }
+ }
+
+ start += ((axisSamples - 1) * (radialSamples + 1));
+
+ // Add most of the bottom dome triangles.
+ for (int plane = 1; plane < (sphereSamples); plane++) {
+ final int topPlaneStart = start + plane * (radialSamples + 1);
+ final int bottomPlaneStart = start + (plane - 1) * (radialSamples + 1);
+ for (int sample = 1; sample <= radialSamples; sample++) {
+ _meshData.getIndices().put(bottomPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ }
+ }
+
+ start += ((sphereSamples - 1) * (radialSamples + 1));
+ // Finally the bottom of bottom dome.
+ for (int samples = 1; samples <= radialSamples; samples++) {
+ _meshData.getIndices().put(start + samples);
+ _meshData.getIndices().put(start + samples + 1);
+ _meshData.getIndices().put(start + radialSamples + 2);
+ }
+ }
+
+ public void reconstruct(final Vector3 top, final Vector3 bottom, final double radius) {
+ // our temp vars
+ final Vector3 localTranslation = Vector3.fetchTempInstance();
+ final Vector3 capsuleUp = Vector3.fetchTempInstance();
+
+ // first make the capsule the right shape
+ height = top.distance(bottom);
+ this.radius = radius;
+ setGeometryData();
+
+ // now orient it in space.
+ localTranslation.set(_localTransform.getTranslation());
+ top.add(bottom, localTranslation).multiplyLocal(.5);
+
+ // rotation that takes us from 0,1,0 to the unit vector described by top/center.
+ top.subtract(localTranslation, capsuleUp).normalizeLocal();
+ final Matrix3 rotation = Matrix3.fetchTempInstance();
+ rotation.fromStartEndLocal(Vector3.UNIT_Y, capsuleUp);
+ _localTransform.setRotation(rotation);
+
+ Vector3.releaseTempInstance(localTranslation);
+ Vector3.releaseTempInstance(capsuleUp);
+ Matrix3.releaseTempInstance(rotation);
+
+ updateWorldTransform(false);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(axisSamples, "axisSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(sphereSamples, "sphereSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(height, "height", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ axisSamples = capsule.readInt("circleSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ sphereSamples = capsule.readInt("sphereSamples", 0);
+ radius = capsule.readDouble("radius", 0);
+ height = capsule.readDouble("height", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java
new file mode 100644
index 0000000..1e5b6c4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cone.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+public class Cone extends Cylinder {
+
+ public Cone() {}
+
+ public Cone(final String name, final int axisSamples, final int radialSamples, final float radius,
+ final float height) {
+ this(name, axisSamples, radialSamples, radius, height, true);
+ }
+
+ public Cone(final String name, final int axisSamples, final int radialSamples, final float radius,
+ final float height, final boolean closed) {
+ super(name, axisSamples, radialSamples, radius, height, closed);
+ setRadius2(0);
+ }
+
+ public void setHalfAngle(final float radians) {
+ setRadius1(Math.tan(radians));
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java
new file mode 100644
index 0000000..5b4c7f7
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Cylinder.java
@@ -0,0 +1,399 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Cylinder</code> provides an extension of <code>Mesh</code>. A <code>Cylinder</code> is defined by a height and
+ * radius. The center of the Cylinder is the origin.
+ */
+public class Cylinder extends Mesh {
+
+ private int _axisSamples;
+
+ private int _radialSamples;
+
+ private double _radius;
+ private double _radius2;
+
+ private double _height;
+ private boolean _closed;
+ private boolean _inverted;
+
+ public Cylinder() {}
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better
+ * looking cylinder, but at the cost of more vertex information.
+ *
+ * @param name
+ * The name of this Cylinder.
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ */
+ public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius,
+ final double height) {
+ this(name, axisSamples, radialSamples, radius, height, false);
+ }
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better
+ * looking cylinder, but at the cost of more vertex information. <br>
+ * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for
+ * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to
+ * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture.
+ *
+ * @param name
+ * The name of this Cylinder.
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ * @param closed
+ * true to create a cylinder with top and bottom surface
+ */
+ public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius,
+ final double height, final boolean closed) {
+ this(name, axisSamples, radialSamples, radius, height, closed, false);
+ }
+
+ /**
+ * Creates a new Cylinder. By default its center is the origin. Usually, a higher sample number creates a better
+ * looking cylinder, but at the cost of more vertex information. <br>
+ * If the cylinder is closed the texture is split into axisSamples parts: top most and bottom most part is used for
+ * top and bottom of the cylinder, rest of the texture for the cylinder wall. The middle of the top is mapped to
+ * texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need a suited distorted texture.
+ *
+ * @param name
+ * The name of this Cylinder.
+ * @param axisSamples
+ * Number of triangle samples along the axis.
+ * @param radialSamples
+ * Number of triangle samples along the radial.
+ * @param radius
+ * The radius of the cylinder.
+ * @param height
+ * The cylinder's height.
+ * @param closed
+ * true to create a cylinder with top and bottom surface
+ * @param inverted
+ * true to create a cylinder that is meant to be viewed from the interior.
+ */
+ public Cylinder(final String name, final int axisSamples, final int radialSamples, final double radius,
+ final double height, final boolean closed, final boolean inverted) {
+
+ super(name);
+
+ _axisSamples = axisSamples + (closed ? 2 : 0);
+ _radialSamples = radialSamples;
+ setRadius(radius);
+ _height = height;
+ _closed = closed;
+ _inverted = inverted;
+
+ allocateVertices();
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public double getHeight() {
+ return _height;
+ }
+
+ /**
+ * @param height
+ * The height to set.
+ */
+ public void setHeight(final double height) {
+ _height = height;
+ allocateVertices();
+ }
+
+ /**
+ * @return Returns the radius.
+ */
+ public double getRadius() {
+ return _radius;
+ }
+
+ /**
+ * Change the radius of this cylinder. This resets any second radius.
+ *
+ * @param radius
+ * The radius to set.
+ */
+ public void setRadius(final double radius) {
+ _radius = radius;
+ _radius2 = radius;
+ allocateVertices();
+ }
+
+ /**
+ * Set the top radius of the 'cylinder' to differ from the bottom radius.
+ *
+ * @param radius
+ * The first radius to set.
+ * @see com.ardor3d.extension.shape.Cone
+ */
+ public void setRadius1(final double radius) {
+ _radius = radius;
+ allocateVertices();
+ }
+
+ /**
+ * Set the bottom radius of the 'cylinder' to differ from the top radius. This makes the Mesh be a frustum of
+ * pyramid, or if set to 0, a cone.
+ *
+ * @param radius
+ * The second radius to set.
+ * @see com.ardor3d.extension.shape.Cone
+ */
+ public void setRadius2(final double radius) {
+ _radius2 = radius;
+ allocateVertices();
+ }
+
+ /**
+ * @return the number of samples along the cylinder axis
+ */
+ public int getAxisSamples() {
+ return _axisSamples;
+ }
+
+ /**
+ * @return true if end caps are used.
+ */
+ public boolean isClosed() {
+ return _closed;
+ }
+
+ /**
+ * @return true if normals and uvs are created for interior use
+ */
+ public boolean isInverted() {
+ return _inverted;
+ }
+
+ /**
+ * @return number of samples around cylinder
+ */
+ public int getRadialSamples() {
+ return _radialSamples;
+ }
+
+ private void allocateVertices() {
+ // allocate vertices
+ final int verts = _axisSamples * (_radialSamples + 1) + (_closed ? 2 : 0);
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts));
+
+ // allocate normals if requested
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts));
+
+ // allocate texture coordinates
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ final int count = ((_closed ? 2 : 0) + 2 * (_axisSamples - 1)) * _radialSamples;
+
+ if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * count) {
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * count, verts - 1));
+ }
+
+ setGeometryData();
+ setIndexData();
+ }
+
+ private void setGeometryData() {
+ // generate geometry
+ final double inverseRadial = 1.0 / _radialSamples;
+ final double inverseAxisLess = 1.0 / (_closed ? _axisSamples - 3 : _axisSamples - 1);
+ final double inverseAxisLessTexture = 1.0 / (_axisSamples - 1);
+ final double halfHeight = 0.5 * _height;
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a cylinder slice.
+ final double[] sin = new double[_radialSamples + 1];
+ final double[] cos = new double[_radialSamples + 1];
+
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final double angle = MathUtils.TWO_PI * inverseRadial * radialCount;
+ cos[radialCount] = MathUtils.cos(angle);
+ sin[radialCount] = MathUtils.sin(angle);
+ }
+ sin[_radialSamples] = sin[0];
+ cos[_radialSamples] = cos[0];
+
+ // generate the cylinder itself
+ final Vector3 tempNormal = new Vector3();
+ for (int axisCount = 0, i = 0; axisCount < _axisSamples; axisCount++) {
+ double axisFraction;
+ double axisFractionTexture;
+ int topBottom = 0;
+ if (!_closed) {
+ axisFraction = axisCount * inverseAxisLess; // in [0,1]
+ axisFractionTexture = axisFraction;
+ } else {
+ if (axisCount == 0) {
+ topBottom = -1; // bottom
+ axisFraction = 0;
+ axisFractionTexture = inverseAxisLessTexture;
+ } else if (axisCount == _axisSamples - 1) {
+ topBottom = 1; // top
+ axisFraction = 1;
+ axisFractionTexture = 1 - inverseAxisLessTexture;
+ } else {
+ axisFraction = (axisCount - 1) * inverseAxisLess;
+ axisFractionTexture = axisCount * inverseAxisLessTexture;
+ }
+ }
+ final double z = -halfHeight + _height * axisFraction;
+
+ // compute center of slice
+ final Vector3 sliceCenter = new Vector3(0, 0, z);
+
+ // compute slice vertices with duplication at end point
+ final int save = i;
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final double radialFraction = radialCount * inverseRadial; // in [0,1)
+ tempNormal.set(cos[radialCount], sin[radialCount], 0);
+ if (topBottom == 0) {
+ if (!_inverted) {
+ _meshData.getNormalBuffer().put(tempNormal.getXf()).put(tempNormal.getYf())
+ .put(tempNormal.getZf());
+ } else {
+ _meshData.getNormalBuffer().put(-tempNormal.getXf()).put(-tempNormal.getYf())
+ .put(-tempNormal.getZf());
+ }
+ } else {
+ _meshData.getNormalBuffer().put(0).put(0).put(topBottom * (_inverted ? -1 : 1));
+ }
+
+ tempNormal.multiplyLocal((_radius - _radius2) * axisFraction + _radius2).addLocal(sliceCenter);
+ _meshData.getVertexBuffer().put(tempNormal.getXf()).put(tempNormal.getYf()).put(tempNormal.getZf());
+
+ _meshData.getTextureCoords(0).getBuffer()
+ .put((float) (_inverted ? 1 - radialFraction : radialFraction))
+ .put((float) axisFractionTexture);
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), save, i);
+ BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), save, i);
+
+ _meshData.getTextureCoords(0).getBuffer().put((_inverted ? 0.0f : 1.0f)).put((float) axisFractionTexture);
+
+ i++;
+ }
+
+ if (_closed) {
+ _meshData.getVertexBuffer().put(0).put(0).put((float) -halfHeight); // bottom center
+ _meshData.getNormalBuffer().put(0).put(0).put(-1 * (_inverted ? -1 : 1));
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0);
+ _meshData.getVertexBuffer().put(0).put(0).put((float) halfHeight); // top center
+ _meshData.getNormalBuffer().put(0).put(0).put(1 * (_inverted ? -1 : 1));
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1);
+ }
+ }
+
+ private void setIndexData() {
+ _meshData.getIndexBuffer().rewind();
+
+ // generate connectivity
+ for (int axisCount = 0, axisStart = 0; axisCount < _axisSamples - 1; axisCount++) {
+ int i0 = axisStart;
+ int i1 = i0 + 1;
+ axisStart += _radialSamples + 1;
+ int i2 = axisStart;
+ int i3 = i2 + 1;
+ for (int i = 0; i < _radialSamples; i++) {
+ if (_closed && axisCount == 0) {
+ if (!_inverted) {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 2);
+ _meshData.getIndices().put(i1++);
+ } else {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 2);
+ }
+ } else if (_closed && axisCount == _axisSamples - 2) {
+ if (!_inverted) {
+ _meshData.getIndices().put(i2++);
+ _meshData.getIndices().put(i3++);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ } else {
+ _meshData.getIndices().put(i2++);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ _meshData.getIndices().put(i3++);
+ }
+ } else {
+ if (!_inverted) {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i3++);
+ _meshData.getIndices().put(i2++);
+ } else {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i2++);
+ _meshData.getIndices().put(i3++);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_axisSamples, "axisSamples", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_radius, "radius", 0);
+ capsule.write(_radius2, "radius2", 0);
+ capsule.write(_height, "height", 0);
+ capsule.write(_closed, "closed", false);
+ capsule.write(_inverted, "inverted", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _axisSamples = capsule.readInt("axisSamples", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _radius = capsule.readDouble("radius", 0);
+ _radius2 = capsule.readDouble("radius2", 0);
+ _height = capsule.readDouble("height", 0);
+ _closed = capsule.readBoolean("closed", false);
+ _inverted = capsule.readBoolean("inverted", false);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java
new file mode 100644
index 0000000..3ada7d3
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Disk.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * An approximations of a flat circle. It is simply defined with a radius. It starts out flat along the Z, with center
+ * at the origin.
+ */
+public class Disk extends Mesh {
+
+ private int _shellSamples;
+
+ private int _radialSamples;
+
+ private double _radius;
+
+ public Disk() {}
+
+ /**
+ * Creates a flat disk (circle) at the origin flat along the Z. Usually, a higher sample number creates a better
+ * looking cylinder, but at the cost of more vertex information.
+ *
+ * @param name
+ * The name of the disk.
+ * @param shellSamples
+ * The number of shell samples.
+ * @param radialSamples
+ * The number of radial samples.
+ * @param radius
+ * The radius of the disk.
+ */
+ public Disk(final String name, final int shellSamples, final int radialSamples, final double radius) {
+ super(name);
+
+ _shellSamples = shellSamples;
+ _radialSamples = radialSamples;
+ _radius = radius;
+
+ final int radialless = radialSamples - 1;
+ final int shellLess = shellSamples - 1;
+ // allocate vertices
+ final int verts = 1 + radialSamples * shellLess;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ final int tris = radialSamples * (2 * shellLess - 1);
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+
+ setGeometryData(shellLess);
+ setIndexData(radialless, shellLess);
+
+ }
+
+ private void setGeometryData(final int shellLess) {
+ // generate geometry
+ // center of disk
+ _meshData.getVertexBuffer().put(0).put(0).put(0);
+
+ for (int x = 0; x < _meshData.getVertexCount(); x++) {
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ }
+
+ _meshData.getTextureCoords(0).getBuffer().put(.5f).put(.5f);
+
+ final double inverseShellLess = 1.0 / shellLess;
+ final double inverseRadial = 1.0 / _radialSamples;
+ final Vector3 radialFraction = new Vector3();
+ final Vector2 texCoord = new Vector2();
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final double angle = MathUtils.TWO_PI * inverseRadial * radialCount;
+ final double cos = MathUtils.cos(angle);
+ final double sin = MathUtils.sin(angle);
+ final Vector3 radial = new Vector3(cos, sin, 0);
+
+ for (int shellCount = 1; shellCount < _shellSamples; shellCount++) {
+ final double fraction = inverseShellLess * shellCount; // in (0,R]
+ radialFraction.set(radial).multiplyLocal(fraction);
+ final int i = shellCount + shellLess * radialCount;
+ texCoord.setX(0.5 * (1.0 + radialFraction.getX()));
+ texCoord.setY(0.5 * (1.0 + radialFraction.getY()));
+ BufferUtils.setInBuffer(texCoord, _meshData.getTextureCoords(0).getBuffer(), i);
+
+ radialFraction.multiplyLocal(_radius);
+ BufferUtils.setInBuffer(radialFraction, _meshData.getVertexBuffer(), i);
+ }
+ }
+ }
+
+ private void setIndexData(final int radialless, final int shellLess) {
+ // generate connectivity
+ for (int radialCount0 = radialless, radialCount1 = 0; radialCount1 < _radialSamples; radialCount0 = radialCount1++) {
+ _meshData.getIndices().put(0);
+ _meshData.getIndices().put(1 + shellLess * radialCount0);
+ _meshData.getIndices().put(1 + shellLess * radialCount1);
+ for (int iS = 1; iS < shellLess; iS++) {
+ final int i00 = iS + shellLess * radialCount0;
+ final int i01 = iS + shellLess * radialCount1;
+ final int i10 = i00 + 1;
+ final int i11 = i01 + 1;
+ _meshData.getIndices().put(i00);
+ _meshData.getIndices().put(i10);
+ _meshData.getIndices().put(i11);
+ _meshData.getIndices().put(i00);
+ _meshData.getIndices().put(i11);
+ _meshData.getIndices().put(i01);
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_shellSamples, "shellSamples", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_radius, "radius", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _shellSamples = capsule.readInt("shellSamples", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _radius = capsule.readDouble("radius", 0);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java
new file mode 100644
index 0000000..11e00e9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dodecahedron.java
@@ -0,0 +1,171 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class Dodecahedron extends Mesh {
+
+ private static final int NUM_POINTS = 20;
+ private static final int NUM_TRIS = 36;
+
+ private double _sideLength;
+
+ public Dodecahedron() {}
+
+ /**
+ * Creates an Dodecahedron (think of 12-sided dice) with center at the origin. The length of the sides will be as
+ * specified in sideLength.
+ *
+ * @param name
+ * The name of the octahedron.
+ * @param sideLength
+ * The length of each side of the octahedron.
+ */
+ public Dodecahedron(final String name, final double sideLength) {
+ super(name);
+ _sideLength = sideLength;
+ // allocate vertices
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0);
+
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1));
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ private void setIndexData() {
+ final ByteBuffer indices = (ByteBuffer) _meshData.getIndexBuffer();
+ indices.rewind();
+ indices.put((byte) 0).put((byte) 8).put((byte) 9);
+ indices.put((byte) 0).put((byte) 9).put((byte) 4);
+ indices.put((byte) 0).put((byte) 4).put((byte) 16);
+ indices.put((byte) 0).put((byte) 12).put((byte) 13);
+ indices.put((byte) 0).put((byte) 13).put((byte) 1);
+ indices.put((byte) 0).put((byte) 1).put((byte) 8);
+ indices.put((byte) 0).put((byte) 16).put((byte) 17);
+ indices.put((byte) 0).put((byte) 17).put((byte) 2);
+ indices.put((byte) 0).put((byte) 2).put((byte) 12);
+ indices.put((byte) 8).put((byte) 1).put((byte) 18);
+ indices.put((byte) 8).put((byte) 18).put((byte) 5);
+ indices.put((byte) 8).put((byte) 5).put((byte) 9);
+ indices.put((byte) 12).put((byte) 2).put((byte) 10);
+ indices.put((byte) 12).put((byte) 10).put((byte) 3);
+ indices.put((byte) 12).put((byte) 3).put((byte) 13);
+ indices.put((byte) 16).put((byte) 4).put((byte) 14);
+ indices.put((byte) 16).put((byte) 14).put((byte) 6);
+ indices.put((byte) 16).put((byte) 6).put((byte) 17);
+ indices.put((byte) 9).put((byte) 5).put((byte) 15);
+ indices.put((byte) 9).put((byte) 15).put((byte) 14);
+ indices.put((byte) 9).put((byte) 14).put((byte) 4);
+ indices.put((byte) 6).put((byte) 11).put((byte) 10);
+ indices.put((byte) 6).put((byte) 10).put((byte) 2);
+ indices.put((byte) 6).put((byte) 2).put((byte) 17);
+ indices.put((byte) 3).put((byte) 19).put((byte) 18);
+ indices.put((byte) 3).put((byte) 18).put((byte) 1);
+ indices.put((byte) 3).put((byte) 1).put((byte) 13);
+ indices.put((byte) 7).put((byte) 15).put((byte) 5);
+ indices.put((byte) 7).put((byte) 5).put((byte) 18);
+ indices.put((byte) 7).put((byte) 18).put((byte) 19);
+ indices.put((byte) 7).put((byte) 11).put((byte) 6);
+ indices.put((byte) 7).put((byte) 6).put((byte) 14);
+ indices.put((byte) 7).put((byte) 14).put((byte) 15);
+ indices.put((byte) 7).put((byte) 19).put((byte) 3);
+ indices.put((byte) 7).put((byte) 3).put((byte) 10);
+ indices.put((byte) 7).put((byte) 10).put((byte) 11);
+ }
+
+ private void setTextureData() {
+ final Vector2 tex = new Vector2();
+ final Vector3 vert = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i);
+ if (Math.abs(vert.getZ()) < _sideLength) {
+ tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI));
+ } else {
+ tex.setX(0.5);
+ }
+ tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI);
+ _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY());
+ }
+ }
+
+ private void setNormalData() {
+ final Vector3 norm = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i);
+ norm.normalizeLocal();
+ BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i);
+ }
+ }
+
+ private void setVertexData() {
+ double fA = 1.0 / Math.sqrt(3.0f);
+ double fB = Math.sqrt((3.0 - Math.sqrt(5.0)) / 6.0);
+ double fC = Math.sqrt((3.0 + Math.sqrt(5.0)) / 6.0);
+ fA *= _sideLength;
+ fB *= _sideLength;
+ fC *= _sideLength;
+
+ final FloatBuffer vbuf = _meshData.getVertexBuffer();
+ vbuf.rewind();
+ vbuf.put((float) fA).put((float) fA).put((float) fA);
+ vbuf.put((float) fA).put((float) fA).put((float) -fA);
+ vbuf.put((float) fA).put((float) -fA).put((float) fA);
+ vbuf.put((float) fA).put((float) -fA).put((float) -fA);
+ vbuf.put((float) -fA).put((float) fA).put((float) fA);
+ vbuf.put((float) -fA).put((float) fA).put((float) -fA);
+ vbuf.put((float) -fA).put((float) -fA).put((float) fA);
+ vbuf.put((float) -fA).put((float) -fA).put((float) -fA);
+ vbuf.put((float) fB).put((float) fC).put(0.0f);
+ vbuf.put((float) -fB).put((float) fC).put(0.0f);
+ vbuf.put((float) fB).put((float) -fC).put(0.0f);
+ vbuf.put((float) -fB).put((float) -fC).put(0.0f);
+ vbuf.put((float) fC).put(0.0f).put((float) fB);
+ vbuf.put((float) fC).put(0.0f).put((float) -fB);
+ vbuf.put((float) -fC).put(0.0f).put((float) fB);
+ vbuf.put((float) -fC).put(0.0f).put((float) -fB);
+ vbuf.put(0.0f).put((float) fB).put((float) fC);
+ vbuf.put(0.0f).put((float) -fB).put((float) fC);
+ vbuf.put(0.0f).put((float) fB).put((float) -fC);
+ vbuf.put(0.0f).put((float) -fB).put((float) -fC);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_sideLength, "sideLength", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _sideLength = capsule.readInt("sideLength", 0);
+
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java
new file mode 100644
index 0000000..121f33c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Dome.java
@@ -0,0 +1,299 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * A half sphere.
+ */
+public class Dome extends Mesh {
+
+ private int _planes;
+
+ private int _radialSamples;
+
+ /** The radius of the dome */
+ private double _radius;
+
+ public Dome() {}
+
+ /**
+ * Constructs a dome. By default the dome has not geometry data or center.
+ *
+ * @param name
+ * The name of the dome.
+ */
+ public Dome(final String name) {
+ super(name);
+ }
+
+ /**
+ * Constructs a dome with center at the origin. For details, see the other constructor.
+ *
+ * @param name
+ * Name of dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The samples along the radial.
+ * @param radius
+ * Radius of the dome.
+ * @see #Dome(java.lang.String, com.ardor3d.math.Vector3, int, int, double)
+ */
+ public Dome(final String name, final int planes, final int radialSamples, final double radius) {
+ this(name, new Vector3(0, 0, 0), planes, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase
+ * the quality of the generated dome.
+ *
+ * @param name
+ * Name of the dome.
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the dome.
+ */
+ public Dome(final String name, final Vector3 center, final int planes, final int radialSamples, final double radius) {
+
+ super(name);
+ setData(center, planes, radialSamples, radius, true, true);
+ }
+
+ /**
+ * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase
+ * the quality of the generated dome.
+ *
+ * @param name
+ * Name of the dome.
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the dome.
+ * @param outsideView
+ * If true, the triangles will be connected for a view outside of the dome.
+ */
+ public Dome(final String name, final Vector3 center, final int planes, final int radialSamples,
+ final double radius, final boolean outsideView) {
+ super(name);
+ setData(center, planes, radialSamples, radius, true, outsideView);
+ }
+
+ /**
+ * Changes the information of the dome into the given values. The boolean at the end signals if buffer data should
+ * be updated as well. If the dome is to be rendered, then that value should be true.
+ *
+ * @param center
+ * The new center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The new number of radial samples of the dome.
+ * @param radius
+ * The new radius of the dome.
+ * @param updateBuffers
+ * If true, buffer information is updated as well.
+ * @param outsideView
+ * If true, the triangles will be connected for a view outside of the dome.
+ */
+ public void setData(final Vector3 center, final int planes, final int radialSamples, final double radius,
+ final boolean updateBuffers, final boolean outsideView) {
+ _planes = planes;
+ _radialSamples = radialSamples;
+ _radius = radius;
+
+ if (updateBuffers) {
+ setGeometryData(outsideView, center);
+ setIndexData();
+ }
+ }
+
+ /**
+ * Generates the vertices of the dome
+ *
+ * @param outsideView
+ * If the dome should be viewed from the outside (if not zbuffer is used)
+ * @param center
+ */
+ private void setGeometryData(final boolean outsideView, final Vector3 center) {
+ final Vector3 tempVa = Vector3.fetchTempInstance();
+ final Vector3 tempVb = Vector3.fetchTempInstance();
+ final Vector3 tempVc = Vector3.fetchTempInstance();
+
+ // allocate vertices, we need one extra in each radial to get the
+ // correct texture coordinates
+ final int verts = ((_planes - 1) * (_radialSamples + 1)) + 1;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate normals
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate texture coordinates
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ // generate geometry
+ final double fInvRS = 1.0 / _radialSamples;
+ final double fYFactor = 1.0 / (_planes - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a dome slice.
+ final double[] afSin = new double[(_radialSamples)];
+ final double[] afCos = new double[(_radialSamples)];
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final double fAngle = MathUtils.TWO_PI * fInvRS * iR;
+ afCos[iR] = MathUtils.cos(fAngle);
+ afSin[iR] = MathUtils.sin(fAngle);
+ }
+
+ // generate the dome itself
+ int i = 0;
+ for (int iY = 0; iY < (_planes - 1); iY++) {
+ final double fYFraction = fYFactor * iY; // in (0,1)
+ final double fY = _radius * fYFraction;
+ // compute center of slice
+ final Vector3 kSliceCenter = tempVb.set(center);
+ kSliceCenter.addLocal(0, fY, 0);
+
+ // compute radius of slice
+ final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fY * fY));
+
+ // compute slice vertices
+ Vector3 kNormal;
+ final int iSave = i;
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final double fRadialFraction = iR * fInvRS; // in [0,1)
+ final Vector3 kRadial = tempVc.set(afCos[iR], 0, afSin[iR]);
+ kRadial.multiply(fSliceRadius, tempVa);
+ _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX()))
+ .put((float) (kSliceCenter.getY() + tempVa.getY()))
+ .put((float) (kSliceCenter.getZ() + tempVa.getZ()));
+
+ BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i);
+ kNormal = tempVa.subtractLocal(center);
+ kNormal.normalizeLocal();
+ if (outsideView) {
+ _meshData.getNormalBuffer().put((float) kNormal.getX()).put((float) kNormal.getY())
+ .put((float) kNormal.getZ());
+ } else {
+ _meshData.getNormalBuffer().put((float) -kNormal.getX()).put((float) -kNormal.getY())
+ .put((float) -kNormal.getZ());
+ }
+
+ _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction).put((float) fYFraction);
+
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i);
+ BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i);
+
+ _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) fYFraction);
+
+ i++;
+ }
+
+ // pole
+ _meshData.getVertexBuffer().put((float) center.getX()).put((float) (center.getY() + _radius))
+ .put((float) center.getZ());
+
+ if (outsideView) {
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ } else {
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ }
+
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1.0f);
+
+ Vector3.releaseTempInstance(tempVa);
+ Vector3.releaseTempInstance(tempVb);
+ Vector3.releaseTempInstance(tempVc);
+ }
+
+ /**
+ * Generates the connections
+ */
+ private void setIndexData() {
+ // allocate connectivity
+ final int verts = ((_planes - 1) * (_radialSamples + 1)) + 1;
+ final int tris = (_planes - 2) * _radialSamples * 2 + _radialSamples;
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+
+ // generate connectivity
+ // Generate only for middle planes
+ for (int plane = 1; plane < (_planes - 1); plane++) {
+ final int bottomPlaneStart = (plane - 1) * (_radialSamples + 1);
+ final int topPlaneStart = plane * (_radialSamples + 1);
+ for (int sample = 0; sample < _radialSamples; sample++) {
+ _meshData.getIndices().put(bottomPlaneStart + sample);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(topPlaneStart + sample + 1);
+ }
+ }
+
+ // pole triangles
+ final int bottomPlaneStart = (_planes - 2) * (_radialSamples + 1);
+ for (int samples = 0; samples < _radialSamples; samples++) {
+ _meshData.getIndices().put(bottomPlaneStart + samples);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ _meshData.getIndices().put(bottomPlaneStart + samples + 1);
+ }
+ }
+
+ public int getPlanes() {
+ return _planes;
+ }
+
+ public int getRadialSamples() {
+ return _radialSamples;
+ }
+
+ public double getRadius() {
+ return _radius;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_planes, "planes", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_radius, "radius", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _planes = capsule.readInt("planes", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _radius = capsule.readDouble("radius", 0);
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java
new file mode 100644
index 0000000..c6c2c13
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Extrusion.java
@@ -0,0 +1,360 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * An extrusion of a 2D object ({@link Line}) along a path (List of Vector3). Either a convenience constructor can be
+ * used or the {@link #updateGeometry} method. It is also capable of doing a cubic spline interpolation for a list of
+ * supporting points
+ */
+public class Extrusion extends Mesh {
+
+ /**
+ * Default Constructor. Creates an empty Extrusion.
+ *
+ * @see #updateGeometry(Line, List, Vector3)
+ * @see #updateGeometry(Line, List, boolean, Vector3)
+ * @see #updateGeometry(Line, List, int, Vector3)
+ * @see #updateGeometry(Line, List, int, boolean, Vector3)
+ */
+ public Extrusion() {}
+
+ /**
+ * Creates an empty named Extrusion.
+ *
+ * @param name
+ * name
+ * @see #updateGeometry(Line, List, Vector3)
+ * @see #updateGeometry(Line, List, boolean, Vector3)
+ * @see #updateGeometry(Line, List, int, Vector3)
+ * @see #updateGeometry(Line, List, int, boolean, Vector3)
+ */
+ public Extrusion(final String name) {
+ super(name);
+ }
+
+ /**
+ * Convenience constructor. Calls {@link #updateGeometry(Line, List, Vector3)}.
+ *
+ * @param shape
+ * see {@link #updateGeometry(Line, List, Vector3)}
+ * @param path
+ * see {@link #updateGeometry(Line, List, Vector3)}
+ * @param up
+ * up vector
+ */
+ public Extrusion(final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) {
+ updateGeometry(shape, path, up);
+ }
+
+ /**
+ * Convenience constructor. Sets the name and calls {@link #updateGeometry(Line, List, Vector3)}.
+ *
+ * @param name
+ * name
+ * @param shape
+ * see {@link #updateGeometry(Line, List, Vector3)}
+ * @param path
+ * see {@link #updateGeometry(Line, List, Vector3)}
+ * @param up
+ * up vector
+ */
+ public Extrusion(final String name, final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) {
+ super(name);
+ updateGeometry(shape, path, up);
+ }
+
+ /**
+ * Update vertex, color, index and texture buffers (0) to contain an extrusion of shape along path.
+ *
+ * @param shape
+ * an instance of Line that describes the 2D shape
+ * @param path
+ * a list of vectors that describe the path the shape should be extruded
+ * @param up
+ * up vector
+ */
+ public void updateGeometry(final Line shape, final List<ReadOnlyVector3> path, final ReadOnlyVector3 up) {
+ updateGeometry(shape, path, false, up);
+ }
+
+ /**
+ * Update vertex, color, index and texture buffers (0) to contain an extrusion of shape along path.
+ *
+ * @param shape
+ * an instance of Line that describes the 2D shape
+ * @param path
+ * a list of vectors that describe the path the shape should be extruded
+ * @param closed
+ * true to connect first and last point
+ * @param up
+ * up vector
+ */
+ public void updateGeometry(final Line shape, final List<ReadOnlyVector3> path, final boolean closed,
+ final ReadOnlyVector3 up) {
+ final FloatBuffer shapeBuffer = shape.getMeshData().getVertexBuffer();
+ final FloatBuffer shapeNormalBuffer = shape.getMeshData().getNormalBuffer();
+
+ final int numVertices = path.size() * shapeBuffer.limit();
+
+ FloatBuffer vertices;
+ if (_meshData.getVertexBuffer() != null && _meshData.getVertexBuffer().limit() == numVertices) {
+ vertices = _meshData.getVertexBuffer();
+ vertices.rewind();
+ } else {
+ vertices = BufferUtils.createFloatBuffer(numVertices);
+ }
+
+ FloatBuffer normals = null;
+ if (shapeNormalBuffer != null) {
+ if (_meshData.getNormalBuffer() != null && _meshData.getNormalBuffer().limit() == numVertices) {
+ normals = _meshData.getNormalBuffer();
+ normals.rewind();
+ } else {
+ normals = BufferUtils.createFloatBuffer(numVertices);
+ }
+ }
+
+ final int numIndices = (path.size() - 1) * 2 * shapeBuffer.limit();
+ IndexBufferData<?> indices;
+ if (_meshData.getIndexBuffer() != null && _meshData.getIndexBuffer().limit() == numIndices) {
+ indices = _meshData.getIndices();
+ indices.getBuffer().rewind();
+ } else {
+ indices = BufferUtils.createIndexBufferData(numIndices, numVertices - 1);
+ }
+
+ final IndexMode indexMode = shape.getMeshData().getIndexMode(0);
+
+ final int shapeVertices = shapeBuffer.limit() / 3;
+ final Vector3 vector = new Vector3();
+ final Vector3 direction = new Vector3();
+ final Quaternion rotation = new Quaternion();
+
+ for (int i = 0; i < path.size(); i++) {
+ final ReadOnlyVector3 point = path.get(i);
+ shapeBuffer.rewind();
+ if (shapeNormalBuffer != null) {
+ shapeNormalBuffer.rewind();
+ }
+ int shapeVertice = 0;
+ do {
+ final ReadOnlyVector3 nextPoint = i < path.size() - 1 ? path.get(i + 1) : closed ? path.get(0) : null;
+ final ReadOnlyVector3 lastPoint = i > 0 ? path.get(i - 1) : null;
+ if (nextPoint != null) {
+ direction.set(nextPoint).subtractLocal(point);
+ } else {
+ direction.set(point).subtractLocal(lastPoint);
+ }
+ rotation.lookAt(direction, up);
+
+ if (shapeNormalBuffer != null && normals != null) {
+ vector.set(shapeNormalBuffer.get(), shapeNormalBuffer.get(), shapeNormalBuffer.get());
+ rotation.apply(vector, vector);
+ normals.put(vector.getXf());
+ normals.put(vector.getYf());
+ normals.put(vector.getZf());
+ }
+
+ vector.set(shapeBuffer.get(), shapeBuffer.get(), shapeBuffer.get());
+ rotation.apply(vector, vector);
+ vector.addLocal(point);
+ vertices.put(vector.getXf());
+ vertices.put(vector.getYf());
+ vertices.put(vector.getZf());
+
+ if (indexMode != IndexMode.Lines || (shapeVertice & 1) == 0) {
+ if (i < path.size() - 1) {
+ if (shapeBuffer.hasRemaining()) {
+ indices.put(i * shapeVertices + shapeVertice);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ indices.put((i + 1) * shapeVertices + shapeVertice);
+
+ indices.put((i + 1) * shapeVertices + shapeVertice + 1);
+ indices.put((i + 1) * shapeVertices + shapeVertice);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ } else if (indexMode == IndexMode.LineLoop) {
+ indices.put(i * shapeVertices + shapeVertice);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ indices.put((i + 1) * shapeVertices + shapeVertice);
+
+ indices.put(i * shapeVertices + shapeVertice);
+ indices.put((i - 1) * shapeVertices + shapeVertice + 1);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ }
+ } else if (closed) {
+ indices.put(i * shapeVertices + shapeVertice);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ indices.put(0 + shapeVertice);
+
+ indices.put(0 + shapeVertice + 1);
+ indices.put(0 + shapeVertice);
+ indices.put(i * shapeVertices + shapeVertice + 1);
+ }
+ }
+ shapeVertice++;
+ } while (shapeBuffer.hasRemaining());
+ }
+
+ _meshData.setVertexBuffer(vertices);
+ if (normals != null) {
+ _meshData.setNormalBuffer(normals);
+ }
+ _meshData.setIndices(indices);
+ }
+
+ /**
+ * Performs cubic spline interpolation to find a path through the supporting points where the second derivative is
+ * zero. Then calls {@link #updateGeometry(Line, List, Vector3)} with this path.
+ *
+ * @param shape
+ * an instance of Line that describes the 2D shape
+ * @param points
+ * a list of supporting points for the spline interpolation
+ * @param segments
+ * number of resulting path segments per supporting point
+ * @param up
+ * up vector
+ */
+ public void updateGeometry(final Line shape, final List<ReadOnlyVector3> points, final int segments,
+ final ReadOnlyVector3 up) {
+ updateGeometry(shape, points, segments, false, up);
+ }
+
+ /**
+ * Performs cubic spline interpolation to find a path through the supporting points where the second derivative is
+ * zero. Then calls {@link #updateGeometry(Line, List, boolean, Vector3)} with this path.
+ *
+ * @param shape
+ * an instance of Line that describes the 2D shape
+ * @param points
+ * a list of supporting points for the spline interpolation
+ * @param segments
+ * number of resulting path segments per supporting point
+ * @param closed
+ * true to close the shape (connect last and first point)
+ * @param up
+ * up vector
+ */
+ public void updateGeometry(final Line shape, final List<ReadOnlyVector3> points, final int segments,
+ final boolean closed, final ReadOnlyVector3 up) {
+ int np = points.size(); // number of points
+ if (closed) {
+ np = np + 3;
+ }
+ final double d[][] = new double[3][np]; // Newton form coefficients
+ final double x[] = new double[np]; // x-coordinates of nodes
+
+ final List<ReadOnlyVector3> path = new ArrayList<ReadOnlyVector3>();
+
+ for (int i = 0; i < np; i++) {
+ ReadOnlyVector3 p;
+ if (!closed) {
+ p = points.get(i);
+ } else {
+ if (i == 0) {
+ p = points.get(points.size() - 1);
+ } else if (i >= np - 2) {
+ p = points.get(i - np + 2);
+ } else {
+ p = points.get(i - 1);
+ }
+ }
+ x[i] = i;
+ d[0][i] = p.getX();
+ d[1][i] = p.getY();
+ d[2][i] = p.getZ();
+ }
+
+ if (np > 1) {
+ final double[][] a = new double[3][np];
+ final double h[] = new double[np];
+ for (int i = 1; i <= np - 1; i++) {
+ h[i] = x[i] - x[i - 1];
+ }
+ if (np > 2) {
+ final double sub[] = new double[np - 1];
+ final double diag[] = new double[np - 1];
+ final double sup[] = new double[np - 1];
+
+ for (int i = 1; i <= np - 2; i++) {
+ diag[i] = (h[i] + h[i + 1]) / 3;
+ sup[i] = h[i + 1] / 6;
+ sub[i] = h[i] / 6;
+ for (int dim = 0; dim < 3; dim++) {
+ a[dim][i] = (d[dim][i + 1] - d[dim][i]) / h[i + 1] - (d[dim][i] - d[dim][i - 1]) / h[i];
+ }
+ }
+ for (int dim = 0; dim < 3; dim++) {
+ solveTridiag(sub.clone(), diag.clone(), sup.clone(), a[dim], np - 2);
+ }
+ }
+ // note that a[0]=a[np-1]=0
+ // draw
+ if (!closed) {
+ path.add(new Vector3(d[0][0], d[1][0], d[2][0]));
+ }
+ final double[] point = new double[3];
+ for (int i = closed ? 2 : 1; i <= np - 2; i++) { // loop over
+ // intervals
+ // between nodes
+ for (int j = 1; j <= segments; j++) {
+ for (int dim = 0; dim < 3; dim++) {
+ final double t1 = (h[i] * j) / segments;
+ final double t2 = h[i] - t1;
+ final double v = ((-a[dim][i - 1] / 6 * (t2 + h[i]) * t1 + d[dim][i - 1]) * t2 + (-a[dim][i]
+ / 6 * (t1 + h[i]) * t2 + d[dim][i])
+ * t1)
+ / h[i];
+ // float t = x[i - 1] + t1;
+ point[dim] = v;
+ }
+ path.add(new Vector3(point[0], point[1], point[2]));
+ }
+ }
+ }
+
+ this.updateGeometry(shape, path, closed, up);
+ }
+
+ /*
+ * solve linear system with tridiagonal n by n matrix a using Gaussian elimination without pivoting where a(i,i-1) =
+ * sub[i] for 2<=i<=n a(i,i) = diag[i] for 1<=i<=n a(i,i+1) = sup[i] for 1<=i<=n-1 (the values sub[1], sup[n] are
+ * ignored) right hand side vector b[1:n] is overwritten with solution NOTE: 1...n is used in all arrays, 0 is
+ * unused
+ */
+ private static void solveTridiag(final double sub[], final double diag[], final double sup[], final double b[],
+ final int n) {
+ // factorization and forward substitution
+ for (int i = 2; i <= n; i++) {
+ sub[i] = sub[i] / diag[i - 1];
+ diag[i] = diag[i] - sub[i] * sup[i - 1];
+ b[i] = b[i] - sub[i] * b[i - 1];
+ }
+ b[n] = b[n] / diag[n];
+ for (int i = n - 1; i >= 1; i--) {
+ b[i] = (b[i] - sup[i] * b[i + 1]) / diag[i];
+ }
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java
new file mode 100644
index 0000000..eaa2580
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/GeoSphere.java
@@ -0,0 +1,366 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * GeoSphere - generate a polygon mesh approximating a sphere by recursive subdivision. First approximation is an
+ * octahedron; each level of refinement increases the number of polygons by a factor of 4.
+ * <p/>
+ * Shared vertices are not retained, so numerical errors may produce cracks between polygons at high subdivision levels.
+ * <p/>
+ * Initial idea and text from C-Sourcecode by Jon Leech 3/24/89
+ */
+
+public class GeoSphere extends Mesh {
+
+ public enum TextureMode {
+ Original, Projected;
+ }
+
+ private int _maxlevels;
+ private boolean _usingIcosahedron = true;
+ private TextureMode _textureMode = TextureMode.Original;
+ private double _radius;
+
+ /**
+ * @param name
+ * name of the spatial
+ * @param useIcosahedron
+ * true to start with a 20 triangle mesh, false to start with a 8 triangle mesh
+ * @param radius
+ * the radius of this sphere
+ * @param maxlevels
+ * an integer >= 1 setting the recursion level
+ * @param textureMode
+ * the texture mode to use when generating texture coordinates
+ */
+ public GeoSphere(final String name, final boolean useIcosahedron, final double radius, final int maxlevels,
+ final TextureMode textureMode) {
+ super(name);
+ _maxlevels = maxlevels;
+ _radius = radius;
+ _maxlevels = maxlevels;
+ _usingIcosahedron = useIcosahedron;
+ _textureMode = textureMode;
+ updateGeometry();
+ }
+
+ /**
+ * Default Constructor for Savable use.
+ */
+ public GeoSphere() {}
+
+ public double getRadius() {
+ return _radius;
+ }
+
+ public boolean isUsingIcosahedron() {
+ return _usingIcosahedron;
+ }
+
+ public void setTextureMode(final TextureMode textureMode) {
+ if (textureMode != _textureMode) {
+ _textureMode = textureMode;
+ updateGeometry();
+ }
+ }
+
+ public TextureMode getTextureMode() {
+ return _textureMode;
+ }
+
+ private void updateGeometry() {
+ final int initialTriangleCount = _usingIcosahedron ? 20 : 8;
+ final int initialVertexCount = _usingIcosahedron ? 12 : 6;
+ // number of triangles = initialTriangleCount * 4^(maxlevels-1)
+ final int tris = initialTriangleCount << ((_maxlevels - 1) * 2);
+
+ // number of vertBuf = (initialVertexCount + initialTriangleCount*4 +
+ // initialTriangleCount*4*4 + ...)
+ // = initialTriangleCount*(((4^maxlevels)-1)/(4-1)-1) +
+ // initialVertexCount
+ final int verts = initialTriangleCount * (((1 << (_maxlevels * 2)) - 1) / (4 - 1) - 1) + initialVertexCount
+ + calculateBorderTriangles(_maxlevels);
+
+ FloatBuffer vertBuf = _meshData.getVertexBuffer();
+ _meshData.setVertexBuffer(vertBuf = BufferUtils.createVector3Buffer(vertBuf, verts));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts));
+ final FloatBufferData textureCoords = _meshData.getTextureCoords(0);
+ _meshData.setTextureCoords(
+ new FloatBufferData(BufferUtils.createVector2Buffer(textureCoords != null ? textureCoords.getBuffer()
+ : null, verts), 2), 0);
+
+ int pos = 0;
+
+ Triangle[] old;
+ if (_usingIcosahedron) {
+ final int[] indices = new int[] { pos + 0, pos + 1, pos + 2, pos + 0, pos + 2, pos + 3, pos + 0, pos + 3,
+ pos + 4, pos + 0, pos + 4, pos + 5, pos + 0, pos + 5, pos + 1, pos + 1, pos + 10, pos + 6, pos + 2,
+ pos + 6, pos + 7, pos + 3, pos + 7, pos + 8, pos + 4, pos + 8, pos + 9, pos + 5, pos + 9, pos + 10,
+ pos + 6, pos + 2, pos + 1, pos + 7, pos + 3, pos + 2, pos + 8, pos + 4, pos + 3, pos + 9, pos + 5,
+ pos + 4, pos + 10, pos + 1, pos + 5, pos + 11, pos + 7, pos + 6, pos + 11, pos + 8, pos + 7,
+ pos + 11, pos + 9, pos + 8, pos + 11, pos + 10, pos + 9, pos + 11, pos + 6, pos + 10 };
+ final double y = 0.4472 * _radius;
+ final double a = 0.8944 * _radius;
+ final double b = 0.2764 * _radius;
+ final double c = 0.7236 * _radius;
+ final double d = 0.8507 * _radius;
+ final double e = 0.5257 * _radius;
+ pos++;
+ put(new Vector3(0, _radius, 0));
+ pos++;
+ put(new Vector3(a, y, 0));
+ pos++;
+ put(new Vector3(b, y, -d));
+ pos++;
+ put(new Vector3(-c, y, -e));
+ pos++;
+ put(new Vector3(-c, y, e));
+ pos++;
+ put(new Vector3(b, y, d));
+ pos++;
+ put(new Vector3(c, -y, -e));
+ pos++;
+ put(new Vector3(-b, -y, -d));
+ pos++;
+ put(new Vector3(-a, -y, 0));
+ pos++;
+ put(new Vector3(-b, -y, d));
+ pos++;
+ put(new Vector3(c, -y, e));
+ pos++;
+ put(new Vector3(0, -_radius, 0));
+ final Triangle[] ikosaedron = new Triangle[indices.length / 3];
+ for (int i = 0; i < ikosaedron.length; i++) {
+ final Triangle triangle = ikosaedron[i] = new Triangle();
+ triangle.pt[0] = indices[i * 3];
+ triangle.pt[1] = indices[i * 3 + 1];
+ triangle.pt[2] = indices[i * 3 + 2];
+ }
+
+ old = ikosaedron;
+ } else {
+ /* Six equidistant points lying on the unit sphere */
+ final Vector3 XPLUS = new Vector3(_radius, 0, 0); /* X */
+ final Vector3 XMIN = new Vector3(-_radius, 0, 0); /* -X */
+ final Vector3 YPLUS = new Vector3(0, _radius, 0); /* Y */
+ final Vector3 YMIN = new Vector3(0, -_radius, 0); /* -Y */
+ final Vector3 ZPLUS = new Vector3(0, 0, _radius); /* Z */
+ final Vector3 ZMIN = new Vector3(0, 0, -_radius); /* -Z */
+
+ final int xplus = pos++;
+ put(XPLUS);
+ final int xmin = pos++;
+ put(XMIN);
+ final int yplus = pos++;
+ put(YPLUS);
+ final int ymin = pos++;
+ put(YMIN);
+ final int zplus = pos++;
+ put(ZPLUS);
+ final int zmin = pos++;
+ put(ZMIN);
+
+ final Triangle[] octahedron = new Triangle[] { new Triangle(yplus, zplus, xplus),
+ new Triangle(xmin, zplus, yplus), new Triangle(ymin, zplus, xmin),
+ new Triangle(xplus, zplus, ymin), new Triangle(zmin, yplus, xplus),
+ new Triangle(zmin, xmin, yplus), new Triangle(zmin, ymin, xmin), new Triangle(zmin, xplus, ymin) };
+
+ old = octahedron;
+ }
+
+ final Vector3 pt0 = new Vector3();
+ final Vector3 pt1 = new Vector3();
+ final Vector3 pt2 = new Vector3();
+
+ /* Subdivide each starting triangle (maxlevels - 1) times */
+ for (int level = 1; level < _maxlevels; level++) {
+ /* Allocate a next triangle[] */
+ final Triangle[] next = new Triangle[old.length * 4];
+ for (int i = 0; i < next.length; i++) {
+ next[i] = new Triangle();
+ }
+
+ /**
+ * Subdivide each polygon in the old approximation and normalize the next points thus generated to lie on
+ * the surface of the unit sphere. Each input triangle with vertBuf labeled [0,1,2] as shown below will be
+ * turned into four next triangles:
+ *
+ * <pre>
+ * Make next points
+ * a = (0+2)/2
+ * b = (0+1)/2
+ * c = (1+2)/2
+ *
+ * 1 /\ Normalize a, b, c
+ * / \
+ * b /____\ c
+ *
+ * Construct next triangles
+ *
+ * /\ /\ [0,b,a]
+ * / \ / \ [b,1,c]
+ * /____\/____\ [a,b,c]
+ * 0 a 2 [a,c,2]
+ * </pre>
+ */
+ for (int i = 0; i < old.length; i++) {
+ int newi = i * 4;
+ final Triangle oldt = old[i];
+ Triangle newt = next[newi];
+
+ BufferUtils.populateFromBuffer(pt0, vertBuf, oldt.pt[0]);
+ BufferUtils.populateFromBuffer(pt1, vertBuf, oldt.pt[1]);
+ BufferUtils.populateFromBuffer(pt2, vertBuf, oldt.pt[2]);
+ final Vector3 av = createMidpoint(pt0, pt2).normalizeLocal().multiplyLocal(_radius);
+ final Vector3 bv = createMidpoint(pt0, pt1).normalizeLocal().multiplyLocal(_radius);
+ final Vector3 cv = createMidpoint(pt1, pt2).normalizeLocal().multiplyLocal(_radius);
+ final int a = pos++;
+ put(av);
+ final int b = pos++;
+ put(bv);
+ final int c = pos++;
+ put(cv);
+
+ newt.pt[0] = oldt.pt[0];
+ newt.pt[1] = b;
+ newt.pt[2] = a;
+ newt = next[++newi];
+
+ newt.pt[0] = b;
+ newt.pt[1] = oldt.pt[1];
+ newt.pt[2] = c;
+ newt = next[++newi];
+
+ newt.pt[0] = a;
+ newt.pt[1] = b;
+ newt.pt[2] = c;
+ newt = next[++newi];
+
+ newt.pt[0] = a;
+ newt.pt[1] = c;
+ newt.pt[2] = oldt.pt[2];
+ }
+
+ /* Continue subdividing next triangles */
+ old = next;
+ }
+
+ final IndexBufferData<?> indexBuffer = BufferUtils.createIndexBufferData(tris * 3, verts - 1);
+ _meshData.setIndices(indexBuffer);
+
+ int carryIntIndex = _meshData.getVertexBuffer().position() / 3;
+ for (final Triangle triangle : old) {
+ for (final int aPt : triangle.pt) {
+ final Vector3 point = new Vector3();
+ BufferUtils.populateFromBuffer(point, _meshData.getVertexBuffer(), aPt);
+ if (point.getX() > 0 && point.getY() == 0) {
+ // Find out which 'y' side the triangle is on
+ final double yCenter = (_meshData.getVertexBuffer().get(triangle.pt[0] * 3 + 1)
+ + _meshData.getVertexBuffer().get(triangle.pt[1] * 3 + 1) + _meshData.getVertexBuffer()
+ .get(triangle.pt[2] * 3 + 1)) / 3.0;
+ if (yCenter > 0.0) {
+ put(point, true);
+ indexBuffer.put(carryIntIndex++);
+ continue;
+ }
+ }
+ indexBuffer.put(aPt);
+ }
+ }
+ }
+
+ private void put(final Vector3 vec) {
+ put(vec, false);
+ }
+
+ private void put(final Vector3 vec, final boolean begining) {
+ final FloatBuffer vertBuf = _meshData.getVertexBuffer();
+ vertBuf.put((float) vec.getX());
+ vertBuf.put((float) vec.getY());
+ vertBuf.put((float) vec.getZ());
+
+ final double length = vec.length();
+ final FloatBuffer normBuf = _meshData.getNormalBuffer();
+ final double xNorm = vec.getX() / length;
+ normBuf.put((float) xNorm);
+ final double yNorm = vec.getY() / length;
+ normBuf.put((float) yNorm);
+ final double zNorm = vec.getZ() / length;
+ normBuf.put((float) zNorm);
+
+ final FloatBuffer texBuf = _meshData.getTextureCoords(0).getBuffer();
+ if (vec.getX() > 0.0 && vec.getY() == 0.0) {
+ if (begining) {
+ texBuf.put(0);
+ } else {
+ texBuf.put(1);
+ }
+ } else {
+ texBuf.put((float) ((Math.atan2(yNorm, xNorm) / (2 * Math.PI) + 1) % 1));
+ }
+
+ double vPos = 0;
+ switch (_textureMode) {
+ case Original:
+ vPos = .5 * (zNorm + 1);
+ break;
+ case Projected:
+ vPos = MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(zNorm));
+ break;
+ }
+ texBuf.put((float) vPos);
+ }
+
+ private int calculateBorderTriangles(int levels) {
+ int current = 108;
+ // Pattern starts at 4
+ levels -= 4;
+ while (levels-- > 0) {
+ current = 2 * current + 12;
+ }
+ return current;
+ }
+
+ /**
+ * Compute the average of two vectors.
+ *
+ * @param a
+ * first vector
+ * @param b
+ * second vector
+ * @return the average of two points
+ */
+ protected Vector3 createMidpoint(final Vector3 a, final Vector3 b) {
+ return new Vector3((a.getX() + b.getX()) * 0.5, (a.getY() + b.getY()) * 0.5, (a.getZ() + b.getZ()) * 0.5);
+ }
+
+ static class Triangle {
+ int[] pt = new int[3]; /* Vertices of triangle */
+
+ public Triangle() {}
+
+ public Triangle(final int pt0, final int pt1, final int pt2) {
+ pt[0] = pt0;
+ pt[1] = pt1;
+ pt[2] = pt2;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java
new file mode 100644
index 0000000..361fbb1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Hexagon.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * A regular hexagon with each triangle having side length that is given in the constructor.
+ */
+public class Hexagon extends Mesh {
+
+ private static final int NUM_POINTS = 7;
+
+ private static final int NUM_TRIS = 6;
+
+ private float _sideLength;
+
+ public Hexagon() {}
+
+ /**
+ * Hexagon Constructor instantiates a new Hexagon. This element is center on 0,0,0 with all normals pointing up. The
+ * user must move and rotate for positioning.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ * @param sideLength
+ * The length of all the sides of the triangles
+ */
+ public Hexagon(final String name, final float sideLength) {
+ super(name);
+ _sideLength = sideLength;
+ // allocate vertices
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0);
+
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1));
+
+ setVertexData();
+ setIndexData();
+ setTextureData();
+ setNormalData();
+
+ }
+
+ /**
+ * Vertexes are set up like this: 0__1 / \ / \ 5/__\6/__\2 \ / \ / \ /___\ / 4 3 All lines on this diagram are
+ * sideLength long. Therefore, the width of the hexagon is sideLength * 2, and the height is 2 * the height of one
+ * equalateral triangle with all side = sideLength which is .866
+ */
+ private void setVertexData() {
+ _meshData.getVertexBuffer().put(-(_sideLength / 2)).put(_sideLength * 0.866f).put(0.0f);
+ _meshData.getVertexBuffer().put(_sideLength / 2).put(_sideLength * 0.866f).put(0.0f);
+ _meshData.getVertexBuffer().put(_sideLength).put(0.0f).put(0.0f);
+ _meshData.getVertexBuffer().put(_sideLength / 2).put(-_sideLength * 0.866f).put(0.0f);
+ _meshData.getVertexBuffer().put(-(_sideLength / 2)).put(-_sideLength * 0.866f).put(0.0f);
+ _meshData.getVertexBuffer().put(-_sideLength).put(0.0f).put(0.0f);
+ _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(0.0f);
+ }
+
+ /**
+ * Sets up the indexes of the mesh. These go in a clockwise fashion and thus only the 'up' side of the hex is lit
+ * properly. If you wish to have to either set two sided lighting or create two hexes back-to-back
+ */
+
+ private void setIndexData() {
+ _meshData.getIndexBuffer().rewind();
+ // tri 1
+ _meshData.getIndices().put(0);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(1);
+ // tri 2
+ _meshData.getIndices().put(1);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(2);
+ // tri 3
+ _meshData.getIndices().put(2);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(3);
+ // tri 4
+ _meshData.getIndices().put(3);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(4);
+ // tri 5
+ _meshData.getIndices().put(4);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(5);
+ // tri 6
+ _meshData.getIndices().put(5);
+ _meshData.getIndices().put(6);
+ _meshData.getIndices().put(0);
+ }
+
+ private void setTextureData() {
+ _meshData.getTextureCoords(0).getBuffer().put(0.25f).put(0);
+ _meshData.getTextureCoords(0).getBuffer().put(0.75f).put(0);
+ _meshData.getTextureCoords(0).getBuffer().put(1.0f).put(0.5f);
+ _meshData.getTextureCoords(0).getBuffer().put(0.75f).put(1.0f);
+ _meshData.getTextureCoords(0).getBuffer().put(0.25f).put(1.0f);
+ _meshData.getTextureCoords(0).getBuffer().put(0.0f).put(0.5f);
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f);
+ }
+
+ /**
+ * Sets all the default vertex normals to 'up', +1 in the Z direction.
+ */
+ private void setNormalData() {
+ final Vector3 zAxis = new Vector3(0, 0, 1);
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.setInBuffer(zAxis, _meshData.getNormalBuffer(), i);
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_sideLength, "sideLength", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _sideLength = capsule.readInt("sideLength", 0);
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java
new file mode 100644
index 0000000..4725e36
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Icosahedron.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class Icosahedron extends Mesh {
+
+ private static final int NUM_POINTS = 12;
+
+ private double _sideLength;
+
+ public Icosahedron() {}
+
+ /**
+ * Creates an Icosahedron (think of 20-sided dice) with center at the origin. The length of the sides will be as
+ * specified in sideLength.
+ *
+ * @param name
+ * The name of the Icosahedron.
+ * @param sideLength
+ * The length of each side of the Icosahedron.
+ */
+ public Icosahedron(final String name, final double sideLength) {
+ super(name);
+ _sideLength = sideLength;
+
+ // allocate vertices
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0);
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ private void setIndexData() {
+ final byte[] indices = { 0, 8, 4, 0, 5, 10, 2, 4, 9, 2, 11, 5, 1, 6, 8, 1, 10, 7, 3, 9, 6, 3, 7, 11, 0, 10, 8,
+ 1, 8, 10, 2, 9, 11, 3, 11, 9, 4, 2, 0, 5, 0, 2, 6, 1, 3, 7, 3, 1, 8, 6, 4, 9, 4, 6, 10, 5, 7, 11, 7, 5 };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+ }
+
+ private void setTextureData() {
+ final Vector2 tex = new Vector2();
+ final Vector3 vert = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i);
+ if (Math.abs(vert.getZ()) < _sideLength) {
+ tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI));
+ } else {
+ tex.setX(0.5);
+ }
+ tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI);
+
+ _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY());
+ }
+ }
+
+ private void setNormalData() {
+ final Vector3 norm = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i);
+ norm.normalizeLocal();
+ BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i);
+ }
+ }
+
+ private void setVertexData() {
+ final double dGoldenRatio = 0.5 * (1.0 + Math.sqrt(5.0));
+ final double dInvRoot = 1.0 / Math.sqrt(1.0 + dGoldenRatio * dGoldenRatio);
+ final float dU = (float) (dGoldenRatio * dInvRoot * _sideLength);
+ final float dV = (float) (dInvRoot * _sideLength);
+
+ final FloatBuffer vbuf = _meshData.getVertexBuffer();
+ vbuf.rewind();
+ vbuf.put(dU).put(dV).put(0.0f);
+ vbuf.put(-dU).put(dV).put(0.0f);
+ vbuf.put(dU).put(-dV).put(0.0f);
+ vbuf.put(-dU).put(-dV).put(0.0f);
+ vbuf.put(dV).put(0.0f).put(dU);
+ vbuf.put(dV).put(0.0f).put(-dU);
+ vbuf.put(-dV).put(0.0f).put(dU);
+ vbuf.put(-dV).put(0.0f).put(-dU);
+ vbuf.put(0.0f).put(dU).put(dV);
+ vbuf.put(0.0f).put(-dU).put(dV);
+ vbuf.put(0.0f).put(dU).put(-dV);
+ vbuf.put(0.0f).put(-dU).put(-dV);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_sideLength, "sideLength", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _sideLength = capsule.readInt("sideLength", 0);
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java
new file mode 100644
index 0000000..4698562
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/MultiFaceBox.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+
+public class MultiFaceBox extends Box {
+
+ public MultiFaceBox() {
+ super();
+ remap();
+ }
+
+ public MultiFaceBox(final String name) {
+ super(name);
+ remap();
+ }
+
+ public MultiFaceBox(final String name, final Vector3 min, final Vector3 max) {
+ super(name, min, max);
+ remap();
+ }
+
+ public MultiFaceBox(final String name, final Vector3 center, final float xExtent, final float yExtent,
+ final float zExtent) {
+ super(name, center, xExtent, yExtent, zExtent);
+ remap();
+ }
+
+ private void remap() {
+ final FloatBuffer fb = _meshData.getTextureCoords(0).getBuffer();
+ fb.rewind();
+ for (int i = 0; i < 6; i++) {
+ final float bottom = i / 8f;
+ final float top = (i + 1) / 8f;
+ final float[] tex = new float[] { 1, bottom, 0, bottom, 0, top, 1, top };
+ fb.put(tex);
+ }
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java
new file mode 100644
index 0000000..b4e1785
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Octahedron.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * An eight faced polyhedron. It looks somewhat like two pyramids placed bottom to bottom.
+ */
+public class Octahedron extends Mesh {
+
+ private static final int NUM_POINTS = 6;
+
+ private static final int NUM_TRIS = 8;
+
+ private double _sideLength;
+
+ public Octahedron() {}
+
+ /**
+ * Creates an octahedron with center at the origin. The lenght sides are given.
+ *
+ * @param name
+ * The name of the octahedron.
+ * @param sideLength
+ * The length of each side of the octahedron.
+ */
+ public Octahedron(final String name, final double sideLength) {
+ super(name);
+ _sideLength = sideLength;
+
+ // allocate vertices
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(NUM_POINTS));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(NUM_POINTS), 0);
+
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * NUM_TRIS, NUM_POINTS - 1));
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ private void setIndexData() {
+ final IndexBufferData<?> indices = _meshData.getIndices();
+ indices.getBuffer().rewind();
+ indices.put(4).put(0).put(2);
+ indices.put(4).put(2).put(1);
+ indices.put(4).put(1).put(3);
+ indices.put(4).put(3).put(0);
+ indices.put(5).put(2).put(0);
+ indices.put(5).put(1).put(2);
+ indices.put(5).put(3).put(1);
+ indices.put(5).put(0).put(3);
+ }
+
+ private void setTextureData() {
+ final Vector2 tex = new Vector2();
+ final Vector3 vert = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(vert, _meshData.getVertexBuffer(), i);
+ if (Math.abs(vert.getZ()) < _sideLength) {
+ tex.setX(0.5 * (1.0 + Math.atan2(vert.getY(), vert.getX()) * MathUtils.INV_PI));
+ } else {
+ tex.setX(0.5);
+ }
+ tex.setY(Math.acos(vert.getZ() / _sideLength) * MathUtils.INV_PI);
+ _meshData.getTextureCoords(0).getBuffer().put((float) tex.getX()).put((float) tex.getY());
+ }
+ }
+
+ private void setNormalData() {
+ final Vector3 norm = new Vector3();
+ for (int i = 0; i < NUM_POINTS; i++) {
+ BufferUtils.populateFromBuffer(norm, _meshData.getVertexBuffer(), i);
+ norm.normalizeLocal();
+ BufferUtils.setInBuffer(norm, _meshData.getNormalBuffer(), i);
+ }
+ }
+
+ private void setVertexData() {
+ final float floatSideLength = (float) _sideLength;
+
+ _meshData.getVertexBuffer().put(floatSideLength).put(0.0f).put(0.0f);
+ _meshData.getVertexBuffer().put(-floatSideLength).put(0.0f).put(0.0f);
+ _meshData.getVertexBuffer().put(0.0f).put(floatSideLength).put(0.0f);
+ _meshData.getVertexBuffer().put(0.0f).put(-floatSideLength).put(0.0f);
+ _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(floatSideLength);
+ _meshData.getVertexBuffer().put(0.0f).put(0.0f).put(-floatSideLength);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_sideLength, "sideLength", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _sideLength = capsule.readInt("sideLength", 0);
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java
new file mode 100644
index 0000000..c24b3b5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/OrientedBox.java
@@ -0,0 +1,404 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.CapsuleUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * This primitive represents a box that has options to orient it according to its X/Y/Z axis. It is used to create an
+ * OrientedBoundingBox mostly.
+ */
+public class OrientedBox extends Mesh {
+
+ /** Center of the Oriented Box. */
+ protected Vector3 _center;
+
+ /** X axis of the Oriented Box. */
+ protected Vector3 _xAxis = new Vector3(1, 0, 0);
+
+ /** Y axis of the Oriented Box. */
+ protected Vector3 _yAxis = new Vector3(0, 1, 0);
+
+ /** Z axis of the Oriented Box. */
+ protected Vector3 _zAxis = new Vector3(0, 0, 1);
+
+ /** Extents of the box along the x,y,z axis. */
+ protected Vector3 _extent = new Vector3(0, 0, 0);
+
+ /** Texture coordintae values for the corners of the box. */
+ protected Vector2 _texTopRight, _texTopLeft, _texBotRight, _texBotLeft;
+
+ /** Vector array used to store the array of 8 corners the box has. */
+ public Vector3[] _vectorStore;
+
+ /**
+ * If true, the box's vectorStore array correctly represnts the box's corners.
+ */
+ public boolean _correctCorners;
+
+ public OrientedBox() {}
+
+ /**
+ * Creates a new OrientedBox with the given name.
+ *
+ * @param name
+ * The name of the new box.
+ */
+ public OrientedBox(final String name) {
+ super(name);
+ _vectorStore = new Vector3[8];
+ for (int i = 0; i < _vectorStore.length; i++) {
+ _vectorStore[i] = new Vector3();
+ }
+ _texTopRight = new Vector2(1, 1);
+ _texTopLeft = new Vector2(1, 0);
+ _texBotRight = new Vector2(0, 1);
+ _texBotLeft = new Vector2(0, 0);
+ _center = new Vector3(0, 0, 0);
+ _correctCorners = false;
+ computeInformation();
+ }
+
+ /**
+ * Takes the plane and center information and creates the correct vertex,normal,color,texture,index information to
+ * represent the OrientedBox.
+ */
+ public void computeInformation() {
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+ }
+
+ /**
+ * Sets the correct indices array for the box.
+ */
+ private void setIndexData() {
+ if (_meshData.getIndexBuffer() == null) {
+ _meshData.setIndexBuffer(BufferUtils.createByteBuffer(36));
+
+ for (int i = 0; i < 6; i++) {
+ _meshData.getIndices().put(i * 4 + 0);
+ _meshData.getIndices().put(i * 4 + 1);
+ _meshData.getIndices().put(i * 4 + 3);
+ _meshData.getIndices().put(i * 4 + 1);
+ _meshData.getIndices().put(i * 4 + 2);
+ _meshData.getIndices().put(i * 4 + 3);
+ }
+ }
+ }
+
+ /**
+ * Sets the correct texture array for the box.
+ */
+ private void setTextureData() {
+ if (_meshData.getTextureBuffer(0) == null) {
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0);
+
+ for (int x = 0; x < 6; x++) {
+ _meshData.getTextureCoords(0).getBuffer().put(_texTopRight.getXf()).put(_texTopRight.getYf());
+ _meshData.getTextureCoords(0).getBuffer().put(_texTopLeft.getXf()).put(_texTopLeft.getYf());
+ _meshData.getTextureCoords(0).getBuffer().put(_texBotLeft.getXf()).put(_texBotLeft.getYf());
+ _meshData.getTextureCoords(0).getBuffer().put(_texBotRight.getXf()).put(_texBotRight.getYf());
+ }
+ }
+ }
+
+ /**
+ * Sets the correct normal array for the box.
+ */
+ private void setNormalData() {
+ if (_meshData.getNormalBuffer() == null) {
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24));
+ } else {
+ _meshData.getNormalBuffer().rewind();
+ }
+
+ // top
+ _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf());
+ _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf());
+ _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf());
+ _meshData.getNormalBuffer().put(_yAxis.getXf()).put(_yAxis.getYf()).put(_yAxis.getZf());
+
+ // right
+ _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf());
+ _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf());
+ _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf());
+ _meshData.getNormalBuffer().put(_xAxis.getXf()).put(_xAxis.getYf()).put(_xAxis.getZf());
+
+ // left
+ _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf());
+ _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf());
+ _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf());
+ _meshData.getNormalBuffer().put(-_xAxis.getXf()).put(-_xAxis.getYf()).put(-_xAxis.getZf());
+
+ // bottom
+ _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf());
+ _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf());
+ _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf());
+ _meshData.getNormalBuffer().put(-_yAxis.getXf()).put(-_yAxis.getYf()).put(-_yAxis.getZf());
+
+ // back
+ _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf());
+ _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf());
+ _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf());
+ _meshData.getNormalBuffer().put(-_zAxis.getXf()).put(-_zAxis.getYf()).put(-_zAxis.getZf());
+
+ // front
+ _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf());
+ _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf());
+ _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf());
+ _meshData.getNormalBuffer().put(_zAxis.getXf()).put(_zAxis.getYf()).put(_zAxis.getZf());
+ }
+
+ /**
+ * Sets the correct vertex information for the box.
+ */
+ private void setVertexData() {
+ computeCorners();
+ if (_meshData.getVertexBuffer() == null) {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24));
+ } else {
+ _meshData.getVertexBuffer().rewind();
+ }
+
+ // Top
+ _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf())
+ .put(_vectorStore[0].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf())
+ .put(_vectorStore[1].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf())
+ .put(_vectorStore[5].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf())
+ .put(_vectorStore[3].getZf());
+
+ // Right
+ _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf())
+ .put(_vectorStore[0].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf())
+ .put(_vectorStore[3].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf())
+ .put(_vectorStore[6].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf())
+ .put(_vectorStore[2].getZf());
+
+ // Left
+ _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf())
+ .put(_vectorStore[5].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf())
+ .put(_vectorStore[1].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf())
+ .put(_vectorStore[4].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf())
+ .put(_vectorStore[7].getZf());
+
+ // Bottom
+ _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf())
+ .put(_vectorStore[6].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf())
+ .put(_vectorStore[7].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf())
+ .put(_vectorStore[4].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf())
+ .put(_vectorStore[2].getZf());
+
+ // Back
+ _meshData.getVertexBuffer().put(_vectorStore[3].getXf()).put(_vectorStore[3].getYf())
+ .put(_vectorStore[3].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[5].getXf()).put(_vectorStore[5].getYf())
+ .put(_vectorStore[5].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[7].getXf()).put(_vectorStore[7].getYf())
+ .put(_vectorStore[7].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[6].getXf()).put(_vectorStore[6].getYf())
+ .put(_vectorStore[6].getZf());
+
+ // Front
+ _meshData.getVertexBuffer().put(_vectorStore[1].getXf()).put(_vectorStore[1].getYf())
+ .put(_vectorStore[1].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[4].getXf()).put(_vectorStore[4].getYf())
+ .put(_vectorStore[4].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[2].getXf()).put(_vectorStore[2].getYf())
+ .put(_vectorStore[2].getZf());
+ _meshData.getVertexBuffer().put(_vectorStore[0].getXf()).put(_vectorStore[0].getYf())
+ .put(_vectorStore[0].getZf());
+ }
+
+ /**
+ * Sets the vectorStore information to the 8 corners of the box.
+ */
+ public void computeCorners() {
+ _correctCorners = true;
+
+ final Vector3 tempVa = Vector3.fetchTempInstance();
+ final Vector3 tempVb = Vector3.fetchTempInstance();
+ final Vector3 tempVc = Vector3.fetchTempInstance();
+ tempVa.set(_xAxis).multiplyLocal(_extent.getX());
+ tempVb.set(_yAxis).multiplyLocal(_extent.getY());
+ tempVc.set(_zAxis).multiplyLocal(_extent.getZ());
+
+ _vectorStore[0].set(_center).addLocal(tempVa).addLocal(tempVb).addLocal(tempVc);
+ _vectorStore[1].set(_center).addLocal(tempVa).subtractLocal(tempVb).addLocal(tempVc);
+ _vectorStore[2].set(_center).addLocal(tempVa).addLocal(tempVb).subtractLocal(tempVc);
+ _vectorStore[3].set(_center).subtractLocal(tempVa).addLocal(tempVb).addLocal(tempVc);
+ _vectorStore[4].set(_center).addLocal(tempVa).subtractLocal(tempVb).subtractLocal(tempVc);
+ _vectorStore[5].set(_center).subtractLocal(tempVa).subtractLocal(tempVb).addLocal(tempVc);
+ _vectorStore[6].set(_center).subtractLocal(tempVa).addLocal(tempVb).subtractLocal(tempVc);
+ _vectorStore[7].set(_center).subtractLocal(tempVa).subtractLocal(tempVb).subtractLocal(tempVc);
+
+ Vector3.releaseTempInstance(tempVa);
+ Vector3.releaseTempInstance(tempVb);
+ Vector3.releaseTempInstance(tempVc);
+ }
+
+ /**
+ * Returns the center of the box.
+ *
+ * @return The box's center.
+ */
+ public Vector3 getCenter() {
+ return _center;
+ }
+
+ /**
+ * Sets the box's center to the given value. Shallow copy only.
+ *
+ * @param center
+ * The box's new center.
+ */
+ public void setCenter(final Vector3 center) {
+ _center = center;
+ }
+
+ /**
+ * Returns the box's extent vector along the x,y,z.
+ *
+ * @return The box's extent vector.
+ */
+ public Vector3 getExtent() {
+ return _extent;
+ }
+
+ /**
+ * Sets the box's extent vector to the given value. Shallow copy only.
+ *
+ * @param extent
+ * The box's new extent.
+ */
+ public void setExtent(final Vector3 extent) {
+ _extent = extent;
+ }
+
+ /**
+ * Returns the x axis of this box.
+ *
+ * @return This OB's x axis.
+ */
+ public Vector3 getxAxis() {
+ return _xAxis;
+ }
+
+ /**
+ * Sets the x axis of this OB. Shallow copy.
+ *
+ * @param xAxis
+ * The new x axis.
+ */
+ public void setXAxis(final Vector3 xAxis) {
+ _xAxis = xAxis;
+ }
+
+ /**
+ * Gets the Y axis of this OB.
+ *
+ * @return This OB's Y axis.
+ */
+ public Vector3 getYAxis() {
+ return _yAxis;
+ }
+
+ /**
+ * Sets the Y axis of this OB. Shallow copy.
+ *
+ * @param yAxis
+ * The new Y axis.
+ */
+ public void setYAxis(final Vector3 yAxis) {
+ _yAxis = yAxis;
+ }
+
+ /**
+ * Returns the Z axis of this OB.
+ *
+ * @return The Z axis.
+ */
+ public Vector3 getZAxis() {
+ return _zAxis;
+ }
+
+ /**
+ * Sets the Z axis of this OB. Shallow copy.
+ *
+ * @param zAxis
+ * The new Z axis.
+ */
+ public void setZAxis(final Vector3 zAxis) {
+ _zAxis = zAxis;
+ }
+
+ /**
+ * Returns if the corners are set corectly.
+ *
+ * @return True if the vectorStore is correct.
+ */
+ public boolean isCorrectCorners() {
+ return _correctCorners;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+ capsule.write(_xAxis, "_xAxis", new Vector3(Vector3.UNIT_X));
+ capsule.write(_yAxis, "yAxis", new Vector3(Vector3.UNIT_Y));
+ capsule.write(_zAxis, "zAxis", new Vector3(Vector3.UNIT_Z));
+ capsule.write(_extent, "extent", new Vector3(Vector3.ZERO));
+ capsule.write(_texTopRight, "texTopRight", new Vector2(1, 1));
+ capsule.write(_texTopLeft, "texTopLeft", new Vector2(1, 0));
+ capsule.write(_texBotRight, "texBotRight", new Vector2(0, 1));
+ capsule.write(_texBotLeft, "texBotLeft", new Vector2(0, 0));
+ capsule.write(_vectorStore, "vectorStore", new Vector3[8]);
+ capsule.write(_correctCorners, "correctCorners", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _center = (Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO));
+ _xAxis = (Vector3) capsule.readSavable("_xAxis", new Vector3(Vector3.UNIT_X));
+ _yAxis = (Vector3) capsule.readSavable("yAxis", new Vector3(Vector3.UNIT_Y));
+ _zAxis = (Vector3) capsule.readSavable("zAxis", new Vector3(Vector3.UNIT_Z));
+ _extent = (Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO));
+ _texTopRight = (Vector2) capsule.readSavable("texTopRight", new Vector2(1, 1));
+ _texTopLeft = (Vector2) capsule.readSavable("texTopLeft", new Vector2(1, 0));
+ _texBotRight = (Vector2) capsule.readSavable("texBotRight", new Vector2(0, 1));
+ _texBotLeft = (Vector2) capsule.readSavable("texBotLeft", new Vector2(0, 0));
+ _vectorStore = CapsuleUtils.asArray(capsule.readSavableArray("vectorStore", new Vector3[8]), Vector3.class);
+ _correctCorners = capsule.readBoolean("correctCorners", false);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java
new file mode 100644
index 0000000..f1380a0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/PQTorus.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * PQTorus generates the geometry of a parameterized torus, also known as a pq torus.
+ */
+public class PQTorus extends Mesh {
+
+ private double _p, _q;
+
+ private double _radius, _width;
+
+ private int _steps, _radialSamples;
+
+ public PQTorus() {}
+
+ /**
+ * Creates a parameterized torus. Steps and radialSamples are both degree of accuracy values.
+ *
+ * @param name
+ * The name of the torus.
+ * @param p
+ * The x/z oscillation.
+ * @param q
+ * The y oscillation.
+ * @param radius
+ * The radius of the PQTorus.
+ * @param width
+ * The width of the torus.
+ * @param steps
+ * The steps along the torus.
+ * @param radialSamples
+ * Radial samples for the torus.
+ */
+ public PQTorus(final String name, final double p, final double q, final double radius, final double width,
+ final int steps, final int radialSamples) {
+ super(name);
+
+ _p = p;
+ _q = q;
+ _radius = radius;
+ _width = width;
+ _steps = steps;
+ _radialSamples = radialSamples;
+
+ setGeometryData();
+ setIndexData();
+ }
+
+ private void setGeometryData() {
+ final double THETA_STEP = (MathUtils.TWO_PI / _steps);
+ final double BETA_STEP = (MathUtils.TWO_PI / _radialSamples);
+
+ final Vector3[] toruspoints = new Vector3[_steps];
+ // allocate vertices
+ final int verts = _radialSamples * _steps;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate normals if requested
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate texture coordinates
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ final Vector3 pointB = Vector3.fetchTempInstance();
+ final Vector3 T = Vector3.fetchTempInstance(), N = Vector3.fetchTempInstance(), B = Vector3.fetchTempInstance();
+ final Vector3 tempNormA = Vector3.fetchTempInstance();
+ final Vector3 tempNormB = Vector3.fetchTempInstance();
+ double r, x, y, z, theta = 0.0, beta = 0.0;
+
+ // Move along the length of the pq torus
+ for (int i = 0; i < _steps; i++) {
+ theta += THETA_STEP;
+ final double circleFraction = ((double) i) / (double) _steps;
+
+ // Find the point on the torus
+ r = (0.5 * (2.0 + MathUtils.sin(_q * theta)) * _radius);
+ x = (r * MathUtils.cos(_p * theta) * _radius);
+ y = (r * MathUtils.sin(_p * theta) * _radius);
+ z = (r * MathUtils.cos(_q * theta) * _radius);
+ toruspoints[i] = new Vector3(x, y, z);
+
+ // Now find a point slightly farther along the torus
+ r = (0.5 * (2.0 + MathUtils.sin(_q * (theta + 0.01))) * _radius);
+ x = (r * MathUtils.cos(_p * (theta + 0.01)) * _radius);
+ y = (r * MathUtils.sin(_p * (theta + 0.01)) * _radius);
+ z = (r * MathUtils.cos(_q * (theta + 0.01)) * _radius);
+ pointB.set(x, y, z);
+
+ // Approximate the Frenet Frame
+ pointB.subtract(toruspoints[i], T);
+ toruspoints[i].add(pointB, N);
+ T.cross(N, B);
+ B.cross(T, N);
+
+ // Normalize the two vectors before use
+ N.normalizeLocal();
+ B.normalizeLocal();
+
+ // Create a circle oriented by these new vectors
+ beta = 0.0;
+ for (int j = 0; j < _radialSamples; j++) {
+ beta += BETA_STEP;
+ final double cx = MathUtils.cos(beta) * _width;
+ final double cy = MathUtils.sin(beta) * _width;
+ final double radialFraction = ((double) j) / _radialSamples;
+ tempNormA.setX((cx * N.getX() + cy * B.getX()));
+ tempNormA.setY((cx * N.getY() + cy * B.getY()));
+ tempNormA.setZ((cx * N.getZ() + cy * B.getZ()));
+ tempNormA.normalize(tempNormB);
+ tempNormA.addLocal(toruspoints[i]);
+
+ _meshData.getVertexBuffer().put(tempNormA.getXf()).put(tempNormA.getYf()).put(tempNormA.getZf());
+ _meshData.getNormalBuffer().put(tempNormB.getXf()).put(tempNormB.getYf()).put(tempNormB.getZf());
+ _meshData.getTextureCoords(0).getBuffer().put((float) radialFraction).put((float) circleFraction);
+ }
+ }
+ Vector3.releaseTempInstance(tempNormA);
+ Vector3.releaseTempInstance(tempNormB);
+ Vector3.releaseTempInstance(T);
+ Vector3.releaseTempInstance(N);
+ Vector3.releaseTempInstance(B);
+ Vector3.releaseTempInstance(pointB);
+ }
+
+ private void setIndexData() {
+ final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(6 * _meshData.getVertexCount(),
+ _meshData.getVertexCount() - 1);
+
+ for (int i = _radialSamples; i < _meshData.getVertexCount() + (_radialSamples); i++) {
+ indices.put(i);
+ indices.put(i - _radialSamples);
+ indices.put(i + 1);
+
+ indices.put(i + 1);
+ indices.put(i - _radialSamples);
+ indices.put(i - _radialSamples + 1);
+ }
+
+ for (int i = 0, len = indices.getBufferCapacity(); i < len; i++) {
+ int ind = indices.get(i);
+ if (ind < 0) {
+ ind += _meshData.getVertexCount();
+ indices.put(i, ind);
+ }
+ if (ind >= _meshData.getVertexCount()) {
+ ind -= _meshData.getVertexCount();
+ indices.put(i, ind);
+ }
+ }
+ indices.getBuffer().rewind();
+
+ _meshData.setIndices(indices);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_p, "p", 0);
+ capsule.write(_q, "q", 0);
+ capsule.write(_radius, "radius", 0);
+ capsule.write(_width, "width", 0);
+ capsule.write(_steps, "steps", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _p = capsule.readDouble("p", 0);
+ _q = capsule.readDouble("q", 0);
+ _radius = capsule.readDouble("radius", 0);
+ _width = capsule.readDouble("width", 0);
+ _steps = capsule.readInt("steps", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java
new file mode 100644
index 0000000..9a008f6
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Pyramid.java
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Pyramid</code> provides an extension of <code>Mesh</code>. A pyramid is defined by a width at the base and a
+ * height. The pyramid is a four sided pyramid with the center at (0,0). The pyramid will be axis aligned with the peak
+ * being on the positive y axis and the base being in the x-z plane.
+ */
+public class Pyramid extends Mesh {
+
+ private double _height;
+
+ private double _width;
+
+ public Pyramid() {}
+
+ /**
+ * Constructor instantiates a new <code>Pyramid</code> object. The base width and the height are provided.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparison purposes.
+ * @param width
+ * the base width of the pyramid.
+ * @param height
+ * the height of the pyramid from the base to the peak.
+ */
+ public Pyramid(final String name, final double width, final double height) {
+ super(name);
+ _width = width;
+ _height = height;
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+ }
+
+ /**
+ * <code>setVertexData</code> sets the vertices that make the pyramid. Where the center of the box is the origin and
+ * the base and height are set during construction.
+ */
+ private void setVertexData() {
+ final Vector3 peak = new Vector3(0, _height / 2, 0);
+ final Vector3 vert0 = new Vector3(-_width / 2, -_height / 2, -_width / 2);
+ final Vector3 vert1 = new Vector3(_width / 2, -_height / 2, -_width / 2);
+ final Vector3 vert2 = new Vector3(_width / 2, -_height / 2, _width / 2);
+ final Vector3 vert3 = new Vector3(-_width / 2, -_height / 2, _width / 2);
+
+ final FloatBuffer verts = BufferUtils.createVector3Buffer(16);
+
+ // base
+ verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf());
+ verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf());
+ verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf());
+ verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf());
+
+ // side 1
+ verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf());
+ verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf());
+ verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf());
+
+ // side 2
+ verts.put(vert1.getXf()).put(vert1.getYf()).put(vert1.getZf());
+ verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf());
+ verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf());
+
+ // side 3
+ verts.put(vert2.getXf()).put(vert2.getYf()).put(vert2.getZf());
+ verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf());
+ verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf());
+
+ // side 4
+ verts.put(vert3.getXf()).put(vert3.getYf()).put(vert3.getZf());
+ verts.put(vert0.getXf()).put(vert0.getYf()).put(vert0.getZf());
+ verts.put(peak.getXf()).put(peak.getYf()).put(peak.getZf());
+
+ verts.rewind();
+ _meshData.setVertexBuffer(verts);
+ }
+
+ /**
+ * <code>setNormalData</code> defines the normals of each face of the pyramid.
+ */
+ private void setNormalData() {
+ final FloatBuffer norms = BufferUtils.createVector3Buffer(16);
+
+ // bottom
+ norms.put(0).put(-1).put(0);
+ norms.put(0).put(-1).put(0);
+ norms.put(0).put(-1).put(0);
+ norms.put(0).put(-1).put(0);
+
+ // back
+ norms.put(0).put(0.70710677f).put(-0.70710677f);
+ norms.put(0).put(0.70710677f).put(-0.70710677f);
+ norms.put(0).put(0.70710677f).put(-0.70710677f);
+
+ // right
+ norms.put(0.70710677f).put(0.70710677f).put(0);
+ norms.put(0.70710677f).put(0.70710677f).put(0);
+ norms.put(0.70710677f).put(0.70710677f).put(0);
+
+ // front
+ norms.put(0).put(0.70710677f).put(0.70710677f);
+ norms.put(0).put(0.70710677f).put(0.70710677f);
+ norms.put(0).put(0.70710677f).put(0.70710677f);
+
+ // left
+ norms.put(-0.70710677f).put(0.70710677f).put(0);
+ norms.put(-0.70710677f).put(0.70710677f).put(0);
+ norms.put(-0.70710677f).put(0.70710677f).put(0);
+
+ norms.rewind();
+ _meshData.setNormalBuffer(norms);
+ }
+
+ /**
+ * <code>setTextureData</code> sets the texture that defines the look of the pyramid. The top point of the pyramid
+ * is the top center of the texture, with the remaining texture wrapping around it.
+ */
+ private void setTextureData() {
+ final FloatBuffer texCoords = BufferUtils.createVector2Buffer(16);
+
+ texCoords.put(1).put(0);
+ texCoords.put(0).put(0);
+ texCoords.put(0).put(1);
+ texCoords.put(1).put(1);
+
+ texCoords.put(1).put(0);
+ texCoords.put(0.75f).put(0);
+ texCoords.put(0.5f).put(1);
+
+ texCoords.put(0.75f).put(0);
+ texCoords.put(0.5f).put(0);
+ texCoords.put(0.5f).put(1);
+
+ texCoords.put(0.5f).put(0);
+ texCoords.put(0.25f).put(0);
+ texCoords.put(0.5f).put(1);
+
+ texCoords.put(0.25f).put(0);
+ texCoords.put(0).put(0);
+ texCoords.put(0.5f).put(1);
+
+ texCoords.rewind();
+ _meshData.setTextureBuffer(texCoords, 0);
+ }
+
+ /**
+ * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the
+ * pyramid.
+ */
+ private void setIndexData() {
+ final IndexBufferData<?> indices = BufferUtils.createIndexBufferData(18, 16 - 1);
+ indices.put(3).put(2).put(1);
+ indices.put(3).put(1).put(0);
+ indices.put(6).put(5).put(4);
+ indices.put(9).put(8).put(7);
+ indices.put(12).put(11).put(10);
+ indices.put(15).put(14).put(13);
+
+ indices.getBuffer().rewind();
+ _meshData.setIndices(indices);
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_height, "height", 0);
+ capsule.write(_width, "width", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _height = capsule.readDouble("height", 0);
+ _width = capsule.readDouble("width", 0);
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java
new file mode 100644
index 0000000..e2036fd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Quad.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>Quad</code> defines a four sided, two dimensional shape. The local height of the <code>Quad</code> defines it's
+ * size about the y-axis, while the width defines the x-axis. The z-axis will always be 0.
+ */
+public class Quad extends Mesh {
+
+ protected double _width = 0;
+ protected double _height = 0;
+
+ public Quad() {
+
+ }
+
+ /**
+ * Constructor creates a new <code>Quad</code> object.
+ *
+ * @param name
+ * the name of this <code>Quad</code>.
+ */
+ public Quad(final String name) {
+ this(name, 1, 1);
+ }
+
+ /**
+ * Constructor creates a new <code>Quade</code> object with the provided width and height.
+ *
+ * @param name
+ * the name of the <code>Quad</code>.
+ * @param width
+ * the width of the <code>Quad</code>.
+ * @param height
+ * the height of the <code>Quad</code>.
+ */
+ public Quad(final String name, final double width, final double height) {
+ super(name);
+ initialize(width, height);
+ }
+
+ /**
+ * <code>resize</code> changes the width and height of the given quad by altering its vertices.
+ *
+ * @param width
+ * the new width of the <code>Quad</code>.
+ * @param height
+ * the new height of the <code>Quad</code>.
+ */
+ public void resize(final double width, final double height) {
+ _width = width;
+ _height = height;
+
+ _meshData.getVertexBuffer().clear();
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (-height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (-height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (height / 2)).put(0);
+ }
+
+ /**
+ * <code>initialize</code> builds the data for the <code>Quad</code> object.
+ *
+ * @param width
+ * the width of the <code>Quad</code>.
+ * @param height
+ * the height of the <code>Quad</code>.
+ */
+ private void initialize(final double width, final double height) {
+ final int verts = 4;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+ final FloatBuffer tbuf = BufferUtils.createVector2Buffer(verts);
+ _meshData.setTextureBuffer(tbuf, 0);
+
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+
+ tbuf.put(0).put(1);
+ tbuf.put(0).put(0);
+ tbuf.put(1).put(0);
+ tbuf.put(1).put(1);
+
+ final byte[] indices = { 0, 1, 2, 0, 2, 3 };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+
+ resize(width, height);
+ }
+
+ public double getWidth() {
+ return _width;
+ }
+
+ public double getHeight() {
+ return _height;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java
new file mode 100644
index 0000000..1f252e7
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/RoundedBox.java
@@ -0,0 +1,305 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class RoundedBox extends Mesh {
+
+ private final Vector3 _extent = new Vector3(0.5, 0.5, 0.5);
+ private final Vector3 _border = new Vector3(0.05, 0.05, 0.05);
+ private final Vector3 _slope = new Vector3(0.02, 0.02, 0.02);
+
+ /** Creates a new instance of RoundedBox */
+ public RoundedBox(final String name) {
+ super(name);
+ setData();
+ }
+
+ public RoundedBox(final String name, final Vector3 extent) {
+ super(name);
+ extent.subtract(_slope, _extent);
+ setData();
+ }
+
+ public RoundedBox(final String name, final Vector3 extent, final Vector3 border, final Vector3 slope) {
+ super(name);
+ _border.set(border);
+ _slope.set(slope);
+ extent.subtract(_slope, _extent);
+ setData();
+ }
+
+ private void setData() {
+ setVertexAndNormalData();
+ setTextureData();
+ setIndexData();
+ }
+
+ private void put(final FloatBuffer fb, final FloatBuffer nb, final Vector3 vec) {
+ fb.put((float) vec.getX()).put((float) vec.getY()).put((float) vec.getZ());
+ final Vector3 v = vec.normalize(Vector3.fetchTempInstance());
+ nb.put((float) v.getX()).put((float) v.getY()).put((float) v.getZ());
+ Vector3.releaseTempInstance(v);
+ }
+
+ private void setVertexAndNormalData() {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), 48));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(48));
+ final Vector3[] vert = computeVertices(); // returns 32
+ final FloatBuffer vb = _meshData.getVertexBuffer();
+ final FloatBuffer nb = _meshData.getNormalBuffer();
+
+ // bottom
+ put(vb, nb, vert[0]);
+ put(vb, nb, vert[1]);
+ put(vb, nb, vert[2]);
+ put(vb, nb, vert[3]);
+ put(vb, nb, vert[8]);
+ put(vb, nb, vert[9]);
+ put(vb, nb, vert[10]);
+ put(vb, nb, vert[11]);
+
+ // front
+ put(vb, nb, vert[1]);
+ put(vb, nb, vert[0]);
+ put(vb, nb, vert[5]);
+ put(vb, nb, vert[4]);
+ put(vb, nb, vert[13]);
+ put(vb, nb, vert[12]);
+ put(vb, nb, vert[15]);
+ put(vb, nb, vert[14]);
+
+ // right
+ put(vb, nb, vert[3]);
+ put(vb, nb, vert[1]);
+ put(vb, nb, vert[7]);
+ put(vb, nb, vert[5]);
+ put(vb, nb, vert[17]);
+ put(vb, nb, vert[16]);
+ put(vb, nb, vert[19]);
+ put(vb, nb, vert[18]);
+
+ // back
+ put(vb, nb, vert[2]);
+ put(vb, nb, vert[3]);
+ put(vb, nb, vert[6]);
+ put(vb, nb, vert[7]);
+ put(vb, nb, vert[20]);
+ put(vb, nb, vert[21]);
+ put(vb, nb, vert[22]);
+ put(vb, nb, vert[23]);
+
+ // left
+ put(vb, nb, vert[0]);
+ put(vb, nb, vert[2]);
+ put(vb, nb, vert[4]);
+ put(vb, nb, vert[6]);
+ put(vb, nb, vert[24]);
+ put(vb, nb, vert[25]);
+ put(vb, nb, vert[26]);
+ put(vb, nb, vert[27]);
+
+ // top
+ put(vb, nb, vert[5]);
+ put(vb, nb, vert[4]);
+ put(vb, nb, vert[7]);
+ put(vb, nb, vert[6]);
+ put(vb, nb, vert[29]);
+ put(vb, nb, vert[28]);
+ put(vb, nb, vert[31]);
+ put(vb, nb, vert[30]);
+ }
+
+ private void setTextureData() {
+ if (_meshData.getTextureCoords(0) == null) {
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(48), 0);
+ final FloatBuffer tex = _meshData.getTextureCoords(0).getBuffer();
+
+ final double[][] ratio = new double[][] {
+ { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()),
+ 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()) },
+ { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()),
+ 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) },
+ { 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()),
+ 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) },
+ { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()),
+ 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) },
+ { 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()),
+ 0.5 * _border.getY() / (_extent.getY() + _slope.getY()) },
+ { 0.5 * _border.getX() / (_extent.getX() + _slope.getX()),
+ 0.5 * _border.getZ() / (_extent.getZ() + _slope.getZ()) }, };
+
+ for (int i = 0; i < 6; i++) {
+ tex.put(1).put(0);
+ tex.put(0).put(0);
+ tex.put(1).put(1);
+ tex.put(0).put(1);
+ tex.put((float) (1 - ratio[i][0])).put((float) (0 + ratio[i][1]));
+ tex.put((float) (0 + ratio[i][0])).put((float) (0 + ratio[i][1]));
+ tex.put((float) (1 - ratio[i][0])).put((float) (1 - ratio[i][1]));
+ tex.put((float) (0 + ratio[i][0])).put((float) (1 - ratio[i][1]));
+ }
+ }
+ }
+
+ private void setIndexData() {
+ if (_meshData.getIndexBuffer() == null) {
+ final IndexBufferData<?> buff = BufferUtils.createIndexBufferData(180, 48 - 1);
+ final int[] data = new int[] { 0, 4, 1, 1, 4, 5, 1, 5, 3, 3, 5, 7, 3, 7, 2, 2, 7, 6, 2, 6, 0, 0, 6, 4, 4,
+ 6, 5, 5, 6, 7 };
+ for (int i = 0; i < 6; i++) {
+ for (int n = 0; n < 30; n++) {
+ buff.put(30 * i + n, 8 * i + data[n]);
+ }
+ }
+ _meshData.setIndices(buff);
+ }
+ }
+
+ public Vector3[] computeVertices() {
+ return new Vector3[] {
+ // Cube
+ new Vector3(-_extent.getX(), -_extent.getY(), _extent.getZ()), // 0
+ new Vector3(_extent.getX(), -_extent.getY(), _extent.getZ()), // 1
+ new Vector3(-_extent.getX(), -_extent.getY(), -_extent.getZ()), // 2
+ new Vector3(_extent.getX(), -_extent.getY(), -_extent.getZ()), // 3
+ new Vector3(-_extent.getX(), _extent.getY(), _extent.getZ()), // 4
+ new Vector3(_extent.getX(), _extent.getY(), _extent.getZ()), // 5
+ new Vector3(-_extent.getX(), _extent.getY(), -_extent.getZ()), // 6
+ new Vector3(_extent.getX(), _extent.getY(), -_extent.getZ()), // 7
+ // bottom
+ new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() - _slope.getY(), _extent.getZ()
+ - _border.getZ()), // 8 (0)
+ new Vector3(_extent.getX() - _border.getX(), -_extent.getY() - _slope.getY(), _extent.getZ()
+ - _border.getZ()), // 9
+ // (
+ // 1
+ // )
+ new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() - _slope.getY(), -_extent.getZ()
+ + _border.getZ()), // 10 (2)
+ new Vector3(_extent.getX() - _border.getX(), -_extent.getY() - _slope.getY(), -_extent.getZ()
+ + _border.getZ()), // 11 (3)
+ // front
+ new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() + _border.getY(), _extent.getZ()
+ + _slope.getZ()), // 12 (0)
+ new Vector3(_extent.getX() - _border.getX(), -_extent.getY() + _border.getY(), _extent.getZ()
+ + _slope.getZ()), // 13
+ // (
+ // 1
+ // )
+ new Vector3(-_extent.getX() + _border.getX(), _extent.getY() - _border.getY(), _extent.getZ()
+ + _slope.getZ()), // 14
+ // (
+ // 4
+ // )
+ new Vector3(_extent.getX() - _border.getX(), _extent.getY() - _border.getY(), _extent.getZ()
+ + _slope.getZ()), // 15
+ // (
+ // 5
+ // )
+ // right
+ new Vector3(_extent.getX() + _slope.getX(), -_extent.getY() + _border.getY(), _extent.getZ()
+ - _border.getZ()), // 16
+ // (
+ // 1
+ // )
+ new Vector3(_extent.getX() + _slope.getX(), -_extent.getY() + _border.getY(), -_extent.getZ()
+ + _border.getZ()), // 17 (3)
+ new Vector3(_extent.getX() + _slope.getX(), _extent.getY() - _border.getY(), _extent.getZ()
+ - _border.getZ()), // 18
+ // (
+ // 5
+ // )
+ new Vector3(_extent.getX() + _slope.getX(), _extent.getY() - _border.getY(), -_extent.getZ()
+ + _border.getZ()), // 19
+ // (
+ // 7
+ // )
+ // back
+ new Vector3(-_extent.getX() + _border.getX(), -_extent.getY() + _border.getY(), -_extent.getZ()
+ - _slope.getZ()), // 20 (2)
+ new Vector3(_extent.getX() - _border.getX(), -_extent.getY() + _border.getY(), -_extent.getZ()
+ - _slope.getZ()), // 21 (3)
+ new Vector3(-_extent.getX() + _border.getX(), _extent.getY() - _border.getY(), -_extent.getZ()
+ - _slope.getZ()), // 22 (6)
+ new Vector3(_extent.getX() - _border.getX(), _extent.getY() - _border.getY(), -_extent.getZ()
+ - _slope.getZ()), // 23
+ // (
+ // 7
+ // )
+ // left
+ new Vector3(-_extent.getX() - _slope.getX(), -_extent.getY() + _border.getY(), _extent.getZ()
+ - _border.getZ()), // 24 (0)
+ new Vector3(-_extent.getX() - _slope.getX(), -_extent.getY() + _border.getY(), -_extent.getZ()
+ + _border.getZ()), // 25 (2)
+ new Vector3(-_extent.getX() - _slope.getX(), _extent.getY() - _border.getY(), _extent.getZ()
+ - _border.getZ()), // 26
+ // (
+ // 4
+ // )
+ new Vector3(-_extent.getX() - _slope.getX(), _extent.getY() - _border.getY(), -_extent.getZ()
+ + _border.getZ()), // 27 (6)
+ // top
+ new Vector3(-_extent.getX() + _border.getX(), _extent.getY() + _slope.getY(), _extent.getZ()
+ - _border.getZ()), // 28
+ // (
+ // 4
+ // )
+ new Vector3(_extent.getX() - _border.getX(), _extent.getY() + _slope.getY(), _extent.getZ()
+ - _border.getZ()), // 29
+ // (
+ // 5
+ // )
+ new Vector3(-_extent.getX() + _border.getX(), _extent.getY() + _slope.getY(), -_extent.getZ()
+ + _border.getZ()), // 30 (6)
+ new Vector3(_extent.getX() - _border.getX(), _extent.getY() + _slope.getY(), -_extent.getZ()
+ + _border.getZ()), // 31
+ // (
+ // 7
+ // )
+ };
+ }
+
+ /**
+ * <code>clone</code> creates a new RoundedBox object containing the same data as this one.
+ *
+ * @return the new Box
+ */
+ @Override
+ public RoundedBox clone() {
+ return new RoundedBox(getName() + "_clone", _extent.clone(), _border.clone(), _slope.clone());
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_extent, "extent", new Vector3(Vector3.ZERO));
+ capsule.write(_border, "border", new Vector3(Vector3.ZERO));
+ capsule.write(_slope, "slope", new Vector3(Vector3.ZERO));
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _extent.set((Vector3) capsule.readSavable("extent", new Vector3(Vector3.ZERO)));
+ _border.set((Vector3) capsule.readSavable("border", new Vector3(Vector3.ZERO)));
+ _slope.set((Vector3) capsule.readSavable("slope", new Vector3(Vector3.ZERO)));
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java
new file mode 100644
index 0000000..46e1e18
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Sphere.java
@@ -0,0 +1,436 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Sphere represents a 3D object with all points equi-distance from a center point.
+ */
+public class Sphere extends Mesh {
+
+ public enum TextureMode {
+ Linear, Projected, Polar;
+ }
+
+ protected int _zSamples;
+
+ protected int _radialSamples;
+
+ /** the distance from the center point each point falls on */
+ public double _radius;
+ /** the center of the sphere */
+ public final Vector3 _center = new Vector3();
+
+ protected TextureMode _textureMode = TextureMode.Linear;
+
+ protected boolean _viewInside = false;
+
+ public Sphere() {}
+
+ /**
+ * Constructs a sphere. By default the Sphere has not geometry data or center.
+ *
+ * @param name
+ * The name of the sphere.
+ */
+ public Sphere(final String name) {
+ super(name);
+ }
+
+ /**
+ * Constructs a sphere with center at the origin. For details, see the other constructor.
+ *
+ * @param name
+ * Name of sphere.
+ * @param zSamples
+ * The samples along the Z.
+ * @param radialSamples
+ * The samples along the radial.
+ * @param radius
+ * Radius of the sphere.
+ * @see #Sphere(java.lang.String, com.ardor3d.math.Vector3, int, int, double)
+ */
+ public Sphere(final String name, final int zSamples, final int radialSamples, final double radius) {
+ this(name, new Vector3(0, 0, 0), zSamples, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a sphere. All geometry data buffers are updated automatically. Both zSamples and radialSamples
+ * increase the quality of the generated sphere.
+ *
+ * @param name
+ * Name of the sphere.
+ * @param center
+ * Center of the sphere.
+ * @param zSamples
+ * The number of samples along the Z.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the sphere.
+ */
+ public Sphere(final String name, final ReadOnlyVector3 center, final int zSamples, final int radialSamples,
+ final double radius) {
+ super(name);
+ setData(center, zSamples, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a sphere. All geometry data buffers are updated automatically. Both zSamples and radialSamples
+ * increase the quality of the generated sphere.
+ *
+ * @param name
+ * Name of the sphere.
+ * @param center
+ * Center of the sphere.
+ * @param zSamples
+ * The number of samples along the Z.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the sphere.
+ * @param textureMode
+ * the mode to use when setting uv coordinates for this Sphere.
+ */
+ public Sphere(final String name, final ReadOnlyVector3 center, final int zSamples, final int radialSamples,
+ final double radius, final TextureMode textureMode) {
+ super(name);
+ _textureMode = textureMode;
+ setData(center, zSamples, radialSamples, radius);
+ }
+
+ /**
+ * Changes the information of the sphere into the given values.
+ *
+ * @param center
+ * The new center of the sphere.
+ * @param zSamples
+ * The new number of zSamples of the sphere.
+ * @param radialSamples
+ * The new number of radial samples of the sphere.
+ * @param radius
+ * The new radius of the sphere.
+ */
+ public void setData(final ReadOnlyVector3 center, final int zSamples, final int radialSamples, final double radius) {
+ _center.set(center);
+ _zSamples = zSamples;
+ _radialSamples = radialSamples;
+ _radius = radius;
+
+ setGeometryData();
+ setIndexData();
+ }
+
+ /**
+ * builds the vertices based on the radius, center and radial and zSamples.
+ */
+ private void setGeometryData() {
+ // allocate vertices
+ final int verts = (_zSamples - 2) * (_radialSamples + 1) + 2;
+ final FloatBufferData vertsData = _meshData.getVertexCoords();
+ if (vertsData == null) {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+ } else {
+ vertsData.setBuffer(BufferUtils.createVector3Buffer(vertsData.getBuffer(), verts));
+ }
+
+ // allocate normals if requested
+ final FloatBufferData normsData = _meshData.getNormalCoords();
+ if (normsData == null) {
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+ } else {
+ normsData.setBuffer(BufferUtils.createVector3Buffer(normsData.getBuffer(), verts));
+ }
+
+ // allocate texture coordinates
+ final FloatBufferData texData = _meshData.getTextureCoords(0);
+ if (texData == null) {
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+ } else {
+ texData.setBuffer(BufferUtils.createVector2Buffer(texData.getBuffer(), verts));
+ }
+
+ // generate geometry
+ final double fInvRS = 1.0 / _radialSamples;
+ final double fZFactor = 2.0 / (_zSamples - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a sphere slice.
+ final double[] afSin = new double[(_radialSamples + 1)];
+ final double[] afCos = new double[(_radialSamples + 1)];
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final double fAngle = MathUtils.TWO_PI * fInvRS * iR;
+ afCos[iR] = MathUtils.cos(fAngle);
+ afSin[iR] = MathUtils.sin(fAngle);
+ }
+ afSin[_radialSamples] = afSin[0];
+ afCos[_radialSamples] = afCos[0];
+
+ // generate the sphere itself
+ int i = 0;
+ final Vector3 tempVa = Vector3.fetchTempInstance();
+ final Vector3 tempVb = Vector3.fetchTempInstance();
+ final Vector3 tempVc = Vector3.fetchTempInstance();
+ for (int iZ = 1; iZ < (_zSamples - 1); iZ++) {
+ final double fAFraction = MathUtils.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2)
+ final double fZFraction = MathUtils.sin(fAFraction); // in (-1,1)
+ final double fZ = _radius * fZFraction;
+
+ // compute center of slice
+ final Vector3 kSliceCenter = tempVb.set(_center);
+ kSliceCenter.setZ(kSliceCenter.getZ() + fZ);
+
+ // compute radius of slice
+ final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fZ * fZ));
+
+ // compute slice vertices with duplication at end point
+ Vector3 kNormal;
+ final int iSave = i;
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final double fRadialFraction = iR * fInvRS; // in [0,1)
+ final Vector3 kRadial = tempVc.set(afCos[iR], afSin[iR], 0);
+ kRadial.multiply(fSliceRadius, tempVa);
+ _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX()))
+ .put((float) (kSliceCenter.getY() + tempVa.getY()))
+ .put((float) (kSliceCenter.getZ() + tempVa.getZ()));
+
+ BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i);
+ kNormal = tempVa.subtractLocal(_center);
+ kNormal.normalizeLocal();
+ if (!_viewInside) {
+ _meshData.getNormalBuffer().put(kNormal.getXf()).put(kNormal.getYf()).put(kNormal.getZf());
+ } else {
+ _meshData.getNormalBuffer().put(-kNormal.getXf()).put(-kNormal.getYf()).put(-kNormal.getZf());
+ }
+
+ if (_textureMode == TextureMode.Linear) {
+ _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction)
+ .put((float) (0.5 * (fZFraction + 1.0)));
+ } else if (_textureMode == TextureMode.Projected) {
+ _meshData.getTextureCoords(0).getBuffer().put((float) fRadialFraction)
+ .put((float) (MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(fZFraction))));
+ } else if (_textureMode == TextureMode.Polar) {
+ final double r = (MathUtils.HALF_PI - Math.abs(fAFraction)) / MathUtils.PI;
+ final double u = r * afCos[iR] + 0.5;
+ final double v = r * afSin[iR] + 0.5;
+ _meshData.getTextureCoords(0).getBuffer().put((float) u).put((float) v);
+ }
+
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i);
+ BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i);
+
+ if (_textureMode == TextureMode.Linear) {
+ _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) (0.5 * (fZFraction + 1.0)));
+ } else if (_textureMode == TextureMode.Projected) {
+ _meshData.getTextureCoords(0).getBuffer().put(1.0f)
+ .put((float) (MathUtils.INV_PI * (MathUtils.HALF_PI + Math.asin(fZFraction))));
+ } else if (_textureMode == TextureMode.Polar) {
+ final float r = (float) ((MathUtils.HALF_PI - Math.abs(fAFraction)) / MathUtils.PI);
+ _meshData.getTextureCoords(0).getBuffer().put(r + 0.5f).put(0.5f);
+ }
+
+ i++;
+ }
+
+ // south pole
+ _meshData.getVertexBuffer().position(i * 3);
+ _meshData.getVertexBuffer().put(_center.getXf()).put(_center.getYf()).put((float) (_center.getZ() - _radius));
+
+ _meshData.getNormalBuffer().position(i * 3);
+ if (!_viewInside) {
+ // TODO: allow for inner texture orientation later.
+ _meshData.getNormalBuffer().put(0).put(0).put(-1);
+ } else {
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ }
+
+ _meshData.getTextureCoords(0).getBuffer().position(i * 2);
+ if (_textureMode == TextureMode.Polar) {
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f);
+ } else {
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.0f);
+ }
+
+ i++;
+
+ // north pole
+ _meshData.getVertexBuffer().put(_center.getXf()).put(_center.getYf()).put((float) (_center.getZ() + _radius));
+
+ if (!_viewInside) {
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ } else {
+ _meshData.getNormalBuffer().put(0).put(0).put(-1);
+ }
+
+ if (_textureMode == TextureMode.Polar) {
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(0.5f);
+ } else {
+ _meshData.getTextureCoords(0).getBuffer().put(0.5f).put(1.0f);
+ }
+ Vector3.releaseTempInstance(tempVa);
+ Vector3.releaseTempInstance(tempVb);
+ Vector3.releaseTempInstance(tempVc);
+ }
+
+ /**
+ * sets the indices for rendering the sphere.
+ */
+ private void setIndexData() {
+ // allocate connectivity
+ final int verts = (_zSamples - 2) * (_radialSamples + 1) + 2;
+ final int tris = 2 * (_zSamples - 2) * _radialSamples;
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+
+ // generate connectivity
+ for (int iZ = 0, iZStart = 0; iZ < (_zSamples - 3); iZ++) {
+ int i0 = iZStart;
+ int i1 = i0 + 1;
+ iZStart += (_radialSamples + 1);
+ int i2 = iZStart;
+ int i3 = i2 + 1;
+ for (int i = 0; i < _radialSamples; i++) {
+ if (!_viewInside) {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i3++);
+ _meshData.getIndices().put(i2++);
+ } else // inside view
+ {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i2++);
+ _meshData.getIndices().put(i3++);
+ }
+ }
+ }
+
+ // south pole triangles
+ for (int i = 0; i < _radialSamples; i++) {
+ if (!_viewInside) {
+ _meshData.getIndices().put(i);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 2);
+ _meshData.getIndices().put(i + 1);
+ } else // inside view
+ {
+ _meshData.getIndices().put(i);
+ _meshData.getIndices().put(i + 1);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 2);
+ }
+ }
+
+ // north pole triangles
+ final int iOffset = (_zSamples - 3) * (_radialSamples + 1);
+ for (int i = 0; i < _radialSamples; i++) {
+ if (!_viewInside) {
+ _meshData.getIndices().put(i + iOffset);
+ _meshData.getIndices().put(i + 1 + iOffset);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ } else // inside view
+ {
+ _meshData.getIndices().put(i + iOffset);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ _meshData.getIndices().put(i + 1 + iOffset);
+ }
+ }
+ }
+
+ /**
+ * Returns the center of this sphere.
+ *
+ * @return The sphere's center.
+ */
+ public Vector3 getCenter() {
+ return _center;
+ }
+
+ /**
+ *
+ * @return true if the normals are inverted to point into the sphere so that the face is oriented for a viewer
+ * inside the sphere. false (the default) for exterior viewing.
+ */
+ public boolean isViewFromInside() {
+ return _viewInside;
+ }
+
+ /**
+ *
+ * @param viewInside
+ * if true, the normals are inverted to point into the sphere so that the face is oriented for a viewer
+ * inside the sphere. Default is false (for outside viewing)
+ */
+ public void setViewFromInside(final boolean viewInside) {
+ if (viewInside != _viewInside) {
+ _viewInside = viewInside;
+ setGeometryData();
+ setIndexData();
+ }
+ }
+
+ /**
+ * @return Returns the textureMode.
+ */
+ public TextureMode getTextureMode() {
+ return _textureMode;
+ }
+
+ /**
+ * @param textureMode
+ * The textureMode to set.
+ */
+ public void setTextureMode(final TextureMode textureMode) {
+ _textureMode = textureMode;
+ setGeometryData();
+ setIndexData();
+ }
+
+ public double getRadius() {
+ return _radius;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_zSamples, "zSamples", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_radius, "radius", 0);
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+ capsule.write(_textureMode, "textureMode", TextureMode.Linear);
+ capsule.write(_viewInside, "viewInside", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _zSamples = capsule.readInt("zSamples", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _radius = capsule.readDouble("radius", 0);
+ _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)));
+ _textureMode = capsule.readEnum("textureMode", TextureMode.class, TextureMode.Linear);
+ _viewInside = capsule.readBoolean("viewInside", false);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java
new file mode 100644
index 0000000..9c98bee
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/StripBox.java
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class StripBox extends Mesh {
+
+ public double _xExtent, _yExtent, _zExtent;
+
+ public final Vector3 _center = new Vector3(0f, 0f, 0f);
+
+ /**
+ * instantiates a new <code>StripBox</code> object. All information must be applies later. For internal usage only
+ */
+ public StripBox() {
+ super("temp");
+ }
+
+ /**
+ * Constructor instantiates a new <code>StripBox</code> object. Center and vertice information must be supplied
+ * later.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparision purposes.
+ */
+ public StripBox(final String name) {
+ super(name);
+ }
+
+ /**
+ * Constructor instantiates a new <code>StripBox</code> object. The minimum and maximum point are provided. These
+ * two points define the shape and size of the box, but not it's orientation or position. You should use the
+ * <code>setTranslation</code> and <code>setLocalRotation</code> for those attributes.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparison purposes.
+ * @param min
+ * the minimum point that defines the box.
+ * @param max
+ * the maximum point that defines the box.
+ */
+ public StripBox(final String name, final Vector3 min, final Vector3 max) {
+ super(name);
+ setData(min, max);
+ }
+
+ /**
+ * Constructs a new box. The box has the given center and extends in the x, y, and z out from the center (+ and -)
+ * by the given amounts. So, for example, a box with extent of .5 would be the unit cube.
+ *
+ * @param name
+ * Name of the box.
+ * @param center
+ * Center of the box.
+ * @param xExtent
+ * x extent of the box, in both directions.
+ * @param yExtent
+ * y extent of the box, in both directions.
+ * @param zExtent
+ * z extent of the box, in both directions.
+ */
+ public StripBox(final String name, final Vector3 center, final double xExtent, final double yExtent,
+ final double zExtent) {
+ super(name);
+ setData(center, xExtent, yExtent, zExtent);
+ }
+
+ /**
+ * Changes the data of the box so that the two opposite corners are minPoint and maxPoint. The other corners are
+ * created from those two poitns. If update buffers is flagged as true, the vertex/normal/texture/color/index
+ * buffers are updated when the data is changed.
+ *
+ * @param minPoint
+ * The new minPoint of the box.
+ * @param maxPoint
+ * The new maxPoint of the box.
+ */
+ public void setData(final Vector3 minPoint, final Vector3 maxPoint) {
+ _center.set(maxPoint).addLocal(minPoint).multiplyLocal(0.5f);
+
+ final double x = maxPoint.getX() - _center.getX();
+ final double y = maxPoint.getY() - _center.getY();
+ final double z = maxPoint.getZ() - _center.getZ();
+ setData(_center, x, y, z);
+ }
+
+ /**
+ * Changes the data of the box so that its center is <code>center</code> and it extends in the x, y, and z
+ * directions by the given extent. Note that the actual sides will be 2x the given extent values because the box
+ * extends in + & - from the center for each extent.
+ *
+ * @param center
+ * The center of the box.
+ * @param xExtent
+ * x extent of the box, in both directions.
+ * @param yExtent
+ * y extent of the box, in both directions.
+ * @param zExtent
+ * z extent of the box, in both directions.
+ */
+ public void setData(final Vector3 center, final double xExtent, final double yExtent, final double zExtent) {
+ if (center != null) {
+ _center.set(center);
+ }
+
+ _xExtent = xExtent;
+ _yExtent = yExtent;
+ _zExtent = zExtent;
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ /**
+ *
+ * <code>setVertexData</code> sets the vertex positions that define the box. These eight points are determined from
+ * the minimum and maximum point.
+ *
+ */
+ private void setVertexData() {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), 8));
+ final Vector3[] vert = computeVertices(); // returns 8
+ _meshData.getVertexBuffer().clear();
+ _meshData.getVertexBuffer().put(vert[0].getXf()).put(vert[0].getYf()).put(vert[0].getZf());
+ _meshData.getVertexBuffer().put(vert[1].getXf()).put(vert[1].getYf()).put(vert[1].getZf());
+ _meshData.getVertexBuffer().put(vert[2].getXf()).put(vert[2].getYf()).put(vert[2].getZf());
+ _meshData.getVertexBuffer().put(vert[3].getXf()).put(vert[3].getYf()).put(vert[3].getZf());
+ _meshData.getVertexBuffer().put(vert[4].getXf()).put(vert[4].getYf()).put(vert[4].getZf());
+ _meshData.getVertexBuffer().put(vert[5].getXf()).put(vert[5].getYf()).put(vert[5].getZf());
+ _meshData.getVertexBuffer().put(vert[6].getXf()).put(vert[6].getYf()).put(vert[6].getZf());
+ _meshData.getVertexBuffer().put(vert[7].getXf()).put(vert[7].getYf()).put(vert[7].getZf());
+ }
+
+ /**
+ *
+ * <code>setNormalData</code> sets the normals of each of the box's planes.
+ *
+ *
+ */
+ private void setNormalData() {
+ final Vector3[] vert = computeVertices(); // returns 8
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), 8));
+ final Vector3 norm = new Vector3();
+
+ _meshData.getNormalBuffer().clear();
+ for (int i = 0; i < 8; i++) {
+ norm.set(vert[i]).normalizeLocal();
+ _meshData.getNormalBuffer().put(norm.getXf()).put(norm.getYf()).put(norm.getZf());
+ }
+ }
+
+ /**
+ *
+ * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where
+ * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six
+ * faces.
+ *
+ */
+ private void setTextureData() {
+ if (_meshData.getTextureCoords(0) == null) {
+ _meshData.setTextureCoords(new FloatBufferData(BufferUtils.createVector2Buffer(8), 2), 0);
+ final FloatBuffer tex = _meshData.getTextureCoords(0).getBuffer();
+ tex.put(1).put(0); // 0
+ tex.put(0).put(0); // 1
+ tex.put(0).put(1); // 2
+ tex.put(1).put(1); // 3
+ tex.put(1).put(0); // 4
+ tex.put(0).put(0); // 5
+ tex.put(1).put(1); // 6
+ tex.put(0).put(1); // 7
+ }
+ }
+
+ /**
+ *
+ * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the
+ * box.
+ *
+ */
+ private void setIndexData() {
+ _meshData.setIndexMode(IndexMode.TriangleStrip);
+ if (_meshData.getIndexBuffer() == null) {
+ final byte[] indices = new byte[] { 2, 3, 6, 7, 5, 3, 0, 2, 1, 6, 4, 5, 1, 0 };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+ }
+ }
+
+ /**
+ * <code>clone</code> creates a new StripBox object containing the same data as this one.
+ *
+ * @return the new StripBox
+ */
+ @Override
+ public StripBox clone() {
+ return new StripBox(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent);
+ }
+
+ /**
+ *
+ * @return a size 8 array of Vectors representing the 8 points of the box.
+ */
+ public Vector3[] computeVertices() {
+
+ final Vector3 akEAxis[] = { Vector3.UNIT_X.multiply(_xExtent, Vector3.fetchTempInstance()),
+ Vector3.UNIT_Y.multiply(_yExtent, Vector3.fetchTempInstance()),
+ Vector3.UNIT_Z.multiply(_zExtent, Vector3.fetchTempInstance()) };
+
+ final Vector3 rVal[] = new Vector3[8];
+ rVal[0] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
+ rVal[1] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
+ rVal[2] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
+ rVal[3] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).subtractLocal(akEAxis[2]);
+ rVal[4] = _center.add(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]);
+ rVal[5] = _center.subtract(akEAxis[0], new Vector3()).subtractLocal(akEAxis[1]).addLocal(akEAxis[2]);
+ rVal[6] = _center.add(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]);
+ rVal[7] = _center.subtract(akEAxis[0], new Vector3()).addLocal(akEAxis[1]).addLocal(akEAxis[2]);
+ for (final Vector3 axis : akEAxis) {
+ Vector3.releaseTempInstance(axis);
+ }
+ return rVal;
+ }
+
+ /**
+ * Returns the current center of the box.
+ *
+ * @return The box's center.
+ */
+ public Vector3 getCenter() {
+ return _center;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xExtent, "xExtent", 0);
+ capsule.write(_yExtent, "yExtent", 0);
+ capsule.write(_zExtent, "zExtent", 0);
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xExtent = capsule.readDouble("xExtent", 0);
+ _yExtent = capsule.readDouble("yExtent", 0);
+ _zExtent = capsule.readDouble("zExtent", 0);
+ _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)));
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java
new file mode 100644
index 0000000..06945f2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Teapot.java
@@ -0,0 +1,852 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Teapot is the classical teapot model ready for you to use in ardor3d! If you plan to texture this shape, use wrapmode
+ * WM_WRAP_S_WRAP_T.
+ *
+ * @see "http://www.sjbaker.org/teapot/"
+ */
+public class Teapot extends Mesh {
+
+ /**
+ * Instantiates a new Teapot object.
+ */
+ public Teapot() {
+ super("teapot");
+ resetData();
+ }
+
+ /**
+ * Constructor instantiates a new Teapot object.
+ *
+ * @param name
+ * the name of the scene element. This is required for identification and comparison purposes.
+ */
+ public Teapot(final String name) {
+ super(name);
+ resetData();
+ }
+
+ /**
+ * sets up the data for the Teapot
+ */
+ public void resetData() {
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+ }
+
+ private void setVertexData() {
+ final float[] verts = new float[] { 1.4403734f, 2.7980254f, 0.6128418f, 1.5613803f, 2.7980254f, 0.0f,
+ 1.5834712f, 2.7145221f, 0.0f, 1.4607521f, 2.7145221f, 0.62151235f, 1.4640129f, 2.8258598f, 0.6228997f,
+ 1.5870057f, 2.8258598f, 0.0f, 1.5121067f, 2.7980254f, 0.6433625f, 1.6391401f, 2.7980254f, 0.0f,
+ 1.5650915f, 2.7145221f, 0.6659062f, 1.6965764f, 2.7145221f, 0.0f, 1.10858f, 2.7980254f, 1.10858f,
+ 1.1242645f, 2.7145221f, 1.1242645f, 1.1267741f, 2.8258598f, 1.1267741f, 1.1637895f, 2.7980254f,
+ 1.1637895f, 1.2045691f, 2.7145221f, 1.2045691f, 0.6128418f, 2.7980254f, 1.4403734f, 0.62151235f,
+ 2.7145221f, 1.4607521f, 0.6228997f, 2.8258598f, 1.4640129f, 0.6433625f, 2.7980254f, 1.5121067f,
+ 0.6659062f, 2.7145221f, 1.5650915f, 0.0f, 2.7980254f, 1.5613803f, 0.0f, 2.7145221f, 1.5834712f, 0.0f,
+ 2.8258598f, 1.5870057f, 0.0f, 2.7980254f, 1.6391401f, 0.0f, 2.7145221f, 1.6965764f, -0.63095903f,
+ 2.7980254f, 1.4403734f, 0.0f, 2.7980254f, 1.5613803f, 0.0f, 2.7145221f, 1.5834712f, -0.664457f,
+ 2.7145221f, 1.4607521f, -0.6282678f, 2.8258598f, 1.4640129f, 0.0f, 2.8258598f, 1.5870057f, -0.6440335f,
+ 2.7980254f, 1.5121067f, 0.0f, 2.7980254f, 1.6391401f, -0.6659062f, 2.7145221f, 1.5650915f, 0.0f,
+ 2.7145221f, 1.6965764f, -1.1246843f, 2.7980254f, 1.10858f, -1.1624376f, 2.7145221f, 1.1242645f,
+ -1.1315457f, 2.8258598f, 1.1267741f, -1.1643858f, 2.7980254f, 1.1637895f, -1.2045691f, 2.7145221f,
+ 1.2045691f, -1.4464124f, 2.7980254f, 0.6128418f, -1.475067f, 2.7145221f, 0.62151235f, -1.4658021f,
+ 2.8258598f, 0.6228997f, -1.5123304f, 2.7980254f, 0.6433625f, -1.5650915f, 2.7145221f, 0.6659062f,
+ -1.5613803f, 2.7980254f, 0.0f, -1.5834712f, 2.7145221f, 0.0f, -1.5870057f, 2.8258598f, 0.0f,
+ -1.6391401f, 2.7980254f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -1.4403734f, 2.7980254f, -0.6128418f,
+ -1.5613803f, 2.7980254f, 0.0f, -1.5834712f, 2.7145221f, 0.0f, -1.4607521f, 2.7145221f, -0.62151235f,
+ -1.4640129f, 2.8258598f, -0.6228997f, -1.5870057f, 2.8258598f, 0.0f, -1.5121067f, 2.7980254f,
+ -0.6433625f, -1.6391401f, 2.7980254f, 0.0f, -1.5650915f, 2.7145221f, -0.6659062f, -1.6965764f,
+ 2.7145221f, 0.0f, -1.10858f, 2.7980254f, -1.10858f, -1.1242645f, 2.7145221f, -1.1242645f, -1.1267741f,
+ 2.8258598f, -1.1267741f, -1.1637895f, 2.7980254f, -1.1637895f, -1.2045691f, 2.7145221f, -1.2045691f,
+ -0.6128418f, 2.7980254f, -1.4403734f, -0.62151235f, 2.7145221f, -1.4607521f, -0.6228997f, 2.8258598f,
+ -1.4640129f, -0.6433625f, 2.7980254f, -1.5121067f, -0.6659062f, 2.7145221f, -1.5650915f, 0.0f,
+ 2.7980254f, -1.5613803f, 0.0f, 2.7145221f, -1.5834712f, 0.0f, 2.8258598f, -1.5870057f, 0.0f,
+ 2.7980254f, -1.6391401f, 0.0f, 2.7145221f, -1.6965764f, 0.6128418f, 2.7980254f, -1.4403734f, 0.0f,
+ 2.7980254f, -1.5613803f, 0.0f, 2.7145221f, -1.5834712f, 0.62151235f, 2.7145221f, -1.4607521f,
+ 0.6228997f, 2.8258598f, -1.4640129f, 0.0f, 2.8258598f, -1.5870057f, 0.6433625f, 2.7980254f,
+ -1.5121067f, 0.0f, 2.7980254f, -1.6391401f, 0.6659062f, 2.7145221f, -1.5650915f, 0.0f, 2.7145221f,
+ -1.6965764f, 1.10858f, 2.7980254f, -1.10858f, 1.1242645f, 2.7145221f, -1.1242645f, 1.1267741f,
+ 2.8258598f, -1.1267741f, 1.1637895f, 2.7980254f, -1.1637895f, 1.2045691f, 2.7145221f, -1.2045691f,
+ 1.4403734f, 2.7980254f, -0.6128418f, 1.4607521f, 2.7145221f, -0.62151235f, 1.4640129f, 2.8258598f,
+ -0.6228997f, 1.5121067f, 2.7980254f, -0.6433625f, 1.5650915f, 2.7145221f, -0.6659062f, 1.5613803f,
+ 2.7980254f, 0.0f, 1.5834712f, 2.7145221f, 0.0f, 1.5870057f, 2.8258598f, 0.0f, 1.6391401f, 2.7980254f,
+ 0.0f, 1.6965764f, 2.7145221f, 0.0f, 1.7566524f, 2.2704964f, 0.7474103f, 1.9042302f, 2.2704964f, 0.0f,
+ 1.6965764f, 2.7145221f, 0.0f, 1.5650915f, 2.7145221f, 0.6659062f, 1.9237585f, 1.8344232f, 0.8185097f,
+ 2.085375f, 1.8344232f, 0.0f, 2.0419555f, 1.4142554f, 0.86879945f, 2.2135017f, 1.4142554f, 0.0f,
+ 2.086789f, 1.0179458f, 0.88787496f, 2.2621017f, 1.0179458f, 0.0f, 1.3520035f, 2.2704964f, 1.3520035f,
+ 1.2045691f, 2.7145221f, 1.2045691f, 1.4806162f, 1.8344232f, 1.4806162f, 1.5715863f, 1.4142554f,
+ 1.5715863f, 1.6060922f, 1.0179458f, 1.6060922f, 0.7474103f, 2.2704964f, 1.7566524f, 0.6659062f,
+ 2.7145221f, 1.5650915f, 0.8185097f, 1.8344232f, 1.9237585f, 0.86879945f, 1.4142554f, 2.0419555f,
+ 0.88787496f, 1.0179458f, 2.086789f, 0.0f, 2.2704964f, 1.9042302f, 0.0f, 2.7145221f, 1.6965764f, 0.0f,
+ 1.8344232f, 2.085375f, 0.0f, 1.4142554f, 2.2135017f, 0.0f, 1.0179458f, 2.2621017f, -0.7474103f,
+ 2.2704964f, 1.7566524f, 0.0f, 2.2704964f, 1.9042302f, 0.0f, 2.7145221f, 1.6965764f, -0.6659062f,
+ 2.7145221f, 1.5650915f, -0.8185097f, 1.8344232f, 1.9237585f, 0.0f, 1.8344232f, 2.085375f, -0.86879945f,
+ 1.4142554f, 2.0419555f, 0.0f, 1.4142554f, 2.2135017f, -0.88787496f, 1.0179458f, 2.086789f, 0.0f,
+ 1.0179458f, 2.2621017f, -1.3520035f, 2.2704964f, 1.3520035f, -1.2045691f, 2.7145221f, 1.2045691f,
+ -1.4806162f, 1.8344232f, 1.4806162f, -1.5715863f, 1.4142554f, 1.5715863f, -1.6060922f, 1.0179458f,
+ 1.6060922f, -1.7566524f, 2.2704964f, 0.7474103f, -1.5650915f, 2.7145221f, 0.6659062f, -1.9237585f,
+ 1.8344232f, 0.8185097f, -2.0419555f, 1.4142554f, 0.86879945f, -2.086789f, 1.0179458f, 0.88787496f,
+ -1.9042302f, 2.2704964f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -2.085375f, 1.8344232f, 0.0f,
+ -2.2135017f, 1.4142554f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -1.7566524f, 2.2704964f, -0.7474103f,
+ -1.9042302f, 2.2704964f, 0.0f, -1.6965764f, 2.7145221f, 0.0f, -1.5650915f, 2.7145221f, -0.6659062f,
+ -1.9237585f, 1.8344232f, -0.8185097f, -2.085375f, 1.8344232f, 0.0f, -2.0419555f, 1.4142554f,
+ -0.86879945f, -2.2135017f, 1.4142554f, 0.0f, -2.086789f, 1.0179458f, -0.88787496f, -2.2621017f,
+ 1.0179458f, 0.0f, -1.3520035f, 2.2704964f, -1.3520035f, -1.2045691f, 2.7145221f, -1.2045691f,
+ -1.4806162f, 1.8344232f, -1.4806162f, -1.5715863f, 1.4142554f, -1.5715863f, -1.6060922f, 1.0179458f,
+ -1.6060922f, -0.7474103f, 2.2704964f, -1.7566524f, -0.6659062f, 2.7145221f, -1.5650915f, -0.8185097f,
+ 1.8344232f, -1.9237585f, -0.86879945f, 1.4142554f, -2.0419555f, -0.88787496f, 1.0179458f, -2.086789f,
+ 0.0f, 2.2704964f, -1.9042302f, 0.0f, 2.7145221f, -1.6965764f, 0.0f, 1.8344232f, -2.085375f, 0.0f,
+ 1.4142554f, -2.2135017f, 0.0f, 1.0179458f, -2.2621017f, 0.7474103f, 2.2704964f, -1.7566524f, 0.0f,
+ 2.2704964f, -1.9042302f, 0.0f, 2.7145221f, -1.6965764f, 0.6659062f, 2.7145221f, -1.5650915f,
+ 0.8185097f, 1.8344232f, -1.9237585f, 0.0f, 1.8344232f, -2.085375f, 0.86879945f, 1.4142554f,
+ -2.0419555f, 0.0f, 1.4142554f, -2.2135017f, 0.88787496f, 1.0179458f, -2.086789f, 0.0f, 1.0179458f,
+ -2.2621017f, 1.3520035f, 2.2704964f, -1.3520035f, 1.2045691f, 2.7145221f, -1.2045691f, 1.4806162f,
+ 1.8344232f, -1.4806162f, 1.5715863f, 1.4142554f, -1.5715863f, 1.6060922f, 1.0179458f, -1.6060922f,
+ 1.7566524f, 2.2704964f, -0.7474103f, 1.5650915f, 2.7145221f, -0.6659062f, 1.9237585f, 1.8344232f,
+ -0.8185097f, 2.0419555f, 1.4142554f, -0.86879945f, 2.086789f, 1.0179458f, -0.88787496f, 1.9042302f,
+ 2.2704964f, 0.0f, 1.6965764f, 2.7145221f, 0.0f, 2.085375f, 1.8344232f, 0.0f, 2.2135017f, 1.4142554f,
+ 0.0f, 2.2621017f, 1.0179458f, 0.0f, 2.0052736f, 0.6826069f, 0.85319227f, 2.1737385f, 0.6826069f, 0.0f,
+ 2.2621017f, 1.0179458f, 0.0f, 2.086789f, 1.0179458f, 0.88787496f, 1.8259401f, 0.43474767f, 0.7768906f,
+ 1.9793389f, 0.43474767f, 0.0f, 1.6466068f, 0.26641548f, 0.7005888f, 1.7849396f, 0.26641548f, 0.0f,
+ 1.5650915f, 0.16965766f, 0.6659062f, 1.6965764f, 0.16965766f, 0.0f, 1.5433542f, 0.6826069f, 1.5433542f,
+ 1.6060922f, 1.0179458f, 1.6060922f, 1.4053307f, 0.43474767f, 1.4053307f, 1.2673072f, 0.26641548f,
+ 1.2673072f, 1.2045691f, 0.16965766f, 1.2045691f, 0.85319227f, 0.6826069f, 2.0052736f, 0.88787496f,
+ 1.0179458f, 2.086789f, 0.7768906f, 0.43474767f, 1.8259401f, 0.7005888f, 0.26641548f, 1.6466068f,
+ 0.6659062f, 0.16965766f, 1.5650915f, 0.0f, 0.6826069f, 2.1737385f, 0.0f, 1.0179458f, 2.2621017f, 0.0f,
+ 0.43474767f, 1.9793389f, 0.0f, 0.26641548f, 1.7849396f, 0.0f, 0.16965766f, 1.6965764f, -0.85319227f,
+ 0.6826069f, 2.0052736f, 0.0f, 0.6826069f, 2.1737385f, 0.0f, 1.0179458f, 2.2621017f, -0.88787496f,
+ 1.0179458f, 2.086789f, -0.7768906f, 0.43474767f, 1.8259401f, 0.0f, 0.43474767f, 1.9793389f,
+ -0.7005888f, 0.26641548f, 1.6466068f, 0.0f, 0.26641548f, 1.7849396f, -0.6659062f, 0.16965766f,
+ 1.5650915f, 0.0f, 0.16965766f, 1.6965764f, -1.5433542f, 0.6826069f, 1.5433542f, -1.6060922f,
+ 1.0179458f, 1.6060922f, -1.4053307f, 0.43474767f, 1.4053307f, -1.2673072f, 0.26641548f, 1.2673072f,
+ -1.2045691f, 0.16965766f, 1.2045691f, -2.0052736f, 0.6826069f, 0.85319227f, -2.086789f, 1.0179458f,
+ 0.88787496f, -1.8259401f, 0.43474767f, 0.7768906f, -1.6466068f, 0.26641548f, 0.7005888f, -1.5650915f,
+ 0.16965766f, 0.6659062f, -2.1737385f, 0.6826069f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -1.9793389f,
+ 0.43474767f, 0.0f, -1.7849396f, 0.26641548f, 0.0f, -1.6965764f, 0.16965766f, 0.0f, -2.0052736f,
+ 0.6826069f, -0.85319227f, -2.1737385f, 0.6826069f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, -2.086789f,
+ 1.0179458f, -0.88787496f, -1.8259401f, 0.43474767f, -0.7768906f, -1.9793389f, 0.43474767f, 0.0f,
+ -1.6466068f, 0.26641548f, -0.7005888f, -1.7849396f, 0.26641548f, 0.0f, -1.5650915f, 0.16965766f,
+ -0.6659062f, -1.6965764f, 0.16965766f, 0.0f, -1.5433542f, 0.6826069f, -1.5433542f, -1.6060922f,
+ 1.0179458f, -1.6060922f, -1.4053307f, 0.43474767f, -1.4053307f, -1.2673072f, 0.26641548f, -1.2673072f,
+ -1.2045691f, 0.16965766f, -1.2045691f, -0.85319227f, 0.6826069f, -2.0052736f, -0.88787496f, 1.0179458f,
+ -2.086789f, -0.7768906f, 0.43474767f, -1.8259401f, -0.7005888f, 0.26641548f, -1.6466068f, -0.6659062f,
+ 0.16965766f, -1.5650915f, 0.0f, 0.6826069f, -2.1737385f, 0.0f, 1.0179458f, -2.2621017f, 0.0f,
+ 0.43474767f, -1.9793389f, 0.0f, 0.26641548f, -1.7849396f, 0.0f, 0.16965766f, -1.6965764f, 0.85319227f,
+ 0.6826069f, -2.0052736f, 0.0f, 0.6826069f, -2.1737385f, 0.0f, 1.0179458f, -2.2621017f, 0.88787496f,
+ 1.0179458f, -2.086789f, 0.7768906f, 0.43474767f, -1.8259401f, 0.0f, 0.43474767f, -1.9793389f,
+ 0.7005888f, 0.26641548f, -1.6466068f, 0.0f, 0.26641548f, -1.7849396f, 0.6659062f, 0.16965766f,
+ -1.5650915f, 0.0f, 0.16965766f, -1.6965764f, 1.5433542f, 0.6826069f, -1.5433542f, 1.6060922f,
+ 1.0179458f, -1.6060922f, 1.4053307f, 0.43474767f, -1.4053307f, 1.2673072f, 0.26641548f, -1.2673072f,
+ 1.2045691f, 0.16965766f, -1.2045691f, 2.0052736f, 0.6826069f, -0.85319227f, 2.086789f, 1.0179458f,
+ -0.88787496f, 1.8259401f, 0.43474767f, -0.7768906f, 1.6466068f, 0.26641548f, -0.7005888f, 1.5650915f,
+ 0.16965766f, -0.6659062f, 2.1737385f, 0.6826069f, 0.0f, 2.2621017f, 1.0179458f, 0.0f, 1.9793389f,
+ 0.43474767f, 0.0f, 1.7849396f, 0.26641548f, 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.5296324f,
+ 0.10736148f, 0.6508193f, 1.6581382f, 0.10736148f, 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.5650915f,
+ 0.16965766f, 0.6659062f, 1.3401097f, 0.053018f, 0.5701822f, 1.4526935f, 0.053018f, 0.0f, 0.87180483f,
+ 0.014579957f, 0.37093055f, 0.94504595f, 0.014579957f, 0.0f, 0.0f, 0.0f, 0.0f, 0.87180483f,
+ 0.014579957f, -0.37093055f, 1.1772782f, 0.10736148f, 1.1772782f, 1.2045691f, 0.16965766f, 1.2045691f,
+ 1.0314124f, 0.053018f, 1.0314124f, 0.6709826f, 0.014579957f, 0.6709826f, 0.6508193f, 0.10736148f,
+ 1.5296324f, 0.6659062f, 0.16965766f, 1.5650915f, 0.5701822f, 0.053018f, 1.3401097f, 0.37093055f,
+ 0.014579957f, 0.87180483f, 0.0f, 0.10736148f, 1.6581382f, 0.0f, 0.16965766f, 1.6965764f, 0.0f,
+ 0.053018f, 1.4526935f, 0.0f, 0.014579957f, 0.94504595f, -0.6508193f, 0.10736148f, 1.5296324f, 0.0f,
+ 0.10736148f, 1.6581382f, 0.0f, 0.16965766f, 1.6965764f, -0.6659062f, 0.16965766f, 1.5650915f,
+ -0.5701822f, 0.053018f, 1.3401097f, 0.0f, 0.053018f, 1.4526935f, -0.37093055f, 0.014579957f,
+ 0.87180483f, 0.0f, 0.014579957f, 0.94504595f, -1.1772782f, 0.10736148f, 1.1772782f, -1.2045691f,
+ 0.16965766f, 1.2045691f, -1.0314124f, 0.053018f, 1.0314124f, -0.6709826f, 0.014579957f, 0.6709826f,
+ -1.5296324f, 0.10736148f, 0.6508193f, -1.5650915f, 0.16965766f, 0.6659062f, -1.3401097f, 0.053018f,
+ 0.5701822f, -0.87180483f, 0.014579957f, 0.37093055f, -1.6581382f, 0.10736148f, 0.0f, -1.6965764f,
+ 0.16965766f, 0.0f, -1.4526935f, 0.053018f, 0.0f, -0.94504595f, 0.014579957f, 0.0f, -1.5296324f,
+ 0.10736148f, -0.6508193f, -1.6581382f, 0.10736148f, 0.0f, -1.6965764f, 0.16965766f, 0.0f, -1.5650915f,
+ 0.16965766f, -0.6659062f, -1.3401097f, 0.053018f, -0.5701822f, -1.4526935f, 0.053018f, 0.0f,
+ -0.87180483f, 0.014579957f, -0.37093055f, -0.94504595f, 0.014579957f, 0.0f, -1.1772782f, 0.10736148f,
+ -1.1772782f, -1.2045691f, 0.16965766f, -1.2045691f, -1.0314124f, 0.053018f, -1.0314124f, -0.6709826f,
+ 0.014579957f, -0.6709826f, -0.6508193f, 0.10736148f, -1.5296324f, -0.6659062f, 0.16965766f,
+ -1.5650915f, -0.5701822f, 0.053018f, -1.3401097f, -0.37093055f, 0.014579957f, -0.87180483f, 0.0f,
+ 0.10736148f, -1.6581382f, 0.0f, 0.16965766f, -1.6965764f, 0.0f, 0.053018f, -1.4526935f, 0.0f,
+ 0.014579957f, -0.94504595f, 0.6508193f, 0.10736148f, -1.5296324f, 0.0f, 0.10736148f, -1.6581382f, 0.0f,
+ 0.16965766f, -1.6965764f, 0.6659062f, 0.16965766f, -1.5650915f, 0.5701822f, 0.053018f, -1.3401097f,
+ 0.0f, 0.053018f, -1.4526935f, 0.37093055f, 0.014579957f, -0.87180483f, 0.0f, 0.014579957f,
+ -0.94504595f, 1.1772782f, 0.10736148f, -1.1772782f, 1.2045691f, 0.16965766f, -1.2045691f, 1.0314124f,
+ 0.053018f, -1.0314124f, 0.6709826f, 0.014579957f, -0.6709826f, 1.5296324f, 0.10736148f, -0.6508193f,
+ 1.5650915f, 0.16965766f, -0.6659062f, 1.3401097f, 0.053018f, -0.5701822f, 1.6581382f, 0.10736148f,
+ 0.0f, 1.6965764f, 0.16965766f, 0.0f, 1.4526935f, 0.053018f, 0.0f, 0.94504595f, 0.014579957f, 0.0f,
+ -2.353834f, 2.3255439f, 0.19086483f, -2.3380942f, 2.2864017f, 0.0f, -1.8096814f, 2.290378f, 0.0f,
+ -1.7920088f, 2.3301415f, 0.19086483f, -2.7662144f, 2.2933602f, 0.19086483f, -2.72866f, 2.2585673f,
+ 0.0f, -3.020204f, 2.2060049f, 0.19086483f, -2.9707758f, 2.1830165f, 0.0f, -3.1068554f, 2.0358915f,
+ 0.19086483f, -3.0538373f, 2.0358915f, 0.0f, -2.3884614f, 2.4116569f, 0.25448647f, -1.7531288f,
+ 2.4176214f, 0.25448647f, -2.8488343f, 2.369905f, 0.25448647f, -3.128946f, 2.256579f, 0.25448647f,
+ -3.2234948f, 2.0358915f, 0.25448647f, -2.4230888f, 2.4977696f, 0.19086483f, -1.714249f, 2.505101f,
+ 0.19086483f, -2.9314542f, 2.4464495f, 0.19086483f, -3.237688f, 2.3071532f, 0.19086483f, -3.3401346f,
+ 2.0358915f, 0.19086483f, -2.4388282f, 2.5369117f, 0.0f, -1.6965764f, 2.5448644f, 0.0f, -2.9690084f,
+ 2.4812427f, 0.0f, -3.2871168f, 2.3301415f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -2.4230888f,
+ 2.4977696f, -0.19086483f, -2.4388282f, 2.5369117f, 0.0f, -1.6965764f, 2.5448644f, 0.0f, -1.714249f,
+ 2.505101f, -0.19086483f, -2.9314542f, 2.4464495f, -0.19086483f, -2.9690084f, 2.4812427f, 0.0f,
+ -3.237688f, 2.3071532f, -0.19086483f, -3.2871168f, 2.3301415f, 0.0f, -3.3401346f, 2.0358915f,
+ -0.19086483f, -3.3931527f, 2.0358915f, 0.0f, -2.3884614f, 2.4116569f, -0.25448647f, -1.7531288f,
+ 2.4176214f, -0.25448647f, -2.8488343f, 2.369905f, -0.25448647f, -3.128946f, 2.256579f, -0.25448647f,
+ -3.2234948f, 2.0358915f, -0.25448647f, -2.353834f, 2.3255439f, -0.19086483f, -1.7920088f, 2.3301415f,
+ -0.19086483f, -2.7662144f, 2.2933602f, -0.19086483f, -3.020204f, 2.2060049f, -0.19086483f, -3.1068554f,
+ 2.0358915f, -0.19086483f, -2.3380942f, 2.2864017f, 0.0f, -1.8096814f, 2.290378f, 0.0f, -2.72866f,
+ 2.2585673f, 0.0f, -2.9707758f, 2.1830165f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -3.0578415f,
+ 1.7829998f, 0.19086483f, -3.0096557f, 1.8052632f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -3.1068554f,
+ 2.0358915f, 0.19086483f, -2.9042823f, 1.4929541f, 0.19086483f, -2.8700416f, 1.5269186f, 0.0f,
+ -2.6364033f, 1.2066361f, 0.19086483f, -2.6243916f, 1.2485741f, 0.0f, -2.244429f, 0.96492773f,
+ 0.19086483f, -2.2621017f, 1.0179458f, 0.0f, -3.1638496f, 1.7340202f, 0.25448647f, -3.2234948f,
+ 2.0358915f, 0.25448647f, -2.9796124f, 1.4182318f, 0.25448647f, -2.6628296f, 1.1143724f, 0.25448647f,
+ -2.2055492f, 0.8482882f, 0.25448647f, -3.269858f, 1.6850407f, 0.19086483f, -3.3401346f, 2.0358915f,
+ 0.19086483f, -3.054942f, 1.3435094f, 0.19086483f, -2.6892557f, 1.0221086f, 0.19086483f, -2.1666694f,
+ 0.73164856f, 0.19086483f, -3.3180435f, 1.6627773f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -3.0891826f,
+ 1.3095448f, 0.0f, -2.7012677f, 0.9801704f, 0.0f, -2.1489966f, 0.67863053f, 0.0f, -3.269858f,
+ 1.6850407f, -0.19086483f, -3.3180435f, 1.6627773f, 0.0f, -3.3931527f, 2.0358915f, 0.0f, -3.3401346f,
+ 2.0358915f, -0.19086483f, -3.054942f, 1.3435094f, -0.19086483f, -3.0891826f, 1.3095448f, 0.0f,
+ -2.6892557f, 1.0221086f, -0.19086483f, -2.7012677f, 0.9801704f, 0.0f, -2.1666694f, 0.73164856f,
+ -0.19086483f, -2.1489966f, 0.67863053f, 0.0f, -3.1638496f, 1.7340202f, -0.25448647f, -3.2234948f,
+ 2.0358915f, -0.25448647f, -2.9796124f, 1.4182318f, -0.25448647f, -2.6628296f, 1.1143724f, -0.25448647f,
+ -2.2055492f, 0.8482882f, -0.25448647f, -3.0578415f, 1.7829998f, -0.19086483f, -3.1068554f, 2.0358915f,
+ -0.19086483f, -2.9042823f, 1.4929541f, -0.19086483f, -2.6364033f, 1.2066361f, -0.19086483f, -2.244429f,
+ 0.96492773f, -0.19086483f, -3.0096557f, 1.8052632f, 0.0f, -3.0538373f, 2.0358915f, 0.0f, -2.8700416f,
+ 1.5269186f, 0.0f, -2.6243916f, 1.2485741f, 0.0f, -2.2621017f, 1.0179458f, 0.0f, 2.5067577f, 1.6282327f,
+ 0.37914506f, 2.4653373f, 1.7363397f, 0.0f, 1.9227866f, 1.6117474f, 0.0f, 1.9227866f, 1.4659479f,
+ 0.41990262f, 2.753402f, 1.9729326f, 0.28947833f, 2.700384f, 2.0358915f, 0.0f, 2.89401f, 2.3762836f,
+ 0.19981161f, 2.8293946f, 2.399065f, 0.0f, 3.1598732f, 2.7145221f, 0.15905404f, 3.0538373f, 2.7145221f,
+ 0.0f, 2.5978825f, 1.3903973f, 0.5055267f, 1.9227866f, 1.145189f, 0.5598702f, 2.8700416f, 1.8344232f,
+ 0.3859711f, 3.0361648f, 2.326165f, 0.26641548f, 3.3931527f, 2.7145221f, 0.21207204f, 2.689007f,
+ 1.1525618f, 0.37914506f, 1.9227866f, 0.82443005f, 0.41990262f, 2.986681f, 1.6959136f, 0.28947833f,
+ 3.1783192f, 2.2760465f, 0.19981161f, 3.6264317f, 2.7145221f, 0.15905404f, 2.7304275f, 1.0444548f, 0.0f,
+ 1.9227866f, 0.67863053f, 0.0f, 3.039699f, 1.6329546f, 0.0f, 3.242935f, 2.2532656f, 0.0f, 3.7324677f,
+ 2.7145221f, 0.0f, 2.689007f, 1.1525618f, -0.37914506f, 2.7304275f, 1.0444548f, 0.0f, 1.9227866f,
+ 0.67863053f, 0.0f, 1.9227866f, 0.82443005f, -0.41990262f, 2.986681f, 1.6959136f, -0.28947833f,
+ 3.039699f, 1.6329546f, 0.0f, 3.1783192f, 2.2760465f, -0.19981161f, 3.242935f, 2.2532656f, 0.0f,
+ 3.6264317f, 2.7145221f, -0.15905404f, 3.7324677f, 2.7145221f, 0.0f, 2.5978825f, 1.3903973f,
+ -0.5055267f, 1.9227866f, 1.145189f, -0.5598702f, 2.8700416f, 1.8344232f, -0.3859711f, 3.0361648f,
+ 2.326165f, -0.26641548f, 3.3931527f, 2.7145221f, -0.21207204f, 2.5067577f, 1.6282327f, -0.37914506f,
+ 1.9227866f, 1.4659479f, -0.41990262f, 2.753402f, 1.9729326f, -0.28947833f, 2.89401f, 2.3762836f,
+ -0.19981161f, 3.1598732f, 2.7145221f, -0.15905404f, 2.4653373f, 1.7363397f, 0.0f, 1.9227866f,
+ 1.6117474f, 0.0f, 2.700384f, 2.0358915f, 0.0f, 2.8293946f, 2.399065f, 0.0f, 3.0538373f, 2.7145221f,
+ 0.0f, 3.248692f, 2.764568f, 0.14911313f, 3.1351316f, 2.7622383f, 0.0f, 3.0538373f, 2.7145221f, 0.0f,
+ 3.1598732f, 2.7145221f, 0.15905404f, 3.301807f, 2.7818716f, 0.12724322f, 3.1952186f, 2.7781436f, 0.0f,
+ 3.3033948f, 2.7655f, 0.1053733f, 3.2128913f, 2.7622383f, 0.0f, 3.237633f, 2.7145221f, 0.09543244f,
+ 3.1669424f, 2.7145221f, 0.0f, 3.4985259f, 2.7696939f, 0.19881752f, 3.3931527f, 2.7145221f, 0.21207204f,
+ 3.5363014f, 2.7900727f, 0.16965766f, 3.5025022f, 2.7726762f, 0.14049773f, 3.3931527f, 2.7145221f,
+ 0.12724322f, 3.7483597f, 2.7748199f, 0.14911313f, 3.6264317f, 2.7145221f, 0.15905404f, 3.7707958f,
+ 2.7982738f, 0.12724322f, 3.7016098f, 2.7798524f, 0.1053733f, 3.5486722f, 2.7145221f, 0.09543244f,
+ 3.86192f, 2.7771497f, 0.0f, 3.7324677f, 2.7145221f, 0.0f, 3.877384f, 2.802002f, 0.0f, 3.7921128f,
+ 2.7831142f, 0.0f, 3.6193628f, 2.7145221f, 0.0f, 3.7483597f, 2.7748199f, -0.14911313f, 3.86192f,
+ 2.7771497f, 0.0f, 3.7324677f, 2.7145221f, 0.0f, 3.6264317f, 2.7145221f, -0.15905404f, 3.7707958f,
+ 2.7982738f, -0.12724322f, 3.877384f, 2.802002f, 0.0f, 3.7016098f, 2.7798524f, -0.1053733f, 3.7921128f,
+ 2.7831142f, 0.0f, 3.5486722f, 2.7145221f, -0.09543244f, 3.6193628f, 2.7145221f, 0.0f, 3.4985259f,
+ 2.7696939f, -0.19881752f, 3.3931527f, 2.7145221f, -0.21207204f, 3.5363014f, 2.7900727f, -0.16965766f,
+ 3.5025022f, 2.7726762f, -0.14049773f, 3.3931527f, 2.7145221f, -0.12724322f, 3.248692f, 2.764568f,
+ -0.14911313f, 3.1598732f, 2.7145221f, -0.15905404f, 3.301807f, 2.7818716f, -0.12724322f, 3.3033948f,
+ 2.7655f, -0.1053733f, 3.237633f, 2.7145221f, -0.09543244f, 3.1351316f, 2.7622383f, 0.0f, 3.0538373f,
+ 2.7145221f, 0.0f, 3.1952186f, 2.7781436f, 0.0f, 3.2128913f, 2.7622383f, 0.0f, 3.1669424f, 2.7145221f,
+ 0.0f, 0.0f, 3.5628102f, 0.0f, 0.27389547f, 3.507141f, 0.27389547f, 0.3555404f, 3.507141f, 0.15161878f,
+ 0.38526422f, 3.507141f, 0.0f, 0.33922246f, 3.3719451f, 0.14463753f, 0.36759156f, 3.3719451f, 0.0f,
+ 0.20546299f, 3.204939f, 0.08753438f, 0.22267565f, 3.204939f, 0.0f, 0.20867887f, 3.0538373f,
+ 0.08878748f, 0.22621018f, 3.0538373f, 0.0f, 0.2613081f, 3.3719451f, 0.2613081f, 0.158219f, 3.204939f,
+ 0.158219f, 0.16060922f, 3.0538373f, 0.16060922f, 0.0f, 3.5628102f, 0.0f, 0.0f, 3.507141f, 0.38526422f,
+ 0.15161878f, 3.507141f, 0.3555404f, 0.14463753f, 3.3719451f, 0.33922246f, 0.08753438f, 3.204939f,
+ 0.20546299f, 0.08878748f, 3.0538373f, 0.20867887f, 0.0f, 3.3719451f, 0.36759156f, 0.0f, 3.204939f,
+ 0.22267565f, 0.0f, 3.0538373f, 0.22621018f, 0.0f, 3.5628102f, 0.0f, -0.27389547f, 3.507141f,
+ 0.27389547f, -0.15161878f, 3.507141f, 0.3555404f, 0.0f, 3.507141f, 0.38526422f, -0.14463753f,
+ 3.3719451f, 0.33922246f, 0.0f, 3.3719451f, 0.36759156f, -0.08753438f, 3.204939f, 0.20546299f, 0.0f,
+ 3.204939f, 0.22267565f, -0.08878748f, 3.0538373f, 0.20867887f, 0.0f, 3.0538373f, 0.22621018f,
+ -0.2613081f, 3.3719451f, 0.2613081f, -0.158219f, 3.204939f, 0.158219f, -0.16060922f, 3.0538373f,
+ 0.16060922f, 0.0f, 3.5628102f, 0.0f, -0.38526422f, 3.507141f, 0.0f, -0.3555404f, 3.507141f,
+ 0.15161878f, -0.33922246f, 3.3719451f, 0.14463753f, -0.20546299f, 3.204939f, 0.08753438f, -0.20867887f,
+ 3.0538373f, 0.08878748f, -0.36759156f, 3.3719451f, 0.0f, -0.22267565f, 3.204939f, 0.0f, -0.22621018f,
+ 3.0538373f, 0.0f, 0.0f, 3.5628102f, 0.0f, -0.27389547f, 3.507141f, -0.27389547f, -0.3555404f,
+ 3.507141f, -0.15161878f, -0.38526422f, 3.507141f, 0.0f, -0.33922246f, 3.3719451f, -0.14463753f,
+ -0.36759156f, 3.3719451f, 0.0f, -0.20546299f, 3.204939f, -0.08753438f, -0.22267565f, 3.204939f, 0.0f,
+ -0.20867887f, 3.0538373f, -0.08878748f, -0.22621018f, 3.0538373f, 0.0f, -0.2613081f, 3.3719451f,
+ -0.2613081f, -0.158219f, 3.204939f, -0.158219f, -0.16060922f, 3.0538373f, -0.16060922f, 0.0f,
+ 3.5628102f, 0.0f, 0.0f, 3.507141f, -0.38526422f, -0.15161878f, 3.507141f, -0.3555404f, -0.14463753f,
+ 3.3719451f, -0.33922246f, -0.08753438f, 3.204939f, -0.20546299f, -0.08878748f, 3.0538373f,
+ -0.20867887f, 0.0f, 3.3719451f, -0.36759156f, 0.0f, 3.204939f, -0.22267565f, 0.0f, 3.0538373f,
+ -0.22621018f, 0.0f, 3.5628102f, 0.0f, 0.27389547f, 3.507141f, -0.27389547f, 0.15161878f, 3.507141f,
+ -0.3555404f, 0.0f, 3.507141f, -0.38526422f, 0.14463753f, 3.3719451f, -0.33922246f, 0.0f, 3.3719451f,
+ -0.36759156f, 0.08753438f, 3.204939f, -0.20546299f, 0.0f, 3.204939f, -0.22267565f, 0.08878748f,
+ 3.0538373f, -0.20867887f, 0.0f, 3.0538373f, -0.22621018f, 0.2613081f, 3.3719451f, -0.2613081f,
+ 0.158219f, 3.204939f, -0.158219f, 0.16060922f, 3.0538373f, -0.16060922f, 0.0f, 3.5628102f, 0.0f,
+ 0.38526422f, 3.507141f, 0.0f, 0.3555404f, 3.507141f, -0.15161878f, 0.33922246f, 3.3719451f,
+ -0.14463753f, 0.20546299f, 3.204939f, -0.08753438f, 0.20867887f, 3.0538373f, -0.08878748f, 0.36759156f,
+ 3.3719451f, 0.0f, 0.22267565f, 3.204939f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.47604877f, 2.953103f,
+ 0.20254643f, 0.51604193f, 2.953103f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.20867887f, 3.0538373f,
+ 0.08878748f, 0.8608004f, 2.8841796f, 0.36624837f, 0.9331169f, 2.8841796f, 0.0f, 1.2064248f, 2.815256f,
+ 0.5133026f, 1.3077775f, 2.815256f, 0.0f, 1.3564128f, 2.7145221f, 0.5771187f, 1.4703661f, 2.7145221f,
+ 0.0f, 0.3663898f, 2.953103f, 0.3663898f, 0.16060922f, 3.0538373f, 0.16060922f, 0.662513f, 2.8841796f,
+ 0.662513f, 0.92852205f, 2.815256f, 0.92852205f, 1.0439599f, 2.7145221f, 1.0439599f, 0.20254643f,
+ 2.953103f, 0.47604877f, 0.08878748f, 3.0538373f, 0.20867887f, 0.36624837f, 2.8841796f, 0.8608004f,
+ 0.5133026f, 2.815256f, 1.2064248f, 0.5771187f, 2.7145221f, 1.3564128f, 0.0f, 2.953103f, 0.51604193f,
+ 0.0f, 3.0538373f, 0.22621018f, 0.0f, 2.8841796f, 0.9331169f, 0.0f, 2.815256f, 1.3077775f, 0.0f,
+ 2.7145221f, 1.4703661f, -0.20254643f, 2.953103f, 0.47604877f, 0.0f, 2.953103f, 0.51604193f, 0.0f,
+ 3.0538373f, 0.22621018f, -0.08878748f, 3.0538373f, 0.20867887f, -0.36624837f, 2.8841796f, 0.8608004f,
+ 0.0f, 2.8841796f, 0.9331169f, -0.5133026f, 2.815256f, 1.2064248f, 0.0f, 2.815256f, 1.3077775f,
+ -0.5771187f, 2.7145221f, 1.3564128f, 0.0f, 2.7145221f, 1.4703661f, -0.3663898f, 2.953103f, 0.3663898f,
+ -0.16060922f, 3.0538373f, 0.16060922f, -0.662513f, 2.8841796f, 0.662513f, -0.92852205f, 2.815256f,
+ 0.92852205f, -1.0439599f, 2.7145221f, 1.0439599f, -0.47604877f, 2.953103f, 0.20254643f, -0.20867887f,
+ 3.0538373f, 0.08878748f, -0.8608004f, 2.8841796f, 0.36624837f, -1.2064248f, 2.815256f, 0.5133026f,
+ -1.3564128f, 2.7145221f, 0.5771187f, -0.51604193f, 2.953103f, 0.0f, -0.22621018f, 3.0538373f, 0.0f,
+ -0.9331169f, 2.8841796f, 0.0f, -1.3077775f, 2.815256f, 0.0f, -1.4703661f, 2.7145221f, 0.0f,
+ -0.47604877f, 2.953103f, -0.20254643f, -0.51604193f, 2.953103f, 0.0f, -0.22621018f, 3.0538373f, 0.0f,
+ -0.20867887f, 3.0538373f, -0.08878748f, -0.8608004f, 2.8841796f, -0.36624837f, -0.9331169f, 2.8841796f,
+ 0.0f, -1.2064248f, 2.815256f, -0.5133026f, -1.3077775f, 2.815256f, 0.0f, -1.3564128f, 2.7145221f,
+ -0.5771187f, -1.4703661f, 2.7145221f, 0.0f, -0.3663898f, 2.953103f, -0.3663898f, -0.16060922f,
+ 3.0538373f, -0.16060922f, -0.662513f, 2.8841796f, -0.662513f, -0.92852205f, 2.815256f, -0.92852205f,
+ -1.0439599f, 2.7145221f, -1.0439599f, -0.20254643f, 2.953103f, -0.47604877f, -0.08878748f, 3.0538373f,
+ -0.20867887f, -0.36624837f, 2.8841796f, -0.8608004f, -0.5133026f, 2.815256f, -1.2064248f, -0.5771187f,
+ 2.7145221f, -1.3564128f, 0.0f, 2.953103f, -0.51604193f, 0.0f, 3.0538373f, -0.22621018f, 0.0f,
+ 2.8841796f, -0.9331169f, 0.0f, 2.815256f, -1.3077775f, 0.0f, 2.7145221f, -1.4703661f, 0.20254643f,
+ 2.953103f, -0.47604877f, 0.0f, 2.953103f, -0.51604193f, 0.0f, 3.0538373f, -0.22621018f, 0.08878748f,
+ 3.0538373f, -0.20867887f, 0.36624837f, 2.8841796f, -0.8608004f, 0.0f, 2.8841796f, -0.9331169f,
+ 0.5133026f, 2.815256f, -1.2064248f, 0.0f, 2.815256f, -1.3077775f, 0.5771187f, 2.7145221f, -1.3564128f,
+ 0.0f, 2.7145221f, -1.4703661f, 0.3663898f, 2.953103f, -0.3663898f, 0.16060922f, 3.0538373f,
+ -0.16060922f, 0.662513f, 2.8841796f, -0.662513f, 0.92852205f, 2.815256f, -0.92852205f, 1.0439599f,
+ 2.7145221f, -1.0439599f, 0.47604877f, 2.953103f, -0.20254643f, 0.20867887f, 3.0538373f, -0.08878748f,
+ 0.8608004f, 2.8841796f, -0.36624837f, 1.2064248f, 2.815256f, -0.5133026f, 1.3564128f, 2.7145221f,
+ -0.5771187f, 0.51604193f, 2.953103f, 0.0f, 0.22621018f, 3.0538373f, 0.0f, 0.9331169f, 2.8841796f, 0.0f,
+ 1.3077775f, 2.815256f, 0.0f, 1.4703661f, 2.7145221f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f };
+ _meshData.setVertexBuffer(BufferUtils.createFloatBuffer(verts));
+ }
+
+ private void setNormalData() {
+ final float[] norms = new float[] { -0.893437f, 0.255997f, -0.369102f, -0.966824f, 0.255444f, 0.0f, -0.966742f,
+ -0.255753f, 0.0f, -0.893014f, -0.256343f, -0.369883f, -0.083878f, 0.995843f, -0.035507f, -0.092051f,
+ 0.995754f, -0.0f, 0.629723f, 0.731861f, 0.260439f, 0.682049f, 0.731306f, 0.0f, 0.803725f, 0.49337f,
+ 0.332584f, 0.8703f, 0.492521f, 0.0f, -0.683531f, 0.256069f, -0.683531f, -0.683407f, -0.256729f,
+ -0.683407f, -0.064924f, 0.995776f, -0.064924f, 0.481398f, 0.73247f, 0.481398f, 0.614804f, 0.493997f,
+ 0.614804f, -0.369102f, 0.255997f, -0.893437f, -0.369882f, -0.256343f, -0.893015f, -0.035507f,
+ 0.995843f, -0.083878f, 0.260439f, 0.731861f, 0.629723f, 0.332584f, 0.49337f, 0.803725f, -0.001923f,
+ 0.254737f, -0.967008f, -0.002849f, -0.257864f, -0.966177f, -2.66E-4f, 0.995734f, -0.092269f, 2.4E-5f,
+ 0.731296f, 0.68206f, 0.0f, 0.492521f, 0.870301f, 0.37711f, 0.149085f, -0.914091f, -0.001923f,
+ 0.254737f, -0.967008f, -0.002849f, -0.257864f, -0.966177f, 0.379058f, -0.359299f, -0.852772f,
+ 0.027503f, 0.992081f, -0.122552f, -2.66E-4f, 0.995734f, -0.092269f, -0.26101f, 0.726762f, 0.635366f,
+ 2.4E-5f, 0.731296f, 0.68206f, -0.332485f, 0.492547f, 0.804271f, 0.0f, 0.492521f, 0.870301f, 0.712664f,
+ 0.073723f, -0.697621f, 0.663548f, -0.410791f, -0.625264f, 0.099726f, 0.987509f, -0.121983f, -0.487319f,
+ 0.723755f, 0.488568f, -0.615242f, 0.492602f, 0.615484f, 0.917276f, 0.167113f, -0.361493f, 0.880028f,
+ -0.332906f, -0.338709f, 0.113585f, 0.992365f, -0.04807f, -0.63415f, 0.727509f, 0.261888f, -0.804126f,
+ 0.492634f, 0.332705f, 0.967442f, 0.252963f, 0.008103f, 0.966689f, -0.255739f, 0.010454f, 0.093435f,
+ 0.995625f, 0.001281f, -0.682166f, 0.731197f, -3.44E-4f, -0.870322f, 0.492484f, -5.4E-5f, 0.893437f,
+ 0.255997f, 0.369102f, 0.967442f, 0.252963f, 0.008103f, 0.966689f, -0.255739f, 0.010454f, 0.893014f,
+ -0.256343f, 0.369883f, 0.083878f, 0.995843f, 0.035507f, 0.093435f, 0.995625f, 0.001281f, -0.629723f,
+ 0.731861f, -0.260439f, -0.682166f, 0.731197f, -3.44E-4f, -0.803725f, 0.49337f, -0.332584f, -0.870322f,
+ 0.492484f, -5.4E-5f, 0.683531f, 0.256069f, 0.683531f, 0.683407f, -0.256729f, 0.683407f, 0.064924f,
+ 0.995776f, 0.064924f, -0.481398f, 0.73247f, -0.481398f, -0.614804f, 0.493997f, -0.614804f, 0.369102f,
+ 0.255997f, 0.893437f, 0.369882f, -0.256343f, 0.893015f, 0.035507f, 0.995843f, 0.083878f, -0.260439f,
+ 0.731861f, -0.629723f, -0.332584f, 0.49337f, -0.803725f, 0.0f, 0.255444f, 0.966824f, 0.0f, -0.255753f,
+ 0.966742f, -0.0f, 0.995754f, 0.092051f, 0.0f, 0.731306f, -0.682049f, -0.0f, 0.492521f, -0.870301f,
+ -0.369102f, 0.255997f, 0.893437f, 0.0f, 0.255444f, 0.966824f, 0.0f, -0.255753f, 0.966742f, -0.369883f,
+ -0.256343f, 0.893014f, -0.035507f, 0.995843f, 0.083878f, -0.0f, 0.995754f, 0.092051f, 0.260439f,
+ 0.731861f, -0.629723f, 0.0f, 0.731306f, -0.682049f, 0.332584f, 0.49337f, -0.803725f, -0.0f, 0.492521f,
+ -0.870301f, -0.683531f, 0.256069f, 0.683531f, -0.683407f, -0.256729f, 0.683407f, -0.064924f, 0.995776f,
+ 0.064924f, 0.481398f, 0.73247f, -0.481398f, 0.614804f, 0.493997f, -0.614804f, -0.893437f, 0.255997f,
+ 0.369102f, -0.893015f, -0.256343f, 0.369882f, -0.083878f, 0.995843f, 0.035507f, 0.629723f, 0.731861f,
+ -0.260439f, 0.803725f, 0.49337f, -0.332584f, -0.966824f, 0.255444f, 0.0f, -0.966742f, -0.255753f, 0.0f,
+ -0.092051f, 0.995754f, -0.0f, 0.682049f, 0.731306f, 0.0f, 0.8703f, 0.492521f, 0.0f, 0.845438f,
+ 0.403545f, 0.349835f, 0.915321f, 0.402725f, -0.0f, 0.8703f, 0.492521f, 0.0f, 0.803725f, 0.49337f,
+ 0.332584f, 0.869996f, 0.336859f, 0.360047f, 0.941808f, 0.336151f, -0.0f, 0.904193f, 0.205791f,
+ 0.37428f, 0.97869f, 0.205342f, -0.0f, 0.921879f, -0.06637f, 0.381752f, 0.997804f, -0.06624f, 0.0f,
+ 0.646802f, 0.404096f, 0.646802f, 0.614804f, 0.493997f, 0.614804f, 0.665655f, 0.337351f, 0.665655f,
+ 0.691923f, 0.20612f, 0.691923f, 0.705542f, -0.06648f, 0.705543f, 0.349835f, 0.403546f, 0.845438f,
+ 0.332584f, 0.49337f, 0.803725f, 0.360047f, 0.336858f, 0.869996f, 0.37428f, 0.205791f, 0.904193f,
+ 0.381752f, -0.06637f, 0.921879f, 0.0f, 0.402725f, 0.915321f, 0.0f, 0.492521f, 0.870301f, 0.0f,
+ 0.336151f, 0.941808f, 0.0f, 0.205342f, 0.97869f, -0.0f, -0.06624f, 0.997804f, -0.349835f, 0.403545f,
+ 0.845438f, 0.0f, 0.402725f, 0.915321f, 0.0f, 0.492521f, 0.870301f, -0.332485f, 0.492547f, 0.804271f,
+ -0.360047f, 0.336859f, 0.869996f, 0.0f, 0.336151f, 0.941808f, -0.37428f, 0.205791f, 0.904193f, 0.0f,
+ 0.205342f, 0.97869f, -0.381752f, -0.06637f, 0.921879f, -0.0f, -0.06624f, 0.997804f, -0.646802f,
+ 0.404096f, 0.646802f, -0.615242f, 0.492602f, 0.615484f, -0.665655f, 0.337351f, 0.665655f, -0.691923f,
+ 0.20612f, 0.691923f, -0.705543f, -0.06648f, 0.705542f, -0.845438f, 0.403546f, 0.349835f, -0.804126f,
+ 0.492634f, 0.332705f, -0.869996f, 0.336858f, 0.360047f, -0.904193f, 0.205791f, 0.37428f, -0.921879f,
+ -0.06637f, 0.381752f, -0.915321f, 0.402725f, 0.0f, -0.870322f, 0.492484f, -5.4E-5f, -0.941808f,
+ 0.336151f, 0.0f, -0.97869f, 0.205342f, 0.0f, -0.997804f, -0.06624f, -0.0f, -0.845438f, 0.403545f,
+ -0.349835f, -0.915321f, 0.402725f, 0.0f, -0.870322f, 0.492484f, -5.4E-5f, -0.803725f, 0.49337f,
+ -0.332584f, -0.869996f, 0.336859f, -0.360047f, -0.941808f, 0.336151f, 0.0f, -0.904193f, 0.205791f,
+ -0.37428f, -0.97869f, 0.205342f, 0.0f, -0.921879f, -0.06637f, -0.381752f, -0.997804f, -0.06624f, -0.0f,
+ -0.646802f, 0.404096f, -0.646802f, -0.614804f, 0.493997f, -0.614804f, -0.665655f, 0.337351f,
+ -0.665655f, -0.691923f, 0.20612f, -0.691923f, -0.705542f, -0.06648f, -0.705543f, -0.349835f, 0.403546f,
+ -0.845438f, -0.332584f, 0.49337f, -0.803725f, -0.360047f, 0.336858f, -0.869996f, -0.37428f, 0.205791f,
+ -0.904193f, -0.381752f, -0.06637f, -0.921879f, -0.0f, 0.402725f, -0.915321f, -0.0f, 0.492521f,
+ -0.870301f, -0.0f, 0.336151f, -0.941808f, -0.0f, 0.205342f, -0.97869f, 0.0f, -0.06624f, -0.997804f,
+ 0.349835f, 0.403545f, -0.845438f, -0.0f, 0.402725f, -0.915321f, -0.0f, 0.492521f, -0.870301f,
+ 0.332584f, 0.49337f, -0.803725f, 0.360047f, 0.336859f, -0.869996f, -0.0f, 0.336151f, -0.941808f,
+ 0.37428f, 0.205791f, -0.904193f, -0.0f, 0.205342f, -0.97869f, 0.381752f, -0.06637f, -0.921879f, 0.0f,
+ -0.06624f, -0.997804f, 0.646802f, 0.404096f, -0.646802f, 0.614804f, 0.493997f, -0.614804f, 0.665655f,
+ 0.337351f, -0.665655f, 0.691923f, 0.20612f, -0.691923f, 0.705543f, -0.06648f, -0.705542f, 0.845438f,
+ 0.403546f, -0.349835f, 0.803725f, 0.49337f, -0.332584f, 0.869996f, 0.336858f, -0.360047f, 0.904193f,
+ 0.205791f, -0.37428f, 0.921879f, -0.06637f, -0.381752f, 0.915321f, 0.402725f, -0.0f, 0.8703f,
+ 0.492521f, 0.0f, 0.941808f, 0.336151f, -0.0f, 0.97869f, 0.205342f, -0.0f, 0.997804f, -0.06624f, 0.0f,
+ 0.831437f, -0.43618f, 0.344179f, 0.900182f, -0.435513f, 0.0f, 0.997804f, -0.06624f, 0.0f, 0.921879f,
+ -0.06637f, 0.381752f, 0.673512f, -0.684666f, 0.278594f, 0.729611f, -0.683863f, -0.0f, 0.640399f,
+ -0.720924f, 0.264874f, 0.693951f, -0.720022f, 0.0f, 0.732949f, -0.608995f, 0.303167f, 0.793949f,
+ -0.607984f, -0.0f, 0.636092f, -0.436777f, 0.636092f, 0.705542f, -0.06648f, 0.705543f, 0.514965f,
+ -0.68529f, 0.514965f, 0.489651f, -0.721446f, 0.489651f, 0.560555f, -0.609553f, 0.560555f, 0.344179f,
+ -0.43618f, 0.831437f, 0.381752f, -0.06637f, 0.921879f, 0.278594f, -0.684665f, 0.673512f, 0.264874f,
+ -0.720924f, 0.640399f, 0.303167f, -0.608995f, 0.732949f, -0.0f, -0.435513f, 0.900182f, -0.0f,
+ -0.06624f, 0.997804f, 0.0f, -0.683863f, 0.729611f, -0.0f, -0.720022f, 0.693951f, 0.0f, -0.607984f,
+ 0.793949f, -0.344179f, -0.43618f, 0.831437f, -0.0f, -0.435513f, 0.900182f, -0.0f, -0.06624f, 0.997804f,
+ -0.381752f, -0.06637f, 0.921879f, -0.278594f, -0.684666f, 0.673512f, 0.0f, -0.683863f, 0.729611f,
+ -0.264874f, -0.720924f, 0.640399f, -0.0f, -0.720022f, 0.693951f, -0.303167f, -0.608995f, 0.732949f,
+ 0.0f, -0.607984f, 0.793949f, -0.636092f, -0.436777f, 0.636092f, -0.705543f, -0.06648f, 0.705542f,
+ -0.514965f, -0.68529f, 0.514965f, -0.489651f, -0.721446f, 0.489651f, -0.560555f, -0.609553f, 0.560555f,
+ -0.831437f, -0.43618f, 0.344179f, -0.921879f, -0.06637f, 0.381752f, -0.673512f, -0.684665f, 0.278594f,
+ -0.640399f, -0.720924f, 0.264874f, -0.732949f, -0.608995f, 0.303167f, -0.900182f, -0.435513f, -0.0f,
+ -0.997804f, -0.06624f, -0.0f, -0.729611f, -0.683863f, 0.0f, -0.693951f, -0.720022f, -0.0f, -0.793949f,
+ -0.607984f, 0.0f, -0.831437f, -0.43618f, -0.344179f, -0.900182f, -0.435513f, -0.0f, -0.997804f,
+ -0.06624f, -0.0f, -0.921879f, -0.06637f, -0.381752f, -0.673512f, -0.684666f, -0.278594f, -0.729611f,
+ -0.683863f, 0.0f, -0.640399f, -0.720924f, -0.264874f, -0.693951f, -0.720022f, -0.0f, -0.732949f,
+ -0.608995f, -0.303167f, -0.793949f, -0.607984f, 0.0f, -0.636092f, -0.436777f, -0.636092f, -0.705542f,
+ -0.06648f, -0.705543f, -0.514965f, -0.68529f, -0.514965f, -0.489651f, -0.721446f, -0.489651f,
+ -0.560555f, -0.609553f, -0.560555f, -0.344179f, -0.43618f, -0.831437f, -0.381752f, -0.06637f,
+ -0.921879f, -0.278594f, -0.684665f, -0.673512f, -0.264874f, -0.720924f, -0.640399f, -0.303167f,
+ -0.608995f, -0.732949f, 0.0f, -0.435513f, -0.900182f, 0.0f, -0.06624f, -0.997804f, -0.0f, -0.683863f,
+ -0.729611f, 0.0f, -0.720022f, -0.693951f, -0.0f, -0.607984f, -0.793949f, 0.344179f, -0.43618f,
+ -0.831437f, 0.0f, -0.435513f, -0.900182f, 0.0f, -0.06624f, -0.997804f, 0.381752f, -0.06637f,
+ -0.921879f, 0.278594f, -0.684666f, -0.673512f, -0.0f, -0.683863f, -0.729611f, 0.264874f, -0.720924f,
+ -0.640399f, 0.0f, -0.720022f, -0.693951f, 0.303167f, -0.608995f, -0.732949f, -0.0f, -0.607984f,
+ -0.793949f, 0.636092f, -0.436777f, -0.636092f, 0.705543f, -0.06648f, -0.705542f, 0.514965f, -0.68529f,
+ -0.514965f, 0.489651f, -0.721446f, -0.489651f, 0.560555f, -0.609553f, -0.560555f, 0.831437f, -0.43618f,
+ -0.344179f, 0.921879f, -0.06637f, -0.381752f, 0.673512f, -0.684665f, -0.278594f, 0.640399f, -0.720924f,
+ -0.264874f, 0.732949f, -0.608995f, -0.303167f, 0.900182f, -0.435513f, 0.0f, 0.997804f, -0.06624f, 0.0f,
+ 0.729611f, -0.683863f, -0.0f, 0.693951f, -0.720022f, 0.0f, 0.793949f, -0.607984f, -0.0f, 0.57623f,
+ -0.7818f, 0.238217f, 0.623859f, -0.781537f, -0.0f, 0.793949f, -0.607984f, -0.0f, 0.732949f, -0.608995f,
+ 0.303167f, 0.163628f, -0.984208f, 0.067527f, 0.177291f, -0.984159f, 0.0f, 0.045422f, -0.998792f,
+ 0.018736f, 0.049207f, -0.998789f, 0.0f, 0.0f, -1.0f, -0.0f, 0.045422f, -0.998792f, -0.018736f,
+ 0.440416f, -0.782348f, 0.440416f, 0.560555f, -0.609553f, 0.560555f, 0.124903f, -0.984276f, 0.124903f,
+ 0.034662f, -0.998798f, 0.034662f, 0.238217f, -0.7818f, 0.57623f, 0.303167f, -0.608995f, 0.732949f,
+ 0.067527f, -0.984208f, 0.163628f, 0.018736f, -0.998792f, 0.045422f, 0.0f, -0.781537f, 0.623859f, 0.0f,
+ -0.607984f, 0.793949f, -0.0f, -0.984159f, 0.177291f, -0.0f, -0.998789f, 0.049207f, -0.238217f,
+ -0.7818f, 0.57623f, 0.0f, -0.781537f, 0.623859f, 0.0f, -0.607984f, 0.793949f, -0.303167f, -0.608995f,
+ 0.732949f, -0.067527f, -0.984208f, 0.163628f, -0.0f, -0.984159f, 0.177291f, -0.018736f, -0.998792f,
+ 0.045422f, -0.0f, -0.998789f, 0.049207f, -0.440416f, -0.782348f, 0.440416f, -0.560555f, -0.609553f,
+ 0.560555f, -0.124903f, -0.984276f, 0.124903f, -0.034662f, -0.998798f, 0.034662f, -0.57623f, -0.7818f,
+ 0.238217f, -0.732949f, -0.608995f, 0.303167f, -0.163628f, -0.984208f, 0.067527f, -0.045422f,
+ -0.998792f, 0.018736f, -0.623859f, -0.781537f, 0.0f, -0.793949f, -0.607984f, 0.0f, -0.177291f,
+ -0.984159f, -0.0f, -0.049207f, -0.998789f, -0.0f, -0.57623f, -0.7818f, -0.238217f, -0.623859f,
+ -0.781537f, 0.0f, -0.793949f, -0.607984f, 0.0f, -0.732949f, -0.608995f, -0.303167f, -0.163628f,
+ -0.984208f, -0.067527f, -0.177291f, -0.984159f, -0.0f, -0.045422f, -0.998792f, -0.018736f, -0.049207f,
+ -0.998789f, -0.0f, -0.440416f, -0.782348f, -0.440416f, -0.560555f, -0.609553f, -0.560555f, -0.124903f,
+ -0.984276f, -0.124903f, -0.034662f, -0.998798f, -0.034662f, -0.238217f, -0.7818f, -0.57623f,
+ -0.303167f, -0.608995f, -0.732949f, -0.067527f, -0.984208f, -0.163628f, -0.018736f, -0.998792f,
+ -0.045422f, -0.0f, -0.781537f, -0.623859f, -0.0f, -0.607984f, -0.793949f, 0.0f, -0.984159f, -0.177291f,
+ 0.0f, -0.998789f, -0.049207f, 0.238217f, -0.7818f, -0.57623f, -0.0f, -0.781537f, -0.623859f, -0.0f,
+ -0.607984f, -0.793949f, 0.303167f, -0.608995f, -0.732949f, 0.067527f, -0.984208f, -0.163628f, 0.0f,
+ -0.984159f, -0.177291f, 0.018736f, -0.998792f, -0.045422f, 0.0f, -0.998789f, -0.049207f, 0.440416f,
+ -0.782348f, -0.440416f, 0.560555f, -0.609553f, -0.560555f, 0.124903f, -0.984276f, -0.124903f,
+ 0.034662f, -0.998798f, -0.034662f, 0.57623f, -0.7818f, -0.238217f, 0.732949f, -0.608995f, -0.303167f,
+ 0.163628f, -0.984208f, -0.067527f, 0.623859f, -0.781537f, -0.0f, 0.793949f, -0.607984f, -0.0f,
+ 0.177291f, -0.984159f, 0.0f, 0.049207f, -0.998789f, 0.0f, 0.036127f, -0.837257f, 0.545614f, 0.039138f,
+ -0.999233f, -9.89E-4f, 0.007786f, -0.99997f, -2.16E-4f, 0.007039f, -0.812494f, 0.582926f, 0.161845f,
+ -0.810421f, 0.563048f, 0.179512f, -0.983746f, -0.004369f, 0.482365f, -0.595148f, 0.642746f, 0.6123f,
+ -0.790557f, -0.01046f, 0.73872f, -0.114594f, 0.664199f, 0.986152f, -0.165708f, -0.00667f, 0.002762f,
+ 0.017107f, 0.99985f, -0.001909f, 0.162121f, 0.986769f, 0.010533f, 0.073398f, 0.997247f, -0.066041f,
+ 0.13007f, 0.989303f, -0.094427f, 0.016594f, 0.995394f, -0.048606f, 0.840609f, 0.539458f, -0.009203f,
+ 0.871509f, 0.490293f, -0.223298f, 0.802881f, 0.552739f, -0.596365f, 0.559971f, 0.575136f, -0.803337f,
+ 0.068236f, 0.591603f, -0.058799f, 0.99827f, 7.1E-4f, -0.010561f, 0.999944f, 1.03E-4f, -0.28071f,
+ 0.959787f, 0.003269f, -0.749723f, 0.661738f, 0.004268f, -0.997351f, 0.072714f, 0.002059f, -0.046494f,
+ 0.841178f, -0.538756f, -0.058799f, 0.99827f, 7.1E-4f, -0.010561f, 0.999944f, 1.03E-4f, -0.008792f,
+ 0.871493f, -0.49033f, -0.217909f, 0.806807f, -0.549161f, -0.28071f, 0.959787f, 0.003269f, -0.59729f,
+ 0.560026f, -0.574121f, -0.749723f, 0.661738f, 0.004268f, -0.804f, 0.062913f, -0.591292f, -0.997351f,
+ 0.072714f, 0.002059f, 0.002031f, 0.014555f, -0.999892f, -0.001806f, 0.161691f, -0.98684f, 0.009215f,
+ 0.060069f, -0.998152f, -0.059334f, 0.113865f, -0.991723f, -0.086899f, 0.01229f, -0.996141f, 0.033783f,
+ -0.837512f, -0.545373f, 0.006418f, -0.812379f, -0.583095f, 0.157113f, -0.811947f, -0.562189f,
+ 0.484406f, -0.589366f, -0.646528f, 0.73887f, -0.10132f, -0.666187f, 0.039138f, -0.999233f, -9.89E-4f,
+ 0.007786f, -0.99997f, -2.16E-4f, 0.179512f, -0.983746f, -0.004369f, 0.6123f, -0.790557f, -0.01046f,
+ 0.986152f, -0.165708f, -0.00667f, 0.725608f, 0.259351f, 0.637361f, 0.946512f, 0.32265f, -0.003357f,
+ 0.986152f, -0.165708f, -0.00667f, 0.73872f, -0.114594f, 0.664199f, 0.645945f, 0.461988f, 0.607719f,
+ 0.82583f, 0.56387f, -0.007452f, 0.531615f, 0.63666f, 0.558614f, 0.650011f, 0.759893f, -0.006937f,
+ 0.424964f, 0.681717f, 0.595539f, 0.532429f, 0.846458f, -0.005245f, -0.049562f, -0.019755f, 0.998576f,
+ -0.094427f, 0.016594f, 0.995394f, -0.037817f, -0.035625f, 0.998649f, -0.037914f, -0.036512f, 0.998614f,
+ -0.168854f, -0.297946f, 0.93953f, -0.742342f, -0.299166f, 0.599523f, -0.803337f, 0.068236f, 0.591603f,
+ -0.619602f, -0.529406f, 0.579502f, -0.483708f, -0.68576f, 0.543837f, -0.445293f, -0.794355f, 0.413177f,
+ -0.926513f, -0.376258f, 0.001996f, -0.997351f, 0.072714f, 0.002059f, -0.75392f, -0.656952f, 0.004317f,
+ -0.566224f, -0.824244f, 0.003461f, -0.481804f, -0.876277f, 0.001851f, -0.744675f, -0.294425f,
+ -0.598976f, -0.926513f, -0.376258f, 0.001996f, -0.997351f, 0.072714f, 0.002059f, -0.804f, 0.062913f,
+ -0.591292f, -0.62195f, -0.528114f, -0.578165f, -0.75392f, -0.656952f, 0.004317f, -0.481171f, -0.68834f,
+ -0.542829f, -0.566224f, -0.824244f, 0.003461f, -0.438055f, -0.797035f, -0.415744f, -0.481804f,
+ -0.876277f, 0.001851f, -0.044337f, -0.017056f, -0.998871f, -0.086899f, 0.01229f, -0.996141f,
+ -0.026176f, -0.028167f, -0.99926f, -0.025294f, -0.028332f, -0.999278f, -0.157482f, -0.289392f,
+ -0.944167f, 0.728244f, 0.25241f, -0.637142f, 0.73887f, -0.10132f, -0.666187f, 0.647055f, 0.459725f,
+ -0.608254f, 0.522994f, 0.640657f, -0.56217f, 0.409978f, 0.682857f, -0.604669f, 0.946512f, 0.32265f,
+ -0.003357f, 0.986152f, -0.165708f, -0.00667f, 0.82583f, 0.56387f, -0.007452f, 0.650011f, 0.759893f,
+ -0.006937f, 0.532429f, 0.846458f, -0.005245f, -0.316721f, 0.63775f, 0.702113f, -0.548936f, 0.835863f,
+ -0.001511f, -0.230787f, 0.972982f, -0.006523f, -0.152878f, 0.687211f, 0.71019f, -0.601067f, 0.471452f,
+ 0.64533f, -0.875671f, 0.482807f, 0.009893f, -0.635889f, 0.446089f, 0.629801f, -0.877554f, 0.479097f,
+ 0.019092f, -0.435746f, 0.601008f, 0.670011f, -0.69619f, 0.717439f, 0.024497f, 0.22331f, 0.00654f,
+ 0.974726f, 0.111113f, -0.085069f, 0.99016f, 0.190097f, 0.154964f, 0.969458f, 0.005271f, 0.189482f,
+ 0.98187f, -0.011752f, 0.246688f, 0.969024f, 0.572489f, -0.567656f, 0.591627f, 0.343906f, -0.722796f,
+ 0.599412f, 0.787436f, -0.256459f, 0.560511f, 0.647097f, -0.306374f, 0.698141f, 0.427528f, -0.499344f,
+ 0.753575f, 0.67152f, -0.740986f, -8.99E-4f, 0.410926f, -0.911668f, 0.001284f, 0.922026f, -0.38706f,
+ -0.007253f, 0.84691f, -0.531556f, -0.013854f, 0.535924f, -0.844201f, -0.010505f, 0.578664f, -0.561139f,
+ -0.591838f, 0.67152f, -0.740986f, -8.99E-4f, 0.410926f, -0.911668f, 0.001284f, 0.341188f, -0.722823f,
+ -0.600931f, 0.784869f, -0.25102f, -0.566542f, 0.922026f, -0.38706f, -0.007253f, 0.642681f, -0.302257f,
+ -0.70399f, 0.84691f, -0.531556f, -0.013854f, 0.418589f, -0.500042f, -0.758117f, 0.535924f, -0.844201f,
+ -0.010505f, 0.232811f, 0.012565f, -0.972441f, 0.115806f, -0.079139f, -0.990114f, 0.206662f, 0.153601f,
+ -0.96628f, 0.0245f, 0.161443f, -0.986578f, 0.003382f, 0.211115f, -0.977455f, -0.31954f, 0.633073f,
+ -0.705062f, -0.134912f, 0.687491f, -0.713551f, -0.603902f, 0.461442f, -0.649903f, -0.631815f,
+ 0.437169f, -0.640072f, -0.424306f, 0.612706f, -0.666751f, -0.548936f, 0.835863f, -0.001511f,
+ -0.230787f, 0.972982f, -0.006523f, -0.875671f, 0.482807f, 0.009893f, -0.877554f, 0.479097f, 0.019092f,
+ -0.69619f, 0.717439f, 0.024497f, -0.259858f, 0.791937f, 0.552548f, -0.4258f, 0.904753f, 0.010805f,
+ -0.69619f, 0.717439f, 0.024497f, -0.435746f, 0.601008f, 0.670011f, 0.009539f, 0.99972f, -0.021673f,
+ 0.022046f, 0.999756f, 0.001623f, 0.410156f, 0.332913f, -0.849082f, 0.999598f, 0.025879f, 0.011556f,
+ 0.541522f, -0.54862f, -0.637001f, 0.709587f, -0.704552f, 0.009672f, 0.046311f, 0.455225f, 0.889171f,
+ -0.011752f, 0.246688f, 0.969024f, -0.010688f, 0.988795f, 0.1489f, -0.044375f, 0.682947f, -0.729118f,
+ 0.122825f, 0.009233f, -0.992385f, 0.481839f, -0.18044f, 0.857481f, 0.427528f, -0.499344f, 0.753575f,
+ 0.455273f, 0.73675f, 0.499926f, -0.220541f, 0.907194f, -0.358275f, -0.235919f, 0.65725f, -0.715797f,
+ 0.728092f, -0.685302f, -0.015585f, 0.535924f, -0.844201f, -0.010505f, 0.888738f, 0.458112f, -0.016678f,
+ -0.260099f, 0.965582f, 8.01E-4f, -0.371611f, 0.928378f, -0.004418f, 0.480165f, -0.178361f, -0.858853f,
+ 0.728092f, -0.685302f, -0.015585f, 0.535924f, -0.844201f, -0.010505f, 0.418589f, -0.500042f,
+ -0.758117f, 0.488104f, 0.716799f, -0.497949f, 0.888738f, 0.458112f, -0.016678f, -0.222004f, 0.9054f,
+ 0.361891f, -0.260099f, 0.965582f, 8.01E-4f, -0.235404f, 0.66318f, 0.710477f, -0.371611f, 0.928378f,
+ -0.004418f, 0.058721f, 0.437704f, -0.8972f, 0.003382f, 0.211115f, -0.977455f, 0.001326f, 0.986459f,
+ -0.164002f, -0.04419f, 0.681677f, 0.730318f, 0.138801f, -0.034188f, 0.98973f, -0.258889f, 0.797206f,
+ -0.545379f, -0.424306f, 0.612706f, -0.666751f, 0.012271f, 0.999739f, 0.019287f, 0.398631f, 0.354891f,
+ 0.845663f, 0.537564f, -0.5814f, 0.610737f, -0.4258f, 0.904753f, 0.010805f, -0.69619f, 0.717439f,
+ 0.024497f, 0.022046f, 0.999756f, 0.001623f, 0.999598f, 0.025879f, 0.011556f, 0.709587f, -0.704552f,
+ 0.009672f, 0.0f, 1.0f, 0.0f, 0.583357f, 0.565165f, 0.583338f, 0.76264f, 0.565035f, 0.314825f, 0.82454f,
+ 0.565803f, 1.7E-5f, 0.847982f, -0.397998f, 0.350034f, 0.917701f, -0.397272f, 3.4E-5f, 0.864141f,
+ -0.355261f, 0.356441f, 0.935268f, -0.353939f, 1.13E-4f, 0.720992f, 0.625625f, 0.297933f, 0.780712f,
+ 0.62489f, 7.5E-5f, 0.648485f, -0.398726f, 0.648448f, 0.660872f, -0.355894f, 0.660748f, 0.551862f,
+ 0.62529f, 0.55178f, 0.0f, 1.0f, 0.0f, -1.7E-5f, 0.565803f, 0.82454f, 0.314824f, 0.565051f, 0.762629f,
+ 0.350045f, -0.397977f, 0.847988f, 0.356474f, -0.3552f, 0.864152f, 0.297983f, 0.625515f, 0.721067f,
+ -3.3E-5f, -0.397272f, 0.917701f, -1.13E-4f, -0.353939f, 0.935268f, -7.5E-5f, 0.62489f, 0.780712f, 0.0f,
+ 1.0f, 0.0f, -0.583338f, 0.565165f, 0.583357f, -0.314825f, 0.565035f, 0.76264f, -1.7E-5f, 0.565803f,
+ 0.82454f, -0.350034f, -0.397998f, 0.847982f, -3.3E-5f, -0.397272f, 0.917701f, -0.356441f, -0.355261f,
+ 0.864141f, -1.13E-4f, -0.353939f, 0.935268f, -0.297933f, 0.625625f, 0.720992f, -7.5E-5f, 0.62489f,
+ 0.780712f, -0.648448f, -0.398726f, 0.648485f, -0.660748f, -0.355894f, 0.660872f, -0.55178f, 0.62529f,
+ 0.551862f, 0.0f, 1.0f, 0.0f, -0.82454f, 0.565803f, -1.7E-5f, -0.762629f, 0.565051f, 0.314824f,
+ -0.847988f, -0.397977f, 0.350045f, -0.864152f, -0.3552f, 0.356474f, -0.721067f, 0.625515f, 0.297983f,
+ -0.917701f, -0.397272f, -3.3E-5f, -0.935268f, -0.353939f, -1.13E-4f, -0.780712f, 0.62489f, -7.5E-5f,
+ 0.0f, 1.0f, 0.0f, -0.583357f, 0.565165f, -0.583338f, -0.76264f, 0.565035f, -0.314825f, -0.82454f,
+ 0.565803f, -1.7E-5f, -0.847982f, -0.397998f, -0.350034f, -0.917701f, -0.397272f, -3.3E-5f, -0.864141f,
+ -0.355261f, -0.356441f, -0.935268f, -0.353939f, -1.13E-4f, -0.720992f, 0.625625f, -0.297933f,
+ -0.780712f, 0.62489f, -7.5E-5f, -0.648485f, -0.398726f, -0.648448f, -0.660872f, -0.355894f, -0.660748f,
+ -0.551862f, 0.62529f, -0.55178f, 0.0f, 1.0f, 0.0f, 1.7E-5f, 0.565803f, -0.82454f, -0.314824f,
+ 0.565051f, -0.762629f, -0.350045f, -0.397977f, -0.847988f, -0.356474f, -0.3552f, -0.864152f,
+ -0.297983f, 0.625515f, -0.721067f, 3.3E-5f, -0.397272f, -0.917701f, 1.13E-4f, -0.353939f, -0.935268f,
+ 7.5E-5f, 0.62489f, -0.780712f, 0.0f, 1.0f, 0.0f, 0.583338f, 0.565165f, -0.583357f, 0.314825f,
+ 0.565035f, -0.76264f, 1.7E-5f, 0.565803f, -0.82454f, 0.350034f, -0.397998f, -0.847982f, 3.3E-5f,
+ -0.397272f, -0.917701f, 0.356441f, -0.355261f, -0.864141f, 1.13E-4f, -0.353939f, -0.935268f, 0.297933f,
+ 0.625625f, -0.720992f, 7.5E-5f, 0.62489f, -0.780712f, 0.648448f, -0.398726f, -0.648485f, 0.660748f,
+ -0.355894f, -0.660872f, 0.55178f, 0.62529f, -0.551862f, 0.0f, 1.0f, 0.0f, 0.82454f, 0.565803f, 1.7E-5f,
+ 0.762629f, 0.565051f, -0.314824f, 0.847988f, -0.397977f, -0.350045f, 0.864152f, -0.3552f, -0.356474f,
+ 0.721067f, 0.625515f, -0.297983f, 0.917701f, -0.397272f, 3.4E-5f, 0.935268f, -0.353939f, 1.13E-4f,
+ 0.780712f, 0.62489f, 7.5E-5f, 0.217978f, 0.971775f, 0.090216f, 0.236584f, 0.971611f, 0.0f, 0.780712f,
+ 0.62489f, 7.5E-5f, 0.720992f, 0.625625f, 0.297933f, 0.159589f, 0.984977f, 0.065961f, 0.173084f,
+ 0.984907f, 0.0f, 0.350498f, 0.925312f, 0.144739f, 0.379703f, 0.925108f, -0.0f, 0.485589f, 0.850654f,
+ 0.201474f, 0.526672f, 0.850069f, 0.0f, 0.166631f, 0.971838f, 0.166631f, 0.551862f, 0.62529f, 0.55178f,
+ 0.121908f, 0.985026f, 0.121908f, 0.267668f, 0.925585f, 0.267668f, 0.371315f, 0.851029f, 0.371315f,
+ 0.090216f, 0.971775f, 0.217978f, 0.297983f, 0.625515f, 0.721067f, 0.065961f, 0.984977f, 0.159589f,
+ 0.144739f, 0.925312f, 0.350498f, 0.201474f, 0.850654f, 0.485589f, -0.0f, 0.971611f, 0.236584f,
+ -7.5E-5f, 0.62489f, 0.780712f, -0.0f, 0.984907f, 0.173084f, 0.0f, 0.925108f, 0.379703f, 0.0f,
+ 0.850069f, 0.526672f, -0.090216f, 0.971775f, 0.217978f, -0.0f, 0.971611f, 0.236584f, -7.5E-5f,
+ 0.62489f, 0.780712f, -0.297933f, 0.625625f, 0.720992f, -0.065961f, 0.984977f, 0.159589f, -0.0f,
+ 0.984907f, 0.173084f, -0.144739f, 0.925312f, 0.350498f, 0.0f, 0.925108f, 0.379703f, -0.201474f,
+ 0.850654f, 0.485589f, 0.0f, 0.850069f, 0.526672f, -0.166631f, 0.971838f, 0.166631f, -0.55178f,
+ 0.62529f, 0.551862f, -0.121908f, 0.985026f, 0.121908f, -0.267668f, 0.925585f, 0.267668f, -0.371315f,
+ 0.851029f, 0.371315f, -0.217978f, 0.971775f, 0.090216f, -0.721067f, 0.625515f, 0.297983f, -0.159589f,
+ 0.984977f, 0.065961f, -0.350498f, 0.925312f, 0.144739f, -0.485589f, 0.850654f, 0.201474f, -0.236584f,
+ 0.971611f, -0.0f, -0.780712f, 0.62489f, -7.5E-5f, -0.173084f, 0.984907f, -0.0f, -0.379703f, 0.925108f,
+ 0.0f, -0.526672f, 0.850069f, 0.0f, -0.217978f, 0.971775f, -0.090216f, -0.236584f, 0.971611f, -0.0f,
+ -0.780712f, 0.62489f, -7.5E-5f, -0.720992f, 0.625625f, -0.297933f, -0.159589f, 0.984977f, -0.065961f,
+ -0.173084f, 0.984907f, -0.0f, -0.350498f, 0.925312f, -0.144739f, -0.379703f, 0.925108f, 0.0f,
+ -0.485589f, 0.850654f, -0.201474f, -0.526672f, 0.850069f, 0.0f, -0.166631f, 0.971838f, -0.166631f,
+ -0.551862f, 0.62529f, -0.55178f, -0.121908f, 0.985026f, -0.121908f, -0.267668f, 0.925585f, -0.267668f,
+ -0.371315f, 0.851029f, -0.371315f, -0.090216f, 0.971775f, -0.217978f, -0.297983f, 0.625515f,
+ -0.721067f, -0.065961f, 0.984977f, -0.159589f, -0.144739f, 0.925312f, -0.350498f, -0.201474f,
+ 0.850654f, -0.485589f, 0.0f, 0.971611f, -0.236584f, 7.5E-5f, 0.62489f, -0.780712f, 0.0f, 0.984907f,
+ -0.173084f, -0.0f, 0.925108f, -0.379703f, -0.0f, 0.850069f, -0.526672f, 0.090216f, 0.971775f,
+ -0.217978f, 0.0f, 0.971611f, -0.236584f, 7.5E-5f, 0.62489f, -0.780712f, 0.297933f, 0.625625f,
+ -0.720992f, 0.065961f, 0.984977f, -0.159589f, 0.0f, 0.984907f, -0.173084f, 0.144739f, 0.925312f,
+ -0.350498f, -0.0f, 0.925108f, -0.379703f, 0.201474f, 0.850654f, -0.485589f, -0.0f, 0.850069f,
+ -0.526672f, 0.166631f, 0.971838f, -0.166631f, 0.55178f, 0.62529f, -0.551862f, 0.121908f, 0.985026f,
+ -0.121908f, 0.267668f, 0.925585f, -0.267668f, 0.371315f, 0.851029f, -0.371315f, 0.217978f, 0.971775f,
+ -0.090216f, 0.721067f, 0.625515f, -0.297983f, 0.159589f, 0.984977f, -0.065961f, 0.350498f, 0.925312f,
+ -0.144739f, 0.485589f, 0.850654f, -0.201474f, 0.236584f, 0.971611f, 0.0f, 0.780712f, 0.62489f, 7.5E-5f,
+ 0.173084f, 0.984907f, 0.0f, 0.379703f, 0.925108f, -0.0f, 0.526672f, 0.850069f, 0.0f, 0.0f, -1.0f,
+ -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f,
+ -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f,
+ -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f, 0.0f, -1.0f, -0.0f };
+ _meshData.setNormalBuffer(BufferUtils.createFloatBuffer(norms));
+ }
+
+ /**
+ * <code>setTextureData</code> sets the points that define the texture of the Teapot. It uses a range of [0, 2] so
+ * it is important to use wrapping on your texture.
+ */
+ private void setTextureData() {
+ final float[] texs = new float[] { 1.75f, 1.975f, 2.0f, 1.975f, 2.0f, 2.0f, 1.75f, 2.0f, 1.75f, 1.95f, 2.0f,
+ 1.95f, 1.75f, 1.925f, 2.0f, 1.925f, 1.75f, 1.9f, 2.0f, 1.9f, 1.5f, 1.975f, 1.5f, 2.0f, 1.5f, 1.95f,
+ 1.5f, 1.925f, 1.5f, 1.9f, 1.25f, 1.975f, 1.25f, 2.0f, 1.25f, 1.95f, 1.25f, 1.925f, 1.25f, 1.9f, 1.0f,
+ 1.975f, 1.0f, 2.0f, 1.0f, 1.95f, 1.0f, 1.925f, 1.0f, 1.9f, 0.75f, 1.975f, 1.0f, 1.975f, 1.0f, 2.0f,
+ 0.75f, 2.0f, 0.75f, 1.95f, 1.0f, 1.95f, 0.75f, 1.925f, 1.0f, 1.925f, 0.75f, 1.9f, 1.0f, 1.9f, 0.5f,
+ 1.975f, 0.5f, 2.0f, 0.5f, 1.95f, 0.5f, 1.925f, 0.5f, 1.9f, 0.25f, 1.975f, 0.25f, 2.0f, 0.25f, 1.95f,
+ 0.25f, 1.925f, 0.25f, 1.9f, 0.0f, 1.975f, 0.0f, 2.0f, 0.0f, 1.95f, 0.0f, 1.925f, 0.0f, 1.9f, 1.75f,
+ 1.975f, 2.0f, 1.975f, 2.0f, 2.0f, 1.75f, 2.0f, 1.75f, 1.95f, 2.0f, 1.95f, 1.75f, 1.925f, 2.0f, 1.925f,
+ 1.75f, 1.9f, 2.0f, 1.9f, 1.5f, 1.975f, 1.5f, 2.0f, 1.5f, 1.95f, 1.5f, 1.925f, 1.5f, 1.9f, 1.25f,
+ 1.975f, 1.25f, 2.0f, 1.25f, 1.95f, 1.25f, 1.925f, 1.25f, 1.9f, 1.0f, 1.975f, 1.0f, 2.0f, 1.0f, 1.95f,
+ 1.0f, 1.925f, 1.0f, 1.9f, 0.75f, 1.975f, 1.0f, 1.975f, 1.0f, 2.0f, 0.75f, 2.0f, 0.75f, 1.95f, 1.0f,
+ 1.95f, 0.75f, 1.925f, 1.0f, 1.925f, 0.75f, 1.9f, 1.0f, 1.9f, 0.5f, 1.975f, 0.5f, 2.0f, 0.5f, 1.95f,
+ 0.5f, 1.925f, 0.5f, 1.9f, 0.25f, 1.975f, 0.25f, 2.0f, 0.25f, 1.95f, 0.25f, 1.925f, 0.25f, 1.9f, 0.0f,
+ 1.975f, 0.0f, 2.0f, 0.0f, 1.95f, 0.0f, 1.925f, 0.0f, 1.9f, 1.75f, 1.675f, 2.0f, 1.675f, 2.0f, 1.9f,
+ 1.75f, 1.9f, 1.75f, 1.45f, 2.0f, 1.45f, 1.75f, 1.225f, 2.0f, 1.225f, 1.75f, 1.0f, 2.0f, 1.0f, 1.5f,
+ 1.675f, 1.5f, 1.9f, 1.5f, 1.45f, 1.5f, 1.225f, 1.5f, 1.0f, 1.25f, 1.675f, 1.25f, 1.9f, 1.25f, 1.45f,
+ 1.25f, 1.225f, 1.25f, 1.0f, 1.0f, 1.675f, 1.0f, 1.9f, 1.0f, 1.45f, 1.0f, 1.225f, 1.0f, 1.0f, 0.75f,
+ 1.675f, 1.0f, 1.675f, 1.0f, 1.9f, 0.75f, 1.9f, 0.75f, 1.45f, 1.0f, 1.45f, 0.75f, 1.225f, 1.0f, 1.225f,
+ 0.75f, 1.0f, 1.0f, 1.0f, 0.5f, 1.675f, 0.5f, 1.9f, 0.5f, 1.45f, 0.5f, 1.225f, 0.5f, 1.0f, 0.25f,
+ 1.675f, 0.25f, 1.9f, 0.25f, 1.45f, 0.25f, 1.225f, 0.25f, 1.0f, 0.0f, 1.675f, 0.0f, 1.9f, 0.0f, 1.45f,
+ 0.0f, 1.225f, 0.0f, 1.0f, 1.75f, 1.675f, 2.0f, 1.675f, 2.0f, 1.9f, 1.75f, 1.9f, 1.75f, 1.45f, 2.0f,
+ 1.45f, 1.75f, 1.225f, 2.0f, 1.225f, 1.75f, 1.0f, 2.0f, 1.0f, 1.5f, 1.675f, 1.5f, 1.9f, 1.5f, 1.45f,
+ 1.5f, 1.225f, 1.5f, 1.0f, 1.25f, 1.675f, 1.25f, 1.9f, 1.25f, 1.45f, 1.25f, 1.225f, 1.25f, 1.0f, 1.0f,
+ 1.675f, 1.0f, 1.9f, 1.0f, 1.45f, 1.0f, 1.225f, 1.0f, 1.0f, 0.75f, 1.675f, 1.0f, 1.675f, 1.0f, 1.9f,
+ 0.75f, 1.9f, 0.75f, 1.45f, 1.0f, 1.45f, 0.75f, 1.225f, 1.0f, 1.225f, 0.75f, 1.0f, 1.0f, 1.0f, 0.5f,
+ 1.675f, 0.5f, 1.9f, 0.5f, 1.45f, 0.5f, 1.225f, 0.5f, 1.0f, 0.25f, 1.675f, 0.25f, 1.9f, 0.25f, 1.45f,
+ 0.25f, 1.225f, 0.25f, 1.0f, 0.0f, 1.675f, 0.0f, 1.9f, 0.0f, 1.45f, 0.0f, 1.225f, 0.0f, 1.0f, 1.75f,
+ 0.85f, 2.0f, 0.85f, 2.0f, 1.0f, 1.75f, 1.0f, 1.75f, 0.7f, 2.0f, 0.7f, 1.75f, 0.55f, 2.0f, 0.55f, 1.75f,
+ 0.4f, 2.0f, 0.4f, 1.5f, 0.85f, 1.5f, 1.0f, 1.5f, 0.7f, 1.5f, 0.55f, 1.5f, 0.4f, 1.25f, 0.85f, 1.25f,
+ 1.0f, 1.25f, 0.7f, 1.25f, 0.55f, 1.25f, 0.4f, 1.0f, 0.85f, 1.0f, 1.0f, 1.0f, 0.7f, 1.0f, 0.55f, 1.0f,
+ 0.4f, 0.75f, 0.85f, 1.0f, 0.85f, 1.0f, 1.0f, 0.75f, 1.0f, 0.75f, 0.7f, 1.0f, 0.7f, 0.75f, 0.55f, 1.0f,
+ 0.55f, 0.75f, 0.4f, 1.0f, 0.4f, 0.5f, 0.85f, 0.5f, 1.0f, 0.5f, 0.7f, 0.5f, 0.55f, 0.5f, 0.4f, 0.25f,
+ 0.85f, 0.25f, 1.0f, 0.25f, 0.7f, 0.25f, 0.55f, 0.25f, 0.4f, 0.0f, 0.85f, 0.0f, 1.0f, 0.0f, 0.7f, 0.0f,
+ 0.55f, 0.0f, 0.4f, 1.75f, 0.85f, 2.0f, 0.85f, 2.0f, 1.0f, 1.75f, 1.0f, 1.75f, 0.7f, 2.0f, 0.7f, 1.75f,
+ 0.55f, 2.0f, 0.55f, 1.75f, 0.4f, 2.0f, 0.4f, 1.5f, 0.85f, 1.5f, 1.0f, 1.5f, 0.7f, 1.5f, 0.55f, 1.5f,
+ 0.4f, 1.25f, 0.85f, 1.25f, 1.0f, 1.25f, 0.7f, 1.25f, 0.55f, 1.25f, 0.4f, 1.0f, 0.85f, 1.0f, 1.0f, 1.0f,
+ 0.7f, 1.0f, 0.55f, 1.0f, 0.4f, 0.75f, 0.85f, 1.0f, 0.85f, 1.0f, 1.0f, 0.75f, 1.0f, 0.75f, 0.7f, 1.0f,
+ 0.7f, 0.75f, 0.55f, 1.0f, 0.55f, 0.75f, 0.4f, 1.0f, 0.4f, 0.5f, 0.85f, 0.5f, 1.0f, 0.5f, 0.7f, 0.5f,
+ 0.55f, 0.5f, 0.4f, 0.25f, 0.85f, 0.25f, 1.0f, 0.25f, 0.7f, 0.25f, 0.55f, 0.25f, 0.4f, 0.0f, 0.85f,
+ 0.0f, 1.0f, 0.0f, 0.7f, 0.0f, 0.55f, 0.0f, 0.4f, 1.75f, 0.3f, 2.0f, 0.3f, 2.0f, 0.4f, 1.75f, 0.4f,
+ 1.75f, 0.2f, 2.0f, 0.2f, 1.75f, 0.1f, 2.0f, 0.1f, 1.75f, 0.0f, 0.25f, 0.1f, 1.5f, 0.3f, 1.5f, 0.4f,
+ 1.5f, 0.2f, 1.5f, 0.1f, 1.25f, 0.3f, 1.25f, 0.4f, 1.25f, 0.2f, 1.25f, 0.1f, 1.0f, 0.3f, 1.0f, 0.4f,
+ 1.0f, 0.2f, 1.0f, 0.1f, 0.75f, 0.3f, 1.0f, 0.3f, 1.0f, 0.4f, 0.75f, 0.4f, 0.75f, 0.2f, 1.0f, 0.2f,
+ 0.75f, 0.1f, 1.0f, 0.1f, 0.5f, 0.3f, 0.5f, 0.4f, 0.5f, 0.2f, 0.5f, 0.1f, 0.25f, 0.3f, 0.25f, 0.4f,
+ 0.25f, 0.2f, 0.25f, 0.1f, 0.0f, 0.3f, 0.0f, 0.4f, 0.0f, 0.2f, 0.0f, 0.1f, 1.75f, 0.3f, 2.0f, 0.3f,
+ 2.0f, 0.4f, 1.75f, 0.4f, 1.75f, 0.2f, 2.0f, 0.2f, 1.75f, 0.1f, 2.0f, 0.1f, 1.5f, 0.3f, 1.5f, 0.4f,
+ 1.5f, 0.2f, 1.5f, 0.1f, 1.25f, 0.3f, 1.25f, 0.4f, 1.25f, 0.2f, 1.25f, 0.1f, 1.0f, 0.3f, 1.0f, 0.4f,
+ 1.0f, 0.2f, 1.0f, 0.1f, 0.75f, 0.3f, 1.0f, 0.3f, 1.0f, 0.4f, 0.75f, 0.4f, 0.75f, 0.2f, 1.0f, 0.2f,
+ 0.75f, 0.1f, 1.0f, 0.1f, 0.5f, 0.3f, 0.5f, 0.4f, 0.5f, 0.2f, 0.5f, 0.1f, 0.25f, 0.3f, 0.25f, 0.4f,
+ 0.25f, 0.2f, 0.0f, 0.3f, 0.0f, 0.4f, 0.0f, 0.2f, 0.0f, 0.1f, 0.875f, 0.875f, 1.0f, 0.875f, 1.0f, 1.0f,
+ 0.875f, 1.0f, 0.875f, 0.75f, 1.0f, 0.75f, 0.875f, 0.625f, 1.0f, 0.625f, 0.875f, 0.5f, 1.0f, 0.5f,
+ 0.75f, 0.875f, 0.75f, 1.0f, 0.75f, 0.75f, 0.75f, 0.625f, 0.75f, 0.5f, 0.625f, 0.875f, 0.625f, 1.0f,
+ 0.625f, 0.75f, 0.625f, 0.625f, 0.625f, 0.5f, 0.5f, 0.875f, 0.5f, 1.0f, 0.5f, 0.75f, 0.5f, 0.625f, 0.5f,
+ 0.5f, 0.375f, 0.875f, 0.5f, 0.875f, 0.5f, 1.0f, 0.375f, 1.0f, 0.375f, 0.75f, 0.5f, 0.75f, 0.375f,
+ 0.625f, 0.5f, 0.625f, 0.375f, 0.5f, 0.5f, 0.5f, 0.25f, 0.875f, 0.25f, 1.0f, 0.25f, 0.75f, 0.25f,
+ 0.625f, 0.25f, 0.5f, 0.125f, 0.875f, 0.125f, 1.0f, 0.125f, 0.75f, 0.125f, 0.625f, 0.125f, 0.5f, 0.0f,
+ 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.0f, 0.625f, 0.0f, 0.5f, 0.875f, 0.375f, 1.0f, 0.375f, 1.0f, 0.5f,
+ 0.875f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.125f, 1.0f, 0.125f, 0.875f, 0.0f, 1.0f, 0.0f,
+ 0.75f, 0.375f, 0.75f, 0.5f, 0.75f, 0.25f, 0.75f, 0.125f, 0.75f, 0.0f, 0.625f, 0.375f, 0.625f, 0.5f,
+ 0.625f, 0.25f, 0.625f, 0.125f, 0.625f, 0.0f, 0.5f, 0.375f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.125f, 0.5f,
+ 0.0f, 0.375f, 0.375f, 0.5f, 0.375f, 0.5f, 0.5f, 0.375f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f,
+ 0.125f, 0.5f, 0.125f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.375f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f,
+ 0.125f, 0.25f, 0.0f, 0.125f, 0.375f, 0.125f, 0.5f, 0.125f, 0.25f, 0.125f, 0.125f, 0.125f, 0.0f, 0.0f,
+ 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.125f, 0.0f, 0.0f, 0.625f, 0.225f, 0.5f, 0.225f, 0.5f, 0.0f,
+ 0.625f, 0.0f, 0.625f, 0.45f, 0.5f, 0.45f, 0.625f, 0.675f, 0.5f, 0.675f, 0.625f, 0.9f, 0.5f, 0.9f,
+ 0.75f, 0.225f, 0.75f, 0.0f, 0.75f, 0.45f, 0.75f, 0.675f, 0.75f, 0.9f, 0.875f, 0.225f, 0.875f, 0.0f,
+ 0.875f, 0.45f, 0.875f, 0.675f, 0.875f, 0.9f, 1.0f, 0.225f, 1.0f, 0.0f, 1.0f, 0.45f, 1.0f, 0.675f, 1.0f,
+ 0.9f, 0.125f, 0.225f, 0.0f, 0.225f, 0.0f, 0.0f, 0.125f, 0.0f, 0.125f, 0.45f, 0.0f, 0.45f, 0.125f,
+ 0.675f, 0.0f, 0.675f, 0.125f, 0.9f, 0.0f, 0.9f, 0.25f, 0.225f, 0.25f, 0.0f, 0.25f, 0.45f, 0.25f,
+ 0.675f, 0.25f, 0.9f, 0.375f, 0.225f, 0.375f, 0.0f, 0.375f, 0.45f, 0.375f, 0.675f, 0.375f, 0.9f, 0.5f,
+ 0.225f, 0.5f, 0.0f, 0.5f, 0.45f, 0.5f, 0.675f, 0.5f, 0.9f, 0.625f, 0.925f, 0.5f, 0.925f, 0.5f, 0.9f,
+ 0.625f, 0.9f, 0.625f, 0.95f, 0.5f, 0.95f, 0.625f, 0.975f, 0.5f, 0.975f, 0.625f, 1.0f, 0.5f, 1.0f,
+ 0.75f, 0.925f, 0.75f, 0.9f, 0.75f, 0.95f, 0.75f, 0.975f, 0.75f, 1.0f, 0.875f, 0.925f, 0.875f, 0.9f,
+ 0.875f, 0.95f, 0.875f, 0.975f, 0.875f, 1.0f, 1.0f, 0.925f, 1.0f, 0.9f, 1.0f, 0.95f, 1.0f, 0.975f, 1.0f,
+ 1.0f, 0.125f, 0.925f, 0.0f, 0.925f, 0.0f, 0.9f, 0.125f, 0.9f, 0.125f, 0.95f, 0.0f, 0.95f, 0.125f,
+ 0.975f, 0.0f, 0.975f, 0.125f, 1.0f, 0.0f, 1.0f, 0.25f, 0.925f, 0.25f, 0.9f, 0.25f, 0.95f, 0.25f,
+ 0.975f, 0.25f, 1.0f, 0.375f, 0.925f, 0.375f, 0.9f, 0.375f, 0.95f, 0.375f, 0.975f, 0.375f, 1.0f, 0.5f,
+ 0.925f, 0.5f, 0.9f, 0.5f, 0.95f, 0.5f, 0.975f, 0.5f, 1.0f, 1.0f, 1.0f, 0.75f, 0.75f, 0.875f, 0.75f,
+ 1.0f, 0.75f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f,
+ 0.5f, 0.75f, 0.25f, 0.75f, 0.0f, 0.75f, 1.0f, 0.5f, 0.75f, 0.625f, 0.75f, 0.625f, 0.5f, 0.625f, 0.25f,
+ 0.625f, 0.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.375f, 0.75f, 0.5f,
+ 0.75f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.5f,
+ 0.25f, 0.25f, 0.25f, 0.0f, 0.25f, 1.0f, 0.0f, 0.75f, 0.125f, 0.75f, 0.125f, 0.5f, 0.125f, 0.25f,
+ 0.125f, 0.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f, 1.0f, 0.75f, 0.75f, 0.875f, 0.75f, 1.0f,
+ 0.75f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.5f,
+ 0.75f, 0.25f, 0.75f, 0.0f, 0.75f, 1.0f, 0.5f, 0.75f, 0.625f, 0.75f, 0.625f, 0.5f, 0.625f, 0.25f,
+ 0.625f, 0.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.5f, 1.0f, 0.25f, 0.75f, 0.375f, 0.75f, 0.5f,
+ 0.75f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.5f,
+ 0.25f, 0.25f, 0.25f, 0.0f, 0.25f, 1.0f, 0.0f, 0.75f, 0.125f, 0.75f, 0.125f, 0.5f, 0.125f, 0.25f,
+ 0.125f, 0.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 0.875f, 0.75f, 1.0f, 0.75f, 1.0f, 1.0f, 0.875f,
+ 1.0f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f, 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.75f,
+ 0.75f, 1.0f, 0.75f, 0.5f, 0.75f, 0.25f, 0.75f, 0.0f, 0.625f, 0.75f, 0.625f, 1.0f, 0.625f, 0.5f, 0.625f,
+ 0.25f, 0.625f, 0.0f, 0.5f, 0.75f, 0.5f, 1.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.375f, 0.75f, 0.5f,
+ 0.75f, 0.5f, 1.0f, 0.375f, 1.0f, 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f,
+ 0.5f, 0.0f, 0.25f, 0.75f, 0.25f, 1.0f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f, 0.0f, 0.125f, 0.75f, 0.125f,
+ 1.0f, 0.125f, 0.5f, 0.125f, 0.25f, 0.125f, 0.0f, 0.0f, 0.75f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.25f,
+ 0.0f, 0.0f, 0.875f, 0.75f, 1.0f, 0.75f, 1.0f, 1.0f, 0.875f, 1.0f, 0.875f, 0.5f, 1.0f, 0.5f, 0.875f,
+ 0.25f, 1.0f, 0.25f, 0.875f, 0.0f, 1.0f, 0.0f, 0.75f, 0.75f, 0.75f, 1.0f, 0.75f, 0.5f, 0.75f, 0.25f,
+ 0.75f, 0.0f, 0.625f, 0.75f, 0.625f, 1.0f, 0.625f, 0.5f, 0.625f, 0.25f, 0.625f, 0.0f, 0.5f, 0.75f, 0.5f,
+ 1.0f, 0.5f, 0.5f, 0.5f, 0.25f, 0.5f, 0.0f, 0.375f, 0.75f, 0.5f, 0.75f, 0.5f, 1.0f, 0.375f, 1.0f,
+ 0.375f, 0.5f, 0.5f, 0.5f, 0.375f, 0.25f, 0.5f, 0.25f, 0.375f, 0.0f, 0.5f, 0.0f, 0.25f, 0.75f, 0.25f,
+ 1.0f, 0.25f, 0.5f, 0.25f, 0.25f, 0.25f, 0.0f, 0.125f, 0.75f, 0.125f, 1.0f, 0.125f, 0.5f, 0.125f, 0.25f,
+ 0.125f, 0.0f, 0.0f, 0.75f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 1.5f, 0.0f, 1.25f, 0.0f,
+ 1.0f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f, 0.0f, 0.0f, 1.75f, 0.0f, 1.5f, 0.0f, 1.25f, 0.0f,
+ 1.0f, 0.0f, 0.75f, 0.0f, 0.5f, 0.0f, 0.25f, 0.0f };
+ _meshData.setTextureBuffer(BufferUtils.createFloatBuffer(texs), 0);
+ }
+
+ private void setIndexData() {
+ final short[] indices = { 0, 1, 2, 0, 2, 3, 4, 5, 1, 4, 1, 0, 6, 7, 5, 6, 5, 4, 8, 9, 7, 8, 7, 6, 10, 0, 3, 10,
+ 3, 11, 12, 4, 0, 12, 0, 10, 13, 6, 4, 13, 4, 12, 14, 8, 6, 14, 6, 13, 15, 10, 11, 15, 11, 16, 17, 12,
+ 10, 17, 10, 15, 18, 13, 12, 18, 12, 17, 19, 14, 13, 19, 13, 18, 20, 15, 16, 20, 16, 21, 22, 17, 15, 22,
+ 15, 20, 23, 18, 17, 23, 17, 22, 24, 19, 18, 24, 18, 23, 25, 26, 27, 25, 27, 28, 29, 30, 26, 29, 26, 25,
+ 31, 32, 30, 31, 30, 29, 33, 34, 32, 33, 32, 31, 35, 25, 28, 35, 28, 36, 37, 29, 25, 37, 25, 35, 38, 31,
+ 29, 38, 29, 37, 39, 33, 31, 39, 31, 38, 40, 35, 36, 40, 36, 41, 42, 37, 35, 42, 35, 40, 43, 38, 37, 43,
+ 37, 42, 44, 39, 38, 44, 38, 43, 45, 40, 41, 45, 41, 46, 47, 42, 40, 47, 40, 45, 48, 43, 42, 48, 42, 47,
+ 49, 44, 43, 49, 43, 48, 50, 51, 52, 50, 52, 53, 54, 55, 51, 54, 51, 50, 56, 57, 55, 56, 55, 54, 58, 59,
+ 57, 58, 57, 56, 60, 50, 53, 60, 53, 61, 62, 54, 50, 62, 50, 60, 63, 56, 54, 63, 54, 62, 64, 58, 56, 64,
+ 56, 63, 65, 60, 61, 65, 61, 66, 67, 62, 60, 67, 60, 65, 68, 63, 62, 68, 62, 67, 69, 64, 63, 69, 63, 68,
+ 70, 65, 66, 70, 66, 71, 72, 67, 65, 72, 65, 70, 73, 68, 67, 73, 67, 72, 74, 69, 68, 74, 68, 73, 75, 76,
+ 77, 75, 77, 78, 79, 80, 76, 79, 76, 75, 81, 82, 80, 81, 80, 79, 83, 84, 82, 83, 82, 81, 85, 75, 78, 85,
+ 78, 86, 87, 79, 75, 87, 75, 85, 88, 81, 79, 88, 79, 87, 89, 83, 81, 89, 81, 88, 90, 85, 86, 90, 86, 91,
+ 92, 87, 85, 92, 85, 90, 93, 88, 87, 93, 87, 92, 94, 89, 88, 94, 88, 93, 95, 90, 91, 95, 91, 96, 97, 92,
+ 90, 97, 90, 95, 98, 93, 92, 98, 92, 97, 99, 94, 93, 99, 93, 98, 100, 101, 102, 100, 102, 103, 104, 105,
+ 101, 104, 101, 100, 106, 107, 105, 106, 105, 104, 108, 109, 107, 108, 107, 106, 110, 100, 103, 110,
+ 103, 111, 112, 104, 100, 112, 100, 110, 113, 106, 104, 113, 104, 112, 114, 108, 106, 114, 106, 113,
+ 115, 110, 111, 115, 111, 116, 117, 112, 110, 117, 110, 115, 118, 113, 112, 118, 112, 117, 119, 114,
+ 113, 119, 113, 118, 120, 115, 116, 120, 116, 121, 122, 117, 115, 122, 115, 120, 123, 118, 117, 123,
+ 117, 122, 124, 119, 118, 124, 118, 123, 125, 126, 127, 125, 127, 128, 129, 130, 126, 129, 126, 125,
+ 131, 132, 130, 131, 130, 129, 133, 134, 132, 133, 132, 131, 135, 125, 128, 135, 128, 136, 137, 129,
+ 125, 137, 125, 135, 138, 131, 129, 138, 129, 137, 139, 133, 131, 139, 131, 138, 140, 135, 136, 140,
+ 136, 141, 142, 137, 135, 142, 135, 140, 143, 138, 137, 143, 137, 142, 144, 139, 138, 144, 138, 143,
+ 145, 140, 141, 145, 141, 146, 147, 142, 140, 147, 140, 145, 148, 143, 142, 148, 142, 147, 149, 144,
+ 143, 149, 143, 148, 150, 151, 152, 150, 152, 153, 154, 155, 151, 154, 151, 150, 156, 157, 155, 156,
+ 155, 154, 158, 159, 157, 158, 157, 156, 160, 150, 153, 160, 153, 161, 162, 154, 150, 162, 150, 160,
+ 163, 156, 154, 163, 154, 162, 164, 158, 156, 164, 156, 163, 165, 160, 161, 165, 161, 166, 167, 162,
+ 160, 167, 160, 165, 168, 163, 162, 168, 162, 167, 169, 164, 163, 169, 163, 168, 170, 165, 166, 170,
+ 166, 171, 172, 167, 165, 172, 165, 170, 173, 168, 167, 173, 167, 172, 174, 169, 168, 174, 168, 173,
+ 175, 176, 177, 175, 177, 178, 179, 180, 176, 179, 176, 175, 181, 182, 180, 181, 180, 179, 183, 184,
+ 182, 183, 182, 181, 185, 175, 178, 185, 178, 186, 187, 179, 175, 187, 175, 185, 188, 181, 179, 188,
+ 179, 187, 189, 183, 181, 189, 181, 188, 190, 185, 186, 190, 186, 191, 192, 187, 185, 192, 185, 190,
+ 193, 188, 187, 193, 187, 192, 194, 189, 188, 194, 188, 193, 195, 190, 191, 195, 191, 196, 197, 192,
+ 190, 197, 190, 195, 198, 193, 192, 198, 192, 197, 199, 194, 193, 199, 193, 198, 200, 201, 202, 200,
+ 202, 203, 204, 205, 201, 204, 201, 200, 206, 207, 205, 206, 205, 204, 208, 209, 207, 208, 207, 206,
+ 210, 200, 203, 210, 203, 211, 212, 204, 200, 212, 200, 210, 213, 206, 204, 213, 204, 212, 214, 208,
+ 206, 214, 206, 213, 215, 210, 211, 215, 211, 216, 217, 212, 210, 217, 210, 215, 218, 213, 212, 218,
+ 212, 217, 219, 214, 213, 219, 213, 218, 220, 215, 216, 220, 216, 221, 222, 217, 215, 222, 215, 220,
+ 223, 218, 217, 223, 217, 222, 224, 219, 218, 224, 218, 223, 225, 226, 227, 225, 227, 228, 229, 230,
+ 226, 229, 226, 225, 231, 232, 230, 231, 230, 229, 233, 234, 232, 233, 232, 231, 235, 225, 228, 235,
+ 228, 236, 237, 229, 225, 237, 225, 235, 238, 231, 229, 238, 229, 237, 239, 233, 231, 239, 231, 238,
+ 240, 235, 236, 240, 236, 241, 242, 237, 235, 242, 235, 240, 243, 238, 237, 243, 237, 242, 244, 239,
+ 238, 244, 238, 243, 245, 240, 241, 245, 241, 246, 247, 242, 240, 247, 240, 245, 248, 243, 242, 248,
+ 242, 247, 249, 244, 243, 249, 243, 248, 250, 251, 252, 250, 252, 253, 254, 255, 251, 254, 251, 250,
+ 256, 257, 255, 256, 255, 254, 258, 259, 257, 258, 257, 256, 260, 250, 253, 260, 253, 261, 262, 254,
+ 250, 262, 250, 260, 263, 256, 254, 263, 254, 262, 264, 258, 256, 264, 256, 263, 265, 260, 261, 265,
+ 261, 266, 267, 262, 260, 267, 260, 265, 268, 263, 262, 268, 262, 267, 269, 264, 263, 269, 263, 268,
+ 270, 265, 266, 270, 266, 271, 272, 267, 265, 272, 265, 270, 273, 268, 267, 273, 267, 272, 274, 269,
+ 268, 274, 268, 273, 275, 276, 277, 275, 277, 278, 279, 280, 276, 279, 276, 275, 281, 282, 280, 281,
+ 280, 279, 283, 284, 282, 283, 282, 281, 285, 275, 278, 285, 278, 286, 287, 279, 275, 287, 275, 285,
+ 288, 281, 279, 288, 279, 287, 289, 283, 281, 289, 281, 288, 290, 285, 286, 290, 286, 291, 292, 287,
+ 285, 292, 285, 290, 293, 288, 287, 293, 287, 292, 294, 289, 288, 294, 288, 293, 295, 290, 291, 295,
+ 291, 296, 297, 292, 290, 297, 290, 295, 298, 293, 292, 298, 292, 297, 299, 294, 293, 299, 293, 298,
+ 300, 301, 302, 300, 302, 303, 304, 305, 301, 304, 301, 300, 306, 307, 305, 306, 305, 304, 308, 309,
+ 307, 308, 307, 306, 310, 300, 303, 310, 303, 311, 312, 304, 300, 312, 300, 310, 313, 306, 304, 313,
+ 304, 312, 314, 310, 311, 314, 311, 315, 316, 312, 310, 316, 310, 314, 317, 313, 312, 317, 312, 316,
+ 318, 314, 315, 318, 315, 319, 320, 316, 314, 320, 314, 318, 321, 317, 316, 321, 316, 320, 322, 323,
+ 324, 322, 324, 325, 326, 327, 323, 326, 323, 322, 328, 329, 327, 328, 327, 326, 330, 322, 325, 330,
+ 325, 331, 332, 326, 322, 332, 322, 330, 333, 328, 326, 333, 326, 332, 334, 330, 331, 334, 331, 335,
+ 336, 332, 330, 336, 330, 334, 337, 333, 332, 337, 332, 336, 338, 334, 335, 338, 335, 339, 340, 336,
+ 334, 340, 334, 338, 341, 337, 336, 341, 336, 340, 342, 343, 344, 342, 344, 345, 346, 347, 343, 346,
+ 343, 342, 348, 349, 347, 348, 347, 346, 350, 342, 345, 350, 345, 351, 352, 346, 342, 352, 342, 350,
+ 353, 348, 346, 353, 346, 352, 354, 350, 351, 354, 351, 355, 356, 352, 350, 356, 350, 354, 357, 353,
+ 352, 357, 352, 356, 358, 354, 355, 358, 355, 359, 360, 356, 354, 360, 354, 358, 361, 357, 356, 361,
+ 356, 360, 362, 363, 364, 362, 364, 365, 366, 367, 363, 366, 363, 362, 368, 369, 367, 368, 367, 366,
+ 370, 362, 365, 370, 365, 371, 372, 366, 362, 372, 362, 370, 373, 368, 366, 373, 366, 372, 374, 370,
+ 371, 374, 371, 375, 376, 372, 370, 376, 370, 374, 309, 373, 372, 309, 372, 376, 377, 374, 375, 377,
+ 375, 378, 379, 376, 374, 379, 374, 377, 380, 309, 376, 380, 376, 379, 381, 382, 383, 381, 383, 384,
+ 385, 386, 382, 385, 382, 381, 387, 388, 386, 387, 386, 385, 389, 390, 388, 389, 388, 387, 391, 381,
+ 384, 391, 384, 392, 393, 385, 381, 393, 381, 391, 394, 387, 385, 394, 385, 393, 395, 389, 387, 395,
+ 387, 394, 396, 391, 392, 396, 392, 397, 398, 393, 391, 398, 391, 396, 399, 394, 393, 399, 393, 398,
+ 400, 395, 394, 400, 394, 399, 401, 396, 397, 401, 397, 402, 403, 398, 396, 403, 396, 401, 404, 399,
+ 398, 404, 398, 403, 405, 400, 399, 405, 399, 404, 406, 407, 408, 406, 408, 409, 410, 411, 407, 410,
+ 407, 406, 412, 413, 411, 412, 411, 410, 414, 415, 413, 414, 413, 412, 416, 406, 409, 416, 409, 417,
+ 418, 410, 406, 418, 406, 416, 419, 412, 410, 419, 410, 418, 420, 414, 412, 420, 412, 419, 421, 416,
+ 417, 421, 417, 422, 423, 418, 416, 423, 416, 421, 424, 419, 418, 424, 418, 423, 425, 420, 419, 425,
+ 419, 424, 426, 421, 422, 426, 422, 427, 428, 423, 421, 428, 421, 426, 429, 424, 423, 429, 423, 428,
+ 430, 425, 424, 430, 424, 429, 431, 432, 433, 431, 433, 434, 435, 436, 432, 435, 432, 431, 437, 438,
+ 436, 437, 436, 435, 439, 440, 438, 439, 438, 437, 441, 431, 434, 441, 434, 442, 443, 435, 431, 443,
+ 431, 441, 444, 437, 435, 444, 435, 443, 445, 439, 437, 445, 437, 444, 446, 441, 442, 446, 442, 447,
+ 448, 443, 441, 448, 441, 446, 449, 444, 443, 449, 443, 448, 450, 445, 444, 450, 444, 449, 451, 446,
+ 447, 451, 447, 452, 453, 448, 446, 453, 446, 451, 454, 449, 448, 454, 448, 453, 455, 450, 449, 455,
+ 449, 454, 456, 457, 458, 456, 458, 459, 460, 461, 457, 460, 457, 456, 462, 463, 461, 462, 461, 460,
+ 464, 465, 463, 464, 463, 462, 466, 456, 459, 466, 459, 467, 468, 460, 456, 468, 456, 466, 469, 462,
+ 460, 469, 460, 468, 470, 464, 462, 470, 462, 469, 471, 466, 467, 471, 467, 472, 473, 468, 466, 473,
+ 466, 471, 474, 469, 468, 474, 468, 473, 475, 470, 469, 475, 469, 474, 476, 471, 472, 476, 472, 477,
+ 478, 473, 471, 478, 471, 476, 479, 474, 473, 479, 473, 478, 480, 475, 474, 480, 474, 479, 481, 482,
+ 483, 481, 483, 484, 485, 486, 482, 485, 482, 481, 487, 488, 486, 487, 486, 485, 489, 490, 488, 489,
+ 488, 487, 491, 481, 484, 491, 484, 492, 493, 485, 481, 493, 481, 491, 494, 487, 485, 494, 485, 493,
+ 495, 489, 487, 495, 487, 494, 496, 491, 492, 496, 492, 497, 498, 493, 491, 498, 491, 496, 499, 494,
+ 493, 499, 493, 498, 500, 495, 494, 500, 494, 499, 501, 496, 497, 501, 497, 502, 503, 498, 496, 503,
+ 496, 501, 504, 499, 498, 504, 498, 503, 505, 500, 499, 505, 499, 504, 506, 507, 508, 506, 508, 509,
+ 510, 511, 507, 510, 507, 506, 512, 513, 511, 512, 511, 510, 514, 515, 513, 514, 513, 512, 516, 506,
+ 509, 516, 509, 517, 518, 510, 506, 518, 506, 516, 519, 512, 510, 519, 510, 518, 520, 514, 512, 520,
+ 512, 519, 521, 516, 517, 521, 517, 522, 523, 518, 516, 523, 516, 521, 524, 519, 518, 524, 518, 523,
+ 525, 520, 519, 525, 519, 524, 526, 521, 522, 526, 522, 527, 528, 523, 521, 528, 521, 526, 529, 524,
+ 523, 529, 523, 528, 530, 525, 524, 530, 524, 529, 531, 532, 533, 531, 533, 534, 535, 536, 532, 535,
+ 532, 531, 537, 538, 536, 537, 536, 535, 539, 540, 538, 539, 538, 537, 541, 531, 534, 541, 534, 542,
+ 543, 535, 531, 543, 531, 541, 544, 537, 535, 544, 535, 543, 545, 539, 537, 545, 537, 544, 546, 541,
+ 542, 546, 542, 547, 548, 543, 541, 548, 541, 546, 549, 544, 543, 549, 543, 548, 550, 545, 544, 550,
+ 544, 549, 551, 546, 547, 551, 547, 552, 553, 548, 546, 553, 546, 551, 554, 549, 548, 554, 548, 553,
+ 555, 550, 549, 555, 549, 554, 556, 557, 558, 556, 558, 559, 560, 561, 557, 560, 557, 556, 562, 563,
+ 561, 562, 561, 560, 564, 565, 563, 564, 563, 562, 566, 556, 559, 566, 559, 567, 568, 560, 556, 568,
+ 556, 566, 569, 562, 560, 569, 560, 568, 570, 564, 562, 570, 562, 569, 571, 566, 567, 571, 567, 572,
+ 573, 568, 566, 573, 566, 571, 574, 569, 568, 574, 568, 573, 575, 570, 569, 575, 569, 574, 576, 571,
+ 572, 576, 572, 577, 578, 573, 571, 578, 571, 576, 579, 574, 573, 579, 573, 578, 580, 575, 574, 580,
+ 574, 579, 581, 582, 583, 581, 583, 584, 585, 586, 584, 585, 584, 583, 587, 588, 586, 587, 586, 585,
+ 589, 590, 588, 589, 588, 587, 591, 585, 583, 591, 583, 582, 592, 587, 585, 592, 585, 591, 593, 589,
+ 587, 593, 587, 592, 594, 595, 596, 594, 596, 582, 597, 591, 582, 597, 582, 596, 598, 592, 591, 598,
+ 591, 597, 599, 593, 592, 599, 592, 598, 600, 597, 596, 600, 596, 595, 601, 598, 597, 601, 597, 600,
+ 602, 599, 598, 602, 598, 601, 603, 604, 605, 603, 605, 606, 607, 608, 606, 607, 606, 605, 609, 610,
+ 608, 609, 608, 607, 611, 612, 610, 611, 610, 609, 613, 607, 605, 613, 605, 604, 614, 609, 607, 614,
+ 607, 613, 615, 611, 609, 615, 609, 614, 616, 617, 618, 616, 618, 604, 619, 613, 604, 619, 604, 618,
+ 620, 614, 613, 620, 613, 619, 621, 615, 614, 621, 614, 620, 622, 619, 618, 622, 618, 617, 623, 620,
+ 619, 623, 619, 622, 624, 621, 620, 624, 620, 623, 625, 626, 627, 625, 627, 628, 629, 630, 628, 629,
+ 628, 627, 631, 632, 630, 631, 630, 629, 633, 634, 632, 633, 632, 631, 635, 629, 627, 635, 627, 626,
+ 636, 631, 629, 636, 629, 635, 637, 633, 631, 637, 631, 636, 638, 639, 640, 638, 640, 626, 641, 635,
+ 626, 641, 626, 640, 642, 636, 635, 642, 635, 641, 643, 637, 636, 643, 636, 642, 644, 641, 640, 644,
+ 640, 639, 645, 642, 641, 645, 641, 644, 646, 643, 642, 646, 642, 645, 647, 648, 649, 647, 649, 650,
+ 651, 652, 650, 651, 650, 649, 653, 654, 652, 653, 652, 651, 655, 656, 654, 655, 654, 653, 657, 651,
+ 649, 657, 649, 648, 658, 653, 651, 658, 651, 657, 659, 655, 653, 659, 653, 658, 660, 661, 662, 660,
+ 662, 648, 663, 657, 648, 663, 648, 662, 664, 658, 657, 664, 657, 663, 665, 659, 658, 665, 658, 664,
+ 666, 663, 662, 666, 662, 661, 667, 664, 663, 667, 663, 666, 668, 665, 664, 668, 664, 667, 669, 670,
+ 671, 669, 671, 672, 673, 674, 670, 673, 670, 669, 675, 676, 674, 675, 674, 673, 677, 678, 676, 677,
+ 676, 675, 679, 669, 672, 679, 672, 680, 681, 673, 669, 681, 669, 679, 682, 675, 673, 682, 673, 681,
+ 683, 677, 675, 683, 675, 682, 684, 679, 680, 684, 680, 685, 686, 681, 679, 686, 679, 684, 687, 682,
+ 681, 687, 681, 686, 688, 683, 682, 688, 682, 687, 689, 684, 685, 689, 685, 690, 691, 686, 684, 691,
+ 684, 689, 692, 687, 686, 692, 686, 691, 693, 688, 687, 693, 687, 692, 694, 695, 696, 694, 696, 697,
+ 698, 699, 695, 698, 695, 694, 700, 701, 699, 700, 699, 698, 702, 703, 701, 702, 701, 700, 704, 694,
+ 697, 704, 697, 705, 706, 698, 694, 706, 694, 704, 707, 700, 698, 707, 698, 706, 708, 702, 700, 708,
+ 700, 707, 709, 704, 705, 709, 705, 710, 711, 706, 704, 711, 704, 709, 712, 707, 706, 712, 706, 711,
+ 713, 708, 707, 713, 707, 712, 714, 709, 710, 714, 710, 715, 716, 711, 709, 716, 709, 714, 717, 712,
+ 711, 717, 711, 716, 718, 713, 712, 718, 712, 717, 719, 720, 721, 719, 721, 722, 723, 724, 720, 723,
+ 720, 719, 725, 726, 724, 725, 724, 723, 727, 728, 726, 727, 726, 725, 729, 719, 722, 729, 722, 730,
+ 731, 723, 719, 731, 719, 729, 732, 725, 723, 732, 723, 731, 733, 727, 725, 733, 725, 732, 734, 729,
+ 730, 734, 730, 735, 736, 731, 729, 736, 729, 734, 737, 732, 731, 737, 731, 736, 738, 733, 732, 738,
+ 732, 737, 739, 734, 735, 739, 735, 740, 741, 736, 734, 741, 734, 739, 742, 737, 736, 742, 736, 741,
+ 743, 738, 737, 743, 737, 742, 744, 745, 746, 744, 746, 747, 748, 749, 745, 748, 745, 744, 750, 751,
+ 749, 750, 749, 748, 752, 753, 751, 752, 751, 750, 754, 744, 747, 754, 747, 755, 756, 748, 744, 756,
+ 744, 754, 757, 750, 748, 757, 748, 756, 758, 752, 750, 758, 750, 757, 759, 754, 755, 759, 755, 760,
+ 761, 756, 754, 761, 754, 759, 762, 757, 756, 762, 756, 761, 763, 758, 757, 763, 757, 762, 764, 759,
+ 760, 764, 760, 765, 766, 761, 759, 766, 759, 764, 767, 762, 761, 767, 761, 766, 768, 763, 762, 768,
+ 762, 767, 306, 313, 769, 313, 317, 770, 317, 321, 771, 329, 328, 772, 328, 333, 773, 333, 337, 774,
+ 337, 341, 775, 349, 348, 776, 348, 353, 777, 353, 357, 778, 357, 361, 779, 369, 368, 780, 368, 373,
+ 781, 373, 309, 782 };
+ _meshData.setIndexBuffer(BufferUtils.createShortBuffer(indices));
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java
new file mode 100644
index 0000000..6f36c28
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Torus.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class Torus extends Mesh {
+
+ protected int _circleSamples;
+
+ protected int _radialSamples;
+
+ protected double _tubeRadius;
+
+ protected double _centerRadius;
+
+ protected boolean _viewInside;
+
+ /**
+ * private constructor for Savable use only.
+ */
+ public Torus() {
+
+ }
+
+ /**
+ * Constructs a new Torus. Center is the origin, but the Torus may be transformed.
+ *
+ * @param name
+ * The name of the Torus.
+ * @param circleSamples
+ * The number of samples along the circles.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param tubeRadius
+ * the radius of the torus tube.
+ * @param centerRadius
+ * The distance from the center of the torus hole to the center of the torus tube.
+ */
+ public Torus(final String name, final int circleSamples, final int radialSamples, final double tubeRadius,
+ final double centerRadius) {
+
+ super(name);
+ _circleSamples = circleSamples;
+ _radialSamples = radialSamples;
+ _tubeRadius = tubeRadius;
+ _centerRadius = centerRadius;
+
+ setGeometryData();
+ setIndexData();
+
+ }
+
+ private void setGeometryData() {
+ // allocate vertices
+ final int verts = ((_circleSamples + 1) * (_radialSamples + 1));
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate normals if requested
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate texture coordinates
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(verts), 0);
+
+ // generate geometry
+ final double inverseCircleSamples = 1.0 / _circleSamples;
+ final double inverseRadialSamples = 1.0 / _radialSamples;
+ int i = 0;
+ // generate the cylinder itself
+ final Vector3 radialAxis = new Vector3(), torusMiddle = new Vector3(), tempNormal = new Vector3();
+ for (int circleCount = 0; circleCount < _circleSamples; circleCount++) {
+ // compute center point on torus circle at specified angle
+ final double circleFraction = circleCount * inverseCircleSamples;
+ final double theta = MathUtils.TWO_PI * circleFraction;
+ final double cosTheta = MathUtils.cos(theta);
+ final double sinTheta = MathUtils.sin(theta);
+ radialAxis.set(cosTheta, sinTheta, 0);
+ radialAxis.multiply(_centerRadius, torusMiddle);
+
+ // compute slice vertices with duplication at end point
+ final int iSave = i;
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final double radialFraction = radialCount * inverseRadialSamples;
+ // in [0,1)
+ final double phi = MathUtils.TWO_PI * radialFraction;
+ final double cosPhi = MathUtils.cos(phi);
+ final double sinPhi = MathUtils.sin(phi);
+ tempNormal.set(radialAxis).multiplyLocal(cosPhi);
+ tempNormal.setZ(tempNormal.getZ() + sinPhi);
+ tempNormal.normalizeLocal();
+ if (!_viewInside) {
+ _meshData.getNormalBuffer().put((float) tempNormal.getX()).put((float) tempNormal.getY())
+ .put((float) tempNormal.getZ());
+ } else {
+ _meshData.getNormalBuffer().put((float) -tempNormal.getX()).put((float) -tempNormal.getY())
+ .put((float) -tempNormal.getZ());
+ }
+
+ tempNormal.multiplyLocal(_tubeRadius).addLocal(torusMiddle);
+ _meshData.getVertexBuffer().put((float) tempNormal.getX()).put((float) tempNormal.getY())
+ .put((float) tempNormal.getZ());
+
+ _meshData.getTextureCoords(0).getBuffer().put((float) radialFraction).put((float) circleFraction);
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i);
+ BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iSave, i);
+
+ _meshData.getTextureCoords(0).getBuffer().put(1.0f).put((float) circleFraction);
+
+ i++;
+ }
+
+ // duplicate the cylinder ends to form a torus
+ for (int iR = 0; iR <= _radialSamples; iR++, i++) {
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iR, i);
+ BufferUtils.copyInternalVector3(_meshData.getNormalBuffer(), iR, i);
+ BufferUtils.copyInternalVector2(_meshData.getTextureCoords(0).getBuffer(), iR, i);
+ _meshData.getTextureCoords(0).getBuffer().put(i * 2 + 1, 1.0f);
+ }
+ }
+
+ private void setIndexData() {
+ // allocate connectivity
+ final int verts = ((_circleSamples + 1) * (_radialSamples + 1));
+ final int tris = (2 * _circleSamples * _radialSamples);
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+ int i;
+ // generate connectivity
+ int connectionStart = 0;
+ for (int circleCount = 0; circleCount < _circleSamples; circleCount++) {
+ int i0 = connectionStart;
+ int i1 = i0 + 1;
+ connectionStart += _radialSamples + 1;
+ int i2 = connectionStart;
+ int i3 = i2 + 1;
+ for (i = 0; i < _radialSamples; i++) {
+ if (!_viewInside) {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i2++);
+ _meshData.getIndices().put(i3++);
+ } else {
+ _meshData.getIndices().put(i0++);
+ _meshData.getIndices().put(i1);
+ _meshData.getIndices().put(i2);
+ _meshData.getIndices().put(i1++);
+ _meshData.getIndices().put(i3++);
+ _meshData.getIndices().put(i2++);
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * @return true if the normals are inverted to point into the torus so that the face is oriented for a viewer inside
+ * the torus. false (the default) for exterior viewing.
+ */
+ public boolean isViewFromInside() {
+ return _viewInside;
+ }
+
+ /**
+ *
+ * @param viewInside
+ * if true, the normals are inverted to point into the torus so that the face is oriented for a viewer
+ * inside the torus. Default is false (for outside viewing)
+ */
+ public void setViewFromInside(final boolean viewInside) {
+ if (viewInside != _viewInside) {
+ _viewInside = viewInside;
+ setGeometryData();
+ setIndexData();
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_circleSamples, "circleSamples", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_tubeRadius, "tubeRadius", 0);
+ capsule.write(_centerRadius, "centerRadius", 0);
+ capsule.write(_viewInside, "viewInside", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _circleSamples = capsule.readInt("circleSamples", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _tubeRadius = capsule.readDouble("tubeRadius", 0);
+ _centerRadius = capsule.readDouble("centerRadius", 0);
+ _viewInside = capsule.readBoolean("viewInside", false);
+ }
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java
new file mode 100644
index 0000000..5b93988
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/shape/Tube.java
@@ -0,0 +1,325 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.shape;
+
+import java.io.IOException;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class Tube extends Mesh {
+
+ private static final long serialVersionUID = 1L;
+
+ public static long getSerialVersionUID() {
+ return serialVersionUID;
+ }
+
+ private int _axisSamples;
+ private int _radialSamples;
+
+ private double _outerRadius;
+ private double _innerRadius;
+ private double _height;
+
+ protected boolean _viewInside;
+
+ /**
+ * Constructor meant for Savable use only.
+ */
+ public Tube() {}
+
+ public Tube(final String name, final double outerRadius, final double innerRadius, final double height,
+ final int axisSamples, final int radialSamples) {
+ super(name);
+ _outerRadius = outerRadius;
+ _innerRadius = innerRadius;
+ _height = height;
+ _axisSamples = axisSamples;
+ _radialSamples = radialSamples;
+ allocateVertices();
+ }
+
+ public Tube(final String name, final double outerRadius, final double innerRadius, final double height) {
+ this(name, outerRadius, innerRadius, height, 2, 20);
+ }
+
+ private void allocateVertices() {
+ final int verts = (2 * (_axisSamples + 1) * (_radialSamples + 1) + _radialSamples * 4);
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(_meshData.getVertexBuffer(), verts));
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(_meshData.getNormalBuffer(), verts));
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(_meshData.getTextureBuffer(0), verts), 0);
+
+ final int tris = (4 * _radialSamples * (1 + _axisSamples));
+ if (_meshData.getIndices() == null || _meshData.getIndices().getBufferLimit() != 3 * tris) {
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+ }
+
+ setGeometryData();
+ setIndexData();
+ }
+
+ public int getAxisSamples() {
+ return _axisSamples;
+ }
+
+ public void setAxisSamples(final int axisSamples) {
+ _axisSamples = axisSamples;
+ allocateVertices();
+ }
+
+ public int getRadialSamples() {
+ return _radialSamples;
+ }
+
+ public void setRadialSamples(final int radialSamples) {
+ _radialSamples = radialSamples;
+ allocateVertices();
+ }
+
+ public double getOuterRadius() {
+ return _outerRadius;
+ }
+
+ public void setOuterRadius(final double outerRadius) {
+ _outerRadius = outerRadius;
+ allocateVertices();
+ }
+
+ public double getInnerRadius() {
+ return _innerRadius;
+ }
+
+ public void setInnerRadius(final double innerRadius) {
+ _innerRadius = innerRadius;
+ allocateVertices();
+ }
+
+ public double getHeight() {
+ return _height;
+ }
+
+ public void setHeight(final double height) {
+ _height = height;
+ allocateVertices();
+ }
+
+ private void setGeometryData() {
+ _meshData.getVertexBuffer().rewind();
+ _meshData.getNormalBuffer().rewind();
+ _meshData.getTextureCoords(0).getBuffer().rewind();
+
+ final double inverseRadial = 1.0 / _radialSamples;
+ final double axisStep = _height / _axisSamples;
+ final double axisTextureStep = 1.0 / _axisSamples;
+ final double halfHeight = 0.5 * _height;
+ final double innerOuterRatio = _innerRadius / _outerRadius;
+ final double[] sin = new double[_radialSamples];
+ final double[] cos = new double[_radialSamples];
+
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final double angle = MathUtils.TWO_PI * inverseRadial * radialCount;
+ cos[radialCount] = MathUtils.cos(angle);
+ sin[radialCount] = MathUtils.sin(angle);
+ }
+
+ // outer cylinder
+ for (int radialCount = 0; radialCount < _radialSamples + 1; radialCount++) {
+ for (int axisCount = 0; axisCount < _axisSamples + 1; axisCount++) {
+ _meshData.getVertexBuffer().put((float) (cos[radialCount % _radialSamples] * _outerRadius)).put(
+ (float) (axisStep * axisCount - halfHeight)).put(
+ (float) (sin[radialCount % _radialSamples] * _outerRadius));
+ if (_viewInside) {
+ _meshData.getNormalBuffer().put((float) cos[radialCount % _radialSamples]).put(0).put(
+ (float) sin[radialCount % _radialSamples]);
+ } else {
+ _meshData.getNormalBuffer().put((float) -cos[radialCount % _radialSamples]).put(0).put(
+ (float) -sin[radialCount % _radialSamples]);
+ }
+ _meshData.getTextureCoords(0).getBuffer().put((float) (radialCount * inverseRadial)).put(
+ (float) (axisTextureStep * axisCount));
+ }
+ }
+ // inner cylinder
+ for (int radialCount = 0; radialCount < _radialSamples + 1; radialCount++) {
+ for (int axisCount = 0; axisCount < _axisSamples + 1; axisCount++) {
+ _meshData.getVertexBuffer().put((float) (cos[radialCount % _radialSamples] * _innerRadius)).put(
+ (float) (axisStep * axisCount - halfHeight)).put(
+ (float) (sin[radialCount % _radialSamples] * _innerRadius));
+ if (_viewInside) {
+ _meshData.getNormalBuffer().put((float) -cos[radialCount % _radialSamples]).put(0).put(
+ (float) -sin[radialCount % _radialSamples]);
+ } else {
+ _meshData.getNormalBuffer().put((float) cos[radialCount % _radialSamples]).put(0).put(
+ (float) sin[radialCount % _radialSamples]);
+ }
+ _meshData.getTextureCoords(0).getBuffer().put((float) (radialCount * inverseRadial)).put(
+ (float) (axisTextureStep * axisCount));
+ }
+ }
+ // bottom edge
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ _meshData.getVertexBuffer().put((float) (cos[radialCount] * _outerRadius)).put((float) -halfHeight).put(
+ (float) (sin[radialCount] * _outerRadius));
+ _meshData.getVertexBuffer().put((float) (cos[radialCount] * _innerRadius)).put((float) -halfHeight).put(
+ (float) (sin[radialCount] * _innerRadius));
+ if (_viewInside) {
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ } else {
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ }
+ _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + 0.5 * cos[radialCount])).put(
+ (float) (0.5 + 0.5 * sin[radialCount]));
+ _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + innerOuterRatio * 0.5 * cos[radialCount]))
+ .put((float) (0.5 + innerOuterRatio * 0.5 * sin[radialCount]));
+ }
+ // top edge
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ _meshData.getVertexBuffer().put((float) (cos[radialCount] * _outerRadius)).put((float) halfHeight).put(
+ (float) (sin[radialCount] * _outerRadius));
+ _meshData.getVertexBuffer().put((float) (cos[radialCount] * _innerRadius)).put((float) halfHeight).put(
+ (float) (sin[radialCount] * _innerRadius));
+ if (_viewInside) {
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ } else {
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ }
+ _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + 0.5 * cos[radialCount])).put(
+ (float) (0.5 + 0.5 * sin[radialCount]));
+ _meshData.getTextureCoords(0).getBuffer().put((float) (0.5 + innerOuterRatio * 0.5 * cos[radialCount]))
+ .put((float) (0.5 + innerOuterRatio * 0.5 * sin[radialCount]));
+ }
+
+ }
+
+ private void setIndexData() {
+ _meshData.getIndexBuffer().rewind();
+
+ final int outerCylinder = (_axisSamples + 1) * (_radialSamples + 1);
+ final int bottomEdge = 2 * outerCylinder;
+ final int topEdge = bottomEdge + 2 * _radialSamples;
+ // inner cylinder
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ for (int axisCount = 0; axisCount < _axisSamples; axisCount++) {
+ final int index0 = axisCount + (_axisSamples + 1) * radialCount;
+ final int index1 = index0 + 1;
+ final int index2 = index0 + (_axisSamples + 1);
+ final int index3 = index2 + 1;
+ if (_viewInside) {
+ _meshData.getIndices().put(index0).put(index1).put(index2);
+ _meshData.getIndices().put(index1).put(index3).put(index2);
+ } else {
+ _meshData.getIndices().put(index0).put(index2).put(index1);
+ _meshData.getIndices().put(index1).put(index2).put(index3);
+ }
+ }
+ }
+
+ // outer cylinder
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ for (int axisCount = 0; axisCount < _axisSamples; axisCount++) {
+ final int index0 = outerCylinder + axisCount + (_axisSamples + 1) * radialCount;
+ final int index1 = index0 + 1;
+ final int index2 = index0 + (_axisSamples + 1);
+ final int index3 = index2 + 1;
+ if (_viewInside) {
+ _meshData.getIndices().put(index0).put(index2).put(index1);
+ _meshData.getIndices().put(index1).put(index2).put(index3);
+ } else {
+ _meshData.getIndices().put(index0).put(index1).put(index2);
+ _meshData.getIndices().put(index1).put(index3).put(index2);
+ }
+ }
+ }
+
+ // bottom edge
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final int index0 = bottomEdge + 2 * radialCount;
+ final int index1 = index0 + 1;
+ final int index2 = bottomEdge + 2 * ((radialCount + 1) % _radialSamples);
+ final int index3 = index2 + 1;
+ if (_viewInside) {
+ _meshData.getIndices().put(index0).put(index2).put(index1);
+ _meshData.getIndices().put(index1).put(index2).put(index3);
+ } else {
+ _meshData.getIndices().put(index0).put(index1).put(index2);
+ _meshData.getIndices().put(index1).put(index3).put(index2);
+ }
+ }
+
+ // top edge
+ for (int radialCount = 0; radialCount < _radialSamples; radialCount++) {
+ final int index0 = topEdge + 2 * radialCount;
+ final int index1 = index0 + 1;
+ final int index2 = topEdge + 2 * ((radialCount + 1) % _radialSamples);
+ final int index3 = index2 + 1;
+ if (_viewInside) {
+ _meshData.getIndices().put(index0).put(index1).put(index2);
+ _meshData.getIndices().put(index1).put(index3).put(index2);
+ } else {
+ _meshData.getIndices().put(index0).put(index2).put(index1);
+ _meshData.getIndices().put(index1).put(index2).put(index3);
+ }
+ }
+ }
+
+ /**
+ *
+ * @return true if the normals are inverted to point into the torus so that the face is oriented for a viewer inside
+ * the torus. false (the default) for exterior viewing.
+ */
+ public boolean isViewFromInside() {
+ return _viewInside;
+ }
+
+ /**
+ *
+ * @param viewInside
+ * if true, the normals are inverted to point into the torus so that the face is oriented for a viewer
+ * inside the torus. Default is false (for outside viewing)
+ */
+ public void setViewFromInside(final boolean viewInside) {
+ if (viewInside != _viewInside) {
+ _viewInside = viewInside;
+ setGeometryData();
+ setIndexData();
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(getAxisSamples(), "axisSamples", 0);
+ capsule.write(getRadialSamples(), "radialSamples", 0);
+ capsule.write(getOuterRadius(), "outerRadius", 0);
+ capsule.write(getInnerRadius(), "innerRadius", 0);
+ capsule.write(getHeight(), "height", 0);
+ capsule.write(_viewInside, "viewInside", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ setAxisSamples(capsule.readInt("axisSamples", 0));
+ setRadialSamples(capsule.readInt("radialSamples", 0));
+ setOuterRadius(capsule.readDouble("outerRadius", 0));
+ setInnerRadius(capsule.readDouble("innerRadius", 0));
+ setHeight(capsule.readDouble("height", 0));
+ _viewInside = capsule.readBoolean("viewInside", false);
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java
new file mode 100644
index 0000000..6d8ce8d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/DeleteVBOsVisitor.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public class DeleteVBOsVisitor implements Visitor {
+ final Renderer _deleter;
+
+ public DeleteVBOsVisitor(final Renderer deleter) {
+ _deleter = deleter;
+ }
+
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+ _deleter.deleteVBOs(mesh.getMeshData().getVertexCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getIndices());
+ _deleter.deleteVBOs(mesh.getMeshData().getInterleavedData());
+ _deleter.deleteVBOs(mesh.getMeshData().getNormalCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getTangentCoords());
+ for (final FloatBufferData coords : mesh.getMeshData().getTextureCoords()) {
+ _deleter.deleteVBOs(coords);
+ }
+ _deleter.deleteVBOs(mesh.getMeshData().getColorCoords());
+ _deleter.deleteVBOs(mesh.getMeshData().getFogCoords());
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java
new file mode 100644
index 0000000..9e809df
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/SetModelBoundVisitor.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public class SetModelBoundVisitor implements Visitor {
+ private final BoundingVolume _bound;
+
+ public SetModelBoundVisitor(final BoundingVolume bound) {
+ _bound = bound;
+ }
+
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ ((Mesh) spatial).setModelBound(_bound.clone(null));
+ }
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java
new file mode 100644
index 0000000..db2e891
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/UpdateModelBoundVisitor.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+
+public class UpdateModelBoundVisitor implements Visitor {
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ ((Mesh) spatial).updateModelBound();
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java
new file mode 100644
index 0000000..f90f8c7
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/scenegraph/visitor/Visitor.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.scenegraph.visitor;
+
+import com.ardor3d.scenegraph.Spatial;
+
+public interface Visitor {
+
+ /**
+ * Execute our logic on the given Spatial
+ *
+ * @param spatial
+ */
+ void visit(Spatial spatial);
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java
new file mode 100644
index 0000000..df2fc6c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/ArcLengthTable.java
@@ -0,0 +1,270 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.spline;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.controller.ComplexSpatialController;
+import com.ardor3d.scenegraph.controller.interpolation.InterpolationController;
+
+/**
+ * ArcLengthTable class contains methods for generating and storing arc lengths of a curve. Arc Lengths are used to get
+ * constant speed interpolation over a curve.
+ * <p>
+ * This class does not automatically generate the look up tables, you must manually call {@link #generate(int, boolean)}
+ * to generate the table.
+ * </p>
+ */
+public class ArcLengthTable {
+
+ /** Classes logger */
+ private static final Logger LOGGER = Logger.getLogger(ArcLengthTable.class.getName());
+
+ /** Table containing arc lengths for look up */
+ private Map<Integer, List<ArcLengthEntry>> _lookupTable;
+
+ /** The curve who's values to cache */
+ private final Curve _curve;
+
+ /**
+ * Creates a new instance of <code>ArcLengthTable</code>.
+ *
+ * @param curve
+ * The curve to create the table for, can not be <code>null</code>.
+ */
+ public ArcLengthTable(final Curve curve) {
+ super();
+
+ if (null == curve) {
+ throw new IllegalArgumentException("curve was null!");
+ }
+
+ _curve = curve;
+ }
+
+ /**
+ * @param index
+ * The index of the control point you want the length for.
+ * @return The total approximate length of the segment starting at the given index.
+ */
+ public double getLength(final int index) {
+ if (null == _lookupTable) {
+ throw new IllegalStateException(
+ "You must generate the look up table before calling this method! see generate()");
+ }
+
+ final List<ArcLengthEntry> entries = _lookupTable.get(index);
+
+ if (null == entries) {
+ throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index);
+ }
+
+ final ArcLengthEntry arcLength = entries.get(entries.size() - 1);
+
+ return arcLength.getLength();
+ }
+
+ /**
+ * @param index
+ * The index of the first control point you are interpolating from.
+ * @param distance
+ * The distance you want the spatial to travel.
+ * @return The delta you should use to travel the specified distance from the control point given, will not be
+ * negative but may be greater than 1.0 if the distance is greater than the length of the segment.
+ */
+ public double getDelta(final int index, final double distance) {
+ if (null == _lookupTable) {
+ throw new IllegalStateException(
+ "You must generate the look up table before calling this method! see generate()");
+ }
+
+ ArcLengthEntry previous = null;
+ ArcLengthEntry next = null;
+
+ final List<ArcLengthEntry> entries = _lookupTable.get(index);
+
+ if (null == entries) {
+ throw new IllegalArgumentException("entries was null, the index parameter was invalid. index=" + index);
+ }
+
+ for (final ArcLengthEntry entry : entries) {
+ if (entry.getLength() <= distance) {
+ previous = entry;
+ }
+ if (entry.getLength() >= distance) {
+ next = entry;
+ break;
+ }
+ }
+
+ if (null == previous) {
+ throw new IllegalArgumentException(
+ "previous was null, either the index or distance parameters were invalid. index=" + index
+ + ", distance=" + distance);
+ }
+
+ final double delta;
+
+ /*
+ * If next is null then the length we need to travel is longer than the length of the segment, in this case we
+ * work out the delta required to travel from the start of the segment minus what we've already travelled. We
+ * then add that delta to the delta required to traverse the next segment and return that value, it's up to the
+ * controller to handle this value correctly (update indices etc)
+ *
+ * We need to be careful about wrapping around the end of the curve.
+ */
+ if (null == next) {
+ final int newIndex = (index + 1 >= _lookupTable.size()) ? 1 : index + 1;
+
+ delta = getDelta(newIndex, distance - previous.getLength()) + previous.getDelta();
+
+ } else {
+ if (previous.equals(next)) {
+ delta = previous.getDelta();
+
+ } else {
+ final double d0 = previous.getDelta();
+ final double d1 = next.getDelta();
+ final double l0 = previous.getLength();
+ final double l1 = next.getLength();
+
+ delta = (d0 + ((distance - l0) / (l1 - l0)) * (d1 - d0));
+ }
+ }
+
+ return delta;
+ }
+
+ /**
+ * Actually generates the arc length table, this needs to be called before this class can actually perform any
+ * useful functions.
+ *
+ * @param step
+ * The larger the step value used the more accurate the resulting table will be and thus the smoother the
+ * motion will be, must be greater than zero.
+ * @param reverse
+ * <code>true</code> to generate the table while stepping from the end of the curve to the beginning,
+ * <code>false</code> to generate the table from the beginning of the curve. You only need to generate a
+ * reverse table if you are using the {@link ComplexSpatialController.RepeatType#CYCLE cycle} repeat
+ * type.
+ */
+ public void generate(final int step, final boolean reverse) {
+ if (step <= 0) {
+ throw new IllegalArgumentException("step must be > 0! step=" + step);
+ }
+
+ _lookupTable = new HashMap<Integer, List<ArcLengthEntry>>();
+
+ final Vector3 target = Vector3.fetchTempInstance();
+ final Vector3 previous = Vector3.fetchTempInstance();
+
+ final int loopStart = reverse ? (_curve.getControlPointCount() - 2) : 1;
+ final double tStep = InterpolationController.DELTA_MAX / step;
+
+ for (int i = loopStart; continueLoop(i, reverse); i = updateCounter(i, reverse)) {
+ final int startIndex = i;
+ double t = 0f;
+ double length = 0;
+
+ previous.set(_curve.getControlPoints().get(i));
+
+ final ArrayList<ArcLengthEntry> entries = new ArrayList<ArcLengthEntry>();
+ entries.add(new ArcLengthEntry(0f, 0));
+
+ final int endIndex = reverse ? startIndex - 1 : startIndex + 1;
+
+ while (true) {
+ t += tStep;
+
+ /*
+ * If we are over delta max force to 1. We need to do this to avoid precision issues causing errors
+ * later on (e.g. if last entry for an index had a delta of 0.996 and later during an update we passed
+ * 0.998 for that index we'd fail to find a valid entry and error out)
+ */
+ if (t > InterpolationController.DELTA_MAX) {
+ t = InterpolationController.DELTA_MAX;
+ }
+
+ _curve.interpolate(startIndex, endIndex, t, target);
+
+ length += previous.distance(target);
+
+ previous.set(target);
+
+ entries.add(new ArcLengthEntry(t, length));
+
+ if (t == InterpolationController.DELTA_MAX) {
+ break;
+ }
+ }
+
+ _lookupTable.put(i, entries);
+ }
+
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.fine("look up table = " + _lookupTable);
+ }
+
+ Vector3.releaseTempInstance(target);
+ Vector3.releaseTempInstance(previous);
+ }
+
+ private boolean continueLoop(final int i, final boolean reverse) {
+ return (reverse ? i > 0 : i < _curve.getControlPointCount() - 2);
+ }
+
+ private int updateCounter(final int i, final boolean reverse) {
+ return (reverse ? i - 1 : i + 1);
+ }
+
+ /**
+ * A private inner class used to store the required arc length variables for the table
+ */
+ private static class ArcLengthEntry implements Serializable {
+ /** Serial UID */
+ private static final long serialVersionUID = 1L;
+
+ private final double _delta;
+ private final double _length;
+
+ public ArcLengthEntry(final double delta, final double length) {
+ super();
+
+ _delta = delta;
+ _length = length;
+ }
+
+ public double getDelta() {
+ return _delta;
+ }
+
+ public double getLength() {
+ return _length;
+ }
+
+ @Override
+ public String toString() {
+ return "ArcLengthEntry[length=" + _length + ", delta=" + _delta + ']';
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java
new file mode 100644
index 0000000..14ceb0c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/CatmullRomSpline.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+/**
+ *
+ */
+
+package com.ardor3d.spline;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+/**
+ * CatmullRomSpline class is an implementation of spline that uses the Catmull-Rom equation:
+ *
+ * <pre>
+ * q(t) = 0.5 * ((2 * P1) + (-P0 + P2) * t + (2 * P0 - 5 * P1 + 4 * P2 - P3) * t2 + (-P0 + 3 * P1 - 3 * P2 + P3) * t3)
+ * </pre>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Cubic Hermite spline -
+ * Wikipedia, the free encyclopedia</a>
+ */
+public class CatmullRomSpline implements Spline {
+
+ /**
+ * @see #interpolate(ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3, ReadOnlyVector3, double, Vector3)
+ */
+ public Vector3 interpolate(final ReadOnlyVector3 p0, final ReadOnlyVector3 p1, final ReadOnlyVector3 p2,
+ final ReadOnlyVector3 p3, final double t) {
+
+ return interpolate(p0, p1, p2, p3, t, new Vector3());
+ }
+
+ /**
+ * If any vector is <code>null</code> then the result is just returned unchanged.
+ *
+ * @param p0
+ * The start control point.
+ * @param p1
+ * Result will be between this and p2.
+ * @param p2
+ * result will be between this and p1.
+ * @param p3
+ * The end control point.
+ * @param t
+ * <code>0.0 <= t <= 1.0</code>, if t <= 0.0 then result will be contain exact same values as p1 and if
+ * its >= 1.0 it will contain the exact same values as p2.
+ * @param result
+ * The results from the interpolation will be stored in this vector.
+ * @return The result vector as a convenience.
+ */
+ public Vector3 interpolate(final ReadOnlyVector3 p0, final ReadOnlyVector3 p1, final ReadOnlyVector3 p2,
+ final ReadOnlyVector3 p3, final double t, final Vector3 result) {
+
+ if (null != result && null != p0 && null != p1 && null != p2 && null != p3) {
+ if (t <= 0.0) {
+ result.set(p1);
+
+ } else if (t >= 1.0) {
+ result.set(p2);
+
+ } else {
+ final double t2 = t * t;
+ final double t3 = t2 * t;
+
+ result.setX(0.5 * ((2.0 * p1.getX()) + (-p0.getX() + p2.getX()) * t
+ + (2.0 * p0.getX() - 5.0 * p1.getX() + 4.0 * p2.getX() - p3.getX()) * t2 + (-p0.getX() + 3.0
+ * p1.getX() - 3.0 * p2.getX() + p3.getX())
+ * t3));
+
+ result.setY(0.5 * ((2.0 * p1.getY()) + (-p0.getY() + p2.getY()) * t
+ + (2.0 * p0.getY() - 5.0 * p1.getY() + 4.0 * p2.getY() - p3.getY()) * t2 + (-p0.getY() + 3.0
+ * p1.getY() - 3.0 * p2.getY() + p3.getY())
+ * t3));
+
+ result.setZ(0.5 * ((2.0 * p1.getZ()) + (-p0.getZ() + p2.getZ()) * t
+ + (2.0 * p0.getZ() - 5.0 * p1.getZ() + 4.0 * p2.getZ() - p3.getZ()) * t2 + (-p0.getZ() + 3.0
+ * p1.getZ() - 3.0 * p2.getZ() + p3.getZ())
+ * t3));
+ }
+ }
+
+ return result;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java
new file mode 100644
index 0000000..07121a6
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Curve.java
@@ -0,0 +1,325 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.spline;
+
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Point;
+
+/**
+ * Curve class contains a list of control points and a spline. It also contains method for visualising itself as a
+ * renderable series of points or a line.
+ */
+public class Curve {
+
+ /** @see #setControlPoints(List) */
+ private List<ReadOnlyVector3> _controlPoints;
+
+ /** @see #setSpline(Spline) */
+ private Spline _spline;
+
+ /**
+ * Creates a new instance of <code>Curve</code>.
+ *
+ * @param controlPoints
+ * see {@link #setControlPoints(List)}
+ * @param spline
+ * see {@link #setSpline(Spline)}
+ */
+ public Curve(final List<ReadOnlyVector3> controlPoints, final Spline spline) {
+ super();
+
+ setControlPoints(controlPoints);
+ setSpline(spline);
+ }
+
+ /**
+ * Creates a new <code>Point</code> from the control points making up this curve. It will have the name
+ * <code>point</code>, no normals, colour or texture, these can be added to the returned point if needed.
+ *
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the more points
+ * will be shown, but it will also contain more vertices, must be greater than one. Use two to just show
+ * the actual control points set for the curve.
+ * @return A <code>Point</code> containing all the curve points, will not be <code>null</code>.
+ */
+ public Point toRenderablePoint(final int steps) {
+ return toRenderablePoint(1, getControlPointCount() - 2, steps);
+ }
+
+ /**
+ * Creates a new <code>Point</code> from the given control point indices. It will have the name <code>point</code>,
+ * no normals, colour or texture, these can be added to the returned point if needed.
+ *
+ * @param start
+ * The index of the control point to start from, must be greater than or equal to one and less than
+ * <code>end</code>.
+ * @param end
+ * The index of the control point to end with, must be less than {@link #getControlPointCount()
+ * controlPointCount} minus one and greater than <code>start</code>.
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the more points
+ * will be shown, but it will also contain more vertices, must be greater than one.
+ * @return A <code>Point</code> containing all the curve points, will not be <code>null</code>.
+ */
+ public Point toRenderablePoint(final int start, final int end, final int steps) {
+ final Vector3[] points = toVector3(start, end, steps);
+
+ return new Point("point", points, null, null, null);
+ }
+
+ /**
+ * Creates a new <code>Line</code> from the control points making up this curve. It will have the name
+ * <code>curve</code>, no normals, colour or texture, these can be added to the returned line if needed.
+ *
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the smoother the
+ * returned line will be, but it will also contain more vertices, must be greater than one.
+ * @return A <code>Line</code> representing this curve, will not be <code>null</code>.
+ */
+ public Line toRenderableLine(final int steps) {
+ return toRenderableLine(1, getControlPointCount() - 2, steps);
+ }
+
+ /**
+ * Creates a new <code>Line</code> from the given control point indices. It will have the name <code>curve</code>,
+ * no normals, colour or texture, these can be added to the returned line if needed.
+ *
+ * @param start
+ * The index of the control point to start from, must be greater than or equal to one and less than
+ * <code>end</code>.
+ * @param end
+ * The index of the control point to end with, must be less than {@link #getControlPointCount()
+ * controlPointCount} minus one and greater than <code>start</code>.
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the smoother the
+ * returned line will be, but it will also contain more vertices, must be greater than one.
+ * @return A <code>Line</code> representing this curve, will not be <code>null</code>.
+ */
+ public Line toRenderableLine(final int start, final int end, final int steps) {
+ final Vector3[] vertex = toVector3(start, end, steps);
+ final Vector3[] normal = null;
+ final ColorRGBA[] color = null;
+ final Vector2[] texture = null;
+
+ final Line line = new Line("curve", vertex, normal, color, texture);
+
+ line.getMeshData().setIndexMode(IndexMode.LineStrip);
+
+ return line;
+ }
+
+ /**
+ * Calculates the length of this curve.
+ * <p>
+ * <strong>Important note:</strong><br />
+ * To calculate the length of a curve it must be interpolated (hence the steps parameter), this method will do this
+ * EVERY time it's called (creating a lot of garbage vectors in the process). This has been done for the sake of
+ * keeping this class simple and the code as readable as possible. Therefore the length should be manually cached
+ * somewhere in your code if it is going to be used repeatedly.
+ * </p>
+ *
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the more accurate
+ * the returned result will be.
+ * @return The length of this curve.
+ * @see #getApproximateLength(int, int, int)
+ */
+ public double getApproximateLength(final int steps) {
+ return getApproximateLength(1, getControlPointCount() - 2, steps);
+ }
+
+ /**
+ * Calculates the length between the given control point indices.
+ * <p>
+ * <strong>Important note:</strong><br />
+ * See the Javadoc for the {@link #getApproximateLength(int)} method for important information.
+ * </p>
+ *
+ * @param start
+ * The index of the control point to start from, must be greater than or equal to one and less than
+ * <code>end</code>.
+ * @param end
+ * The index of the control point to end with, must be less than {@link #getControlPointCount()
+ * controlPointCount} minus one and greater than <code>start</code>.
+ * @param steps
+ * The number of iterations to perform between control points, the higher this number the more accurate
+ * the returned result will be.
+ * @return The length between the given control points.
+ * @see #getApproximateLength(int)
+ */
+ public double getApproximateLength(final int start, final int end, final int steps) {
+ double length = 0.0;
+
+ final Vector3[] vectors = toVector3(start, end, steps);
+
+ for (int i = 0; i < (vectors.length - 1); i++) {
+ length += vectors[i].distance(vectors[i + 1]);
+ }
+
+ return length;
+ }
+
+ /**
+ * Interpolates between the control points at the given indices.
+ *
+ * @param start
+ * The index of the control point to start from.
+ * @param end
+ * The index of the control point to end at.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>start</code> while one will return
+ * <code>end</code>, a value in between will return an interpolated vector between the two.
+ * @return The interpolated vector.
+ */
+ public ReadOnlyVector3 interpolate(final int start, final int end, final double t) {
+ return interpolate(start, end, t, new Vector3());
+ }
+
+ /**
+ * Interpolates between the control points at the given indices.
+ *
+ * @param start
+ * The index of the control point to start from.
+ * @param end
+ * The index of the control point to end at.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>start</code> while one will return
+ * <code>end</code>, a value in between will return an interpolated vector between the two.
+ * @param result
+ * The result of the interpolation will be stored in this vector.
+ * @return The result vector as a convenience.
+ */
+ public ReadOnlyVector3 interpolate(final int start, final int end, final double t, final Vector3 result) {
+ if (start <= 0) {
+ throw new IllegalArgumentException("start must be > 0! start=" + start);
+ }
+ if (end >= (getControlPointCount() - 1)) {
+ throw new IllegalArgumentException("end must be < " + (getControlPointCount() - 1) + "! end=" + end);
+ }
+
+ final List<ReadOnlyVector3> points = getControlPoints();
+
+ return getSpline().interpolate(points.get(start - 1), points.get(start), points.get(end), points.get(end + 1),
+ t, result);
+ }
+
+ /**
+ * @return The number of control points in this curve.
+ */
+ public int getControlPointCount() {
+ return getControlPoints().size();
+ }
+
+ /**
+ * @param controlPoints
+ * The new control points, can not be <code>null</code>.
+ * @see #getControlPoints()
+ */
+ public void setControlPoints(final List<ReadOnlyVector3> controlPoints) {
+ if (null == controlPoints) {
+ throw new IllegalArgumentException("controlPoints can not be null!");
+ }
+ if (controlPoints.size() < 4) {
+ throw new IllegalArgumentException("controlPoints must contain at least 4 elements for this class to work!");
+ }
+
+ _controlPoints = controlPoints;
+ }
+
+ /**
+ * @return The control points making up this curve, will not be <code>null</code>.
+ * @see #setControlPoints(List)
+ */
+ public List<ReadOnlyVector3> getControlPoints() {
+ assert (null != _controlPoints) : "_controlPoints was null, it must be set before use!";
+ assert (_controlPoints.size() >= 4) : "_controlPoints contained less than 4 elements, it must be contain at least 4 for this class to work!";
+
+ return _controlPoints;
+ }
+
+ /**
+ * @param spline
+ * The new spline, can not be <code>null</code>.
+ * @see #getSpline()
+ */
+ public void setSpline(final Spline spline) {
+ if (null == spline) {
+ throw new IllegalArgumentException("spline can not be null!");
+ }
+
+ _spline = spline;
+ }
+
+ /**
+ * The default is a {@link CatmullRomSpline}.
+ *
+ * @return The spline, will not be <code>null</code>.
+ * @see #setSpline(Spline)
+ */
+ public Spline getSpline() {
+ assert (null != _spline) : "_spline was null, it must be set before use!";
+
+ return _spline;
+ }
+
+ /**
+ * Interpolates the curve and returns an array of vectors.
+ */
+ private Vector3[] toVector3(final int start, final int end, final int steps) {
+ if (start <= 0) {
+ throw new IllegalArgumentException("start must be > 0! start=" + start);
+ }
+ if (end >= (getControlPointCount() - 1)) {
+ throw new IllegalArgumentException("end must be < " + (getControlPointCount() - 1) + "! end=" + end);
+ }
+ if (start >= end) {
+ throw new IllegalArgumentException("start must be < end! start=" + start + ", end=" + end);
+ }
+ if (steps <= 1) {
+ throw new IllegalArgumentException("steps must be >= 1! steps=" + steps);
+ }
+
+ final List<ReadOnlyVector3> controlPoints = getControlPoints();
+
+ final int count = (end - start) * steps;
+
+ final Vector3[] vectors = new Vector3[count];
+
+ int index = start;
+
+ for (int i = 0; i < count; i++) {
+ final int is = i % steps;
+
+ if (0 == is && i >= steps) {
+ index++;
+ }
+
+ final double t = is / (steps - 1.0);
+
+ final int p0 = index - 1;
+ final int p1 = index;
+ final int p2 = index + 1;
+ final int p3 = index + 2;
+
+ vectors[i] = getSpline().interpolate(controlPoints.get(p0), controlPoints.get(p1), controlPoints.get(p2),
+ controlPoints.get(p3), t);
+ }
+
+ return vectors;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java
new file mode 100644
index 0000000..311f5ba
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/spline/Spline.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.spline;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+/**
+ * Spline interface allows an interpolated vector to be calculated along a path.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Spline_(mathematics)">Spline (mathematics) - Wikipedia, the free
+ * encyclopedia</a>
+ */
+public interface Spline {
+ /**
+ * Will return an interpolated vector between parameters <code>p1</code> and <code>p2</code> using <code>t</code>.
+ *
+ * @param p0
+ * The starting control point.
+ * @param p1
+ * The second control point.
+ * @param p2
+ * The third control point.
+ * @param p3
+ * The final control point.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>p1</code> while one will return
+ * <code>p2</code>, a value in between will return an interpolated vector between the two.
+ * @return The interpolated vector.
+ */
+ public Vector3 interpolate(ReadOnlyVector3 p0, ReadOnlyVector3 p1, ReadOnlyVector3 p2, ReadOnlyVector3 p3, double t);
+
+ /**
+ * Will return an interpolated vector between parameters <code>p1</code> and <code>p2</code> using <code>t</code>.
+ *
+ * @param p0
+ * The starting control point.
+ * @param p1
+ * The second control point.
+ * @param p2
+ * The third control point.
+ * @param p3
+ * The final control point.
+ * @param t
+ * Should be between zero and one. Zero will return point <code>p1</code> while one will return
+ * <code>p2</code>, a value in between will return an interpolated vector between the two.
+ * @param result
+ * The interpolated values will be added to this vector.
+ * @return The result vector passed in.
+ */
+ public Vector3 interpolate(ReadOnlyVector3 p0, ReadOnlyVector3 p1, ReadOnlyVector3 p2, ReadOnlyVector3 p3,
+ double t, Vector3 result);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java
new file mode 100644
index 0000000..9e9e343
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMFont.java
@@ -0,0 +1,954 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.ui.text;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.TextureKey;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.resource.ResourceSource;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Loads a font generated by BMFont (http://www.angelcode.com/products/bmfont/).
+ * <ul>
+ * <li>Font info file *must* be in XML format.
+ * <li>The texture should be saved in 32 bit PNG format - TGA does not appear to work.
+ * <li>This class only supports a single page (see BMFont documentation for details on pages)
+ * </ul>
+ */
+public class BMFont implements Savable {
+ private static Logger logger = Logger.getLogger(BMFont.class.getName());
+
+ private final Map<Integer, Char> _charMap = Maps.newHashMap();
+ private final Map<Integer, Map<Integer, Integer>> _kernMap = Maps.newHashMap();
+
+ private String _styleName; // e.g. "Courier-12-bold"
+ private final ArrayList<Page> _pages = new ArrayList<Page>();
+ private Texture _pageTexture;
+ private RenderStateSetter _blendStateSetter = null;
+ private RenderStateSetter _alphaStateSetter = null;
+ private boolean _useMipMaps;
+ private int _maxCharAdv;
+ private Common _common = null;
+ private Info _info = null;
+
+ /**
+ * This constructor should be used when loading as Savable
+ */
+ public BMFont() {}
+
+ /**
+ * Reads an XML BMFont description file and loads corresponding texture. Note that the TGA written by BMFont does
+ * not seem to be read properly by the Ardor3D loader. PNG works fine.
+ *
+ * @param fileUrl
+ * - the location of the .fnt font file. Can not be null.
+ * @param useMipMaps
+ * if true, use trilinear filtering with max anisotropy, else min filter is bilinear. MipMaps result in
+ * blurrier text, but less shimmering.
+ * @throws IOException
+ * if there are any problems reading the .fnt file.
+ */
+ public BMFont(final ResourceSource source, final boolean useMipMaps) throws IOException {
+ _useMipMaps = useMipMaps;
+
+ parseFontFile(source);
+ initialize(source);
+ }
+
+ /** apply default render states to spatial */
+ public void applyRenderStatesTo(final Spatial spatial, final boolean useBlend) {
+ if (useBlend) {
+ if (_blendStateSetter == null) {
+ _blendStateSetter = new RenderStateSetter(_pageTexture, true);
+ }
+ _blendStateSetter.applyTo(spatial);
+ } else {
+ if (_alphaStateSetter == null) {
+ _alphaStateSetter = new RenderStateSetter(_pageTexture, false);
+ }
+ _alphaStateSetter.applyTo(spatial);
+ }
+ }
+
+ public String getStyleName() {
+ return _styleName;
+ }
+
+ public int getSize() {
+ return Math.abs(_info.size);
+ }
+
+ public int getLineHeight() {
+ return _common.lineHeight;
+ }
+
+ public int getBaseHeight() {
+ return _common.base;
+ }
+
+ public int getTextureWidth() {
+ return _common.scaleW;
+ }
+
+ public int getTextureHeight() {
+ return _common.scaleH;
+ }
+
+ /**
+ * @param chr
+ * ascii character code
+ * @return character descriptor for chr. If character is not in the char set, return '?' (if '?' is not in the char
+ * set, return will be null)
+ */
+ public BMFont.Char getChar(int chr) {
+ BMFont.Char retVal = _charMap.get(chr);
+ if (retVal == null) {
+ chr = '?';
+ retVal = _charMap.get(chr);
+ if (retVal == null) { // if still null, use the first char
+ final Iterator<Char> it = _charMap.values().iterator();
+ retVal = it.next();
+ }
+ }
+ return retVal;
+ }
+
+ /**
+ * @return kerning information for this character pair
+ */
+ public int getKerning(final int chr, final int nextChr) {
+ final Map<Integer, Integer> map = _kernMap.get(chr);
+ if (map != null) {
+ final Integer amt = map.get(nextChr);
+ if (amt != null) {
+ return amt;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * @return the largest xadvance in this char set
+ */
+ public int getMaxCharAdvance() {
+ return _maxCharAdv;
+ }
+
+ public int getOutlineWidth() {
+ return _info.outline;
+ }
+
+ public Info getInfo() {
+ return _info;
+ }
+
+ /**
+ * Writes the XML for this font out to the OutputStream provided.
+ *
+ * @param outputStream
+ * the OutputStream to which the XML for this font will be written to
+ * @throws IOException
+ * thrown if there is any problem writing out to the OutputStream
+ */
+ public void writeXML(final OutputStream outputStream) throws IOException {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append("<?xml version=\"1.0\"?>\n");
+ xml.append("<font>\n");
+ xml.append(generateInfoXML());
+ xml.append(generateCommonXML());
+ xml.append(generatePagesXML());
+ xml.append(generateCharsXML());
+ xml.append(generateKerningsXML());
+ xml.append("</font>");
+
+ // Write out to the output stream now
+ outputStream.write(xml.toString().getBytes());
+ outputStream.flush();
+
+ return;
+ }
+
+ private String generateInfoXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <info face=\"");
+ xml.append(_info.face);
+ xml.append("\" size=\"");
+ xml.append(_info.size);
+ xml.append("\" bold=\"");
+ xml.append(_info.bold ? "1" : "0");
+ xml.append("\" italic=\"");
+ xml.append(_info.italic ? "1" : "0");
+ xml.append("\" charset=\"");
+ xml.append(_info.charset);
+ xml.append("\" unicode=\"");
+ xml.append(_info.unicode ? "1" : "0");
+ xml.append("\" stretchH=\"");
+ xml.append(_info.stretchH);
+ xml.append("\" smooth=\"");
+ xml.append(_info.smooth ? "1" : "0");
+ xml.append("\" aa=\"");
+ xml.append(_info.aa ? "1" : "0");
+ xml.append("\" padding=\"");
+
+ for (int i = 0; i < _info.padding.length; i++) {
+ xml.append(_info.padding[i]);
+
+ if (i < (_info.padding.length - 1)) {
+ xml.append(",");
+ }
+ }
+
+ xml.append("\" spacing=\"");
+
+ for (int i = 0; i < _info.spacing.length; i++) {
+ xml.append(_info.spacing[i]);
+
+ if (i < (_info.spacing.length - 1)) {
+ xml.append(",");
+ }
+ }
+
+ xml.append("\" outline=\"");
+ xml.append(_info.outline);
+ xml.append("\"/>\n");
+
+ return xml.toString();
+ }
+
+ private String generateCommonXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <common lineHeight=\"");
+ xml.append(_common.lineHeight);
+ xml.append("\" base=\"");
+ xml.append(_common.base);
+ xml.append("\" scaleW=\"");
+ xml.append(_common.scaleW);
+ xml.append("\" scaleH=\"");
+ xml.append(_common.scaleH);
+ xml.append("\" pages=\"");
+ xml.append(_common.pages);
+ xml.append("\" packed=\"");
+ xml.append(_common.packed ? "1" : "0");
+ xml.append("\" alphaChnl=\"");
+ xml.append(_common.alphaChnl);
+ xml.append("\" redChnl=\"");
+ xml.append(_common.redChnl);
+ xml.append("\" greenChnl=\"");
+ xml.append(_common.greenChnl);
+ xml.append("\" blueChnl=\"");
+ xml.append(_common.blueChnl);
+ xml.append("\"/>\n");
+
+ return xml.toString();
+ }
+
+ private String generatePagesXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <pages>\n");
+
+ for (final Iterator<Page> iterator = _pages.iterator(); iterator.hasNext();) {
+ final Page page = iterator.next();
+
+ xml.append(" <page id=\"");
+ xml.append(page.id);
+ xml.append("\" file=\"");
+ xml.append(page.file);
+ xml.append("\" />\n");
+ }
+
+ xml.append(" </pages>\n");
+
+ return xml.toString();
+ }
+
+ private String generateCharsXML() {
+ final StringBuilder xml = new StringBuilder();
+
+ xml.append(" <chars count=\"");
+ xml.append(_charMap.size());
+ xml.append("\">\n");
+
+ for (final Iterator<Integer> iterator = _charMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer key = iterator.next();
+ final Char character = _charMap.get(key);
+
+ xml.append(" <char id=\"");
+ xml.append(character.id);
+ xml.append("\" x=\"");
+ xml.append(character.x);
+ xml.append("\" y=\"");
+ xml.append(character.y);
+ xml.append("\" width=\"");
+ xml.append(character.width);
+ xml.append("\" height=\"");
+ xml.append(character.height);
+ xml.append("\" xoffset=\"");
+ xml.append(character.xoffset);
+ xml.append("\" yoffset=\"");
+ xml.append(character.yoffset);
+ xml.append("\" xadvance=\"");
+ xml.append(character.xadvance);
+ xml.append("\" page=\"");
+ xml.append(character.page);
+ xml.append("\" chnl=\"");
+ xml.append(character.chnl);
+ xml.append("\" />\n");
+ }
+
+ xml.append(" </chars>\n");
+
+ return xml.toString();
+ }
+
+ private String generateKerningsXML() {
+ final StringBuilder xml = new StringBuilder();
+ int count = 0;
+
+ for (final Iterator<Integer> iterator = _kernMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer first = iterator.next();
+ final Map<Integer, Integer> amtHash = _kernMap.get(first);
+
+ for (final Iterator<Integer> iterator2 = amtHash.keySet().iterator(); iterator2.hasNext();) {
+ final Integer second = iterator2.next();
+ final Integer amount = amtHash.get(second);
+
+ xml.append(" <kerning first=\"");
+ xml.append(first);
+ xml.append("\" second=\"");
+ xml.append(second);
+ xml.append("\" amount=\"");
+ xml.append(amount);
+ xml.append("\" />\n");
+
+ count++;
+ }
+ }
+
+ final String xmlString = " <kernings count=\"" + count + "\">\n" + xml.toString() + " </kernings>\n";
+
+ return xmlString;
+ }
+
+ /**
+ * load the texture and create default render states. Only a single page is supported.
+ *
+ * @param fontUrl
+ */
+ // ----------------------------------------------------------
+ protected void initialize(final ResourceSource source) throws MalformedURLException {
+ _styleName = _info.face + "-" + _info.size;
+
+ if (_info.bold) {
+ _styleName += "-bold";
+ } else {
+ _styleName += "-medium";
+ }
+
+ if (_info.italic) {
+ _styleName += "-italic";
+ } else {
+ _styleName += "-regular";
+ }
+
+ // only a single page is supported
+ if (_pages.size() > 0) {
+ final Page page = _pages.get(0);
+
+ final ResourceSource texSrc = source.getRelativeSource("./" + page.file);
+
+ Texture.MinificationFilter minFilter;
+ Texture.MagnificationFilter magFilter;
+
+ magFilter = Texture.MagnificationFilter.Bilinear;
+ minFilter = Texture.MinificationFilter.BilinearNoMipMaps;
+ if (_useMipMaps) {
+ minFilter = Texture.MinificationFilter.Trilinear;
+ }
+ final TextureKey tkey = TextureKey.getKey(texSrc, false, TextureStoreFormat.GuessNoCompressedFormat,
+ minFilter);
+ _pageTexture = TextureManager.loadFromKey(tkey, null, null);
+ _pageTexture.setMagnificationFilter(magFilter);
+
+ // Add a touch higher mipmap selection.
+ _pageTexture.setLodBias(-1);
+
+ if (_useMipMaps) {
+ _pageTexture.setAnisotropicFilterPercent(1.0f);
+ }
+ }
+ }
+
+ public Texture getPageTexture() {
+ return _pageTexture;
+ }
+
+ public List<Integer> getMappedChars() {
+ return Lists.newArrayList(_charMap.keySet());
+ }
+
+ public Map<Integer, Integer> getKerningsForCharacter(final int val) {
+ return _kernMap.get(val);
+ }
+
+ public Map<Integer, Map<Integer, Integer>> getKerningMap() {
+ return _kernMap;
+ }
+
+ /**
+ *
+ * @param fontUrl
+ * @throws IOException
+ */
+ protected void parseFontFile(final ResourceSource source) throws IOException {
+ _maxCharAdv = 0;
+ _charMap.clear();
+ _pages.clear();
+ try {
+ final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ final DocumentBuilder db = dbf.newDocumentBuilder();
+ final Document doc = db.parse(source.openStream());
+
+ doc.getDocumentElement().normalize();
+ recurse(doc.getElementsByTagName("font").item(0));
+
+ // db.reset();
+ } catch (final Throwable t) {
+ final IOException ex = new IOException("Error loading font file " + source.toString());
+ ex.initCause(t);
+ throw ex;
+ }
+ }
+
+ private void recurse(final Node node) {
+ final NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ final Node child = children.item(i);
+ processNode(child);
+ recurse(child);
+ }
+ }
+
+ private void processNode(final Node node) {
+ final String tagName = node.getNodeName();
+ if (tagName != null) {
+ if (tagName.equals("info")) {
+ processInfoNode(node);
+ } else if (tagName.equals("common")) {
+ processCommonNode(node);
+ } else if (tagName.equals("page")) {
+ processPageNode(node);
+ } else if (tagName.equals("char")) {
+ processCharNode(node);
+ } else if (tagName.equals("kerning")) {
+ procesKerningNode(node);
+ }
+ }
+ }
+
+ private void processInfoNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ _info = new Info();
+ _info.face = getStringAttrib("face", attribs);
+ _info.size = getIntAttrib("size", attribs);
+ _info.bold = getBoolAttrib("bold", attribs);
+ _info.italic = getBoolAttrib("italic", attribs);
+ _info.charset = getStringAttrib("charset", attribs);
+ _info.unicode = getBoolAttrib("unicode", attribs);
+ _info.stretchH = getIntAttrib("stretchH", attribs);
+ _info.smooth = getBoolAttrib("smooth", attribs);
+ _info.aa = getBoolAttrib("aa", attribs);
+ _info.padding = getIntArrayAttrib("padding", attribs);
+ _info.spacing = getIntArrayAttrib("spacing", attribs);
+ _info.outline = getIntAttrib("outline", attribs);
+ }
+
+ private void processCommonNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ _common = new Common();
+ _common.lineHeight = getIntAttrib("lineHeight", attribs);
+ _common.base = getIntAttrib("base", attribs);
+ _common.scaleW = getIntAttrib("scaleW", attribs);
+ _common.scaleH = getIntAttrib("scaleH", attribs);
+ _common.pages = getIntAttrib("pages", attribs);
+ _common.packed = getBoolAttrib("packed", attribs);
+ _common.alphaChnl = getIntAttrib("alphaChnl", attribs);
+ _common.redChnl = getIntAttrib("redChnl", attribs);
+ _common.greenChnl = getIntAttrib("greenChnl", attribs);
+ _common.blueChnl = getIntAttrib("blueChnl", attribs);
+ }
+
+ private void processCharNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final Char c = new Char();
+ c.id = getIntAttrib("id", attribs);
+ c.x = getIntAttrib("x", attribs);
+ c.y = getIntAttrib("y", attribs);
+ c.width = getIntAttrib("width", attribs);
+ c.height = getIntAttrib("height", attribs);
+ c.xoffset = getIntAttrib("xoffset", attribs);
+ c.yoffset = getIntAttrib("yoffset", attribs);
+ c.xadvance = getIntAttrib("xadvance", attribs);
+ c.page = getIntAttrib("page", attribs);
+ c.chnl = getIntAttrib("chnl", attribs);
+ _charMap.put(c.id, c);
+ if (c.xadvance > _maxCharAdv) {
+ _maxCharAdv = c.xadvance;
+ }
+ }
+
+ private void processPageNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final Page page = new Page();
+ page.id = getIntAttrib("id", attribs);
+ page.file = getStringAttrib("file", attribs);
+ _pages.add(page);
+ if (_pages.size() > 1) {
+ logger.warning("multiple pages defined in font description file, but only a single page is supported.");
+ }
+ }
+
+ private void procesKerningNode(final Node node) {
+ final NamedNodeMap attribs = node.getAttributes();
+ final int first = getIntAttrib("first", attribs);
+ final int second = getIntAttrib("second", attribs);
+ final int amount = getIntAttrib("amount", attribs);
+ Map<Integer, Integer> amtHash;
+ amtHash = _kernMap.get(first);
+ if (amtHash == null) {
+ amtHash = Maps.newHashMap();
+ _kernMap.put(first, amtHash);
+ }
+ amtHash.put(second, amount);
+ }
+
+ // == xml attribute getters ============================
+ int getIntAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return Integer.parseInt(node.getNodeValue());
+ }
+
+ String getStringAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return node.getNodeValue();
+ }
+
+ boolean getBoolAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ return (Integer.parseInt(node.getNodeValue()) == 1);
+ }
+
+ int[] getIntArrayAttrib(final String name, final NamedNodeMap attribs) {
+ final Node node = attribs.getNamedItem(name);
+ final String str = node.getNodeValue();
+ final StringTokenizer strtok = new StringTokenizer(str, ",");
+ final int sz = strtok.countTokens();
+ final int[] retVal = new int[sz];
+ for (int i = 0; i < sz; i++) {
+ retVal[i] = Integer.parseInt(strtok.nextToken());
+ }
+ return retVal;
+ }
+
+ // == support structs ==================================
+ @SavableFactory(factoryMethod = "create")
+ public static class Info implements Savable {
+ public String face;
+ public int size;
+ public boolean bold;
+ public boolean italic;
+ public String charset;
+ public boolean unicode;
+ public int stretchH;
+ public boolean smooth;
+ public boolean aa;
+ public int[] padding;
+ public int[] spacing;
+ public int outline;
+
+ public static Info create() {
+ return new Info();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(face, "face", null);
+ capsule.write(size, "size", 0);
+ capsule.write(bold, "bold", false);
+ capsule.write(italic, "italic", false);
+ capsule.write(charset, "charset", null);
+ capsule.write(unicode, "unicode", false);
+ capsule.write(stretchH, "stretchH", 0);
+ capsule.write(smooth, "smooth", false);
+ capsule.write(aa, "aa", false);
+ capsule.write(padding, "padding", null);
+ capsule.write(spacing, "spacing", null);
+ capsule.write(outline, "outline", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ face = capsule.readString("face", null);
+ size = capsule.readInt("size", 0);
+ bold = capsule.readBoolean("bold", false);
+ italic = capsule.readBoolean("italic", false);
+ charset = capsule.readString("charset", null);
+ unicode = capsule.readBoolean("unicode", false);
+ stretchH = capsule.readInt("stretchH", 0);
+ smooth = capsule.readBoolean("smooth", false);
+ aa = capsule.readBoolean("aa", false);
+ padding = capsule.readIntArray("padding", null);
+ spacing = capsule.readIntArray("spacing", null);
+ outline = capsule.readInt("outline", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Common implements Savable {
+ public int lineHeight;
+ public int base;
+ public int scaleW;
+ public int scaleH;
+ public int pages;
+ public boolean packed;
+ public int alphaChnl;
+ public int redChnl;
+ public int greenChnl;
+ public int blueChnl;
+
+ public static Common create() {
+ return new Common();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(lineHeight, "lineHeight", 0);
+ capsule.write(base, "base", 0);
+ capsule.write(scaleW, "scaleW", 1);
+ capsule.write(scaleH, "scaleH", 1);
+ capsule.write(pages, "pages", 0);
+ capsule.write(packed, "packed", false);
+ capsule.write(alphaChnl, "alphaChnl", 0);
+ capsule.write(redChnl, "redChnl", 0);
+ capsule.write(greenChnl, "greenChnl", 0);
+ capsule.write(blueChnl, "blueChnl", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ lineHeight = capsule.readInt("lineHeight", 0);
+ base = capsule.readInt("base", 0);
+ scaleW = capsule.readInt("scaleW", 0);
+ scaleH = capsule.readInt("scaleH", 0);
+ pages = capsule.readInt("pages", 0);
+ packed = capsule.readBoolean("packed", false);
+ alphaChnl = capsule.readInt("alphaChnl", 0);
+ redChnl = capsule.readInt("redChnl", 0);
+ greenChnl = capsule.readInt("greenChnl", 0);
+ blueChnl = capsule.readInt("blueChnl", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Page implements Savable {
+ public int id;
+ public String file;
+
+ public static Page create() {
+ return new Page();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(id, "id", 0);
+ capsule.write(file, "file", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ id = capsule.readInt("id", 0);
+ file = capsule.readString("file", null);
+
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return this.getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Char implements Savable {
+ public int id;
+ public int x;
+ public int y;
+ public int width;
+ public int height;
+ public int xoffset;
+ public int yoffset;
+ public int xadvance;
+ public int page;
+ public int chnl;
+
+ public static Char create() {
+ return new Char();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(id, "id", 0);
+ capsule.write(x, "x", 0);
+ capsule.write(y, "y", 0);
+ capsule.write(width, "width", 0);
+ capsule.write(height, "height", 0);
+ capsule.write(xoffset, "xoffset", 0);
+ capsule.write(yoffset, "yoffset", 0);
+ capsule.write(xadvance, "xadvance", 0);
+ capsule.write(page, "page", 0);
+ capsule.write(chnl, "chnl", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ id = capsule.readInt("id", 0);
+ x = capsule.readInt("x", 0);
+ y = capsule.readInt("y", 0);
+ width = capsule.readInt("width", 0);
+ height = capsule.readInt("height", 0);
+ xoffset = capsule.readInt("xoffset", 0);
+ yoffset = capsule.readInt("yoffset", 0);
+ xadvance = capsule.readInt("xadvance", 0);
+ page = capsule.readInt("page", 0);
+ chnl = capsule.readInt("chnl", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ @SavableFactory(factoryMethod = "create")
+ public static class Kerning implements Savable {
+ public int first;
+ public int second;
+ public int amount;
+
+ public Kerning() {}
+
+ public Kerning(final int first, final int second, final int amount) {
+ this.first = first;
+ this.second = second;
+ this.amount = amount;
+ }
+
+ public static Kerning create() {
+ return new BMFont.Kerning();
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(first, "fist", 0);
+ capsule.write(second, "second", 0);
+ capsule.write(amount, "amount", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ first = capsule.readInt("first", 0);
+ second = capsule.readInt("second", 0);
+ amount = capsule.readInt("amount", 0);
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+ }
+
+ /**
+ * utility to set default render states for text
+ */
+ public class RenderStateSetter {
+ public TextureState textureState;
+ public BlendState blendState;
+ public ZBufferState zBuffState;
+
+ float _blendDisabledTestRef = 0.3f;
+ float _blendEnabledTestRef = 0.02f;
+
+ boolean _useBlend;
+
+ RenderStateSetter(final Texture texture, final boolean useBlend) {
+ textureState = new TextureState();
+ textureState.setTexture(texture);
+
+ blendState = new BlendState();
+ blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ blendState.setTestEnabled(true);
+ blendState.setTestFunction(BlendState.TestFunction.GreaterThan);
+
+ zBuffState = new ZBufferState();
+ zBuffState.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
+
+ setUseBlend(useBlend);
+ }
+
+ void applyTo(final Spatial spatial) {
+ spatial.setRenderState(textureState);
+ spatial.setRenderState(blendState);
+ spatial.setRenderState(zBuffState);
+ if (_useBlend) {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Transparent);
+ } else {
+ spatial.getSceneHints().setRenderBucketType(RenderBucketType.Opaque);
+ }
+ }
+
+ void setUseBlend(final boolean blend) {
+ _useBlend = blend;
+ if (blend == false) {
+ blendState.setBlendEnabled(false);
+ blendState.setReference(_blendDisabledTestRef);
+ zBuffState.setWritable(true);
+ } else {
+ blendState.setBlendEnabled(true);
+ blendState.setReference(_blendEnabledTestRef);
+ zBuffState.setWritable(false);
+ }
+ }
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ _pageTexture.setStoreImage(true);
+ capsule.write(_useMipMaps, "useMipMaps", false);
+ capsule.write(_styleName, "styleName", null);
+ capsule.write(_pageTexture, "pageTexture", null);
+
+ // Info
+ capsule.write(_info, "info", null);
+ // Common
+ capsule.write(_common, "common", null);
+ // Pages
+ capsule.writeSavableList(_pages, "pages", _pages);
+ // Chars
+ capsule.writeSavableList(new ArrayList<Char>(_charMap.values()), "charMap", null);
+
+ // Kernings
+ final List<Kerning> kernings = new ArrayList<Kerning>();
+ for (final Iterator<Integer> iterator = _kernMap.keySet().iterator(); iterator.hasNext();) {
+ final Integer first = iterator.next();
+ final Map<Integer, Integer> amtHash = _kernMap.get(first);
+ for (final Iterator<Integer> iterator2 = amtHash.keySet().iterator(); iterator2.hasNext();) {
+ final Integer second = iterator2.next();
+ final Integer amount = amtHash.get(second);
+ kernings.add(new Kerning(first, second, amount));
+ }
+ }
+ capsule.writeSavableList(kernings, "kernings", kernings);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ _useMipMaps = capsule.readBoolean("useMipMaps", false);
+ _styleName = capsule.readString("styleName", null);
+ _pageTexture = (Texture) capsule.readSavable("pageTexture", null);
+ _pageTexture = TextureManager.loadFromImage(_pageTexture.getImage(), _pageTexture.getMinificationFilter());
+
+ // Info
+ _info = (Info) capsule.readSavable("info", _info);
+ // Common
+ _common = (Common) capsule.readSavable("common", _common);
+ // Pages
+ _pages.clear();
+ final List<Savable> pages = capsule.readSavableList("pages", new ArrayList<Savable>());
+ for (final Savable savable : pages) {
+ _pages.add((Page) savable);
+ if (_pages.size() > 1) {
+ logger.warning("multiple pages defined in font description file, but only a single page is supported.");
+ }
+ }
+ // Chars
+ _charMap.clear();
+ final List<Savable> chars = capsule.readSavableList("charMap", new ArrayList<Savable>());
+ for (final Savable savable : chars) {
+ final Char c = (Char) savable;
+ _charMap.put(c.id, c);
+ if (c.xadvance > _maxCharAdv) {
+ _maxCharAdv = c.xadvance;
+ }
+ }
+ // Kernings
+ _kernMap.clear();
+ final List<Savable> kernings = capsule.readSavableList("kernings", new ArrayList<Savable>());
+ for (final Savable savable : kernings) {
+ final Kerning k = (Kerning) savable;
+ Map<Integer, Integer> amtHash;
+ amtHash = _kernMap.get(k.first);
+ if (amtHash == null) {
+ amtHash = Maps.newHashMap();
+ _kernMap.put(k.first, amtHash);
+ }
+ amtHash.put(k.second, k.amount);
+ _kernMap.put(k.first, amtHash);
+ }
+ }
+
+ @Override
+ public Class<?> getClassTag() {
+ return getClass();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java
new file mode 100644
index 0000000..c305c45
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BMText.java
@@ -0,0 +1,761 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.ui.text;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Camera.ProjectionMode;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * Text spatial which uses textures generated by BMFont
+ */
+@SavableFactory(factoryMethod = "initSavable")
+public class BMText extends Mesh {
+ protected BMFont _font;
+
+ protected String _textString;
+ private final int _tabSize = 4;
+
+ protected double _fontScale = 1.0;
+ protected boolean _autoRotate = true;
+
+ protected int _lines = 1;
+
+ protected final Vector2 _size = new Vector2(); // width and height of text string
+ protected float[] _lineWidths = new float[64]; // size of each line of text
+
+ protected ColorRGBA _textClr = new ColorRGBA(1, 1, 1, 1);
+ protected ColorRGBA _tempClr = new ColorRGBA(1, 1, 1, 1);
+
+ public enum AutoScale {
+ /**
+ * No auto scaling
+ */
+ Off,
+
+ /**
+ * Maintain native point size of font regardless of distance from camera
+ */
+ FixedScreenSize,
+
+ /**
+ * Do not auto scale if font screen size is smaller than native point size, otherwise maintain native point
+ * size.
+ */
+ CapScreenSize;
+ }
+
+ protected AutoScale _autoScale = AutoScale.CapScreenSize;
+
+ /**
+ * @see BMText#setAutoFadeDistanceRange(double, double)
+ * @see BMText#setAutoFadeFixedPixelSize(int)
+ * @see BMText#setAutoFadeFalloff(float)
+ */
+ public enum AutoFade {
+ /**
+ * No auto fade.
+ */
+ Off,
+
+ /**
+ * Fade based on a fixed distance between text and camera.
+ */
+ DistanceRange,
+
+ /**
+ * Fade when screen size is less than fixed pixel size.
+ */
+ FixedPixelSize,
+
+ /**
+ * Fade when screen size is less than native size. Equivalent to FixedPixelSize +
+ * setAutoFadeFixedPixelSize(font.getSize()).
+ */
+ CapScreenSize;
+ }
+
+ protected AutoFade _autoFade = AutoFade.FixedPixelSize;
+ protected int _fixedPixelAlphaThresh = 14;
+ protected float _screenSizeAlphaFalloff = 0.7f; // 0=instant, 1=half size
+ protected final Vector2 _distanceAlphaRange = new Vector2(50, 75);
+ protected boolean _useBlend;
+
+ /**
+ * Justification within a text block
+ */
+ public enum Justify {
+ Left, Center, Right;
+ }
+
+ protected Justify _justify;
+ protected int _spacing = 0; // additional spacing between characters
+
+ /**
+ * Alignment of the text block from the pivot point
+ */
+ public enum Align {
+ North(-0.5f, 0.0f), NorthWest(0.0f, 0.0f), NorthEast(-1.0f, 0.0f), Center(-0.5f, -0.5f), West(0.0f, -0.5f), East(
+ -1.0f, -0.5f), South(-0.5f, -1.0f), SouthWest(0.0f, -1.0f), SouthEast(-1.0f, -1.0f);
+ public final float horizontal;
+ public final float vertical;
+
+ private Align(final float h, final float v) {
+ horizontal = h;
+ vertical = v;
+ }
+ }
+
+ protected Align _align;
+ protected final Vector2 _alignOffset = new Vector2();
+ protected final Vector2 _fixedOffset = new Vector2();
+
+ protected final Vector3 _look = new Vector3();
+ protected final Vector3 _left = new Vector3();
+ protected final Matrix3 _rot = new Matrix3();
+
+ public static BMText initSavable() {
+ return new BMText();
+ }
+
+ protected BMText() {}
+
+ /**
+ *
+ * @param sName
+ * @param text
+ * @param font
+ */
+ public BMText(final String sName, final String text, final BMFont font) {
+ this(sName, text, font, Align.SouthWest);
+ }
+
+ public BMText(final String sName, final String text, final BMFont font, final Align align) {
+ this(sName, text, font, align, Justify.Left);
+ }
+
+ public BMText(final String sName, final String text, final BMFont font, final Align align, final Justify justify) {
+ this(sName, text, font, align, justify, true);
+ }
+
+ /**
+ *
+ * @param sName
+ * spatial name
+ * @param text
+ * text to render.
+ * @param font
+ * @param align
+ * @param justify
+ * @param useBlend
+ * if true: use alpha blending and use transparent render bucket, else if false: alpha test only and use
+ * opaque render bucket
+ */
+ public BMText(final String sName, final String text, final BMFont font, final Align align, final Justify justify,
+ final boolean useBlend) {
+ super(sName);
+ _font = font;
+ _align = align;
+ _justify = justify;
+ _spacing = 0;
+ _useBlend = useBlend;
+ if (_font.getOutlineWidth() > 1) {
+ _spacing = _font.getOutlineWidth() - 1;
+ }
+
+ // -- never cull
+ setModelBound(null);
+ getSceneHints().setCullHint(CullHint.Never);
+
+ // -- default to non-pickable
+ getSceneHints().setAllPickingHints(false);
+
+ // no light, basic texture
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ // triangles
+ getMeshData().setIndexMode(IndexMode.Triangles);
+
+ setText(text);
+
+ _font.applyRenderStatesTo(this, useBlend);
+ }
+
+ public void setTextColor(final ReadOnlyColorRGBA clr) {
+ _textClr.set(clr);
+ setDefaultColor(_textClr);
+ }
+
+ public void setTextColor(final float r, final float g, final float b, final float a) {
+ _textClr.set(r, g, b, a);
+ setDefaultColor(_textClr);
+ }
+
+ /**
+ * If AutoScale is enabled, this scale parameter acts as a bias. Setting the scale to 0.95 will sharpen the font and
+ * increase readability a bit if you're using a bilinear min filter on the texture. When AutoScale is disabled, this
+ * scales the font to world units, e.g. setScale(1) would make the font characters approximately 1 world unit in
+ * size, regardless of the font point size.
+ */
+ public void setFontScale(final double scale) {
+ _fontScale = scale;
+
+ if (_autoScale == AutoScale.Off) {
+ final double unit = 1.0 / _font.getSize();
+ final double s = unit * _fontScale;
+ this.setScale(s, s, -s);
+ }
+ }
+
+ public double getFontScale() {
+ return _fontScale;
+ }
+
+ /**
+ * Set scaling policy
+ */
+ public void setAutoScale(final AutoScale autoScale) {
+ _autoScale = autoScale;
+ setFontScale(_fontScale);
+ }
+
+ public AutoScale getAutoScale() {
+ return _autoScale;
+ }
+
+ public void setAutoFade(final AutoFade autoFade) {
+ _autoFade = autoFade;
+ }
+
+ public AutoFade getAutoFade() {
+ return _autoFade;
+ }
+
+ public void setAutoFadeFixedPixelSize(final int pixelSize) {
+ _fixedPixelAlphaThresh = pixelSize;
+ }
+
+ public int getAutoFadeFixedPixelSize() {
+ return _fixedPixelAlphaThresh;
+ }
+
+ /**
+ * alpha falloff factor used when FixedPixelSize or CapScreenSize is used. Can be any positive value; useful range
+ * is ~ 0-2
+ * <ul>
+ * <li>0 = transparent instantaneously
+ * <li>1 = transparent when approximately 1/2 size
+ * </ul>
+ */
+ public void setAutoFadeFalloff(final float factor) {
+ _screenSizeAlphaFalloff = factor;
+ }
+
+ /**
+ * @param nearOpaque
+ * text is completely opaque when distance between camera and text is less than this value
+ * @param farTransparent
+ * text is completely transparent when distance between camera and text is greater than this value
+ */
+ public void setAutoFadeDistanceRange(final double nearOpaque, final double farTransparent) {
+ _distanceAlphaRange.set(nearOpaque, farTransparent);
+ }
+
+ /**
+ * automatically rotate test to face the camera
+ */
+ public void setAutoRotate(final boolean doAutoTransform) {
+ _autoRotate = doAutoTransform;
+ }
+
+ public boolean getAutoRotate() {
+ return _autoRotate;
+ }
+
+ @Override
+ public synchronized void draw(final Renderer r) {
+ if (_textString.length() > 0) {
+ final Camera cam = Camera.getCurrentCamera();
+
+ if (!(_autoScale == AutoScale.Off && _autoFade == AutoFade.Off)) {
+ updateScaleAndAlpha(cam, r);
+ }
+ correctTransform(cam);
+
+ super.draw(r);
+ }
+ }
+
+ /**
+ *
+ * @param cam
+ */
+ public void correctTransform(final Camera cam) {
+ updateWorldTransform(false);
+
+ if (_autoRotate) {
+ // Billboard rotation
+ _look.set(cam.getDirection());
+ _left.set(cam.getLeft()).negateLocal();
+ _rot.fromAxes(_left, _look, cam.getUp());
+ _worldTransform.setRotation(_rot);
+ }
+ _worldTransform.setScale(_localTransform.getScale());
+ }
+
+ /**
+ * Update the text's scale
+ *
+ * @param cam
+ */
+ public void updateScaleAndAlpha(final Camera cam, final Renderer r) {
+ // get our depth distance
+ _look.set(cam.getLocation());
+ _look.negateLocal().addLocal(_worldTransform.getTranslation());
+
+ final double zDepth = cam.getDirection().dot(_look);
+ if (zDepth > cam.getFrustumFar() || zDepth < cam.getFrustumNear()) {
+ // it is out of the picture.
+ return;
+ }
+
+ // calculate the height in world units of the screen at that depth
+ final double heightAtZ;
+ if (cam.getProjectionMode() == ProjectionMode.Parallel) {
+ heightAtZ = cam.getFrustumTop();
+ } else {
+ heightAtZ = zDepth * cam.getFrustumTop() / cam.getFrustumNear();
+ }
+
+ // determine a unit/pixel ratio using height
+ final double screenHeight = cam.getHeight();
+ final double pixelRatio = heightAtZ / screenHeight;
+
+ final double capSize = 1.0 / (_fontScale * _font.getSize());
+
+ // scale value used to maintain uniform size in screen coords.
+ // when depthScale > unitFont, text is far away
+ final double depthScale = 2 * pixelRatio;
+
+ if (_autoScale != AutoScale.Off) {
+ double finalScale = depthScale;
+ if (_autoScale == AutoScale.CapScreenSize) {
+ if (finalScale > capSize) {
+ finalScale = capSize;
+ }
+ }
+ finalScale *= _fontScale;
+ setScale(finalScale, finalScale, -finalScale);
+ }
+
+ // -- adjust alpha -------
+ switch (_autoFade) {
+ case Off:
+ break;
+ case DistanceRange:
+ distanceAlphaFade(_distanceAlphaRange, _look.length());
+ break;
+ case FixedPixelSize:
+ screenSizeCapAlphaFade(1.0 / _fixedPixelAlphaThresh, depthScale, _screenSizeAlphaFalloff);
+ break;
+ case CapScreenSize:
+ screenSizeCapAlphaFade(capSize, depthScale, _screenSizeAlphaFalloff);
+ break;
+ }
+ }
+
+ /**
+ * Set transparency based on native screen size.
+ *
+ * @param capSize
+ * 1/(font point size)
+ * @param depthScale
+ * @param alphaFallof
+ */
+ protected void screenSizeCapAlphaFade(final double capSize, final double depthScale, final float alphaFallof) {
+ if (capSize < depthScale) {
+ final float unit = (float) ((depthScale - capSize) / capSize);
+ float f = alphaFallof - unit;
+ f = (f < 0) ? 0 : f / alphaFallof;
+ final float alpha = _textClr.getAlpha() * f;
+ _tempClr.set(_textClr);
+ _tempClr.setAlpha(alpha);
+ setDefaultColor(_tempClr);
+ } else {
+ setDefaultColor(_textClr);
+ }
+ }
+
+ /**
+ * Set transparency based on distance from camera to text. if (distance < range.x) then opaque, if (distance >
+ * range.y) then transparent, else lerp
+ */
+ protected void distanceAlphaFade(final ReadOnlyVector2 range, final double distance) {
+ float alpha = 1;
+ if (distance > range.getY()) {
+ alpha = 0;
+ } else if (distance > range.getX()) {
+ final float a = (float) (distance - range.getX());
+ final float r = (float) (range.getY() - range.getX());
+ alpha = 1.0f - a / r;
+ }
+ _tempClr.set(_textClr);
+ _tempClr.setAlpha(_textClr.getAlpha() * alpha);
+ setDefaultColor(_tempClr);
+ }
+
+ /** get width in world units */
+ public float getWidth() {
+ return (_size.getXf() * _worldTransform.getScale().getXf());
+ }
+
+ /** get height in world units */
+ public float getHeight() {
+ return (_size.getYf() * _worldTransform.getScale().getYf());
+ }
+
+ protected void addToLineSizes(final float sizeX, final int lineIndex) {
+ if (lineIndex >= _lineWidths.length) { // make sure array is big enough
+ final float[] newLineSizes = new float[_lineWidths.length * 2];
+ System.arraycopy(_lineWidths, 0, newLineSizes, 0, _lineWidths.length);
+ _lineWidths = newLineSizes;
+ }
+ _lineWidths[lineIndex] = sizeX;
+ }
+
+ /**
+ */
+ protected void calculateSize(final String text) {
+ _size.set(0, 0);
+
+ BMFont.Char chr;
+ float cursorX = 0;
+ float cursorY = 0;
+ final float lineHeight = _font.getLineHeight();
+ _lines = 0;
+
+ _lineWidths[0] = 0;
+ final int strLen = _textString.length();
+ for (int i = 0; i < strLen; i++) {
+ final int charVal = _textString.charAt(i);
+ if (charVal == '\n') { // newline special case
+
+ addToLineSizes(cursorX, _lines);
+ _lines++;
+ if (cursorX > _size.getX()) {
+ _size.setX(cursorX);
+ }
+ cursorX = 0;
+ cursorY = _lines * lineHeight;
+ } else if (charVal == '\t') { // tab special case
+ final float tabStop = _tabSize * _font.getMaxCharAdvance();
+ final float stops = 1 + (float) Math.floor(cursorX / tabStop);
+ cursorX = stops * tabStop;
+ } else { // normal character
+ chr = _font.getChar(charVal);
+ int nextVal = 0;
+ if (i < strLen - 1) {
+ nextVal = _textString.charAt(i + 1);
+ }
+ final int kern = _font.getKerning(charVal, nextVal);
+ cursorX += chr.xadvance + kern + _spacing;
+ }
+ }
+ addToLineSizes(cursorX, _lines);
+ if (cursorX > _size.getX()) {
+ _size.setX(cursorX);
+ }
+
+ _size.setY(cursorY + lineHeight);
+ _lines++;
+ }
+
+ /**
+ */
+ protected void calculateAlignmentOffset() {
+ _alignOffset.set(0, 0);
+ if (_align != null) {
+ _alignOffset.setX(_size.getX() * _align.horizontal);
+ _alignOffset.setY(_size.getY() * _align.vertical);
+ }
+ }
+
+ /**
+ * Check whether buffers have sufficient capacity to hold current string values; if not, increase capacity and set
+ * the limit.
+ *
+ * @param text
+ */
+ protected void checkBuffers(final String text) {
+ final int chunkSize = 20;
+ final int vertices = 6 * text.length();
+ final int chunks = 1 + (vertices / chunkSize);
+ final int required = chunks * chunkSize;
+ FloatBuffer vertexBuffer = getMeshData().getVertexBuffer();
+ FloatBuffer texCrdBuffer = getMeshData().getTextureBuffer(0);
+ if (vertexBuffer == null || vertexBuffer.capacity() < required * 3) {
+ vertexBuffer = BufferUtils.createVector3Buffer(required);
+ texCrdBuffer = BufferUtils.createVector2Buffer(required);
+ getMeshData().setVertexBuffer(vertexBuffer);
+ getMeshData().setTextureBuffer(texCrdBuffer, 0);
+ }
+ vertexBuffer.limit(vertices * 3).rewind();
+ texCrdBuffer.limit(vertices * 2).rewind();
+ }
+
+ protected float getJustificationXOffset(final int lineIndex) {
+ float cursorX = 0;
+ switch (_justify) {
+ case Left:
+ cursorX = 0;
+ break;
+ case Center:
+ cursorX = 0.5f * (_size.getXf() - _lineWidths[lineIndex]);
+ break;
+ case Right:
+ cursorX = _size.getXf() - _lineWidths[lineIndex];
+ break;
+ }
+ return cursorX;
+ }
+
+ public BMFont getFont() {
+ return _font;
+ }
+
+ public void setFont(final BMFont font) {
+ _font = font;
+ _font.applyRenderStatesTo(this, _useBlend);
+ setFontScale(_fontScale);
+ setText(_textString);
+ }
+
+ /**
+ * @param useBlend
+ * if true: use alpha blending and use transparent render bucket, else if false: alpha test only and use
+ * opaque render bucket
+ */
+ public void setUseBlend(final boolean useBlend) {
+ _useBlend = useBlend;
+ _font.applyRenderStatesTo(this, _useBlend);
+ }
+
+ public boolean getUseBlend() {
+ return _useBlend;
+ }
+
+ /**
+ * Set text string and recreate geometry
+ */
+ public synchronized void setText(final String text) {
+ if (text == null) {
+ _textString = "";
+ } else {
+ _textString = text;
+ }
+
+ checkBuffers(_textString);
+ calculateSize(_textString);
+ calculateAlignmentOffset();
+
+ final FloatBuffer vertices = getMeshData().getVertexBuffer();
+ final FloatBuffer texCrds = getMeshData().getTextureBuffer(0);
+
+ BMFont.Char chr;
+ final float txW = _font.getTextureWidth();
+ final float txH = _font.getTextureHeight();
+
+ int lineIndex = 0;
+ float cursorX = getJustificationXOffset(lineIndex);
+ float cursorY = 0;
+ final float lineHeight = _font.getLineHeight();
+ float t, b, l, r;
+
+ float alignX = _size.getXf() * _align.horizontal;
+ float alignY = _size.getYf() * _align.vertical;
+ alignX = Math.round(alignX);
+ alignY = Math.round(alignY);
+ alignX += _fixedOffset.getX();
+ alignY += _fixedOffset.getY();
+
+ final int strLen = _textString.length();
+ for (int i = 0; i < strLen; i++) {
+ final int charVal = _textString.charAt(i);
+
+ if (charVal == '\n') { // newline special case
+ lineIndex++;
+ cursorX = getJustificationXOffset(lineIndex);
+ cursorY += lineHeight;
+ addEmptyCharacter(vertices, texCrds);
+ } else if (charVal == '\t') { // tab special case
+ final float tabStop = _tabSize * _font.getMaxCharAdvance();
+ final float stops = 1 + (float) Math.floor(cursorX / tabStop);
+ cursorX = stops * tabStop;
+ addEmptyCharacter(vertices, texCrds);
+ } else { // normal character
+ chr = _font.getChar(charVal);
+
+ // -- vertices -----------------
+ l = alignX + cursorX + chr.xoffset;
+ t = alignY + cursorY + chr.yoffset;
+ r = alignX + cursorX + chr.xoffset + chr.width;
+ b = alignY + cursorY + chr.yoffset + chr.height;
+
+ vertices.put(l).put(0).put(t); // left top
+ vertices.put(l).put(0).put(b); // left bottom
+ vertices.put(r).put(0).put(t); // right top
+ vertices.put(r).put(0).put(t); // right top
+ vertices.put(l).put(0).put(b); // left bottom
+ vertices.put(r).put(0).put(b); // right bottom
+
+ // -- tex coords ----------------
+ l = chr.x / txW;
+ t = chr.y / txH;
+ r = (chr.x + chr.width) / txW;
+ b = (chr.y + chr.height) / txH;
+
+ texCrds.put(l).put(t); // left top
+ texCrds.put(l).put(b); // left bottom
+ texCrds.put(r).put(t); // right top
+ texCrds.put(r).put(t); // right top
+ texCrds.put(l).put(b); // left bottom
+ texCrds.put(r).put(b); // right bottom
+
+ int nextVal = 0;
+ if (i < strLen - 1) {
+ nextVal = _textString.charAt(i + 1);
+ }
+ final int kern = _font.getKerning(charVal, nextVal);
+ cursorX += chr.xadvance + kern + _spacing;
+ }
+ }
+ _meshData.setVertexBuffer(vertices);
+ _meshData.setTextureBuffer(texCrds, 0);
+ _meshData.setIndices(null);
+ }
+
+ // this is inefficient yet incredibly convenient
+ // used for tab and newline
+ private void addEmptyCharacter(final FloatBuffer vertices, final FloatBuffer uvs) {
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ vertices.put(0).put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ uvs.put(0).put(0);
+ }
+
+ public synchronized String getText() {
+ return _textString;
+ }
+
+ /**
+ * @param align
+ */
+ public void setAlign(final Align align) {
+ _align = align;
+ setText(_textString);
+ }
+
+ public Align getAlign() {
+ return _align;
+ }
+
+ public void setJustify(final Justify justify) {
+ _justify = justify;
+ setText(_textString);
+ }
+
+ public Justify getJustify() {
+ return _justify;
+ }
+
+ /**
+ * set a fixed offset from the alignment center of rotation IN FONT UNITS
+ */
+ public void setFixedOffset(double x, double y) {
+ x *= _font.getSize();
+ y *= _font.getSize();
+ _fixedOffset.set(x, y);
+ setText(_textString);
+ }
+
+ /**
+ * set a fixed offset from the alignment center of rotation IN FONT UNITS
+ */
+ public void setFixedOffset(final Vector2 offset) {
+ final double x = offset.getX() * _font.getSize();
+ final double y = offset.getY() * _font.getSize();
+ _fixedOffset.set(x, y);
+ setText(_textString);
+ }
+
+ public int getLineCount() {
+ return _lines;
+ }
+
+ @Override
+ public BMText makeCopy(final boolean shareGeometricData) {
+ final BMText text = (BMText) super.makeCopy(shareGeometricData);
+
+ // copy our text properties
+ text._font = _font;
+ text._textString = _textString;
+ text._fontScale = _fontScale;
+ text._autoRotate = _autoRotate;
+ text._lines = _lines;
+ text._size.set(_size);
+ System.arraycopy(_lineWidths, 0, text._lineWidths, 0, _lineWidths.length);
+
+ text._textClr.set(_textClr);
+ text._tempClr.set(_tempClr);
+
+ text._autoScale = _autoScale;
+ text._autoFade = _autoFade;
+
+ text._fixedPixelAlphaThresh = _fixedPixelAlphaThresh;
+ text._screenSizeAlphaFalloff = _screenSizeAlphaFalloff;
+ text._distanceAlphaRange.set(_distanceAlphaRange);
+ text._useBlend = _useBlend;
+
+ text._justify = _justify;
+ text._spacing = _spacing;
+
+ text._align = _align;
+ text._alignOffset.set(_alignOffset);
+ text._fixedOffset.set(_fixedOffset);
+
+ // return
+ return text;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java
new file mode 100644
index 0000000..0b96e53
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/ui/text/BasicText.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.ui.text;
+
+import java.util.logging.Logger;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.URLResourceSource;
+
+@SavableFactory(factoryMethod = "initSavable")
+public class BasicText extends BMText {
+ static Logger logger = Logger.getLogger(BasicText.class.getName());
+
+ public static BMFont DEFAULT_FONT;
+
+ public static double DEFAULT_FONT_SIZE = 24;
+
+ static {
+ try {
+ DEFAULT_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(BasicText.class,
+ "com/ardor3d/ui/text/arial-24-bold-regular.fnt")), true);
+ } catch (final Exception ex) {
+ logger.throwing(BasicText.class.getCanonicalName(), "static font init", ex);
+ }
+ }
+
+ public static BasicText initSavable() {
+ return new BasicText();
+ }
+
+ protected BasicText() {}
+
+ public BasicText(final String name, final String text, final BMFont font, final double fontSize) {
+ super(name, text, font);
+ getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ setFontScale(fontSize);
+ setAutoFade(AutoFade.Off);
+ setAutoScale(AutoScale.Off);
+ setAutoRotate(false);
+ setRotation(new Matrix3().fromAngles(-MathUtils.HALF_PI, 0, 0));
+
+ final ZBufferState zState = new ZBufferState();
+ zState.setEnabled(false);
+ zState.setWritable(false);
+ setRenderState(zState);
+
+ final CullState cState = new CullState();
+ cState.setEnabled(false);
+ setRenderState(cState);
+
+ final BlendState blend = new BlendState();
+ blend.setBlendEnabled(true);
+ blend.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blend.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ blend.setTestEnabled(true);
+ blend.setReference(0f);
+ blend.setTestFunction(BlendState.TestFunction.GreaterThan);
+ setRenderState(blend);
+
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ updateModelBound();
+ }
+
+ public static BasicText createDefaultTextLabel(final String name, final String text, final double fontSize) {
+ return new BasicText(name, text, DEFAULT_FONT, fontSize);
+ }
+
+ public static BasicText createDefaultTextLabel(final String name, final String text) {
+ return new BasicText(name, text, DEFAULT_FONT, DEFAULT_FONT_SIZE);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java
new file mode 100644
index 0000000..d851b96
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Ardor3dException.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+public class Ardor3dException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public Ardor3dException() {
+ super();
+ }
+
+ public Ardor3dException(final String desc) {
+ super(desc);
+ }
+
+ public Ardor3dException(final Throwable cause) {
+ super(cause);
+ }
+
+ public Ardor3dException(final String desc, final Throwable cause) {
+ super(desc, cause);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java
new file mode 100644
index 0000000..cc8a793
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Constants.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+/**
+ * Just a simple flag holder for runtime stripping of various ardor3d logging and debugging features.
+ */
+public class Constants {
+
+ public static boolean updateGraphs = false;
+
+ public static final boolean useStatePools;
+
+ public static final boolean stats;
+
+ public static final boolean trackDirectMemory;
+
+ public static final boolean useMultipleContexts;
+
+ public static final boolean storeSavableImages;
+
+ public static final int maxStatePoolSize;
+
+ public static final boolean useValidatingTransform;
+
+ public static final boolean enableInstancedGeometrySupport;
+
+ static {
+ boolean hasPropertyAccess = true;
+ try {
+ if (System.getSecurityManager() != null) {
+ System.getSecurityManager().checkPropertiesAccess();
+ }
+ } catch (final SecurityException e) {
+ hasPropertyAccess = false;
+ }
+
+ if (hasPropertyAccess) {
+ stats = (System.getProperty("ardor3d.stats") != null);
+ trackDirectMemory = (System.getProperty("ardor3d.trackDirect") != null);
+ useMultipleContexts = (System.getProperty("ardor3d.useMultipleContexts") != null);
+ useStatePools = (System.getProperty("ardor3d.noStatePools") == null);
+ storeSavableImages = (System.getProperty("ardor3d.storeSavableImages") != null);
+ maxStatePoolSize = (System.getProperty("ardor3d.maxStatePoolSize") != null ? Integer.parseInt(System
+ .getProperty("ardor3d.maxStatePoolSize")) : 11);
+
+ useValidatingTransform = (System.getProperty("ardor3d.disableValidatingTransform") == null);
+ enableInstancedGeometrySupport = (System.getProperty("ardor3d.enableInstancedGeometrySupport") != null);
+ } else {
+ stats = false;
+ trackDirectMemory = false;
+ useMultipleContexts = false;
+ useStatePools = true;
+ storeSavableImages = false;
+ maxStatePoolSize = 11;
+ useValidatingTransform = true;
+ enableInstancedGeometrySupport = false;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java
new file mode 100644
index 0000000..8ee7111
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextGarbageCollector.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.AbstractBufferData;
+import com.ardor3d.util.scenegraph.DisplayListDelegate;
+
+public class ContextGarbageCollector {
+
+ private ContextGarbageCollector() {}
+
+ /**
+ * Handle detecting and scheduling cleanup of OpenGL assets. This method will place delete calls on the task queue
+ * of appropriate RenderContexts when an asset such as a Texture is determined to no longer be reachable by Java.
+ *
+ * @param immediateDelete
+ * an optional Renderer to use for immediate cleanup when the asset is owned by the current context. In
+ * general this is best used in single context applications, and null is a perfectly acceptable value.
+ */
+ public static void doRuntimeCleanup(final Renderer immediateDelete) {
+ TextureManager.cleanExpiredTextures(immediateDelete, null);
+ AbstractBufferData.cleanExpiredVBOs(immediateDelete);
+ DisplayListDelegate.cleanExpiredDisplayLists(immediateDelete);
+ }
+
+ /**
+ * Handle cleanup of all open OpenGL assets. This method is meant to be used on application shutdown.
+ *
+ * @param immediateDelete
+ * an optional Renderer to use for immediate cleanup when the asset is owned by the current context. In
+ * general this is best used in single context applications, and null is a perfectly acceptable value.
+ * However, if there is more than one context or null was passed, you must have all of the contexts
+ * process at least one more (empty) frame to allow for the final gl calls to be processed.
+ */
+ public static void doFinalCleanup(final Renderer immediateDelete) {
+ TextureManager.cleanAllTextures(immediateDelete, null);
+ AbstractBufferData.cleanAllVBOs(immediateDelete);
+ DisplayListDelegate.cleanAllDisplayLists(immediateDelete);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java
new file mode 100644
index 0000000..427b6cd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ContextIdReference.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+public class ContextIdReference<T> extends PhantomReference<T> {
+
+ /**
+ * Keep a strong reference to these objects until their reference is cleared.
+ */
+ private static final List<ContextIdReference<?>> REFS = Lists.newLinkedList();
+
+ private final Map<Object, Integer> _idCache;
+ private Integer _singleContextId;
+
+ public ContextIdReference(final T reference, final ReferenceQueue<? super T> queue) {
+ super(reference, queue);
+ if (Constants.useMultipleContexts) {
+ _idCache = new MapMaker().initialCapacity(2).weakKeys().makeMap();
+ } else {
+ _idCache = null;
+ }
+ REFS.add(this);
+ }
+
+ public boolean containsKey(final Object glContext) {
+ if (Constants.useMultipleContexts) {
+ return _idCache.containsKey(glContext);
+ } else {
+ return true;
+ }
+ }
+
+ public Integer getValue(final Object glContext) {
+ if (Constants.useMultipleContexts) {
+ return _idCache.get(glContext);
+ } else {
+ return _singleContextId;
+ }
+ }
+
+ public Integer removeValue(final Object glContext) {
+ if (Constants.useMultipleContexts) {
+ return _idCache.remove(glContext);
+ } else {
+ final Integer r = _singleContextId;
+ _singleContextId = 0;
+ return r;
+ }
+ }
+
+ public void put(final Object glContext, final Integer id) {
+ if (Constants.useMultipleContexts) {
+ _idCache.put(glContext, id);
+ } else {
+ _singleContextId = id;
+ }
+ }
+
+ public Set<Object> getContextObjects() {
+ if (Constants.useMultipleContexts) {
+ return _idCache.keySet();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ _singleContextId = 0;
+ REFS.remove(this);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java
new file mode 100644
index 0000000..e4ca2a8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/DrawableCamera.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.Debugger;
+
+/**
+ * Camera with additional pssm related functionality.
+ */
+public class DrawableCamera extends Mesh {
+ private final Camera trackedCamera;
+
+ private final ColorRGBA color;
+
+ private final short pattern;
+
+ /**
+ * Instantiates a new drawable camera.
+ */
+ public DrawableCamera() {
+ this(null, new ColorRGBA(0, 1, 1, 1), (short) 0xF000);
+ }
+
+ /**
+ * Instantiates a new drawable camera.
+ *
+ * @param width
+ * the width
+ * @param height
+ * the height
+ */
+ public DrawableCamera(final Camera camera, final ColorRGBA color, final short pattern) {
+ super("DrawableCamera");
+ trackedCamera = camera;
+ this.color = color;
+ this.pattern = pattern;
+ }
+
+ @Override
+ public void draw(final Renderer r) {
+ Debugger.drawCameraFrustum(r, trackedCamera, color, pattern, true);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java
new file mode 100644
index 0000000..23833f2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ExtendedCamera.java
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyMatrix4;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.Camera;
+
+/**
+ * Camera with additional pssm related functionality.
+ */
+public class ExtendedCamera extends Camera {
+ /** The corners of the camera frustum. */
+ protected final Vector3[] _corners = new Vector3[8];
+
+ /** Temporary vector used for storing extents during corner calculations. */
+ protected final Vector3 _extents = new Vector3();
+
+ /**
+ * Instantiates a new PSSM camera.
+ */
+ public ExtendedCamera() {
+ this(0, 0); // copy later
+ }
+
+ /**
+ * Instantiates a new PSSM camera.
+ *
+ * @param width
+ * the width
+ * @param height
+ * the height
+ */
+ public ExtendedCamera(final int width, final int height) {
+ super(width, height);
+ init();
+ }
+
+ /**
+ * Instantiates a new PSSM camera.
+ *
+ * @param source
+ * the source
+ */
+ public ExtendedCamera(final Camera source) {
+ super(source);
+ init();
+ }
+
+ /**
+ * Initialize structures.
+ */
+ private void init() {
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i] = new Vector3();
+ }
+ }
+
+ /**
+ * Compress this camera's near and far frustum planes to be smaller if possible, using the given bounds as a
+ * measure.
+ *
+ * @param sceneBounds
+ * the scene bounds
+ */
+ public void pack(final BoundingVolume sceneBounds) {
+ final ReadOnlyVector3 center = sceneBounds.getCenter();
+ for (int i = 0; i < _corners.length; i++) {
+ _corners[i].set(center);
+ }
+
+ if (sceneBounds instanceof BoundingBox) {
+ final BoundingBox bbox = (BoundingBox) sceneBounds;
+ bbox.getExtent(_extents);
+ } else if (sceneBounds instanceof BoundingSphere) {
+ final BoundingSphere bsphere = (BoundingSphere) sceneBounds;
+ _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius());
+ }
+
+ _corners[0].addLocal(_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[1].addLocal(_extents.getX(), -_extents.getY(), _extents.getZ());
+ _corners[2].addLocal(_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[3].addLocal(_extents.getX(), -_extents.getY(), -_extents.getZ());
+ _corners[4].addLocal(-_extents.getX(), _extents.getY(), _extents.getZ());
+ _corners[5].addLocal(-_extents.getX(), -_extents.getY(), _extents.getZ());
+ _corners[6].addLocal(-_extents.getX(), _extents.getY(), -_extents.getZ());
+ _corners[7].addLocal(-_extents.getX(), -_extents.getY(), -_extents.getZ());
+
+ final ReadOnlyMatrix4 mvMatrix = getModelViewMatrix();
+ double optimalCameraNear = Double.MAX_VALUE;
+ double optimalCameraFar = -Double.MAX_VALUE;
+ final Vector4 position = Vector4.fetchTempInstance();
+ for (int i = 0; i < _corners.length; i++) {
+ position.set(_corners[i].getX(), _corners[i].getY(), _corners[i].getZ(), 1);
+ mvMatrix.applyPre(position, position);
+
+ optimalCameraNear = Math.min(-position.getZ(), optimalCameraNear);
+ optimalCameraFar = Math.max(-position.getZ(), optimalCameraFar);
+ }
+ Vector4.releaseTempInstance(position);
+
+ // XXX: use of getFrustumNear and getFrustumFar seems suspicious...
+ // XXX: It depends on the frustum being reset each update
+ optimalCameraNear = Math.min(Math.max(getFrustumNear(), optimalCameraNear), getFrustumFar());
+ optimalCameraFar = Math.max(optimalCameraNear, Math.min(getFrustumFar(), optimalCameraFar));
+
+ final double change = optimalCameraNear / _frustumNear;
+ setFrustumLeft(getFrustumLeft() * change);
+ setFrustumRight(getFrustumRight() * change);
+ setFrustumTop(getFrustumTop() * change);
+ setFrustumBottom(getFrustumBottom() * change);
+
+ setFrustumNear(optimalCameraNear);
+ setFrustumFar(optimalCameraFar);
+ }
+
+ public void calculateFrustum() {
+ calculateFrustum(_frustumNear, _frustumFar);
+ }
+
+ /**
+ * Calculate frustum corners and center.
+ *
+ * @param fNear
+ * the near distance
+ * @param fFar
+ * the far distance
+ */
+ public void calculateFrustum(final double fNear, final double fFar) {
+ double fNearPlaneHeight = (_frustumTop - _frustumBottom) * fNear * 0.5 / _frustumNear;
+ double fNearPlaneWidth = (_frustumRight - _frustumLeft) * fNear * 0.5 / _frustumNear;
+
+ double fFarPlaneHeight = (_frustumTop - _frustumBottom) * fFar * 0.5 / _frustumNear;
+ double fFarPlaneWidth = (_frustumRight - _frustumLeft) * fFar * 0.5 / _frustumNear;
+
+ if (getProjectionMode() == ProjectionMode.Parallel) {
+ fNearPlaneHeight = (_frustumTop - _frustumBottom) * 0.5;
+ fNearPlaneWidth = (_frustumRight - _frustumLeft) * 0.5;
+
+ fFarPlaneHeight = (_frustumTop - _frustumBottom) * 0.5;
+ fFarPlaneWidth = (_frustumRight - _frustumLeft) * 0.5;
+ }
+
+ final Vector3 vNearPlaneCenter = Vector3.fetchTempInstance();
+ final Vector3 vFarPlaneCenter = Vector3.fetchTempInstance();
+ final Vector3 direction = Vector3.fetchTempInstance();
+ final Vector3 left = Vector3.fetchTempInstance();
+ final Vector3 up = Vector3.fetchTempInstance();
+
+ direction.set(getDirection()).multiplyLocal(fNear);
+ vNearPlaneCenter.set(getLocation()).addLocal(direction);
+ direction.set(getDirection()).multiplyLocal(fFar);
+ vFarPlaneCenter.set(getLocation()).addLocal(direction);
+
+ left.set(getLeft()).multiplyLocal(fNearPlaneWidth);
+ up.set(getUp()).multiplyLocal(fNearPlaneHeight);
+ _corners[0].set(vNearPlaneCenter).subtractLocal(left).subtractLocal(up);
+ _corners[1].set(vNearPlaneCenter).subtractLocal(left).addLocal(up);
+ _corners[2].set(vNearPlaneCenter).addLocal(left).addLocal(up);
+ _corners[3].set(vNearPlaneCenter).addLocal(left).subtractLocal(up);
+
+ left.set(getLeft()).multiplyLocal(fFarPlaneWidth);
+ up.set(getUp()).multiplyLocal(fFarPlaneHeight);
+ _corners[4].set(vFarPlaneCenter).subtractLocal(left).subtractLocal(up);
+ _corners[5].set(vFarPlaneCenter).subtractLocal(left).addLocal(up);
+ _corners[6].set(vFarPlaneCenter).addLocal(left).addLocal(up);
+ _corners[7].set(vFarPlaneCenter).addLocal(left).subtractLocal(up);
+
+ Vector3.releaseTempInstance(vNearPlaneCenter);
+ Vector3.releaseTempInstance(vFarPlaneCenter);
+ Vector3.releaseTempInstance(direction);
+ Vector3.releaseTempInstance(left);
+ Vector3.releaseTempInstance(up);
+ }
+
+ public Vector3[] getCorners() {
+ return _corners;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java
new file mode 100644
index 0000000..99034b4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTask.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>GameTask</code> is used in <code>GameTaskQueue</code> to manage tasks that have yet to be accomplished.
+ */
+public class GameTask<V> implements Future<V> {
+ private static final Logger logger = Logger.getLogger(GameTask.class.getName());
+
+ private final Callable<V> callable;
+
+ private V _result;
+ private ExecutionException _exception;
+ private boolean _cancelled, _finished;
+ private final ReentrantLock _stateLock = new ReentrantLock();
+ private final Condition _finishedCondition = _stateLock.newCondition();
+
+ public GameTask(final Callable<V> callable) {
+ this.callable = callable;
+ }
+
+ public boolean cancel(final boolean mayInterruptIfRunning) {
+ // TODO mayInterruptIfRunning was ignored in previous code, should this param be removed?
+ _stateLock.lock();
+ try {
+ if (_result != null) {
+ return false;
+ }
+ _cancelled = true;
+
+ _finishedCondition.signalAll();
+
+ return true;
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+
+ public V get() throws InterruptedException, ExecutionException {
+ _stateLock.lock();
+ try {
+ while (!isDone()) {
+ _finishedCondition.await();
+ }
+ if (_exception != null) {
+ throw _exception;
+ }
+ return _result;
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+
+ public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException,
+ TimeoutException {
+ _stateLock.lock();
+ try {
+ if (!isDone()) {
+ _finishedCondition.await(timeout, unit);
+ }
+ if (_exception != null) {
+ throw _exception;
+ }
+ if (_result == null) {
+ throw new TimeoutException("Object not returned in time allocated.");
+ }
+ return _result;
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+
+ public boolean isCancelled() {
+ _stateLock.lock();
+ try {
+ return _cancelled;
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+
+ public boolean isDone() {
+ _stateLock.lock();
+ try {
+ return _finished || _cancelled || (_exception != null);
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+
+ public Callable<V> getCallable() {
+ return callable;
+ }
+
+ public void invoke() {
+ try {
+ final V tmpResult = callable.call();
+
+ _stateLock.lock();
+ try {
+ _result = tmpResult;
+ _finished = true;
+
+ _finishedCondition.signalAll();
+ } finally {
+ _stateLock.unlock();
+ }
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "invoke()", "Exception", e);
+
+ _stateLock.lock();
+ try {
+ _exception = new ExecutionException(e);
+
+ _finishedCondition.signalAll();
+ } finally {
+ _stateLock.unlock();
+ }
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java
new file mode 100644
index 0000000..3ae77d5
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueue.java
@@ -0,0 +1,162 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.RendererCallable;
+
+/**
+ * <code>GameTaskQueue</code> is a simple queuing system to enqueue tasks that need to be accomplished in a specific
+ * thread or phase of the application execution (for example, the OpenGL rendering thread.) Upon sending in a task, the
+ * caller gets back a Future object useful for retrieving a return from the Callable that was passed in.
+ *
+ * @see Future
+ * @see Callable
+ */
+public class GameTaskQueue {
+
+ public static final String RENDER = "render";
+ public static final String UPDATE = "update";
+
+ private final ConcurrentLinkedQueue<GameTask<?>> _queue = new ConcurrentLinkedQueue<GameTask<?>>();
+ private final AtomicBoolean _executeMultiple = new AtomicBoolean();
+
+ // Default execution time is 0, which means only 1 task will be executed at a time.
+ private long _executionTime = 0;
+
+ /**
+ * The state of this <code>GameTaskQueue</code> if it will execute all enqueued Callables on an execute invokation.
+ *
+ * @return boolean
+ */
+ public boolean isExecuteAll() {
+ return _executeMultiple.get();
+ }
+
+ /**
+ * @param executeMultiple
+ * if false, only one task at most is executed per call to execute. If true, we will execute as many
+ * tasks as are available, bounded by the max execution time.
+ * @see #setExecutionTime(int)
+ */
+ public void setExecuteMultiple(final boolean executeMultiple) {
+ _executeMultiple.set(executeMultiple);
+ if (executeMultiple == true) {
+ _executionTime = Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * Sets the minimum amount of time the queue will execute tasks per frame. If this is set, executeMultiple is
+ * automatically set to true and the execute() loop will execute as many tasks as it can before the execution window
+ * threshold is passed. Any remaining tasks will be executed in the following frame.
+ *
+ * @param msecs
+ * the maximum number of milliseconds to start tasks. Note that this does not guarantee the tasks will
+ * finish under this time, only start.
+ */
+ public void setExecutionTime(final int msecs) {
+ _executionTime = msecs;
+ _executeMultiple.set(true);
+ }
+
+ /**
+ * min time queue is permitted to execute tasks per frame
+ *
+ * @return -1 if executeAll is false, else min time allocated for task execution per frame
+ */
+ public long getExecutionTime() {
+ if (_executeMultiple.get() == false) {
+ return -1;
+ }
+ return _executionTime;
+ }
+
+ /**
+ * Adds the Callable to the internal queue to invoked and returns a Future that wraps the return. This is useful for
+ * checking the status of the task as well as being able to retrieve the return object from Callable asynchronously.
+ *
+ * @param <V>
+ * @param callable
+ * @return
+ */
+ public <V> Future<V> enqueue(final Callable<V> callable) {
+ final GameTask<V> task = new GameTask<V>(callable);
+ _queue.add(task);
+ return task;
+ }
+
+ /**
+ * Execute the tasks from this queue. Note that depending on the queue type, tasks may expect to be run in a certain
+ * context (for example, the Render queue expects to be run from the Thread owning a GL context.)
+ */
+ public void execute() {
+ execute(null);
+ }
+
+ /**
+ * Execute the tasks from this queue. Note that depending on the queue type, tasks may expect to be run in a certain
+ * context (for example, the Render queue expects to be run from the Thread owning a GL context.)
+ */
+ public void execute(final Renderer renderer) {
+ final long beginTime = System.currentTimeMillis();
+ long elapsedTime;
+ GameTask<?> task = _queue.poll();
+ do {
+ if (task == null) {
+ return;
+ }
+
+ // Inject the Renderer if correct type of Callable.
+ if (renderer != null && task.getCallable() instanceof RendererCallable<?>) {
+ ((RendererCallable<?>) task.getCallable()).setRenderer(renderer);
+ }
+
+ while (task.isCancelled()) {
+ task = _queue.poll();
+ if (task == null) {
+ return;
+ }
+ }
+ task.invoke();
+ elapsedTime = System.currentTimeMillis() - beginTime;
+ } while ((_executeMultiple.get()) && (elapsedTime < _executionTime) && ((task = _queue.poll()) != null));
+ }
+
+ /**
+ * Remove all tasks from this queue without executing them.
+ */
+ public void clear() {
+ _queue.clear();
+ }
+
+ /**
+ * Move the tasks from the given queue to this one.
+ *
+ * @param gameTaskQueue
+ */
+ public void enqueueAll(final GameTaskQueue queue) {
+ _queue.addAll(queue._queue);
+ queue._queue.clear();
+ }
+
+ /**
+ * @return count of tasks in queue.
+ */
+ public int size() {
+ return _queue.size();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java
new file mode 100644
index 0000000..4f57874
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/GameTaskQueueManager.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
+
+import com.google.common.collect.MapMaker;
+
+/**
+ * <code>GameTaskQueueManager</code> is just a simple Singleton class allowing easy access to task queues.
+ */
+public final class GameTaskQueueManager {
+
+ private static final Object MAP_LOCK = new Object();
+ private static final ConcurrentMap<Object, GameTaskQueueManager> _managers = new MapMaker().weakKeys().makeMap();
+
+ private final ConcurrentMap<String, GameTaskQueue> _managedQueues = new ConcurrentHashMap<String, GameTaskQueue>(2);
+
+ public static GameTaskQueueManager getManager(final Object key) {
+ synchronized (MAP_LOCK) {
+ GameTaskQueueManager manager = _managers.get(key);
+ if (manager == null) {
+ manager = new GameTaskQueueManager();
+ _managers.put(key, manager);
+ }
+ return manager;
+ }
+ }
+
+ public static GameTaskQueueManager clearManager(final Object key) {
+ return _managers.remove(key);
+ }
+
+ private GameTaskQueueManager() {
+ addQueue(GameTaskQueue.RENDER, new GameTaskQueue());
+ addQueue(GameTaskQueue.UPDATE, new GameTaskQueue());
+ }
+
+ public void addQueue(final String name, final GameTaskQueue queue) {
+ _managedQueues.put(name, queue);
+ }
+
+ public GameTaskQueue getQueue(final String name) {
+ return _managedQueues.get(name);
+ }
+
+ public void moveTasksTo(final GameTaskQueueManager manager) {
+ for (final String key : _managedQueues.keySet()) {
+ final GameTaskQueue q = manager.getQueue(key);
+ final GameTaskQueue mq = _managedQueues.get(key);
+ if (q != null && mq.size() > 0) {
+ q.enqueueAll(mq);
+ }
+ }
+ }
+
+ /**
+ * Clears all tasks from the queues managed by this manager.
+ */
+ public void clearTasks() {
+ for (final GameTaskQueue q : _managedQueues.values()) {
+ q.clear();
+ }
+ }
+
+ /**
+ * This method adds <code>callable</code> to the queue to be invoked in the update() method in the OpenGL thread.
+ * The Future returned may be utilized to cancel the task or wait for the return object.
+ *
+ * @param callable
+ * @return Future<V>
+ */
+
+ public <V> Future<V> update(final Callable<V> callable) {
+ return getQueue(GameTaskQueue.UPDATE).enqueue(callable);
+ }
+
+ /**
+ * This method adds <code>callable</code> to the queue to be invoked in the render() method in the OpenGL thread.
+ * The Future returned may be utilized to cancel the task or wait for the return object.
+ *
+ * @param callable
+ * @return Future<V>
+ */
+
+ public <V> Future<V> render(final Callable<V> callable) {
+ return getQueue(GameTaskQueue.RENDER).enqueue(callable);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java
new file mode 100644
index 0000000..c3c8ec4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianDataInput.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * LittleEndianDataInput is a class to read little-endian stored data via a InputStream. All functions work as defined
+ * in DataInput, but assume they come from a LittleEndian input stream.
+ */
+public class LittleEndianDataInput implements DataInput {
+
+ private final BufferedInputStream _stream;
+
+ /**
+ * Number of bytes to read when reading a char... For data meant to be read from C/C++ this is often 1, for Java and
+ * C# this is usually 2.
+ */
+ public int CHAR_SIZE = 2;
+
+ /**
+ * Creates a new LittleEndian reader from the given input stream. The stream is wrapped in a BufferedInputStream
+ * automatically.
+ *
+ * @param in
+ * The input stream to read from.
+ */
+ public LittleEndianDataInput(final InputStream in) {
+ _stream = new BufferedInputStream(in);
+ }
+
+ public final int readUnsignedShort() throws IOException {
+ return (_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8);
+ }
+
+ /**
+ * read an unsigned int as a long
+ */
+ public final long readUnsignedInt() throws IOException {
+ return ((_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8) | ((_stream.read() & 0xff) << 16) | (((long) (_stream
+ .read() & 0xff)) << 24));
+ }
+
+ public final boolean readBoolean() throws IOException {
+ return (_stream.read() != 0);
+ }
+
+ public final byte readByte() throws IOException {
+ return (byte) _stream.read();
+ }
+
+ public final int readUnsignedByte() throws IOException {
+ return _stream.read();
+ }
+
+ public final short readShort() throws IOException {
+ return (short) readUnsignedShort();
+ }
+
+ public final char readChar() throws IOException {
+ return (char) readUnsignedShort();
+ }
+
+ public final int readInt() throws IOException {
+ return ((_stream.read() & 0xff) | ((_stream.read() & 0xff) << 8) | ((_stream.read() & 0xff) << 16) | ((_stream
+ .read() & 0xff) << 24));
+ }
+
+ public final long readLong() throws IOException {
+ return ((_stream.read() & 0xff) | ((long) (_stream.read() & 0xff) << 8)
+ | ((long) (_stream.read() & 0xff) << 16) | ((long) (_stream.read() & 0xff) << 24)
+ | ((long) (_stream.read() & 0xff) << 32) | ((long) (_stream.read() & 0xff) << 40)
+ | ((long) (_stream.read() & 0xff) << 48) | ((long) (_stream.read() & 0xff) << 56));
+ }
+
+ public final float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public final double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ public final void readFully(final byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ public final void readFully(final byte b[], final int off, final int len) throws IOException {
+ // this may look over-complicated, but the problem is that the InputStream.read() methods are
+ // not guaranteed to fill up the buffer you pass to it. So we need to loop until we have filled
+ // up the buffer or until we reach the end of the file.
+
+ final int bytesRead = _stream.read(b, off, len);
+
+ if (bytesRead == -1) {
+ throw new EOFException("EOF reached");
+ }
+
+ if (bytesRead < len) {
+ // we didn't get all the data we wanted, so read some more
+ readFully(b, off + bytesRead, len - bytesRead);
+ }
+ }
+
+ public final int skipBytes(final int n) throws IOException {
+ return (int) _stream.skip(n);
+ }
+
+ public final String readLine() throws IOException {
+ throw new IOException("Unsupported operation");
+ }
+
+ public final String readUTF() throws IOException {
+ throw new IOException("Unsupported operation");
+ }
+
+ public final void close() throws IOException {
+ _stream.close();
+ }
+
+ public final int available() throws IOException {
+ return _stream.available();
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java
new file mode 100644
index 0000000..33ced8f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/LittleEndianRandomAccessDataInput.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.io.DataInput;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.InvalidMarkException;
+import java.nio.charset.Charset;
+
+import com.ardor3d.util.export.ByteUtils;
+
+/**
+ * Utility class useful for reading little-endian stored data in a random access fashion. All functions work as defined
+ * in DataInput, but assume they come from a LittleEndian input stream.
+ *
+ * <p>
+ * Note: random access is implemented by reading the entire stream into memory.
+ * </p>
+ */
+public class LittleEndianRandomAccessDataInput implements DataInput {
+ private final ByteBuffer _contents;
+
+ /**
+ * Number of bytes to read when reading a char... For data meant to be read from C/C++ this is often 1, for Java and
+ * C# this is usually 2.
+ */
+ public int CHAR_SIZE = 2;
+
+ /**
+ * Creates a new LittleEndian reader from the given input stream. Note that this stream is loaded completely into
+ * memory.
+ *
+ * @param in
+ * The stream to read from.
+ */
+ public LittleEndianRandomAccessDataInput(final InputStream in) throws IOException {
+ _contents = ByteBuffer.wrap(ByteUtils.getByteContent(in));
+ }
+
+ /**
+ * Creates a new LittleEndian reader from the given byte buffer. Note that this byte buffer is not cloned or copied,
+ * so take care not to alter it during read. This constructor is useful for working with memory-mapped files.
+ *
+ * @param contents
+ * The contents to read from.
+ */
+ public LittleEndianRandomAccessDataInput(final ByteBuffer contents) throws IOException {
+ _contents = contents;
+ }
+
+ public final int readUnsignedShort() throws IOException {
+ return (readByte() & 0xff) | ((readByte() & 0xff) << 8);
+ }
+
+ public final long readUnsignedInt() throws IOException {
+ return ((readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | (((long) (readByte() & 0xff)) << 24));
+ }
+
+ public final boolean readBoolean() throws IOException {
+ return (readByte() != 0);
+ }
+
+ public final byte readByte() throws IOException {
+ return _contents.get();
+ }
+
+ public final int readUnsignedByte() throws IOException {
+ return readByte() & 0xff;
+ }
+
+ public final short readShort() throws IOException {
+ return (short) readUnsignedShort();
+ }
+
+ public final char readChar() throws IOException {
+ return (char) readUnsignedShort();
+ }
+
+ public final int readInt() throws IOException {
+ return ((readByte() & 0xff) | ((readByte() & 0xff) << 8) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 24));
+ }
+
+ public final long readLong() throws IOException {
+ return ((readByte() & 0xff) | ((long) (readByte() & 0xff) << 8) | ((long) (readByte() & 0xff) << 16)
+ | ((long) (readByte() & 0xff) << 24) | ((long) (readByte() & 0xff) << 32)
+ | ((long) (readByte() & 0xff) << 40) | ((long) (readByte() & 0xff) << 48) | ((long) (readByte() & 0xff) << 56));
+ }
+
+ public final float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public final double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ public final void readFully(final byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ public final void readFully(final byte b[], final int off, final int len) throws IOException {
+ if (len - off + _contents.position() > _contents.capacity()) {
+ throw new EOFException("EOF reached");
+ } else {
+ _contents.get(b, off, len);
+ }
+ }
+
+ public final int skipBytes(final int n) throws IOException {
+ if (_contents.remaining() >= n) {
+ _contents.position(_contents.position() + n);
+ return n;
+ }
+ final int skipped = _contents.remaining();
+ _contents.position(_contents.limit());
+ return skipped;
+ }
+
+ /**
+ * Sets a mark at the current position in the underlying buffer. This position can be returned to by calling reset.
+ *
+ * @return this object
+ */
+ public final LittleEndianRandomAccessDataInput mark() {
+ _contents.mark();
+ return this;
+ }
+
+ /**
+ * Seeks to the position of the last mark. The mark is not changed or discarded.
+ *
+ * @return this object
+ * @throws InvalidMarkException
+ * if mark was not previously called.
+ */
+ public final LittleEndianRandomAccessDataInput reset() {
+ _contents.reset();
+ return this;
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @throws IOException
+ * if this method is called.
+ */
+ public final String readLine() throws IOException {
+ throw new IOException("operation unsupported.");
+ }
+
+ /**
+ * Unsupported.
+ *
+ * @throws IOException
+ * if this method is called.
+ */
+ public final String readUTF() throws IOException {
+ throw new IOException("operation unsupported.");
+ }
+
+ /**
+ * Reads a specified number of bytes to form a string. The length of the string (number of characters) is required
+ * to notify when reading should stop. The index is increased the number of characters read.
+ *
+ * @param size
+ * the length of the string to read.
+ * @param charset
+ * the charset used to convert the bytes to a string.
+ * @return the string read.
+ * @throws IOException
+ * if EOS/EOF is reached before "size" number of bytes are read.
+ */
+ public String readString(final int size, final Charset charset) throws IOException {
+ final int start = position();
+ final byte[] content = new byte[size];
+ readFully(content);
+ seek(start + size);
+ int indexOfNullByte = size;
+ // Look for zero terminated string from byte array
+ for (int i = 0; i < size; i++) {
+ if (content[i] == 0) {
+ indexOfNullByte = i;
+ break;
+ }
+ }
+ final String s = new String(content, 0, indexOfNullByte, charset);
+ return s;
+ }
+
+ /**
+ * Reads a specified number of bytes to form a string. The length of the string (number of characters) is required
+ * to notify when reading should stop. The index is increased the number of characters read. Will use the platform's
+ * default Charset to convert the bytes to string.
+ *
+ * @param size
+ * the length of the string to read.
+ * @return the string read.
+ * @throws IOException
+ * if EOS/EOF is reached before "size" number of bytes are read.
+ */
+ public String readString(final int size) throws IOException {
+ return readString(size, Charset.defaultCharset());
+ }
+
+ public final void seek(final int pos) throws IOException {
+ _contents.position(pos);
+ }
+
+ public int position() {
+ return _contents.position();
+ }
+
+ public int capacity() {
+ return _contents.capacity();
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java
new file mode 100644
index 0000000..281add4
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/ReadOnlyTimer.java
@@ -0,0 +1,59 @@
+/**
+/ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+/**
+ * <code>ReadOnlyTimer</code> is the base interface for all Ardor3D timer implementations. Used throughout Ardor3D for
+ * framerate and time dependent calculations.
+ */
+public interface ReadOnlyTimer {
+ /**
+ * Get elapsed time in seconds since this timer was created or reset.
+ *
+ * @see #getTime()
+ *
+ * @return Time in seconds
+ */
+ double getTimeInSeconds();
+
+ /**
+ * Get elapsed time since this timer was created or reset, in the resolution specified by the implementation
+ * (usually in nanoseconds).
+ *
+ * @see #getResolution()
+ * @see #getTimeInSeconds()
+ *
+ * @return Time in resolution specified by implementation
+ */
+ long getTime();
+
+ /**
+ * Get the resolution used by this timer. Nanosecond resolution would return 10^9
+ *
+ * @return Timer resolution
+ */
+ long getResolution();
+
+ /**
+ * Get the current number of frames per second (fps).
+ *
+ * @return Current frames per second (fps)
+ */
+ double getFrameRate();
+
+ /**
+ * Get the time elapsed between the latest two frames, in seconds.
+ *
+ * @return Time between frames, in seconds
+ */
+ double getTimePerFrame();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java
new file mode 100644
index 0000000..74adc79
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SimpleContextIdReference.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+public class SimpleContextIdReference<T> extends PhantomReference<T> {
+
+ /**
+ * Keep a string reference to these objects until their reference is cleared.
+ */
+ private static final List<SimpleContextIdReference<?>> REFS = Lists.newLinkedList();
+
+ private final int _id;
+ private final Object _glContext;
+
+ public SimpleContextIdReference(final T reference, final ReferenceQueue<? super T> queue, final int id,
+ final Object glContext) {
+ super(reference, queue);
+ REFS.add(this);
+ _id = id;
+ _glContext = glContext;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ REFS.remove(this);
+ }
+
+ public int getId() {
+ return _id;
+ }
+
+ public Object getGlContext() {
+ return _glContext;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java
new file mode 100644
index 0000000..da15d1b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/SortUtil.java
@@ -0,0 +1,173 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.util.Comparator;
+
+/**
+ * Shell and merge sort implementations with the goal of reducing garbage and allowing tuning.
+ */
+public abstract class SortUtil {
+
+ /**
+ * The size at or below which we will use shell sort instead of the system sort.
+ */
+ public static int SHELL_SORT_THRESHOLD = 17;
+
+ /**
+ * <p>
+ * Merge sorts the supplied data, in the given range, using the given comparator.
+ * </p>
+ * <p>
+ * <b>Note: this internally creates a temporary copy of the array to use as work space during sort.</b>
+ * </p>
+ *
+ * @param source
+ * the array to sort. Will hold the sorted array on completion.
+ * @param left
+ * the left-most index of our sort range.
+ * @param right
+ * the right-most index of our sort range.
+ * @param comp
+ * our object Comparator
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> void msort(final T[] source, final int left, final int right, final Comparator<? super T> comp) {
+ final T[] copy = (T[]) new Object[source.length];
+ System.arraycopy(source, 0, copy, 0, source.length);
+ msort(copy, source, left, right, comp);
+ }
+
+ /**
+ * <p>
+ * Merge sorts the supplied data, in the given range, using the given comparator.
+ * </p>
+ *
+ * @param sourceCopy
+ * contains the elements to be sorted and acts as a work space for the sort.
+ * @param destinationCopy
+ * contains the elements to be sorted and will hold the fully sorted array when complete.
+ * @param left
+ * the left-most index of our sort range.
+ * @param right
+ * the right-most index of our sort range.
+ * @param comp
+ * our object Comparator
+ */
+ public static <T> void msort(final T[] source, final T[] copy, final int left, final int right,
+ final Comparator<? super T> comp) {
+ // use an insertion sort on small arrays to avoid recursion down to 1:1
+ final int length = right - left + 1;
+ if (length <= SHELL_SORT_THRESHOLD) {
+ // insertion sort in place
+ shellSort(copy, left, right, comp);
+ // copy into destination
+ return;
+ }
+
+ // recursively sort each half of array
+ final int mid = (left + right) >> 1;
+ msort(copy, source, left, mid, comp);
+ msort(copy, source, mid + 1, right, comp);
+
+ // merge the sorted halves
+ merge(source, copy, left, mid, right, comp);
+ }
+
+ /**
+ * Performs a merge on two sets of data stored in source, represented by the ranges formed by [left, mid] and
+ * [mid+1, right]. Stores the result in destination.
+ *
+ * @param source
+ * our source data
+ * @param destination
+ * the array to store our result in
+ * @param left
+ * @param mid
+ * @param right
+ * @param comp
+ * our object Comparator
+ */
+ protected static <T> void merge(final T[] source, final T[] destination, final int left, final int mid,
+ final int right, final Comparator<? super T> comp) {
+ int i = left, j = mid + 1;
+
+ for (int k = left; k <= right; k++) {
+ if (i == mid + 1) {
+ destination[k] = source[j++];
+ continue;
+ } else if (j == right + 1) {
+ destination[k] = source[i++];
+ continue;
+ } else {
+ destination[k] = comp.compare(source[i], source[j]) <= 0 ? source[i++] : source[j++];
+ }
+ }
+ }
+
+ /**
+ * Performs an in-place shell sort (extension of insertion sort) of the provided data.
+ *
+ * @param array
+ * our source data
+ * @param left
+ * the left index of the range to sort
+ * @param right
+ * the right index (inclusive) of the range to sort
+ * @param comp
+ * our object Comparator
+ */
+ public static <T> void shellSort(final T[] array, final int left, final int right, final Comparator<? super T> comp) {
+ int h;
+ for (h = 1; h <= (right - 1) / 9; h = 3 * h + 1) {
+ ;
+ }
+ for (; h > 0; h /= 3) {
+ for (int i = left + h; i <= right; i++) {
+ int j = i;
+ final T val = array[i];
+ while (j >= left + h && comp.compare(val, array[j - h]) < 0) {
+ array[j] = array[j - h];
+ j -= h;
+ }
+ array[j] = val;
+ }
+ }
+ }
+
+ /**
+ * Performs an in-place shell sort (extension of insertion sort) of the provided data.
+ *
+ * @param array
+ * our source data
+ * @param left
+ * the left index of the range to sort
+ * @param right
+ * the right index (inclusive) of the range to sort
+ */
+ public static <T extends Comparable<T>> void shellSort(final T[] array, final int left, final int right) {
+ int h;
+ for (h = 1; h <= (right - 1) / 9; h = 3 * h + 1) {
+ ;
+ }
+ for (; h > 0; h /= 3) {
+ for (int i = left + h; i <= right; i++) {
+ int j = i;
+ final T val = array[i];
+ while (j >= left + h && val.compareTo(array[j - 1]) < 0) {
+ array[j] = array[j - h];
+ j -= h;
+ }
+ array[j] = val;
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java
new file mode 100644
index 0000000..a8b7bb9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureKey.java
@@ -0,0 +1,417 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.resource.ResourceSource;
+import com.google.common.collect.Lists;
+
+/**
+ * <code>TextureKey</code> provides a way for the TextureManager to cache and retrieve <code>Texture</code> objects.
+ */
+@SavableFactory(factoryMethod = "initSavable")
+final public class TextureKey implements Savable {
+
+ /** The source of the image used in this Texture. */
+ protected ResourceSource _source = null;
+
+ /** Whether we had asked the loader of the image to flip vertically. */
+ protected boolean _flipped;
+
+ /** The stored format of our image. */
+ protected TextureStoreFormat _format = TextureStoreFormat.GuessCompressedFormat;
+
+ /**
+ * An optional id, used to differentiate keys where the rest of the values are the same. RTT operations are a good
+ * example.
+ */
+ protected String _id;
+
+ /**
+ * The minification filter used on our texture (generally determines what mipmaps are created - if any - and how.)
+ */
+ protected Texture.MinificationFilter _minFilter = MinificationFilter.Trilinear;
+
+ /**
+ * In multi-context rendering, this is used to track if this texture is in need of updating. An entry in this list
+ * means yes for the given Object (usually a RenderContext's glContextRep).
+ */
+ private final List<WeakReference<Object>> _dirtyContexts;
+
+ /**
+ * In single-context rendering this is used to track if this texture is in need of updating.
+ */
+ private boolean _dirty;
+
+ /** cache of OpenGL context specific texture ids for the associated texture. */
+ protected final transient ContextIdReference<TextureKey> _idCache = new ContextIdReference<TextureKey>(this,
+ TextureManager.getRefQueue());
+
+ /** cached hashcode value. */
+ protected transient int _code = Integer.MAX_VALUE;
+
+ /** cache of texturekey objects allowing us to find an existing texture key. */
+ protected static final List<WeakReference<TextureKey>> _keyCache = Lists.newLinkedList();
+
+ private static final Integer ZERO = new Integer(0);
+
+ /** RTT code use */
+ private static AtomicInteger _uniqueTK = new AtomicInteger(Integer.MIN_VALUE);
+
+ /** DO NOT USE. FOR INTERNAL USE ONLY */
+ protected TextureKey() {
+ if (Constants.useMultipleContexts) {
+ _dirtyContexts = Lists.newArrayList();
+ } else {
+ _dirtyContexts = null;
+ }
+ }
+
+ /** DO NOT USE. FOR INTERNAL USE ONLY */
+ public static TextureKey initSavable() {
+ return new TextureKey();
+ }
+
+ public void setDirty() {
+ if (Constants.useMultipleContexts) {
+ synchronized (_dirtyContexts) {
+ _dirtyContexts.clear();
+ // grab all contexts we currently have ids for and add them all as dirty
+ for (final Object context : _idCache.getContextObjects()) {
+ final WeakReference<Object> ref = new WeakReference<Object>(context);
+ _dirtyContexts.add(ref);
+ }
+ }
+ } else {
+ _dirty = true;
+ }
+ }
+
+ public boolean isDirty(final Object glContext) {
+ if (Constants.useMultipleContexts) {
+ synchronized (_dirtyContexts) {
+ // check if we are empty...
+ if (_dirtyContexts.isEmpty()) {
+ return false;
+ } else {
+ WeakReference<Object> ref;
+ Object check;
+ // look for a matching reference
+ for (final Iterator<WeakReference<Object>> it = _dirtyContexts.iterator(); it.hasNext();) {
+ ref = it.next();
+ check = ref.get();
+ if (check == null) {
+ // found empty, clean up
+ it.remove();
+ continue;
+ }
+
+ if (check.equals(glContext)) {
+ // found match, return true
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ } else {
+ return _dirty;
+ }
+ }
+
+ public void setClean(final Object glContext) {
+ if (Constants.useMultipleContexts) {
+ synchronized (_dirtyContexts) {
+ if (!_dirtyContexts.isEmpty()) {
+ WeakReference<Object> ref;
+ Object check;
+ for (final Iterator<WeakReference<Object>> it = _dirtyContexts.iterator(); it.hasNext();) {
+ ref = it.next();
+ check = ref.get();
+ if (check != null && check.equals(glContext)) {
+ it.remove();
+ return;
+ }
+ }
+ }
+ }
+ } else {
+ _dirty = false;
+ }
+ }
+
+ /**
+ * Get a new unique TextureKey. This is meant for use by RTT and other situations where we know we are making a
+ * unique texture.
+ *
+ * @param minFilter
+ * our minification filter value.
+ * @return the new TextureKey
+ */
+ public static synchronized TextureKey getRTTKey(final MinificationFilter minFilter) {
+ int val = _uniqueTK.addAndGet(1);
+ if (val == Integer.MAX_VALUE) {
+ _uniqueTK.set(Integer.MIN_VALUE);
+ val = Integer.MIN_VALUE;
+ }
+ return getKey(null, false, TextureStoreFormat.GuessCompressedFormat, "RTT_" + val, minFilter);
+ }
+
+ public static synchronized TextureKey getKey(final ResourceSource source, final boolean flipped,
+ final TextureStoreFormat storeFormat, final Texture.MinificationFilter minFilter) {
+ return getKey(source, flipped, storeFormat, null, minFilter);
+ }
+
+ public static synchronized TextureKey getKey(final ResourceSource source, final boolean flipped,
+ final TextureStoreFormat storeFormat, final String id, final Texture.MinificationFilter minFilter) {
+ final TextureKey key = new TextureKey();
+
+ key._source = source;
+ key._flipped = flipped;
+ key._minFilter = minFilter;
+ key._format = storeFormat;
+ key._id = id;
+ key._code = Integer.MAX_VALUE;
+
+ {
+ WeakReference<TextureKey> ref;
+ TextureKey check;
+ for (final Iterator<WeakReference<TextureKey>> it = _keyCache.iterator(); it.hasNext();) {
+ ref = it.next();
+ check = ref.get();
+ if (check == null) {
+ // found empty, clean up
+ it.remove();
+ continue;
+ }
+
+ if (check.equals(key)) {
+ // found match, return
+ return check;
+ }
+ }
+ }
+
+ // not found
+ _keyCache.add(new WeakReference<TextureKey>(key));
+ return key;
+ }
+
+ public static synchronized boolean clearKey(final TextureKey key) {
+ WeakReference<TextureKey> ref;
+ TextureKey check;
+ for (final Iterator<WeakReference<TextureKey>> it = _keyCache.iterator(); it.hasNext();) {
+ ref = it.next();
+ check = ref.get();
+ if (check != null && check.equals(key)) {
+ it.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param glContext
+ * the object representing the OpenGL context a texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @return the texture id of a texture in the given context. If the texture is not found in the given context, 0 is
+ * returned.
+ */
+ public Integer getTextureIdForContext(final Object glContext) {
+ final Integer id = _idCache.getValue(glContext);
+ if (id != null) {
+ return id;
+ }
+ return ZERO;
+ }
+
+ /**
+ * @return a Set of context objects that currently reference this texture.
+ */
+ public Set<Object> getContextObjects() {
+ return _idCache.getContextObjects();
+ }
+
+ /**
+ * <p>
+ * Removes any texture id for this texture for the given OpenGL context.
+ * </p>
+ * <p>
+ * Note: This does not remove the texture from the card and is provided for use by code that does remove textures
+ * from the card.
+ * </p>
+ *
+ * @param glContext
+ * the object representing the OpenGL context this texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ */
+ public void removeFromIdCache(final Object glContext) {
+ _idCache.removeValue(glContext);
+ }
+
+ /**
+ * <p>
+ * Clears all currently associated texture ids for this texture.
+ * </p>
+ * <p>
+ * Note: This does not remove the texture from the card and is provided for use by code that does remove textures
+ * from the card.
+ * </p>
+ */
+ public void removeFromIdCache() {
+ _idCache.clear();
+ }
+
+ /**
+ * Sets the id for a texture in regards to the given OpenGL context.
+ *
+ * @param glContext
+ * the object representing the OpenGL context a texture belongs to. See
+ * {@link RenderContext#getGlContextRep()}
+ * @param textureId
+ * the texture id of a texture. To be valid, this must not be 0.
+ * @throws IllegalArgumentException
+ * if textureId is equal to 0.
+ */
+ public void setTextureIdForContext(final Object glContext, final int textureId) {
+ if (textureId == 0) {
+ throw new IllegalArgumentException("textureId must != 0");
+ }
+
+ _idCache.put(glContext, textureId);
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof TextureKey)) {
+ return false;
+ }
+
+ final TextureKey that = (TextureKey) other;
+ if (_source == null) {
+ if (that._source != null) {
+ return false;
+ }
+ } else if (!_source.equals(that._source)) {
+ return false;
+ }
+
+ if (_id == null && that._id != null) {
+ return false;
+ } else if (_id != null && !_id.equals(that._id)) {
+ return false;
+ }
+
+ if (_minFilter != that._minFilter) {
+ return false;
+ }
+
+ if (_format != that._format) {
+ return false;
+ }
+
+ if (_flipped != that._flipped) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ if (_code == Integer.MAX_VALUE) {
+ _code = 17;
+
+ _code += 31 * _code + (_source != null ? _source.hashCode() : 0);
+ _code += 31 * _code + (_id != null ? _id.hashCode() : 0);
+ _code += 31 * _code + _minFilter.hashCode();
+ _code += 31 * _code + _format.hashCode();
+ _code += 31 * _code + (_flipped ? 1 : 0);
+ }
+ return _code;
+ }
+
+ public Texture.MinificationFilter getMinificationFilter() {
+ return _minFilter;
+ }
+
+ public TextureStoreFormat getFormat() {
+ return _format;
+ }
+
+ /**
+ * @return Returns the flipped.
+ */
+ public boolean isFlipped() {
+ return _flipped;
+ }
+
+ /**
+ * @return Returns the source.
+ */
+ public ResourceSource getSource() {
+ return _source;
+ }
+
+ public String getId() {
+ return _id;
+ }
+
+ @Override
+ public String toString() {
+ final String x = "tkey: src:" + _source + " flip: " + _flipped + " code: " + hashCode() + " imageType: "
+ + _format + " id: " + _id;
+ return x;
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<? extends TextureKey> getClassTag() {
+ return this.getClass();
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_source, "source", null);
+ capsule.write(_flipped, "flipped", false);
+ capsule.write(_format, "format", TextureStoreFormat.GuessCompressedFormat);
+ capsule.write(_minFilter, "minFilter", MinificationFilter.Trilinear);
+ capsule.write(_id, "id", null);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _source = (ResourceSource) capsule.readSavable("source", null);
+ _flipped = capsule.readBoolean("flipped", false);
+ _format = capsule.readEnum("format", TextureStoreFormat.class, TextureStoreFormat.GuessCompressedFormat);
+ _minFilter = capsule.readEnum("minFilter", MinificationFilter.class, MinificationFilter.Trilinear);
+ _id = capsule.readString("id", null);
+ _code = Integer.MAX_VALUE;
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java
new file mode 100644
index 0000000..8a39852
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/TextureManager.java
@@ -0,0 +1,483 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.logging.Logger;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.image.Image;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.Texture3D;
+import com.ardor3d.image.TextureCubeMap;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.image.util.ImageLoaderUtil;
+import com.ardor3d.image.util.ImageUtils;
+import com.ardor3d.renderer.ContextCleanListener;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.RendererCallable;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.ResourceSource;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.MapMaker;
+import com.google.common.collect.Multimap;
+
+/**
+ * <code>TextureManager</code> provides static methods for building or retrieving a <code>Texture</code> object from
+ * cache.
+ */
+final public class TextureManager {
+ private static final Logger logger = Logger.getLogger(TextureManager.class.getName());
+
+ private static Map<TextureKey, Texture> _tCache = new MapMaker().weakKeys().weakValues().makeMap();
+
+ private static ReferenceQueue<TextureKey> _textureRefQueue = new ReferenceQueue<TextureKey>();
+
+ static {
+ ContextManager.addContextCleanListener(new ContextCleanListener() {
+ public void cleanForContext(final RenderContext renderContext) {
+ TextureManager.cleanAllTextures(null, renderContext, null);
+ }
+ });
+ }
+
+ private TextureManager() {}
+
+ /**
+ * Loads a texture by attempting to locate the given name using ResourceLocatorTool.
+ *
+ * @param name
+ * the name of the texture image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @param flipVertically
+ * If true, the image is flipped vertically during image loading.
+ * @return the loaded texture.
+ */
+ public static Texture load(final String name, final Texture.MinificationFilter minFilter,
+ final boolean flipVertically) {
+ return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter,
+ TextureStoreFormat.GuessNoCompressedFormat, flipVertically);
+ }
+
+ /**
+ * Loads a texture by attempting to locate the given name using ResourceLocatorTool.
+ *
+ * @param name
+ * the name of the texture image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @param format
+ * the specific format to use when storing this texture on the card.
+ * @param flipVertically
+ * If true, the image is flipped vertically during image loading.
+ * @return the loaded texture.
+ */
+ public static Texture load(final String name, final Texture.MinificationFilter minFilter,
+ final TextureStoreFormat format, final boolean flipVertically) {
+ return load(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, name), minFilter, format,
+ flipVertically);
+ }
+
+ /**
+ * Loads a texture from the given source.
+ *
+ * @param source
+ * the source of the texture image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @param flipVertically
+ * If true, the image is flipped vertically during image loading.
+ * @return the loaded texture.
+ */
+ public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter,
+ final boolean flipVertically) {
+ return load(source, minFilter, TextureStoreFormat.GuessNoCompressedFormat, flipVertically);
+ }
+
+ /**
+ * Loads a texture from the given source.
+ *
+ * @param source
+ * the source of the texture image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @param format
+ * the specific format to use when storing this texture on the card.
+ * @param flipVertically
+ * If true, the image is flipped vertically during image loading.
+ * @return the loaded texture.
+ */
+ public static Texture load(final ResourceSource source, final Texture.MinificationFilter minFilter,
+ final TextureStoreFormat format, final boolean flipVertically) {
+
+ if (null == source) {
+ logger.warning("Could not load image... source was null. defaultTexture used.");
+ return TextureState.getDefaultTexture();
+ }
+
+ final TextureKey tkey = TextureKey.getKey(source, flipVertically, format, minFilter);
+
+ return loadFromKey(tkey, null, null);
+ }
+
+ /**
+ * Creates a texture from a given Ardor3D Image object.
+ *
+ * @param image
+ * the Ardor3D image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @return the loaded texture.
+ */
+ public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter) {
+ return loadFromImage(image, minFilter, TextureStoreFormat.GuessNoCompressedFormat);
+ }
+
+ /**
+ * Creates a texture from a given Ardor3D Image object.
+ *
+ * @param image
+ * the Ardor3D image.
+ * @param minFilter
+ * the filter for the near values. Used to determine if we should generate mipmaps.
+ * @param format
+ * the specific format to use when storing this texture on the card.
+ * @return the loaded texture.
+ */
+ public static Texture loadFromImage(final Image image, final Texture.MinificationFilter minFilter,
+ final TextureStoreFormat format) {
+ final TextureKey key = TextureKey.getKey(null, false, format, "img_" + image.hashCode(), minFilter);
+ return loadFromKey(key, image, null);
+ }
+
+ /**
+ * Load a texture from the given TextureKey. If imageData is given, use that, otherwise load it using the key's
+ * source information. If store is given, populate and return that Texture object.
+ *
+ * @param tkey
+ * our texture key. Must not be null.
+ * @param imageData
+ * optional Image data. If present, this is used instead of loading from source.
+ * @param store
+ * if not null, this Texture object is populated and returned instead of a new Texture object.
+ * @return the resulting texture.
+ */
+ public static Texture loadFromKey(final TextureKey tkey, final Image imageData, final Texture store) {
+ if (tkey == null) {
+ logger.warning("TextureKey is null, cannot load");
+ return TextureState.getDefaultTexture();
+ }
+
+ Texture result = store;
+
+ // First look for the texture using the supplied key
+ final Texture cache = findCachedTexture(tkey);
+
+ if (cache != null) {
+ // look into cache.
+ if (result == null) {
+ result = cache.createSimpleClone();
+ if (result.getTextureKey() == null) {
+ result.setTextureKey(tkey);
+ }
+ return result;
+ }
+ cache.createSimpleClone(result);
+ return result;
+ }
+
+ Image img = imageData;
+ if (img == null) {
+ img = ImageLoaderUtil.loadImage(tkey.getSource(), tkey.isFlipped());
+ }
+
+ if (null == img) {
+ logger.warning("(image null) Could not load: " + tkey.getSource());
+ return TextureState.getDefaultTexture();
+ }
+
+ // Default to Texture2D
+ if (result == null) {
+ if (img.getDataSize() == 6) {
+ result = new TextureCubeMap();
+ } else if (img.getDataSize() > 1) {
+ result = new Texture3D();
+ } else {
+ result = new Texture2D();
+ }
+ }
+
+ result.setTextureKey(tkey);
+ result.setImage(img);
+ result.setMinificationFilter(tkey.getMinificationFilter());
+ result.setTextureStoreFormat(ImageUtils.getTextureStoreFormat(tkey.getFormat(), result.getImage()));
+
+ // Cache the no-context version
+ addToCache(result);
+ return result;
+ }
+
+ /**
+ * Add a given texture to the cache
+ *
+ * @param texture
+ * our texture
+ */
+ public static void addToCache(final Texture texture) {
+ if (TextureState.getDefaultTexture() == null
+ || (texture != TextureState.getDefaultTexture() && texture.getImage() != TextureState
+ .getDefaultTextureImage())) {
+ _tCache.put(texture.getTextureKey(), texture);
+ }
+ }
+
+ /**
+ * Locate a texture in the cache by key
+ *
+ * @param textureKey
+ * our key
+ * @return the texture, or null if not found.
+ */
+ public static Texture findCachedTexture(final TextureKey textureKey) {
+ return _tCache.get(textureKey);
+ }
+
+ public static Texture removeFromCache(final TextureKey tk) {
+ return _tCache.remove(tk);
+ }
+
+ /**
+ * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete
+ * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will
+ * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures
+ * that are not part of the current context, then we will queue those textures to be deleted later using the
+ * GameTaskQueueManager.
+ *
+ * @param deleter
+ * if not null, this renderer will be used to immediately delete any textures in the currently active
+ * context. All other textures will be queued to delete in their own contexts.
+ */
+ public static void cleanAllTextures(final Renderer deleter) {
+ cleanAllTextures(deleter, null);
+ }
+
+ /**
+ * Delete all textures from card. This will gather all texture ids believed to be on the card and try to delete
+ * them. If a deleter is passed in, textures that are part of the currently active context (if one is active) will
+ * be deleted immediately. If a deleter is not passed in, we do not have an active context, or we encounter textures
+ * that are not part of the current context, then we will queue those textures to be deleted later using the
+ * GameTaskQueueManager.
+ *
+ * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
+ * These objects may be used to discover when the deletion tasks have all completed.
+ *
+ * @param deleter
+ * if not null, this renderer will be used to immediately delete any textures in the currently active
+ * context. All other textures will be queued to delete in their own contexts.
+ * @param futureStore
+ * if not null, this map will be populated with any Future task handles created during cleanup.
+ */
+ public static void cleanAllTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) {
+ // gather up expired textures... these don't exist in our cache
+ Multimap<Object, Integer> idMap = gatherGCdIds();
+
+ // Walk through the cached items and gather those too.
+ for (final TextureKey key : _tCache.keySet()) {
+ // possibly lazy init
+ if (idMap == null) {
+ idMap = ArrayListMultimap.create();
+ }
+
+ if (Constants.useMultipleContexts) {
+ final Set<Object> contextObjects = key.getContextObjects();
+ for (final Object o : contextObjects) {
+ // Add id to map
+ idMap.put(o, key.getTextureIdForContext(o));
+ }
+ } else {
+ idMap.put(ContextManager.getCurrentContext().getGlContextRep(), key.getTextureIdForContext(null));
+ }
+ key.removeFromIdCache();
+ }
+
+ // delete the ids
+ if (idMap != null && !idMap.isEmpty()) {
+ handleTextureDelete(deleter, idMap, futureStore);
+ }
+ }
+
+ /**
+ * Deletes all textures from card for a specific gl context. This will gather all texture ids believed to be on the
+ * card for the given context and try to delete them. If a deleter is passed in, textures that are part of the
+ * currently active context (if one is active) will be deleted immediately. If a deleter is not passed in, we do not
+ * have an active context, or we encounter textures that are not part of the current context, then we will queue
+ * those textures to be deleted later using the GameTaskQueueManager.
+ *
+ * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
+ * These objects may be used to discover when the deletion tasks have all completed.
+ *
+ * @param deleter
+ * if not null, this renderer will be used to immediately delete any textures in the currently active
+ * context. All other textures will be queued to delete in their own contexts.
+ * @param context
+ * the context to delete for.
+ * @param futureStore
+ * if not null, this map will be populated with any Future task handles created during cleanup.
+ */
+ public static void cleanAllTextures(final Renderer deleter, final RenderContext context,
+ final Map<Object, Future<Void>> futureStore) {
+ // gather up expired textures... these don't exist in our cache
+ Multimap<Object, Integer> idMap = gatherGCdIds();
+
+ final Object glRep = context.getGlContextRep();
+ // Walk through the cached items and gather those too.
+ for (final TextureKey key : _tCache.keySet()) {
+ // possibly lazy init
+ if (idMap == null) {
+ idMap = ArrayListMultimap.create();
+ }
+
+ final Integer id = key.getTextureIdForContext(glRep);
+ if (id != 0) {
+ idMap.put(context.getGlContextRep(), id);
+ key.removeFromIdCache(glRep);
+ }
+ }
+
+ // delete the ids
+ if (!idMap.isEmpty()) {
+ handleTextureDelete(deleter, idMap, futureStore);
+ }
+ }
+
+ /**
+ * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in,
+ * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a
+ * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of
+ * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager.
+ *
+ * @param deleter
+ * if not null, this renderer will be used to immediately delete any gc'd textures in the currently
+ * active context. All other gc'd textures will be queued to delete in their own contexts.
+ */
+ public static void cleanExpiredTextures(final Renderer deleter) {
+ cleanExpiredTextures(deleter, null);
+ }
+
+ /**
+ * Delete any textures from the card that have been recently garbage collected in Java. If a deleter is passed in,
+ * gc'd textures that are part of the currently active context (if one is active) will be deleted immediately. If a
+ * deleter is not passed in, we do not have an active context, or we encounter gc'd textures that are not part of
+ * the current context, then we will queue those textures to be deleted later using the GameTaskQueueManager.
+ *
+ * If a non null map is passed into futureStore, it will be populated with Future objects for each queued context.
+ * These objects may be used to discover when the deletion tasks have all completed.
+ *
+ * @param deleter
+ * if not null, this renderer will be used to immediately delete any gc'd textures in the currently
+ * active context. All other gc'd textures will be queued to delete in their own contexts.
+ * @param futureStore
+ * if not null, this map will be populated with any Future task handles created during cleanup.
+ */
+ public static void cleanExpiredTextures(final Renderer deleter, final Map<Object, Future<Void>> futureStore) {
+ // gather up expired textures...
+ final Multimap<Object, Integer> idMap = gatherGCdIds();
+
+ // send to be deleted on next render.
+ if (idMap != null) {
+ handleTextureDelete(deleter, idMap, futureStore);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Multimap<Object, Integer> gatherGCdIds() {
+ Multimap<Object, Integer> idMap = null;
+ // Pull all expired textures from ref queue and add to an id multimap.
+ ContextIdReference<TextureKey> ref;
+ Integer id;
+ while ((ref = (ContextIdReference<TextureKey>) _textureRefQueue.poll()) != null) {
+ // lazy init
+ if (idMap == null) {
+ idMap = ArrayListMultimap.create();
+ }
+ if (Constants.useMultipleContexts) {
+ final Set<Object> contextObjects = ref.getContextObjects();
+ for (final Object o : contextObjects) {
+ id = ref.getValue(o);
+ if (id != null && id.intValue() != 0) {
+ // Add id to map
+ idMap.put(o, id);
+ }
+ }
+ } else {
+ id = ref.getValue(null);
+ if (id != null && id.intValue() != 0) {
+ idMap.put(ContextManager.getCurrentContext().getGlContextRep(), id);
+ }
+ }
+ ref.clear();
+ }
+ return idMap;
+ }
+
+ private static void handleTextureDelete(final Renderer deleter, final Multimap<Object, Integer> idMap,
+ final Map<Object, Future<Void>> futureStore) {
+ Object currentGLRef = null;
+ // Grab the current context, if any.
+ if (deleter != null && ContextManager.getCurrentContext() != null) {
+ currentGLRef = ContextManager.getCurrentContext().getGlContextRep();
+ }
+ // For each affected context...
+ for (final Object glref : idMap.keySet()) {
+ // If we have a deleter and the context is current, immediately delete
+ if (currentGLRef != null && (!Constants.useMultipleContexts || glref.equals(currentGLRef))) {
+ deleter.deleteTextureIds(idMap.get(glref));
+ }
+ // Otherwise, add a delete request to that context's render task queue.
+ else {
+ final Future<Void> future = GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref))
+ .render(new RendererCallable<Void>() {
+ public Void call() throws Exception {
+ getRenderer().deleteTextureIds(idMap.get(glref));
+ return null;
+ }
+ });
+ if (futureStore != null) {
+ futureStore.put(glref, future);
+ }
+ }
+ }
+ }
+
+ @MainThread
+ public static void preloadCache(final Renderer r) {
+ for (final Texture t : _tCache.values()) {
+ if (t == null) {
+ continue;
+ }
+ if (t.getTextureKey().getSource() != null) {
+ r.loadTexture(t, 0);
+ }
+ }
+ }
+
+ static ReferenceQueue<TextureKey> getRefQueue() {
+ return _textureRefQueue;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java
new file mode 100644
index 0000000..4f99a60
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/Timer.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+/**
+ * <code>Timer</code> is a ReadOnlyTimer implementation with nanosecond resolution.
+ */
+public class Timer implements ReadOnlyTimer {
+
+ private static final long TIMER_RESOLUTION = 1000000000L;
+ private static final double INVERSE_TIMER_RESOLUTION = 1.0 / TIMER_RESOLUTION;
+
+ private long _startTime;
+ private long _previousTime;
+ private double _tpf;
+ private double _fps;
+
+ public Timer() {
+ _startTime = System.nanoTime();
+ }
+
+ public double getTimeInSeconds() {
+ return getTime() * INVERSE_TIMER_RESOLUTION;
+ }
+
+ public long getTime() {
+ return System.nanoTime() - _startTime;
+ }
+
+ public long getResolution() {
+ return TIMER_RESOLUTION;
+ }
+
+ public double getFrameRate() {
+ return _fps;
+ }
+
+ public double getTimePerFrame() {
+ return _tpf;
+ }
+
+ /**
+ * Update should be called once per frame to correctly update "time per frame" and "frame rate (fps)"
+ */
+ public void update() {
+ final long time = getTime();
+ _tpf = (time - _previousTime) * INVERSE_TIMER_RESOLUTION;
+ _fps = 1.0 / _tpf;
+ _previousTime = time;
+ }
+
+ /**
+ * Reset this timer, so that {@link #getTime()} and {@link #getTimeInSeconds()} reflects the time spend from this
+ * call.
+ */
+ public void reset() {
+ _startTime = System.nanoTime();
+ _previousTime = getTime();
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java
new file mode 100644
index 0000000..d504892
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/UrlUtils.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+public class UrlUtils {
+
+ /**
+ * Create a new URL by resolving a given relative string (such as "mydir/myfile.ext") against a given url (such as
+ * "http://www.company.com/mycontent/content.html"). This method is necessary because new URL(URL, String) does not
+ * handle primaryUrls that contain %2F instead of a slash.
+ *
+ * @param primaryUrl
+ * the primary or base URL.
+ * @param relativeLoc
+ * a String representing a relative file or path to resolve against the primary URL.
+ * @return the resolved URL.
+ * @throws MalformedURLException
+ * if we are unable to create the URL.
+ */
+ public static URL resolveRelativeURL(final URL primaryUrl, final String relativeLoc) throws MalformedURLException {
+ // Because URL(base, string) does not handle correctly URLs that have %2F, we have to manually replace these.
+ // So, we grab the URL as a string
+ String url = primaryUrl.toString();
+
+ // Replace any %2F (or %2f) with forward slashes
+ url = url.replaceAll("\\%2[F,f]", "/");
+
+ // And make our new URL
+ return new URL(new URL(url), relativeLoc);
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java
new file mode 100644
index 0000000..d76d5fa
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassField.java
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+public class BinaryClassField {
+
+ public static final byte BYTE = 0;
+ public static final byte BYTE_1D = 1;
+ public static final byte BYTE_2D = 2;
+
+ public static final byte INT = 10;
+ public static final byte INT_1D = 11;
+ public static final byte INT_2D = 12;
+
+ public static final byte FLOAT = 20;
+ public static final byte FLOAT_1D = 21;
+ public static final byte FLOAT_2D = 22;
+
+ public static final byte DOUBLE = 30;
+ public static final byte DOUBLE_1D = 31;
+ public static final byte DOUBLE_2D = 32;
+
+ public static final byte LONG = 40;
+ public static final byte LONG_1D = 41;
+ public static final byte LONG_2D = 42;
+
+ public static final byte SHORT = 50;
+ public static final byte SHORT_1D = 51;
+ public static final byte SHORT_2D = 52;
+
+ public static final byte BOOLEAN = 60;
+ public static final byte BOOLEAN_1D = 61;
+ public static final byte BOOLEAN_2D = 62;
+
+ public static final byte STRING = 70;
+ public static final byte STRING_1D = 71;
+ public static final byte STRING_2D = 72;
+
+ public static final byte BITSET = 80;
+
+ public static final byte SAVABLE = 90;
+ public static final byte SAVABLE_1D = 91;
+ public static final byte SAVABLE_2D = 92;
+
+ public static final byte SAVABLE_ARRAYLIST = 100;
+ public static final byte SAVABLE_ARRAYLIST_1D = 101;
+ public static final byte SAVABLE_ARRAYLIST_2D = 102;
+
+ public static final byte SAVABLE_MAP = 105;
+ public static final byte STRING_SAVABLE_MAP = 106;
+
+ public static final byte FLOATBUFFER_ARRAYLIST = 110;
+ public static final byte BYTEBUFFER_ARRAYLIST = 111;
+
+ public static final byte FLOATBUFFER = 120;
+ public static final byte INTBUFFER = 121;
+ public static final byte BYTEBUFFER = 122;
+ public static final byte SHORTBUFFER = 123;
+
+ public byte _type;
+ public String _name;
+ public byte _alias;
+
+ public BinaryClassField(final String name, final byte alias, final byte type) {
+ _name = name;
+ _alias = alias;
+ _type = type;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java
new file mode 100644
index 0000000..06e2595
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryClassObject.java
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.util.HashMap;
+
+public class BinaryClassObject {
+
+ // When exporting, use nameFields field, importing use aliasFields.
+ public HashMap<String, BinaryClassField> _nameFields;
+ public HashMap<Byte, BinaryClassField> _aliasFields;
+
+ public byte[] _alias;
+ public String _className;
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java
new file mode 100644
index 0000000..c2fbc08
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryCloner.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Simple utility class that uses BinaryImporter/Exporter in memory to clone a spatial.
+ */
+public class BinaryCloner {
+ @SuppressWarnings("unchecked")
+ public <T extends Savable> T copy(final T source) {
+ try {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ final BinaryExporter exporter = new BinaryExporter();
+ exporter.save(source, bos);
+ bos.flush();
+ final ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ final BinaryImporter importer = new BinaryImporter();
+ return (T) importer.load(bis);
+ } catch (final IOException ex) {
+ // should not happen, since we are dealing with only byte array streams.
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java
new file mode 100644
index 0000000..1c3359c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryExporter.java
@@ -0,0 +1,357 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.Deflater;
+import java.util.zip.GZIPOutputStream;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.util.export.Ardor3dExporter;
+import com.ardor3d.util.export.ByteUtils;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * Exports to the ardor3d Binary Format. Format descriptor: (each numbered item denotes a series of bytes that follows
+ * sequentially one after the next.)
+ * <p>
+ * 1. "number of classes" - four bytes - int value representing the number of entries in the class lookup table.
+ * </p>
+ * <p>
+ * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9, where X = the number read in 1.
+ * </p>
+ * <p>
+ * 2. "class alias" - 1...X bytes, where X = ((int) MathUtils.log(aliasCount, 256) + 1) - an alias used when writing
+ * object data to match an object to its appropriate object class type.
+ * </p>
+ * <p>
+ * 3. "full class name size" - four bytes - int value representing number of bytes to read in for next field.
+ * </p>
+ * <p>
+ * 4. "full class name" - 1...X bytes representing a String value, where X = the number read in 3. The String is the
+ * fully qualified class name of the Savable class, eg "<code>com.ardor3d.math.Vector3</code>"
+ * </p>
+ * <p>
+ * 5. "number of fields" - four bytes - int value representing number of blocks to read in next (numbers 6 - 9), where
+ * each block represents a field in this class.
+ * </p>
+ * <p>
+ * 6. "field alias" - 1 byte - the alias used when writing out fields in a class. Because it is a single byte, a single
+ * class can not save out more than a total of 256 fields.
+ * </p>
+ * <p>
+ * 7. "field type" - 1 byte - a value representing the type of data a field contains. This value is taken from the
+ * static fields of <code>com.ardor3d.util.export.binary.BinaryClassField</code>.
+ * </p>
+ * <p>
+ * 8. "field name size" - 4 bytes - int value representing the size of the next field.
+ * </p>
+ * <p>
+ * 9. "field name" - 1...X bytes representing a String value, where X = the number read in 8. The String is the full
+ * String value used when writing the current field.
+ * </p>
+ * <p>
+ * 10. "number of unique objects" - four bytes - int value representing the number of data entries in this file.
+ * </p>
+ * <p>
+ * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and 12, where X = the number read in 10.
+ * </p>
+ * <p>
+ * 11. "data id" - four bytes - int value identifying a single unique object that was saved in this data file.
+ * </p>
+ * <p>
+ * 12. "data location" - four bytes - int value representing the offset in the object data portion of this file where
+ * the object identified in 11 is located.
+ * </p>
+ * <p>
+ * 13. "future use" - four bytes - hardcoded int value 1.
+ * </p>
+ * <p>
+ * 14. "root id" - four bytes - int value identifying the top level object.
+ * </p>
+ * <p>
+ * OBJECT DATA SECTION: There will be X blocks each consisting of numbers 15 thru 19, where X = the number of unique
+ * location values named in 12.
+ * <p>
+ * 15. "class alias" - see 2.
+ * </p>
+ * <p>
+ * 16. "data length" - four bytes - int value representing the length in bytes of data stored in fields 17 and 18 for
+ * this object.
+ * </p>
+ * <p>
+ * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19
+ * </p>
+ * <p>
+ * 17. "field alias" - see 6.
+ * </p>
+ * <p>
+ * 18. "field data" - 1...X bytes representing the field data. The data length is dependent on the field type and
+ * contents.
+ * </p>
+ */
+
+public class BinaryExporter implements Ardor3dExporter {
+ private static final Logger logger = Logger.getLogger(BinaryExporter.class.getName());
+
+ /**
+ * The default compression level to use during output. Defaults to Deflater.BEST_COMPRESSION.
+ */
+ public static int DEFAULT_COMPRESSION = Deflater.BEST_COMPRESSION;
+
+ protected final int _compression;
+
+ protected int _aliasCount = 1;
+ protected int _idCount = 1;
+
+ protected final Map<Savable, BinaryIdContentPair> _contentTable = Maps.newIdentityHashMap();
+
+ protected final Map<Integer, Integer> _locationTable = Maps.newHashMap();
+
+ // key - class name, value = bco
+ protected final Map<String, BinaryClassObject> _classes = Maps.newHashMap();
+
+ protected final List<Savable> _contentKeys = Lists.newArrayList();
+
+ public BinaryExporter() {
+ this(DEFAULT_COMPRESSION);
+ }
+
+ /**
+ * Construct a new exporter, specifying some options.
+ *
+ * @param compression
+ * the compression type to use. One of the constants from {@link java.util.zip.Deflater}
+ */
+ public BinaryExporter(final int compression) {
+ _compression = compression;
+ }
+
+ public void save(final Savable object, final OutputStream os) throws IOException {
+ try {
+ GZIPOutputStream zos = new GZIPOutputStream(os) {
+ {
+ def.setLevel(_compression);
+ }
+ };
+ final int id = processBinarySavable(object);
+
+ // write out tag table
+ int ttbytes = 0;
+ final int classNum = _classes.keySet().size();
+ final int aliasWidth = ((int) MathUtils.log(classNum, 256) + 1); // make all
+ // aliases a
+ // fixed width
+ zos.write(ByteUtils.convertToBytes(classNum));
+ for (final String key : _classes.keySet()) {
+ final BinaryClassObject bco = _classes.get(key);
+
+ // write alias
+ final byte[] aliasBytes = fixClassAlias(bco._alias, aliasWidth);
+ zos.write(aliasBytes);
+ ttbytes += aliasWidth;
+
+ // write classname size & classname
+ final byte[] classBytes = key.getBytes();
+ zos.write(ByteUtils.convertToBytes(classBytes.length));
+ zos.write(classBytes);
+ ttbytes += 4 + classBytes.length;
+
+ zos.write(ByteUtils.convertToBytes(bco._nameFields.size()));
+
+ for (final String fieldName : bco._nameFields.keySet()) {
+ final BinaryClassField bcf = bco._nameFields.get(fieldName);
+ zos.write(bcf._alias);
+ zos.write(bcf._type);
+
+ // write classname size & classname
+ final byte[] fNameBytes = fieldName.getBytes();
+ zos.write(ByteUtils.convertToBytes(fNameBytes.length));
+ zos.write(fNameBytes);
+ ttbytes += 2 + 4 + fNameBytes.length;
+ }
+ }
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ // write out data to a seperate stream
+ int location = 0;
+ // keep track of location for each piece
+ final HashMap<String, List<BinaryIdContentPair>> alreadySaved = new HashMap<String, List<BinaryIdContentPair>>(
+ _contentTable.size());
+ for (final Savable savable : _contentKeys) {
+ // look back at previous written data for matches
+ final String savableName = savable.getClassTag().getName();
+ final BinaryIdContentPair pair = _contentTable.get(savable);
+ List<BinaryIdContentPair> bucket = alreadySaved.get(savableName + getChunk(pair));
+ final int prevLoc = findPrevMatch(pair, bucket);
+ if (prevLoc != -1) {
+ _locationTable.put(pair.getId(), prevLoc);
+ continue;
+ }
+
+ _locationTable.put(pair.getId(), location);
+ if (bucket == null) {
+ bucket = new ArrayList<BinaryIdContentPair>();
+ alreadySaved.put(savableName + getChunk(pair), bucket);
+ }
+ bucket.add(pair);
+ final byte[] aliasBytes = fixClassAlias(_classes.get(savableName)._alias, aliasWidth);
+ out.write(aliasBytes);
+ location += aliasWidth;
+ final BinaryOutputCapsule cap = _contentTable.get(savable).getContent();
+ out.write(ByteUtils.convertToBytes(cap._bytes.length));
+ location += 4; // length of bytes
+ out.write(cap._bytes);
+ location += cap._bytes.length;
+ }
+
+ // write out location table
+ // tag/location
+ final int locNum = _locationTable.keySet().size();
+ zos.write(ByteUtils.convertToBytes(locNum));
+ int locbytes = 0;
+ for (final Integer key : _locationTable.keySet()) {
+ zos.write(ByteUtils.convertToBytes(key));
+ zos.write(ByteUtils.convertToBytes(_locationTable.get(key)));
+ locbytes += 8;
+ }
+
+ // write out number of root ids - hardcoded 1 for now
+ zos.write(ByteUtils.convertToBytes(1));
+
+ // write out root id
+ zos.write(ByteUtils.convertToBytes(id));
+
+ // append stream to the output stream
+ out.writeTo(zos);
+
+ zos.finish();
+
+ out = null;
+ zos = null;
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("Stats:");
+ logger.fine("classes: " + classNum);
+ logger.fine("class table: " + ttbytes + " bytes");
+ logger.fine("objects: " + locNum);
+ logger.fine("location table: " + locbytes + " bytes");
+ logger.fine("data: " + location + " bytes");
+ }
+ } finally {
+ _aliasCount = 1;
+ _idCount = 1;
+
+ _contentTable.clear();
+ _locationTable.clear();
+ _classes.clear();
+ _contentKeys.clear();
+ }
+ }
+
+ protected String getChunk(final BinaryIdContentPair pair) {
+ return new String(pair.getContent()._bytes, 0, Math.min(64, pair.getContent()._bytes.length));
+ }
+
+ protected int findPrevMatch(final BinaryIdContentPair oldPair, final List<BinaryIdContentPair> bucket) {
+ if (bucket == null) {
+ return -1;
+ }
+ for (int x = bucket.size(); --x >= 0;) {
+ final BinaryIdContentPair pair = bucket.get(x);
+ if (pair.getContent().equals(oldPair.getContent())) {
+ return _locationTable.get(pair.getId());
+ }
+ }
+ return -1;
+ }
+
+ protected byte[] fixClassAlias(final byte[] bytes, final int width) {
+ if (bytes.length != width) {
+ final byte[] newAlias = new byte[width];
+ for (int x = width - bytes.length; x < width; x++) {
+ newAlias[x] = bytes[x - bytes.length];
+ }
+ return newAlias;
+ }
+ return bytes;
+ }
+
+ public void save(final Savable object, final File file) throws IOException {
+ final File parentDirectory = file.getParentFile();
+ if (parentDirectory != null && !parentDirectory.exists()) {
+ parentDirectory.mkdirs();
+ }
+
+ final FileOutputStream fos = new FileOutputStream(file);
+ save(object, fos);
+ fos.close();
+ }
+
+ public int processBinarySavable(final Savable object) throws IOException {
+ if (object == null) {
+ return -1;
+ }
+ BinaryClassObject bco = _classes.get(object.getClassTag().getName());
+ // is this class been looked at before? in tagTable?
+ if (bco == null) {
+ bco = new BinaryClassObject();
+ bco._alias = generateTag();
+ bco._nameFields = new HashMap<String, BinaryClassField>();
+ _classes.put(object.getClassTag().getName(), bco);
+ }
+
+ // is object in contentTable?
+ if (_contentTable.get(object) != null) {
+ return (_contentTable.get(object).getId());
+ }
+ final BinaryIdContentPair newPair = generateIdContentPair(bco);
+ final BinaryIdContentPair old = _contentTable.put(object, newPair);
+ if (old == null) {
+ _contentKeys.add(object);
+ }
+ object.write(_contentTable.get(object).getContent());
+ newPair.getContent().finish();
+ return newPair.getId();
+
+ }
+
+ protected byte[] generateTag() {
+ final int width = ((int) MathUtils.log(_aliasCount, 256) + 1);
+ int count = _aliasCount;
+ _aliasCount++;
+ final byte[] bytes = new byte[width];
+ for (int x = width - 1; x >= 0; x--) {
+ final int pow = (int) Math.pow(256, x);
+ final int factor = count / pow;
+ bytes[width - x - 1] = (byte) factor;
+ count %= pow;
+ }
+ return bytes;
+ }
+
+ protected BinaryIdContentPair generateIdContentPair(final BinaryClassObject bco) {
+ final BinaryIdContentPair pair = new BinaryIdContentPair(_idCount++, new BinaryOutputCapsule(this, bco));
+ return pair;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java
new file mode 100644
index 0000000..228f314
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryIdContentPair.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+public class BinaryIdContentPair {
+ private int _id;
+ private BinaryOutputCapsule _content;
+
+ public BinaryIdContentPair(final int id, final BinaryOutputCapsule content) {
+ _id = id;
+ _content = content;
+ }
+
+ public BinaryOutputCapsule getContent() {
+ return _content;
+ }
+
+ public void setContent(final BinaryOutputCapsule content) {
+ _content = content;
+ }
+
+ public int getId() {
+ return _id;
+ }
+
+ public void setId(final int id) {
+ _id = id;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java
new file mode 100644
index 0000000..6e2c847
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryImporter.java
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.export.Ardor3dImporter;
+import com.ardor3d.util.export.ByteUtils;
+import com.ardor3d.util.export.ReadListener;
+import com.ardor3d.util.export.Savable;
+import com.google.common.collect.Maps;
+
+public class BinaryImporter implements Ardor3dImporter {
+ private static final Logger logger = Logger.getLogger(BinaryImporter.class.getName());
+
+ // Key - alias, object - bco
+ protected final Map<String, BinaryClassObject> _classes = Maps.newHashMap();
+ // Key - id, object - the savable
+ protected final Map<Integer, Savable> _contentTable = Maps.newHashMap();
+ // Key - savable, object - capsule
+ protected final Map<Savable, BinaryInputCapsule> _capsuleTable = Maps.newIdentityHashMap();
+ // Key - id, opject - location in the file
+ protected final Map<Integer, Integer> _locationTable = Maps.newHashMap();
+
+ protected byte[] _dataArray = null;
+ protected int _aliasWidth = 0;
+
+ public BinaryImporter() {}
+
+ public Savable load(final InputStream is) throws IOException {
+ return load(is, null, null);
+ }
+
+ public Savable load(final InputStream is, final ReadListener listener) throws IOException {
+ return load(is, listener, null);
+ }
+
+ public Savable load(final InputStream is, final ReadListener listener, final ByteArrayOutputStream reuseableStream)
+ throws IOException {
+ try {
+ final GZIPInputStream zis = new GZIPInputStream(is);
+ BufferedInputStream bis = new BufferedInputStream(zis);
+ final int numClasses = ByteUtils.readInt(bis);
+ int bytes = 4;
+ _aliasWidth = ((int) MathUtils.log(numClasses, 256) + 1);
+ for (int i = 0; i < numClasses; i++) {
+ final String alias = readString(bis, _aliasWidth);
+
+ final int classLength = ByteUtils.readInt(bis);
+ final String className = readString(bis, classLength);
+ final BinaryClassObject bco = new BinaryClassObject();
+ bco._alias = alias.getBytes();
+ bco._className = className;
+
+ final int fields = ByteUtils.readInt(bis);
+ bytes += (8 + _aliasWidth + classLength);
+
+ bco._nameFields = new HashMap<String, BinaryClassField>(fields);
+ bco._aliasFields = new HashMap<Byte, BinaryClassField>(fields);
+ for (int x = 0; x < fields; x++) {
+ final byte fieldAlias = (byte) bis.read();
+ final byte fieldType = (byte) bis.read();
+
+ final int fieldNameLength = ByteUtils.readInt(bis);
+ final String fieldName = readString(bis, fieldNameLength);
+ final BinaryClassField bcf = new BinaryClassField(fieldName, fieldAlias, fieldType);
+ bco._nameFields.put(fieldName, bcf);
+ bco._aliasFields.put(fieldAlias, bcf);
+ bytes += (6 + fieldNameLength);
+ }
+ _classes.put(alias, bco);
+ }
+ if (listener != null) {
+ listener.readBytes(bytes);
+ }
+
+ final int numLocs = ByteUtils.readInt(bis);
+ bytes = 4;
+
+ for (int i = 0; i < numLocs; i++) {
+ final int id = ByteUtils.readInt(bis);
+ final int loc = ByteUtils.readInt(bis);
+ _locationTable.put(id, loc);
+ bytes += 8;
+ }
+
+ @SuppressWarnings("unused")
+ final int numbIDs = ByteUtils.readInt(bis); // XXX: NOT CURRENTLY USED
+ final int id = ByteUtils.readInt(bis);
+ bytes += 8;
+ if (listener != null) {
+ listener.readBytes(bytes);
+ }
+
+ ByteArrayOutputStream baos = reuseableStream;
+ if (baos == null) {
+ baos = new ByteArrayOutputStream(bytes);
+ } else {
+ baos.reset();
+ }
+ int size = -1;
+ final byte[] cache = new byte[4096];
+ while ((size = bis.read(cache)) != -1) {
+ baos.write(cache, 0, size);
+ if (listener != null) {
+ listener.readBytes(size);
+ }
+ }
+ bis = null;
+
+ _dataArray = baos.toByteArray();
+ baos = null;
+
+ final Savable rVal = readObject(id);
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("Importer Stats: ");
+ logger.fine("Tags: " + numClasses);
+ logger.fine("Objects: " + numLocs);
+ logger.fine("Data Size: " + _dataArray.length);
+ }
+ return rVal;
+
+ } finally {
+ // Let go of / reset contents.
+ _aliasWidth = 0;
+ _contentTable.clear();
+ _classes.clear();
+ _capsuleTable.clear();
+ _locationTable.clear();
+ _dataArray = null;
+ }
+ }
+
+ public Savable load(final URL url) throws IOException {
+ return load(url, null);
+ }
+
+ public Savable load(final URL url, final ReadListener listener) throws IOException {
+ final InputStream is = url.openStream();
+ final Savable rVal = load(is, listener);
+ is.close();
+ return rVal;
+ }
+
+ public Savable load(final File file) throws IOException {
+ return load(file, null);
+ }
+
+ public Savable load(final File file, final ReadListener listener) throws IOException {
+ final FileInputStream fis = new FileInputStream(file);
+ final Savable rVal = load(fis, listener);
+ fis.close();
+ return rVal;
+ }
+
+ public Savable load(final byte[] data) throws IOException {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ final Savable rVal = load(bais);
+ bais.close();
+ return rVal;
+ }
+
+ protected String readString(final InputStream is, final int length) throws IOException {
+ final byte[] data = new byte[length];
+ is.read(data, 0, length);
+ return new String(data);
+ }
+
+ protected String readString(final int length, final int offset) throws IOException {
+ final byte[] data = new byte[length];
+ for (int j = 0; j < length; j++) {
+ data[j] = _dataArray[j + offset];
+ }
+
+ return new String(data);
+ }
+
+ public Savable readObject(final int id) {
+
+ if (_contentTable.get(id) != null) {
+ return _contentTable.get(id);
+ }
+
+ try {
+ int loc = _locationTable.get(id);
+
+ final String alias = readString(_aliasWidth, loc);
+ loc += _aliasWidth;
+
+ final BinaryClassObject bco = _classes.get(alias);
+
+ if (bco == null) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int id)", "NULL class object: "
+ + alias);
+ return null;
+ }
+
+ final int dataLength = ByteUtils.convertIntFromBytes(_dataArray, loc);
+ loc += 4;
+
+ final BinaryInputCapsule cap = new BinaryInputCapsule(this, bco);
+ cap.setContent(_dataArray, loc, loc + dataLength);
+
+ final Savable out;
+
+ try {
+ @SuppressWarnings("unchecked")
+ final Class<? extends Savable> clazz = (Class<? extends Savable>) Class.forName(bco._className);
+ final SavableFactory ann = clazz.getAnnotation(SavableFactory.class);
+ if (ann == null) {
+ out = clazz.newInstance();
+ } else {
+ out = (Savable) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null,
+ (Object[]) null);
+ }
+ } catch (final InstantiationException e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int)",
+ "Could not access constructor of class '" + bco._className + "'! \n"
+ + "Some types may require the annotation SavableFactory. Please double check.", e);
+ throw new Ardor3dException(e);
+ } catch (final NoSuchMethodException e) {
+ logger
+ .logp(
+ Level.SEVERE,
+ this.getClass().toString(),
+ "readObject(int)",
+ e.getMessage()
+ + " \n"
+ + "Method specified in annotation does not appear to exist or has an invalid method signature.",
+ e);
+ throw new Ardor3dException(e);
+ }
+
+ _capsuleTable.put(out, cap);
+ _contentTable.put(id, out);
+
+ out.read(_capsuleTable.get(out));
+
+ _capsuleTable.remove(out);
+
+ return out;
+
+ } catch (final Ardor3dException e) {
+ // rethrow our own exceptions
+ throw e;
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "readObject(int)", "Exception", e);
+ throw new Ardor3dException(e);
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java
new file mode 100644
index 0000000..ade60ff
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryInputCapsule.java
@@ -0,0 +1,1417 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.export.ByteUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class BinaryInputCapsule implements InputCapsule {
+ private static final Logger logger = Logger.getLogger(BinaryInputCapsule.class.getName());
+
+ protected BinaryImporter _importer;
+ protected BinaryClassObject _cObj;
+ protected HashMap<Byte, Object> _fieldData;
+
+ protected int _index = 0;
+
+ public BinaryInputCapsule(final BinaryImporter importer, final BinaryClassObject bco) {
+ _importer = importer;
+ _cObj = bco;
+ }
+
+ public void setContent(final byte[] content, final int start, final int limit) {
+ _fieldData = new HashMap<Byte, Object>();
+ for (_index = start; _index < limit;) {
+ final byte alias = content[_index];
+
+ _index++;
+
+ try {
+ final byte type = _cObj._aliasFields.get(alias)._type;
+ Object value = null;
+
+ switch (type) {
+ case BinaryClassField.BITSET: {
+ value = readBitSet(content);
+ break;
+ }
+ case BinaryClassField.BOOLEAN: {
+ value = readBoolean(content);
+ break;
+ }
+ case BinaryClassField.BOOLEAN_1D: {
+ value = readBooleanArray(content);
+ break;
+ }
+ case BinaryClassField.BOOLEAN_2D: {
+ value = readBooleanArray2D(content);
+ break;
+ }
+ case BinaryClassField.BYTE: {
+ value = readByte(content);
+ break;
+ }
+ case BinaryClassField.BYTE_1D: {
+ value = readByteArray(content);
+ break;
+ }
+ case BinaryClassField.BYTE_2D: {
+ value = readByteArray2D(content);
+ break;
+ }
+ case BinaryClassField.BYTEBUFFER: {
+ value = readByteBuffer(content);
+ break;
+ }
+ case BinaryClassField.DOUBLE: {
+ value = readDouble(content);
+ break;
+ }
+ case BinaryClassField.DOUBLE_1D: {
+ value = readDoubleArray(content);
+ break;
+ }
+ case BinaryClassField.DOUBLE_2D: {
+ value = readDoubleArray2D(content);
+ break;
+ }
+ case BinaryClassField.FLOAT: {
+ value = readFloat(content);
+ break;
+ }
+ case BinaryClassField.FLOAT_1D: {
+ value = readFloatArray(content);
+ break;
+ }
+ case BinaryClassField.FLOAT_2D: {
+ value = readFloatArray2D(content);
+ break;
+ }
+ case BinaryClassField.FLOATBUFFER: {
+ value = readFloatBuffer(content);
+ break;
+ }
+ case BinaryClassField.FLOATBUFFER_ARRAYLIST: {
+ value = readFloatBufferArrayList(content);
+ break;
+ }
+ case BinaryClassField.BYTEBUFFER_ARRAYLIST: {
+ value = readByteBufferArrayList(content);
+ break;
+ }
+ case BinaryClassField.INT: {
+ value = readInt(content);
+ break;
+ }
+ case BinaryClassField.INT_1D: {
+ value = readIntArray(content);
+ break;
+ }
+ case BinaryClassField.INT_2D: {
+ value = readIntArray2D(content);
+ break;
+ }
+ case BinaryClassField.INTBUFFER: {
+ value = readIntBuffer(content);
+ break;
+ }
+ case BinaryClassField.LONG: {
+ value = readLong(content);
+ break;
+ }
+ case BinaryClassField.LONG_1D: {
+ value = readLongArray(content);
+ break;
+ }
+ case BinaryClassField.LONG_2D: {
+ value = readLongArray2D(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE: {
+ value = readSavable(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_1D: {
+ value = readSavableArray(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_2D: {
+ value = readSavableArray2D(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_ARRAYLIST: {
+ value = readSavableArray(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_ARRAYLIST_1D: {
+ value = readSavableArray2D(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_ARRAYLIST_2D: {
+ value = readSavableArray3D(content);
+ break;
+ }
+ case BinaryClassField.SAVABLE_MAP: {
+ value = readSavableMap(content);
+ break;
+ }
+ case BinaryClassField.STRING_SAVABLE_MAP: {
+ value = readStringSavableMap(content);
+ break;
+ }
+ case BinaryClassField.SHORT: {
+ value = readShort(content);
+ break;
+ }
+ case BinaryClassField.SHORT_1D: {
+ value = readShortArray(content);
+ break;
+ }
+ case BinaryClassField.SHORT_2D: {
+ value = readShortArray2D(content);
+ break;
+ }
+ case BinaryClassField.SHORTBUFFER: {
+ value = readShortBuffer(content);
+ break;
+ }
+ case BinaryClassField.STRING: {
+ value = readString(content);
+ break;
+ }
+ case BinaryClassField.STRING_1D: {
+ value = readStringArray(content);
+ break;
+ }
+ case BinaryClassField.STRING_2D: {
+ value = readStringArray2D(content);
+ break;
+ }
+
+ default:
+ // skip put statement
+ continue;
+ }
+
+ _fieldData.put(alias, value);
+
+ } catch (final IOException e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "setContent(byte[] content)", "Exception", e);
+ }
+ }
+ }
+
+ public BitSet readBitSet(final String name, final BitSet defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (BitSet) _fieldData.get(field._alias);
+ }
+
+ public boolean readBoolean(final String name, final boolean defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Boolean) _fieldData.get(field._alias)).booleanValue();
+ }
+
+ public boolean[] readBooleanArray(final String name, final boolean[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (boolean[]) _fieldData.get(field._alias);
+ }
+
+ public boolean[][] readBooleanArray2D(final String name, final boolean[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (boolean[][]) _fieldData.get(field._alias);
+ }
+
+ public byte readByte(final String name, final byte defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Byte) _fieldData.get(field._alias)).byteValue();
+ }
+
+ public byte[] readByteArray(final String name, final byte[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (byte[]) _fieldData.get(field._alias);
+ }
+
+ public byte[][] readByteArray2D(final String name, final byte[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (byte[][]) _fieldData.get(field._alias);
+ }
+
+ public ByteBuffer readByteBuffer(final String name, final ByteBuffer defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (ByteBuffer) _fieldData.get(field._alias);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<ByteBuffer> readByteBufferList(final String name, final List<ByteBuffer> defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (List<ByteBuffer>) _fieldData.get(field._alias);
+ }
+
+ public double readDouble(final String name, final double defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Double) _fieldData.get(field._alias)).doubleValue();
+ }
+
+ public double[] readDoubleArray(final String name, final double[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (double[]) _fieldData.get(field._alias);
+ }
+
+ public double[][] readDoubleArray2D(final String name, final double[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (double[][]) _fieldData.get(field._alias);
+ }
+
+ public float readFloat(final String name, final float defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Float) _fieldData.get(field._alias)).floatValue();
+ }
+
+ public float[] readFloatArray(final String name, final float[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (float[]) _fieldData.get(field._alias);
+ }
+
+ public float[][] readFloatArray2D(final String name, final float[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (float[][]) _fieldData.get(field._alias);
+ }
+
+ public FloatBuffer readFloatBuffer(final String name, final FloatBuffer defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (FloatBuffer) _fieldData.get(field._alias);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<FloatBuffer> readFloatBufferList(final String name, final List<FloatBuffer> defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (List<FloatBuffer>) _fieldData.get(field._alias);
+ }
+
+ public int readInt(final String name, final int defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Integer) _fieldData.get(field._alias)).intValue();
+ }
+
+ public int[] readIntArray(final String name, final int[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (int[]) _fieldData.get(field._alias);
+ }
+
+ public int[][] readIntArray2D(final String name, final int[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (int[][]) _fieldData.get(field._alias);
+ }
+
+ public IntBuffer readIntBuffer(final String name, final IntBuffer defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (IntBuffer) _fieldData.get(field._alias);
+ }
+
+ public long readLong(final String name, final long defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Long) _fieldData.get(field._alias)).longValue();
+ }
+
+ public long[] readLongArray(final String name, final long[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (long[]) _fieldData.get(field._alias);
+ }
+
+ public long[][] readLongArray2D(final String name, final long[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (long[][]) _fieldData.get(field._alias);
+ }
+
+ public Savable readSavable(final String name, final Savable defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value == null) {
+ return null;
+ } else if (value instanceof ID) {
+ value = _importer.readObject(((ID) value).id);
+ _fieldData.put(field._alias, value);
+ return (Savable) value;
+ } else {
+ return defVal;
+ }
+ }
+
+ public Savable[] readSavableArray(final String name, final Savable[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object[] values = (Object[]) _fieldData.get(field._alias);
+ if (values instanceof ID[]) {
+ values = resolveIDs(values);
+ _fieldData.put(field._alias, values);
+ return (Savable[]) values;
+ } else {
+ return defVal;
+ }
+ }
+
+ private Savable[] resolveIDs(final Object[] values) {
+ if (values != null) {
+ final Savable[] savables = new Savable[values.length];
+ for (int i = 0; i < values.length; i++) {
+ final ID id = (ID) values[i];
+ savables[i] = id != null ? _importer.readObject(id.id) : null;
+ }
+ return savables;
+ } else {
+ return null;
+ }
+ }
+
+ public Savable[][] readSavableArray2D(final String name, final Savable[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object[][] values = (Object[][]) _fieldData.get(field._alias);
+ if (values instanceof ID[][]) {
+ final Savable[][] savables = new Savable[values.length][];
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != null) {
+ savables[i] = resolveIDs(values[i]);
+ } else {
+ savables[i] = null;
+ }
+ }
+ values = savables;
+ _fieldData.put(field._alias, values);
+ }
+ return (Savable[][]) values;
+ }
+
+ public Savable[][][] readSavableArray3D(final String name, final Savable[][][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ final Object[][][] values = (Object[][][]) _fieldData.get(field._alias);
+ if (values instanceof ID[][][]) {
+ final Savable[][][] savables = new Savable[values.length][][];
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] != null) {
+ savables[i] = new Savable[values[i].length][];
+ for (int j = 0; j < values[i].length; j++) {
+ savables[i][j] = resolveIDs(values[i][j]);
+ }
+ } else {
+ savables[i] = null;
+ }
+ }
+ _fieldData.put(field._alias, savables);
+ return savables;
+ } else {
+ return defVal;
+ }
+ }
+
+ private List<Savable> savableArrayListFromArray(final Savable[] savables) {
+ if (savables == null) {
+ return null;
+ }
+ final List<Savable> list = new ArrayList<Savable>(savables.length);
+ for (int x = 0; x < savables.length; x++) {
+ list.add(savables[x]);
+ }
+ return list;
+ }
+
+ // Assumes array of size 2 arrays where pos 0 is key and pos 1 is value.
+ private Map<Savable, Savable> savableMapFrom2DArray(final Savable[][] savables) {
+ if (savables == null) {
+ return null;
+ }
+ final Map<Savable, Savable> map = new HashMap<Savable, Savable>(savables.length);
+ for (int x = 0; x < savables.length; x++) {
+ map.put(savables[x][0], savables[x][1]);
+ }
+ return map;
+ }
+
+ private Map<String, Savable> stringSavableMapFromKV(final String[] keys, final Savable[] values) {
+ if (keys == null || values == null) {
+ return null;
+ }
+
+ final Map<String, Savable> map = new HashMap<String, Savable>(keys.length);
+ for (int x = 0; x < keys.length; x++) {
+ map.put(keys[x], values[x]);
+ }
+
+ return map;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Savable> List<E> readSavableList(final String name, final List<E> defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value instanceof ID[]) {
+ // read Savable array and convert to ArrayList
+ final Savable[] savables = readSavableArray(name, null);
+ value = savableArrayListFromArray(savables);
+ _fieldData.put(field._alias, value);
+ }
+ return (List<E>) value;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Savable> List<E>[] readSavableListArray(final String name, final List<E>[] defVal)
+ throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value instanceof ID[][]) {
+ // read 2D Savable array and convert to ArrayList array
+ final Savable[][] savables = readSavableArray2D(name, null);
+ if (savables != null) {
+ final List<Savable>[] arrayLists = new ArrayList[savables.length];
+ for (int i = 0; i < savables.length; i++) {
+ arrayLists[i] = savableArrayListFromArray(savables[i]);
+ }
+ value = arrayLists;
+ } else {
+ value = defVal;
+ }
+ _fieldData.put(field._alias, value);
+ }
+ return (List<E>[]) value;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Savable> List<E>[][] readSavableListArray2D(final String name, final List<E>[][] defVal)
+ throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value instanceof ID[][][]) {
+ // read 3D Savable array and convert to 2D ArrayList array
+ final Savable[][][] savables = readSavableArray3D(name, null);
+ if (savables != null && savables.length > 0) {
+ final List<Savable>[][] arrayLists = new ArrayList[savables.length][];
+ for (int i = 0; i < savables.length; i++) {
+ arrayLists[i] = new ArrayList[savables[i].length];
+ for (int j = 0; j < savables[i].length; j++) {
+ arrayLists[i][j] = savableArrayListFromArray(savables[i][j]);
+ }
+ }
+ value = arrayLists;
+ } else {
+ value = defVal;
+ }
+ _fieldData.put(field._alias, value);
+ }
+ return (List<E>[][]) value;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <K extends Savable, V extends Savable> Map<K, V> readSavableMap(final String name, final Map<K, V> defVal)
+ throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value instanceof ID[][]) {
+ // read Savable array and convert to Map
+ final Savable[][] savables = readSavableArray2D(name, null);
+ value = savableMapFrom2DArray(savables);
+ _fieldData.put(field._alias, value);
+ }
+ return (Map<K, V>) value;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <V extends Savable> Map<String, V> readStringSavableMap(final String name, final Map<String, V> defVal)
+ throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ Object value = _fieldData.get(field._alias);
+ if (value instanceof StringIDMap) {
+ // read Savable array and convert to Map values
+ final StringIDMap in = (StringIDMap) value;
+ final Savable[] values = resolveIDs(in.values);
+ value = stringSavableMapFromKV(in.keys, values);
+ _fieldData.put(field._alias, value);
+ }
+ return (Map<String, V>) value;
+ }
+
+ public short readShort(final String name, final short defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return ((Short) _fieldData.get(field._alias)).shortValue();
+ }
+
+ public short[] readShortArray(final String name, final short[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (short[]) _fieldData.get(field._alias);
+ }
+
+ public short[][] readShortArray2D(final String name, final short[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (short[][]) _fieldData.get(field._alias);
+ }
+
+ public ShortBuffer readShortBuffer(final String name, final ShortBuffer defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (ShortBuffer) _fieldData.get(field._alias);
+ }
+
+ public String readString(final String name, final String defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (String) _fieldData.get(field._alias);
+ }
+
+ public String[] readStringArray(final String name, final String[] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (String[]) _fieldData.get(field._alias);
+ }
+
+ public String[][] readStringArray2D(final String name, final String[][] defVal) throws IOException {
+ final BinaryClassField field = _cObj._nameFields.get(name);
+ if (field == null || !_fieldData.containsKey(field._alias)) {
+ return defVal;
+ }
+ return (String[][]) _fieldData.get(field._alias);
+ }
+
+ // byte primitive
+
+ protected byte readByte(final byte[] content) throws IOException {
+ final byte value = content[_index];
+ _index++;
+ return value;
+ }
+
+ protected byte[] readByteArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final byte[] value = new byte[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readByte(content);
+ }
+ return value;
+ }
+
+ protected byte[][] readByteArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final byte[][] value = new byte[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readByteArray(content);
+ }
+ return value;
+ }
+
+ // int primitive
+
+ protected int readInt(final byte[] content) throws IOException {
+ byte[] bytes = inflateFrom(content, _index);
+ _index += 1 + bytes.length;
+ bytes = ByteUtils.rightAlignBytes(bytes, 4);
+ final int value = ByteUtils.convertIntFromBytes(bytes);
+ if (value == BinaryOutputCapsule.NULL_OBJECT || value == BinaryOutputCapsule.DEFAULT_OBJECT) {
+ _index -= 4;
+ }
+ return value;
+ }
+
+ protected int[] readIntArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final int[] value = new int[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readInt(content);
+ }
+ return value;
+ }
+
+ protected int[][] readIntArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final int[][] value = new int[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readIntArray(content);
+ }
+ return value;
+ }
+
+ // float primitive
+
+ protected float readFloat(final byte[] content) throws IOException {
+ final float value = ByteUtils.convertFloatFromBytes(content, _index);
+ _index += 4;
+ return value;
+ }
+
+ protected float[] readFloatArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final float[] value = new float[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readFloat(content);
+ }
+ return value;
+ }
+
+ protected float[][] readFloatArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final float[][] value = new float[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readFloatArray(content);
+ }
+ return value;
+ }
+
+ // double primitive
+
+ protected double readDouble(final byte[] content) throws IOException {
+ final double value = ByteUtils.convertDoubleFromBytes(content, _index);
+ _index += 8;
+ return value;
+ }
+
+ protected double[] readDoubleArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final double[] value = new double[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readDouble(content);
+ }
+ return value;
+ }
+
+ protected double[][] readDoubleArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final double[][] value = new double[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readDoubleArray(content);
+ }
+ return value;
+ }
+
+ // long primitive
+
+ protected long readLong(final byte[] content) throws IOException {
+ byte[] bytes = inflateFrom(content, _index);
+ _index += 1 + bytes.length;
+ bytes = ByteUtils.rightAlignBytes(bytes, 8);
+ final long value = ByteUtils.convertLongFromBytes(bytes);
+ return value;
+ }
+
+ protected long[] readLongArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final long[] value = new long[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readLong(content);
+ }
+ return value;
+ }
+
+ protected long[][] readLongArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final long[][] value = new long[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readLongArray(content);
+ }
+ return value;
+ }
+
+ // short primitive
+
+ protected short readShort(final byte[] content) throws IOException {
+ final short value = ByteUtils.convertShortFromBytes(content, _index);
+ _index += 2;
+ return value;
+ }
+
+ protected short[] readShortArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final short[] value = new short[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readShort(content);
+ }
+ return value;
+ }
+
+ protected short[][] readShortArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final short[][] value = new short[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readShortArray(content);
+ }
+ return value;
+ }
+
+ // boolean primitive
+
+ protected boolean readBoolean(final byte[] content) throws IOException {
+ final boolean value = ByteUtils.convertBooleanFromBytes(content, _index);
+ _index += 1;
+ return value;
+ }
+
+ protected boolean[] readBooleanArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final boolean[] value = new boolean[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readBoolean(content);
+ }
+ return value;
+ }
+
+ protected boolean[][] readBooleanArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final boolean[][] value = new boolean[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readBooleanArray(content);
+ }
+ return value;
+ }
+
+ /*
+ * UTF-8 crash course:
+ *
+ * UTF-8 codepoints map to UTF-16 codepoints and vv, which is what Java uses for it's Strings. (so a UTF-8 codepoint
+ * can contain all possible values for a Java char)
+ *
+ * A UTF-8 codepoint can be 1, 2 or 3 bytes long. How long a codepoint is can be told by reading the first byte: b <
+ * 0x80, 1 byte (b & 0xC0) == 0xC0, 2 bytes (b & 0xE0) == 0xE0, 3 bytes
+ *
+ * However there is an additional restriction to UTF-8, to enable you to find the start of a UTF-8 codepoint, if you
+ * start reading at a random point in a UTF-8 byte stream. That's why UTF-8 requires for the second and third byte
+ * of a multibyte codepoint: (b & 0x80) == 0x80 (in other words, first bit must be 1)
+ */
+ private final static int UTF8_START = 0; // next byte should be the start of a new
+ private final static int UTF8_2BYTE = 2; // next byte should be the second byte of a 2 byte codepoint
+ private final static int UTF8_3BYTE_1 = 3; // next byte should be the second byte of a 3 byte codepoint
+ private final static int UTF8_3BYTE_2 = 4; // next byte should be the third byte of a 3 byte codepoint
+ private final static int UTF8_ILLEGAL = 10; // not an UTF8 string
+
+ // String
+ protected String readString(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ /*
+ * We'll transfer the bytes into a separate byte array. While we do that we'll take the opportunity to check if
+ * the byte data is valid UTF-8.
+ *
+ * If it is not UTF-8 it is most likely saved with the BinaryOutputCapsule bug, that saves Strings using their
+ * native encoding. Unfortunatly there is no way to know what encoding was used, so we'll parse using the most
+ * common one in that case; latin-1 aka ISO8859_1
+ *
+ * Encoding of "low" ASCII codepoint (in plain speak: when no special characters are used) will usually look the
+ * same for UTF-8 and the other 1 byte codepoint encodings (espc true for numbers and regular letters of the
+ * alphabet). So these are valid UTF-8 and will give the same result (at most a few charakters will appear
+ * different, such as the euro sign).
+ *
+ * However, when "high" codepoints are used (any codepoint that over 0x7F, in other words where the first bit is
+ * a 1) it's a different matter and UTF-8 and the 1 byte encoding greatly will differ, as well as most 1 byte
+ * encodings relative to each other.
+ *
+ * It is impossible to detect which one-byte encoding is used. Since UTF8 and practically all 1-byte encodings
+ * share the most used characters (the "none-high" ones) parsing them will give the same result. However, not
+ * all byte sequences are legal in UTF-8 (see explantion above). If not UTF-8 encoded content is detected we
+ * therefor fallback on latin1. We also log a warning.
+ *
+ * By this method we detect all use of 1 byte encoding if they: - use a "high" codepoint after a "low" codepoint
+ * or a sequence of codepoints that is valid as UTF-8 bytes, that starts with 1000 - use a "low" codepoint after
+ * a "high" codepoint - use a "low" codepoint after "high" codepoint, after a "high" codepoint that starts with
+ * 1110
+ *
+ * In practice this means that unless 2 or 3 "high" codepoints are used after each other in proper order, we'll
+ * detect the string was not originally UTF-8 encoded.
+ */
+ final byte[] bytes = new byte[length];
+ int utf8State = UTF8_START;
+ int b;
+ for (int x = 0; x < length; x++) {
+ bytes[x] = content[_index++];
+ b = bytes[x] & 0xFF; // unsign our byte
+
+ switch (utf8State) {
+ case UTF8_START:
+ if (b < 0x80) {
+ // good
+ } else if ((b & 0xC0) == 0xC0) {
+ utf8State = UTF8_2BYTE;
+ } else if ((b & 0xE0) == 0xE0) {
+ utf8State = UTF8_3BYTE_1;
+ } else {
+ utf8State = UTF8_ILLEGAL;
+ }
+ break;
+ case UTF8_3BYTE_1:
+ case UTF8_3BYTE_2:
+ case UTF8_2BYTE:
+ if ((b & 0x80) == 0x80) {
+ utf8State = utf8State == UTF8_3BYTE_1 ? UTF8_3BYTE_2 : UTF8_START;
+ } else {
+ utf8State = UTF8_ILLEGAL;
+ }
+ break;
+ }
+ }
+
+ try {
+ // even though so far the parsing might have been a legal UTF-8 sequence, only if a codepoint is fully given
+ // is it correct UTF-8
+ if (utf8State == UTF8_START) {
+ // Java misspells UTF-8 as UTF8 for official use in java.lang
+ return new String(bytes, "UTF8");
+ } else {
+ logger.log(Level.WARNING,
+ "Your export has been saved with an incorrect encoding for it's String fields which means it might not load correctly "
+ + "due to encoding issues.");
+ // We use ISO8859_1 to be consistent across platforms. We could default to native encoding, but this
+ // would lead to inconsistent
+ // behaviour across platforms!
+ // Developers that have previously saved their exports using the old exporter (wich uses native
+ // encoding), can temporarly
+ // remove the ""ISO8859_1" parameter, and change the above if statement to "if (false)".
+ // They should then import and re-export their models using the same enviroment they were orginally
+ // created in.
+ return new String(bytes, "ISO8859_1");
+ }
+ } catch (final UnsupportedEncodingException uee) {
+ // as a last resort fall back to platform native.
+ // JavaDoc is vague about what happens when a decoding a String that contains un undecodable sequence
+ // it also doesn't specify which encodings have to be supported (though UTF-8 and ISO8859 have been in the
+ // SUN JRE since at least 1.1)
+ logger.log(
+ Level.SEVERE,
+ "Your export has been saved with an incorrect encoding or your version of Java is unable to decode the stored string. "
+ + "While your export may load correctly by falling back, using it on different platforms or java versions might lead to "
+ + "very strange inconsitenties. You should probably re-export your work.");
+ return new String(bytes);
+ }
+ }
+
+ protected String[] readStringArray(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final String[] value = new String[length];
+ for (int x = 0; x < length; x++) {
+ value[x] = readString(content);
+ }
+ return value;
+ }
+
+ protected String[][] readStringArray2D(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final String[][] value = new String[length][];
+ for (int x = 0; x < length; x++) {
+ value[x] = readStringArray(content);
+ }
+ return value;
+ }
+
+ // BitSet
+
+ protected BitSet readBitSet(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final BitSet value = new BitSet(length);
+ for (int x = 0; x < length; x++) {
+ value.set(x, readBoolean(content));
+ }
+ return value;
+ }
+
+ // INFLATOR for int and long
+
+ protected static byte[] inflateFrom(final byte[] contents, final int index) {
+ final byte firstByte = contents[index];
+ if (firstByte == BinaryOutputCapsule.NULL_OBJECT) {
+ return ByteUtils.convertToBytes(BinaryOutputCapsule.NULL_OBJECT);
+ } else if (firstByte == BinaryOutputCapsule.DEFAULT_OBJECT) {
+ return ByteUtils.convertToBytes(BinaryOutputCapsule.DEFAULT_OBJECT);
+ } else if (firstByte == 0) {
+ return new byte[0];
+ } else {
+ final byte[] rVal = new byte[firstByte];
+ for (int x = 0; x < rVal.length; x++) {
+ rVal[x] = contents[x + 1 + index];
+ }
+ return rVal;
+ }
+ }
+
+ // BinarySavable
+
+ protected ID readSavable(final byte[] content) throws IOException {
+ final int id = readInt(content);
+ if (id == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ return new ID(id);
+ }
+
+ // BinarySavable array
+
+ protected ID[] readSavableArray(final byte[] content) throws IOException {
+ final int elements = readInt(content);
+ if (elements == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final ID[] rVal = new ID[elements];
+ for (int x = 0; x < elements; x++) {
+ rVal[x] = readSavable(content);
+ }
+ return rVal;
+ }
+
+ protected ID[][] readSavableArray2D(final byte[] content) throws IOException {
+ final int elements = readInt(content);
+ if (elements == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final ID[][] rVal = new ID[elements][];
+ for (int x = 0; x < elements; x++) {
+ rVal[x] = readSavableArray(content);
+ }
+ return rVal;
+ }
+
+ protected ID[][][] readSavableArray3D(final byte[] content) throws IOException {
+ final int elements = readInt(content);
+ if (elements == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final ID[][][] rVal = new ID[elements][][];
+ for (int x = 0; x < elements; x++) {
+ rVal[x] = readSavableArray2D(content);
+ }
+ return rVal;
+ }
+
+ // BinarySavable map
+
+ protected ID[][] readSavableMap(final byte[] content) throws IOException {
+ final int elements = readInt(content);
+ if (elements == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final ID[][] rVal = new ID[elements][];
+ for (int x = 0; x < elements; x++) {
+ rVal[x] = readSavableArray(content);
+ }
+ return rVal;
+ }
+
+ protected StringIDMap readStringSavableMap(final byte[] content) throws IOException {
+ final int elements = readInt(content);
+ if (elements == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final String[] keys = readStringArray(content);
+ final ID[] values = readSavableArray(content);
+ final StringIDMap rVal = new StringIDMap();
+ rVal.keys = keys;
+ rVal.values = values;
+ return rVal;
+ }
+
+ // ArrayList<FloatBuffer>
+
+ protected List<FloatBuffer> readFloatBufferArrayList(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final List<FloatBuffer> rVal = new ArrayList<FloatBuffer>(length);
+ for (int x = 0; x < length; x++) {
+ rVal.add(readFloatBuffer(content));
+ }
+ return rVal;
+ }
+
+ // ArrayList<ByteBuffer>
+
+ protected List<ByteBuffer> readByteBufferArrayList(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+ final List<ByteBuffer> rVal = new ArrayList<ByteBuffer>(length);
+ for (int x = 0; x < length; x++) {
+ rVal.add(readByteBuffer(content));
+ }
+ return rVal;
+ }
+
+ // NIO BUFFERS
+
+ // float buffer
+ protected FloatBuffer readFloatBuffer(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ final boolean direct = readBoolean(content);
+
+ // Pull data in as a little endian byte buffer.
+ final ByteBuffer buf = ByteBuffer.allocateDirect(length * 4).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(content, _index, length * 4).rewind();
+
+ // increment index
+ _index += length * 4;
+
+ // Convert to float buffer.
+ final FloatBuffer value;
+ final boolean contentCopyRequired;
+ if (direct) {
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ value = buf.asFloatBuffer();
+ contentCopyRequired = false;
+ } else {
+ value = BufferUtils.createFloatBuffer(length);
+ contentCopyRequired = true;
+ }
+ } else {
+ value = BufferUtils.createFloatBufferOnHeap(length);
+ contentCopyRequired = true;
+ }
+ if (contentCopyRequired) {
+ value.put(buf.asFloatBuffer());
+ value.rewind();
+ }
+
+ return value;
+ }
+
+ // int buffer
+ protected IntBuffer readIntBuffer(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ final boolean direct = readBoolean(content);
+
+ // Pull data in as a little endian byte buffer.
+ final ByteBuffer buf = ByteBuffer.allocateDirect(length * 4).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(content, _index, length * 4).rewind();
+
+ // increment index
+ _index += length * 4;
+
+ // Convert to int buffer.
+ final IntBuffer value;
+ final boolean contentCopyRequired;
+ if (direct) {
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ value = buf.asIntBuffer();
+ contentCopyRequired = false;
+ } else {
+ value = BufferUtils.createIntBuffer(length);
+ contentCopyRequired = true;
+ }
+ } else {
+ value = BufferUtils.createIntBufferOnHeap(length);
+ contentCopyRequired = true;
+ }
+ if (contentCopyRequired) {
+ value.put(buf.asIntBuffer());
+ value.rewind();
+ }
+ return value;
+ }
+
+ // short buffer
+ protected ShortBuffer readShortBuffer(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ final boolean direct = readBoolean(content);
+
+ // Pull data in as a little endian byte buffer.
+ final ByteBuffer buf = ByteBuffer.allocateDirect(length * 2).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(content, _index, length * 2).rewind();
+
+ // increment index
+ _index += length * 2;
+
+ // Convert to short buffer.
+ final ShortBuffer value;
+ final boolean contentCopyRequired;
+ if (direct) {
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ value = buf.asShortBuffer();
+ contentCopyRequired = false;
+ } else {
+ value = BufferUtils.createShortBuffer(length);
+ contentCopyRequired = true;
+ }
+ } else {
+ value = BufferUtils.createShortBufferOnHeap(length);
+ contentCopyRequired = true;
+ }
+ if (contentCopyRequired) {
+ value.put(buf.asShortBuffer());
+ value.rewind();
+ }
+ return value;
+ }
+
+ // byte buffer
+ protected ByteBuffer readByteBuffer(final byte[] content) throws IOException {
+ final int length = readInt(content);
+ if (length == BinaryOutputCapsule.NULL_OBJECT) {
+ return null;
+ }
+
+ final boolean direct = readBoolean(content);
+
+ // Pull data in as a little endian byte buffer.
+ final ByteBuffer buf = ByteBuffer.allocateDirect(length).order(ByteOrder.LITTLE_ENDIAN);
+ buf.put(content, _index, length).rewind();
+
+ // increment index
+ _index += length;
+
+ // Convert to platform endian buffer.
+ final ByteBuffer value;
+ final boolean contentCopyRequired;
+ if (direct) {
+ if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
+ value = buf;
+ contentCopyRequired = false;
+ } else {
+ value = BufferUtils.createByteBuffer(length);
+ contentCopyRequired = true;
+ }
+ } else {
+ value = BufferUtils.createByteBufferOnHeap(length);
+ contentCopyRequired = true;
+ }
+ if (contentCopyRequired) {
+ value.put(buf);
+ value.rewind();
+ }
+ return value;
+ }
+
+ static private class ID {
+ public int id;
+
+ public ID(final int id) {
+ this.id = id;
+ }
+ }
+
+ static private class StringIDMap {
+ public String[] keys;
+ public ID[] values;
+ }
+
+ public <T extends Enum<T>> T readEnum(final String name, final Class<T> enumType, final T defVal)
+ throws IOException {
+ final String eVal = readString(name, defVal != null ? defVal.name() : null);
+ if (eVal != null) {
+ return Enum.valueOf(enumType, eVal);
+ } else {
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends Enum<T>> T[] readEnumArray(final String name, final Class<T> enumType, final T[] defVal)
+ throws IOException {
+ final String[] eVals = readStringArray(name, null);
+ if (eVals != null) {
+ final T[] rVal = (T[]) Array.newInstance(enumType, eVals.length);
+ int i = 0;
+ for (final String eVal : eVals) {
+ rVal[i++] = Enum.valueOf(enumType, eVal);
+ }
+ return rVal;
+ } else {
+ return defVal;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java
new file mode 100644
index 0000000..82a14dd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/binary/BinaryOutputCapsule.java
@@ -0,0 +1,1013 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.binary;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.ardor3d.util.export.ByteUtils;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+public class BinaryOutputCapsule implements OutputCapsule {
+
+ public static final int NULL_OBJECT = -1;
+ public static final int DEFAULT_OBJECT = -2;
+
+ public static byte[] NULL_BYTES = new byte[] { (byte) -1 };
+ public static byte[] DEFAULT_BYTES = new byte[] { (byte) -2 };
+
+ protected ByteArrayOutputStream _baos;
+ protected byte[] _bytes;
+ protected BinaryExporter _exporter;
+ protected BinaryClassObject _cObj;
+ protected boolean _forceDirectNioBuffers;
+
+ public BinaryOutputCapsule(final BinaryExporter exporter, final BinaryClassObject bco) {
+ this(exporter, bco, false);
+ }
+
+ public BinaryOutputCapsule(final BinaryExporter exporter, final BinaryClassObject bco,
+ final boolean forceDirectNioBuffers) {
+ _baos = new ByteArrayOutputStream();
+ _exporter = exporter;
+ _cObj = bco;
+ _forceDirectNioBuffers = forceDirectNioBuffers;
+ }
+
+ public void write(final byte value, final String name, final byte defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BYTE);
+ write(value);
+ }
+
+ public void write(final byte[] value, final String name, final byte[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BYTE_1D);
+ write(value);
+ }
+
+ public void write(final byte[][] value, final String name, final byte[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BYTE_2D);
+ write(value);
+ }
+
+ public void write(final int value, final String name, final int defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.INT);
+ write(value);
+ }
+
+ public void write(final int[] value, final String name, final int[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.INT_1D);
+ write(value);
+ }
+
+ public void write(final int[][] value, final String name, final int[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.INT_2D);
+ write(value);
+ }
+
+ public void write(final float value, final String name, final float defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.FLOAT);
+ write(value);
+ }
+
+ public void write(final float[] value, final String name, final float[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.FLOAT_1D);
+ write(value);
+ }
+
+ public void write(final float[][] value, final String name, final float[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.FLOAT_2D);
+ write(value);
+ }
+
+ public void write(final double value, final String name, final double defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.DOUBLE);
+ write(value);
+ }
+
+ public void write(final double[] value, final String name, final double[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.DOUBLE_1D);
+ write(value);
+ }
+
+ public void write(final double[][] value, final String name, final double[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.DOUBLE_2D);
+ write(value);
+ }
+
+ public void write(final long value, final String name, final long defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.LONG);
+ write(value);
+ }
+
+ public void write(final long[] value, final String name, final long[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.LONG_1D);
+ write(value);
+ }
+
+ public void write(final long[][] value, final String name, final long[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.LONG_2D);
+ write(value);
+ }
+
+ public void write(final short value, final String name, final short defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SHORT);
+ write(value);
+ }
+
+ public void write(final short[] value, final String name, final short[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SHORT_1D);
+ write(value);
+ }
+
+ public void write(final short[][] value, final String name, final short[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SHORT_2D);
+ write(value);
+ }
+
+ public void write(final boolean value, final String name, final boolean defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BOOLEAN);
+ write(value);
+ }
+
+ public void write(final boolean[] value, final String name, final boolean[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BOOLEAN_1D);
+ write(value);
+ }
+
+ public void write(final boolean[][] value, final String name, final boolean[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BOOLEAN_2D);
+ write(value);
+ }
+
+ public void write(final String value, final String name, final String defVal) throws IOException {
+ if (value == null ? defVal == null : value.equals(defVal)) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.STRING);
+ write(value);
+ }
+
+ public void write(final String[] value, final String name, final String[] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.STRING_1D);
+ write(value);
+ }
+
+ public void write(final String[][] value, final String name, final String[][] defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.STRING_2D);
+ write(value);
+ }
+
+ public void write(final BitSet value, final String name, final BitSet defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BITSET);
+ write(value);
+ }
+
+ public void write(final Savable object, final String name, final Savable defVal) throws IOException {
+ if (object == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE);
+ write(object);
+ }
+
+ public void write(final Savable[] objects, final String name, final Savable[] defVal) throws IOException {
+ if (objects == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_1D);
+ write(objects);
+ }
+
+ public void write(final Savable[][] objects, final String name, final Savable[][] defVal) throws IOException {
+ if (objects == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_2D);
+ write(objects);
+ }
+
+ public void write(final FloatBuffer value, final String name, final FloatBuffer defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.FLOATBUFFER);
+ write(value);
+ }
+
+ public void write(final IntBuffer value, final String name, final IntBuffer defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.INTBUFFER);
+ write(value);
+ }
+
+ public void write(final ByteBuffer value, final String name, final ByteBuffer defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BYTEBUFFER);
+ write(value);
+ }
+
+ public void write(final ShortBuffer value, final String name, final ShortBuffer defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SHORTBUFFER);
+ write(value);
+ }
+
+ public void writeFloatBufferList(final List<FloatBuffer> array, final String name, final List<FloatBuffer> defVal)
+ throws IOException {
+ if (array == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.FLOATBUFFER_ARRAYLIST);
+ writeFloatBufferArrayList(array);
+ }
+
+ public void writeByteBufferList(final List<ByteBuffer> array, final String name, final List<ByteBuffer> defVal)
+ throws IOException {
+ if (array == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.BYTEBUFFER_ARRAYLIST);
+ writeByteBufferArrayList(array);
+ }
+
+ public void writeSavableList(final List<? extends Savable> array, final String name,
+ final List<? extends Savable> defVal) throws IOException {
+ if (array == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST);
+ writeSavableArrayList(array);
+ }
+
+ public void writeSavableListArray(final List<? extends Savable>[] array, final String name,
+ final List<? extends Savable>[] defVal) throws IOException {
+ if (array == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_1D);
+ writeSavableArrayListArray(array);
+ }
+
+ public void writeSavableListArray2D(final List<? extends Savable>[][] array, final String name,
+ final List<? extends Savable>[][] defVal) throws IOException {
+ if (array == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_ARRAYLIST_2D);
+ writeSavableArrayListArray2D(array);
+ }
+
+ public void writeSavableMap(final Map<? extends Savable, ? extends Savable> map, final String name,
+ final Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+ if (map == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.SAVABLE_MAP);
+ writeSavableMap(map);
+ }
+
+ public void writeStringSavableMap(final Map<String, ? extends Savable> map, final String name,
+ final Map<String, ? extends Savable> defVal) throws IOException {
+ if (map == defVal) {
+ return;
+ }
+ writeAlias(name, BinaryClassField.STRING_SAVABLE_MAP);
+ writeStringSavableMap(map);
+ }
+
+ protected void writeAlias(final String name, final byte fieldType) throws IOException {
+ if (_cObj._nameFields.get(name) == null) {
+ generateAlias(name, fieldType);
+ }
+
+ final byte alias = _cObj._nameFields.get(name)._alias;
+ write(alias);
+ }
+
+ // XXX: The generation of aliases is limited to 256 possible values.
+ // If we run into classes with more than 256 fields, we need to expand this.
+ // But I mean, come on...
+ protected void generateAlias(final String name, final byte type) {
+ final byte alias = (byte) _cObj._nameFields.size();
+ _cObj._nameFields.put(name, new BinaryClassField(name, alias, type));
+ }
+
+ @Override
+ public boolean equals(final Object arg0) {
+ if (!(arg0 instanceof BinaryOutputCapsule)) {
+ return false;
+ }
+
+ final byte[] other = ((BinaryOutputCapsule) arg0)._bytes;
+ if (_bytes.length != other.length) {
+ return false;
+ }
+ return Arrays.equals(_bytes, other);
+ }
+
+ public void finish() {
+ // renamed to finish as 'finalize' in java.lang.Object should not be
+ // overridden like this
+ // - finalize should not be called directly but is called by garbage
+ // collection!!!
+ _bytes = _baos.toByteArray();
+ _baos = null;
+ }
+
+ // byte primitive
+
+ protected void write(final byte value) throws IOException {
+ _baos.write(value);
+ }
+
+ protected void write(final byte[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ _baos.write(value);
+ }
+
+ protected void write(final byte[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // int primitive
+
+ protected void write(final int value) throws IOException {
+ _baos.write(deflate(ByteUtils.convertToBytes(value)));
+ }
+
+ protected void write(final int[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final int[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // float primitive
+
+ protected void write(final float value) throws IOException {
+ _baos.write(ByteUtils.convertToBytes(value));
+ }
+
+ protected void write(final float[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final float[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // double primitive
+
+ protected void write(final double value) throws IOException {
+ _baos.write(ByteUtils.convertToBytes(value));
+ }
+
+ protected void write(final double[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final double[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // long primitive
+
+ protected void write(final long value) throws IOException {
+ _baos.write(deflate(ByteUtils.convertToBytes(value)));
+ }
+
+ protected void write(final long[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final long[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // short primitive
+
+ protected void write(final short value) throws IOException {
+ _baos.write(ByteUtils.convertToBytes(value));
+ }
+
+ protected void write(final short[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final short[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // boolean primitive
+
+ protected void write(final boolean value) throws IOException {
+ _baos.write(ByteUtils.convertToBytes(value));
+ }
+
+ protected void write(final boolean[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final boolean[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // String
+
+ protected void write(final String value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ // write our output as UTF-8. Java misspells UTF-8 as UTF8 for official use in java.lang
+ final byte[] bytes = value.getBytes("UTF8");
+ write(bytes.length);
+ _baos.write(bytes);
+ }
+
+ protected void write(final String[] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ protected void write(final String[][] value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.length);
+ for (int x = 0; x < value.length; x++) {
+ write(value[x]);
+ }
+ }
+
+ // BitSet
+
+ protected void write(final BitSet value) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(value.size());
+ // TODO: MAKE THIS SMALLER
+ for (int x = 0, max = value.size(); x < max; x++) {
+ write(value.get(x));
+ }
+ }
+
+ // DEFLATOR for int and long
+
+ protected static byte[] deflate(final byte[] bytes) {
+ int size = bytes.length;
+ if (size == 4) {
+ final int possibleMagic = ByteUtils.convertIntFromBytes(bytes);
+ if (possibleMagic == NULL_OBJECT) {
+ return NULL_BYTES;
+ } else if (possibleMagic == DEFAULT_OBJECT) {
+ return DEFAULT_BYTES;
+ }
+ }
+ for (int x = 0; x < bytes.length; x++) {
+ if (bytes[x] != 0) {
+ break;
+ }
+ size--;
+ }
+ if (size == 0) {
+ return new byte[1];
+ }
+
+ final byte[] rVal = new byte[1 + size];
+ rVal[0] = (byte) size;
+ for (int x = 1; x < rVal.length; x++) {
+ rVal[x] = bytes[bytes.length - size - 1 + x];
+ }
+
+ return rVal;
+ }
+
+ // BinarySavable
+
+ protected void write(final Savable object) throws IOException {
+ if (object == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ final int id = _exporter.processBinarySavable(object);
+ write(id);
+ }
+
+ // BinarySavable array
+
+ protected void write(final Savable[] objects) throws IOException {
+ if (objects == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(objects.length);
+ for (int x = 0; x < objects.length; x++) {
+ write(objects[x]);
+ }
+ }
+
+ protected void write(final Savable[][] objects) throws IOException {
+ if (objects == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(objects.length);
+ for (int x = 0; x < objects.length; x++) {
+ write(objects[x]);
+ }
+ }
+
+ // List<BinarySavable>
+
+ protected void writeSavableArrayList(final List<? extends Savable> array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.size());
+ for (final Savable bs : array) {
+ write(bs);
+ }
+ }
+
+ protected void writeSavableArrayListArray(final List<? extends Savable>[] array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.length);
+ for (final List<? extends Savable> bs : array) {
+ writeSavableArrayList(bs);
+ }
+ }
+
+ protected void writeSavableArrayListArray2D(final List<? extends Savable>[][] array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.length);
+ for (final List<? extends Savable>[] bs : array) {
+ writeSavableArrayListArray(bs);
+ }
+ }
+
+ // Map<BinarySavable, BinarySavable>
+
+ protected void writeSavableMap(final Map<? extends Savable, ? extends Savable> array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.size());
+ for (final Entry<? extends Savable, ? extends Savable> entry : array.entrySet()) {
+ write(new Savable[] { entry.getKey(), entry.getValue() });
+ }
+ }
+
+ protected void writeStringSavableMap(final Map<String, ? extends Savable> array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.size());
+
+ // write String array for keys
+ final String[] keys = array.keySet().toArray(new String[array.keySet().size()]);
+ write(keys);
+
+ // write Savable array for values
+ final Savable[] values = array.values().toArray(new Savable[array.values().size()]);
+ write(values);
+ }
+
+ // List<FloatBuffer>
+
+ protected void writeFloatBufferArrayList(final List<FloatBuffer> array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.size());
+ for (final FloatBuffer buf : array) {
+ write(buf);
+ }
+ }
+
+ // List<FloatBuffer>
+
+ protected void writeByteBufferArrayList(final List<ByteBuffer> array) throws IOException {
+ if (array == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+ write(array.size());
+ for (final ByteBuffer buf : array) {
+ write(buf);
+ }
+ }
+
+ // NIO BUFFERS
+
+ // float buffer
+ protected void write(final FloatBuffer source) throws IOException {
+ if (source == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+
+ final int sizeof = 4;
+
+ // write length
+ final int length = source.limit();
+ write(length);
+
+ // write boolean for directness
+ write(_forceDirectNioBuffers || source.isDirect());
+
+ final byte[] array = new byte[length * sizeof];
+ if (source.hasArray()) {
+ // get the backing array of the source buffer
+ final float[] backingArray = source.array();
+
+ // create a tiny store only to perform the conversion into little endian
+ final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN);
+
+ for (int i = 0; i < backingArray.length; i++) {
+ buf.putFloat(backingArray[i]).rewind();
+ buf.get(array, i * sizeof, sizeof).rewind();
+ }
+ } else {
+ // duplicate buffer to allow modification of limit/position without changing original.
+ final FloatBuffer value = source.duplicate();
+
+ // create little endian store
+ final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN);
+
+ // place buffer into store.
+ value.rewind();
+ buf.asFloatBuffer().put(value);
+ buf.rewind();
+
+ // Pull out store as array
+ buf.get(array);
+ }
+
+ // write to stream
+ _baos.write(array);
+ }
+
+ // int buffer
+ protected void write(final IntBuffer source) throws IOException {
+ if (source == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+
+ final int sizeof = 4;
+
+ // write length
+ final int length = source.limit();
+ write(length);
+
+ // write boolean for directness
+ write(_forceDirectNioBuffers || source.isDirect());
+
+ final byte[] array = new byte[length * sizeof];
+ if (source.hasArray()) {
+ // get the backing array of the source buffer
+ final int[] backingArray = source.array();
+
+ // create a tiny store only to perform the conversion into little endian
+ final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN);
+
+ for (int i = 0; i < backingArray.length; i++) {
+ buf.putInt(backingArray[i]).rewind();
+ buf.get(array, i * sizeof, sizeof).rewind();
+ }
+ } else {
+ // duplicate buffer to allow modification of limit/position without changing original.
+ final IntBuffer value = source.duplicate();
+
+ // create little endian store
+ final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN);
+
+ // place buffer into store. Rewind buffers
+ value.rewind();
+ buf.asIntBuffer().put(value);
+ buf.rewind();
+
+ // Pull out store as array
+ buf.get(array);
+ }
+
+ // write to stream
+ _baos.write(array);
+ }
+
+ // short buffer
+ protected void write(final ShortBuffer source) throws IOException {
+ if (source == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+
+ final int sizeof = 2;
+
+ // write length
+ final int length = source.limit();
+ write(length);
+
+ // write boolean for directness
+ write(_forceDirectNioBuffers || source.isDirect());
+
+ final byte[] array = new byte[length * sizeof];
+ if (source.hasArray()) {
+ // get the backing array of the source buffer
+ final short[] backingArray = source.array();
+
+ // create a tiny store only to perform the conversion into little endian
+ final ByteBuffer buf = ByteBuffer.allocate(sizeof).order(ByteOrder.LITTLE_ENDIAN);
+
+ for (int i = 0; i < backingArray.length; i++) {
+ buf.putShort(backingArray[i]).rewind();
+ buf.get(array, i * sizeof, sizeof).rewind();
+ }
+ } else {
+ // duplicate buffer to allow modification of limit/position without changing original.
+ final ShortBuffer value = source.duplicate();
+
+ // create little endian store
+ final ByteBuffer buf = ByteBuffer.allocate(array.length).order(ByteOrder.LITTLE_ENDIAN);
+
+ // place buffer into store. Rewind buffers
+ value.rewind();
+ buf.asShortBuffer().put(value);
+ buf.rewind();
+
+ // Pull out store as array
+ buf.get(array);
+ }
+
+ // write to stream
+ _baos.write(array);
+ }
+
+ // byte buffer
+ protected void write(final ByteBuffer source) throws IOException {
+ if (source == null) {
+ write(NULL_OBJECT);
+ return;
+ }
+
+ // write length
+ final int length = source.limit();
+ write(length);
+
+ // write boolean for directness
+ write(_forceDirectNioBuffers || source.isDirect());
+
+ final byte[] array;
+ if (source.hasArray()) {
+ array = source.array();
+ } else {
+ // duplicate buffer to allow modification of limit/position without changing original.
+ final ByteBuffer value = source.duplicate();
+
+ // Pull out value as array
+ array = new byte[length];
+ value.rewind();
+ value.get(array);
+ }
+
+ // write to stream
+ _baos.write(array);
+ }
+
+ public void write(final Enum<?> value, final String name, final Enum<?> defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ if (value == null) {
+ write(NULL_OBJECT);
+ } else {
+ write(value.name(), name, null);
+ }
+ }
+
+ public void write(final Enum<?>[] value, final String name) throws IOException {
+ if (value == null) {
+ write(NULL_OBJECT);
+ } else {
+ final String[] toWrite = new String[value.length];
+ int i = 0;
+ for (final Enum<?> val : value) {
+ toWrite[i++] = val.name();
+ }
+ write(toWrite, name, null);
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java
new file mode 100644
index 0000000..00babb9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMInputCapsule.java
@@ -0,0 +1,1295 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import com.ardor3d.annotation.SavableFactory;
+import com.ardor3d.image.Texture;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.TextureKey;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.Savable;
+import com.ardor3d.util.geom.BufferUtils;
+import com.google.common.collect.Lists;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class DOMInputCapsule implements InputCapsule {
+
+ private final Document _doc;
+ private Element _currentElem;
+ private boolean _isAtRoot = true;
+ private final Map<String, Savable> _referencedSavables = new HashMap<String, Savable>();
+
+ public DOMInputCapsule(final Document doc) {
+ _doc = doc;
+ _currentElem = doc.getDocumentElement();
+ }
+
+ private static String decodeString(String s) {
+ if (s == null) {
+ return null;
+ }
+ s = s.replaceAll("\\&quot;", "\"").replaceAll("\\&lt;", "<").replaceAll("\\&amp;", "&");
+ return s;
+ }
+
+ private Element findFirstChildElement(final Element parent) {
+ Node ret = parent.getFirstChild();
+ while (ret != null && (!(ret instanceof Element))) {
+ ret = ret.getNextSibling();
+ }
+ return (Element) ret;
+ }
+
+ private Element findChildElement(final Element parent, final String name) {
+ if (parent == null) {
+ return null;
+ }
+ Node ret = parent.getFirstChild();
+ while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) {
+ ret = ret.getNextSibling();
+ }
+ return (Element) ret;
+ }
+
+ private Element findNextSiblingElement(final Element current) {
+ Node ret = current.getNextSibling();
+ while (ret != null) {
+ if (ret instanceof Element) {
+ return (Element) ret;
+ }
+ ret = ret.getNextSibling();
+ }
+ return null;
+ }
+
+ public byte readByte(final String name, final byte defVal) throws IOException {
+ byte ret = defVal;
+ try {
+ ret = Byte.parseByte(_currentElem.getAttribute(name));
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public byte[] readByteArray(final String name, final byte[] defVal) throws IOException {
+ byte[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final byte[] tmp = new byte[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Byte.parseByte(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public byte[][] readByteArray2D(final String name, final byte[][] defVal) throws IOException {
+ byte[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final byte[][] tmp = new byte[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readByteArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public int readInt(final String name, final int defVal) throws IOException {
+ int ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Integer.parseInt(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public int[] readIntArray(final String name, final int[] defVal) throws IOException {
+ int[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final int[] tmp = new int[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Integer.parseInt(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public int[][] readIntArray2D(final String name, final int[][] defVal) throws IOException {
+ int[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final int[][] tmp = new int[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readIntArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public float readFloat(final String name, final float defVal) throws IOException {
+ float ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Float.parseFloat(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public float[] readFloatArray(final String name, final float[] defVal) throws IOException {
+ float[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final float[] tmp = new float[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Float.parseFloat(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public float[][] readFloatArray2D(final String name, final float[][] defVal) throws IOException {
+ float[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+ final int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+
+ final float[][] tmp = new float[size_outer][size_inner];
+
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size_outer; i++) {
+ tmp[i] = new float[size_inner];
+ for (int k = 0; k < size_inner; k++) {
+ tmp[i][k] = Float.parseFloat(strings[i]);
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public double readDouble(final String name, final double defVal) throws IOException {
+ double ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Double.parseDouble(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public double[] readDoubleArray(final String name, final double[] defVal) throws IOException {
+ double[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final double[] tmp = new double[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Double.parseDouble(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public double[][] readDoubleArray2D(final String name, final double[][] defVal) throws IOException {
+ double[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final double[][] tmp = new double[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readDoubleArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public long readLong(final String name, final long defVal) throws IOException {
+ long ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Long.parseLong(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public long[] readLongArray(final String name, final long[] defVal) throws IOException {
+ long[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final long[] tmp = new long[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Long.parseLong(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public long[][] readLongArray2D(final String name, final long[][] defVal) throws IOException {
+ long[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final long[][] tmp = new long[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readLongArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public short readShort(final String name, final short defVal) throws IOException {
+ short ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Short.parseShort(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public short[] readShortArray(final String name, final short[] defVal) throws IOException {
+ short[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final short[] tmp = new short[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Short.parseShort(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public short[][] readShortArray2D(final String name, final short[][] defVal) throws IOException {
+ short[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final short[][] tmp = new short[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readShortArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public boolean readBoolean(final String name, final boolean defVal) throws IOException {
+ boolean ret = defVal;
+ try {
+ final String s = _currentElem.getAttribute(name);
+ if (s.length() > 0) {
+ ret = Boolean.parseBoolean(s);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public boolean[] readBooleanArray(final String name, final boolean[] defVal) throws IOException {
+ boolean[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final boolean[] tmp = new boolean[size];
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (int i = 0; i < size; i++) {
+ tmp[i] = Boolean.parseBoolean(strings[i]);
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public boolean[][] readBooleanArray2D(final String name, final boolean[][] defVal) throws IOException {
+ boolean[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final boolean[][] tmp = new boolean[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readBooleanArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public String readString(final String name, final String defVal) throws IOException {
+ String ret = defVal;
+ try {
+ ret = decodeString(_currentElem.getAttribute(name));
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public String[] readStringArray(final String name, final String[] defVal) throws IOException {
+ String[] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final String[] tmp = new String[size];
+ final NodeList nodes = tmpEl.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("String")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = ((Element) n).getAttributeNode("value").getValue();
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ // _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public String[][] readStringArray2D(final String name, final String[][] defVal) throws IOException {
+ String[][] ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final String[][] tmp = new String[size][];
+ final NodeList nodes = _currentElem.getChildNodes();
+ int strIndex = 0;
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().contains("array")) {
+ if (strIndex < size) {
+ tmp[strIndex++] = readStringArray(n.getNodeName(), null);
+ } else {
+ throw new IOException("String array contains more elements than specified!");
+ }
+ }
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ _currentElem = (Element) _currentElem.getParentNode();
+ return ret;
+ }
+
+ public BitSet readBitSet(final String name, final BitSet defVal) throws IOException {
+ BitSet ret = defVal;
+ try {
+ final BitSet set = new BitSet();
+ final String bitString = _currentElem.getAttribute(name);
+ final String[] strings = bitString.split("\\s+");
+ for (int i = 0; i < strings.length; i++) {
+ final int isSet = Integer.parseInt(strings[i]);
+ if (isSet == 1) {
+ set.set(i);
+ }
+ }
+ ret = set;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public Savable readSavable(final String name, final Savable defVal) throws IOException {
+ Savable ret = defVal;
+
+ try {
+ Element tmpEl = null;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+ } else if (_isAtRoot) {
+ tmpEl = _doc.getDocumentElement();
+ _isAtRoot = false;
+ } else {
+ tmpEl = findFirstChildElement(_currentElem);
+ }
+ _currentElem = tmpEl;
+ ret = readSavableFromCurrentElem(defVal);
+ if (_currentElem.getParentNode() instanceof Element) {
+ _currentElem = (Element) _currentElem.getParentNode();
+ } else {
+ _currentElem = null;
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+
+ return ret;
+ }
+
+ private Savable readSavableFromCurrentElem(final Savable defVal) throws InstantiationException,
+ ClassNotFoundException, IOException, IllegalAccessException {
+ Savable ret = defVal;
+ Savable tmp = null;
+
+ if (_currentElem == null || _currentElem.getNodeName().equals("null")) {
+ return null;
+ }
+ final String reference = _currentElem.getAttribute("ref");
+ if (reference.length() > 0) {
+ ret = _referencedSavables.get(reference);
+ } else {
+ String className = _currentElem.getNodeName();
+ if (defVal != null) {
+ className = defVal.getClass().getName();
+ } else if (_currentElem.hasAttribute("class")) {
+ className = _currentElem.getAttribute("class");
+ }
+
+ try {
+ @SuppressWarnings("unchecked")
+ final Class<? extends Savable> clazz = (Class<? extends Savable>) Class.forName(className);
+ final SavableFactory ann = clazz.getAnnotation(SavableFactory.class);
+ if (ann == null) {
+ tmp = clazz.newInstance();
+ } else {
+ tmp = (Savable) clazz.getMethod(ann.factoryMethod(), (Class<?>[]) null).invoke(null,
+ (Object[]) null);
+ }
+ } catch (final InstantiationException e) {
+ Logger.getLogger(getClass().getName()).logp(
+ Level.SEVERE,
+ this.getClass().toString(),
+ "readSavableFromCurrentElem(Savable)",
+ "Could not access constructor of class '" + className + "'! \n"
+ + "Some types may require the annotation SavableFactory. Please double check.");
+ throw new Ardor3dException(e);
+ } catch (final NoSuchMethodException e) {
+ Logger.getLogger(getClass().getName())
+ .logp(Level.SEVERE,
+ this.getClass().toString(),
+ "readSavableFromCurrentElem(Savable)",
+ e.getMessage()
+ + " \n"
+ + "Method specified in annotation does not appear to exist or has an invalid method signature.");
+ throw new Ardor3dException(e);
+ } catch (final Exception e) {
+ Logger.getLogger(getClass().getName()).logp(Level.SEVERE, this.getClass().toString(),
+ "readSavableFromCurrentElem(Savable)", "Exception", e);
+ return null;
+ }
+
+ final String refID = _currentElem.getAttribute("reference_ID");
+ if (refID.length() > 0) {
+ _referencedSavables.put(refID, tmp);
+ }
+ if (tmp != null) {
+ tmp.read(this);
+ ret = tmp;
+ }
+ }
+ return ret;
+ }
+
+ private TextureState readTextureStateFromCurrent() {
+ final Element el = _currentElem;
+ TextureState ret = null;
+ try {
+ ret = (TextureState) readSavableFromCurrentElem(null);
+ final Savable[] savs = readSavableArray("texture", new Texture[0]);
+ for (int i = 0; i < savs.length; i++) {
+ Texture t = (Texture) savs[i];
+ final TextureKey tKey = t.getTextureKey();
+ t = TextureManager.loadFromKey(tKey, null, t);
+ ret.setTexture(t, i);
+ }
+ } catch (final Exception e) {
+ Logger.getLogger(DOMInputCapsule.class.getName()).log(Level.SEVERE, null, e);
+ }
+ _currentElem = el;
+ return ret;
+ }
+
+ private Savable[] readRenderStateList(final Element fromElement, final Savable[] defVal) {
+ Savable[] ret = defVal;
+ try {
+ final List<RenderState> tmp = new ArrayList<RenderState>();
+ _currentElem = findFirstChildElement(fromElement);
+ while (_currentElem != null) {
+ final Element el = _currentElem;
+ RenderState rs = null;
+ if (el.getNodeName().equals("com.ardor3d.scene.state.TextureState")) {
+ rs = readTextureStateFromCurrent();
+ } else {
+ rs = (RenderState) (readSavableFromCurrentElem(null));
+ }
+ if (rs != null) {
+ tmp.add(rs);
+ }
+ _currentElem = findNextSiblingElement(el);
+ }
+ ret = tmp.toArray(new RenderState[0]);
+ } catch (final Exception e) {
+ Logger.getLogger(DOMInputCapsule.class.getName()).log(Level.SEVERE, null, e);
+ }
+
+ return ret;
+ }
+
+ public Savable[] readSavableArray(final String name, final Savable[] defVal) throws IOException {
+ Savable[] ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ if (name.equals("renderStateList")) {
+ ret = readRenderStateList(tmpEl, defVal);
+ } else {
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final Savable[] tmp = new Savable[size];
+ _currentElem = findFirstChildElement(tmpEl);
+ for (int i = 0; i < size; i++) {
+ tmp[i] = (readSavableFromCurrentElem(null));
+ if (i == size - 1) {
+ break;
+ }
+ _currentElem = findNextSiblingElement(_currentElem);
+ }
+ ret = tmp;
+ }
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public Savable[][] readSavableArray2D(final String name, final Savable[][] defVal) throws IOException {
+ Savable[][] ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+ final int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer"));
+
+ final Savable[][] tmp = new Savable[size_outer][size_inner];
+ _currentElem = findFirstChildElement(tmpEl);
+ for (int i = 0; i < size_outer; i++) {
+ for (int j = 0; j < size_inner; j++) {
+ tmp[i][j] = (readSavableFromCurrentElem(null));
+ if (i == size_outer - 1 && j == size_inner - 1) {
+ break;
+ }
+ _currentElem = findNextSiblingElement(_currentElem);
+ }
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Savable> List<E> readSavableList(final String name, final List<E> defVal) throws IOException {
+ List<E> ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final String s = tmpEl.getAttribute("size");
+ final int size = Integer.parseInt(s);
+ @SuppressWarnings("rawtypes")
+ final List tmp = Lists.newArrayList();
+ _currentElem = findFirstChildElement(tmpEl);
+ for (int i = 0; i < size; i++) {
+ tmp.add(readSavableFromCurrentElem(null));
+ if (i == size - 1) {
+ break;
+ }
+ _currentElem = findNextSiblingElement(_currentElem);
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <E extends Savable> List<E>[] readSavableListArray(final String name, final List<E>[] defVal)
+ throws IOException {
+ List<E>[] ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+ _currentElem = tmpEl;
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final List<E>[] tmp = new ArrayList[size];
+ for (int i = 0; i < size; i++) {
+ final StringBuilder buf = new StringBuilder("SavableArrayList_");
+ buf.append(i);
+ final List<E> al = readSavableList(buf.toString(), null);
+ tmp[i] = al;
+ if (i == size - 1) {
+ break;
+ }
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public <E extends Savable> List<E>[][] readSavableListArray2D(final String name, final List<E>[][] defVal)
+ throws IOException {
+ List<E>[][] ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+ _currentElem = tmpEl;
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+
+ final List[][] tmp = new ArrayList[size][];
+ for (int i = 0; i < size; i++) {
+ final List[] arr = readSavableListArray("SavableArrayListArray_" + i, null);
+ tmp[i] = arr;
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public List<FloatBuffer> readFloatBufferList(final String name, final List<FloatBuffer> defVal) throws IOException {
+ List<FloatBuffer> ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final List<FloatBuffer> tmp = new ArrayList<FloatBuffer>(size);
+ _currentElem = findFirstChildElement(tmpEl);
+ for (int i = 0; i < size; i++) {
+ tmp.add(readFloatBuffer(null, null));
+ if (i == size - 1) {
+ break;
+ }
+ _currentElem = findNextSiblingElement(_currentElem);
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <K extends Savable, V extends Savable> Map<K, V> readSavableMap(final String name, final Map<K, V> defVal)
+ throws IOException {
+ Map<K, V> ret;
+ Element tempEl;
+
+ if (name != null) {
+ tempEl = findChildElement(_currentElem, name);
+ } else {
+ tempEl = _currentElem;
+ }
+ ret = new HashMap<K, V>();
+
+ final NodeList nodes = tempEl.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().equals("MapEntry")) {
+ final Element elem = (Element) n;
+ _currentElem = elem;
+ final K key = (K) readSavable(XMLExporter.ELEMENT_KEY, null);
+ final V val = (V) readSavable(XMLExporter.ELEMENT_VALUE, null);
+ ret.put(key, val);
+ }
+ }
+ _currentElem = (Element) tempEl.getParentNode();
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <V extends Savable> Map<String, V> readStringSavableMap(final String name, final Map<String, V> defVal)
+ throws IOException {
+ Map<String, V> ret = null;
+ Element tempEl;
+
+ if (name != null) {
+ tempEl = findChildElement(_currentElem, name);
+ } else {
+ tempEl = _currentElem;
+ }
+ if (tempEl != null) {
+ ret = new HashMap<String, V>();
+
+ final NodeList nodes = tempEl.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ final Node n = nodes.item(i);
+ if (n instanceof Element && n.getNodeName().equals("MapEntry")) {
+ final Element elem = (Element) n;
+ _currentElem = elem;
+ final String key = _currentElem.getAttribute("key");
+ final V val = (V) readSavable("Savable", null);
+ ret.put(key, val);
+ }
+ }
+ } else {
+ return defVal;
+ }
+ _currentElem = (Element) tempEl.getParentNode();
+ return ret;
+ }
+
+ /**
+ * reads from currentElem if name is null
+ */
+ public FloatBuffer readFloatBuffer(final String name, final FloatBuffer defVal) throws IOException {
+ FloatBuffer ret = defVal;
+ try {
+ Element tmpEl;
+ if (name != null) {
+ tmpEl = findChildElement(_currentElem, name);
+ } else {
+ tmpEl = _currentElem;
+ }
+ if (tmpEl == null) {
+ return defVal;
+ }
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final FloatBuffer tmp = BufferUtils.createFloatBuffer(size);
+ if (size > 0) {
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (final String s : strings) {
+ tmp.put(Float.parseFloat(s));
+ }
+ tmp.flip();
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public IntBuffer readIntBuffer(final String name, final IntBuffer defVal) throws IOException {
+ IntBuffer ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final IntBuffer tmp = BufferUtils.createIntBuffer(size);
+ if (size > 0) {
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (final String s : strings) {
+ tmp.put(Integer.parseInt(s));
+ }
+ tmp.flip();
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public ByteBuffer readByteBuffer(final String name, final ByteBuffer defVal) throws IOException {
+ ByteBuffer ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final ByteBuffer tmp = BufferUtils.createByteBuffer(size);
+ if (size > 0) {
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (final String s : strings) {
+ tmp.put(Byte.valueOf(s));
+ }
+ tmp.flip();
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public ShortBuffer readShortBuffer(final String name, final ShortBuffer defVal) throws IOException {
+ ShortBuffer ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final ShortBuffer tmp = BufferUtils.createShortBuffer(size);
+ if (size > 0) {
+ final String[] strings = tmpEl.getAttribute("data").split("\\s+");
+ for (final String s : strings) {
+ tmp.put(Short.valueOf(s));
+ }
+ tmp.flip();
+ }
+ ret = tmp;
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public List<ByteBuffer> readByteBufferList(final String name, final List<ByteBuffer> defVal) throws IOException {
+ List<ByteBuffer> ret = defVal;
+ try {
+ final Element tmpEl = findChildElement(_currentElem, name);
+ if (tmpEl == null) {
+ return defVal;
+ }
+
+ final int size = Integer.parseInt(tmpEl.getAttribute("size"));
+ final List<ByteBuffer> tmp = new ArrayList<ByteBuffer>(size);
+ _currentElem = findFirstChildElement(tmpEl);
+ for (int i = 0; i < size; i++) {
+ tmp.add(readByteBuffer(null, null));
+ if (i == size - 1) {
+ break;
+ }
+ _currentElem = findNextSiblingElement(_currentElem);
+ }
+ ret = tmp;
+ _currentElem = (Element) tmpEl.getParentNode();
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ public <T extends Enum<T>> T readEnum(final String name, final Class<T> enumType, final T defVal)
+ throws IOException {
+ T ret = defVal;
+ try {
+ final String eVal = _currentElem.getAttribute(name);
+ if (eVal != null && eVal.length() > 0) {
+ ret = Enum.valueOf(enumType, eVal);
+ }
+ } catch (final Exception e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T extends Enum<T>> T[] readEnumArray(final String name, final Class<T> enumType, final T[] defVal)
+ throws IOException {
+ final String[] eVals = readStringArray(name, null);
+ if (eVals != null) {
+ final T[] rVal = (T[]) Array.newInstance(enumType, eVals.length);
+ int i = 0;
+ for (final String eVal : eVals) {
+ rVal[i++] = Enum.valueOf(enumType, eVal);
+ }
+ return rVal;
+ } else {
+ return defVal;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java
new file mode 100644
index 0000000..b07df78
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMOutputCapsule.java
@@ -0,0 +1,796 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class DOMOutputCapsule implements OutputCapsule {
+
+ private static final String _dataAttributeName = "data";
+ private final Document _doc;
+ private Element _currentElement;
+ private final Map<Savable, Element> _writtenSavables = new IdentityHashMap<Savable, Element>();
+
+ public DOMOutputCapsule(final Document doc) {
+ _doc = doc;
+ _currentElement = null;
+ }
+
+ public Document getDoc() {
+ return _doc;
+ }
+
+ /**
+ * appends a new Element with the given name to currentElement, sets currentElement to be new Element, and returns
+ * the new Element as well
+ */
+ private Element appendElement(final String name) {
+ Element ret = null;
+ ret = _doc.createElement(name);
+ if (_currentElement == null) {
+ _doc.appendChild(ret);
+ } else {
+ _currentElement.appendChild(ret);
+ }
+ _currentElement = ret;
+ return ret;
+ }
+
+ private static String encodeString(String s) {
+ if (s == null) {
+ return null;
+ }
+ s = s.replaceAll("\\&", "&amp;").replaceAll("\\\"", "&quot;").replaceAll("\\<", "&lt;");
+ return s;
+ }
+
+ public void write(final byte value, final String name, final byte defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(byte[] value, final String name, final byte[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final byte b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(byte[][] value, final String name, final byte[][] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final byte[] bs : value) {
+ for (final byte b : bs) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ buf.append(" ");
+ }
+ // remove last spaces
+ buf.setLength(buf.length() - 2);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size_outer", String.valueOf(value.length));
+ el.setAttribute("size_inner", String.valueOf(value[0].length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final int value, final String name, final int defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(final int[] value, final String name, final int[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ return;
+ }
+ if (Arrays.equals(value, defVal)) {
+ return;
+ }
+
+ for (final int b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(Math.max(0, buf.length() - 1));
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final int[][] value, final String name, final int[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final int[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final float value, final String name, final float defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(float[] value, final String name, final float[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final float b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final float[][] value, final String name, final float[][] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ for (final float[] bs : value) {
+ for (final float b : bs) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size_outer", String.valueOf(value.length));
+ el.setAttribute("size_inner", String.valueOf(value[0].length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final double value, final String name, final double defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(double[] value, final String name, final double[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final double b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final double[][] value, final String name, final double[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final double[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final long value, final String name, final long defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(long[] value, final String name, final long[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final long b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final long[][] value, final String name, final long[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final long[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final short value, final String name, final short defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(short[] value, final String name, final short[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final short b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(buf.length() - 1);
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final short[][] value, final String name, final short[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final short[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final boolean value, final String name, final boolean defVal) throws IOException {
+ if (value == defVal) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(boolean[] value, final String name, final boolean[] defVal) throws IOException {
+ final StringBuilder buf = new StringBuilder();
+ if (value == null) {
+ value = defVal;
+ }
+ for (final boolean b : value) {
+ buf.append(b);
+ buf.append(" ");
+ }
+ // remove last space
+ buf.setLength(Math.max(0, buf.length() - 1));
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final boolean[][] value, final String name, final boolean[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final boolean[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final String value, final String name, final String defVal) throws IOException {
+ if (value == null || value.equals(defVal)) {
+ return;
+ }
+ _currentElement.setAttribute(name, encodeString(value));
+ }
+
+ public void write(String[] value, final String name, final String[] defVal) throws IOException {
+ final Element el = appendElement(name);
+
+ if (value == null) {
+ value = defVal;
+ }
+
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final String b = value[i];
+ appendElement("String_" + i);
+ final String val = encodeString(b);
+ _currentElement.setAttribute("value", val);
+ _currentElement = el;
+ }
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void write(final String[][] value, final String name, final String[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.length));
+
+ for (int i = 0; i < value.length; i++) {
+ final String[] array = value[i];
+ write(array, "array_" + i, defVal == null ? null : defVal[i]);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final BitSet value, final String name, final BitSet defVal) throws IOException {
+ if (value == null || value.equals(defVal)) {
+ return;
+ }
+ final StringBuilder buf = new StringBuilder();
+ for (int i = value.nextSetBit(0); i >= 0; i = value.nextSetBit(i + 1)) {
+ buf.append(i);
+ buf.append(" ");
+ }
+ buf.setLength(Math.max(0, buf.length() - 1));
+ _currentElement.setAttribute(name, buf.toString());
+
+ }
+
+ public void write(final Savable object, String name, final Savable defVal) throws IOException {
+ if (object == null) {
+ return;
+ }
+ if (object.equals(defVal)) {
+ return;
+ }
+
+ final Element old = _currentElement;
+ Element el = _writtenSavables.get(object);
+
+ String className = null;
+ if (!object.getClass().getName().equals(name)) {
+ className = object.getClass().getName();
+ }
+ try {
+ _doc.createElement(name);
+ } catch (final DOMException e) {
+ name = "Object";
+ className = object.getClass().getName();
+ }
+
+ if (el != null) {
+ String refID = el.getAttribute("reference_ID");
+ if (refID.length() == 0) {
+ refID = object.getClassTag().getName() + "@" + object.hashCode();
+ el.setAttribute("reference_ID", refID);
+ }
+ el = appendElement(name);
+ el.setAttribute("ref", refID);
+ } else {
+ el = appendElement(name);
+ _writtenSavables.put(object, el);
+ object.write(this);
+ }
+ if (className != null) {
+ el.setAttribute("class", className);
+ }
+
+ _currentElement = old;
+ }
+
+ public void write(final Savable[] objects, final String name, final Savable[] defVal) throws IOException {
+ if (objects == null) {
+ return;
+ }
+ if (Arrays.equals(objects, defVal)) {
+ return;
+ }
+
+ final Element old = _currentElement;
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(objects.length));
+ for (int i = 0; i < objects.length; i++) {
+ final Savable o = objects[i];
+ if (o == null) {
+ // renderStateList has special loading code, so we can leave out the null values
+ if (!name.equals("renderStateList")) {
+ final Element before = _currentElement;
+ appendElement("null");
+ _currentElement = before;
+ }
+ } else {
+ write(o, o.getClassTag().getName(), null);
+ }
+ }
+ _currentElement = old;
+ }
+
+ public void write(final Savable[][] value, final String name, final Savable[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size_outer", String.valueOf(value.length));
+ el.setAttribute("size_inner", String.valueOf(value[0].length));
+ for (final Savable[] bs : value) {
+ for (final Savable b : bs) {
+ write(b, b.getClassTag().getSimpleName(), null);
+ }
+ }
+ _currentElement = (Element) _currentElement.getParentNode();
+ }
+
+ public void writeSavableList(final List<? extends Savable> array, final String name,
+ final List<? extends Savable> defVal) throws IOException {
+ if (array == null) {
+ return;
+ }
+ if (array.equals(defVal)) {
+ return;
+ }
+ final Element old = _currentElement;
+ final Element el = appendElement(name);
+ _currentElement = el;
+ el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
+ for (final Object o : array) {
+ if (o == null) {
+ continue;
+ } else if (o instanceof Savable) {
+ final Savable s = (Savable) o;
+ write(s, s.getClassTag().getName(), null);
+ } else {
+ throw new ClassCastException("Not a Savable instance: " + o);
+ }
+ }
+ _currentElement = old;
+ }
+
+ public void writeSavableListArray(final List<? extends Savable>[] objects, final String name,
+ final List<? extends Savable>[] defVal) throws IOException {
+ if (objects == null) {
+ return;
+ }
+ if (Arrays.equals(objects, defVal)) {
+ return;
+ }
+
+ final Element old = _currentElement;
+ final Element el = appendElement(name);
+ el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length));
+ for (int i = 0; i < objects.length; i++) {
+ final List<? extends Savable> o = objects[i];
+ if (o == null) {
+ final Element before = _currentElement;
+ appendElement("null");
+ _currentElement = before;
+ } else {
+ final StringBuilder buf = new StringBuilder("SavableArrayList_");
+ buf.append(i);
+ writeSavableList(o, buf.toString(), null);
+ }
+ }
+ _currentElement = old;
+ }
+
+ public void writeSavableListArray2D(final List<? extends Savable>[][] value, final String name,
+ final List<? extends Savable>[][] defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (Arrays.deepEquals(value, defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ final int size = value.length;
+ el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size));
+
+ for (int i = 0; i < size; i++) {
+ final List<? extends Savable>[] vi = value[i];
+ writeSavableListArray(vi, "SavableArrayListArray_" + i, null);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void writeFloatBufferList(final List<FloatBuffer> array, final String name, final List<FloatBuffer> defVal)
+ throws IOException {
+ if (array == null) {
+ return;
+ }
+ if (array.equals(defVal)) {
+ return;
+ }
+ final Element el = appendElement(name);
+ el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size()));
+ for (final FloatBuffer o : array) {
+ write(o, XMLExporter.ELEMENT_FLOATBUFFER, null);
+ }
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void writeSavableMap(final Map<? extends Savable, ? extends Savable> map, final String name,
+ final Map<? extends Savable, ? extends Savable> defVal) throws IOException {
+ if (map == null) {
+ return;
+ }
+ if (map.equals(defVal)) {
+ return;
+ }
+ final Element stringMap = appendElement(name);
+
+ final Iterator<? extends Savable> keyIterator = map.keySet().iterator();
+ while (keyIterator.hasNext()) {
+ final Savable key = keyIterator.next();
+ appendElement(XMLExporter.ELEMENT_MAPENTRY);
+ write(key, XMLExporter.ELEMENT_KEY, null);
+ final Savable value = map.get(key);
+ write(value, XMLExporter.ELEMENT_VALUE, null);
+ _currentElement = stringMap;
+ }
+
+ _currentElement = (Element) stringMap.getParentNode();
+ }
+
+ public void writeStringSavableMap(final Map<String, ? extends Savable> map, final String name,
+ final Map<String, ? extends Savable> defVal) throws IOException {
+ if (map == null) {
+ return;
+ }
+ if (map.equals(defVal)) {
+ return;
+ }
+ final Element stringMap = appendElement(name);
+
+ final Iterator<String> keyIterator = map.keySet().iterator();
+ while (keyIterator.hasNext()) {
+ final String key = keyIterator.next();
+ final Element mapEntry = appendElement("MapEntry");
+ mapEntry.setAttribute("key", key);
+ final Savable s = map.get(key);
+ write(s, "Savable", null);
+ _currentElement = stringMap;
+ }
+
+ _currentElement = (Element) stringMap.getParentNode();
+ }
+
+ public void write(final FloatBuffer value, final String name, final FloatBuffer defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.limit()));
+ final StringBuilder buf = new StringBuilder();
+ final int pos = value.position();
+ value.rewind();
+ while (value.hasRemaining()) {
+ buf.append(value.get());
+ buf.append(" ");
+ }
+ buf.setLength(Math.max(0, buf.length() - 1));
+ value.position(pos);
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final IntBuffer value, final String name, final IntBuffer defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (value.equals(defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.limit()));
+ final StringBuilder buf = new StringBuilder();
+ final int pos = value.position();
+ value.rewind();
+ while (value.hasRemaining()) {
+ buf.append(value.get());
+ buf.append(" ");
+ }
+ buf.setLength(buf.length() - 1);
+ value.position(pos);
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final ByteBuffer value, final String name, final ByteBuffer defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (value.equals(defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.limit()));
+ final StringBuilder buf = new StringBuilder();
+ final int pos = value.position();
+ value.rewind();
+ while (value.hasRemaining()) {
+ buf.append(value.get());
+ buf.append(" ");
+ }
+ buf.setLength(buf.length() - 1);
+ value.position(pos);
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void write(final ShortBuffer value, final String name, final ShortBuffer defVal) throws IOException {
+ if (value == null) {
+ return;
+ }
+ if (value.equals(defVal)) {
+ return;
+ }
+
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(value.limit()));
+ final StringBuilder buf = new StringBuilder();
+ final int pos = value.position();
+ value.rewind();
+ while (value.hasRemaining()) {
+ buf.append(value.get());
+ buf.append(" ");
+ }
+ buf.setLength(buf.length() - 1);
+ value.position(pos);
+ el.setAttribute(_dataAttributeName, buf.toString());
+ _currentElement = (Element) el.getParentNode();
+ }
+
+ public void writeByteBufferList(final List<ByteBuffer> array, final String name, final List<ByteBuffer> defVal)
+ throws IOException {
+ if (array == null) {
+ return;
+ }
+ if (array.equals(defVal)) {
+ return;
+ }
+ final Element el = appendElement(name);
+ el.setAttribute("size", String.valueOf(array.size()));
+ for (final ByteBuffer o : array) {
+ write(o, "ByteBuffer", null);
+ }
+ _currentElement = (Element) el.getParentNode();
+
+ }
+
+ public void write(final Enum<?> value, final String name, final Enum<?> defVal) throws IOException {
+ if (value == defVal || value == null) {
+ return;
+ }
+ _currentElement.setAttribute(name, String.valueOf(value));
+ }
+
+ public void write(final Enum<?>[] value, final String name) throws IOException {
+ if (value == null) {
+ return;
+ } else {
+ final String[] toWrite = new String[value.length];
+ int i = 0;
+ for (final Enum<?> val : value) {
+ toWrite[i++] = val.name();
+ }
+ write(toWrite, name, null);
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java
new file mode 100644
index 0000000..eef7b79
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOMSerializer.java
@@ -0,0 +1,210 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * The DOMSerializer was based primarily off the DOMSerializer.java class from the "Java and XML" 3rd Edition book by
+ * Brett McLaughlin, and Justin Edelson. Some modifications were made to support formatting of elements and attributes.
+ *
+ */
+public class DOMSerializer {
+
+ /** Indentation to use (default is no indentation) */
+ private String _indent = "";
+
+ /** Line separator to use (default is for Windows) */
+ private String _lineSeparator = "\n";
+
+ /** Encoding for output (default is UTF-8) */
+ private String _encoding = "UTF8";
+
+ /** Attributes will be displayed on seperate lines */
+ private final boolean _displayAttributesOnSeperateLine = true;
+
+ public void setLineSeparator(final String lineSeparator) {
+ _lineSeparator = lineSeparator;
+ }
+
+ public void setEncoding(final String encoding) {
+ _encoding = encoding;
+ }
+
+ public void setIndent(final int numSpaces) {
+ final StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < numSpaces; i++) {
+ buffer.append("\t");
+ }
+ _indent = buffer.toString();
+ }
+
+ public void serialize(final Document doc, final OutputStream out) throws IOException {
+ final Writer writer = new OutputStreamWriter(out, _encoding);
+ serialize(doc, writer);
+ }
+
+ public void serialize(final Document doc, final File file) throws IOException {
+ final Writer writer = new FileWriter(file);
+ serialize(doc, writer);
+ }
+
+ public void serialize(final Document doc, final Writer writer) throws IOException {
+ // Start serialization recursion with no indenting
+ serializeNode(doc, writer, "");
+ writer.flush();
+ }
+
+ private void serializeNode(final Node node, final Writer writer, final String indentLevel) throws IOException {
+ // Determine action based on node type
+ switch (node.getNodeType()) {
+ case Node.DOCUMENT_NODE:
+ final Document doc = (Document) node;
+ /**
+ * DOM Level 2 code writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+ */
+ writer.write("<?xml version=\"");
+ writer.write(doc.getXmlVersion());
+ writer.write("\" encoding=\"UTF-8\" standalone=\"");
+ if (doc.getXmlStandalone()) {
+ writer.write("yes");
+ } else {
+ writer.write("no");
+ }
+ writer.write("\"");
+ writer.write("?>");
+ writer.write(_lineSeparator);
+
+ // recurse on each top-level node
+ final NodeList nodes = node.getChildNodes();
+ if (nodes != null) {
+ for (int i = 0; i < nodes.getLength(); i++) {
+ serializeNode(nodes.item(i), writer, "");
+ }
+ }
+ break;
+ case Node.ELEMENT_NODE:
+ final String name = node.getNodeName();
+ // writer.write(indentLevel + "<" + name);
+ writer.write("<" + name);
+ final NamedNodeMap attributes = node.getAttributes();
+ for (int i = 0; i < attributes.getLength(); i++) {
+ final Node current = attributes.item(i);
+ String attributeSeperator = " ";
+ if (_displayAttributesOnSeperateLine && i != 0) {
+ attributeSeperator = _lineSeparator + indentLevel + _indent;
+ }
+ // Double indentLevel to match parent element and then one indention to format below parent
+ final String attributeStr = attributeSeperator + current.getNodeName() + "=\"";
+ writer.write(attributeStr);
+ print(writer, current.getNodeValue());
+ writer.write("\"");
+ }
+ writer.write(">");
+
+ // recurse on each child
+ final NodeList children = node.getChildNodes();
+ if (children != null) {
+ if ((children.item(0) != null) && (children.item(0).getNodeType() == Node.ELEMENT_NODE)) {
+ // writer.write(lineSeparator);
+ }
+
+ for (int i = 0; i < children.getLength(); i++) {
+ serializeNode(children.item(i), writer, indentLevel + _indent);
+ }
+
+ if ((children.item(0) != null)
+ && (children.item(children.getLength() - 1).getNodeType() == Node.ELEMENT_NODE)) {
+ ;// writer.write(indentLevel);
+ }
+ }
+
+ writer.write("</" + name + ">");
+ // writer.write(lineSeparator);
+ break;
+ case Node.TEXT_NODE:
+ print(writer, node.getNodeValue());
+ break;
+ case Node.CDATA_SECTION_NODE:
+ writer.write("<![CDATA[");
+ print(writer, node.getNodeValue());
+ writer.write("]]>");
+ break;
+ case Node.COMMENT_NODE:
+ writer.write(indentLevel + "<!-- " + node.getNodeValue() + " -->");
+ writer.write(_lineSeparator);
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ writer.write("<?" + node.getNodeName() + " " + node.getNodeValue() + "?>");
+ writer.write(_lineSeparator);
+ break;
+ case Node.ENTITY_REFERENCE_NODE:
+ writer.write("&" + node.getNodeName() + ";");
+ break;
+ case Node.DOCUMENT_TYPE_NODE:
+ final DocumentType docType = (DocumentType) node;
+ final String publicId = docType.getPublicId();
+ final String systemId = docType.getSystemId();
+ final String internalSubset = docType.getInternalSubset();
+ writer.write("<!DOCTYPE " + docType.getName());
+ if (publicId != null) {
+ writer.write(" PUBLIC \"" + publicId + "\" ");
+ } else {
+ writer.write(" SYSTEM ");
+ }
+ writer.write("\"" + systemId + "\"");
+ if (internalSubset != null) {
+ writer.write(" [" + internalSubset + "]");
+ }
+ writer.write(">");
+ writer.write(_lineSeparator);
+ break;
+ }
+ }
+
+ private void print(final Writer writer, final String s) throws IOException {
+
+ if (s == null) {
+ return;
+ }
+ for (int i = 0, len = s.length(); i < len; i++) {
+ final char c = s.charAt(i);
+ switch (c) {
+ case '<':
+ writer.write("&lt;");
+ break;
+ case '>':
+ writer.write("&gt;");
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '\r':
+ writer.write("&#xD;");
+ break;
+ default:
+ writer.write(c);
+ }
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java
new file mode 100644
index 0000000..7aff34a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/DOM_PrettyPrint.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.OutputStream;
+
+import org.w3c.dom.Document;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class DOM_PrettyPrint {
+ public static void serialize(final Document doc, final OutputStream out) throws Exception {
+ final DOMSerializer serializer = new DOMSerializer();
+ serializer.setIndent(2);
+ serializer.serialize(doc, out);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java
new file mode 100644
index 0000000..e23bf32
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLExporter.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import com.ardor3d.util.export.Ardor3dExporter;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class XMLExporter implements Ardor3dExporter {
+ public static final String ELEMENT_MAPENTRY = "MapEntry";
+ public static final String ELEMENT_KEY = "Key";
+ public static final String ELEMENT_VALUE = "Value";
+ public static final String ELEMENT_FLOATBUFFER = "FloatBuffer";
+ public static final String ATTRIBUTE_SIZE = "size";
+
+ public XMLExporter() {
+
+ }
+
+ public void save(final Savable object, final OutputStream os) throws IOException {
+ try {
+ // Initialize Document when saving so we don't retain state of previous exports
+ final DOMOutputCapsule _domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder().newDocument());
+ _domOut.write(object, object.getClass().getName(), null);
+ DOM_PrettyPrint.serialize(_domOut.getDoc(), os);
+ os.flush();
+ } catch (final Exception ex) {
+ final IOException e = new IOException();
+ e.initCause(ex);
+ throw e;
+ }
+ }
+
+ public void save(final Savable object, final File f) throws IOException {
+ save(object, new FileOutputStream(f));
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java
new file mode 100644
index 0000000..a5caa2a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/export/xml/XMLImporter.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.export.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.xml.sax.SAXException;
+
+import com.ardor3d.util.export.Ardor3dImporter;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Part of the ardor3d XML IO system
+ */
+public class XMLImporter implements Ardor3dImporter {
+
+ public XMLImporter() {}
+
+ public Savable load(final InputStream is) throws IOException {
+ try {
+ final DOMInputCapsule _domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder().parse(is));
+ return _domIn.readSavable(null, null);
+ } catch (final SAXException e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ } catch (final ParserConfigurationException e) {
+ final IOException ex = new IOException();
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+
+ public Savable load(final URL url) throws IOException {
+ return load(url.openStream());
+ }
+
+ public Savable load(final File f) throws IOException {
+ return load(new FileInputStream(f));
+ }
+
+ public Savable load(final byte[] data) throws IOException {
+ return load(new ByteArrayInputStream(data));
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java
new file mode 100644
index 0000000..ebe13ca
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/BufferUtils.java
@@ -0,0 +1,1697 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.Vector4;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.math.type.ReadOnlyVector4;
+import com.ardor3d.scenegraph.ByteBufferData;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.IntBufferData;
+import com.ardor3d.scenegraph.ShortBufferData;
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.Constants;
+import com.google.common.collect.MapMaker;
+
+/**
+ * <code>BufferUtils</code> is a helper class for generating nio buffers from ardor3d data classes such as Vectors and
+ * ColorRGBA.
+ */
+public final class BufferUtils {
+
+ // // -- TRACKER HASH -- ////
+ private static final Map<Buffer, Object> trackingHash = new MapMaker().weakKeys().makeMap();
+ private static final Object ref = new Object();
+
+ // // -- COLORRGBA METHODS -- ////
+
+ /**
+ * Generate a new FloatBuffer using the given array of ColorRGBA objects. The FloatBuffer will be 4 * data.length
+ * long and contain the color data as data[0].r, data[0].g, data[0].b, data[0].a, data[1].r... etc.
+ *
+ * @param data
+ * array of ColorRGBA objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final ReadOnlyColorRGBA... data) {
+ if (data == null) {
+ return null;
+ }
+ return createFloatBuffer(0, data.length, data);
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of ColorRGBA objects. The FloatBuffer will be 4 * data.length
+ * long and contain the color data as data[0].r, data[0].g, data[0].b, data[0].a, data[1].r... etc.
+ *
+ * @param offset
+ * the starting index to read from in our data array
+ * @param length
+ * the number of colors to read
+ * @param data
+ * array of ColorRGBA objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyColorRGBA... data) {
+ if (data == null) {
+ return null;
+ }
+ final FloatBuffer buff = createFloatBuffer(4 * length);
+ for (int x = offset; x < length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].getRed()).put(data[x].getGreen()).put(data[x].getBlue()).put(data[x].getAlpha());
+ } else {
+ buff.put(0).put(0).put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of ColorRGBA object data.
+ *
+ * @param colors
+ * number of colors that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createColorBuffer(final int colors) {
+ final FloatBuffer colorBuff = createFloatBuffer(4 * colors);
+ return colorBuff;
+ }
+
+ /**
+ * Sets the data contained in the given color into the FloatBuffer at the specified index.
+ *
+ * @param color
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the position to place the data; in terms of colors not floats
+ */
+ public static void setInBuffer(final ReadOnlyColorRGBA color, final FloatBuffer buf, final int index) {
+ buf.position(index * 4);
+ buf.put(color.getRed());
+ buf.put(color.getGreen());
+ buf.put(color.getBlue());
+ buf.put(color.getAlpha());
+ }
+
+ /**
+ * Updates the values of the given color from the specified buffer at the index provided.
+ *
+ * @param store
+ * the color to set data on
+ * @param buf
+ * the buffer to read from
+ * @param index
+ * the position (in terms of colors, not floats) to read from the buf
+ */
+ public static void populateFromBuffer(final ColorRGBA store, final FloatBuffer buf, final int index) {
+ store.setRed(buf.get(index * 4));
+ store.setGreen(buf.get(index * 4 + 1));
+ store.setBlue(buf.get(index * 4 + 2));
+ store.setAlpha(buf.get(index * 4 + 3));
+ }
+
+ /**
+ * Generates a ColorRGBA array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of ColorRGBA objects
+ */
+ public static ColorRGBA[] getColorArray(final FloatBuffer buff) {
+ buff.rewind();
+ final ColorRGBA[] colors = new ColorRGBA[buff.limit() >> 2];
+ for (int x = 0; x < colors.length; x++) {
+ final ColorRGBA c = new ColorRGBA(buff.get(), buff.get(), buff.get(), buff.get());
+ colors[x] = c;
+ }
+ return colors;
+ }
+
+ /**
+ * Generates a ColorRGBA array from the given FloatBufferData.
+ *
+ * @param buff
+ * the FloatBufferData to read from
+ * @param defaults
+ * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is
+ * smaller than 4.
+ * @return a newly generated array of ColorRGBA objects
+ */
+ public static ColorRGBA[] getColorArray(final FloatBufferData data, final ReadOnlyColorRGBA defaults) {
+ final FloatBuffer buff = data.getBuffer();
+ buff.clear();
+ final ColorRGBA[] colors = new ColorRGBA[data.getTupleCount()];
+ final int tupleSize = data.getValuesPerTuple();
+ for (int x = 0; x < colors.length; x++) {
+ final ColorRGBA c = new ColorRGBA(defaults);
+ c.setRed(buff.get());
+ if (tupleSize > 1) {
+ c.setGreen(buff.get());
+ }
+ if (tupleSize > 2) {
+ c.setBlue(buff.get());
+ }
+ if (tupleSize > 3) {
+ c.setAlpha(buff.get());
+ }
+ if (tupleSize > 4) {
+ buff.position(buff.position() + tupleSize - 4);
+ }
+ colors[x] = c;
+ }
+ return colors;
+ }
+
+ /**
+ * Copies a ColorRGBA from one position in the buffer to another. The index values are in terms of color number (eg,
+ * color number 0 is positions 0-3 in the FloatBuffer.)
+ *
+ * @param buf
+ * the buffer to copy from/to
+ * @param fromPos
+ * the index of the color to copy
+ * @param toPos
+ * the index to copy the color to
+ */
+ public static void copyInternalColor(final FloatBuffer buf, final int fromPos, final int toPos) {
+ copyInternal(buf, fromPos * 4, toPos * 4, 4);
+ }
+
+ /**
+ * Checks to see if the given ColorRGBA is equals to the data stored in the buffer at the given data index.
+ *
+ * @param check
+ * the color to check against - null will return false.
+ * @param buf
+ * the buffer to compare data with
+ * @param index
+ * the position (in terms of colors, not floats) of the color in the buffer to check against
+ * @return
+ */
+ public static boolean equals(final ReadOnlyColorRGBA check, final FloatBuffer buf, final int index) {
+ final ColorRGBA temp = new ColorRGBA();
+ populateFromBuffer(temp, buf, index);
+ return temp.equals(check);
+ }
+
+ // // -- Vector4 METHODS -- ////
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector4 objects. The FloatBuffer will be 4 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[0].z, data[0].w, data[1].x... etc.
+ *
+ * @param offset
+ * the starting index to read from in our data array
+ * @param length
+ * the number of vectors to read
+ * @param data
+ * array of Vector4 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final ReadOnlyVector4... data) {
+ if (data == null) {
+ return null;
+ }
+ return createFloatBuffer(0, data.length, data);
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector4 objects. The FloatBuffer will be 4 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[0].z, data[0].w, data[1].x... etc.
+ *
+ * @param data
+ * array of Vector4 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector4... data) {
+ if (data == null) {
+ return null;
+ }
+ final FloatBuffer buff = createFloatBuffer(4 * length);
+ for (int x = offset; x < length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].getXf()).put(data[x].getYf()).put(data[x].getZf()).put(data[x].getWf());
+ } else {
+ buff.put(0).put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector4 object data.
+ *
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector4Buffer(final int vertices) {
+ final FloatBuffer vBuff = createFloatBuffer(4 * vertices);
+ return vBuff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector4 object data only if the
+ * given buffer if not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector4Buffer(final FloatBuffer buf, final int vertices) {
+ if (buf != null && buf.limit() == 4 * vertices) {
+ buf.rewind();
+ return buf;
+ }
+
+ return createFloatBuffer(4 * vertices);
+ }
+
+ /**
+ * Sets the data contained in the given Vector4 into the FloatBuffer at the specified index.
+ *
+ * @param vector
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the position to place the data; in terms of vectors not floats
+ */
+ public static void setInBuffer(final ReadOnlyVector4 vector, final FloatBuffer buf, final int index) {
+ if (buf == null) {
+ return;
+ }
+ if (vector == null) {
+ buf.put(index * 4, 0);
+ buf.put((index * 4) + 1, 0);
+ buf.put((index * 4) + 2, 0);
+ buf.put((index * 4) + 3, 0);
+ } else {
+ buf.put(index * 4, vector.getXf());
+ buf.put((index * 4) + 1, vector.getYf());
+ buf.put((index * 4) + 2, vector.getZf());
+ buf.put((index * 4) + 3, vector.getWf());
+ }
+ }
+
+ /**
+ * Updates the values of the given vector from the specified buffer at the index provided.
+ *
+ * @param vector
+ * the vector to set data on
+ * @param buf
+ * the buffer to read from
+ * @param index
+ * the position (in terms of vectors, not floats) to read from the buffer
+ */
+ public static void populateFromBuffer(final Vector4 vector, final FloatBuffer buf, final int index) {
+ vector.setX(buf.get(index * 4));
+ vector.setY(buf.get(index * 4 + 1));
+ vector.setZ(buf.get(index * 4 + 2));
+ vector.setW(buf.get(index * 4 + 3));
+ }
+
+ /**
+ * Generates a Vector4 array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of Vector3 objects
+ */
+ public static Vector4[] getVector4Array(final FloatBuffer buff) {
+ buff.clear();
+ final Vector4[] verts = new Vector4[buff.limit() / 4];
+ for (int x = 0; x < verts.length; x++) {
+ final Vector4 v = new Vector4(buff.get(), buff.get(), buff.get(), buff.get());
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Generates a Vector4 array from the given FloatBufferData.
+ *
+ * @param buff
+ * the FloatBufferData to read from
+ * @param defaults
+ * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is
+ * smaller than 4.
+ * @return a newly generated array of Vector4 objects
+ */
+ public static Vector4[] getVector4Array(final FloatBufferData data, final ReadOnlyVector4 defaults) {
+ final FloatBuffer buff = data.getBuffer();
+ buff.clear();
+ final Vector4[] verts = new Vector4[data.getTupleCount()];
+ final int tupleSize = data.getValuesPerTuple();
+ for (int x = 0; x < verts.length; x++) {
+ final Vector4 v = new Vector4(defaults);
+ v.setX(buff.get());
+ if (tupleSize > 1) {
+ v.setY(buff.get());
+ }
+ if (tupleSize > 2) {
+ v.setZ(buff.get());
+ }
+ if (tupleSize > 3) {
+ v.setW(buff.get());
+ }
+ if (tupleSize > 4) {
+ buff.position(buff.position() + tupleSize - 4);
+ }
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Copies a Vector3 from one position in the buffer to another. The index values are in terms of vector number (eg,
+ * vector number 0 is positions 0-2 in the FloatBuffer.)
+ *
+ * @param buf
+ * the buffer to copy from/to
+ * @param fromPos
+ * the index of the vector to copy
+ * @param toPos
+ * the index to copy the vector to
+ */
+ public static void copyInternalVector4(final FloatBuffer buf, final int fromPos, final int toPos) {
+ copyInternal(buf, fromPos * 4, toPos * 4, 4);
+ }
+
+ /**
+ * Normalize a Vector4 in-buffer.
+ *
+ * @param buf
+ * the buffer to find the Vector4 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to normalize
+ */
+ public static void normalizeVector4(final FloatBuffer buf, final int index) {
+ final Vector4 temp = Vector4.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.normalizeLocal();
+ setInBuffer(temp, buf, index);
+ Vector4.releaseTempInstance(temp);
+ }
+
+ /**
+ * Add to a Vector4 in-buffer.
+ *
+ * @param toAdd
+ * the vector to add from
+ * @param buf
+ * the buffer to find the Vector4 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to add to
+ */
+ public static void addInBuffer(final ReadOnlyVector4 toAdd, final FloatBuffer buf, final int index) {
+ final Vector4 temp = Vector4.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.addLocal(toAdd);
+ setInBuffer(temp, buf, index);
+ Vector4.releaseTempInstance(temp);
+ }
+
+ /**
+ * Multiply and store a Vector3 in-buffer.
+ *
+ * @param toMult
+ * the vector to multiply against
+ * @param buf
+ * the buffer to find the Vector3 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to multiply
+ */
+ public static void multInBuffer(final ReadOnlyVector4 toMult, final FloatBuffer buf, final int index) {
+ final Vector4 temp = Vector4.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.multiplyLocal(toMult);
+ setInBuffer(temp, buf, index);
+ Vector4.releaseTempInstance(temp);
+ }
+
+ /**
+ * Checks to see if the given Vector3 is equals to the data stored in the buffer at the given data index.
+ *
+ * @param check
+ * the vector to check against - null will return false.
+ * @param buf
+ * the buffer to compare data with
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector in the buffer to check against
+ * @return
+ */
+ public static boolean equals(final ReadOnlyVector4 check, final FloatBuffer buf, final int index) {
+ final Vector4 temp = Vector4.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ final boolean equals = temp.equals(check);
+ Vector4.releaseTempInstance(temp);
+ return equals;
+ }
+
+ // // -- Vector3 METHODS -- ////
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector3 objects. The FloatBuffer will be 3 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[0].z, data[1].x... etc.
+ *
+ * @param data
+ * array of Vector3 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final ReadOnlyVector3... data) {
+ if (data == null) {
+ return null;
+ }
+ return createFloatBuffer(0, data.length, data);
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector3 objects. The FloatBuffer will be 3 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[0].z, data[1].x... etc.
+ *
+ * @param offset
+ * the starting index to read from in our data array
+ * @param length
+ * the number of vectors to read
+ * @param data
+ * array of Vector3 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector3... data) {
+ if (data == null) {
+ return null;
+ }
+ final FloatBuffer buff = createFloatBuffer(3 * length);
+ for (int x = offset; x < length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].getXf()).put(data[x].getYf()).put(data[x].getZf());
+ } else {
+ buff.put(0).put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector3 object data.
+ *
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector3Buffer(final int vertices) {
+ final FloatBuffer vBuff = createFloatBuffer(3 * vertices);
+ return vBuff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector3 object data only if the
+ * given buffer is not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector3Buffer(final FloatBuffer buf, final int vertices) {
+ if (buf != null && buf.limit() == 3 * vertices) {
+ buf.rewind();
+ return buf;
+ }
+
+ return createFloatBuffer(3 * vertices);
+ }
+
+ /**
+ * Sets the data contained in the given Vector3 into the FloatBuffer at the specified index.
+ *
+ * @param vector
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the position to place the data; in terms of vectors not floats
+ */
+ public static void setInBuffer(final ReadOnlyVector3 vector, final FloatBuffer buf, final int index) {
+ if (buf == null) {
+ return;
+ }
+ if (vector == null) {
+ buf.put(index * 3, 0);
+ buf.put((index * 3) + 1, 0);
+ buf.put((index * 3) + 2, 0);
+ } else {
+ buf.put(index * 3, vector.getXf());
+ buf.put((index * 3) + 1, vector.getYf());
+ buf.put((index * 3) + 2, vector.getZf());
+ }
+ }
+
+ /**
+ * Updates the values of the given vector from the specified buffer at the index provided.
+ *
+ * @param vector
+ * the vector to set data on
+ * @param buf
+ * the buffer to read from
+ * @param index
+ * the position (in terms of vectors, not floats) to read from the buf
+ */
+ public static void populateFromBuffer(final Vector3 vector, final FloatBuffer buf, final int index) {
+ vector.setX(buf.get(index * 3));
+ vector.setY(buf.get(index * 3 + 1));
+ vector.setZ(buf.get(index * 3 + 2));
+ }
+
+ /**
+ * Generates a Vector3 array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of Vector3 objects
+ */
+ public static Vector3[] getVector3Array(final FloatBuffer buff) {
+ buff.clear();
+ final Vector3[] verts = new Vector3[buff.limit() / 3];
+ for (int x = 0; x < verts.length; x++) {
+ final Vector3 v = new Vector3(buff.get(), buff.get(), buff.get());
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Generates a Vector3 array from the given FloatBufferData.
+ *
+ * @param buff
+ * the FloatBufferData to read from
+ * @param defaults
+ * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is
+ * smaller than 3.
+ * @return a newly generated array of Vector3 objects
+ */
+ public static Vector3[] getVector3Array(final FloatBufferData data, final ReadOnlyVector3 defaults) {
+ final FloatBuffer buff = data.getBuffer();
+ buff.clear();
+ final Vector3[] verts = new Vector3[data.getTupleCount()];
+ final int tupleSize = data.getValuesPerTuple();
+ for (int x = 0; x < verts.length; x++) {
+ final Vector3 v = new Vector3(defaults);
+ v.setX(buff.get());
+ if (tupleSize > 1) {
+ v.setY(buff.get());
+ }
+ if (tupleSize > 2) {
+ v.setZ(buff.get());
+ }
+ if (tupleSize > 3) {
+ buff.position(buff.position() + tupleSize - 3);
+ }
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Copies a Vector3 from one position in the buffer to another. The index values are in terms of vector number (eg,
+ * vector number 0 is positions 0-2 in the FloatBuffer.)
+ *
+ * @param buf
+ * the buffer to copy from/to
+ * @param fromPos
+ * the index of the vector to copy
+ * @param toPos
+ * the index to copy the vector to
+ */
+ public static void copyInternalVector3(final FloatBuffer buf, final int fromPos, final int toPos) {
+ copyInternal(buf, fromPos * 3, toPos * 3, 3);
+ }
+
+ /**
+ * Normalize a Vector3 in-buffer.
+ *
+ * @param buf
+ * the buffer to find the Vector3 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to normalize
+ */
+ public static void normalizeVector3(final FloatBuffer buf, final int index) {
+ final Vector3 temp = Vector3.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.normalizeLocal();
+ setInBuffer(temp, buf, index);
+ Vector3.releaseTempInstance(temp);
+ }
+
+ /**
+ * Add to a Vector3 in-buffer.
+ *
+ * @param toAdd
+ * the vector to add from
+ * @param buf
+ * the buffer to find the Vector3 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to add to
+ */
+ public static void addInBuffer(final ReadOnlyVector3 toAdd, final FloatBuffer buf, final int index) {
+ final Vector3 temp = Vector3.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.addLocal(toAdd);
+ setInBuffer(temp, buf, index);
+ Vector3.releaseTempInstance(temp);
+ }
+
+ /**
+ * Multiply and store a Vector3 in-buffer.
+ *
+ * @param toMult
+ * the vector to multiply against
+ * @param buf
+ * the buffer to find the Vector3 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to multiply
+ */
+ public static void multInBuffer(final ReadOnlyVector3 toMult, final FloatBuffer buf, final int index) {
+ final Vector3 temp = Vector3.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.multiplyLocal(toMult);
+ setInBuffer(temp, buf, index);
+ Vector3.releaseTempInstance(temp);
+ }
+
+ /**
+ * Checks to see if the given Vector3 is equals to the data stored in the buffer at the given data index.
+ *
+ * @param check
+ * the vector to check against - null will return false.
+ * @param buf
+ * the buffer to compare data with
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector in the buffer to check against
+ * @return
+ */
+ public static boolean equals(final ReadOnlyVector3 check, final FloatBuffer buf, final int index) {
+ final Vector3 temp = Vector3.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ final boolean equals = temp.equals(check);
+ Vector3.releaseTempInstance(temp);
+ return equals;
+ }
+
+ // // -- Vector2 METHODS -- ////
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector2 objects. The FloatBuffer will be 2 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[1].x... etc.
+ *
+ * @param data
+ * array of Vector2 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final ReadOnlyVector2... data) {
+ if (data == null) {
+ return null;
+ }
+ return createFloatBuffer(0, data.length, data);
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector2 objects. The FloatBuffer will be 2 * data.length long
+ * and contain the vector data as data[0].x, data[0].y, data[1].x... etc.
+ *
+ * @param offset
+ * the starting index to read from in our data array
+ * @param length
+ * the number of vectors to read
+ * @param data
+ * array of Vector2 objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final int offset, final int length, final ReadOnlyVector2... data) {
+ if (data == null) {
+ return null;
+ }
+ final FloatBuffer buff = createFloatBuffer(2 * length);
+ for (int x = offset; x < length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].getXf()).put(data[x].getYf());
+ } else {
+ buff.put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector2 object data.
+ *
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector2Buffer(final int vertices) {
+ final FloatBuffer vBuff = createFloatBuffer(2 * vertices);
+ return vBuff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified number of Vector2 object data only if the
+ * given buffer if not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param vertices
+ * number of vertices that need to be held by the newly created buffer
+ * @return the requested new FloatBuffer
+ */
+ public static FloatBuffer createVector2Buffer(final FloatBuffer buf, final int vertices) {
+ if (buf != null && buf.limit() == 2 * vertices) {
+ buf.rewind();
+ return buf;
+ }
+
+ return createFloatBuffer(2 * vertices);
+ }
+
+ /**
+ * Sets the data contained in the given Vector2 into the FloatBuffer at the specified index.
+ *
+ * @param vector
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the position to place the data; in terms of vectors not floats
+ */
+ public static void setInBuffer(final ReadOnlyVector2 vector, final FloatBuffer buf, final int index) {
+ buf.put(index * 2, vector.getXf());
+ buf.put((index * 2) + 1, vector.getYf());
+ }
+
+ /**
+ * Updates the values of the given vector from the specified buffer at the index provided.
+ *
+ * @param vector
+ * the vector to set data on
+ * @param buf
+ * the buffer to read from
+ * @param index
+ * the position (in terms of vectors, not floats) to read from the buf
+ */
+ public static void populateFromBuffer(final Vector2 vector, final FloatBuffer buf, final int index) {
+ vector.setX(buf.get(index * 2));
+ vector.setY(buf.get(index * 2 + 1));
+ }
+
+ /**
+ * Generates a Vector2 array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of Vector2 objects
+ */
+ public static Vector2[] getVector2Array(final FloatBuffer buff) {
+ buff.clear();
+ final Vector2[] verts = new Vector2[buff.limit() / 2];
+ for (int x = 0; x < verts.length; x++) {
+ final Vector2 v = new Vector2(buff.get(), buff.get());
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Generates a Vector2 array from the given FloatBufferData.
+ *
+ * @param buff
+ * the FloatBufferData to read from
+ * @param defaults
+ * a default value to set each color to, used when the tuple size of the given {@link FloatBufferData} is
+ * smaller than 2.
+ * @return a newly generated array of Vector2 objects
+ */
+ public static Vector2[] getVector2Array(final FloatBufferData data, final ReadOnlyVector2 defaults) {
+ final FloatBuffer buff = data.getBuffer();
+ buff.clear();
+ final Vector2[] verts = new Vector2[data.getTupleCount()];
+ final int tupleSize = data.getValuesPerTuple();
+ for (int x = 0; x < verts.length; x++) {
+ final Vector2 v = new Vector2(defaults);
+ v.setX(buff.get());
+ if (tupleSize > 1) {
+ v.setY(buff.get());
+ }
+ if (tupleSize > 2) {
+ buff.position(buff.position() + tupleSize - 2);
+ }
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Copies a Vector2 from one position in the buffer to another. The index values are in terms of vector number (eg,
+ * vector number 0 is positions 0-1 in the FloatBuffer.)
+ *
+ * @param buf
+ * the buffer to copy from/to
+ * @param fromPos
+ * the index of the vector to copy
+ * @param toPos
+ * the index to copy the vector to
+ */
+ public static void copyInternalVector2(final FloatBuffer buf, final int fromPos, final int toPos) {
+ copyInternal(buf, fromPos * 2, toPos * 2, 2);
+ }
+
+ /**
+ * Normalize a Vector2 in-buffer.
+ *
+ * @param buf
+ * the buffer to find the Vector2 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to normalize
+ */
+ public static void normalizeVector2(final FloatBuffer buf, final int index) {
+ final Vector2 temp = Vector2.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.normalizeLocal();
+ setInBuffer(temp, buf, index);
+ Vector2.releaseTempInstance(temp);
+ }
+
+ /**
+ * Add to a Vector2 in-buffer.
+ *
+ * @param toAdd
+ * the vector to add from
+ * @param buf
+ * the buffer to find the Vector2 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to add to
+ */
+ public static void addInBuffer(final ReadOnlyVector2 toAdd, final FloatBuffer buf, final int index) {
+ final Vector2 temp = Vector2.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.addLocal(toAdd);
+ setInBuffer(temp, buf, index);
+ Vector2.releaseTempInstance(temp);
+ }
+
+ /**
+ * Multiply and store a Vector2 in-buffer.
+ *
+ * @param toMult
+ * the vector to multiply against
+ * @param buf
+ * the buffer to find the Vector2 within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector to multiply
+ */
+ public static void multInBuffer(final ReadOnlyVector2 toMult, final FloatBuffer buf, final int index) {
+ final Vector2 temp = Vector2.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ temp.multiplyLocal(toMult);
+ setInBuffer(temp, buf, index);
+ Vector2.releaseTempInstance(temp);
+ }
+
+ /**
+ * Checks to see if the given Vector2 is equals to the data stored in the buffer at the given data index.
+ *
+ * @param check
+ * the vector to check against - null will return false.
+ * @param buf
+ * the buffer to compare data with
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector in the buffer to check against
+ * @return
+ */
+ public static boolean equals(final ReadOnlyVector2 check, final FloatBuffer buf, final int index) {
+ final Vector2 temp = Vector2.fetchTempInstance();
+ populateFromBuffer(temp, buf, index);
+ final boolean equals = temp.equals(check);
+ Vector2.releaseTempInstance(temp);
+ return equals;
+ }
+
+ // // -- INT METHODS -- ////
+
+ /**
+ * Generate a new IntBuffer using the given array of ints. The IntBuffer will be data.length long and contain the
+ * int data as data[0], data[1]... etc.
+ *
+ * @param data
+ * array of ints to place into a new IntBuffer
+ */
+ public static IntBuffer createIntBuffer(final int... data) {
+ if (data == null) {
+ return null;
+ }
+ final IntBuffer buff = createIntBuffer(data.length);
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new int[] array and populate it with the given IntBuffer's contents.
+ *
+ * @param buff
+ * the IntBuffer to read from
+ * @return a new int array populated from the IntBuffer
+ */
+ public static int[] getIntArray(final IntBuffer buff) {
+ if (buff == null) {
+ return null;
+ }
+ buff.rewind();
+ final int[] inds = new int[buff.limit()];
+ for (int x = 0; x < inds.length; x++) {
+ inds[x] = buff.get();
+ }
+ return inds;
+ }
+
+ /**
+ * Create a new int[] array and populate it with the given IndexBufferData's contents.
+ *
+ * @param buff
+ * the IndexBufferData to read from
+ * @return a new int array populated from the IndexBufferData
+ */
+ public static int[] getIntArray(final IndexBufferData<?> buff) {
+ if (buff == null || buff.getBufferLimit() == 0) {
+ return null;
+ }
+ buff.getBuffer().rewind();
+ final int[] inds = new int[buff.getBufferLimit()];
+ for (int x = 0; x < inds.length; x++) {
+ inds[x] = buff.get();
+ }
+ return inds;
+ }
+
+ /**
+ * Create a new float[] array and populate it with the given FloatBuffer's contents.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a new float array populated from the FloatBuffer
+ */
+ public static float[] getFloatArray(final FloatBuffer buff) {
+ if (buff == null) {
+ return null;
+ }
+ buff.clear();
+ final float[] inds = new float[buff.limit()];
+ for (int x = 0; x < inds.length; x++) {
+ inds[x] = buff.get();
+ }
+ return inds;
+ }
+
+ // // -- GENERAL DOUBLE ROUTINES -- ////
+
+ /**
+ * Create a new DoubleBuffer of the specified size.
+ *
+ * @param size
+ * required number of double to store.
+ * @return the new DoubleBuffer
+ */
+ public static DoubleBuffer createDoubleBufferOnHeap(final int size) {
+ final DoubleBuffer buf = ByteBuffer.allocate(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
+ buf.clear();
+ return buf;
+ }
+
+ /**
+ * Create a new DoubleBuffer of the specified size.
+ *
+ * @param size
+ * required number of double to store.
+ * @return the new DoubleBuffer
+ */
+ public static DoubleBuffer createDoubleBuffer(final int size) {
+ final DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
+ buf.clear();
+ if (Constants.trackDirectMemory) {
+ trackingHash.put(buf, ref);
+ }
+ return buf;
+ }
+
+ /**
+ * Create a new DoubleBuffer of an appropriate size to hold the specified number of doubles only if the given buffer
+ * if not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param size
+ * number of doubles that need to be held by the newly created buffer
+ * @return the requested new DoubleBuffer
+ */
+ public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, final int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createDoubleBuffer(size);
+ return buf;
+ }
+
+ /**
+ * Creates a new DoubleBuffer with the same contents as the given DoubleBuffer. The new DoubleBuffer is seperate
+ * from the old one and changes are not reflected across. If you want to reflect changes, consider using
+ * Buffer.duplicate().
+ *
+ * @param buf
+ * the DoubleBuffer to copy
+ * @return the copy
+ */
+ public static DoubleBuffer clone(final DoubleBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final DoubleBuffer copy;
+ if (buf.isDirect()) {
+ copy = createDoubleBuffer(buf.limit());
+ } else {
+ copy = createDoubleBufferOnHeap(buf.limit());
+ }
+ copy.put(buf);
+
+ return copy;
+ }
+
+ // // -- GENERAL FLOAT ROUTINES -- ////
+
+ /**
+ * Create a new FloatBuffer of the specified size.
+ *
+ * @param size
+ * required number of floats to store.
+ * @return the new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final int size) {
+ final FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ buf.clear();
+ if (Constants.trackDirectMemory) {
+ trackingHash.put(buf, ref);
+ }
+ return buf;
+ }
+
+ /**
+ * Create a new FloatBuffer of the specified size.
+ *
+ * @param size
+ * required number of floats to store.
+ * @return the new FloatBuffer
+ */
+ public static FloatBuffer createFloatBufferOnHeap(final int size) {
+ final FloatBuffer buf = ByteBuffer.allocate(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ buf.clear();
+ return buf;
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of float primitives.
+ *
+ * @param data
+ * array of float primitives to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final float... data) {
+ return createFloatBuffer(null, data);
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of float primitives.
+ *
+ * @param data
+ * array of float primitives to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(final FloatBuffer reuseStore, final float... data) {
+ if (data == null) {
+ return null;
+ }
+ final FloatBuffer buff;
+ if (reuseStore == null || reuseStore.capacity() != data.length) {
+ buff = createFloatBuffer(data.length);
+ } else {
+ buff = reuseStore;
+ buff.clear();
+ }
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ public static IntBuffer createIntBuffer(final IntBuffer reuseStore, final int... data) {
+ if (data == null) {
+ return null;
+ }
+ final IntBuffer buff;
+ if (reuseStore == null || reuseStore.capacity() != data.length) {
+ buff = createIntBuffer(data.length);
+ } else {
+ buff = reuseStore;
+ buff.clear();
+ }
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Copies floats from one buffer to another.
+ *
+ * @param source
+ * the buffer to copy from
+ * @param fromPos
+ * the starting point to copy from
+ * @param destination
+ * the buffer to copy to
+ * @param toPos
+ * the starting point to copy to
+ * @param length
+ * the number of floats to copy
+ */
+ public static void copy(final FloatBuffer source, final int fromPos, final FloatBuffer destination,
+ final int toPos, final int length) {
+ final int oldLimit = source.limit();
+ source.position(fromPos);
+ source.limit(fromPos + length);
+ destination.position(toPos);
+ destination.put(source);
+ source.limit(oldLimit);
+ }
+
+ /**
+ * Copies floats from one position in the buffer to another.
+ *
+ * @param buf
+ * the buffer to copy from/to
+ * @param fromPos
+ * the starting point to copy from
+ * @param toPos
+ * the starting point to copy to
+ * @param length
+ * the number of floats to copy
+ */
+ public static void copyInternal(final FloatBuffer buf, final int fromPos, final int toPos, final int length) {
+ final float[] data = new float[length];
+ buf.position(fromPos);
+ buf.get(data);
+ buf.position(toPos);
+ buf.put(data);
+ }
+
+ /**
+ * Creates a new FloatBuffer with the same contents as the given FloatBuffer. The new FloatBuffer is seperate from
+ * the old one and changes are not reflected across. If you want to reflect changes, consider using
+ * Buffer.duplicate().
+ *
+ * @param buf
+ * the FloatBuffer to copy
+ * @return the copy
+ */
+ public static FloatBuffer clone(final FloatBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final FloatBuffer copy;
+ if (buf.isDirect()) {
+ copy = createFloatBuffer(buf.limit());
+ } else {
+ copy = createFloatBufferOnHeap(buf.limit());
+ }
+ copy.put(buf);
+
+ return copy;
+ }
+
+ // // -- GENERAL INT ROUTINES -- ////
+
+ /**
+ * Create a new IntBuffer of the specified size.
+ *
+ * @param size
+ * required number of ints to store.
+ * @return the new IntBuffer
+ */
+ public static IntBuffer createIntBufferOnHeap(final int size) {
+ final IntBuffer buf = ByteBuffer.allocate(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
+ buf.clear();
+ return buf;
+ }
+
+ /**
+ * Create a new IntBuffer of the specified size.
+ *
+ * @param size
+ * required number of ints to store.
+ * @return the new IntBuffer
+ */
+ public static IntBuffer createIntBuffer(final int size) {
+ final IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
+ buf.clear();
+ if (Constants.trackDirectMemory) {
+ trackingHash.put(buf, ref);
+ }
+ return buf;
+ }
+
+ /**
+ * Create a new IntBuffer of an appropriate size to hold the specified number of ints only if the given buffer if
+ * not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param size
+ * number of ints that need to be held by the newly created buffer
+ * @return the requested new IntBuffer
+ */
+ public static IntBuffer createIntBuffer(IntBuffer buf, final int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createIntBuffer(size);
+ return buf;
+ }
+
+ /**
+ * Creates a new IntBuffer with the same contents as the given IntBuffer. The new IntBuffer is seperate from the old
+ * one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate().
+ *
+ * @param buf
+ * the IntBuffer to copy
+ * @return the copy
+ */
+ public static IntBuffer clone(final IntBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final IntBuffer copy;
+ if (buf.isDirect()) {
+ copy = createIntBuffer(buf.limit());
+ } else {
+ copy = createIntBufferOnHeap(buf.limit());
+ }
+ copy.put(buf);
+
+ return copy;
+ }
+
+ // // -- GENERAL BYTE ROUTINES -- ////
+
+ /**
+ * Create a new ByteBuffer of the specified size.
+ *
+ * @param size
+ * required number of ints to store.
+ * @return the new IntBuffer
+ */
+ public static ByteBuffer createByteBuffer(final int size) {
+ final ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ buf.clear();
+ if (Constants.trackDirectMemory) {
+ trackingHash.put(buf, ref);
+ }
+ return buf;
+ }
+
+ /**
+ * Create a new ByteBuffer of an appropriate size to hold the specified number of ints only if the given buffer if
+ * not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param size
+ * number of bytes that need to be held by the newly created buffer
+ * @return the requested new IntBuffer
+ */
+ public static ByteBuffer createByteBuffer(ByteBuffer buf, final int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createByteBuffer(size);
+ return buf;
+ }
+
+ /**
+ * Creates a new ByteBuffer with the same contents as the given ByteBuffer. The new ByteBuffer is seperate from the
+ * old one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate().
+ *
+ * @param buf
+ * the ByteBuffer to copy
+ * @return the copy
+ */
+ public static ByteBuffer clone(final ByteBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final ByteBuffer copy;
+ if (buf.isDirect()) {
+ copy = createByteBuffer(buf.limit());
+ } else {
+ copy = createByteBufferOnHeap(buf.limit());
+ }
+ copy.put(buf);
+
+ return copy;
+ }
+
+ // // -- GENERAL SHORT ROUTINES -- ////
+
+ /**
+ * Create a new ShortBuffer of the specified size.
+ *
+ * @param size
+ * required number of shorts to store.
+ * @return the new ShortBuffer
+ */
+ public static ShortBuffer createShortBufferOnHeap(final int size) {
+ final ShortBuffer buf = ByteBuffer.allocate(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
+ buf.clear();
+ return buf;
+ }
+
+ /**
+ * Create a new ShortBuffer of the specified size.
+ *
+ * @param size
+ * required number of shorts to store.
+ * @return the new ShortBuffer
+ */
+ public static ShortBuffer createShortBuffer(final int size) {
+ final ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
+ buf.clear();
+ if (Constants.trackDirectMemory) {
+ trackingHash.put(buf, ref);
+ }
+ return buf;
+ }
+
+ /**
+ * Generate a new ShortBuffer using the given array of short primitives.
+ *
+ * @param data
+ * array of short primitives to place into a new ShortBuffer
+ */
+ public static ShortBuffer createShortBuffer(final short... data) {
+ if (data == null) {
+ return null;
+ }
+ final ShortBuffer buff = createShortBuffer(data.length);
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new ShortBuffer of an appropriate size to hold the specified number of shorts only if the given buffer
+ * if not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param size
+ * number of shorts that need to be held by the newly created buffer
+ * @return the requested new ShortBuffer
+ */
+ public static ShortBuffer createShortBuffer(ShortBuffer buf, final int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createShortBuffer(size);
+ return buf;
+ }
+
+ /**
+ * Creates a new ShortBuffer with the same contents as the given ShortBuffer. The new ShortBuffer is seperate from
+ * the old one and changes are not reflected across. If you want to reflect changes, consider using
+ * Buffer.duplicate().
+ *
+ * @param buf
+ * the ShortBuffer to copy
+ * @return the copy
+ */
+ public static ShortBuffer clone(final ShortBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final ShortBuffer copy;
+ if (buf.isDirect()) {
+ copy = createShortBuffer(buf.limit());
+ } else {
+ copy = createShortBufferOnHeap(buf.limit());
+ }
+ copy.put(buf);
+
+ return copy;
+ }
+
+ /**
+ * Ensures there is at least the <code>required</code> number of entries left after the current position of the
+ * buffer. If the buffer is too small a larger one is created and the old one copied to the new buffer.
+ *
+ * @param buffer
+ * buffer that should be checked/copied (may be null)
+ * @param required
+ * minimum number of elements that should be remaining in the returned buffer
+ * @return a buffer large enough to receive at least the <code>required</code> number of entries, same position as
+ * the input buffer, not null
+ */
+ public static FloatBuffer ensureLargeEnough(FloatBuffer buffer, final int required) {
+ if (buffer == null || (buffer.remaining() < required)) {
+ final int position = (buffer != null ? buffer.position() : 0);
+ final FloatBuffer newVerts = createFloatBuffer(position + required);
+ if (buffer != null) {
+ buffer.rewind();
+ newVerts.put(buffer);
+ newVerts.position(position);
+ }
+ buffer = newVerts;
+ }
+ return buffer;
+ }
+
+ // // -- GENERAL INDEXBUFFERDATA ROUTINES -- ////
+
+ /**
+ * Create a new IndexBufferData of the specified size. The specific implementation will be chosen based on the max
+ * value you need to store in your buffer. If that value is less than 2^8, a ByteBufferData is used. If it is less
+ * than 2^16, a ShortBufferData is used. Otherwise an IntBufferData is used.
+ *
+ * @param size
+ * required number of values to store.
+ * @param maxValue
+ * the largest value you will need to store in your buffer. Often this is equal to
+ * ("size of vertex buffer" - 1).
+ * @return the new IndexBufferData
+ */
+ public static IndexBufferData<?> createIndexBufferData(final int size, final int maxValue) {
+ if (maxValue < 256) { // 2^8
+ return createIndexBufferData(size, ByteBufferData.class);
+ } else if (maxValue < 65536) { // 2^16
+ return createIndexBufferData(size, ShortBufferData.class);
+ } else {
+ return createIndexBufferData(size, IntBufferData.class);
+ }
+ }
+
+ /**
+ * Create a new IndexBufferData of the specified size and class.
+ *
+ * @param size
+ * required number of values to store.
+ * @param clazz
+ * The class type to instantiate.
+ * @return the new IndexBufferData
+ */
+ public static IndexBufferData<?> createIndexBufferData(final int size,
+ final Class<? extends IndexBufferData<?>> clazz) {
+ try {
+ return clazz.getConstructor(int.class).newInstance(size);
+ } catch (final Exception ex) {
+ throw new Ardor3dException(ex.getMessage(), ex);
+ }
+ }
+
+ /**
+ * Creates a new IndexBufferData with the same contents as the given IndexBufferData. The new IndexBufferData is
+ * separate from the old one and changes are not reflected across.
+ *
+ * @param buf
+ * the IndexBufferData to copy
+ * @return the copy
+ */
+ @SuppressWarnings("unchecked")
+ public static IndexBufferData<?> clone(final IndexBufferData<?> buf) {
+ if (buf == null) {
+ return null;
+ }
+
+ final IndexBufferData<?> copy = createIndexBufferData(buf.getBufferLimit(),
+ (Class<? extends IndexBufferData<?>>) buf.getClass());
+ if (buf.getBuffer() == null) {
+ copy.setBuffer(null);
+ } else {
+ buf.getBuffer().rewind();
+ copy.put(buf);
+ }
+
+ return copy;
+ }
+
+ // // -- GENERAL HEAP BYTE ROUTINES -- ////
+
+ /**
+ * Create a new ByteBuffer of the specified size.
+ *
+ * @param size
+ * required number of ints to store.
+ * @return the new IntBuffer
+ */
+ public static ByteBuffer createByteBufferOnHeap(final int size) {
+ final ByteBuffer buf = ByteBuffer.allocate(size).order(ByteOrder.nativeOrder());
+ buf.clear();
+ return buf;
+ }
+
+ /**
+ * Create a new ByteBuffer of an appropriate size to hold the specified number of ints only if the given buffer if
+ * not already the right size.
+ *
+ * @param buf
+ * the buffer to first check and rewind
+ * @param size
+ * number of bytes that need to be held by the newly created buffer
+ * @return the requested new IntBuffer
+ */
+ public static ByteBuffer createByteBufferOnHeap(ByteBuffer buf, final int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createByteBufferOnHeap(size);
+ return buf;
+ }
+
+ /**
+ * Creates a new ByteBuffer with the same contents as the given ByteBuffer. The new ByteBuffer is seperate from the
+ * old one and changes are not reflected across. If you want to reflect changes, consider using Buffer.duplicate().
+ *
+ * @param buf
+ * the ByteBuffer to copy
+ * @return the copy
+ */
+ public static ByteBuffer cloneOnHeap(final ByteBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ final ByteBuffer copy = createByteBufferOnHeap(buf.limit());
+ copy.put(buf);
+
+ return copy;
+ }
+
+ public static void printCurrentDirectMemory(StringBuilder store) {
+ long totalHeld = 0;
+ // make a new set to hold the keys to prevent concurrency issues.
+ final List<Buffer> bufs = new ArrayList<Buffer>(trackingHash.keySet());
+ int fBufs = 0, bBufs = 0, iBufs = 0, sBufs = 0, dBufs = 0;
+ int fBufsM = 0, bBufsM = 0, iBufsM = 0, sBufsM = 0, dBufsM = 0;
+ for (final Buffer b : bufs) {
+ if (b instanceof ByteBuffer) {
+ totalHeld += b.capacity();
+ bBufsM += b.capacity();
+ bBufs++;
+ } else if (b instanceof FloatBuffer) {
+ totalHeld += b.capacity() * 4;
+ fBufsM += b.capacity() * 4;
+ fBufs++;
+ } else if (b instanceof IntBuffer) {
+ totalHeld += b.capacity() * 4;
+ iBufsM += b.capacity() * 4;
+ iBufs++;
+ } else if (b instanceof ShortBuffer) {
+ totalHeld += b.capacity() * 2;
+ sBufsM += b.capacity() * 2;
+ sBufs++;
+ } else if (b instanceof DoubleBuffer) {
+ totalHeld += b.capacity() * 8;
+ dBufsM += b.capacity() * 8;
+ dBufs++;
+ }
+ }
+ final boolean printStout = store == null;
+ if (store == null) {
+ store = new StringBuilder();
+ }
+ store.append("Existing buffers: ").append(bufs.size()).append("\n");
+ store.append("(b: ").append(bBufs).append(" f: ").append(fBufs).append(" i: ").append(iBufs).append(" s: ")
+ .append(sBufs).append(" d: ").append(dBufs).append(")").append("\n");
+ store.append("Total direct memory held: ").append(totalHeld / 1024).append("kb\n");
+ store.append("(b: ").append(bBufsM / 1024).append("kb f: ").append(fBufsM / 1024).append("kb i: ")
+ .append(iBufsM / 1024).append("kb s: ").append(sBufsM / 1024).append("kb d: ").append(dBufsM / 1024)
+ .append("kb)").append("\n");
+ if (printStout) {
+ System.out.println(store.toString());
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java
new file mode 100644
index 0000000..c68fa8f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/CopyLogic.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.ardor3d.scenegraph.Spatial;
+
+@Deprecated
+public interface CopyLogic {
+
+ Spatial copy(Spatial source, AtomicBoolean recurse);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java
new file mode 100644
index 0000000..56b2fbe
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/Debugger.java
@@ -0,0 +1,701 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.bounding.BoundingSphere;
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.bounding.OrientedBoundingBox;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.NormalsMode;
+import com.ardor3d.scenegraph.shape.AxisRods;
+import com.ardor3d.scenegraph.shape.Box;
+import com.ardor3d.scenegraph.shape.OrientedBox;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.scenegraph.shape.Sphere;
+import com.ardor3d.util.ExtendedCamera;
+
+/**
+ * Debugger provides tools for viewing scene data such as boundings and normals.
+ *
+ * Make sure you set the RenderStateFactory before using this class.
+ *
+ * @see Debugger#setRenderStateFactory(RenderStateFactory)
+ */
+public final class Debugger {
+
+ // -- **** METHODS FOR DRAWING BOUNDING VOLUMES **** -- //
+
+ private static final Sphere boundingSphere = new Sphere("bsphere", 10, 10, 1);
+ static {
+ boundingSphere.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ boundingSphere.getSceneHints().setNormalsMode(NormalsMode.Off);
+ boundingSphere.setRenderState(new WireframeState());
+ boundingSphere.setRenderState(new ZBufferState());
+ boundingSphere.updateWorldRenderStates(false);
+ }
+ private static final Box boundingBox = new Box("bbox", new Vector3(), 1, 1, 1);
+ static {
+ boundingBox.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ boundingBox.getSceneHints().setNormalsMode(NormalsMode.Off);
+ boundingBox.setRenderState(new WireframeState());
+ boundingBox.setRenderState(new ZBufferState());
+ boundingBox.updateWorldRenderStates(false);
+ }
+ private static final OrientedBox boundingOB = new OrientedBox("bobox");
+ static {
+ boundingOB.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ boundingOB.getSceneHints().setNormalsMode(NormalsMode.Off);
+ boundingOB.setRenderState(new WireframeState());
+ boundingOB.setRenderState(new ZBufferState());
+ boundingOB.updateWorldRenderStates(false);
+ }
+
+ /**
+ * <code>drawBounds</code> draws the bounding volume for a given Spatial and its children.
+ *
+ * @param se
+ * the Spatial to draw boundings for.
+ * @param r
+ * the Renderer to use to draw the bounding.
+ */
+ public static void drawBounds(final Spatial se, final Renderer r) {
+ drawBounds(se, r, true);
+ }
+
+ /**
+ * <code>drawBounds</code> draws the bounding volume for a given Spatial and optionally its children.
+ *
+ * @param se
+ * the Spatial to draw boundings for.
+ * @param r
+ * the Renderer to use to draw the bounding.
+ * @param doChildren
+ * if true, boundings for any children will also be drawn
+ */
+ public static void drawBounds(final Spatial se, final Renderer r, boolean doChildren) {
+ if (se == null) {
+ return;
+ }
+
+ if (se.getWorldBound() != null && se.getSceneHints().getCullHint() != CullHint.Always) {
+ final Camera cam = Camera.getCurrentCamera();
+ final int state = cam.getPlaneState();
+ if (cam.contains(se.getWorldBound()) != Camera.FrustumIntersect.Outside) {
+ drawBounds(se.getWorldBound(), r);
+ } else {
+ doChildren = false;
+ }
+ cam.setPlaneState(state);
+ }
+ if (doChildren && se instanceof Node) {
+ final Node n = (Node) se;
+ if (n.getNumberOfChildren() != 0) {
+ for (int i = n.getNumberOfChildren(); --i >= 0;) {
+ drawBounds(n.getChild(i), r, true);
+ }
+ }
+ }
+ }
+
+ public static void drawBounds(final BoundingVolume bv, final Renderer r) {
+
+ switch (bv.getType()) {
+ case AABB:
+ drawBoundingBox((BoundingBox) bv, r);
+ break;
+ case Sphere:
+ drawBoundingSphere((BoundingSphere) bv, r);
+ break;
+ case OBB:
+ drawOBB((OrientedBoundingBox) bv, r);
+ break;
+ default:
+ break;
+ }
+ }
+
+ public static void setBoundsColor(final ReadOnlyColorRGBA color) {
+ boundingBox.setDefaultColor(color);
+ boundingOB.setDefaultColor(color);
+ boundingSphere.setDefaultColor(color);
+ }
+
+ public static void drawBoundingSphere(final BoundingSphere sphere, final Renderer r) {
+ boundingSphere.setData(sphere.getCenter(), 10, 10, sphere.getRadius());
+ boundingSphere.draw(r);
+ }
+
+ public static void drawBoundingBox(final BoundingBox box, final Renderer r) {
+ boundingBox.setData(box.getCenter(), box.getXExtent(), box.getYExtent(), box.getZExtent());
+ boundingBox.draw(r);
+ }
+
+ public static void drawOBB(final OrientedBoundingBox box, final Renderer r) {
+ boundingOB.getCenter().set(box.getCenter());
+ boundingOB.getxAxis().set(box.getXAxis());
+ boundingOB.getYAxis().set(box.getYAxis());
+ boundingOB.getZAxis().set(box.getZAxis());
+ boundingOB.getExtent().set(box.getExtent());
+ boundingOB.computeInformation();
+ boundingOB.draw(r);
+ }
+
+ // -- **** METHODS FOR DRAWING NORMALS **** -- //
+
+ private static final Line normalLines = new Line("normLine");
+ static {
+ normalLines.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ normalLines.setRenderState(new ZBufferState());
+ normalLines.setLineWidth(3.0f);
+ normalLines.getMeshData().setIndexMode(IndexMode.Lines);
+ normalLines.getMeshData().setVertexBuffer(BufferUtils.createVector3Buffer(500));
+ normalLines.getMeshData().setColorBuffer(BufferUtils.createColorBuffer(500));
+ normalLines.updateWorldRenderStates(false);
+ }
+ private static final Vector3 _normalVect = new Vector3(), _normalVect2 = new Vector3();
+ public static final ColorRGBA NORMAL_COLOR_BASE = new ColorRGBA(ColorRGBA.RED);
+ public static final ColorRGBA NORMAL_COLOR_TIP = new ColorRGBA(ColorRGBA.PINK);
+ public static final ColorRGBA TANGENT_COLOR_BASE = new ColorRGBA(ColorRGBA.ORANGE);
+ public static final ColorRGBA TANGENT_COLOR_TIP = new ColorRGBA(ColorRGBA.YELLOW);
+ protected static final BoundingBox measureBox = new BoundingBox();
+ public static double AUTO_NORMAL_RATIO = .05;
+
+ /**
+ * <code>drawNormals</code> draws lines representing normals for a given Spatial and its children.
+ *
+ * @param element
+ * the Spatial to draw normals for.
+ * @param r
+ * the Renderer to use to draw the normals.
+ */
+ public static void drawNormals(final Spatial element, final Renderer r) {
+ drawNormals(element, r, -1f, true);
+ }
+
+ public static void drawTangents(final Spatial element, final Renderer r) {
+ drawTangents(element, r, -1f, true);
+ }
+
+ /**
+ * <code>drawNormals</code> draws the normals for a given Spatial and optionally its children.
+ *
+ * @param element
+ * the Spatial to draw normals for.
+ * @param r
+ * the Renderer to use to draw the normals.
+ * @param size
+ * the length of the drawn normal (default is -1.0 which means autocalc based on boundings - if any).
+ * @param doChildren
+ * if true, normals for any children will also be drawn
+ */
+ public static void drawNormals(final Spatial element, final Renderer r, final double size, final boolean doChildren) {
+ if (element == null) {
+ return;
+ }
+
+ final Camera cam = Camera.getCurrentCamera();
+ final int state = cam.getPlaneState();
+ if (element.getWorldBound() != null && cam.contains(element.getWorldBound()) == Camera.FrustumIntersect.Outside) {
+ cam.setPlaneState(state);
+ return;
+ }
+ cam.setPlaneState(state);
+ if (element instanceof Mesh && element.getSceneHints().getCullHint() != CullHint.Always) {
+ final Mesh mesh = (Mesh) element;
+
+ double rSize = size;
+ if (rSize == -1) {
+ final BoundingVolume vol = element.getWorldBound();
+ if (vol != null) {
+ measureBox.setCenter(vol.getCenter());
+ measureBox.setXExtent(0);
+ measureBox.setYExtent(0);
+ measureBox.setZExtent(0);
+ measureBox.mergeLocal(vol);
+ rSize = AUTO_NORMAL_RATIO
+ * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3);
+ } else {
+ rSize = 1.0;
+ }
+
+ if (Double.isInfinite(rSize) || Double.isNaN(rSize)) {
+ rSize = 1.0;
+ }
+ }
+
+ final FloatBuffer norms = mesh.getMeshData().getNormalBuffer();
+ final FloatBuffer verts = mesh.getMeshData().getVertexBuffer();
+ if (norms != null && verts != null && norms.limit() == verts.limit()) {
+ FloatBuffer lineVerts = normalLines.getMeshData().getVertexBuffer();
+ if (lineVerts.capacity() < (3 * (2 * mesh.getMeshData().getVertexCount()))) {
+ normalLines.getMeshData().setVertexBuffer(null);
+ lineVerts = BufferUtils.createVector3Buffer(mesh.getMeshData().getVertexCount() * 2);
+ normalLines.getMeshData().setVertexBuffer(lineVerts);
+ } else {
+ lineVerts.clear();
+ lineVerts.limit(3 * 2 * mesh.getMeshData().getVertexCount());
+ normalLines.getMeshData().setVertexBuffer(lineVerts);
+ }
+
+ FloatBuffer lineColors = normalLines.getMeshData().getColorBuffer();
+ if (lineColors.capacity() < (4 * (2 * mesh.getMeshData().getVertexCount()))) {
+ normalLines.getMeshData().setColorBuffer(null);
+ lineColors = BufferUtils.createColorBuffer(mesh.getMeshData().getVertexCount() * 2);
+ normalLines.getMeshData().setColorBuffer(lineColors);
+ } else {
+ lineColors.clear();
+ }
+
+ IndexBufferData<?> lineInds = normalLines.getMeshData().getIndices();
+ if (lineInds == null || lineInds.getBufferCapacity() < (normalLines.getMeshData().getVertexCount())) {
+ normalLines.getMeshData().setIndices(null);
+ lineInds = BufferUtils.createIndexBufferData(mesh.getMeshData().getVertexCount() * 2, normalLines
+ .getMeshData().getVertexCount() - 1);
+ normalLines.getMeshData().setIndices(lineInds);
+ } else {
+ lineInds.getBuffer().clear();
+ lineInds.getBuffer().limit(normalLines.getMeshData().getVertexCount());
+ }
+
+ verts.rewind();
+ norms.rewind();
+ lineVerts.rewind();
+ lineInds.getBuffer().rewind();
+
+ for (int x = 0; x < mesh.getMeshData().getVertexCount(); x++) {
+ _normalVect.set(verts.get(), verts.get(), verts.get());
+ mesh.getWorldTransform().applyForward(_normalVect);
+ lineVerts.put(_normalVect.getXf());
+ lineVerts.put(_normalVect.getYf());
+ lineVerts.put(_normalVect.getZf());
+
+ lineColors.put(NORMAL_COLOR_BASE.getRed());
+ lineColors.put(NORMAL_COLOR_BASE.getGreen());
+ lineColors.put(NORMAL_COLOR_BASE.getBlue());
+ lineColors.put(NORMAL_COLOR_BASE.getAlpha());
+
+ lineInds.put(x * 2);
+
+ _normalVect2.set(norms.get(), norms.get(), norms.get());
+ mesh.getWorldTransform().applyForwardVector(_normalVect2).normalizeLocal().multiplyLocal(rSize);
+ _normalVect.addLocal(_normalVect2);
+ lineVerts.put(_normalVect.getXf());
+ lineVerts.put(_normalVect.getYf());
+ lineVerts.put(_normalVect.getZf());
+
+ lineColors.put(NORMAL_COLOR_TIP.getRed());
+ lineColors.put(NORMAL_COLOR_TIP.getGreen());
+ lineColors.put(NORMAL_COLOR_TIP.getBlue());
+ lineColors.put(NORMAL_COLOR_TIP.getAlpha());
+
+ lineInds.put((x * 2) + 1);
+ }
+
+ normalLines.onDraw(r);
+ }
+
+ }
+
+ if (doChildren && element instanceof Node) {
+ final Node n = (Node) element;
+ if (n.getNumberOfChildren() != 0) {
+ for (int i = n.getNumberOfChildren(); --i >= 0;) {
+ drawNormals(n.getChild(i), r, size, true);
+ }
+ }
+ }
+ }
+
+ public static void drawTangents(final Spatial element, final Renderer r, final double size, final boolean doChildren) {
+ if (element == null) {
+ return;
+ }
+
+ final Camera cam = Camera.getCurrentCamera();
+ final int state = cam.getPlaneState();
+ if (element.getWorldBound() != null && cam.contains(element.getWorldBound()) == Camera.FrustumIntersect.Outside) {
+ cam.setPlaneState(state);
+ return;
+ }
+ cam.setPlaneState(state);
+ if (element instanceof Mesh && element.getSceneHints().getCullHint() != CullHint.Always) {
+ final Mesh mesh = (Mesh) element;
+
+ double rSize = size;
+ if (rSize == -1) {
+ final BoundingVolume vol = element.getWorldBound();
+ if (vol != null) {
+ measureBox.setCenter(vol.getCenter());
+ measureBox.setXExtent(0);
+ measureBox.setYExtent(0);
+ measureBox.setZExtent(0);
+ measureBox.mergeLocal(vol);
+ rSize = AUTO_NORMAL_RATIO
+ * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3f);
+ } else {
+ rSize = 1.0;
+ }
+ }
+
+ final FloatBuffer norms = mesh.getMeshData().getTangentBuffer();
+ final FloatBuffer verts = mesh.getMeshData().getVertexBuffer();
+ if (norms != null && verts != null && norms.limit() == verts.limit()) {
+ FloatBuffer lineVerts = normalLines.getMeshData().getVertexBuffer();
+ if (lineVerts.capacity() < (3 * (2 * mesh.getMeshData().getVertexCount()))) {
+ normalLines.getMeshData().setVertexBuffer(null);
+ lineVerts = BufferUtils.createVector3Buffer(mesh.getMeshData().getVertexCount() * 2);
+ normalLines.getMeshData().setVertexBuffer(lineVerts);
+ } else {
+ lineVerts.clear();
+ lineVerts.limit(3 * 2 * mesh.getMeshData().getVertexCount());
+ normalLines.getMeshData().setVertexBuffer(lineVerts);
+ }
+
+ FloatBuffer lineColors = normalLines.getMeshData().getColorBuffer();
+ if (lineColors.capacity() < (4 * (2 * mesh.getMeshData().getVertexCount()))) {
+ normalLines.getMeshData().setColorBuffer(null);
+ lineColors = BufferUtils.createColorBuffer(mesh.getMeshData().getVertexCount() * 2);
+ normalLines.getMeshData().setColorBuffer(lineColors);
+ } else {
+ lineColors.clear();
+ }
+
+ IndexBufferData<?> lineInds = normalLines.getMeshData().getIndices();
+ if (lineInds == null || lineInds.getBufferCapacity() < (normalLines.getMeshData().getVertexCount())) {
+ normalLines.getMeshData().setIndices(null);
+ lineInds = BufferUtils.createIndexBufferData(mesh.getMeshData().getVertexCount() * 2, normalLines
+ .getMeshData().getVertexCount() - 1);
+ normalLines.getMeshData().setIndices(lineInds);
+ } else {
+ lineInds.getBuffer().clear();
+ lineInds.getBuffer().limit(normalLines.getMeshData().getVertexCount());
+ }
+
+ verts.rewind();
+ norms.rewind();
+ lineVerts.rewind();
+ lineInds.getBuffer().rewind();
+
+ for (int x = 0; x < mesh.getMeshData().getVertexCount(); x++) {
+ _normalVect.set(verts.get(), verts.get(), verts.get());
+ _normalVect.multiplyLocal(mesh.getWorldScale());
+ lineVerts.put(_normalVect.getXf());
+ lineVerts.put(_normalVect.getYf());
+ lineVerts.put(_normalVect.getZf());
+
+ lineColors.put(TANGENT_COLOR_BASE.getRed());
+ lineColors.put(TANGENT_COLOR_BASE.getGreen());
+ lineColors.put(TANGENT_COLOR_BASE.getBlue());
+ lineColors.put(TANGENT_COLOR_BASE.getAlpha());
+
+ lineInds.put(x * 2);
+
+ _normalVect.addLocal(norms.get() * rSize, norms.get() * rSize, norms.get() * rSize);
+ lineVerts.put(_normalVect.getXf());
+ lineVerts.put(_normalVect.getYf());
+ lineVerts.put(_normalVect.getZf());
+
+ lineColors.put(TANGENT_COLOR_TIP.getRed());
+ lineColors.put(TANGENT_COLOR_TIP.getGreen());
+ lineColors.put(TANGENT_COLOR_TIP.getBlue());
+ lineColors.put(TANGENT_COLOR_TIP.getAlpha());
+
+ lineInds.put((x * 2) + 1);
+ }
+
+ normalLines.setWorldTranslation(mesh.getWorldTranslation());
+ normalLines.setWorldRotation(mesh.getWorldRotation());
+ normalLines.onDraw(r);
+ }
+
+ }
+
+ if (doChildren && element instanceof Node) {
+ final Node n = (Node) element;
+ if (n.getNumberOfChildren() != 0) {
+ for (int i = n.getNumberOfChildren(); --i >= 0;) {
+ drawTangents(n.getChild(i), r, size, true);
+ }
+ }
+ }
+ }
+
+ // -- **** METHODS FOR DRAWING AXIS **** -- //
+
+ private static final AxisRods rods = new AxisRods("debug_rods", true, 1);
+ static {
+ rods.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ }
+ private static boolean axisInited = false;
+
+ public static void drawAxis(final Spatial spat, final Renderer r) {
+ drawAxis(spat, r, true, false);
+ }
+
+ public static void drawAxis(final Spatial spat, final Renderer r, final boolean drawChildren, final boolean drawAll) {
+ if (!axisInited) {
+ final BlendState blendState = new BlendState();
+ blendState.setBlendEnabled(true);
+ blendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ blendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ rods.setRenderState(blendState);
+ rods.updateGeometricState(0, false);
+ axisInited = true;
+ }
+
+ if (drawAll || (spat instanceof Mesh)) {
+ if (spat.getWorldBound() != null) {
+ double rSize;
+ final BoundingVolume vol = spat.getWorldBound();
+ if (vol != null) {
+ measureBox.setCenter(vol.getCenter());
+ measureBox.setXExtent(0);
+ measureBox.setYExtent(0);
+ measureBox.setZExtent(0);
+ measureBox.mergeLocal(vol);
+ rSize = 1 * ((measureBox.getXExtent() + measureBox.getYExtent() + measureBox.getZExtent()) / 3);
+ } else {
+ rSize = 1.0;
+ }
+
+ rods.setTranslation(spat.getWorldBound().getCenter());
+ rods.setScale(rSize);
+ } else {
+ rods.setTranslation(spat.getWorldTranslation());
+ rods.setScale(spat.getWorldScale());
+ }
+ rods.setRotation(spat.getWorldRotation());
+ rods.updateGeometricState(0, false);
+
+ rods.draw(r);
+ }
+
+ if ((spat instanceof Node) && drawChildren) {
+ final Node n = (Node) spat;
+ if (n.getNumberOfChildren() == 0) {
+ return;
+ }
+ for (int x = 0, count = n.getNumberOfChildren(); x < count; x++) {
+ drawAxis(n.getChild(x), r, drawChildren, drawAll);
+ }
+ }
+ }
+
+ // -- **** METHODS FOR DISPLAYING BUFFERS **** -- //
+ public static final int NORTHWEST = 0;
+ public static final int NORTHEAST = 1;
+ public static final int SOUTHEAST = 2;
+ public static final int SOUTHWEST = 3;
+
+ private static final Quad bQuad = new Quad("", 128, 128);
+ private static Texture2D bufTexture;
+ private static TextureRenderer bufTexRend;
+
+ static {
+ bQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ bQuad.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ public static void drawBuffer(final TextureStoreFormat rttFormat, final int location, final Renderer r) {
+ final Camera cam = Camera.getCurrentCamera();
+ drawBuffer(rttFormat, location, r, cam.getWidth() / 6.25);
+ }
+
+ public static void drawBuffer(final TextureStoreFormat rttFormat, final int location, final Renderer r,
+ final double size) {
+ final Camera cam = Camera.getCurrentCamera();
+ r.flushGraphics();
+ double locationX = cam.getWidth(), locationY = cam.getHeight();
+ bQuad.resize(size, (cam.getHeight() / (double) cam.getWidth()) * size);
+ if (bQuad.getLocalRenderState(RenderState.StateType.Texture) == null) {
+ final TextureState ts = new TextureState();
+ bufTexture = new Texture2D();
+ ts.setTexture(bufTexture);
+ bQuad.setRenderState(ts);
+ }
+
+ int width = cam.getWidth();
+ if (!MathUtils.isPowerOfTwo(width)) {
+ int newWidth = 2;
+ do {
+ newWidth <<= 1;
+
+ } while (newWidth < width);
+ bQuad.getMeshData().getTextureBuffer(0).put(4, width / (float) newWidth);
+ bQuad.getMeshData().getTextureBuffer(0).put(6, width / (float) newWidth);
+ width = newWidth;
+ }
+
+ int height = cam.getHeight();
+ if (!MathUtils.isPowerOfTwo(height)) {
+ int newHeight = 2;
+ do {
+ newHeight <<= 1;
+
+ } while (newHeight < height);
+ bQuad.getMeshData().getTextureBuffer(0).put(1, height / (float) newHeight);
+ bQuad.getMeshData().getTextureBuffer(0).put(7, height / (float) newHeight);
+ height = newHeight;
+ }
+ if (bufTexRend == null) {
+ bufTexRend = TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, r, ContextManager
+ .getCurrentContext().getCapabilities());
+ bufTexRend.setupTexture(bufTexture);
+ }
+ bufTexRend.copyToTexture(bufTexture, 0, 0, width, height, 0, 0);
+
+ final double loc = size * .75;
+ switch (location) {
+ case NORTHWEST:
+ locationX = loc;
+ locationY -= loc;
+ break;
+ case NORTHEAST:
+ locationX -= loc;
+ locationY -= loc;
+ break;
+ case SOUTHEAST:
+ locationX -= loc;
+ locationY = loc;
+ break;
+ case SOUTHWEST:
+ default:
+ locationX = loc;
+ locationY = loc;
+ break;
+ }
+
+ bQuad.setWorldTranslation(locationX, locationY, 0);
+
+ bQuad.updateGeometricState(0);
+ bQuad.onDraw(r);
+ r.flushGraphics();
+ }
+
+ // -- **** METHODS FOR DISPLAYING CAMERAS **** -- //
+ private static Line lineFrustum;
+ private static final ExtendedCamera extendedCamera = new ExtendedCamera();
+
+ public static void drawCameraFrustum(final Renderer r, final Camera camera, final ReadOnlyColorRGBA color,
+ final short pattern, final boolean drawOriginConnector) {
+ drawCameraFrustum(r, camera, camera.getFrustumNear(), camera.getFrustumFar(), color, pattern,
+ drawOriginConnector);
+ }
+
+ public static void drawCameraFrustum(final Renderer r, final Camera camera, final double fNear, final double fFar,
+ final ReadOnlyColorRGBA color, final short pattern, final boolean drawOriginConnector) {
+ if (lineFrustum == null) {
+ final FloatBuffer verts = BufferUtils.createVector3Buffer(24);
+ final FloatBuffer colors = BufferUtils.createColorBuffer(24);
+
+ lineFrustum = new Line("Lines", verts, null, colors, null);
+ lineFrustum.getMeshData().setIndexModes(
+ new IndexMode[] { IndexMode.LineLoop, IndexMode.LineLoop, IndexMode.Lines, IndexMode.Lines });
+ lineFrustum.getMeshData().setIndexLengths(new int[] { 4, 4, 8, 8 });
+ lineFrustum.setLineWidth(2);
+ lineFrustum.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ final BlendState lineBlendState = new BlendState();
+ lineBlendState.setEnabled(true);
+ lineBlendState.setBlendEnabled(true);
+ lineBlendState.setTestEnabled(true);
+ lineBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ lineBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ lineFrustum.setRenderState(lineBlendState);
+
+ final ZBufferState zstate = new ZBufferState();
+ lineFrustum.setRenderState(zstate);
+ lineFrustum.updateGeometricState(0.0);
+
+ lineFrustum.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ }
+
+ lineFrustum.setDefaultColor(color);
+ lineFrustum.setStipplePattern(pattern);
+
+ extendedCamera.set(camera);
+ extendedCamera.calculateFrustum(fNear, fFar);
+
+ final FloatBuffer colors = lineFrustum.getMeshData().getColorBuffer();
+ for (int i = 0; i < 16; i++) {
+ BufferUtils.setInBuffer(color, colors, i);
+ }
+ final float alpha = drawOriginConnector ? 0.4f : 0.0f;
+ for (int i = 16; i < 24; i++) {
+ colors.position(i * 4);
+ colors.put(color.getRed());
+ colors.put(color.getGreen());
+ colors.put(color.getBlue());
+ colors.put(alpha);
+ }
+
+ final Vector3[] corners = extendedCamera.getCorners();
+
+ final FloatBuffer verts = lineFrustum.getMeshData().getVertexBuffer();
+ BufferUtils.setInBuffer(corners[0], verts, 0);
+ BufferUtils.setInBuffer(corners[1], verts, 1);
+ BufferUtils.setInBuffer(corners[2], verts, 2);
+ BufferUtils.setInBuffer(corners[3], verts, 3);
+
+ BufferUtils.setInBuffer(corners[4], verts, 4);
+ BufferUtils.setInBuffer(corners[5], verts, 5);
+ BufferUtils.setInBuffer(corners[6], verts, 6);
+ BufferUtils.setInBuffer(corners[7], verts, 7);
+
+ BufferUtils.setInBuffer(corners[0], verts, 8);
+ BufferUtils.setInBuffer(corners[4], verts, 9);
+ BufferUtils.setInBuffer(corners[1], verts, 10);
+ BufferUtils.setInBuffer(corners[5], verts, 11);
+ BufferUtils.setInBuffer(corners[2], verts, 12);
+ BufferUtils.setInBuffer(corners[6], verts, 13);
+ BufferUtils.setInBuffer(corners[3], verts, 14);
+ BufferUtils.setInBuffer(corners[7], verts, 15);
+
+ BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 16);
+ BufferUtils.setInBuffer(corners[0], verts, 17);
+ BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 18);
+ BufferUtils.setInBuffer(corners[1], verts, 19);
+ BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 20);
+ BufferUtils.setInBuffer(corners[2], verts, 21);
+ BufferUtils.setInBuffer(extendedCamera.getLocation(), verts, 22);
+ BufferUtils.setInBuffer(corners[3], verts, 23);
+
+ lineFrustum.draw(r);
+ }
+
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java
new file mode 100644
index 0000000..53d5b49
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/GeometryTool.java
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.EnumSet;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.google.common.collect.Maps;
+
+/**
+ * This tool assists in reducing geometry information.<br>
+ *
+ * Note: Does not work with geometry using texcoords other than 2d coords. <br>
+ * TODO: Consider adding an option for "close enough" vertex matches... ie, smaller than X distance apart.<br>
+ */
+public abstract class GeometryTool {
+ private static final Logger logger = Logger.getLogger(GeometryTool.class.getName());
+
+ /**
+ * Condition options for determining if one vertex is "equal" to another.
+ */
+ public enum MatchCondition {
+ /** Vertices must have identical normals. */
+ Normal,
+ /** Vertices must have identical texture coords on all channels. */
+ UVs,
+ /** Vertices must have identical vertex coloring. */
+ Color,
+ /** Vertices must be in same group. */
+ Group;
+ }
+
+ /**
+ * Attempt to collapse duplicate vertex data in a given mesh. Vertices are considered duplicate if they occupy the
+ * same place in space and match the supplied conditions. All vertices in the mesh are considered part of the same
+ * vertex "group".
+ *
+ * @param mesh
+ * the mesh to reduce
+ * @param conditions
+ * our match conditions.
+ * @return a mapping of old vertex positions to their new positions.
+ */
+ public static VertMap minimizeVerts(final Mesh mesh, final EnumSet<MatchCondition> conditions) {
+ final VertGroupData groupData = new VertGroupData();
+ groupData.setGroupConditions(VertGroupData.DEFAULT_GROUP, conditions);
+ return minimizeVerts(mesh, groupData);
+ }
+
+ /**
+ * Attempt to collapse duplicate vertex data in a given mesh. Vertices are consider duplicate if they occupy the
+ * same place in space and match the supplied conditions. The conditions are supplied per vertex group.
+ *
+ * @param mesh
+ * the mesh to reduce
+ * @param groupData
+ * grouping data for the vertices in this mesh.
+ * @return a mapping of old vertex positions to their new positions.
+ */
+ public static VertMap minimizeVerts(final Mesh mesh, final VertGroupData groupData) {
+ final long start = System.currentTimeMillis();
+
+ int vertCount = -1;
+ final int oldCount = mesh.getMeshData().getVertexCount();
+ int newCount = 0;
+
+ final VertMap result = new VertMap(mesh);
+
+ // while we have not run through this optimization and ended up the same...
+ // XXX: could optimize this to run all in arrays, then write to buffer after while loop.
+ while (vertCount != newCount) {
+ vertCount = mesh.getMeshData().getVertexCount();
+ // go through each vert...
+ final Vector3[] verts = BufferUtils.getVector3Array(mesh.getMeshData().getVertexCoords(), Vector3.ZERO);
+ Vector3[] norms = null;
+ if (mesh.getMeshData().getNormalBuffer() != null) {
+ norms = BufferUtils.getVector3Array(mesh.getMeshData().getNormalCoords(), Vector3.UNIT_Y);
+ }
+
+ // see if we have vertex colors
+ ColorRGBA[] colors = null;
+ if (mesh.getMeshData().getColorBuffer() != null) {
+ colors = BufferUtils.getColorArray(mesh.getMeshData().getColorCoords(), ColorRGBA.WHITE);
+ }
+
+ // see if we have uv coords
+ final Vector2[][] tex = new Vector2[mesh.getMeshData().getNumberOfUnits()][];
+ for (int x = 0; x < tex.length; x++) {
+ if (mesh.getMeshData().getTextureCoords(x) != null) {
+ tex[x] = BufferUtils.getVector2Array(mesh.getMeshData().getTextureCoords(x), Vector2.ZERO);
+ }
+ }
+
+ final Map<VertKey, Integer> store = Maps.newHashMap();
+ final Map<Integer, Integer> indexRemap = Maps.newHashMap();
+ int good = 0;
+ long group;
+ for (int x = 0, max = verts.length; x < max; x++) {
+ group = groupData.getGroupForVertex(x);
+ final VertKey vkey = new VertKey(verts[x], norms != null ? norms[x] : null, colors != null ? colors[x]
+ : null, getTexs(tex, x), groupData.getGroupConditions(group), group);
+ // if we've already seen it, swap it for the max, and decrease max.
+ if (store.containsKey(vkey)) {
+ final int newInd = store.get(vkey);
+ if (indexRemap.containsKey(x)) {
+ indexRemap.put(max, newInd);
+ } else {
+ indexRemap.put(x, newInd);
+ }
+ max--;
+ if (x != max) {
+ indexRemap.put(max, x);
+ verts[x] = verts[max];
+ verts[max] = null;
+ if (norms != null) {
+ norms[newInd].addLocal(norms[x].normalizeLocal());
+ norms[x] = norms[max];
+ }
+ if (colors != null) {
+ colors[x] = colors[max];
+ }
+ for (int y = 0; y < tex.length; y++) {
+ if (mesh.getMeshData().getTextureCoords(y) != null) {
+ tex[y][x] = tex[y][max];
+ }
+ }
+ x--;
+ } else {
+ verts[max] = null;
+ }
+ }
+
+ // otherwise just store it
+ else {
+ store.put(vkey, x);
+ good++;
+ }
+ }
+
+ if (norms != null) {
+ for (final Vector3 norm : norms) {
+ norm.normalizeLocal();
+ }
+ }
+
+ mesh.getMeshData().setVertexBuffer(BufferUtils.createFloatBuffer(0, good, verts));
+ if (norms != null) {
+ mesh.getMeshData().setNormalBuffer(BufferUtils.createFloatBuffer(0, good, norms));
+ }
+ if (colors != null) {
+ mesh.getMeshData().setColorBuffer(BufferUtils.createFloatBuffer(0, good, colors));
+ }
+
+ for (int x = 0; x < tex.length; x++) {
+ if (tex[x] != null) {
+ mesh.getMeshData().setTextureBuffer(BufferUtils.createFloatBuffer(0, good, tex[x]), x);
+ }
+ }
+
+ if (mesh.getMeshData().getIndices() == null || mesh.getMeshData().getIndices().getBufferCapacity() == 0) {
+ final IndexBufferData<?> indexBuffer = BufferUtils.createIndexBufferData(oldCount, oldCount);
+ mesh.getMeshData().setIndices(indexBuffer);
+ for (int i = 0; i < oldCount; i++) {
+ if (indexRemap.containsKey(i)) {
+ indexBuffer.put(indexRemap.get(i));
+ } else {
+ indexBuffer.put(i);
+ }
+ }
+ } else {
+ final IndexBufferData<?> indexBuffer = mesh.getMeshData().getIndices();
+ final int[] inds = BufferUtils.getIntArray(indexBuffer);
+ indexBuffer.rewind();
+ for (final int i : inds) {
+ if (indexRemap.containsKey(i)) {
+ indexBuffer.put(indexRemap.get(i));
+ } else {
+ indexBuffer.put(i);
+ }
+ }
+ }
+ result.applyRemapping(indexRemap);
+ newCount = mesh.getMeshData().getVertexCount();
+ }
+
+ logger.info("Vertex reduction complete on: " + mesh + " old vertex count: " + oldCount + " new vertex count: "
+ + newCount + " (in " + (System.currentTimeMillis() - start) + " ms)");
+
+ return result;
+ }
+
+ private static Vector2[] getTexs(final Vector2[][] tex, final int i) {
+ final Vector2[] res = new Vector2[tex.length];
+ for (int x = 0; x < tex.length; x++) {
+ if (tex[x] != null) {
+ res[x] = tex[x][i];
+ }
+ }
+ return res;
+ }
+
+ public static void trimEmptyBranches(final Spatial spatial) {
+ if (spatial instanceof Node) {
+ final Node node = (Node) spatial;
+ for (int i = node.getNumberOfChildren(); --i >= 0;) {
+ trimEmptyBranches(node.getChild(i));
+ }
+ if (node.getNumberOfChildren() <= 0) {
+ spatial.removeFromParent();
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java
new file mode 100644
index 0000000..87a3573
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/MeshCombiner.java
@@ -0,0 +1,448 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.List;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.visitor.Visitor;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+/**
+ * Utility for combining multiple Meshes into a single Mesh. Note that you generally will want to combine Mesh objects
+ * that have the same render states.
+ *
+ * XXX: should add in a way to combine only meshes with similar renderstates<br>
+ * XXX: Might be able to reduce memory usage in the singular case where all sources do not have indices defined
+ * (arrays).<br>
+ * XXX: combining of triangle strips may not work properly? <br>
+ * XXX: Could be smarter about texcoords and have a tuple size per channel.<br>
+ */
+public class MeshCombiner {
+ public static final float[] DEFAULT_COLOR = { 1f, 1f, 1f, 1f };
+ public static final float[] DEFAULT_NORMAL = { 0f, 1f, 0f };
+ public static final float[] DEFAULT_TEXCOORD = { 0 };
+
+ /**
+ * <p>
+ * Combine all mesh objects that fall under the scene graph the given source node. All Mesh objects must have
+ * vertices and texcoords that have the same tuple width. It is possible to merge Mesh objects together that have
+ * mismatched normals/colors/etc. (eg. one with colors and one without.)
+ * </p>
+ *
+ * @param source
+ * our source node
+ * @return the combined Mesh.
+ */
+ public final static Mesh combine(final Node source) {
+ return combine(source, new MeshCombineLogic());
+ }
+
+ public final static Mesh combine(final Spatial source, final MeshCombineLogic logic) {
+ final List<Mesh> sources = Lists.newArrayList();
+ source.acceptVisitor(new Visitor() {
+ @Override
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ sources.add((Mesh) spatial);
+ }
+ }
+ }, true);
+
+ return combine(sources, logic);
+ }
+
+ /**
+ * Combine the given array of Mesh objects into a single Mesh. All Mesh objects must have vertices and texcoords
+ * that have the same tuple width. It is possible to merge Mesh objects together that have mismatched
+ * normals/colors/etc. (eg. one with colors and one without.)
+ *
+ * @param sources
+ * our Mesh objects to combine.
+ * @return the combined Mesh.
+ */
+ public final static Mesh combine(final Mesh... sources) {
+ return combine(Lists.newArrayList(sources));
+ }
+
+ /**
+ * Combine the given collection of Mesh objects into a single Mesh. All Mesh objects must have vertices and
+ * texcoords that have the same tuple width. It is possible to merge Mesh objects together that have mismatched
+ * normals/colors/etc. (eg. one with colors and one without.)
+ *
+ * @param sources
+ * our collection of Mesh objects to combine.
+ * @return the combined Mesh.
+ */
+ public final static Mesh combine(final Collection<Mesh> sources) {
+ return combine(sources, new MeshCombineLogic());
+ }
+
+ public final static Mesh combine(final Collection<Mesh> sources, final MeshCombineLogic logic) {
+ if (sources == null || sources.isEmpty()) {
+ return null;
+ }
+
+ // go through each MeshData to see what buffers we need and validate sizes.
+ for (final Mesh mesh : sources) {
+ logic.addSource(mesh);
+ }
+
+ // initialize return buffers
+ logic.initDataBuffers();
+
+ // combine sources into buffers
+ logic.combineSources();
+
+ // get and return our combined mesh
+ return logic.getCombinedMesh();
+ }
+
+ public static class MeshCombineLogic {
+ protected boolean useIndices = false, useNormals = false, useTextures = false, useColors = false, first = true;
+ protected int maxTextures = 0, totalVertices = 0, totalIndices = 0, texCoords = 2, vertCoords = 3;
+ protected IndexMode mode = null;
+ protected EnumMap<StateType, RenderState> states = null;
+ protected MeshData data = new MeshData();
+ protected BoundingVolume volumeType = null;
+ protected List<Mesh> sources = Lists.newArrayList();
+ private FloatBufferData vertices;
+ private FloatBufferData colors;
+ private FloatBufferData normals;
+ private List<FloatBufferData> texCoordsList;
+
+ public Mesh getMesh() {
+ final Mesh mesh = new Mesh("combined");
+ mesh.setMeshData(data);
+ return mesh;
+ }
+
+ public Mesh getCombinedMesh() {
+ final Mesh mesh = getMesh();
+
+ // set our bounding volume using the volume type of our first source found above.
+ mesh.setModelBound(volumeType);
+
+ // set the render states from the first mesh
+ for (final RenderState state : states.values()) {
+ mesh.setRenderState(state);
+ }
+
+ return mesh;
+ }
+
+ public void combineSources() {
+ final IndexCombiner iCombiner = new IndexCombiner();
+
+ // Walk through our source meshes and populate return MeshData buffers.
+ int vertexOffset = 0;
+ for (final Mesh mesh : sources) {
+
+ final MeshData md = mesh.getMeshData();
+
+ // Vertices
+ md.getVertexBuffer().rewind();
+ vertices.getBuffer().put(mesh.getWorldVectors(null));
+
+ // Normals
+ if (useNormals) {
+ final FloatBuffer nb = md.getNormalBuffer();
+ if (nb != null) {
+ nb.rewind();
+ normals.getBuffer().put(mesh.getWorldNormals(null));
+ } else {
+ for (int i = 0; i < md.getVertexCount(); i++) {
+ normals.getBuffer().put(DEFAULT_NORMAL);
+ }
+ }
+ }
+
+ // Colors
+ if (useColors) {
+ final FloatBuffer cb = md.getColorBuffer();
+ if (cb != null) {
+ cb.rewind();
+ colors.getBuffer().put(cb);
+ } else {
+ for (int i = 0; i < md.getVertexCount(); i++) {
+ colors.getBuffer().put(DEFAULT_COLOR);
+ }
+ }
+ }
+
+ // Tex Coords
+ if (useTextures) {
+ for (int i = 0; i < maxTextures; i++) {
+ final FloatBuffer dest = texCoordsList.get(i).getBuffer();
+ final FloatBuffer tb = md.getTextureBuffer(i);
+ if (tb != null) {
+ tb.rewind();
+ dest.put(tb);
+ } else {
+ for (int j = 0; j < md.getVertexCount() * texCoords; j++) {
+ dest.put(DEFAULT_TEXCOORD);
+ }
+ }
+ }
+ }
+
+ // Indices
+ if (useIndices) {
+ iCombiner.addEntry(md, vertexOffset);
+ vertexOffset += md.getVertexCount();
+ }
+ }
+
+ // Apply our index combiner to the mesh
+ if (useIndices) {
+ iCombiner.saveTo(data);
+ } else {
+ data.setIndexLengths(null);
+ data.setIndexMode(mode);
+ }
+ }
+
+ public void initDataBuffers() {
+ // Generate our buffers based on the information collected above and populate MeshData
+ vertices = new FloatBufferData(totalVertices * vertCoords, vertCoords);
+ data.setVertexCoords(vertices);
+
+ colors = useColors ? new FloatBufferData(totalVertices * 4, 4) : null;
+ data.setColorCoords(colors);
+
+ normals = useNormals ? new FloatBufferData(totalVertices * 3, 3) : null;
+ data.setNormalCoords(normals);
+
+ texCoordsList = Lists.newArrayListWithCapacity(maxTextures);
+ for (int i = 0; i < maxTextures; i++) {
+ texCoordsList.add(new FloatBufferData(totalVertices * texCoords, texCoords));
+ }
+ data.setTextureCoords(useTextures ? texCoordsList : null);
+ }
+
+ public void addSource(final Mesh mesh) {
+ sources.add(mesh);
+
+ // update world transforms
+ mesh.updateWorldTransform(false);
+
+ final MeshData md = mesh.getMeshData();
+ if (first) {
+ // copy info from first mesh
+ vertCoords = md.getVertexCoords().getValuesPerTuple();
+ volumeType = mesh.getModelBound(null);
+ states = mesh.getLocalRenderStates();
+ first = false;
+ } else if (vertCoords != md.getVertexCoords().getValuesPerTuple()) {
+ throw new IllegalArgumentException("all MeshData vertex coords must use same tuple size.");
+ }
+
+ // update total vertices
+ totalVertices += md.getVertexCount();
+
+ // check for indices
+ if (useIndices || md.getIndexBuffer() != null) {
+ useIndices = true;
+ if (md.getIndexBuffer() != null) {
+ totalIndices += md.getIndices().capacity();
+ } else {
+ totalIndices += md.getVertexCount();
+ }
+ } else {
+ mode = md.getIndexMode(0);
+ }
+
+ // check for normals
+ if (!useNormals && md.getNormalBuffer() != null) {
+ useNormals = true;
+ }
+
+ // check for colors
+ if (!useColors && md.getColorBuffer() != null) {
+ useColors = true;
+ }
+
+ // check for texcoord usage
+ if (md.getNumberOfUnits() > 0) {
+ if (!useTextures) {
+ useTextures = true;
+ texCoords = md.getTextureCoords(0).getValuesPerTuple();
+ } else if (md.getTextureCoords(0) != null && texCoords != md.getTextureCoords(0).getValuesPerTuple()) {
+ throw new IllegalArgumentException("all MeshData objects with texcoords must use same tuple size.");
+ }
+ maxTextures = Math.max(maxTextures, md.getNumberOfUnits());
+ }
+ }
+ }
+}
+
+class IndexCombiner {
+ Multimap<IndexMode, int[]> sectionMap = ArrayListMultimap.create();
+
+ public void addEntry(final MeshData source, final int vertexOffset) {
+ // arrays or elements?
+ if (source.getIndexBuffer() == null) {
+ // arrays...
+ int offset = 0;
+ int indexModeCounter = 0;
+ final IndexMode[] modes = source.getIndexModes();
+ // walk through each section
+ for (int i = 0, maxI = source.getSectionCount(); i < maxI; i++) {
+ // make an int array and populate it.
+ final int size = source.getIndexLengths() != null ? source.getIndexLengths()[i] : source
+ .getVertexCount();
+ final int[] indices = new int[size];
+ for (int j = 0; j < size; j++) {
+ indices[j] = j + vertexOffset + offset;
+ }
+
+ // add to map
+ sectionMap.put(modes[indexModeCounter], indices);
+
+ // move our offsets forward to the section
+ offset += size;
+ if (indexModeCounter < modes.length - 1) {
+ indexModeCounter++;
+ }
+ }
+ } else {
+ // elements...
+ final IndexBufferData<?> ib = source.getIndices();
+ ib.rewind();
+ int offset = 0;
+ int indexModeCounter = 0;
+ final IndexMode[] modes = source.getIndexModes();
+ // walk through each section
+ for (int i = 0, maxI = source.getSectionCount(); i < maxI; i++) {
+ // make an int array and populate it.
+ final int size = source.getIndexLengths() != null ? source.getIndexLengths()[i] : source.getIndices()
+ .capacity();
+ final int[] indices = new int[size];
+ for (int j = 0; j < size; j++) {
+ indices[j] = ib.get(j + offset) + vertexOffset;
+ }
+
+ // add to map
+ sectionMap.put(modes[indexModeCounter], indices);
+
+ // move our offsets forward to the section
+ offset += size;
+ if (indexModeCounter < modes.length - 1) {
+ indexModeCounter++;
+ }
+ }
+ }
+ }
+
+ public void saveTo(final MeshData data) {
+ final List<IntBuffer> sections = Lists.newArrayList();
+ final List<IndexMode> modes = Lists.newArrayList();
+ int max = 0;
+ // walk through index modes and combine those we can.
+ for (final IndexMode mode : sectionMap.keySet()) {
+ final Collection<int[]> sources = sectionMap.get(mode);
+ switch (mode) {
+ case Triangles:
+ case Quads:
+ case Lines:
+ case Points: {
+ // we can combine these as-is to our heart's content.
+ int size = 0;
+ for (final int[] indices : sources) {
+ size += indices.length;
+ }
+ max += size;
+ final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size);
+ for (final int[] indices : sources) {
+ newSection.put(indices);
+ }
+ // save
+ sections.add(newSection);
+ modes.add(mode);
+ break;
+ }
+ case TriangleFan:
+ case QuadStrip:
+ case LineLoop:
+ case LineStrip: {
+ // these have to be kept, as is.
+ int size;
+ for (final int[] indices : sources) {
+ size = indices.length;
+ max += size;
+ final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size);
+ newSection.put(indices);
+
+ sections.add(newSection);
+ modes.add(mode);
+ }
+ break;
+ }
+ case TriangleStrip: {
+ // we CAN combine these, but we have to add degenerate triangles.
+ int size = 0;
+ for (final int[] indices : sources) {
+ size += indices.length + 2;
+ }
+ size -= 2;
+ max += size;
+ final IntBuffer newSection = BufferUtils.createIntBufferOnHeap(size);
+ int i = 0;
+ for (final int[] indices : sources) {
+ if (i != 0) {
+ newSection.put(indices[0]);
+ }
+ newSection.put(indices);
+ if (i < sources.size() - 1) {
+ newSection.put(indices[indices.length - 1]);
+ }
+ i++;
+ }
+ // save
+ sections.add(newSection);
+ modes.add(mode);
+ break;
+ }
+ }
+ }
+
+ // compile into data
+ final IndexBufferData<?> finalIndices = BufferUtils.createIndexBufferData(max, data.getVertexCount() - 1);
+ data.setIndices(finalIndices);
+ final int[] sectionCounts = new int[sections.size()];
+ for (int i = 0; i < sectionCounts.length; i++) {
+ final IntBuffer ib = sections.get(i);
+ ib.rewind();
+ sectionCounts[i] = ib.remaining();
+ while (ib.hasRemaining()) {
+ finalIndices.put(ib.get());
+ }
+ }
+
+ data.setIndexLengths(sectionCounts);
+ data.setIndexModes(modes.toArray(new IndexMode[modes.size()]));
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java
new file mode 100644
index 0000000..78ef6d2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NonIndexedNormalGenerator.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.Arrays;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * A utility class to generate normals for a set of vertices. The triangles must be defined by just the vertices, so
+ * that every 3 consecutive vertices define one triangle. However, an index array must be specified to identify
+ * identical vertices properly (see method {@link #generateNormals(double[], int[], double)}. If the index aray is not
+ * specified, the vertex normals are currently simply taken from the faces they belong to (this might be changed in the
+ * future, so that vertices are compared by their values).
+ */
+public class NonIndexedNormalGenerator {
+
+ private final Vector3 _temp1 = new Vector3();
+
+ private final Vector3 _temp2 = new Vector3();
+
+ private final Vector3 _temp3 = new Vector3();
+
+ private int[] _indices;
+
+ private double _creaseAngle;
+
+ private double[] _faceNormals;
+
+ private int[] _normalsToSet;
+
+ /**
+ * Calculates the normals for a set of faces determined by the specified vertices. Every 3 consecutive vertices
+ * define one triangle.<br /> <strong>Please note:</strong> This method uses class fields and is not synchronized!
+ * Therefore it should only be called from a single thread, unless synchronization is taken care of externally.
+ *
+ * @param vertices
+ * The vertex coordinates. Every three values define one vertex
+ * @param indices
+ * An array containing int values. Each value belongs to one vertex in the <code>vertices</code> array,
+ * the values are stored in the same order as the vertices. For equal vertices in the
+ * <code>vertices</code> array, the indices are also equal.
+ * @param creaseAngle
+ * The maximum angle in radians between faces to which normals between the faces are interpolated to
+ * create a smooth transition
+ * @return An array containing the generated normals for the geometry
+ */
+ public double[] generateNormals(final double[] vertices, final int[] indices, final double creaseAngle) {
+
+ _indices = indices;
+ _creaseAngle = creaseAngle;
+ _normalsToSet = new int[10];
+ Arrays.fill(_normalsToSet, -1);
+
+ initFaceNormals(vertices);
+
+ if (creaseAngle < MathUtils.ZERO_TOLERANCE || indices == null) {
+ return getFacetedVertexNormals();
+ }
+ return getVertexNormals();
+ }
+
+ /**
+ * Initializes the array <code>faceNormals</code> with the normals of all faces (triangles) of the mesh.
+ *
+ * @param vertices
+ * The array containing all vertex coordinates
+ */
+ private void initFaceNormals(final double[] vertices) {
+ _faceNormals = new double[vertices.length / 3];
+
+ for (int i = 0; i * 9 < vertices.length; i++) {
+ _temp1.set(vertices[i * 9 + 0], vertices[i * 9 + 1], vertices[i * 9 + 2]);
+ _temp2.set(vertices[i * 9 + 3], vertices[i * 9 + 4], vertices[i * 9 + 5]);
+ _temp3.set(vertices[i * 9 + 6], vertices[i * 9 + 7], vertices[i * 9 + 8]);
+
+ _temp2.subtractLocal(_temp1); // A -> B
+ _temp3.subtractLocal(_temp1); // A -> C
+
+ _temp2.cross(_temp3, _temp1);
+ _temp1.normalizeLocal(); // Normal
+
+ _faceNormals[i * 3 + 0] = _temp1.getX();
+ _faceNormals[i * 3 + 1] = _temp1.getY();
+ _faceNormals[i * 3 + 2] = _temp1.getZ();
+ }
+ }
+
+ /**
+ * Creates an array containing the interpolated normals for all vertices
+ *
+ * @return The array with the vertex normals
+ */
+ private double[] getVertexNormals() {
+
+ final double[] normals = new double[_faceNormals.length * 3];
+ final boolean[] setNormals = new boolean[_faceNormals.length];
+
+ for (int i = 0; i * 3 < _faceNormals.length; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (!setNormals[i * 3 + j]) {
+ setInterpolatedNormal(normals, setNormals, i, j);
+ }
+ }
+ }
+
+ return normals;
+ }
+
+ /**
+ * Computes the interpolated normal for the specified vertex of the specified face and applies it to all identical
+ * vertices for which the normal is interpolated.
+ *
+ * @param normals
+ * The array to store the vertex normals
+ * @param setNormals
+ * An array indicating which vertex normals have already been set
+ * @param face
+ * The index of the face containing the current vertex
+ * @param vertex
+ * The index of the vertex inside the face (0 - 2)
+ */
+ private void setInterpolatedNormal(final double[] normals, final boolean[] setNormals, final int face,
+ final int vertex) {
+
+ // temp1: Normal of the face the specified vertex belongs to
+ _temp1.set(_faceNormals[face * 3 + 0], _faceNormals[face * 3 + 1], _faceNormals[face * 3 + 2]);
+
+ // temp2: Sum of all face normals to be interpolated
+ _temp2.set(_temp1);
+
+ final int vertIndex = _indices[face * 3 + vertex];
+ _normalsToSet[0] = face * 3 + vertex;
+ int count = 1;
+
+ /*
+ * Get the normals of all faces containing the specified vertex whose angle to the specified one is less than
+ * the crease angle
+ */
+ for (int i = face * 3 + vertex + 1; i < _indices.length; i++) {
+ if (_indices[i] == vertIndex && !setNormals[face * 3 + vertex]) {
+ // temp3: Normal of the face the current vertex belongs to
+ _temp3.set(_faceNormals[(i / 3) * 3 + 0], _faceNormals[(i / 3) * 3 + 1], _faceNormals[(i / 3) * 3 + 2]);
+ if (_temp1.smallestAngleBetween(_temp3) < _creaseAngle) {
+ _normalsToSet = setValue(_normalsToSet, count, i);
+ count++;
+ _temp2.addLocal(_temp3);
+ }
+ }
+ }
+
+ _temp2.normalizeLocal();
+
+ // Set the normals for all vertices marked for interpolation
+ for (int i = 0; i < _normalsToSet.length && _normalsToSet[i] != -1; i++) {
+ normals[_normalsToSet[i] * 3 + 0] = _temp2.getX();
+ normals[_normalsToSet[i] * 3 + 1] = _temp2.getY();
+ normals[_normalsToSet[i] * 3 + 2] = _temp2.getZ();
+ setNormals[_normalsToSet[i]] = true;
+ _normalsToSet[i] = -1;
+ }
+ }
+
+ /**
+ * Puts the value into the array at the specified index. If the index is out of bounds, an new array with a length
+ * of 3 fields more than the specified one is created first and the values copied to it.
+ *
+ * @param array
+ * The array
+ * @param index
+ * The index to insert the value
+ * @param value
+ * The value to insert
+ * @return The array with the values, either the specified one or the new one
+ */
+ private int[] setValue(int[] array, final int index, final int value) {
+ if (index >= array.length) {
+ final int[] temp = new int[array.length + 3];
+ Arrays.fill(temp, -1);
+ System.arraycopy(array, 0, temp, 0, array.length);
+ array = temp;
+ }
+
+ array[index] = value;
+ return array;
+ }
+
+ /**
+ * Simply copies the face normals to the vertices contained in each face, creating a faceted appearance.
+ *
+ * @return The vertex normals
+ */
+ private double[] getFacetedVertexNormals() {
+ final double[] normals = new double[_faceNormals.length * 3];
+ for (int i = 0; i * 3 < _faceNormals.length; i++) {
+ for (int j = 0; j < 3; j++) {
+ normals[i * 9 + j + 0] = _faceNormals[i * 3 + j];
+ normals[i * 9 + j + 3] = _faceNormals[i * 3 + j];
+ normals[i * 9 + j + 6] = _faceNormals[i * 3 + j];
+ }
+ }
+ return normals;
+ }
+
+ public void generateNormals(final Mesh mesh) {
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java
new file mode 100644
index 0000000..aa9d32a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/NormalGenerator.java
@@ -0,0 +1,812 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.IntBufferData;
+import com.ardor3d.scenegraph.Mesh;
+
+/**
+ * A utility class to generate normals for Meshes.<br />
+ * <br />
+ * The generator generates the normals using a given crease angle up to which the transitions between two triangles are
+ * to be smoothed. Normals will be generated for the mesh, as long as it uses the default triangle mode (
+ * <code>TRIANGLE</code>) - the modes <code>TRIANGLE_FAN</code> and <code>TRIANGLE_STRIP</code> are not supported.<br />
+ * <br />
+ * The generator currently only supports a single set of texture coordinates in a mesh. If more than one set of texture
+ * coordinates is specified in a mesh, all sets after the first one will be deleted.<br />
+ * <br />
+ * <strong>Please note:</strong> The mesh must be <cite>manifold</cite>, i.e. only 2 triangles may be connected by one
+ * edge, and the mesh has to be connected by means of edges, not vertices. Otherwise, the normal generation might fail,
+ * with undefined results.
+ */
+public class NormalGenerator {
+
+ private static final Logger logger = Logger.getLogger(NormalGenerator.class.getName());
+
+ // The angle up to which edges between triangles are smoothed
+ private float _creaseAngle;
+
+ // Source data
+ private Vector3[] _sourceVerts;
+ private ColorRGBA[] _sourceColors;
+ private Vector2[] _sourceTexCoords;
+ private int[] _sourceInds;
+ private LinkedList<Triangle> _triangles;
+
+ // Computed (destination) data for one mesh split
+ private List<Vector3> _destVerts;
+ private List<ColorRGBA> _destColors;
+ private List<Vector2> _destTexCoords;
+ private LinkedList<Triangle> _destTris;
+ private LinkedList<Edge> _edges;
+
+ // The lists to store the split data for the final mesh
+ private LinkedList<LinkedList<Triangle>> _splitMeshes;
+ private LinkedList<LinkedList<Edge>> _splitMeshBorders;
+ private Vector3[] _splitVerts;
+ private ColorRGBA[] _splitColors;
+ private Vector2[] _splitTexCoords;
+ private Vector3[] _splitNormals;
+ private int[] _splitIndices;
+
+ // The data used to compute the final mesh
+ private boolean[] _borderIndices;
+
+ // Temporary data used for computation
+ private final Vector3 _compVect0 = new Vector3();
+ private final Vector3 _compVect1 = new Vector3();
+
+ /**
+ * Generates the normals for one Mesh, using the specified crease angle.
+ *
+ * @param mesh
+ * The Mesh to generate the normals for
+ * @param creaseAngle
+ * The angle between two triangles up to which the normal between the two triangles will be interpolated,
+ * creating a smooth transition
+ */
+ public void generateNormals(final Mesh mesh, final float creaseAngle) {
+ if (mesh != null) {
+ _creaseAngle = creaseAngle;
+ generateNormals(mesh);
+ cleanup();
+ }
+ }
+
+ /**
+ * Generates the normals for one Mesh, using the crease angle stored in the field <code>creaseAngle</code>
+ *
+ * @param mesh
+ * The Mesh to generate the normals for
+ */
+ private void generateNormals(final Mesh mesh) {
+ // FIXME: only uses 1st index.
+ if (mesh.getMeshData().getIndexMode(0) != IndexMode.Triangles) {
+ logger.info("Invalid triangles mode in " + mesh);
+ return;
+ }
+
+ // Get the data of the mesh as arrays
+ _sourceInds = BufferUtils.getIntArray(mesh.getMeshData().getIndices());
+ _sourceVerts = BufferUtils.getVector3Array(mesh.getMeshData().getVertexBuffer());
+ if (mesh.getMeshData().getColorBuffer() != null) {
+ _sourceColors = BufferUtils.getColorArray(mesh.getMeshData().getColorBuffer());
+ } else {
+ _sourceColors = null;
+ }
+ if (mesh.getMeshData().getTextureCoords(0) != null) {
+ _sourceTexCoords = BufferUtils.getVector2Array(mesh.getMeshData().getTextureCoords(0).getBuffer());
+ } else {
+ _sourceTexCoords = null;
+ }
+
+ // Initialize the lists needed to generate the normals for the mesh
+ initialize();
+
+ // Process all triangles in the mesh. Create one connected mesh for
+ // every set of triangles that are connected by their vertex indices
+ // with an angle not greater than the creaseAngle.
+ while (!_triangles.isEmpty()) {
+ createMeshSplit();
+ }
+
+ // Duplicate all vertices that are shared by different split meshes
+ if (!_splitMeshes.isEmpty()) {
+ _borderIndices = new boolean[_sourceVerts.length];
+ fillBorderIndices();
+ duplicateCreaseVertices();
+ }
+
+ // Set up the arrays for reconstructing the mesh: Vertices,
+ // texture coordinates, colors, normals and indices
+ _splitVerts = _destVerts.toArray(new Vector3[_destVerts.size()]);
+ if (_destColors != null) {
+ _splitColors = _destColors.toArray(new ColorRGBA[_destColors.size()]);
+ } else {
+ _splitColors = null;
+ }
+ if (_destTexCoords != null) {
+ _splitTexCoords = _destTexCoords.toArray(new Vector2[_destTexCoords.size()]);
+ } else {
+ _splitTexCoords = null;
+ }
+ _splitNormals = new Vector3[_destVerts.size()];
+ for (int j = 0; j < _splitNormals.length; j++) {
+ _splitNormals[j] = new Vector3();
+ }
+ int numTris = 0;
+ for (final LinkedList<Triangle> tris : _splitMeshes) {
+ numTris += tris.size();
+ }
+ _splitIndices = new int[numTris * 3];
+
+ // For each of the split meshes, create the interpolated normals
+ // between its triangles and set up its index array in the process
+ computeNormalsAndIndices();
+
+ // Set up the buffers for the mesh
+
+ // Vertex buffer:
+ FloatBuffer vertices = mesh.getMeshData().getVertexBuffer();
+ if (vertices.capacity() < _splitVerts.length * 3) {
+ vertices = BufferUtils.createFloatBuffer(_splitVerts);
+ } else {
+ vertices.clear();
+ for (final Vector3 vertex : _splitVerts) {
+ vertices.put((float) vertex.getX()).put((float) vertex.getY()).put((float) vertex.getZ());
+ }
+ vertices.flip();
+ }
+
+ // Normal buffer:
+ FloatBuffer normals = mesh.getMeshData().getNormalBuffer();
+ if (normals == null || normals.capacity() < _splitNormals.length * 3) {
+ normals = BufferUtils.createFloatBuffer(_splitNormals);
+ } else {
+ normals.clear();
+ for (final Vector3 normal : _splitNormals) {
+ normals.put((float) normal.getX()).put((float) normal.getY()).put((float) normal.getZ());
+ }
+ normals.flip();
+ }
+
+ // Color buffer:
+ FloatBuffer colors = null;
+ if (_splitColors != null) {
+ colors = mesh.getMeshData().getColorBuffer();
+ if (colors.capacity() < _splitColors.length * 4) {
+ colors = BufferUtils.createFloatBuffer(_splitColors);
+ } else {
+ colors.clear();
+ for (final ColorRGBA color : _splitColors) {
+ colors.put(color.getRed()).put(color.getGreen()).put(color.getBlue()).put(color.getAlpha());
+ }
+ colors.flip();
+ }
+ }
+
+ // Tex coord buffer:
+ FloatBuffer texCoords = null;
+ if (_splitTexCoords != null) {
+ texCoords = mesh.getMeshData().getTextureCoords(0).getBuffer();
+ if (texCoords.capacity() < _splitTexCoords.length * 2) {
+ texCoords = BufferUtils.createFloatBuffer(_splitTexCoords);
+ } else {
+ texCoords.clear();
+ for (final Vector2 texCoord : _splitTexCoords) {
+ texCoords.put((float) texCoord.getX()).put((float) texCoord.getY());
+ }
+ texCoords.flip();
+ }
+ }
+
+ // Index buffer:
+ IndexBufferData<?> indices = mesh.getMeshData().getIndices();
+ if (indices.getBuffer().capacity() < _splitIndices.length) {
+ indices = new IntBufferData(BufferUtils.createIntBuffer(_splitIndices));
+ } else {
+ indices.getBuffer().clear();
+ for (final int i : _splitIndices) {
+ indices.put(i);
+ }
+ indices.getBuffer().flip();
+ }
+
+ // Apply the buffers to the mesh
+ mesh.getMeshData().setVertexBuffer(vertices);
+ mesh.getMeshData().setNormalBuffer(normals);
+ if (colors != null) {
+ mesh.getMeshData().setColorBuffer(colors);
+ }
+ mesh.getMeshData().getTextureCoords().clear();
+ if (texCoords != null) {
+ mesh.getMeshData().setTextureBuffer(texCoords, 0);
+ }
+ mesh.getMeshData().setIndices(indices);
+ }
+
+ /**
+ * Sets up the lists to get the data for normal generation from.
+ */
+ private void initialize() {
+ // Copy the source vertices as a base for the normal generation
+ _destVerts = new ArrayList<Vector3>(_sourceVerts.length);
+ for (int i = 0; i < _sourceVerts.length; i++) {
+ _destVerts.add(_sourceVerts[i]);
+ }
+ if (_sourceColors != null) {
+ _destColors = new ArrayList<ColorRGBA>(_sourceColors.length);
+ for (int i = 0; i < _sourceColors.length; i++) {
+ _destColors.add(_sourceColors[i]);
+ }
+ } else {
+ _destColors = null;
+ }
+ if (_sourceTexCoords != null) {
+ _destTexCoords = new ArrayList<Vector2>(_sourceTexCoords.length);
+ for (int i = 0; i < _sourceTexCoords.length; i++) {
+ _destTexCoords.add(_sourceTexCoords[i]);
+ }
+ } else {
+ _destTexCoords = null;
+ }
+
+ // Set up the base triangles of the mesh and their face normals
+ _triangles = new LinkedList<Triangle>();
+ for (int i = 0; i * 3 < _sourceInds.length; i++) {
+ final Triangle tri = new Triangle(_sourceInds[i * 3 + 0], _sourceInds[i * 3 + 1], _sourceInds[i * 3 + 2]);
+ tri.computeNormal(_sourceVerts);
+ _triangles.add(tri);
+ }
+
+ // Set up the lists to store the created mesh split data
+ if (_splitMeshes == null) {
+ _splitMeshes = new LinkedList<LinkedList<Triangle>>();
+ } else {
+ _splitMeshes.clear();
+ }
+ if (_splitMeshBorders == null) {
+ _splitMeshBorders = new LinkedList<LinkedList<Edge>>();
+ } else {
+ _splitMeshBorders.clear();
+ }
+ }
+
+ /**
+ * Assembles one set of triangles ("split mesh") that are connected and do not contain a transition with an angle
+ * greater than the creaseAngle. The Triangles of the split mesh are stored in splitMeshes, and the Edges along the
+ * border of the split mesh in splitMeshBorders.
+ */
+ private void createMeshSplit() {
+ _destTris = new LinkedList<Triangle>();
+ _edges = new LinkedList<Edge>();
+ final Triangle tri = _triangles.removeFirst();
+ _destTris.addLast(tri);
+ _edges.addLast(tri.edges[0]);
+ _edges.addLast(tri.edges[1]);
+ _edges.addLast(tri.edges[2]);
+
+ Triangle newTri;
+ do {
+ newTri = insertTriangle();
+ } while (newTri != null);
+
+ _splitMeshes.addLast(_destTris);
+ _splitMeshBorders.addLast(_edges);
+ }
+
+ /**
+ * Finds one triangle connected to the split mesh currently being assembled over an edge whose angle does not exceed
+ * the creaseAngle. The Triangle is inserted into destTris and the list edges is updated with the edges of the
+ * triangle accordingly.
+ *
+ * @return The triangle, if one was found, or <code>null</code> otherwise
+ */
+ private Triangle insertTriangle() {
+ final ListIterator<Triangle> triIt = _triangles.listIterator();
+ ListIterator<Edge> edgeIt = null;
+
+ // Find a triangle that is connected to the border of the currently
+ // assembled mesh split and whose angle to the connected border triangle
+ // is less than or equal to the creaseAngle
+ Triangle result = null;
+ int connected = -1;
+ Edge borderEdge = null;
+ while (result == null && triIt.hasNext()) {
+ final Triangle tri = triIt.next();
+ edgeIt = _edges.listIterator();
+ while (result == null && edgeIt.hasNext()) {
+ borderEdge = edgeIt.next();
+ for (int i = 0; i < tri.edges.length && result == null; i++) {
+ if (borderEdge.isConnectedTo(tri.edges[i]) && checkAngle(tri, borderEdge.parent)) {
+ connected = i;
+ result = tri;
+ }
+ }
+ }
+ }
+
+ // If a triangle has been found, remove it from the list of remaining
+ // triangles and add it to the current split mesh, including its borders
+ if (result != null) {
+ // Connect the triangle to the split mesh
+ triIt.remove();
+ _destTris.addLast(result);
+ borderEdge.connected = result;
+ final Edge resultEdge = result.edges[connected];
+ resultEdge.connected = borderEdge.parent;
+ edgeIt.remove();
+ edgeIt.add(result.edges[(connected + 1) % 3]);
+ edgeIt.add(result.edges[(connected + 2) % 3]);
+
+ // If the connected edge had cloned vertices, use them for the new
+ // triangle too
+ if (borderEdge.newI0 > -1) {
+ resultEdge.newI1 = borderEdge.newI0;
+ result.edges[(connected + 1) % 3].newI0 = borderEdge.newI0;
+ }
+ if (borderEdge.newI1 > -1) {
+ resultEdge.newI0 = borderEdge.newI1;
+ result.edges[(connected + 2) % 3].newI1 = borderEdge.newI1;
+ }
+
+ // Check if the triangle is connected to other edges along the
+ // border of the current split mesh
+ for (int i = connected + 1; i < connected + 3; i++) {
+ connectEdge(result, i % 3);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Connects the remaining edges of the given triangle to the split mesh currently being assembled, if possible. The
+ * respective edges are removed from the border, and if the crease angle at this additional connection is exceeded,
+ * the vertices at this link are duplicated.
+ *
+ * @param triangle
+ * The triangle being connected to the split mesh
+ * @param i
+ * The index of the edge in the triangle that is already connected to the split mesh
+ */
+ private void connectEdge(final Triangle triangle, final int i) {
+ final Edge edge = triangle.edges[i];
+ final ListIterator<Edge> edgeIt = _edges.listIterator();
+ boolean found = false;
+ while (!found && edgeIt.hasNext()) {
+ final Edge borderEdge = edgeIt.next();
+ if (borderEdge.isConnectedTo(edge)) {
+ // Connected => remove the connected edges from the
+ // border
+ found = true;
+ edgeIt.remove();
+ _edges.remove(edge);
+ if (!checkAngle(triangle, borderEdge.parent)) {
+ // Crease angle exceeded => duplicate the vertices, colors
+ // and texCoords
+ duplicateValues(edge.i0);
+ edge.newI0 = _destVerts.size() - 1;
+ triangle.edges[(i + 2) % 3].newI1 = edge.newI0;
+ duplicateValues(edge.i1);
+ edge.newI1 = _destVerts.size() - 1;
+ triangle.edges[(i + 1) % 3].newI0 = edge.newI1;
+ } else {
+ // Crease angle okay => share duplicate vertices, if
+ // any
+ if (borderEdge.newI0 > -1) {
+ edge.newI1 = borderEdge.newI0;
+ triangle.edges[(i + 1) % 3].newI0 = borderEdge.newI0;
+ }
+ if (borderEdge.newI1 > -1) {
+ edge.newI0 = borderEdge.newI1;
+ triangle.edges[(i + 2) % 3].newI1 = borderEdge.newI1;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the transition between the tqo given triangles should be smooth, according to the creaseAngle.
+ *
+ * @param tri1
+ * The first triangle
+ * @param tri2
+ * The second triangle
+ * @return <code>true</code>, if the angle between the two triangles is less than or equal to the creaseAngle;
+ * otherwise <code>false</code>
+ */
+ private boolean checkAngle(final Triangle tri1, final Triangle tri2) {
+ return (tri1.normal.smallestAngleBetween(tri2.normal) <= _creaseAngle + MathUtils.ZERO_TOLERANCE);
+ }
+
+ /**
+ * Copies the vertex, color and texCoord at the given index in each of the source lists (if not null) and adds it to
+ * the end of the list.
+ *
+ * @param index
+ * The index to copy the value in each list from
+ */
+ private void duplicateValues(final int index) {
+ _destVerts.add(_destVerts.get(index));
+ if (_destColors != null) {
+ _destColors.add(_destColors.get(index));
+ }
+ if (_destTexCoords != null) {
+ _destTexCoords.add(_destTexCoords.get(index));
+ }
+ }
+
+ /**
+ * Fills the borderIndices array with the all indices contained in the first set of border edges stored in
+ * splitMeshBorders. All values not set in the process are set to -1.
+ */
+ private void fillBorderIndices() {
+ Arrays.fill(_borderIndices, false);
+ final LinkedList<Edge> edges0 = _splitMeshBorders.getFirst();
+ for (final Edge edge : edges0) {
+ _borderIndices[edge.i0] = true;
+ _borderIndices[edge.i1] = true;
+ }
+ }
+
+ /**
+ * Finds all vertices that are used by several split meshes and copies them (including updating the indices in the
+ * corresponding triangles).
+ */
+ private void duplicateCreaseVertices() {
+ if (_splitMeshBorders.size() < 2) {
+ return;
+ }
+
+ final int[] replacementIndices = new int[_sourceVerts.length];
+
+ // Check the borders of all split meshes, starting with the second one
+ final ListIterator<LinkedList<Edge>> borderIt = _splitMeshBorders.listIterator();
+ borderIt.next();
+ final ListIterator<LinkedList<Triangle>> meshIt = _splitMeshes.listIterator();
+ meshIt.next();
+ while (borderIt.hasNext()) {
+ Arrays.fill(replacementIndices, -1);
+ final LinkedList<Edge> edges0 = borderIt.next(); // Border of the split
+ final LinkedList<Triangle> destTris0 = meshIt.next(); // Triangles of the
+ // split
+
+ // Check every border edge of the split mesh. If its indices are
+ // already set in borderIndices, the corresponding vertices are
+ // already used by another split and have to be duplicated
+ final ListIterator<Edge> edgeIt = edges0.listIterator();
+ while (edgeIt.hasNext()) {
+ final Edge edge = edgeIt.next();
+
+ if (edge.newI0 == -1) {
+ if (_borderIndices[edge.i0]) {
+ if (replacementIndices[edge.i0] == -1) {
+ duplicateValues(edge.i0);
+ replacementIndices[edge.i0] = _destVerts.size() - 1;
+ }
+ } else {
+ replacementIndices[edge.i0] = edge.i0;
+ }
+ }
+
+ if (edge.newI1 == -1) {
+ if (_borderIndices[edge.i1]) {
+ if (replacementIndices[edge.i1] == -1) {
+ duplicateValues(edge.i1);
+ replacementIndices[edge.i1] = _destVerts.size() - 1;
+ }
+ } else {
+ replacementIndices[edge.i1] = edge.i1;
+ }
+ }
+ }
+
+ // Replace all indices in the split mesh whose vertices have been
+ // duplicated
+ for (int i = 0; i < _borderIndices.length; i++) {
+ if (_borderIndices[i]) {
+ for (final Triangle tri : destTris0) {
+ replaceIndex(tri, i, replacementIndices[i]);
+ }
+ } else if (replacementIndices[i] > -1) {
+ _borderIndices[i] = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * If the triangle contains the given index, it is replaced with the replacement index, unless it is already
+ * overridden with a newIndex (newI0, newI1).
+ *
+ * @param tri
+ * The triangle
+ * @param index
+ * The index to replace
+ * @param replacement
+ * The replacement index
+ */
+ private void replaceIndex(final Triangle tri, final int index, final int replacement) {
+ for (int i = 0; i < 3; i++) {
+ final Edge edge = tri.edges[i];
+ if (edge.newI0 == -1 && edge.i0 == index) {
+ edge.newI0 = replacement;
+ }
+ if (edge.newI1 == -1 && edge.i1 == index) {
+ edge.newI1 = replacement;
+ }
+ }
+ }
+
+ /**
+ * Sets up the normals and indices for all split meshes.
+ */
+ private void computeNormalsAndIndices() {
+
+ // First, sum up the normals of the adjacent triangles for each vertex.
+ // Store the triangle indices in the process.
+ int count = 0;
+ for (final LinkedList<Triangle> tris : _splitMeshes) {
+ for (final Triangle tri : tris) {
+ for (int i = 0; i < 3; i++) {
+ if (tri.edges[i].newI0 > -1) {
+ _splitNormals[tri.edges[i].newI0].addLocal(tri.normal);
+ _splitIndices[count++] = tri.edges[i].newI0;
+ } else {
+ _splitNormals[tri.edges[i].i0].addLocal(tri.normal);
+ _splitIndices[count++] = tri.edges[i].i0;
+ }
+
+ }
+ }
+ }
+
+ // Normalize all normals
+ for (int i = 0; i < _splitNormals.length; i++) {
+ if (_splitNormals[i].distanceSquared(Vector3.ZERO) > MathUtils.ZERO_TOLERANCE) {
+ _splitNormals[i].normalizeLocal();
+ }
+ }
+ }
+
+ /**
+ * Clears and nulls all used arrays and lists, so the garbage collector can clean them up.
+ */
+ private void cleanup() {
+ _creaseAngle = 0;
+ Arrays.fill(_sourceVerts, null);
+ _sourceVerts = null;
+ if (_sourceColors != null) {
+ Arrays.fill(_sourceColors, null);
+ _sourceColors = null;
+ }
+ if (_sourceTexCoords != null) {
+ Arrays.fill(_sourceTexCoords, null);
+ _sourceTexCoords = null;
+ }
+ _sourceInds = null;
+
+ if (_triangles != null) {
+ _triangles.clear();
+ _triangles = null;
+ }
+ if (_destVerts != null) {
+ _destVerts.clear();
+ _destVerts = null;
+ }
+ if (_destColors != null) {
+ _destColors.clear();
+ _destColors = null;
+ }
+ if (_destTexCoords != null) {
+ _destTexCoords.clear();
+ _destTexCoords = null;
+ }
+ if (_destTris != null) {
+ _destTris.clear();
+ _destTris = null;
+ }
+ if (_edges != null) {
+ _edges.clear();
+ _edges = null;
+ }
+
+ if (_splitMeshes != null) {
+ for (final LinkedList<Triangle> tris : _splitMeshes) {
+ tris.clear();
+ }
+ _splitMeshes.clear();
+ _splitMeshes = null;
+ }
+ if (_splitMeshBorders != null) {
+ for (final LinkedList<Edge> edges : _splitMeshBorders) {
+ edges.clear();
+ }
+ _splitMeshBorders.clear();
+ _splitMeshBorders = null;
+ }
+
+ _splitVerts = null;
+ _splitNormals = null;
+ _splitColors = null;
+ _splitTexCoords = null;
+ _splitIndices = null;
+ _borderIndices = null;
+ }
+
+ /**
+ * A helper class for the normal generator. Stores one triangle, consisting of 3 edges, and the normal for the
+ * triangle.
+ *
+ * @author M. Sattler
+ */
+ private class Triangle {
+
+ public Edge[] edges = new Edge[3];
+
+ public Vector3 normal = new Vector3(0, 0, 0);
+
+ public Triangle() {}
+
+ /**
+ * Creates the triangle.
+ *
+ * @param i0
+ * The index of vertex 0 in the triangle
+ * @param i1
+ * The index of vertex 1 in the triangle
+ * @param i2
+ * The index of vertex 2 in the triangle
+ */
+ public Triangle(final int i0, final int i1, final int i2) {
+ edges[0] = new Edge(this, i0, i1);
+ edges[1] = new Edge(this, i1, i2);
+ edges[2] = new Edge(this, i2, i0);
+ }
+
+ /**
+ * Computes the normal from the three vertices in the given array that are indexed by the edges.
+ *
+ * @param verts
+ * The array containing the vertices
+ */
+ public void computeNormal(final Vector3[] verts) {
+ final int i0 = edges[0].i0;
+ final int i1 = edges[1].i0;
+ final int i2 = edges[2].i0;
+ verts[i2].subtract(verts[i1], _compVect0);
+ verts[i0].subtract(verts[i1], _compVect1);
+ normal.set(_compVect0.crossLocal(_compVect1)).normalizeLocal();
+ }
+
+ /**
+ * @param edge
+ * An Edge to get the index of
+ * @return The index of the edge in the triangle, or -1, if it is not contained in the triangle
+ */
+ public int indexOf(final Edge edge) {
+ for (int i = 0; i < 3; i++) {
+ if (edges[i] == edge) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder result = new StringBuilder("Triangle (");
+ for (int i = 0; i < 3; i++) {
+ final Edge edge = edges[i];
+ if (edge == null) {
+ result.append("?");
+ } else {
+ if (edge.newI0 > -1) {
+ result.append(edge.newI0);
+ } else {
+ result.append(edge.i0);
+ }
+ }
+ if (i < 2) {
+ result.append(", ");
+ }
+ }
+ result.append(")");
+ return result.toString();
+ }
+ }
+
+ /**
+ * Another helper class for the normal generator. Stores one edge in the mesh, consisting of two vertex indices, the
+ * triangle the edge belongs to, and, if applicable, another triangle the edge is connected to.
+ *
+ * @author M. Sattler
+ */
+ private class Edge {
+
+ // The indices of the vertices in the mesh
+ public int i0;
+ public int i1;
+
+ // The indices of duplicated vertices, if > -1
+ public int newI0 = -1;
+ public int newI1 = -1;
+
+ // The Triangle containing this Edge
+ public Triangle parent;
+
+ // A Triangle this Edge is connected to, or null, if it is not connected
+ public Triangle connected;
+
+ public Edge() {}
+
+ /**
+ * Creates this edge.
+ *
+ * @param parent
+ * The Triangle containing this Edge
+ * @param i0
+ * The index of the first vertex of this edge
+ * @param i1
+ * The index of the second vertex of this edge
+ */
+ public Edge(final Triangle parent, final int i0, final int i1) {
+ this.parent = parent;
+ this.i0 = i0;
+ this.i1 = i1;
+ }
+
+ /**
+ * Checks if this edge is connected to another one.
+ *
+ * @param other
+ * The other edge
+ * @return <code>true</code>, if the indices in this edge and the other one are identical, but in inverse order
+ */
+ public boolean isConnectedTo(final Edge other) {
+ return (i0 == other.i1 && i1 == other.i0);
+ }
+
+ @Override
+ public String toString() {
+ String result = "Edge (";
+ if (newI0 > -1) {
+ result += newI0;
+ } else {
+ result += i0;
+ }
+ result += ", ";
+ if (newI1 > -1) {
+ result += newI1;
+ } else {
+ result += i1;
+ }
+ result += ")";
+ return result;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java
new file mode 100644
index 0000000..cb018e6
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SceneCopier.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+@Deprecated
+public abstract class SceneCopier {
+
+ public static Spatial makeCopy(final Spatial source, final CopyLogic logic) {
+ return makeCopy(source, null, logic);
+ }
+
+ private static Spatial makeCopy(final Spatial source, final Spatial parent, final CopyLogic logic) {
+ final AtomicBoolean recurse = new AtomicBoolean();
+ final Spatial result = logic.copy(source, recurse);
+ if (recurse.get() && source instanceof Node && result instanceof Node
+ && ((Node) source).getNumberOfChildren() > 0) {
+ for (final Spatial child : ((Node) source).getChildren()) {
+ final Spatial copy = makeCopy(child, result, logic);
+ if (copy != null) {
+ ((Node) result).attachChild(copy);
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java
new file mode 100644
index 0000000..200ad05
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/SharedCopyLogic.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Spatial;
+
+/**
+ * @deprecated use {@link Spatial#makeCopy(boolean)} instead.
+ */
+@Deprecated
+public class SharedCopyLogic implements CopyLogic {
+ private static final Logger logger = Logger.getLogger(SharedCopyLogic.class.getName());
+
+ public Spatial copy(final Spatial source, final AtomicBoolean recurse) {
+ recurse.set(false);
+ if (source instanceof Node) {
+ recurse.set(true);
+ return clone((Node) source);
+ } else if (source instanceof Mesh) {
+ final Mesh result = clone((Mesh) source);
+ result.setMeshData(((Mesh) source).getMeshData());
+ result.updateModelBound();
+ return result;
+ }
+ return null;
+ }
+
+ protected Mesh clone(final Mesh original) {
+ Mesh copy = null;
+ try {
+ copy = original.getClass().newInstance();
+ } catch (final InstantiationException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ }
+ copy.setName(original.getName() + "_copy");
+ copy.getSceneHints().set(original.getSceneHints());
+ copy.setTransform(original.getTransform());
+ copy.setDefaultColor(original.getDefaultColor());
+
+ for (final StateType type : StateType.values()) {
+ final RenderState state = original.getLocalRenderState(type);
+ if (state != null) {
+ copy.setRenderState(state);
+ }
+ }
+ return copy;
+ }
+
+ protected Node clone(final Node original) {
+ Node copy = null;
+ try {
+ copy = original.getClass().newInstance();
+ } catch (final InstantiationException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ } catch (final IllegalAccessException e) {
+ logger.log(Level.SEVERE, "Could not access final constructor of class "
+ + original.getClass().getCanonicalName(), e);
+ throw new RuntimeException(e);
+ }
+ copy.setName(original.getName() + "_copy");
+ copy.getSceneHints().set(original.getSceneHints());
+ copy.setTransform(original.getTransform());
+
+ for (final StateType type : StateType.values()) {
+ final RenderState state = original.getLocalRenderState(type);
+ if (state != null) {
+ copy.setRenderState(state);
+ }
+ }
+ return copy;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java
new file mode 100644
index 0000000..afa63c8
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/TangentUtil.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.MeshData;
+
+public class TangentUtil {
+ public static FloatBuffer generateTangentBuffer(final MeshData meshData) {
+ return generateTangentBuffer(meshData, 0);
+ }
+
+ public static FloatBuffer generateTangentBuffer(final MeshData meshData, final int uvUnit) {
+ final FloatBuffer vertexBuffer = meshData.getVertexBuffer();
+ if (vertexBuffer == null) {
+ throw new IllegalArgumentException("Vertex buffer is null!");
+ }
+
+ final FloatBuffer normalBuffer = meshData.getNormalBuffer();
+ if (normalBuffer == null) {
+ throw new IllegalArgumentException("Normal buffer is null!");
+ }
+
+ FloatBuffer textureBuffer = meshData.getTextureBuffer(uvUnit);
+ if (textureBuffer == null && uvUnit != 0) {
+ textureBuffer = meshData.getTextureBuffer(0);
+ }
+ if (textureBuffer == null) {
+ throw new IllegalArgumentException("Texture buffer is null!");
+ }
+
+ final IndexBufferData<?> indexBuffer = meshData.getIndices();
+ if (indexBuffer == null) {
+ throw new IllegalArgumentException("Index buffer is null!");
+ }
+
+ final int vertexCount = meshData.getVertexCount();
+ final int triangleCount = meshData.getTotalPrimitiveCount();
+
+ final Vector3[] tan1 = new Vector3[vertexCount];
+ final Vector3[] tan2 = new Vector3[vertexCount];
+ for (int i = 0; i < vertexCount; i++) {
+ tan1[i] = new Vector3();
+ tan2[i] = new Vector3();
+ }
+
+ final Vector3[] vertex = BufferUtils.getVector3Array(vertexBuffer);
+ final Vector3[] normal = BufferUtils.getVector3Array(normalBuffer);
+ final Vector2[] texcoord = BufferUtils.getVector2Array(textureBuffer);
+
+ for (int a = 0; a < triangleCount; a++) {
+ final int i1 = indexBuffer.get(a * 3);
+ final int i2 = indexBuffer.get(a * 3 + 1);
+ final int i3 = indexBuffer.get(a * 3 + 2);
+
+ final Vector3 v1 = vertex[i1];
+ final Vector3 v2 = vertex[i2];
+ final Vector3 v3 = vertex[i3];
+
+ final Vector2 w1 = texcoord[i1];
+ final Vector2 w2 = texcoord[i2];
+ final Vector2 w3 = texcoord[i3];
+
+ final float x1 = v2.getXf() - v1.getXf();
+ final float x2 = v3.getXf() - v1.getXf();
+ final float y1 = v2.getYf() - v1.getYf();
+ final float y2 = v3.getYf() - v1.getYf();
+ final float z1 = v2.getZf() - v1.getZf();
+ final float z2 = v3.getZf() - v1.getZf();
+
+ final float s1 = w2.getXf() - w1.getXf();
+ final float s2 = w3.getXf() - w1.getXf();
+ final float t1 = w2.getYf() - w1.getYf();
+ final float t2 = w3.getYf() - w1.getYf();
+
+ final float r = 1.0F / (s1 * t2 - s2 * t1);
+ if (Float.isNaN(r) || Float.isInfinite(r)) {
+ continue;
+ }
+ final Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
+ final Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
+
+ tan1[i1].addLocal(sdir);
+ tan1[i2].addLocal(sdir);
+ tan1[i3].addLocal(sdir);
+
+ tan2[i1].addLocal(tdir);
+ tan2[i2].addLocal(tdir);
+ tan2[i3].addLocal(tdir);
+ }
+
+ final FloatBuffer tangentBuffer = BufferUtils.createVector4Buffer(vertexCount);
+
+ final Vector3 calc1 = new Vector3();
+ final Vector3 calc2 = new Vector3();
+ for (int a = 0; a < vertexCount; a++) {
+ final Vector3 n = normal[a];
+ final Vector3 t = tan1[a];
+
+ // Gram-Schmidt orthogonalize
+ double dot = n.dot(t);
+ calc1.set(t).subtractLocal(n.multiply(dot, calc2)).normalizeLocal();
+ tangentBuffer.put(calc1.getXf()).put(calc1.getYf()).put(calc1.getZf());
+
+ // Calculate handedness
+ dot = calc1.set(n).crossLocal(t).dot(tan2[a]);
+ final float w = dot < 0.0f ? -1.0f : 1.0f;
+ tangentBuffer.put(w);
+ }
+
+ return tangentBuffer;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java
new file mode 100644
index 0000000..91bc14a
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertGroupData.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.EnumSet;
+import java.util.Map;
+
+import com.ardor3d.util.geom.GeometryTool.MatchCondition;
+import com.google.common.collect.Maps;
+
+public class VertGroupData {
+
+ public static final int DEFAULT_GROUP = 0;
+
+ private final Map<Long, EnumSet<MatchCondition>> _groupConditions = Maps.newHashMap();
+ private long[] _vertGroups = null;
+
+ public VertGroupData() {}
+
+ public void setGroupConditions(final long groupNumber, final EnumSet<MatchCondition> conditions) {
+ _groupConditions.put(groupNumber, conditions);
+ }
+
+ public EnumSet<MatchCondition> getGroupConditions(final long groupNumber) {
+ return _groupConditions.get(groupNumber);
+ }
+
+ public long getGroupForVertex(final int index) {
+ if (_vertGroups != null) {
+ return _vertGroups[index];
+ }
+ return DEFAULT_GROUP;
+ }
+
+ public void setVertGroups(final long[] vertGroupMap) {
+ _vertGroups = vertGroupMap;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java
new file mode 100644
index 0000000..2d0621b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertKey.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.EnumSet;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.geom.GeometryTool.MatchCondition;
+
+public class VertKey {
+
+ private final Vector3 _vert;
+ private final Vector3 _norm;
+ private final ColorRGBA _color;
+ private final Vector2[] _texs;
+ private final long _smoothGroup;
+ private final EnumSet<MatchCondition> _options;
+ private int _hashCode = 0;
+
+ public VertKey(final Vector3 vert, final Vector3 norm, final ColorRGBA color, final Vector2[] texs,
+ final EnumSet<MatchCondition> options) {
+ this(vert, norm, color, texs, options, 0);
+ }
+
+ public VertKey(final Vector3 vert, final Vector3 norm, final ColorRGBA color, final Vector2[] texs,
+ final EnumSet<MatchCondition> options, final long smoothGroup) {
+ _vert = vert;
+ _options = options != null ? options : EnumSet.noneOf(MatchCondition.class);
+ _norm = (_options.contains(MatchCondition.Normal)) ? norm : null;
+ _color = (_options.contains(MatchCondition.Color)) ? color : null;
+ _texs = (_options.contains(MatchCondition.UVs)) ? texs : null;
+ _smoothGroup = (_options.contains(MatchCondition.Group)) ? smoothGroup : 0;
+ }
+
+ @Override
+ public int hashCode() {
+ if (_hashCode != 0) {
+ return _hashCode;
+ }
+ _hashCode = _vert.hashCode();
+ if (_options.contains(MatchCondition.Normal) && _norm != null) {
+ final long x = Double.doubleToLongBits(_norm.getX());
+ _hashCode += 31 * _hashCode + (int) (x ^ (x >>> 32));
+
+ final long y = Double.doubleToLongBits(_norm.getY());
+ _hashCode += 31 * _hashCode + (int) (y ^ (y >>> 32));
+
+ final long z = Double.doubleToLongBits(_norm.getZ());
+ _hashCode += 31 * _hashCode + (int) (z ^ (z >>> 32));
+ }
+ if (_options.contains(MatchCondition.Color) && _color != null) {
+ final int r = Float.floatToIntBits(_color.getRed());
+ _hashCode += 31 * _hashCode + r;
+
+ final int g = Float.floatToIntBits(_color.getGreen());
+ _hashCode += 31 * _hashCode + g;
+
+ final int b = Float.floatToIntBits(_color.getBlue());
+ _hashCode += 31 * _hashCode + b;
+
+ final int a = Float.floatToIntBits(_color.getAlpha());
+ _hashCode += 31 * _hashCode + a;
+ }
+ if (_options.contains(MatchCondition.UVs) && _texs != null) {
+ for (int i = 0; i < _texs.length; i++) {
+ if (_texs[i] != null) {
+ final long x = Double.doubleToLongBits(_texs[i].getX());
+ _hashCode += 31 * _hashCode + (int) (x ^ (x >>> 32));
+
+ final long y = Double.doubleToLongBits(_texs[i].getY());
+ _hashCode += 31 * _hashCode + (int) (y ^ (y >>> 32));
+ }
+ }
+ }
+ if (_options.contains(MatchCondition.Group)) {
+ _hashCode += 31 * _hashCode + _smoothGroup;
+ }
+ return _hashCode;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof VertKey)) {
+ return false;
+ }
+
+ final VertKey other = (VertKey) obj;
+
+ if (other._options != _options) {
+ return false;
+ }
+ if (!other._vert.equals(_vert)) {
+ return false;
+ }
+
+ if (_options.contains(MatchCondition.Normal)) {
+ if (_norm != null) {
+ if (!_norm.equals(other._norm)) {
+ return false;
+ }
+ } else if (other._norm != null) {
+ return false;
+ }
+ }
+
+ if (_options.contains(MatchCondition.Color)) {
+ if (_color != null) {
+ if (!_color.equals(other._color)) {
+ return false;
+ }
+ } else if (other._color != null) {
+ return false;
+ }
+ }
+
+ if (_options.contains(MatchCondition.UVs)) {
+ if (_texs != null) {
+ if (other._texs == null || other._texs.length != _texs.length) {
+ return false;
+ }
+ for (int x = 0; x < _texs.length; x++) {
+ if (_texs[x] != null) {
+ if (!_texs[x].equals(other._texs[x])) {
+ return false;
+ }
+ } else if (other._texs[x] != null) {
+ return false;
+ }
+ }
+ } else if (other._texs != null) {
+ return false;
+ }
+ }
+
+ if (_options.contains(MatchCondition.Group)) {
+ if (other._smoothGroup != _smoothGroup) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java
new file mode 100644
index 0000000..bb33c38
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/geom/VertMap.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.geom;
+
+import java.util.Map;
+
+import com.ardor3d.scenegraph.Mesh;
+
+public class VertMap {
+
+ private int[] _lookupTable;
+
+ public VertMap(final Mesh mesh) {
+ setupTable(mesh);
+ }
+
+ private void setupTable(final Mesh mesh) {
+ _lookupTable = new int[mesh.getMeshData().getVertexCount()];
+ for (int x = 0; x < _lookupTable.length; x++) {
+ _lookupTable[x] = x;
+ }
+ }
+
+ public int getNewIndex(final int oldIndex) {
+ return _lookupTable[oldIndex];
+ }
+
+ public int getFirstOldIndex(final int newIndex) {
+ for (int i = 0; i < _lookupTable.length; i++) {
+ if (_lookupTable[i] == newIndex) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void applyRemapping(final Map<Integer, Integer> indexRemap) {
+ for (int i = 0; i < _lookupTable.length; i++) {
+ if (indexRemap.containsKey(_lookupTable[i])) {
+ _lookupTable[i] = indexRemap.get(_lookupTable[i]);
+ }
+ }
+ }
+
+ public int[] getLookupTable() {
+ return _lookupTable;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java
new file mode 100644
index 0000000..7ed7ce2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/MultiFormatResourceLocator.java
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Arrays;
+
+/**
+ * This class extends the behavior of the {@link SimpleResourceLocator} by replacing the resource's file extension with
+ * different various provided extensions. If none of these work, it will try the original resource name as-is. You can
+ * choose to have the original file searched for first, or last using {@link #setTrySpecifiedFormatFirst(boolean)}.
+ */
+public class MultiFormatResourceLocator extends SimpleResourceLocator {
+
+ private final String[] _extensions;
+ private boolean _trySpecifiedFormatFirst = false;
+
+ /**
+ * Construct a new MultiFormatResourceLocator using the given URI as our context and the list of possible extensions
+ * as extensions to try during file search.
+ *
+ * @param baseDir
+ * our base context. This is meant to be a "directory" wherein we will search for resources. Therefore,
+ * if it does not end in /, a / will be added to ensure we are talking about children of the given
+ * baseDir.
+ * @param extensions
+ * an array of extensions (eg. ".png", ".dds", ".tga", etc.) to try while searching for a resource with
+ * this locator. This is done by replacing any existing extension in the resource name with each of the
+ * given extensions.
+ * @throws URISyntaxException
+ * if the given URI does not end in / and we can not make a new URI with a trailing / from it.
+ */
+ public MultiFormatResourceLocator(final URI baseDir, final String... extensions) throws URISyntaxException {
+ super(baseDir);
+
+ if (extensions == null) {
+ throw new NullPointerException("extensions can not be null.");
+ }
+ _extensions = extensions;
+ }
+
+ /**
+ * Construct a new MultiFormatResourceLocator using the given URL as our context and the list of possible extensions
+ * as extensions to try during file search.
+ *
+ * @param baseDir
+ * our base context. This is converted to a URI. This is meant to be a "directory" wherein we will search
+ * for resources. Therefore, if it does not end in /, a / will be added to ensure we are talking about
+ * children of the given baseDir.
+ * @param extensions
+ * an array of extensions (eg. ".png", ".dds", ".tga", etc.) to try while searching for a resource with
+ * this locator. This is done by replacing any existing extension in the resource name with each of the
+ * given extensions.
+ * @throws URISyntaxException
+ * if this URL can not be converted to a URI, or if the converted URI does not end in / and we can not
+ * make a new URI with a trailing / from it.
+ */
+ public MultiFormatResourceLocator(final URL baseDir, final String... extensions) throws URISyntaxException {
+ this(baseDir.toURI(), extensions);
+ }
+
+ @Override
+ public ResourceSource locateResource(String resourceName) {
+ resourceName = cleanup(resourceName);
+
+ if (_trySpecifiedFormatFirst) {
+ final ResourceSource src = doRecursiveLocate(resourceName);
+ if (src != null) {
+ return src;
+ }
+ }
+
+ final String baseFileName = getBaseFileName(resourceName);
+ for (final String extension : _extensions) {
+ final ResourceSource src = doRecursiveLocate(baseFileName + extension);
+ if (src != null) {
+ return src;
+ }
+ }
+
+ if (!_trySpecifiedFormatFirst) {
+ // If all else fails, just try the original name.
+ return doRecursiveLocate(resourceName);
+ } else {
+ return null;
+ }
+ }
+
+ private String getBaseFileName(final String resourceName) {
+ final File f = new File(resourceName);
+ final String name = f.getPath();
+ final int dot = name.lastIndexOf('.');
+ if (dot < 0) {
+ return name;
+ } else {
+ return name.substring(0, dot);
+ }
+ }
+
+ public boolean isTrySpecifiedFormatFirst() {
+ return _trySpecifiedFormatFirst;
+ }
+
+ public void setTrySpecifiedFormatFirst(final boolean trySpecifiedFormatFirst) {
+ _trySpecifiedFormatFirst = trySpecifiedFormatFirst;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof MultiFormatResourceLocator) {
+ return getBaseDir().equals(((MultiFormatResourceLocator) obj).getBaseDir())
+ && Arrays.equals(_extensions, ((MultiFormatResourceLocator) obj)._extensions);
+ }
+ return super.equals(obj);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java
new file mode 100644
index 0000000..5f959f9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/RelativeResourceLocator.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+/**
+ * This locator takes a base resource and tries to find other resources that are "relative" to it. What relative means
+ * may be up to the type of ResourceSource given.
+ */
+public class RelativeResourceLocator implements ResourceLocator {
+
+ private final ResourceSource _baseSource;
+
+ /**
+ * Construct a new RelativeResourceLocator using the given source as our base.
+ *
+ * @param resource
+ * our base source.
+ */
+ public RelativeResourceLocator(final ResourceSource resource) {
+ assert (resource != null) : "source may not be null.";
+
+ _baseSource = resource;
+ }
+
+ public ResourceSource getBaseSource() {
+ return _baseSource;
+ }
+
+ public ResourceSource locateResource(String resourceName) {
+ // Trim off any prepended local dir.
+ while (resourceName.startsWith("./") && resourceName.length() > 2) {
+ resourceName = resourceName.substring(2);
+ }
+ while (resourceName.startsWith(".\\") && resourceName.length() > 2) {
+ resourceName = resourceName.substring(2);
+ }
+
+ return _baseSource.getRelativeSource(resourceName);
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof RelativeResourceLocator) {
+ return _baseSource.equals(((RelativeResourceLocator) obj)._baseSource);
+ }
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java
new file mode 100644
index 0000000..8353527
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocator.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+/**
+ * Interface for locating resources from resource names.
+ */
+public interface ResourceLocator {
+
+ /**
+ * Locates a resource according to the strategy of the resource locator implementation (subclass).
+ *
+ * @param resourceName
+ * the name of the resource to locate; it this is a path it must be slash separated (no backslashes)
+ * @see SimpleResourceLocator
+ * @see MultiFormatResourceLocator
+ * @return a source for the resource, null if the resource was not found
+ */
+ public ResourceSource locateResource(String resourceName);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java
new file mode 100644
index 0000000..2fdc2cc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceLocatorTool.java
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Manager class for locator utility classes used to find various assets. (XXX: Needs more documentation)
+ */
+public class ResourceLocatorTool {
+ private static final Logger logger = Logger.getLogger(ResourceLocatorTool.class.getName());
+
+ public static final String TYPE_TEXTURE = "texture";
+ public static final String TYPE_MODEL = "model";
+ public static final String TYPE_PARTICLE = "particle";
+ public static final String TYPE_AUDIO = "audio";
+ public static final String TYPE_SHADER = "shader";
+
+ private static final Map<String, List<ResourceLocator>> _locatorMap = new HashMap<String, List<ResourceLocator>>();
+
+ public static ResourceSource locateResource(final String resourceType, String resourceName) {
+ if (resourceName == null) {
+ return null;
+ }
+
+ try {
+ resourceName = URLDecoder.decode(resourceName, "UTF-8");
+ } catch (final UnsupportedEncodingException ex) {
+ ex.printStackTrace();
+ }
+
+ synchronized (_locatorMap) {
+ final List<ResourceLocator> bases = _locatorMap.get(resourceType);
+ if (bases != null) {
+ for (int i = bases.size(); --i >= 0;) {
+ final ResourceLocator loc = bases.get(i);
+ final ResourceSource rVal = loc.locateResource(resourceName);
+ if (rVal != null) {
+ return rVal;
+ }
+ }
+ }
+ // last resort...
+ try {
+ final URL u = ResourceLocatorTool.getClassPathResource(ResourceLocatorTool.class, resourceName);
+ if (u != null) {
+ return new URLResourceSource(u);
+ }
+ } catch (final Exception e) {
+ logger.logp(Level.WARNING, ResourceLocatorTool.class.getName(), "locateResource(String, String)", e
+ .getMessage(), e);
+ }
+
+ logger.warning("Unable to locate: " + resourceName);
+ return null;
+ }
+ }
+
+ public static void addResourceLocator(final String resourceType, final ResourceLocator locator) {
+ if (locator == null) {
+ return;
+ }
+ synchronized (_locatorMap) {
+ List<ResourceLocator> bases = _locatorMap.get(resourceType);
+ if (bases == null) {
+ bases = new ArrayList<ResourceLocator>();
+ _locatorMap.put(resourceType, bases);
+ }
+
+ if (!bases.contains(locator)) {
+ bases.add(locator);
+ }
+ }
+ }
+
+ public static boolean removeResourceLocator(final String resourceType, final ResourceLocator locator) {
+ synchronized (_locatorMap) {
+ final List<ResourceLocator> bases = _locatorMap.get(resourceType);
+ if (bases == null) {
+ return false;
+ }
+ return bases.remove(locator);
+ }
+ }
+
+ /**
+ * Locate a resource using various classloaders.
+ *
+ * <ul>
+ * <li>First it tries the Thread.currentThread().getContextClassLoader().</li>
+ * <li>Then it tries the ClassLoader.getSystemClassLoader() (if not same as context class loader).</li>
+ * <li>Finally it tries the clazz.getClassLoader()</li>
+ * </ul>
+ *
+ * @param clazz
+ * a class to use as a local reference.
+ * @param name
+ * the name and path of the resource.
+ * @return the URL of the resource, or null if none found.
+ */
+ public static URL getClassPathResource(final Class<?> clazz, final String name) {
+ URL result = Thread.currentThread().getContextClassLoader().getResource(name);
+ if (result == null
+ && !Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) {
+ result = ClassLoader.getSystemResource(name);
+ }
+ if (result == null) {
+ result = clazz.getClassLoader().getResource(name);
+ }
+ return result;
+ }
+
+ /**
+ * Locate a resource using various classloaders and open a stream to it.
+ *
+ * @param clazz
+ * a class to use as a local reference.
+ * @param name
+ * the name and path of the resource.
+ * @return the input stream if resource is found, or null if not.
+ */
+ public static InputStream getClassPathResourceAsStream(final Class<?> clazz, final String name) {
+ InputStream result = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
+ if (result == null
+ && !Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) {
+ result = ClassLoader.getSystemResourceAsStream(name);
+ }
+ if (result == null) {
+ result = clazz.getClassLoader().getResourceAsStream(name);
+ }
+ return result;
+ }
+
+ /**
+ * Locate all instances of a resource using various classloaders.
+ *
+ * @param clazz
+ * a class to use as a local reference.
+ * @param name
+ * the name and path of the resource.
+ * @return a set containing the located URLs of the named resource.
+ */
+ public static Set<URL> getClassPathResources(final Class<?> clazz, final String name) {
+ final Set<URL> results = Sets.newHashSet();
+ Enumeration<URL> urls = null;
+ try {
+ urls = Thread.currentThread().getContextClassLoader().getResources(name);
+ for (; urls.hasMoreElements();) {
+ results.add(urls.nextElement());
+ }
+ } catch (final IOException ioe) {
+ }
+ if (!Thread.currentThread().getContextClassLoader().equals(ClassLoader.getSystemClassLoader())) {
+ try {
+ urls = ClassLoader.getSystemResources(name);
+ for (; urls.hasMoreElements();) {
+ results.add(urls.nextElement());
+ }
+ } catch (final IOException ioe) {
+ }
+ }
+ try {
+ urls = clazz.getClassLoader().getResources(name);
+ for (; urls.hasMoreElements();) {
+ results.add(urls.nextElement());
+ }
+ } catch (final IOException ioe) {
+ }
+ return results;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java
new file mode 100644
index 0000000..0e6f024
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/ResourceSource.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.ardor3d.util.export.Savable;
+
+/**
+ * Represents a named resource
+ */
+public interface ResourceSource extends Savable {
+
+ public static final String UNKNOWN_TYPE = "-unknown-";
+
+ /**
+ * @return the name of this resource.
+ */
+ String getName();
+
+ /**
+ * @return the "type" of resource we are pointing to. For example ".jpg", ".dae", etc.
+ */
+ String getType();
+
+ /**
+ * Generate and return a new ResourceSource pointing to a named resource that is relative to this object's resource.
+ *
+ * @param name
+ * the name of the resource we want. eg. "./mypic.jpg" etc.
+ * @return the relative resource, or null if none is found. Will also return null if this ResourceSource type does
+ * not support relative source.
+ */
+ ResourceSource getRelativeSource(String name);
+
+ /**
+ * @return an InputStream to this resource's contents.
+ * @throws IOException
+ */
+ InputStream openStream() throws IOException;
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java
new file mode 100644
index 0000000..580f408
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/SimpleResourceLocator.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLEncoder;
+
+/**
+ * This locator takes a base location for finding resources specified with a relative path. If it cannot find the path
+ * relative to the location, it successively omits the starting components of the relative path until it can find a
+ * resources with such a trimmed path. If no resource is found with this method null is returned.
+ */
+public class SimpleResourceLocator implements ResourceLocator {
+
+ private final URI _baseDir;
+
+ /**
+ * Construct a new SimpleResourceLocator using the given URI as our context.
+ *
+ * @param baseDir
+ * our base context. This is meant to be a "directory" wherein we will search for resources. Therefore,
+ * if it does not end in /, a / will be added to ensure we are talking about children of the given
+ * baseDir.
+ * @throws NullPointerException
+ * if the given URI is null.
+ * @throws URISyntaxException
+ * if the given URI does not end in / and we can not make a new URI with a trailing / from it.
+ */
+ public SimpleResourceLocator(final URI baseDir) throws URISyntaxException {
+ if (baseDir == null) {
+ throw new NullPointerException("baseDir can not be null.");
+ }
+
+ final String uri = baseDir.toString();
+ if (!uri.endsWith("/")) {
+ _baseDir = new URI(baseDir.toString() + "/");
+ } else {
+ _baseDir = baseDir;
+ }
+ }
+
+ /**
+ * Construct a new SimpleResourceLocator using the given URL as our context.
+ *
+ * @param baseDir
+ * our base context. This is converted to a URI. This is meant to be a "directory" wherein we will search
+ * for resources. Therefore, if it does not end in /, a / will be added to ensure we are talking about
+ * children of the given baseDir.
+ * @throws NullPointerException
+ * if the given URL is null.
+ * @throws URISyntaxException
+ * if this URL can not be converted to a URI, or if the converted URI does not end in / and we can not
+ * make a new URI with a trailing / from it.
+ */
+ public SimpleResourceLocator(final URL baseDir) throws URISyntaxException {
+ this(baseDir.toURI());
+ }
+
+ public URI getBaseDir() {
+ return _baseDir;
+ }
+
+ public ResourceSource locateResource(final String resourceName) {
+ return doRecursiveLocate(cleanup(resourceName));
+ }
+
+ protected ResourceSource doRecursiveLocate(String resourceName) {
+ // Trim off any prepended local dir.
+ while (resourceName.startsWith("./") && resourceName.length() > 2) {
+ resourceName = resourceName.substring(2);
+ }
+ while (resourceName.startsWith(".\\") && resourceName.length() > 2) {
+ resourceName = resourceName.substring(2);
+ }
+
+ // Try to locate using resourceName as is.
+ try {
+ String spec = URLEncoder.encode(resourceName, "UTF-8");
+ // this fixes a bug in JRE1.5 (file handler does not decode "+" to spaces)
+ spec = spec.replaceAll("\\+", "%20");
+
+ final URL rVal = new URL(_baseDir.toURL(), spec);
+ // open a stream to see if this is a valid resource
+ // XXX: Perhaps this is wasteful? Also, what info will determine validity?
+ rVal.openStream().close();
+ return new URLResourceSource(rVal);
+ } catch (final IOException e) {
+ // URL wasn't valid in some way, so try up a path.
+ } catch (final IllegalArgumentException e) {
+ // URL wasn't valid in some way, so try up a path.
+ }
+
+ resourceName = trimResourceName(resourceName);
+ if (resourceName == null) {
+ return null;
+ } else {
+ return doRecursiveLocate(resourceName);
+ }
+ }
+
+ protected String trimResourceName(String resourceName) {
+ // it's possible this URL has back slashes, so replace them.
+ resourceName = cleanup(resourceName);
+ final int firstSlashIndex = resourceName.indexOf('/');
+ if (firstSlashIndex >= 0 && firstSlashIndex < resourceName.length() - 1) {
+ return resourceName.substring(firstSlashIndex + 1);
+ } else {
+ return null;
+ }
+ }
+
+ protected String cleanup(String name) {
+ // Replace any %2F (or %2f) with forward slashes
+ name = name.replaceAll("\\%2[F,f]", "/");
+ // replace back slashes with forward
+ name = name.replace('\\', '/');
+ return name;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof SimpleResourceLocator) {
+ return _baseDir.equals(((SimpleResourceLocator) obj)._baseDir);
+ }
+ return false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java
new file mode 100644
index 0000000..69a2339
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/StringResourceSource.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * ResourceSource that pulls its content from a String. This source type does not support relative sources.
+ */
+public class StringResourceSource implements ResourceSource {
+
+ /** Our class logger */
+ private static final Logger logger = Logger.getLogger(StringResourceSource.class.getName());
+
+ /** The data this source returns. */
+ private String _data;
+
+ /** An optional type value for the source. */
+ private String _type;
+
+ /**
+ * Construct a new StringResourceSource.
+ *
+ * @param data
+ * the data this source should return.
+ */
+ public StringResourceSource(final String data) {
+ this(data, null);
+ }
+
+ /**
+ * Construct a new StringResourceSource.
+ *
+ * @param data
+ * the data this source should return.
+ * @param type
+ * the type for this source. Usually a file extension such as .txt or .js. Required for generic loading
+ * when multiple resource handlers could be used.
+ */
+ public StringResourceSource(final String data, final String type) {
+ _data = data;
+ _type = type;
+ }
+
+ /**
+ * Returns "string resource" as strings have no name.
+ */
+ public String getName() {
+ return "string resource";
+ }
+
+ /**
+ * Returns null and logs a warning as this is not supported.
+ */
+ public ResourceSource getRelativeSource(final String name) {
+ if (logger.isLoggable(Level.WARNING)) {
+ logger.logp(Level.WARNING, getClass().getName(), "getRelativeSource(String)",
+ "StringResourceSource does not support this method.");
+ }
+ return null;
+ }
+
+ public String getType() {
+ return _type;
+ }
+
+ /**
+ * Grabs our data as a UTF8 byte array and returns it in a ByteArrayInputStream.
+ */
+ public InputStream openStream() throws IOException {
+ return new ByteArrayInputStream(_data.getBytes("UTF8"));
+ }
+
+ // /////////////////
+ // Methods for Savable
+ // /////////////////
+
+ public Class<?> getClassTag() {
+ return StringResourceSource.class;
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ _data = capsule.readString("data", null);
+ _type = capsule.readString("type", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_data, "data", null);
+ capsule.write(_type, "type", null);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java
new file mode 100644
index 0000000..9bcc3c0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/resource/URLResourceSource.java
@@ -0,0 +1,204 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.resource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.UrlUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+public class URLResourceSource implements ResourceSource {
+ private static final Logger logger = Logger.getLogger(URLResourceSource.class.getName());
+
+ private URL _url;
+ private String _type;
+
+ // used to make equals more efficient
+ private transient String _urlToString = null;
+
+ /**
+ * Construct a new URLResourceSource. Must set URL separately.
+ */
+ public URLResourceSource() {}
+
+ /**
+ * Construct a new URLResourceSource from a specific URL.
+ *
+ * @param sourceUrl
+ * The url to load the resource from. Must not be null. If the URL has a valid URL filename (see
+ * {@link URL#getFile()}) and an extension (eg. http://url/myFile.png) then the extension (.png in this
+ * case) is used as the type.
+ */
+ public URLResourceSource(final URL sourceUrl) {
+ assert (sourceUrl != null) : "sourceUrl must not be null";
+ setURL(sourceUrl);
+
+ // add type, if present
+ final String fileName = _url.getFile();
+ if (fileName != null) {
+ final int dot = fileName.lastIndexOf('.');
+ if (dot >= 0) {
+ _type = fileName.substring(dot);
+ } else {
+ _type = UNKNOWN_TYPE;
+ }
+ }
+ }
+
+ /**
+ * Construct a new URLResourceSource from a specific URL and type.
+ *
+ * @param sourceUrl
+ * The url to load the resource from. Must not be null.
+ * @param type
+ * our type. Usually a file extension such as .png. Required for generic loading when multiple resource
+ * handlers could be used.
+ */
+ public URLResourceSource(final URL sourceUrl, final String type) {
+ assert (sourceUrl != null) : "sourceUrl must not be null";
+ setURL(sourceUrl);
+
+ _type = type;
+ }
+
+ public ResourceSource getRelativeSource(final String name) {
+ try {
+ final URL srcURL = UrlUtils.resolveRelativeURL(_url, "./" + name);
+ if (srcURL != null) {
+ // check if the URL can be opened
+ // just force it to try to grab info
+ srcURL.openStream().close();
+ // Ok satisfied... return
+ return new URLResourceSource(srcURL);
+
+ }
+ } catch (final MalformedURLException ex) {
+ } catch (final IOException ex) {
+ }
+ if (logger.isLoggable(Level.FINEST)) {
+ logger.logp(Level.FINEST, getClass().getName(), "getRelativeSource(String)",
+ "Unable to find relative file {0} from {1}.", new Object[] { name, _url });
+ }
+ return null;
+ }
+
+ public void setURL(final URL url) {
+ _url = url;
+ _urlToString = url != null ? url.toString() : null;
+ }
+
+ public URL getURL() {
+ return _url;
+ }
+
+ public String getName() {
+ return _urlToString;
+ }
+
+ public String getType() {
+ return _type;
+ }
+
+ public void setType(final String type) {
+ _type = type;
+ }
+
+ public InputStream openStream() throws IOException {
+ return _url.openStream();
+ }
+
+ /**
+ * @return the string representation of this URLResourceSource.
+ */
+ @Override
+ public String toString() {
+ return "URLResourceSource [url=" + _urlToString + ", type=" + _type + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((_type == null) ? 0 : _type.hashCode());
+ result = prime * result + ((_urlToString == null) ? 0 : _urlToString.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof URLResourceSource)) {
+ return false;
+ }
+ final URLResourceSource other = (URLResourceSource) obj;
+ if (_type == null) {
+ if (other._type != null) {
+ return false;
+ }
+ } else if (!_type.equals(other._type)) {
+ return false;
+ }
+ if (_url == null) {
+ if (other._url != null) {
+ return false;
+ }
+ } else if (!_urlToString.equals(other._urlToString)) {
+ return false;
+ }
+ return true;
+ }
+
+ public Class<?> getClassTag() {
+ return URLResourceSource.class;
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ final String protocol = capsule.readString("protocol", null);
+ final String host = capsule.readString("host", null);
+ final String file = capsule.readString("file", null);
+ if (file != null) {
+ // see if we would like to divert this to a new location.
+ final ResourceSource src = ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE,
+ URLDecoder.decode(file, "UTF-8"));
+ if (src instanceof URLResourceSource) {
+ setURL(((URLResourceSource) src)._url);
+ _type = ((URLResourceSource) src)._type;
+ return;
+ }
+ }
+
+ if (protocol != null && host != null && file != null) {
+ setURL(new URL(protocol, host, file));
+ }
+
+ _type = capsule.readString("type", null);
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(_url.getProtocol(), "protocol", null);
+ capsule.write(_url.getHost(), "host", null);
+ capsule.write(_url.getFile(), "file", null);
+
+ capsule.write(_type, "type", null);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java
new file mode 100644
index 0000000..3f9af0e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/CompileOptions.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.scenegraph;
+
+public class CompileOptions {
+
+ private boolean _displayList;
+
+ public boolean isDisplayList() {
+ return _displayList;
+ }
+
+ public void setDisplayList(final boolean displayList) {
+ _displayList = displayList;
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java
new file mode 100644
index 0000000..b8f5680
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/DisplayListDelegate.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.scenegraph;
+
+import java.lang.ref.ReferenceQueue;
+import java.util.Map;
+
+import com.ardor3d.renderer.ContextCleanListener;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.RendererCallable;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.util.GameTaskQueueManager;
+import com.ardor3d.util.SimpleContextIdReference;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.MapMaker;
+import com.google.common.collect.Multimap;
+
+public class DisplayListDelegate implements RenderDelegate {
+
+ private static Map<DisplayListDelegate, Object> _identityCache = new MapMaker().weakKeys().makeMap();
+ private static final Object STATIC_REF = new Object();
+
+ private static ReferenceQueue<DisplayListDelegate> _refQueue = new ReferenceQueue<DisplayListDelegate>();
+
+ static {
+ ContextManager.addContextCleanListener(new ContextCleanListener() {
+ public void cleanForContext(final RenderContext renderContext) {
+ // TODO: Need a way to call back to the creator of the display list?
+ }
+ });
+ }
+
+ private final SimpleContextIdReference<DisplayListDelegate> _id;
+
+ public DisplayListDelegate(final int id, final Object glContext) {
+ _id = new SimpleContextIdReference<DisplayListDelegate>(this, _refQueue, id, glContext);
+ _identityCache.put(this, STATIC_REF);
+ }
+
+ public void render(final Spatial spatial, final Renderer renderer) {
+ // do transforms
+ final boolean transformed = renderer.doTransforms(spatial.getWorldTransform());
+
+ // render display list.
+ renderer.renderDisplayList(_id.getId());
+
+ // Our states are in an unknown state at this point, so invalidate tracking.
+ ContextManager.getCurrentContext().invalidateStates();
+
+ // undo transforms
+ if (transformed) {
+ renderer.undoTransforms(spatial.getWorldTransform());
+ }
+ }
+
+ public static void cleanAllDisplayLists(final Renderer deleter) {
+ final Multimap<Object, Integer> idMap = ArrayListMultimap.create();
+
+ // gather up expired Display Lists... these don't exist in our cache
+ gatherGCdIds(idMap);
+
+ // Walk through the cached items and delete those too.
+ for (final DisplayListDelegate buf : _identityCache.keySet()) {
+ // Add id to map
+ idMap.put(buf._id.getGlContext(), buf._id.getId());
+ }
+
+ handleDisplayListDelete(deleter, idMap);
+ }
+
+ public static void cleanExpiredDisplayLists(final Renderer deleter) {
+ // gather up expired display lists...
+ final Multimap<Object, Integer> idMap = gatherGCdIds(null);
+
+ if (idMap != null) {
+ // send to be deleted on next render.
+ handleDisplayListDelete(deleter, idMap);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Multimap<Object, Integer> gatherGCdIds(Multimap<Object, Integer> store) {
+ // Pull all expired display lists from ref queue and add to an id multimap.
+ SimpleContextIdReference<DisplayListDelegate> ref;
+ while ((ref = (SimpleContextIdReference<DisplayListDelegate>) _refQueue.poll()) != null) {
+ if (store == null) { // lazy init
+ store = ArrayListMultimap.create();
+ }
+ store.put(ref.getGlContext(), ref.getId());
+ ref.clear();
+ }
+ return store;
+ }
+
+ private static void handleDisplayListDelete(final Renderer deleter, final Multimap<Object, Integer> idMap) {
+ Object currentGLRef = null;
+ // Grab the current context, if any.
+ if (deleter != null && ContextManager.getCurrentContext() != null) {
+ currentGLRef = ContextManager.getCurrentContext().getGlContextRep();
+ }
+ // For each affected context...
+ for (final Object glref : idMap.keySet()) {
+ // If we have a deleter and the context is current, immediately delete
+ if (deleter != null && glref.equals(currentGLRef)) {
+ deleter.deleteDisplayLists(idMap.get(glref));
+ }
+ // Otherwise, add a delete request to that context's render task queue.
+ else {
+ GameTaskQueueManager.getManager(ContextManager.getContextForRef(glref)).render(
+ new RendererCallable<Void>() {
+ public Void call() throws Exception {
+ getRenderer().deleteDisplayLists(idMap.get(glref));
+ return null;
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java
new file mode 100644
index 0000000..3b43e29
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/RenderDelegate.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.scenegraph;
+
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Spatial;
+
+public interface RenderDelegate {
+
+ void render(Spatial spatial, Renderer renderer);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java
new file mode 100644
index 0000000..285806e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/scenegraph/SceneCompiler.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it under the terms of its license which may be found
+ * in the accompanying LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.scenegraph;
+
+import com.ardor3d.bounding.BoundingVolume;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.RenderContext;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.visitor.Visitor;
+
+public class SceneCompiler {
+
+ public static void compile(final Spatial scene, final Renderer renderer, final CompileOptions options) {
+ // are we making a display list?
+ if (options.isDisplayList()) {
+ // grab our current context
+ final RenderContext context = ContextManager.getCurrentContext();
+
+ // handle camera...
+ // save the current camera...
+ final Camera originalCam = context.getCurrentCamera();
+ // replace with a camera that will always pass frustum checks
+ final Camera yesCam = new Camera(originalCam) {
+ @Override
+ public FrustumIntersect contains(final BoundingVolume bound) {
+ return FrustumIntersect.Inside;
+ }
+ };
+ context.setCurrentCamera(yesCam);
+
+ // setup for display list...
+ // force all textures to load so their setup calls are not part of the displaylist
+ scene.acceptVisitor(new TextureApplyVisitor(renderer), true);
+ // invalidate any current opengl state information.
+ context.invalidateStates();
+ // generate a DL id by starting our list
+ final int id = renderer.startDisplayList();
+ // push our current buckets to back
+ renderer.getQueue().pushBuckets();
+
+ // render...
+ // render our spatial
+ scene.draw(renderer);
+ // process buckets and then pop them
+ renderer.renderBuckets();
+ renderer.getQueue().popBuckets();
+
+ // end list
+ renderer.endDisplayList();
+
+ // restore old camera
+ context.setCurrentCamera(originalCam);
+
+ // add a display list delegate to the given Spatial
+ scene.setRenderDelegate(new DisplayListDelegate(id, context.getGlContextRep()), context.getGlContextRep());
+ }
+ }
+
+ static class TextureApplyVisitor implements Visitor {
+ private final Renderer _renderer;
+
+ public TextureApplyVisitor(final Renderer renderer) {
+ _renderer = renderer;
+ }
+
+ public void visit(final Spatial spatial) {
+ if (spatial instanceof Mesh) {
+ final Mesh mesh = (Mesh) spatial;
+ final TextureState state = (TextureState) mesh.getWorldRenderState(StateType.Texture);
+ if (state != null) {
+ _renderer.applyState(state.getType(), state);
+ }
+ }
+ }
+
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java
new file mode 100644
index 0000000..2031798
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExportable.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.screen;
+
+import java.nio.ByteBuffer;
+
+import com.ardor3d.image.ImageDataFormat;
+
+public interface ScreenExportable {
+
+ /**
+ * Export the given image data (byte buffer) in a manner of our choosing. Note that this byte buffer should be
+ * treated by the implementing class as immutable and temporary. If you need access to it after returning from the
+ * method, make a copy.
+ *
+ * @param data
+ * the data from the screen. Please respect the data's limit() value.
+ * @param width
+ * @param height
+ */
+ public void export(ByteBuffer data, int width, int height);
+
+ /**
+ *
+ * @return the image data format we'd like to pull in.
+ */
+ public ImageDataFormat getFormat();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java
new file mode 100644
index 0000000..cd88e96
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/screen/ScreenExporter.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.screen;
+
+import java.nio.ByteBuffer;
+
+import com.ardor3d.image.ImageDataFormat;
+import com.ardor3d.image.PixelDataType;
+import com.ardor3d.image.util.ImageUtils;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.util.geom.BufferUtils;
+
+public class ScreenExporter {
+
+ static ByteBuffer _scratch = BufferUtils.createByteBuffer(1);
+
+ public synchronized static void exportCurrentScreen(final Renderer renderer, final ScreenExportable exportable) {
+ final ImageDataFormat format = exportable.getFormat();
+ final Camera camera = Camera.getCurrentCamera();
+ final int width = camera.getWidth(), height = camera.getHeight();
+
+ // prepare our data buffer
+ final int size = width * height * ImageUtils.getPixelByteSize(format, PixelDataType.UnsignedByte);
+ if (_scratch.capacity() < size) {
+ _scratch = BufferUtils.createByteBuffer(size);
+ } else {
+ _scratch.limit(size);
+ _scratch.rewind();
+ }
+
+ // Ask the renderer for the current scene to be stored in the buffer
+ renderer.grabScreenContents(_scratch, format, 0, 0, width, height);
+
+ // send the buffer to the exportable object for processing.
+ exportable.export(_scratch, width, height);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java
new file mode 100644
index 0000000..a425753
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/ShaderVariable.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.export.Savable;
+
+/**
+ * An utily class to store shader's uniform variables content.
+ */
+public class ShaderVariable implements Savable {
+ /** Name of the uniform variable. * */
+ public String name;
+
+ /** ID of uniform. * */
+ public int variableID = -1;
+
+ /** Needs to be refreshed */
+ public boolean needsRefresh = true;
+
+ public boolean errorLogged = false;
+
+ public boolean hasData() {
+ return true;
+ }
+
+ public int getSize() {
+ return 1;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ShaderVariable)) {
+ return false;
+ }
+ final ShaderVariable comp = (ShaderVariable) o;
+ if (variableID != -1) {
+ return comp.variableID == variableID;
+ } else if (comp.variableID != -1) {
+ return comp.variableID == variableID;
+ } else {
+ return (name.equals(comp.name));
+ }
+ }
+
+ public void write(final OutputCapsule capsule) throws IOException {
+ capsule.write(name, "name", "");
+ capsule.write(variableID, "variableID", -1);
+ }
+
+ public void read(final InputCapsule capsule) throws IOException {
+ name = capsule.readString("name", "");
+ variableID = capsule.readInt("variableID", -1);
+ }
+
+ public Class<? extends ShaderVariable> getClassTag() {
+ return this.getClass();
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java
new file mode 100644
index 0000000..ea380fa
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableFloat */
+public class ShaderVariableFloat extends ShaderVariable {
+ public float value1;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0.0f);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readFloat("value1", 0.0f);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java
new file mode 100644
index 0000000..ff6f7b1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat2.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableFloat2 */
+public class ShaderVariableFloat2 extends ShaderVariable {
+ public float value1;
+ public float value2;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0.0f);
+ capsule.write(value2, "value2", 0.0f);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readFloat("value1", 0.0f);
+ value2 = capsule.readFloat("value2", 0.0f);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java
new file mode 100644
index 0000000..076f5de
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat3.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableFloat3 */
+public class ShaderVariableFloat3 extends ShaderVariable {
+ public float value1;
+ public float value2;
+ public float value3;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0.0f);
+ capsule.write(value2, "value2", 0.0f);
+ capsule.write(value3, "value3", 0.0f);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readFloat("value1", 0.0f);
+ value2 = capsule.readFloat("value2", 0.0f);
+ value3 = capsule.readFloat("value3", 0.0f);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java
new file mode 100644
index 0000000..2aef387
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloat4.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableFloat4 */
+public class ShaderVariableFloat4 extends ShaderVariable {
+ public float value1;
+ public float value2;
+ public float value3;
+ public float value4;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0.0f);
+ capsule.write(value2, "value2", 0.0f);
+ capsule.write(value3, "value3", 0.0f);
+ capsule.write(value4, "value4", 0.0f);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readFloat("value1", 0.0f);
+ value2 = capsule.readFloat("value2", 0.0f);
+ value3 = capsule.readFloat("value3", 0.0f);
+ value4 = capsule.readFloat("value4", 0.0f);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java
new file mode 100644
index 0000000..fbbd891
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableFloatArray.java
@@ -0,0 +1 @@
+/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.util.shader.uniformtypes; import java.io.IOException; import java.nio.FloatBuffer; import com.ardor3d.util.export.InputCapsule; import com.ardor3d.util.export.OutputCapsule; import com.ardor3d.util.shader.ShaderVariable; /** ShaderVariableFloatArray */ public class ShaderVariableFloatArray extends ShaderVariable { public FloatBuffer value; /** * Specifies the number of values for each element of the array. Must be 1, 2, 3, or 4. */ public int size = 1; @Override public boolean hasData() { return value != null; } @Override public void write(final OutputCapsule capsule) throws IOException { super.write(capsule); capsule.write(value, "value", null); capsule.write(size, "size", 1); } @Override public void read(final InputCapsule capsule) throws IOException { super.read(capsule); value = capsule.readFloatBuffer("value", null); size = capsule.readInt("size", 1); } } \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java
new file mode 100644
index 0000000..d3f09a9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableInt */
+public class ShaderVariableInt extends ShaderVariable {
+ public int value1;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readInt("value1", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java
new file mode 100644
index 0000000..fc9e4ca
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt2.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableInt2 */
+public class ShaderVariableInt2 extends ShaderVariable {
+ public int value1;
+ public int value2;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0);
+ capsule.write(value2, "value2", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readInt("value1", 0);
+ value2 = capsule.readInt("value2", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java
new file mode 100644
index 0000000..ac859cd
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt3.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableInt3 */
+public class ShaderVariableInt3 extends ShaderVariable {
+ public int value1;
+ public int value2;
+ public int value3;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0);
+ capsule.write(value2, "value2", 0);
+ capsule.write(value3, "value3", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readInt("value1", 0);
+ value2 = capsule.readInt("value2", 0);
+ value3 = capsule.readInt("value3", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java
new file mode 100644
index 0000000..52184bc
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableInt4.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableInt4 */
+public class ShaderVariableInt4 extends ShaderVariable {
+ public int value1;
+ public int value2;
+ public int value3;
+ public int value4;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(value1, "value1", 0);
+ capsule.write(value2, "value2", 0);
+ capsule.write(value3, "value3", 0);
+ capsule.write(value4, "value4", 0);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ value1 = capsule.readInt("value1", 0);
+ value2 = capsule.readInt("value2", 0);
+ value3 = capsule.readInt("value3", 0);
+ value4 = capsule.readInt("value4", 0);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java
new file mode 100644
index 0000000..ca3f21b
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableIntArray.java
@@ -0,0 +1 @@
+/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.util.shader.uniformtypes; import java.io.IOException; import java.nio.IntBuffer; import com.ardor3d.util.export.InputCapsule; import com.ardor3d.util.export.OutputCapsule; import com.ardor3d.util.shader.ShaderVariable; /** ShaderVariableIntArray */ public class ShaderVariableIntArray extends ShaderVariable { public IntBuffer value; /** * Specifies the number of values for each element of the array. Must be 1, 2, 3, or 4. */ public int size = 1; @Override public boolean hasData() { return value != null; } @Override public void write(final OutputCapsule capsule) throws IOException { super.write(capsule); capsule.write(value, "value", null); capsule.write(size, "size", 1); } @Override public void read(final InputCapsule capsule) throws IOException { super.read(capsule); value = capsule.readIntBuffer("value", null); size = capsule.readInt("size", 1); } } \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java
new file mode 100644
index 0000000..2727fad
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix2.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableMatrix2 */
+public class ShaderVariableMatrix2 extends ShaderVariable {
+ public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(4);
+ public boolean rowMajor;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(matrixBuffer, "matrixBuffer", null);
+ capsule.write(rowMajor, "rowMajor", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null);
+ rowMajor = capsule.readBoolean("rowMajor", false);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java
new file mode 100644
index 0000000..ab2320c
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix3.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableMatrix3 */
+public class ShaderVariableMatrix3 extends ShaderVariable {
+ public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(9);
+ public boolean rowMajor;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(matrixBuffer, "matrixBuffer", null);
+ capsule.write(rowMajor, "rowMajor", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null);
+ rowMajor = capsule.readBoolean("rowMajor", false);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java
new file mode 100644
index 0000000..eb2a4d0
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariableMatrix4 */
+public class ShaderVariableMatrix4 extends ShaderVariable {
+ public FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
+ public boolean rowMajor;
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(matrixBuffer, "matrixBuffer", null);
+ capsule.write(rowMajor, "rowMajor", false);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null);
+ rowMajor = capsule.readBoolean("rowMajor", false);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java
new file mode 100644
index 0000000..a638270
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariableMatrix4Array.java
@@ -0,0 +1 @@
+/** * Copyright (c) 2008-2012 Ardor Labs, Inc. * * This file is part of Ardor3D. * * Ardor3D is free software: you can redistribute it and/or modify it * under the terms of its license which may be found in the accompanying * LICENSE file or at <http://www.ardor3d.com/LICENSE>. */ package com.ardor3d.util.shader.uniformtypes; import java.io.IOException; import java.nio.FloatBuffer; import com.ardor3d.util.export.InputCapsule; import com.ardor3d.util.export.OutputCapsule; import com.ardor3d.util.shader.ShaderVariable; /** ShaderVariableMatrix4Array */ public class ShaderVariableMatrix4Array extends ShaderVariable { public FloatBuffer matrixBuffer; public boolean rowMajor; @Override public boolean hasData() { return matrixBuffer != null; } @Override public void write(final OutputCapsule capsule) throws IOException { super.write(capsule); capsule.write(matrixBuffer, "matrixBuffer", null); capsule.write(rowMajor, "rowMajor", false); } @Override public void read(final InputCapsule capsule) throws IOException { super.read(capsule); matrixBuffer = capsule.readFloatBuffer("matrixBuffer", null); rowMajor = capsule.readBoolean("rowMajor", false); } } \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java
new file mode 100644
index 0000000..4c8e7bf
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerByte.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import com.ardor3d.scenegraph.ByteBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariablePointerByte */
+public class ShaderVariablePointerByte extends ShaderVariable {
+ /**
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute
+ * values are understood to be tightly packed in the array.
+ */
+ public int stride;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** Specifies if the data is in signed or unsigned format */
+ public boolean unsigned;
+ /** The data for the attribute value */
+ public ByteBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(stride, "stride", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(unsigned, "unsigned", false);
+ capsule.write(data, "data", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ stride = capsule.readInt("stride", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ unsigned = capsule.readBoolean("unsigned", false);
+ data = (ByteBufferData) capsule.readSavable("data", null);
+ // XXX: transitional
+ if (data == null) {
+ final ByteBuffer buff = capsule.readByteBuffer("data", null);
+ if (buff != null) {
+ data = new ByteBufferData(buff);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java
new file mode 100644
index 0000000..6da1c17
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloat.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariablePointerFloat */
+public class ShaderVariablePointerFloat extends ShaderVariable {
+ /**
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute
+ * values are understood to be tightly packed in the array.
+ */
+ public int stride;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** The data for the attribute value */
+ public FloatBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(stride, "stride", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(data, "data", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ stride = capsule.readInt("stride", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ data = (FloatBufferData) capsule.readSavable("data", null);
+ // XXX: transitional
+ if (data == null) {
+ final FloatBuffer buff = capsule.readFloatBuffer("data", null);
+ if (buff != null) {
+ data = new FloatBufferData(buff, size);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java
new file mode 100644
index 0000000..7f74fef
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerFloatMatrix.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.scenegraph.FloatBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/**
+ * ShaderVariablePointerFloatMatrix - data is stored by row... all matrices row 0, then all matrices row 1, etc.
+ */
+public class ShaderVariablePointerFloatMatrix extends ShaderVariable {
+ /**
+ * Specifies the number of rows and cols in the matrix. Must be 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** The data for the attribute value */
+ public FloatBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public int getSize() {
+ return size;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(data, "bdata", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ data = (FloatBufferData) capsule.readSavable("bdata", null);
+ // XXX: transitional
+ if (data == null) {
+ final FloatBuffer buff = capsule.readFloatBuffer("data", null);
+ if (buff != null) {
+ data = new FloatBufferData(buff, size);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java
new file mode 100644
index 0000000..eaa90cb
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerInt.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.IntBuffer;
+
+import com.ardor3d.scenegraph.IntBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariablePointerInt */
+public class ShaderVariablePointerInt extends ShaderVariable {
+ /**
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute
+ * values are understood to be tightly packed in the array.
+ */
+ public int stride;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** Specifies if the data is in signed or unsigned format */
+ public boolean unsigned;
+ /** The data for the attribute value */
+ public IntBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(stride, "stride", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(unsigned, "unsigned", false);
+ capsule.write(data, "data", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ stride = capsule.readInt("stride", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ unsigned = capsule.readBoolean("unsigned", false);
+ data = (IntBufferData) capsule.readSavable("data", null);
+ // XXX: transitional
+ if (data == null) {
+ final IntBuffer buff = capsule.readIntBuffer("data", null);
+ if (buff != null) {
+ data = new IntBufferData(buff);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java
new file mode 100644
index 0000000..48a0d37
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/shader/uniformtypes/ShaderVariablePointerShort.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.shader.uniformtypes;
+
+import java.io.IOException;
+import java.nio.ShortBuffer;
+
+import com.ardor3d.scenegraph.ShortBufferData;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.shader.ShaderVariable;
+
+/** ShaderVariablePointerShort */
+public class ShaderVariablePointerShort extends ShaderVariable {
+ /**
+ * Specifies the number of values for each element of the generic vertex attribute array. Must be 1, 2, 3, or 4.
+ */
+ public int size;
+ /**
+ * Specifies the byte offset between consecutive attribute values. If stride is 0 (the initial value), the attribute
+ * values are understood to be tightly packed in the array.
+ */
+ public int stride;
+ /**
+ * Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values
+ * (false) when they are accessed.
+ */
+ public boolean normalized;
+ /** Specifies if the data is in signed or unsigned format */
+ public boolean unsigned;
+ /** The data for the attribute value */
+ public ShortBufferData data;
+
+ @Override
+ public boolean hasData() {
+ return data != null && data.getBuffer() != null;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(size, "size", 0);
+ capsule.write(stride, "stride", 0);
+ capsule.write(normalized, "normalized", false);
+ capsule.write(unsigned, "unsigned", false);
+ capsule.write(data, "data", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ size = capsule.readInt("size", 0);
+ stride = capsule.readInt("stride", 0);
+ normalized = capsule.readBoolean("normalized", false);
+ unsigned = capsule.readBoolean("unsigned", false);
+ data = (ShortBufferData) capsule.readSavable("data", null);
+ // XXX: transitional
+ if (data == null) {
+ final ShortBuffer buff = capsule.readShortBuffer("data", null);
+ if (buff != null) {
+ data = new ShortBufferData(buff);
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java
new file mode 100644
index 0000000..1674c86
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/MultiStatSample.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import com.google.common.collect.Maps;
+
+public class MultiStatSample {
+ private final HashMap<StatType, StatValue> _values = Maps.newHashMap();
+ private double _elapsedTime = 0.0;
+
+ public static MultiStatSample createNew(final HashMap<StatType, StatValue> current) {
+ final MultiStatSample rVal = new MultiStatSample();
+ for (final StatType type : current.keySet()) {
+ final StatValue entry = current.get(type);
+ // only count values we've seen at least 1 time from this sample set.
+ if (entry.getIterations() > 0) {
+ final StatValue store = new StatValue(entry);
+ rVal._values.put(type, store);
+ }
+ }
+ return rVal;
+ }
+
+ public void setTimeElapsed(final double time) {
+ _elapsedTime = time;
+ }
+
+ public boolean containsStat(final StatType type) {
+ return _values.containsKey(type);
+ }
+
+ public StatValue getStatValue(final StatType type) {
+ return _values.get(type);
+ }
+
+ public Set<StatType> getStatTypes() {
+ return _values.keySet();
+ }
+
+ public double getElapsedTime() {
+ return _elapsedTime;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java
new file mode 100644
index 0000000..51e3432
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatCollector.java
@@ -0,0 +1,356 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.Timer;
+import com.google.common.collect.Lists;
+
+/**
+ * This class acts as a centralized data store for statistics. As data is added to the collector, a sum total is kept as
+ * well as the total number of data samples given for the particular stat.
+ */
+public abstract class StatCollector {
+ private static final Logger logger = Logger.getLogger(StatCollector.class.getName());
+
+ /**
+ * How many distinct past aggregate samples are kept before the oldest one is dropped on add. You can multiply this
+ * by the time sample rate to determine the total history length in time.
+ */
+ protected static int maxSamples = 100;
+
+ /**
+ * Our map of current stat values. Current means values that have been collected within the current time sample. For
+ * example, if sampleRate = 1.0, then current will hold values collected since the last 1 second ping.
+ */
+ protected static HashMap<StatType, StatValue> current = new HashMap<StatType, StatValue>();
+
+ protected static List<MultiStatSample> historical = Collections.synchronizedList(new LinkedList<MultiStatSample>());
+
+ /**
+ * How long to gather stats as a single unit before pushing them onto the historical stack.
+ */
+ protected static double sampleRateMS = 1000;
+
+ protected static double lastSampleTime = 0;
+
+ protected static double lastTimeCheckMS = 0;
+
+ protected static List<StatListener> listeners = Lists.newArrayList();
+
+ protected static double startOffset = 0;
+
+ protected static boolean ignoreStats = false;
+
+ protected static Stack<StatType> timeStatStack = new Stack<StatType>();
+ protected static HashSet<StatType> timedStats = new HashSet<StatType>();
+
+ protected static Timer timer = new Timer();
+
+ protected static final double TO_MS = 1000.0 / timer.getResolution();
+
+ protected static long pausedTime;
+
+ protected static long pausedStartTime;
+
+ /**
+ * Construct a new StatCollector.
+ *
+ * @param sampleRateMS
+ * The amount of time between aggregated samples in milliseconds.
+ */
+ public static void init(final long sampleRateMS, final int maxHistorical) {
+ StatCollector.sampleRateMS = sampleRateMS;
+ StatCollector.maxSamples = maxHistorical;
+ }
+
+ public static void addStat(final StatType type, final double statValue) {
+ if (ignoreStats) {
+ return;
+ }
+
+ synchronized (current) {
+ StatValue val = current.get(type);
+ if (val == null) {
+ val = new StatValue();
+ current.put(type, val);
+ }
+ val.incrementValue(statValue);
+ val.incrementIterations();
+ }
+ }
+
+ public static void startStat(final StatType type) {
+ if (ignoreStats || !timedStats.contains(type)) {
+ return;
+ }
+
+ synchronized (current) {
+ final StatType top = !timeStatStack.isEmpty() ? timeStatStack.peek() : null;
+
+ final double timeMS = timer.getTime() * TO_MS;
+ if (top != null) {
+ // tally timer and include in stats.
+ final StatValue val = current.get(top);
+ val.incrementValue(timeMS - lastTimeCheckMS);
+ } else {
+ StatValue val = current.get(StatType.STAT_UNSPECIFIED_TIMER);
+ if (val == null) {
+ val = new StatValue();
+ val.setIterations(1);
+ current.put(StatType.STAT_UNSPECIFIED_TIMER, val);
+ }
+ val.incrementValue(timeMS - lastTimeCheckMS);
+ }
+
+ lastTimeCheckMS = timeMS;
+ timeStatStack.push(type);
+
+ if (type != null) {
+ StatValue val = current.get(type);
+ if (val == null) {
+ val = new StatValue();
+ current.put(type, val);
+ }
+ val.incrementIterations();
+ }
+ }
+ }
+
+ public static void endStat(final StatType type) {
+ if (ignoreStats || !timedStats.contains(type)) {
+ return;
+ }
+
+ synchronized (current) {
+ // This will throw error if called out of turn.
+ StatType top = timeStatStack.pop();
+
+ final double timeMS = timer.getTime() * TO_MS;
+
+ // tally timer and include in stats.
+ final StatValue val = current.get(top);
+ val.incrementValue(timeMS - lastTimeCheckMS);
+
+ lastTimeCheckMS = timeMS;
+
+ // Pop until we find our stat type
+ while (!top.equals(type)) {
+ logger.warning("Mismatched endStat, found " + top + ". Expected '" + type + "'");
+ top = timeStatStack.pop();
+ }
+ }
+ }
+
+ public static synchronized void update() {
+ final double timeMS = timer.getTime() * TO_MS;
+ final double elapsed = timeMS - lastSampleTime;
+
+ // Only continue if we've gone past our sample time threshold
+ if (elapsed < sampleRateMS) {
+ return;
+ }
+
+ synchronized (current) {
+ // Check if we have a timed stat currently tracking... if so, update it
+ if (!timeStatStack.isEmpty()) {
+ // tally timer and include in stats.
+ final StatValue val = current.get(timeStatStack.peek());
+ val.incrementValue(timeMS - lastTimeCheckMS - (pausedTime * TO_MS));
+ lastTimeCheckMS = timeMS;
+ // reset iterations of all stack to 0 to indicate we have not used it yet
+ for (int x = timeStatStack.size(); --x >= 0;) {
+ final StatValue val2 = current.get(timeStatStack.get(x));
+ if (val2 != null) {
+ val2.setIterations(0);
+ }
+ }
+
+ // set current iterations to 1 to indicate we've used this stat
+ val.setIterations(1);
+ } else {
+ final StatValue val = current.get(StatType.STAT_UNSPECIFIED_TIMER);
+ if (val != null) {
+ val.incrementValue(timeMS - lastTimeCheckMS - (pausedTime * TO_MS));
+ lastTimeCheckMS = timeMS;
+ val.setIterations(1);
+ }
+ }
+
+ // Add "current" hash into historical stat list
+ final MultiStatSample sample = MultiStatSample.createNew(current);
+ sample.setTimeElapsed(elapsed);
+ historical.add(sample); // adds onto tail
+
+ // reset the "current" hash... basically set things to 0 to decrease
+ // object recreation
+ for (final StatValue value : current.values()) {
+ value.reset();
+ }
+ }
+
+ // reset startOffset
+ startOffset = 0;
+ pausedTime = 0;
+
+ // stat list should drop old stats from list when greater than a certain
+ // threshold.
+ while (historical.size() > maxSamples) {
+ final MultiStatSample removed = historical.remove(0); // removes from head
+ if (removed != null) {
+ startOffset += removed.getElapsedTime();
+ }
+ }
+
+ lastSampleTime = timeMS;
+ fireActionEvent();
+ }
+
+ /**
+ * Add a listener to the pool of listeners that are notified when a new stats aggregate is created (at the end of
+ * each time sample).
+ *
+ * @param listener
+ * the listener to add
+ */
+ public static void addStatListener(final StatListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes a listener from the pool of listeners that are notified when a new stats aggregate is created (at the end
+ * of each time sample).
+ *
+ * @param listener
+ * the listener to remove
+ */
+ public static boolean removeStatListener(final StatListener listener) {
+ return listeners.remove(listener);
+ }
+
+ /**
+ * Cleans the listener pool of all listeners.
+ */
+ public static void removeAllListeners() {
+ listeners.clear();
+ }
+
+ /**
+ * Add a type to the set of stat types that are paid attention to when doing timed stat checking.
+ *
+ * @param type
+ * the listener to add
+ */
+ public static void addTimedStat(final StatType type) {
+ timedStats.add(type);
+ }
+
+ /**
+ * Removes a type from the set of stat types that are paid attention to when doing timed stat checking.
+ *
+ * @param type
+ * the listener to remove
+ */
+ public static boolean removeTimedStat(final StatType type) {
+ return timedStats.remove(type);
+ }
+
+ /**
+ * Cleans the set of stat types we paid attention to when doing timed stat checking.
+ */
+ public static void removeAllTimedStats() {
+ timedStats.clear();
+ }
+
+ /**
+ * Notifies all registered listeners that a new stats aggregate was created.
+ */
+ public static void fireActionEvent() {
+ for (final StatListener l : listeners) {
+ l.statsUpdated();
+ }
+ }
+
+ public static double getStartOffset() {
+ return startOffset;
+ }
+
+ public static double getSampleRate() {
+ return sampleRateMS;
+ }
+
+ public static void setSampleRate(final long sampleRateMS) {
+ StatCollector.sampleRateMS = sampleRateMS;
+ }
+
+ public static void setMaxSamples(final int samples) {
+ StatCollector.maxSamples = samples;
+ }
+
+ public static int getMaxSamples() {
+ return maxSamples;
+ }
+
+ public static List<MultiStatSample> getHistorical() {
+ return historical;
+ }
+
+ public static MultiStatSample lastStats() {
+ if (historical.size() == 0) {
+ return null;
+ }
+ return historical.get(historical.size() - 1);
+ }
+
+ public static boolean isIgnoreStats() {
+ return ignoreStats;
+ }
+
+ public static void setIgnoreStats(final boolean ignoreStats) {
+ StatCollector.ignoreStats = ignoreStats;
+ }
+
+ public static boolean hasHistoricalStat(final StatType type) {
+ for (final MultiStatSample mss : historical) {
+ if (mss.containsStat(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Call this if you've caught an error, etc and you need to reset timed stats collecting.
+ *
+ * NOTE: You must ensure you are not inside a START/END timed block, (or you recreate any necessary start calls)
+ * otherwise when endStat is called a stack exception will occur.
+ */
+ public static void resetTimedStack() {
+ timeStatStack.clear();
+ }
+
+ public static void pause() {
+ setIgnoreStats(true);
+ pausedStartTime = timer.getTime();
+ }
+
+ public static void resume() {
+ setIgnoreStats(false);
+ pausedTime += (timer.getTime() - pausedStartTime);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java
new file mode 100644
index 0000000..845b681
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatListener.java
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat;
+
+public interface StatListener {
+
+ public void statsUpdated();
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java
new file mode 100644
index 0000000..2d4436f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatType.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat;
+
+public class StatType implements Comparable<StatType> {
+
+ public static final StatType STAT_FRAMES = new StatType("_frames");
+
+ public static final StatType STAT_TRIANGLE_COUNT = new StatType("_triCount");
+ public static final StatType STAT_QUAD_COUNT = new StatType("_quadCount");
+ public static final StatType STAT_LINE_COUNT = new StatType("_lineCount");
+ public static final StatType STAT_POINT_COUNT = new StatType("_pointCount");
+ public static final StatType STAT_VERTEX_COUNT = new StatType("_vertCount");
+ public static final StatType STAT_MESH_COUNT = new StatType("_meshCount");
+ public static final StatType STAT_TEXTURE_BINDS = new StatType("_texBind");
+ public static final StatType STAT_SHADER_BINDS = new StatType("_shaderBind");
+
+ public static final StatType STAT_UNSPECIFIED_TIMER = new StatType("_timedOther");
+ public static final StatType STAT_RENDER_TIMER = new StatType("_timedRenderer");
+ public static final StatType STAT_STATES_TIMER = new StatType("_timedStates");
+ public static final StatType STAT_TEXTURE_STATE_TIMER = new StatType("_timedTextureState");
+ public static final StatType STAT_SHADER_STATE_TIMER = new StatType("_timedShaderState");
+ public static final StatType STAT_UPDATE_TIMER = new StatType("_timedUpdates");
+ public static final StatType STAT_DISPLAYSWAP_TIMER = new StatType("_timedSwap");
+
+ private String _statName = "-unknown-";
+
+ public StatType(final String name) {
+ _statName = name;
+ }
+
+ public String getStatName() {
+ return _statName;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (!(obj instanceof StatType)) {
+ return false;
+ }
+ final StatType other = (StatType) obj;
+ if (!_statName.equals(other._statName)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int hash = _statName.hashCode();
+ return hash;
+ }
+
+ public int compareTo(final StatType obj) {
+ final StatType other = obj;
+ return _statName.compareTo(other._statName);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java
new file mode 100644
index 0000000..6e748cf
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/StatValue.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat;
+
+public class StatValue {
+ private double _accumulatedValue = 0;
+ private double _averageValue = 0;
+ private long _iterations;
+ private boolean _averageDirty = true;
+
+ public StatValue() {}
+
+ public StatValue(final StatValue entry) {
+ _accumulatedValue = entry._accumulatedValue;
+ _averageValue = entry._averageValue;
+ _averageDirty = entry._averageDirty;
+ _iterations = entry._iterations;
+ }
+
+ public double getAccumulatedValue() {
+ return _accumulatedValue;
+ }
+
+ public long getIterations() {
+ return _iterations;
+ }
+
+ public double getAverageValue() {
+ if (_averageDirty) {
+ _averageValue = _iterations > 0 ? _accumulatedValue / _iterations : _accumulatedValue;
+ _averageDirty = false;
+ }
+ return _averageValue;
+ }
+
+ public void incrementValue(final double statValue) {
+ _accumulatedValue += statValue;
+ _averageDirty = true;
+ }
+
+ public void incrementIterations() {
+ _iterations++;
+ _averageDirty = true;
+ }
+
+ public void setIterations(final long iterations) {
+ _iterations = iterations;
+ _averageDirty = true;
+ }
+
+ public void reset() {
+ _accumulatedValue = 0;
+ _iterations = 0;
+ _averageValue = 0;
+ _averageDirty = false;
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java
new file mode 100644
index 0000000..10dc3b9
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/AbstractStatGrapher.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import java.util.HashMap;
+import java.util.TreeMap;
+
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRenderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.util.stat.StatListener;
+import com.ardor3d.util.stat.StatType;
+
+/**
+ * Base class for graphers.
+ */
+public abstract class AbstractStatGrapher implements StatListener {
+
+ protected TextureRenderer _textureRenderer;
+ protected Texture2D _texture;
+ protected int _gWidth, _gHeight;
+
+ protected TreeMap<StatType, HashMap<String, Object>> _config = new TreeMap<StatType, HashMap<String, Object>>();
+
+ protected boolean _enabled = true;
+
+ /**
+ * Must be constructed in the GL thread.
+ *
+ * @param factory
+ */
+ public AbstractStatGrapher(final int width, final int height, final Renderer renderer,
+ final ContextCapabilities caps) {
+ _gWidth = width;
+ _gHeight = height;
+ // prepare our TextureRenderer
+ _textureRenderer = TextureRendererFactory.INSTANCE.createTextureRenderer(width, height, renderer, caps);
+
+ if (_textureRenderer != null) {
+ _textureRenderer.setBackgroundColor(new ColorRGBA(ColorRGBA.BLACK));
+ }
+ }
+
+ // - set a texture for offscreen rendering
+ public void setTexture(final Texture2D tex) {
+ _textureRenderer.setupTexture(tex);
+ _texture = tex;
+ }
+
+ public TextureRenderer getTextureRenderer() {
+ return _textureRenderer;
+ }
+
+ public void clearConfig() {
+ _config.clear();
+ }
+
+ public void clearConfig(final StatType type) {
+ if (_config.get(type) != null) {
+ _config.get(type).clear();
+ }
+ }
+
+ public void clearConfig(final StatType type, final String key) {
+ if (_config.get(type) != null) {
+ _config.get(type).remove(key);
+ }
+ }
+
+ public void addConfig(final StatType type, final HashMap<String, Object> configs) {
+ _config.put(type, configs);
+ }
+
+ public void addConfig(final StatType type, final String key, final Object value) {
+ HashMap<String, Object> vals = _config.get(type);
+ if (vals == null) {
+ vals = new HashMap<String, Object>();
+ _config.put(type, vals);
+ }
+ vals.put(key, value);
+ }
+
+ protected ColorRGBA getColorConfig(final StatType type, final String configName, final ColorRGBA defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof ColorRGBA) {
+ return (ColorRGBA) val;
+ }
+ }
+ return defaultVal;
+ }
+
+ protected String getStringConfig(final StatType type, final String configName, final String defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof String) {
+ return (String) val;
+ }
+ }
+ return defaultVal;
+ }
+
+ protected short getShortConfig(final StatType type, final String configName, final short defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Number) {
+ return ((Number) val).shortValue();
+ }
+ }
+ return defaultVal;
+ }
+
+ protected int getIntConfig(final StatType type, final String configName, final int defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Number) {
+ return ((Number) val).intValue();
+ }
+ }
+ return defaultVal;
+ }
+
+ protected long getLongConfig(final StatType type, final String configName, final long defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Number) {
+ return ((Number) val).longValue();
+ }
+ }
+ return defaultVal;
+ }
+
+ protected float getFloatConfig(final StatType type, final String configName, final float defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Number) {
+ return ((Number) val).floatValue();
+ }
+ }
+ return defaultVal;
+ }
+
+ protected double getDoubleConfig(final StatType type, final String configName, final double defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Number) {
+ return ((Number) val).doubleValue();
+ }
+ }
+ return defaultVal;
+ }
+
+ protected boolean getBooleanConfig(final StatType type, final String configName, final boolean defaultVal) {
+ final HashMap<String, Object> vals = _config.get(type);
+ if (vals != null && vals.containsKey(configName)) {
+ final Object val = vals.get(configName);
+ if (val instanceof Boolean) {
+ return (Boolean) val;
+ }
+ }
+ return defaultVal;
+ }
+
+ public boolean hasConfig(final StatType type) {
+ return _config.containsKey(type) && !_config.get(type).isEmpty();
+ }
+
+ public boolean isEnabled() {
+ return _enabled;
+ }
+
+ public void setEnabled(final boolean enabled) {
+ _enabled = enabled;
+ }
+
+ /**
+ * Called when the graph needs to be reset back to the original display state. (iow, remove all points, lines, etc.)
+ */
+ public abstract void reset();
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java
new file mode 100644
index 0000000..c69b02f
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/DefColorFadeController.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Spatial;
+import com.ardor3d.scenegraph.controller.SpatialController;
+import com.ardor3d.scenegraph.hint.CullHint;
+
+/**
+ * <p>
+ * A controller that changes over time the alpha value of the default color of a given Geometry. When coupled with an
+ * appropriate BlendState, this can be used to fade in and out unlit objects.
+ * </p>
+ *
+ * <p>
+ * An example of an appropriate BlendState to use with this class:
+ * </p>
+ *
+ * <pre>
+ * BlendState blend = new BlendState();
+ * blend.setBlendEnabled(true);
+ * blend.setSourceFunction(SourceFunction.SourceAlpha);
+ * blend.setDestinationFunction(DestinationFunction.OneMinusSourceAlpha);
+ * </pre>
+ */
+public class DefColorFadeController implements SpatialController<Spatial> {
+
+ private Mesh _target;
+ private final float _targetAlpha;
+ private final double _rate;
+ private final boolean _dir;
+
+ /**
+ * Sets up a new instance of the controller. The
+ *
+ * @param target
+ * the object whose default color we want to change the alpha on.
+ * @param targetAlpha
+ * the alpha value we want to end up at.
+ * @param rate
+ * the amount, per second, to change the alpha. This value will be have its sign flipped if it is not the
+ * appropriate direction given the current default color's alpha.
+ */
+ public DefColorFadeController(final Mesh target, final float targetAlpha, double rate) {
+ _target = target;
+ _targetAlpha = targetAlpha;
+ _dir = target.getDefaultColor().getAlpha() > targetAlpha;
+ if ((_dir && rate > 0) || (!_dir && rate < 0)) {
+ rate *= -1;
+ }
+ _rate = rate;
+ }
+
+ public void update(final double time, final Spatial caller) {
+ if (_target == null) {
+ return;
+ }
+ final ColorRGBA color = ColorRGBA.fetchTempInstance().set(_target.getDefaultColor());
+ float alpha = color.getAlpha();
+
+ alpha += _rate * time;
+ if (_dir && alpha <= _targetAlpha) {
+ alpha = _targetAlpha;
+ } else if (!_dir && alpha >= _targetAlpha) {
+ alpha = _targetAlpha;
+ }
+
+ if (alpha != 0) {
+ _target.getSceneHints().setCullHint(CullHint.Inherit);
+ } else {
+ _target.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ color.setAlpha(alpha);
+ _target.setDefaultColor(color);
+ ColorRGBA.releaseTempInstance(color);
+
+ if (alpha == _targetAlpha) {
+ _target.removeController(this);
+
+ // enable gc
+ _target = null;
+ }
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java
new file mode 100644
index 0000000..a93c02e
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/GraphFactory.java
@@ -0,0 +1,164 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.Texture.MagnificationFilter;
+import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.BlendState.DestinationFunction;
+import com.ardor3d.renderer.state.BlendState.SourceFunction;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.stat.StatCollector;
+
+/**
+ * Factory class useful for setting up various types of graphs.
+ */
+public abstract class GraphFactory {
+
+ /**
+ * Makes a new line grapher and sets up a quad to display it.
+ *
+ * @param width
+ * the width in pixels of the graph
+ * @param height
+ * the height in pixels of the graph
+ * @param quad
+ * the quad on whose surface we'll display our graph.
+ * @return the new LineGrapher
+ */
+ public static LineGrapher makeLineGraph(final int width, final int height, final Quad quad,
+ final Renderer renderer, final ContextCapabilities caps) {
+ final LineGrapher grapher = new LineGrapher(width, height, renderer, caps);
+ grapher.setThreshold(1);
+ StatCollector.addStatListener(grapher);
+ final Texture2D graphTex = setupGraphTexture(grapher);
+
+ final float dW = (float) width / grapher._textureRenderer.getWidth();
+ final float dH = (float) height / grapher._textureRenderer.getHeight();
+
+ setupGraphQuad(quad, graphTex, dW, dH);
+
+ return grapher;
+ }
+
+ /**
+ * Makes a new area grapher and sets up a quad to display it.
+ *
+ * @param width
+ * the width in pixels of the graph
+ * @param height
+ * the height in pixels of the graph
+ * @param quad
+ * the quad on whose surface we'll display our graph.
+ * @return the new TimedAreaGrapher
+ */
+ public static TimedAreaGrapher makeTimedGraph(final int width, final int height, final Quad quad,
+ final Renderer renderer, final ContextCapabilities caps) {
+ final TimedAreaGrapher grapher = new TimedAreaGrapher(width, height, renderer, caps);
+ grapher.setThreshold(1);
+ StatCollector.addStatListener(grapher);
+ final Texture2D graphTex = setupGraphTexture(grapher);
+ final float dW = (float) width / grapher._textureRenderer.getWidth();
+ final float dH = (float) height / grapher._textureRenderer.getHeight();
+
+ setupGraphQuad(quad, graphTex, dW, dH);
+
+ return grapher;
+ }
+
+ /**
+ * Makes a new label grapher and sets up a quad to display it.
+ *
+ * @param width
+ * the width in pixels of the graph
+ * @param height
+ * the height in pixels of the graph
+ * @param quad
+ * the quad on whose surface we'll display our graph.
+ * @return the new TabledLabelGrapher
+ */
+ public static TabledLabelGrapher makeTabledLabelGraph(final int width, final int height, final Quad quad,
+ final Renderer renderer, final ContextCapabilities caps) {
+ final TabledLabelGrapher grapher = new TabledLabelGrapher(width, height, renderer, caps);
+ grapher.setThreshold(1);
+ StatCollector.addStatListener(grapher);
+ final Texture2D graphTex = setupGraphTexture(grapher);
+ final float dW = (float) width / grapher._textureRenderer.getWidth();
+ final float dH = (float) height / grapher._textureRenderer.getHeight();
+
+ setupGraphQuad(quad, graphTex, dW, dH);
+
+ return grapher;
+ }
+
+ /**
+ * Creates and sets up a texture to be used as the texture for a given grapher. Also applies appropriate texture
+ * filter modes. (NearestNeighborNoMipMaps and Bilinear)
+ *
+ * @param grapher
+ * the grapher to associate the texture with
+ * @return the texture
+ */
+ private static Texture2D setupGraphTexture(final AbstractStatGrapher grapher) {
+ final Texture2D graphTex = new Texture2D();
+ graphTex.setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps);
+ graphTex.setMagnificationFilter(MagnificationFilter.Bilinear);
+ grapher.setTexture(graphTex);
+ return graphTex;
+ }
+
+ /**
+ * Sets up a Quad to be used as the display surface for a grapher. Puts it in the ortho mode, sets up UVs, and sets
+ * up a TextureState and an alpha transparency BlendState.
+ *
+ * @param quad
+ * the Quad to use
+ * @param graphTexture
+ * the texture to use
+ * @param maxU
+ * the maximum value along the U axis to use in the texture for UVs
+ * @param maxV
+ * the maximum value along the V axis to use in the texture for UVs
+ */
+ private static void setupGraphQuad(final Quad quad, final Texture2D graphTexture, final float maxU, final float maxV) {
+ quad.getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+ quad.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ quad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ quad.getSceneHints().setOrthoOrder(-1);
+
+ final FloatBuffer tbuf = quad.getMeshData().getTextureCoords(0).getBuffer();
+ tbuf.clear();
+ tbuf.put(0).put(0);
+ tbuf.put(0).put(maxV);
+ tbuf.put(maxU).put(maxV);
+ tbuf.put(maxU).put(0);
+ tbuf.rewind();
+
+ final TextureState texState = new TextureState();
+ texState.setTexture(graphTexture);
+ quad.setRenderState(texState);
+
+ final BlendState blend = new BlendState();
+ blend.setBlendEnabled(true);
+ blend.setSourceFunction(SourceFunction.SourceAlpha);
+ blend.setDestinationFunction(DestinationFunction.OneMinusSourceAlpha);
+ quad.setRenderState(blend);
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java
new file mode 100644
index 0000000..544b530
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/LineGrapher.java
@@ -0,0 +1,338 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.Point;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.stat.MultiStatSample;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardor3d.util.stat.StatType;
+
+public class LineGrapher extends AbstractStatGrapher implements TableLinkable {
+
+ public static final StatType Vertical = new StatType("_lineGrapher_vert");
+ public static final StatType Horizontal = new StatType("_lineGrapher_horiz");
+
+ public enum ConfigKeys {
+ ShowPoints, PointSize, PointColor, Antialias, ShowLines, Width, Stipple, Color, FrameAverage,
+ }
+
+ protected Node _graphRoot = new Node("root");
+ protected Line _horizontals, _verticals;
+ protected int _eventCount = 0;
+ protected int _threshold = 1;
+ protected float _startMarker = 0;
+ private float _off;
+ private float _vSpan;
+ private static final int majorHBar = 20;
+ private static final int majorVBar = 10;
+
+ private final HashMap<StatType, LineEntry> _entries = new HashMap<StatType, LineEntry>();
+
+ private BlendState _defBlendState = null;
+
+ public LineGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) {
+ super(width, height, renderer, caps);
+
+ // Setup our static horizontal graph lines
+ createHLines();
+
+ _defBlendState = new BlendState();
+ _defBlendState.setEnabled(true);
+ _defBlendState.setBlendEnabled(true);
+ _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ _graphRoot.setRenderState(_defBlendState);
+ _graphRoot.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ public void statsUpdated() {
+ if (!isEnabled() || !Constants.updateGraphs) {
+ return;
+ }
+
+ // Turn off stat collection while we draw this graph.
+ StatCollector.pause();
+
+ // some basic stats:
+ final int texWidth = _gWidth;
+ final int texHeight = _gHeight;
+
+ // On stat event:
+ // - check if enough events have been triggered to cause an update.
+ _eventCount++;
+ _off += StatCollector.getStartOffset();
+ if (_eventCount < _threshold) {
+ return;
+ } else {
+ _eventCount = 0;
+ }
+
+ // - (Re)attach horizontal bars.
+ if (!_graphRoot.equals(_horizontals.getParent())) {
+ _graphRoot.attachChild(_horizontals);
+ }
+
+ // - Check if we have valid vertical bars:
+ final float newVSpan = calcVSpan();
+ if (_verticals == null || newVSpan != _vSpan) {
+ _vSpan = newVSpan;
+ createVLines();
+ }
+ _off %= (StatCollector.getSampleRate() * majorVBar);
+
+ // - (Re)attach vertical bars.
+ if (!_graphRoot.equals(_verticals.getParent())) {
+ _graphRoot.attachChild(_verticals);
+ }
+
+ // - shift verticals based on current time
+ shiftVerticals();
+
+ for (final StatType type : _entries.keySet()) {
+ _entries.get(type).visited = false;
+ _entries.get(type).verts.clear();
+ }
+
+ // - For each sample, add points and extend the lines of the
+ // corresponding Line objects.
+ synchronized (StatCollector.getHistorical()) {
+ for (int i = 0; i < StatCollector.getHistorical().size(); i++) {
+ final MultiStatSample sample = StatCollector.getHistorical().get(i);
+ for (final StatType type : _config.keySet()) {
+ if (sample.containsStat(type)) {
+ LineEntry entry = _entries.get(type);
+ // Prepare our entry object as needed.
+ if (entry == null || entry.maxSamples != StatCollector.getMaxSamples()) {
+ entry = new LineEntry(StatCollector.getMaxSamples(), type);
+ _entries.put(type, entry);
+ }
+
+ final double value = getBooleanConfig(type, ConfigKeys.FrameAverage.name(), false) ? sample
+ .getStatValue(type).getAverageValue() : sample.getStatValue(type).getAccumulatedValue();
+
+ final Vector3 point = new Vector3(i, value, 0);
+ // Now, add
+ entry.verts.add(point);
+
+ // Update min/max
+ if (entry.max < value) {
+ entry.max = value;
+ }
+
+ entry.visited = true;
+ } else {
+ final LineEntry entry = _entries.get(type);
+ if (entry != null) {
+ entry.verts.add(new Vector3(i, 0, 0));
+ }
+ }
+ }
+ }
+ }
+
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final LineEntry entry = _entries.get(i.next());
+ // - Go through the entries list and remove any that were not visited.
+ if (!entry.visited) {
+ entry.line.removeFromParent();
+ entry.point.removeFromParent();
+ i.remove();
+ continue;
+ }
+
+ // - Update the Point and Line params with the verts and count.
+ final FloatBuffer fb = BufferUtils.createFloatBuffer(entry.verts.toArray(new Vector3[entry.verts.size()]));
+ entry.point.getMeshData().setVertexBuffer(fb);
+ final double scaleWidth = texWidth / (StatCollector.getMaxSamples() - 1.0);
+ final double scaleHeight = texHeight / (entry.max * 1.02);
+ entry.point.setScale(new Vector3(scaleWidth, scaleHeight, 1));
+ entry.line.getMeshData().setVertexBuffer(fb);
+ entry.line.setScale(new Vector3(scaleWidth, scaleHeight, 1));
+ fb.rewind();
+
+ // - attach point/line to root as needed
+ if (!_graphRoot.equals(entry.line.getParent())) {
+ _graphRoot.attachChild(entry.line);
+ }
+ if (!_graphRoot.equals(entry.point.getParent())) {
+ _graphRoot.attachChild(entry.point);
+ }
+ }
+
+ // - Now, draw to texture via a TextureRenderer
+ _graphRoot.updateGeometricState(0, true);
+ _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ // Turn stat collection back on.
+ StatCollector.resume();
+ }
+
+ private float calcVSpan() {
+ return _textureRenderer.getWidth() * majorVBar / StatCollector.getMaxSamples();
+ }
+
+ private void shiftVerticals() {
+ final int texWidth = _textureRenderer.getWidth();
+ final double xOffset = -(_off * texWidth) / (StatCollector.getMaxSamples() * StatCollector.getSampleRate());
+ final ReadOnlyVector3 trans = _verticals.getTranslation();
+ _verticals.setTranslation(xOffset, trans.getY(), trans.getZ());
+ }
+
+ public int getThreshold() {
+ return _threshold;
+ }
+
+ public void setThreshold(final int threshold) {
+ _threshold = threshold;
+ }
+
+ // - Setup horizontal bars
+ private void createHLines() {
+ // some basic stats:
+ final int texWidth = _textureRenderer.getWidth();
+ final int texHeight = _textureRenderer.getHeight();
+
+ final FloatBuffer verts = BufferUtils.createVector3Buffer((100 / majorHBar) * 2);
+
+ final float div = texHeight * majorHBar / 100f;
+
+ for (int y = 0, i = 0; i < verts.capacity(); i += 6, y += div) {
+ verts.put(0).put(y).put(0);
+ verts.put(texWidth).put(y).put(0);
+ }
+
+ _horizontals = new Line("horiz", verts, null, null, null);
+ _horizontals.getMeshData().setIndexMode(IndexMode.Lines);
+ _horizontals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ _horizontals.setDefaultColor(getColorConfig(LineGrapher.Horizontal, ConfigKeys.Color.name(), new ColorRGBA(
+ ColorRGBA.BLUE)));
+ _horizontals.setLineWidth(getIntConfig(LineGrapher.Horizontal, ConfigKeys.Width.name(), 1));
+ _horizontals
+ .setStipplePattern(getShortConfig(LineGrapher.Horizontal, ConfigKeys.Stipple.name(), (short) 0xFF00));
+ _horizontals.setAntialiased(getBooleanConfig(LineGrapher.Horizontal, ConfigKeys.Antialias.name(), true));
+ }
+
+ // - Setup enough vertical bars to have one at every (10 X samplerate)
+ // secs... we'll need +1 bar.
+ private void createVLines() {
+ // some basic stats:
+ final int texWidth = _textureRenderer.getWidth();
+ final int texHeight = _textureRenderer.getHeight();
+
+ final FloatBuffer verts = BufferUtils.createVector3Buffer(((int) (texWidth / _vSpan) + 1) * 2);
+
+ for (float x = _vSpan; x <= texWidth + _vSpan; x += _vSpan) {
+ verts.put(x).put(0).put(0);
+ verts.put(x).put(texHeight).put(0);
+ }
+
+ _verticals = new Line("vert", verts, null, null, null);
+ _verticals.getMeshData().setIndexMode(IndexMode.Lines);
+ _verticals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ _verticals.setDefaultColor(getColorConfig(LineGrapher.Vertical, ConfigKeys.Color.name(), new ColorRGBA(
+ ColorRGBA.RED)));
+ _verticals.setLineWidth(getIntConfig(LineGrapher.Vertical, ConfigKeys.Width.name(), 1));
+ _verticals.setStipplePattern(getShortConfig(LineGrapher.Vertical, ConfigKeys.Stipple.name(), (short) 0xFF00));
+ _verticals.setAntialiased(getBooleanConfig(LineGrapher.Vertical, ConfigKeys.Antialias.name(), true));
+ }
+
+ class LineEntry {
+ public List<Vector3> verts = new ArrayList<Vector3>();
+ public int maxSamples;
+ public double min = 0;
+ public double max = 10;
+ public boolean visited;
+ public Point point;
+ public Line line;
+
+ public LineEntry(final int maxSamples, final StatType type) {
+ this.maxSamples = maxSamples;
+
+ point = new Point("p", BufferUtils.createVector3Buffer(maxSamples), null, null, null);
+ point.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ point.setDefaultColor(getColorConfig(type, ConfigKeys.PointColor.name(), new ColorRGBA(ColorRGBA.WHITE)));
+ point.setPointSize(getIntConfig(type, ConfigKeys.PointSize.name(), 5));
+ point.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true));
+ if (!getBooleanConfig(type, ConfigKeys.ShowPoints.name(), false)) {
+ point.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ line = new Line("l", BufferUtils.createVector3Buffer(maxSamples), null, null, null);
+ line.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ line.getMeshData().setIndexMode(IndexMode.LineStrip);
+
+ line.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY)));
+ line.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3));
+ line.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF));
+ line.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true));
+ if (!getBooleanConfig(type, ConfigKeys.ShowLines.name(), true)) {
+ line.getSceneHints().setCullHint(CullHint.Always);
+ }
+ }
+ }
+
+ public Line updateLineKey(final StatType type, Line lineKey) {
+ if (lineKey == null) {
+ lineKey = new Line("lk", BufferUtils.createVector3Buffer(2), null, null, null);
+ final FloatBuffer fb = BufferUtils.createFloatBuffer(new Vector3[] { new Vector3(0, 0, 0),
+ new Vector3(30, 0, 0) });
+ fb.rewind();
+ lineKey.getMeshData().setVertexBuffer(fb);
+ }
+
+ lineKey.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ lineKey.getMeshData().setIndexMode(IndexMode.LineStrip);
+
+ lineKey.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY)));
+ lineKey.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3));
+ lineKey.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF));
+ lineKey.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true));
+ if (!getBooleanConfig(type, ConfigKeys.ShowLines.name(), true)) {
+ lineKey.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ return lineKey;
+ }
+
+ @Override
+ public void reset() {
+ synchronized (StatCollector.getHistorical()) {
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final LineEntry entry = _entries.get(i.next());
+ entry.line.removeFromParent();
+ entry.point.removeFromParent();
+ i.remove();
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java
new file mode 100644
index 0000000..2cdd290
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TableLinkable.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.util.stat.StatType;
+
+/**
+ * Interface describing the ability for a class to create or update a line with values, usually to match those in
+ * another graph.
+ */
+public interface TableLinkable {
+
+ /**
+ * Update/Create a line to reflect the color, stipple, antialias and width used in the other graph.
+ *
+ * @param type
+ * the StatType the Line is associated with.
+ * @param lineKey
+ * the Line we want to update values on (if null, a new Line should be created.)
+ * @return the updated (or created) Line
+ */
+ public Line updateLineKey(StatType type, Line lineKey);
+
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java
new file mode 100644
index 0000000..359c158
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TabledLabelGrapher.java
@@ -0,0 +1,303 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.ui.text.BasicText;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.stat.MultiStatSample;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardor3d.util.stat.StatType;
+import com.ardor3d.util.stat.StatValue;
+
+public class TabledLabelGrapher extends AbstractStatGrapher {
+
+ public enum ConfigKeys {
+ TextColor, Name, FrameAverage, Decimals, FontScale, ValueScale, Abbreviate,
+ }
+
+ public static final int DEFAULT_DECIMALS = 2;
+
+ protected Node _graphRoot = new Node("root");
+ protected int _eventCount = 0;
+ protected int _threshold = 1;
+ protected int _columns = 1;
+
+ protected Quad _bgQuad = new Quad("bgQuad", 1, 1);
+
+ protected BlendState _defBlendState = null;
+
+ private final HashMap<StatType, LabelEntry> _entries = new HashMap<StatType, LabelEntry>();
+
+ private boolean _minimalBackground;
+
+ private AbstractStatGrapher _linkedGraph;
+
+ public TabledLabelGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) {
+ super(width, height, renderer, caps);
+
+ _defBlendState = new BlendState();
+ _defBlendState.setEnabled(true);
+ _defBlendState.setBlendEnabled(true);
+ _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ _graphRoot.setRenderState(_defBlendState);
+
+ _bgQuad.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ _bgQuad.setDefaultColor(new ColorRGBA(ColorRGBA.BLACK));
+ _graphRoot.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ public void statsUpdated() {
+ if (!isEnabled() || !Constants.updateGraphs) {
+ return;
+ }
+
+ // Turn off stat collection while we draw this graph.
+ StatCollector.pause();
+
+ // some basic stats:
+ final int texWidth = _gWidth;
+ final int texHeight = _gHeight;
+
+ // On stat event:
+ // - check if enough events have been triggered to cause an update.
+ _eventCount++;
+ if (_eventCount < _threshold) {
+ return;
+ } else {
+ _eventCount = 0;
+ }
+
+ int col = 0;
+ double lastY = texHeight - 3, maxY = 0;
+ final float colSize = texWidth / (float) getColumns();
+
+ // clear visitations
+ for (final StatType type : _entries.keySet()) {
+ _entries.get(type).visited = false;
+ }
+
+ // - We only care about the most recent stats
+ synchronized (StatCollector.getHistorical()) {
+ final MultiStatSample sample = StatCollector.getHistorical().get(StatCollector.getHistorical().size() - 1);
+ // - go through things we are configured for
+ for (final StatType type : _config.keySet()) {
+ StatValue val = sample.getStatValue(type);
+ if (val == null) {
+ if (!StatCollector.hasHistoricalStat(type)) {
+ continue;
+ } else {
+ val = new StatValue();
+ val.incrementIterations();
+ }
+ }
+
+ LabelEntry entry = _entries.get(type);
+ // Prepare our entry object as needed.
+ if (entry == null) {
+ entry = new LabelEntry(type);
+ _entries.put(type, entry);
+ _graphRoot.attachChild(entry.text);
+ }
+ entry.visited = true;
+
+ // Update text value
+ final double value = getBooleanConfig(type, ConfigKeys.FrameAverage.name(), false) ? val
+ .getAverageValue() : val.getAccumulatedValue();
+ entry.text.setText(getStringConfig(type, ConfigKeys.Name.name(), type.getStatName()) + " "
+ + stripVal(value, type));
+
+ // Set font scale
+ final float scale = getFloatConfig(type, ConfigKeys.FontScale.name(), .80f);
+ entry.text.setScale(scale);
+
+ // See if we have a defained color for this type, otherwise use
+ // the corresponding color from a linked line grapher, or if
+ // none, use white.
+ entry.text.setTextColor(getColorConfig(type, ConfigKeys.TextColor.name(),
+ _linkedGraph != null ? _linkedGraph.getColorConfig(type, LineGrapher.ConfigKeys.Color.name(),
+ new ColorRGBA(ColorRGBA.WHITE)) : new ColorRGBA(ColorRGBA.WHITE)));
+
+ // Update text placement.
+ final double labelHeight = entry.text.getHeight();
+ if (maxY < labelHeight) {
+ maxY = labelHeight;
+ }
+ entry.text.setTranslation(colSize * col, lastY, 0);
+
+ // Update line key as needed
+ if (_linkedGraph != null && _linkedGraph.hasConfig(type) && _linkedGraph instanceof TableLinkable) {
+ // add line keys
+ entry.lineKey = ((TableLinkable) _linkedGraph).updateLineKey(type, entry.lineKey);
+ if (entry.lineKey.getParent() != _graphRoot) {
+ _graphRoot.attachChild(entry.lineKey);
+ }
+ final ReadOnlyVector3 tLoc = entry.text.getTranslation();
+ entry.lineKey.setTranslation((float) (tLoc.getX() + entry.text.getWidth() + 15), (float) (tLoc
+ .getY() + (.5 * entry.text.getHeight())), 0);
+ }
+
+ // update column / row variables
+ col++;
+ col %= getColumns();
+ if (col == 0) {
+ lastY -= maxY;
+ maxY = 0;
+ }
+ }
+
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final LabelEntry entry = _entries.get(i.next());
+ // - Go through the entries list and remove any that were not
+ // visited.
+ if (!entry.visited) {
+ entry.text.removeFromParent();
+ if (entry.lineKey != null) {
+ entry.lineKey.removeFromParent();
+ }
+ i.remove();
+ }
+ }
+ }
+
+ _graphRoot.updateGeometricState(0, true);
+
+ final ColorRGBA bgColor = ColorRGBA.fetchTempInstance().set(_textureRenderer.getBackgroundColor());
+ if (_minimalBackground) {
+ bgColor.setAlpha(0);
+ _textureRenderer.setBackgroundColor(bgColor);
+
+ lastY -= 3;
+ if (col != 0) {
+ lastY -= maxY;
+ }
+ _bgQuad.resize(texWidth, texHeight - lastY);
+ _bgQuad.setRenderState(_defBlendState);
+ _bgQuad.setTranslation(texWidth / 2f, texHeight - (texHeight - lastY) / 2f, 0);
+ _bgQuad.updateGeometricState(0, true);
+
+ // - Draw our bg quad
+ _textureRenderer.render(_bgQuad, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ // - Now, draw to texture via a TextureRenderer
+ _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_NONE);
+ } else {
+ bgColor.setAlpha(1);
+ _textureRenderer.setBackgroundColor(bgColor);
+
+ // - Now, draw to texture via a TextureRenderer
+ _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+ }
+ ColorRGBA.releaseTempInstance(bgColor);
+
+ // Turn stat collection back on.
+ StatCollector.resume();
+ }
+
+ private String stripVal(double val, final StatType type) {
+ // scale as needed
+ val = val * getDoubleConfig(type, ConfigKeys.ValueScale.name(), 1.0);
+
+ String post = "";
+ // Break it down if needed.
+ if (getBooleanConfig(type, ConfigKeys.Abbreviate.name(), true)) {
+ if (val >= 1000000) {
+ val /= 1000000;
+ post = "m";
+ } else if (val >= 1000) {
+ val /= 1000;
+ post = "k";
+ }
+ }
+
+ int decimals = getIntConfig(type, ConfigKeys.Decimals.name(), DEFAULT_DECIMALS);
+ if (!"".equals(post) && decimals == 0) {
+ decimals = 1; // use 1 spot anyway.
+ }
+
+ final StringBuilder format = new StringBuilder(decimals > 0 ? "0.0" : "0");
+ for (int x = 1; x < decimals; x++) {
+ format.append("0");
+ }
+
+ return new DecimalFormat(format.toString()).format(val) + post;
+ }
+
+ public int getThreshold() {
+ return _threshold;
+ }
+
+ public void setThreshold(final int threshold) {
+ _threshold = threshold;
+ }
+
+ public int getColumns() {
+ return _columns;
+ }
+
+ public void setColumns(final int columns) {
+ if (columns < 1) {
+ throw new IllegalArgumentException("columns must be >= 1 (" + columns + ")");
+ }
+ _columns = columns;
+ }
+
+ public boolean isMinimalBackground() {
+ return _minimalBackground;
+ }
+
+ public void setMinimalBackground(final boolean minimalBackground) {
+ _minimalBackground = minimalBackground;
+ }
+
+ public void linkTo(final AbstractStatGrapher grapher) {
+ _linkedGraph = grapher;
+ }
+
+ class LabelEntry {
+ BasicText text;
+ Line lineKey;
+ boolean visited;
+ StatType _type;
+
+ public LabelEntry(final StatType type) {
+ _type = type;
+ text = BasicText.createDefaultTextLabel("label", getStringConfig(type, ConfigKeys.Name.name(), type
+ .getStatName()));
+ }
+ }
+
+ @Override
+ public void reset() {
+ synchronized (StatCollector.getHistorical()) {
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final LabelEntry entry = _entries.get(i.next());
+ entry.text.removeFromParent();
+ entry.lineKey.removeFromParent();
+ i.remove();
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java
new file mode 100644
index 0000000..4b0e5d2
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/java/com/ardor3d/util/stat/graph/TimedAreaGrapher.java
@@ -0,0 +1,323 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util.stat.graph;
+
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.scenegraph.Line;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.geom.BufferUtils;
+import com.ardor3d.util.stat.MultiStatSample;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardor3d.util.stat.StatType;
+
+public class TimedAreaGrapher extends AbstractStatGrapher implements TableLinkable {
+
+ public static final StatType Vertical = new StatType("_timedGrapher_vert");
+ public static final StatType Horizontal = new StatType("_timedGrapher_horiz");
+
+ public enum ConfigKeys {
+ Antialias, ShowAreas, Width, Stipple, Color,
+ }
+
+ protected Node _graphRoot = new Node("root");
+ protected Line _horizontals, _verticals;
+ protected int _eventCount = 0;
+ protected int _threshold = 1;
+ protected float _startMarker = 0;
+ private float _off;
+ private float _vSpan;
+ private static final int majorHBar = 20;
+ private static final int majorVBar = 10;
+
+ private final HashMap<StatType, AreaEntry> _entries = new HashMap<StatType, AreaEntry>();
+
+ private BlendState _defBlendState = null;
+
+ public TimedAreaGrapher(final int width, final int height, final Renderer renderer, final ContextCapabilities caps) {
+ super(width, height, renderer, caps);
+
+ // Setup our static horizontal graph lines
+ createHLines();
+
+ _defBlendState = new BlendState();
+ _defBlendState.setEnabled(true);
+ _defBlendState.setBlendEnabled(true);
+ _defBlendState.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
+ _defBlendState.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
+ _graphRoot.setRenderState(_defBlendState);
+ _graphRoot.getSceneHints().setCullHint(CullHint.Never);
+ }
+
+ public void statsUpdated() {
+ if (!isEnabled() || !Constants.updateGraphs) {
+ return;
+ }
+
+ // Turn off stat collection while we draw this graph.
+ StatCollector.pause();
+
+ // some basic stats:
+ final int texWidth = _gWidth;
+ final int texHeight = _gHeight;
+
+ // On stat event:
+ // - check if enough events have been triggered to cause an update.
+ _eventCount++;
+ _off += StatCollector.getStartOffset();
+ if (_eventCount < _threshold) {
+ return;
+ } else {
+ _eventCount = 0;
+ }
+
+ // - (Re)attach horizontal bars.
+ if (!_graphRoot.equals(_horizontals.getParent())) {
+ _graphRoot.attachChild(_horizontals);
+ }
+
+ // - Check if we have valid vertical bars:
+ final float newVSpan = calcVSpan();
+ if (_verticals == null || newVSpan != _vSpan) {
+ _vSpan = newVSpan;
+ createVLines();
+ }
+ _off %= (StatCollector.getSampleRate() * majorVBar);
+
+ // - (Re)attach vertical bars.
+ if (!_graphRoot.equals(_verticals.getParent())) {
+ _graphRoot.attachChild(_verticals);
+ }
+
+ // - shift verticals based on current time
+ shiftVerticals();
+
+ for (final StatType type : _entries.keySet()) {
+ _entries.get(type).visited = false;
+ _entries.get(type).verts.clear();
+ }
+
+ // - For each sample, add points and extend the lines of the
+ // corresponding Line objects.
+ synchronized (StatCollector.getHistorical()) {
+ for (int i = 0; i < StatCollector.getHistorical().size(); i++) {
+ final MultiStatSample sample = StatCollector.getHistorical().get(i);
+ // First figure out the max value.
+ double max = 0;
+ for (final StatType type : sample.getStatTypes()) {
+ if (_config.containsKey(type)) {
+ max = Math.max(sample.getStatValue(type).getAccumulatedValue(), max);
+ }
+ }
+ double accum = 0;
+ for (final StatType type : sample.getStatTypes()) {
+ if (_config.containsKey(type)) {
+ AreaEntry entry = _entries.get(type);
+ // Prepare our entry object as needed.
+ if (entry == null || entry.maxSamples != StatCollector.getMaxSamples()) {
+ entry = new AreaEntry(StatCollector.getMaxSamples(), type);
+ _entries.put(type, entry);
+ }
+
+ // average by max and bump by accumulated total.
+ final double value = sample.getStatValue(type).getAccumulatedValue() / max;
+ final Vector3 point1 = new Vector3(i, (float) (value + accum), 0);
+ entry.verts.add(point1);
+ final Vector3 point2 = new Vector3(i, (float) (accum), 0);
+ entry.verts.add(point2);
+ entry.visited = true;
+ accum += value;
+ }
+ }
+ }
+ }
+
+ final float scaleWidth = texWidth / (float) (StatCollector.getMaxSamples() - 1);
+ final float scaleHeight = texHeight / 1.02f;
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final AreaEntry entry = _entries.get(i.next());
+ // - Go through the entries list and remove any that were not
+ // visited.
+ if (!entry.visited) {
+ entry.area.removeFromParent();
+ i.remove();
+ continue;
+ }
+
+ // - Update the params with the verts and count.
+ final FloatBuffer fb = BufferUtils.createFloatBuffer(entry.verts.toArray(new Vector3[entry.verts.size()]));
+ fb.rewind();
+ entry.area.getMeshData().setVertexBuffer(fb);
+ entry.area.setScale(new Vector3(scaleWidth, scaleHeight, 1));
+ entry.area.getMeshData().getIndexBuffer().limit(entry.verts.size());
+
+ // - attach to root as needed
+ if (!_graphRoot.equals(entry.area.getParent())) {
+ _graphRoot.attachChild(entry.area);
+ }
+ }
+
+ _graphRoot.updateGeometricState(0, true);
+
+ // - Now, draw to texture via a TextureRenderer
+ _textureRenderer.render(_graphRoot, _texture, Renderer.BUFFER_COLOR_AND_DEPTH);
+
+ // Turn stat collection back on.
+ StatCollector.resume();
+ }
+
+ private float calcVSpan() {
+ return _textureRenderer.getWidth() * majorVBar / StatCollector.getMaxSamples();
+ }
+
+ private void shiftVerticals() {
+ final int texWidth = _textureRenderer.getWidth();
+ final double xOffset = -(_off * texWidth) / (StatCollector.getMaxSamples() * StatCollector.getSampleRate());
+ final ReadOnlyVector3 trans = _verticals.getTranslation();
+ _verticals.setTranslation(xOffset, trans.getY(), trans.getZ());
+ }
+
+ public int getThreshold() {
+ return _threshold;
+ }
+
+ public void setThreshold(final int threshold) {
+ _threshold = threshold;
+ }
+
+ // - Setup horizontal bars
+ private void createHLines() {
+ // some basic stats:
+ final int texWidth = _textureRenderer.getWidth();
+ final int texHeight = _textureRenderer.getHeight();
+
+ final FloatBuffer verts = BufferUtils.createVector3Buffer((100 / majorHBar) * 2);
+
+ final float div = texHeight * majorHBar / 100f;
+
+ for (int y = 0, i = 0; i < verts.capacity(); i += 6, y += div) {
+ verts.put(0).put(y).put(0);
+ verts.put(texWidth).put(y).put(0);
+ }
+
+ _horizontals = new Line("horiz", verts, null, null, null);
+ _horizontals.getMeshData().setIndexMode(IndexMode.Lines);
+ _horizontals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ _horizontals.setDefaultColor(getColorConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Color.name(),
+ new ColorRGBA(ColorRGBA.BLUE)));
+ _horizontals.setLineWidth(getIntConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Width.name(), 1));
+ _horizontals.setStipplePattern(getShortConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Stipple.name(),
+ (short) 0xFF00));
+ _horizontals.setAntialiased(getBooleanConfig(TimedAreaGrapher.Horizontal, ConfigKeys.Antialias.name(), true));
+ }
+
+ // - Setup enough vertical bars to have one at every (10 X samplerate)
+ // secs... we'll need +1 bar.
+ private void createVLines() {
+ // some basic stats:
+ final int texWidth = _textureRenderer.getWidth();
+ final int texHeight = _textureRenderer.getHeight();
+
+ final FloatBuffer verts = BufferUtils.createVector3Buffer(((int) (texWidth / _vSpan) + 1) * 2);
+
+ for (float x = _vSpan; x <= texWidth + _vSpan; x += _vSpan) {
+ verts.put(x).put(0).put(0);
+ verts.put(x).put(texHeight).put(0);
+ }
+
+ _verticals = new Line("vert", verts, null, null, null);
+ _verticals.getMeshData().setIndexMode(IndexMode.Lines);
+ _verticals.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+
+ _verticals.setDefaultColor(getColorConfig(TimedAreaGrapher.Vertical, ConfigKeys.Color.name(), new ColorRGBA(
+ ColorRGBA.RED)));
+ _verticals.setLineWidth(getIntConfig(TimedAreaGrapher.Vertical, ConfigKeys.Width.name(), 1));
+ _verticals.setStipplePattern(getShortConfig(TimedAreaGrapher.Vertical, ConfigKeys.Stipple.name(),
+ (short) 0xFF00));
+ _verticals.setAntialiased(getBooleanConfig(TimedAreaGrapher.Vertical, ConfigKeys.Antialias.name(), true));
+ }
+
+ class AreaEntry {
+ public List<Vector3> verts = new ArrayList<Vector3>();
+ public int maxSamples;
+ public boolean visited;
+ public Mesh area;
+
+ public AreaEntry(final int maxSamples, final StatType type) {
+ this.maxSamples = maxSamples;
+
+ area = new Mesh("a");
+ area.getMeshData().setVertexBuffer(BufferUtils.createVector3Buffer(maxSamples * 2));
+ area.getMeshData().setIndexBuffer(BufferUtils.createIntBuffer(maxSamples * 2));
+ for (int i = 0; i < maxSamples * 2; i++) {
+ area.getMeshData().getIndices().put(i);
+ }
+ area.getMeshData().getIndexBuffer().rewind();
+ area.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ area.getMeshData().setIndexMode(IndexMode.LineStrip);
+
+ area.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY)));
+ if (!getBooleanConfig(type, ConfigKeys.ShowAreas.name(), true)) {
+ area.getSceneHints().setCullHint(CullHint.Always);
+ }
+ }
+ }
+
+ public Line updateLineKey(final StatType type, Line lineKey) {
+ if (lineKey == null) {
+ lineKey = new Line("lk", BufferUtils.createVector3Buffer(2), null, null, null);
+ final FloatBuffer fb = BufferUtils.createFloatBuffer(new Vector3[] { new Vector3(0, 0, 0),
+ new Vector3(30, 0, 0) });
+ fb.rewind();
+ lineKey.getMeshData().setVertexBuffer(fb);
+ }
+
+ lineKey.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ lineKey.getMeshData().setIndexMode(IndexMode.LineLoop);
+
+ lineKey.setDefaultColor(getColorConfig(type, ConfigKeys.Color.name(), new ColorRGBA(ColorRGBA.LIGHT_GRAY)));
+ lineKey.setLineWidth(getIntConfig(type, ConfigKeys.Width.name(), 3));
+ lineKey.setStipplePattern(getShortConfig(type, ConfigKeys.Stipple.name(), (short) 0xFFFF));
+ lineKey.setAntialiased(getBooleanConfig(type, ConfigKeys.Antialias.name(), true));
+ if (!getBooleanConfig(type, ConfigKeys.ShowAreas.name(), true)) {
+ lineKey.getSceneHints().setCullHint(CullHint.Always);
+ }
+
+ return lineKey;
+ }
+
+ @Override
+ public void reset() {
+ synchronized (StatCollector.getHistorical()) {
+ for (final Iterator<StatType> i = _entries.keySet().iterator(); i.hasNext();) {
+ final AreaEntry entry = _entries.get(i.next());
+ entry.area.removeFromParent();
+ i.remove();
+ }
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF b/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..160cd1d
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,46 @@
+Bundle-Name: Ardor3D Core
+Bundle-SymbolicName: com.ardor3d.core
+Bundle-ManifestVersion: 2
+Bundle-Version: 0.7
+Export-Package: com.ardor3d.annotation,
+ com.ardor3d.bounding,
+ com.ardor3d.framework,
+ com.ardor3d.image,
+ com.ardor3d.image.util,
+ com.ardor3d.image.util.dds,
+ com.ardor3d.input,
+ com.ardor3d.input.control,
+ com.ardor3d.input.logical,
+ com.ardor3d.intersection,
+ com.ardor3d.light,
+ com.ardor3d.math,
+ com.ardor3d.math.functions,
+ com.ardor3d.math.type,
+ com.ardor3d.nativeloader,
+ com.ardor3d.renderer,
+ com.ardor3d.renderer.pass,
+ com.ardor3d.renderer.queue,
+ com.ardor3d.renderer.state,
+ com.ardor3d.renderer.state.record,
+ com.ardor3d.scenegraph,
+ com.ardor3d.scenegraph.controller,
+ com.ardor3d.scenegraph.controller.interpolation,
+ com.ardor3d.scenegraph.event,
+ com.ardor3d.scenegraph.extension,
+ com.ardor3d.scenegraph.hint,
+ com.ardor3d.scenegraph.shape,
+ com.ardor3d.scenegraph.visitor,
+ com.ardor3d.spline,
+ com.ardor3d.ui.text,
+ com.ardor3d.util,
+ com.ardor3d.util.export,
+ com.ardor3d.util.export.binary,
+ com.ardor3d.util.export.xml,
+ com.ardor3d.util.geom,
+ com.ardor3d.util.resource,
+ com.ardor3d.util.scenegraph,
+ com.ardor3d.util.screen,
+ com.ardor3d.util.shader,
+ com.ardor3d.util.shader.uniformtypes,
+ com.ardor3d.util.stat,
+ com.ardor3d.util.stat.graph
diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga b/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga
new file mode 100644
index 0000000..d4affa1
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/renderer/state/notloaded.tga
Binary files differ
diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d
new file mode 100644
index 0000000..ee55979
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.a3d
Binary files differ
diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt
new file mode 100644
index 0000000..b8f7d49
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular.fnt
@@ -0,0 +1,1403 @@
+<?xml version="1.0"?>
+<font>
+ <info face="Arial" size="-24" bold="1" italic="0" charset="" unicode="1" stretchH="100" smooth="1" aa="2" padding="0,0,0,0" spacing="1,1" outline="0"/>
+ <common lineHeight="28" base="23" scaleW="512" scaleH="512" pages="1" packed="0" alphaChnl="0" redChnl="0" greenChnl="0" blueChnl="0"/>
+ <pages>
+ <page id="0" file="arial-24-bold-regular_00.png" />
+ </pages>
+ <chars count="994">
+ <char id="32" x="510" y="44" width="1" height="1" xoffset="0" yoffset="22" xadvance="6" page="0" chnl="15" />
+ <char id="33" x="116" y="389" width="4" height="18" xoffset="2" yoffset="5" xadvance="7" page="0" chnl="15" />
+ <char id="34" x="392" y="475" width="9" height="6" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="35" x="210" y="311" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="36" x="92" y="171" width="12" height="22" xoffset="1" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="37" x="340" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="38" x="442" y="267" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="39" x="430" y="474" width="4" height="6" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="40" x="109" y="147" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="41" x="102" y="147" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="42" x="223" y="481" width="8" height="8" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="43" x="352" y="463" width="12" height="12" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="44" x="507" y="460" width="4" height="8" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="45" x="374" y="482" width="7" height="3" xoffset="1" yoffset="15" xadvance="8" page="0" chnl="15" />
+ <char id="46" x="246" y="490" width="4" height="4" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="47" x="504" y="305" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="48" x="347" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="49" x="430" y="362" width="8" height="18" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="50" x="373" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="51" x="386" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="52" x="322" y="307" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="53" x="399" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="54" x="412" y="325" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="55" x="425" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="56" x="438" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="57" x="451" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="58" x="99" y="471" width="4" height="14" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="59" x="121" y="389" width="4" height="18" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="60" x="169" y="452" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="61" x="68" y="486" width="12" height="9" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="62" x="143" y="455" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="63" x="462" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="64" x="0" y="28" width="23" height="23" xoffset="1" yoffset="5" xadvance="23" page="0" chnl="15" />
+ <char id="65" x="318" y="269" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="66" x="301" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="67" x="17" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="68" x="317" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="69" x="476" y="305" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="70" x="13" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="71" x="493" y="188" width="16" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="72" x="135" y="311" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="73" x="141" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="74" x="500" y="247" width="11" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="75" x="119" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="76" x="52" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="77" x="192" y="273" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="78" x="381" y="287" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="79" x="334" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="80" x="56" y="332" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="81" x="250" y="169" width="17" height="21" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="82" x="187" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="83" x="228" y="212" width="14" height="19" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="84" x="180" y="311" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="85" x="195" y="311" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="86" x="459" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="87" x="469" y="229" width="23" height="18" xoffset="0" yoffset="5" xadvance="22" page="0" chnl="15" />
+ <char id="88" x="493" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="89" x="246" y="271" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="90" x="396" y="287" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="91" x="74" y="148" width="6" height="23" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="92" x="59" y="389" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="93" x="123" y="147" width="6" height="23" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="94" x="495" y="460" width="11" height="10" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="95" x="272" y="490" width="14" height="3" xoffset="0" yoffset="24" xadvance="13" page="0" chnl="15" />
+ <char id="96" x="203" y="490" width="5" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="97" x="104" y="455" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="98" x="104" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="99" x="91" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="100" x="130" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="101" x="78" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="102" x="400" y="363" width="9" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="103" x="208" y="232" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="104" x="156" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="105" x="111" y="389" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="106" x="116" y="147" width="6" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="107" x="182" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="108" x="106" y="389" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="109" x="244" y="422" width="19" height="14" xoffset="1" yoffset="9" xadvance="21" page="0" chnl="15" />
+ <char id="110" x="65" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="111" x="294" y="435" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="112" x="52" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="113" x="65" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="114" x="10" y="472" width="9" height="14" xoffset="1" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="115" x="52" y="456" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="116" x="9" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="117" x="39" y="456" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="118" x="124" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="119" x="181" y="422" width="20" height="14" xoffset="0" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="120" x="139" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="121" x="348" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="122" x="269" y="452" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="123" x="0" y="148" width="9" height="23" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="124" x="169" y="147" width="3" height="23" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="125" x="10" y="148" width="9" height="23" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="126" x="439" y="473" width="13" height="5" xoffset="1" yoffset="12" xadvance="14" page="0" chnl="15" />
+ <char id="160" x="510" y="46" width="1" height="1" xoffset="0" yoffset="22" xadvance="6" page="0" chnl="15" />
+ <char id="161" x="281" y="231" width="4" height="19" xoffset="2" yoffset="9" xadvance="7" page="0" chnl="15" />
+ <char id="162" x="202" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="163" x="238" y="309" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="164" x="193" y="467" width="13" height="13" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="165" x="224" y="311" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="166" x="165" y="147" width="3" height="23" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="167" x="215" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="168" x="339" y="484" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="169" x="40" y="275" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="170" x="104" y="485" width="8" height="9" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="171" x="378" y="462" width="12" height="12" xoffset="1" yoffset="10" xadvance="13" page="0" chnl="15" />
+ <char id="172" x="55" y="486" width="12" height="9" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="173" x="366" y="483" width="7" height="3" xoffset="1" yoffset="15" xadvance="8" page="0" chnl="15" />
+ <char id="174" x="59" y="275" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="175" x="287" y="490" width="14" height="3" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="176" x="232" y="481" width="8" height="8" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="177" x="452" y="381" width="12" height="17" xoffset="0" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="178" x="140" y="484" width="7" height="9" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="179" x="148" y="484" width="7" height="9" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="180" x="185" y="490" width="5" height="4" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="181" x="462" y="209" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="182" x="495" y="144" width="13" height="22" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="183" x="241" y="490" width="4" height="4" xoffset="1" yoffset="12" xadvance="6" page="0" chnl="15" />
+ <char id="184" x="471" y="472" width="7" height="5" xoffset="0" yoffset="22" xadvance="8" page="0" chnl="15" />
+ <char id="185" x="163" y="484" width="5" height="9" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="186" x="113" y="485" width="8" height="9" xoffset="0" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="187" x="391" y="462" width="12" height="12" xoffset="1" yoffset="10" xadvance="13" page="0" chnl="15" />
+ <char id="188" x="420" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="189" x="480" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="190" x="153" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="191" x="91" y="235" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="192" x="252" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="193" x="270" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="194" x="288" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="195" x="306" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="196" x="468" y="48" width="17" height="23" xoffset="0" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="197" x="292" y="145" width="17" height="22" xoffset="0" yoffset="1" xadvance="17" page="0" chnl="15" />
+ <char id="198" x="396" y="229" width="24" height="18" xoffset="-1" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="199" x="17" y="76" width="16" height="23" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="200" x="283" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="201" x="297" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="202" x="311" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="203" x="84" y="123" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="204" x="149" y="147" width="5" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="205" x="143" y="147" width="5" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="206" x="38" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="207" x="503" y="48" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="208" x="336" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="209" x="75" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="210" x="342" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="211" x="360" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="212" x="378" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="213" x="396" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="214" x="414" y="49" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="215" x="365" y="462" width="12" height="12" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="216" x="231" y="170" width="18" height="21" xoffset="0" yoffset="3" xadvance="18" page="0" chnl="15" />
+ <char id="217" x="45" y="100" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="218" x="210" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="219" x="233" y="74" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="220" x="248" y="74" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="221" x="54" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="222" x="0" y="332" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="223" x="416" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="224" x="429" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="225" x="442" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="226" x="455" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="227" x="468" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="228" x="481" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="229" x="473" y="167" width="12" height="20" xoffset="1" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="230" x="223" y="422" width="20" height="14" xoffset="1" yoffset="9" xadvance="21" page="0" chnl="15" />
+ <char id="231" x="0" y="370" width="12" height="18" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="232" x="13" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="233" x="143" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="234" x="169" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="235" x="221" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="236" x="88" y="389" width="5" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="237" x="100" y="389" width="5" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="238" x="448" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="239" x="502" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="240" x="168" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="241" x="299" y="346" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="242" x="154" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="243" x="140" y="330" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="244" x="112" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="245" x="84" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="246" x="14" y="332" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="247" x="207" y="467" width="12" height="13" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="248" x="478" y="399" width="13" height="15" xoffset="1" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="249" x="65" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="250" x="78" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="251" x="321" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="252" x="334" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="253" x="428" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="254" x="254" y="122" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="255" x="413" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="256" x="256" y="146" width="17" height="22" xoffset="0" yoffset="1" xadvance="16" page="0" chnl="15" />
+ <char id="257" x="426" y="382" width="12" height="17" xoffset="1" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="258" x="294" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="259" x="117" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="260" x="179" y="27" width="19" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="261" x="288" y="211" width="14" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="262" x="51" y="76" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="263" x="26" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="264" x="170" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="265" x="208" y="349" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="266" x="486" y="48" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="267" x="477" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="268" x="34" y="76" width="16" height="23" xoffset="1" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="269" x="308" y="326" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="270" x="187" y="75" width="15" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="271" x="17" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="272" x="264" y="271" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="273" x="486" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="274" x="467" y="144" width="13" height="22" xoffset="2" yoffset="1" xadvance="16" page="0" chnl="15" />
+ <char id="275" x="465" y="381" width="12" height="17" xoffset="1" yoffset="6" xadvance="13" page="0" chnl="15" />
+ <char id="276" x="367" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="277" x="403" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="278" x="353" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="279" x="494" y="343" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="280" x="70" y="124" width="13" height="23" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="281" x="104" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="282" x="479" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="283" x="52" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="284" x="102" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="285" x="267" y="122" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="286" x="85" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="287" x="280" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="288" x="68" y="75" width="16" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="289" x="293" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="290" x="323" y="0" width="16" height="24" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="291" x="207" y="0" width="12" height="25" xoffset="1" yoffset="3" xadvance="14" page="0" chnl="15" />
+ <char id="292" x="353" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="293" x="306" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="294" x="390" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="295" x="336" y="307" width="13" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="296" x="20" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="297" x="475" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="298" x="166" y="171" width="8" height="22" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
+ <char id="299" x="0" y="408" width="8" height="17" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
+ <char id="300" x="503" y="72" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="301" x="439" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="302" x="81" y="148" width="6" height="23" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="303" x="88" y="147" width="6" height="23" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="304" x="160" y="147" width="4" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="305" x="119" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="306" x="408" y="267" width="16" height="18" xoffset="1" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="307" x="493" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="308" x="0" y="124" width="13" height="23" xoffset="0" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="309" x="56" y="148" width="8" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="310" x="327" y="145" width="16" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="311" x="118" y="171" width="12" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="312" x="117" y="455" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="313" x="319" y="121" width="12" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="314" x="137" y="147" width="5" height="23" xoffset="1" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="315" x="27" y="172" width="12" height="22" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="316" x="175" y="170" width="7" height="22" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="317" x="91" y="351" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="318" x="503" y="324" width="8" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="319" x="234" y="349" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="320" x="410" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="321" x="165" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="322" x="35" y="389" width="7" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="323" x="165" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="324" x="312" y="345" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="325" x="438" y="145" width="14" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="326" x="325" y="345" width="12" height="18" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="327" x="105" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="328" x="338" y="345" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="329" x="354" y="268" width="17" height="18" xoffset="-1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="330" x="119" y="214" width="15" height="19" xoffset="2" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="331" x="169" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="332" x="274" y="146" width="17" height="22" xoffset="1" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="333" x="358" y="383" width="13" height="17" xoffset="1" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="334" x="0" y="52" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="335" x="420" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="336" x="492" y="24" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="337" x="434" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="338" x="121" y="194" width="23" height="19" xoffset="1" yoffset="4" xadvance="24" page="0" chnl="15" />
+ <char id="339" x="159" y="422" width="21" height="14" xoffset="1" yoffset="9" xadvance="22" page="0" chnl="15" />
+ <char id="340" x="0" y="76" width="16" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="341" x="380" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="342" x="310" y="145" width="16" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="343" x="369" y="363" width="10" height="18" xoffset="0" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="344" x="136" y="75" width="16" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="345" x="420" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="9" page="0" chnl="15" />
+ <char id="346" x="15" y="100" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="347" x="360" y="325" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="348" x="0" y="100" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="349" x="286" y="347" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="350" x="473" y="72" width="14" height="23" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="351" x="260" y="347" width="12" height="18" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="352" x="458" y="72" width="14" height="23" xoffset="1" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="353" x="26" y="351" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="354" x="192" y="0" width="14" height="25" xoffset="1" yoffset="3" xadvance="15" page="0" chnl="15" />
+ <char id="355" x="410" y="0" width="8" height="24" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
+ <char id="356" x="398" y="73" width="14" height="23" xoffset="1" yoffset="0" xadvance="15" page="0" chnl="15" />
+ <char id="357" x="351" y="345" width="12" height="18" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="358" x="60" y="313" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="359" x="18" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="360" x="383" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="361" x="117" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="362" x="393" y="145" width="14" height="22" xoffset="2" yoffset="1" xadvance="17" page="0" chnl="15" />
+ <char id="363" x="439" y="381" width="12" height="17" xoffset="1" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="364" x="368" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="365" x="247" y="347" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="366" x="338" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="367" x="486" y="167" width="12" height="20" xoffset="1" yoffset="3" xadvance="14" page="0" chnl="15" />
+ <char id="368" x="308" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="369" x="39" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="370" x="293" y="73" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="371" x="213" y="212" width="14" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="372" x="48" y="27" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="373" x="237" y="252" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="374" x="312" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="375" x="263" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="376" x="324" y="49" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="377" x="218" y="74" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="378" x="238" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="379" x="150" y="99" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="380" x="178" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="381" x="135" y="99" width="14" height="23" xoffset="0" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="382" x="142" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="383" x="51" y="389" width="7" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="399" x="459" y="189" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="402" x="120" y="99" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="416" x="256" y="191" width="20" height="19" xoffset="1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="417" x="468" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="431" x="97" y="275" width="18" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="432" x="485" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="461" x="474" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="462" x="65" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="463" x="29" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="464" x="0" y="389" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="465" x="450" y="48" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="466" x="280" y="309" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="467" x="195" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="468" x="273" y="347" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="469" x="225" y="98" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="470" x="384" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="471" x="240" y="98" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="472" x="131" y="171" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="473" x="443" y="72" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="474" x="79" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="475" x="30" y="100" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="476" x="105" y="171" width="12" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="506" x="287" y="0" width="17" height="24" xoffset="0" yoffset="-1" xadvance="16" page="0" chnl="15" />
+ <char id="507" x="332" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="508" x="419" y="0" width="24" height="23" xoffset="-1" yoffset="0" xadvance="24" page="0" chnl="15" />
+ <char id="509" x="258" y="252" width="20" height="18" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="510" x="249" y="0" width="18" height="24" xoffset="0" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="511" x="308" y="307" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="601" x="182" y="452" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="710" x="149" y="494" width="8" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="711" x="140" y="494" width="8" height="4" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="713" x="348" y="484" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="728" x="312" y="487" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="729" x="399" y="482" width="4" height="3" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="730" x="0" y="498" width="5" height="5" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="731" x="412" y="475" width="6" height="6" xoffset="1" yoffset="22" xadvance="8" page="0" chnl="15" />
+ <char id="732" x="321" y="487" width="8" height="3" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="733" x="130" y="495" width="9" height="4" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="768" x="179" y="493" width="5" height="4" xoffset="-6" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="769" x="173" y="493" width="5" height="4" xoffset="-5" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="771" x="357" y="483" width="8" height="3" xoffset="-10" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="777" x="166" y="494" width="6" height="4" xoffset="-6" yoffset="0" xadvance="0" page="0" chnl="15" />
+ <char id="803" x="221" y="490" width="4" height="4" xoffset="-10" yoffset="24" xadvance="0" page="0" chnl="15" />
+ <char id="894" x="126" y="389" width="4" height="18" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="900" x="197" y="490" width="5" height="4" xoffset="2" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="901" x="104" y="495" width="12" height="4" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="902" x="154" y="273" width="18" height="18" xoffset="-1" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="903" x="226" y="490" width="4" height="4" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="904" x="45" y="256" width="21" height="18" xoffset="-1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="905" x="67" y="256" width="21" height="18" xoffset="-1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="906" x="334" y="364" width="11" height="18" xoffset="-1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="908" x="168" y="194" width="21" height="19" xoffset="-1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="910" x="445" y="229" width="23" height="18" xoffset="0" yoffset="5" xadvance="22" page="0" chnl="15" />
+ <char id="911" x="190" y="192" width="21" height="19" xoffset="-1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="912" x="390" y="344" width="12" height="18" xoffset="-2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="913" x="372" y="268" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="914" x="269" y="290" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="915" x="364" y="344" width="12" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="916" x="116" y="273" width="18" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="917" x="350" y="306" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="918" x="150" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="919" x="0" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="920" x="424" y="189" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="921" x="507" y="343" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="922" x="153" y="292" width="16" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="923" x="170" y="292" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="924" x="210" y="273" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="925" x="471" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="926" x="456" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="927" x="388" y="189" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="928" x="441" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="929" x="364" y="306" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="931" x="392" y="306" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="932" x="426" y="286" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="933" x="300" y="269" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="934" x="296" y="190" width="18" height="19" xoffset="1" yoffset="4" xadvance="19" page="0" chnl="15" />
+ <char id="935" x="204" y="292" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="936" x="173" y="273" width="18" height="18" xoffset="1" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="937" x="277" y="191" width="18" height="19" xoffset="0" yoffset="4" xadvance="19" page="0" chnl="15" />
+ <char id="938" x="65" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="939" x="402" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="940" x="294" y="308" width="13" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="941" x="262" y="366" width="11" height="18" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="942" x="163" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="943" x="94" y="389" width="5" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="944" x="464" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="945" x="182" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="946" x="189" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="947" x="167" y="214" width="15" height="19" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="948" x="406" y="306" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="949" x="329" y="448" width="11" height="14" xoffset="1" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="950" x="482" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="951" x="182" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="952" x="274" y="366" width="11" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="953" x="104" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="954" x="375" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="955" x="221" y="292" width="15" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="956" x="195" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="957" x="79" y="441" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="958" x="471" y="120" width="10" height="23" xoffset="1" yoffset="5" xadvance="10" page="0" chnl="15" />
+ <char id="959" x="168" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="960" x="284" y="420" width="18" height="14" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="961" x="392" y="209" width="13" height="19" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="962" x="257" y="232" width="11" height="19" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="963" x="48" y="441" width="15" height="14" xoffset="1" yoffset="9" xadvance="16" page="0" chnl="15" />
+ <char id="964" x="418" y="446" width="10" height="14" xoffset="0" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="965" x="257" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="966" x="51" y="216" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="967" x="273" y="211" width="14" height="19" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="968" x="85" y="215" width="16" height="19" xoffset="1" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="969" x="264" y="422" width="19" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="970" x="466" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="971" x="310" y="365" width="11" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="972" x="490" y="305" width="13" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="973" x="322" y="364" width="11" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="974" x="440" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1025" x="56" y="124" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="1026" x="132" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1027" x="228" y="122" width="12" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="1028" x="151" y="214" width="15" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1029" x="258" y="211" width="14" height="19" xoffset="1" yoffset="4" xadvance="16" page="0" chnl="15" />
+ <char id="1030" x="131" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1031" x="47" y="148" width="8" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="1032" x="346" y="364" width="11" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1033" x="319" y="230" width="25" height="18" xoffset="0" yoffset="5" xadvance="26" page="0" chnl="15" />
+ <char id="1034" x="421" y="229" width="23" height="18" xoffset="2" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1035" x="460" y="248" width="19" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1036" x="493" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="1038" x="119" y="75" width="16" height="23" xoffset="0" yoffset="0" xadvance="15" page="0" chnl="15" />
+ <char id="1039" x="423" y="145" width="14" height="22" xoffset="2" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1040" x="282" y="270" width="17" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1041" x="349" y="287" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1042" x="333" y="288" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1043" x="195" y="349" width="12" height="18" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1044" x="238" y="146" width="17" height="22" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1045" x="196" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1046" x="0" y="256" width="22" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1047" x="303" y="210" width="14" height="19" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1048" x="75" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1049" x="278" y="73" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="1050" x="210" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1051" x="285" y="289" width="15" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1052" x="228" y="271" width="17" height="18" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1053" x="45" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1054" x="370" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1055" x="30" y="313" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1056" x="252" y="328" width="13" height="18" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1057" x="0" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1058" x="15" y="313" width="14" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1059" x="34" y="294" width="16" height="18" xoffset="0" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1060" x="380" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1061" x="476" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1062" x="361" y="145" width="15" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1063" x="182" y="330" width="13" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1064" x="89" y="256" width="21" height="18" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="1065" x="173" y="147" width="22" height="22" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="1066" x="279" y="251" width="20" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1067" x="111" y="254" width="20" height="18" xoffset="2" yoffset="5" xadvance="23" page="0" chnl="15" />
+ <char id="1068" x="365" y="287" width="15" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1069" x="135" y="214" width="15" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1070" x="145" y="194" width="22" height="19" xoffset="2" yoffset="4" xadvance="25" page="0" chnl="15" />
+ <char id="1071" x="425" y="267" width="16" height="18" xoffset="0" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1072" x="349" y="433" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1073" x="434" y="209" width="13" height="19" xoffset="1" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1074" x="308" y="435" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1075" x="451" y="446" width="9" height="14" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1076" x="299" y="385" width="15" height="17" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1077" x="453" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1078" x="379" y="417" width="18" height="14" xoffset="0" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1079" x="245" y="452" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1080" x="479" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1081" x="91" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1082" x="281" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1083" x="94" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1084" x="16" y="442" width="15" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1085" x="13" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1086" x="238" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1087" x="388" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1088" x="475" y="209" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1089" x="466" y="430" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1090" x="414" y="431" width="12" height="14" xoffset="0" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="1091" x="183" y="214" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1092" x="492" y="0" width="19" height="23" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="1093" x="109" y="440" width="14" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1094" x="372" y="382" width="13" height="17" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1095" x="233" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1096" x="398" y="416" width="17" height="14" xoffset="1" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="1097" x="229" y="387" width="18" height="17" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1098" x="451" y="415" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1099" x="360" y="417" width="18" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1100" x="196" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1101" x="26" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1102" x="322" y="418" width="18" height="14" xoffset="1" yoffset="9" xadvance="20" page="0" chnl="15" />
+ <char id="1103" x="252" y="437" width="13" height="14" xoffset="0" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1105" x="104" y="370" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1106" x="14" y="124" width="13" height="23" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1107" x="390" y="363" width="9" height="18" xoffset="1" yoffset="5" xadvance="10" page="0" chnl="15" />
+ <char id="1108" x="0" y="457" width="12" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1109" x="492" y="430" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1110" x="136" y="387" width="4" height="18" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1111" x="457" y="362" width="8" height="18" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1112" x="130" y="147" width="6" height="23" xoffset="-1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="1113" x="135" y="425" width="23" height="14" xoffset="0" yoffset="9" xadvance="23" page="0" chnl="15" />
+ <char id="1114" x="202" y="422" width="20" height="14" xoffset="1" yoffset="9" xadvance="22" page="0" chnl="15" />
+ <char id="1115" x="126" y="330" width="13" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1116" x="226" y="368" width="11" height="18" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="1118" x="323" y="73" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1119" x="413" y="382" width="12" height="17" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1168" x="156" y="171" width="9" height="22" xoffset="2" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1169" x="490" y="381" width="9" height="17" xoffset="1" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1170" x="198" y="212" width="14" height="19" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1171" x="352" y="448" width="10" height="14" xoffset="0" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1174" x="196" y="147" width="22" height="22" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1175" x="248" y="387" width="18" height="17" xoffset="0" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1178" x="339" y="97" width="13" height="23" xoffset="2" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1179" x="166" y="368" width="11" height="18" xoffset="1" yoffset="8" xadvance="12" page="0" chnl="15" />
+ <char id="1180" x="294" y="327" width="13" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1181" x="221" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1186" x="377" y="145" width="15" height="22" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1187" x="344" y="383" width="13" height="17" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1198" x="90" y="313" width="14" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1199" x="105" y="313" width="14" height="18" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1200" x="120" y="311" width="14" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1201" x="363" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1202" x="344" y="145" width="16" height="22" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="1203" x="330" y="383" width="13" height="17" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1208" x="252" y="309" width="13" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1209" x="293" y="452" width="11" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1210" x="411" y="286" width="14" height="18" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="1211" x="39" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1240" x="476" y="188" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="1241" x="156" y="455" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1256" x="352" y="190" width="17" height="19" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1257" x="154" y="440" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1456" x="17" y="498" width="2" height="5" xoffset="3" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1457" x="462" y="473" width="8" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1458" x="479" y="471" width="7" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1459" x="487" y="471" width="7" height="5" xoffset="1" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1460" x="175" y="467" width="2" height="2" xoffset="3" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1461" x="423" y="481" width="5" height="2" xoffset="1" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1462" x="501" y="471" width="5" height="5" xoffset="1" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1463" x="183" y="192" width="5" height="1" xoffset="1" yoffset="25" xadvance="8" page="0" chnl="15" />
+ <char id="1464" x="209" y="490" width="5" height="4" xoffset="2" yoffset="24" xadvance="8" page="0" chnl="15" />
+ <char id="1465" x="414" y="482" width="2" height="3" xoffset="3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1466" x="411" y="482" width="2" height="3" xoffset="3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1467" x="453" y="473" width="8" height="5" xoffset="0" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1468" x="408" y="482" width="2" height="3" xoffset="3" yoffset="14" xadvance="8" page="0" chnl="15" />
+ <char id="1469" x="509" y="152" width="1" height="5" xoffset="3" yoffset="23" xadvance="8" page="0" chnl="15" />
+ <char id="1470" x="330" y="486" width="8" height="3" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1471" x="169" y="467" width="5" height="2" xoffset="1" yoffset="7" xadvance="8" page="0" chnl="15" />
+ <char id="1472" x="146" y="387" width="3" height="18" xoffset="2" yoffset="7" xadvance="7" page="0" chnl="15" />
+ <char id="1473" x="420" y="481" width="2" height="3" xoffset="9" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1474" x="417" y="482" width="2" height="3" xoffset="-3" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="1475" x="109" y="470" width="4" height="14" xoffset="2" yoffset="9" xadvance="8" page="0" chnl="15" />
+ <char id="1488" x="224" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1489" x="266" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1490" x="341" y="448" width="10" height="14" xoffset="0" yoffset="9" xadvance="11" page="0" chnl="15" />
+ <char id="1491" x="195" y="452" width="12" height="14" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1492" x="208" y="452" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1493" x="114" y="470" width="4" height="14" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1494" x="0" y="472" width="9" height="14" xoffset="1" yoffset="9" xadvance="10" page="0" chnl="15" />
+ <char id="1495" x="336" y="433" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1496" x="362" y="432" width="12" height="14" xoffset="1" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1497" x="287" y="481" width="4" height="8" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1498" x="245" y="232" width="11" height="19" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1499" x="429" y="446" width="10" height="14" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1500" x="501" y="286" width="10" height="18" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="1501" x="401" y="431" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1502" x="280" y="437" width="13" height="14" xoffset="0" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1503" x="286" y="231" width="4" height="19" xoffset="1" yoffset="9" xadvance="6" page="0" chnl="15" />
+ <char id="1504" x="71" y="471" width="7" height="14" xoffset="1" yoffset="9" xadvance="9" page="0" chnl="15" />
+ <char id="1505" x="210" y="437" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1506" x="171" y="405" width="12" height="16" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1507" x="488" y="208" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1508" x="440" y="431" width="12" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1509" x="221" y="232" width="11" height="19" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1510" x="427" y="431" width="12" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1511" x="0" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="1512" x="317" y="450" width="11" height="14" xoffset="0" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1513" x="434" y="416" width="16" height="14" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="1514" x="64" y="441" width="14" height="14" xoffset="0" yoffset="9" xadvance="16" page="0" chnl="15" />
+ <char id="1520" x="385" y="447" width="10" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1521" x="374" y="447" width="10" height="14" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1522" x="202" y="481" width="10" height="8" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="1523" x="419" y="474" width="5" height="6" xoffset="1" yoffset="9" xadvance="7" page="0" chnl="15" />
+ <char id="1524" x="349" y="476" width="10" height="6" xoffset="1" yoffset="9" xadvance="12" page="0" chnl="15" />
+ <char id="1548" x="292" y="481" width="4" height="8" xoffset="2" yoffset="11" xadvance="7" page="0" chnl="15" />
+ <char id="1563" x="430" y="461" width="4" height="12" xoffset="1" yoffset="7" xadvance="7" page="0" chnl="15" />
+ <char id="1567" x="79" y="471" width="7" height="14" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="1569" x="404" y="462" width="9" height="12" xoffset="1" yoffset="10" xadvance="11" page="0" chnl="15" />
+ <char id="1570" x="67" y="389" width="6" height="18" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1571" x="508" y="208" width="3" height="19" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1572" x="298" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1573" x="65" y="195" width="3" height="20" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1574" x="40" y="425" width="12" height="15" xoffset="2" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1575" x="506" y="399" width="3" height="15" xoffset="1" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1576" x="32" y="441" width="15" height="14" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1577" x="291" y="403" width="7" height="16" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
+ <char id="1578" x="336" y="463" width="15" height="12" xoffset="0" yoffset="7" xadvance="15" page="0" chnl="15" />
+ <char id="1579" x="0" y="442" width="15" height="14" xoffset="0" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1580" x="28" y="332" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1581" x="70" y="332" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1582" x="423" y="97" width="13" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="1583" x="467" y="460" width="7" height="11" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1584" x="17" y="408" width="7" height="17" xoffset="1" yoffset="2" xadvance="8" page="0" chnl="15" />
+ <char id="1585" x="246" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1586" x="130" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1587" x="124" y="470" width="21" height="13" xoffset="1" yoffset="11" xadvance="21" page="0" chnl="15" />
+ <char id="1588" x="212" y="192" width="21" height="19" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1589" x="109" y="425" width="25" height="14" xoffset="1" yoffset="10" xadvance="25" page="0" chnl="15" />
+ <char id="1590" x="95" y="194" width="25" height="19" xoffset="1" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1591" x="127" y="408" width="14" height="16" xoffset="-1" yoffset="3" xadvance="12" page="0" chnl="15" />
+ <char id="1592" x="142" y="406" width="14" height="16" xoffset="-1" yoffset="3" xadvance="12" page="0" chnl="15" />
+ <char id="1593" x="459" y="168" width="13" height="20" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1594" x="164" y="0" width="13" height="26" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1600" x="382" y="482" width="6" height="3" xoffset="-1" yoffset="16" xadvance="5" page="0" chnl="15" />
+ <char id="1601" x="25" y="408" width="18" height="16" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1602" x="243" y="212" width="14" height="19" xoffset="0" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="1603" x="492" y="399" width="13" height="15" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1604" x="12" y="195" width="11" height="20" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1605" x="282" y="403" width="8" height="16" xoffset="0" yoffset="12" xadvance="8" page="0" chnl="15" />
+ <char id="1606" x="157" y="405" width="13" height="16" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1607" x="17" y="487" width="6" height="10" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="1608" x="294" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1609" x="233" y="467" width="12" height="13" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1610" x="184" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1611" x="251" y="490" width="4" height="4" xoffset="1" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1612" x="495" y="471" width="5" height="5" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1613" x="507" y="469" width="4" height="5" xoffset="1" yoffset="21" xadvance="5" page="0" chnl="15" />
+ <char id="1614" x="429" y="481" width="4" height="2" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
+ <char id="1615" x="6" y="498" width="5" height="5" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1616" x="394" y="482" width="4" height="3" xoffset="1" yoffset="21" xadvance="5" page="0" chnl="15" />
+ <char id="1617" x="191" y="490" width="5" height="4" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
+ <char id="1618" x="256" y="490" width="3" height="4" xoffset="1" yoffset="2" xadvance="4" page="0" chnl="15" />
+ <char id="1619" x="159" y="437" width="6" height="2" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
+ <char id="1620" x="264" y="490" width="3" height="4" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1621" x="268" y="490" width="3" height="4" xoffset="1" yoffset="22" xadvance="5" page="0" chnl="15" />
+ <char id="1632" x="344" y="476" width="4" height="7" xoffset="4" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1633" x="93" y="471" width="5" height="14" xoffset="4" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1634" x="63" y="471" width="7" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1635" x="407" y="446" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1636" x="505" y="430" width="6" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1637" x="0" y="487" width="9" height="10" xoffset="2" yoffset="7" xadvance="13" page="0" chnl="15" />
+ <char id="1638" x="38" y="472" width="8" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1639" x="502" y="415" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1640" x="396" y="447" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1641" x="29" y="472" width="8" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1642" x="262" y="405" width="10" height="16" xoffset="1" yoffset="4" xadvance="12" page="0" chnl="15" />
+ <char id="1643" x="435" y="473" width="3" height="6" xoffset="2" yoffset="16" xadvance="7" page="0" chnl="15" />
+ <char id="1644" x="297" y="481" width="4" height="8" xoffset="2" yoffset="11" xadvance="7" page="0" chnl="15" />
+ <char id="1645" x="122" y="485" width="8" height="9" xoffset="2" yoffset="4" xadvance="12" page="0" chnl="15" />
+ <char id="1646" x="24" y="487" width="15" height="9" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1647" x="463" y="399" width="14" height="15" xoffset="0" yoffset="11" xadvance="14" page="0" chnl="15" />
+ <char id="1648" x="509" y="144" width="2" height="7" xoffset="0" yoffset="3" xadvance="0" page="0" chnl="15" />
+ <char id="1649" x="275" y="231" width="5" height="19" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1650" x="269" y="231" width="5" height="19" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1651" x="59" y="195" width="5" height="20" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
+ <char id="1652" x="260" y="490" width="3" height="4" xoffset="1" yoffset="1" xadvance="5" page="0" chnl="15" />
+ <char id="1653" x="315" y="402" width="6" height="16" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
+ <char id="1654" x="13" y="236" width="12" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1655" x="26" y="236" width="12" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1656" x="333" y="210" width="14" height="19" xoffset="2" yoffset="5" xadvance="15" page="0" chnl="15" />
+ <char id="1657" x="267" y="385" width="15" height="17" xoffset="0" yoffset="2" xadvance="15" page="0" chnl="15" />
+ <char id="1658" x="447" y="399" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1659" x="95" y="408" width="15" height="16" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1660" x="399" y="400" width="15" height="15" xoffset="0" yoffset="7" xadvance="15" page="0" chnl="15" />
+ <char id="1661" x="415" y="400" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1662" x="111" y="408" width="15" height="16" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1663" x="431" y="400" width="15" height="15" xoffset="0" yoffset="4" xadvance="15" page="0" chnl="15" />
+ <char id="1664" x="283" y="385" width="15" height="17" xoffset="0" yoffset="10" xadvance="15" page="0" chnl="15" />
+ <char id="1665" x="355" y="0" width="13" height="24" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1666" x="150" y="0" width="13" height="26" xoffset="1" yoffset="2" xadvance="14" page="0" chnl="15" />
+ <char id="1667" x="280" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1668" x="266" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1669" x="14" y="0" width="13" height="27" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="1670" x="238" y="328" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1671" x="224" y="330" width="13" height="18" xoffset="1" yoffset="10" xadvance="14" page="0" chnl="15" />
+ <char id="1672" x="43" y="389" width="7" height="18" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1673" x="55" y="471" width="7" height="14" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1674" x="299" y="403" width="7" height="16" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1675" x="504" y="120" width="7" height="23" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1676" x="9" y="408" width="7" height="17" xoffset="1" yoffset="2" xadvance="8" page="0" chnl="15" />
+ <char id="1677" x="307" y="403" width="7" height="16" xoffset="1" yoffset="8" xadvance="8" page="0" chnl="15" />
+ <char id="1678" x="27" y="389" width="7" height="18" xoffset="1" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1679" x="484" y="362" width="8" height="18" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1680" x="493" y="362" width="8" height="18" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
+ <char id="1681" x="144" y="171" width="11" height="22" xoffset="0" yoffset="2" xadvance="10" page="0" chnl="15" />
+ <char id="1682" x="478" y="381" width="11" height="17" xoffset="0" yoffset="7" xadvance="10" page="0" chnl="15" />
+ <char id="1683" x="305" y="450" width="11" height="14" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1684" x="210" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1685" x="223" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1686" x="236" y="405" width="12" height="16" xoffset="0" yoffset="11" xadvance="10" page="0" chnl="15" />
+ <char id="1687" x="154" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="10" page="0" chnl="15" />
+ <char id="1688" x="499" y="167" width="11" height="20" xoffset="0" yoffset="4" xadvance="10" page="0" chnl="15" />
+ <char id="1689" x="296" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="10" page="0" chnl="15" />
+ <char id="1690" x="150" y="387" width="21" height="17" xoffset="1" yoffset="7" xadvance="21" page="0" chnl="15" />
+ <char id="1691" x="322" y="402" width="21" height="15" xoffset="1" yoffset="11" xadvance="21" page="0" chnl="15" />
+ <char id="1692" x="209" y="170" width="21" height="21" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1693" x="83" y="425" width="25" height="14" xoffset="1" yoffset="10" xadvance="25" page="0" chnl="15" />
+ <char id="1694" x="183" y="170" width="25" height="21" xoffset="1" yoffset="3" xadvance="25" page="0" chnl="15" />
+ <char id="1695" x="315" y="384" width="14" height="17" xoffset="-1" yoffset="2" xadvance="12" page="0" chnl="15" />
+ <char id="1696" x="0" y="0" width="13" height="27" xoffset="0" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="1697" x="435" y="461" width="18" height="11" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1698" x="364" y="401" width="18" height="15" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1699" x="381" y="168" width="18" height="20" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="1700" x="315" y="190" width="18" height="19" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1701" x="172" y="387" width="18" height="17" xoffset="0" yoffset="9" xadvance="18" page="0" chnl="15" />
+ <char id="1702" x="78" y="275" width="18" height="18" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1703" x="416" y="168" width="14" height="20" xoffset="0" yoffset="6" xadvance="14" page="0" chnl="15" />
+ <char id="1704" x="408" y="145" width="14" height="22" xoffset="0" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1705" x="303" y="420" width="18" height="14" xoffset="0" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1706" x="146" y="470" width="18" height="13" xoffset="0" yoffset="6" xadvance="18" page="0" chnl="15" />
+ <char id="1707" x="341" y="418" width="18" height="14" xoffset="0" yoffset="5" xadvance="18" page="0" chnl="15" />
+ <char id="1708" x="448" y="305" width="13" height="18" xoffset="0" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="1709" x="386" y="382" width="13" height="17" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1710" x="453" y="145" width="13" height="22" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1711" x="191" y="387" width="18" height="17" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1712" x="210" y="387" width="18" height="17" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1713" x="135" y="273" width="18" height="18" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1714" x="219" y="147" width="18" height="22" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1715" x="268" y="0" width="18" height="24" xoffset="0" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="1716" x="493" y="228" width="18" height="18" xoffset="0" yoffset="1" xadvance="18" page="0" chnl="15" />
+ <char id="1717" x="423" y="121" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1718" x="435" y="121" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1719" x="459" y="120" width="11" height="23" xoffset="0" yoffset="1" xadvance="11" page="0" chnl="15" />
+ <char id="1720" x="447" y="121" width="11" height="23" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1721" x="431" y="168" width="13" height="20" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1722" x="179" y="467" width="13" height="13" xoffset="0" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1723" x="268" y="169" width="13" height="21" xoffset="0" yoffset="3" xadvance="13" page="0" chnl="15" />
+ <char id="1724" x="420" y="209" width="13" height="19" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="1725" x="445" y="168" width="13" height="20" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="1726" x="282" y="467" width="11" height="13" xoffset="-1" yoffset="7" xadvance="11" page="0" chnl="15" />
+ <char id="1727" x="369" y="0" width="13" height="24" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="1728" x="76" y="425" width="6" height="15" xoffset="1" yoffset="5" xadvance="8" page="0" chnl="15" />
+ <char id="1729" x="213" y="481" width="9" height="8" xoffset="0" yoffset="13" xadvance="9" page="0" chnl="15" />
+ <char id="1730" x="306" y="465" width="9" height="13" xoffset="0" yoffset="8" xadvance="9" page="0" chnl="15" />
+ <char id="1731" x="481" y="445" width="9" height="14" xoffset="0" yoffset="7" xadvance="9" page="0" chnl="15" />
+ <char id="1732" x="270" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1733" x="258" y="467" width="11" height="13" xoffset="0" yoffset="11" xadvance="11" page="0" chnl="15" />
+ <char id="1734" x="250" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1735" x="0" y="195" width="11" height="20" xoffset="0" yoffset="4" xadvance="11" page="0" chnl="15" />
+ <char id="1736" x="320" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="11" page="0" chnl="15" />
+ <char id="1737" x="190" y="368" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1738" x="286" y="366" width="11" height="18" xoffset="0" yoffset="6" xadvance="11" page="0" chnl="15" />
+ <char id="1739" x="308" y="168" width="11" height="21" xoffset="0" yoffset="3" xadvance="11" page="0" chnl="15" />
+ <char id="1740" x="220" y="467" width="12" height="13" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1741" x="165" y="470" width="13" height="13" xoffset="1" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1742" x="27" y="425" width="12" height="15" xoffset="2" yoffset="9" xadvance="15" page="0" chnl="15" />
+ <char id="1743" x="233" y="232" width="11" height="19" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="1744" x="249" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1745" x="197" y="405" width="12" height="16" xoffset="2" yoffset="11" xadvance="15" page="0" chnl="15" />
+ <char id="1746" x="316" y="465" width="19" height="12" xoffset="0" yoffset="11" xadvance="19" page="0" chnl="15" />
+ <char id="1747" x="344" y="401" width="19" height="15" xoffset="0" yoffset="8" xadvance="19" page="0" chnl="15" />
+ <char id="1748" x="389" y="482" width="4" height="3" xoffset="0" yoffset="16" xadvance="5" page="0" chnl="15" />
+ <char id="1749" x="10" y="487" width="6" height="10" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="1750" x="81" y="486" width="12" height="9" xoffset="-6" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1751" x="94" y="486" width="9" height="9" xoffset="-5" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1752" x="215" y="490" width="5" height="4" xoffset="-2" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1753" x="256" y="481" width="5" height="8" xoffset="-2" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1754" x="156" y="484" width="6" height="9" xoffset="-3" yoffset="1" xadvance="0" page="0" chnl="15" />
+ <char id="1755" x="404" y="482" width="3" height="3" xoffset="-1" yoffset="7" xadvance="0" page="0" chnl="15" />
+ <char id="1756" x="360" y="476" width="10" height="6" xoffset="-5" yoffset="4" xadvance="0" page="0" chnl="15" />
+ <char id="1757" x="55" y="0" width="26" height="26" xoffset="0" yoffset="1" xadvance="27" page="0" chnl="15" />
+ <char id="1758" x="28" y="0" width="26" height="26" xoffset="0" yoffset="1" xadvance="26" page="0" chnl="15" />
+ <char id="1759" x="440" y="479" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1760" x="437" y="480" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1761" x="158" y="494" width="7" height="4" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1762" x="272" y="481" width="4" height="8" xoffset="-2" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1763" x="322" y="478" width="10" height="7" xoffset="-5" yoffset="21" xadvance="0" page="0" chnl="15" />
+ <char id="1764" x="190" y="212" width="3" height="1" xoffset="-1" yoffset="9" xadvance="0" page="0" chnl="15" />
+ <char id="1765" x="333" y="478" width="5" height="7" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
+ <char id="1766" x="402" y="475" width="9" height="6" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
+ <char id="1767" x="382" y="475" width="9" height="6" xoffset="-4" yoffset="4" xadvance="0" page="0" chnl="15" />
+ <char id="1768" x="249" y="481" width="6" height="8" xoffset="-3" yoffset="2" xadvance="0" page="0" chnl="15" />
+ <char id="1769" x="282" y="169" width="13" height="21" xoffset="1" yoffset="2" xadvance="14" page="0" chnl="15" />
+ <char id="1770" x="12" y="498" width="4" height="5" xoffset="-2" yoffset="22" xadvance="0" page="0" chnl="15" />
+ <char id="1771" x="231" y="490" width="4" height="4" xoffset="-2" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="1772" x="434" y="481" width="2" height="2" xoffset="-1" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="1773" x="282" y="481" width="4" height="8" xoffset="-2" yoffset="20" xadvance="0" page="0" chnl="15" />
+ <char id="1776" x="339" y="476" width="4" height="7" xoffset="4" yoffset="11" xadvance="13" page="0" chnl="15" />
+ <char id="1777" x="87" y="471" width="5" height="14" xoffset="4" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1778" x="47" y="471" width="7" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1779" x="363" y="447" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1780" x="501" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1781" x="491" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1782" x="471" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1783" x="461" y="445" width="9" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1784" x="440" y="446" width="10" height="14" xoffset="2" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1785" x="20" y="472" width="8" height="14" xoffset="3" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="1786" x="234" y="192" width="21" height="19" xoffset="1" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="1787" x="69" y="195" width="25" height="19" xoffset="1" yoffset="5" xadvance="25" page="0" chnl="15" />
+ <char id="1788" x="178" y="0" width="13" height="26" xoffset="0" yoffset="2" xadvance="13" page="0" chnl="15" />
+ <char id="1789" x="500" y="381" width="9" height="17" xoffset="1" yoffset="10" xadvance="11" page="0" chnl="15" />
+ <char id="1790" x="273" y="403" width="8" height="16" xoffset="0" yoffset="12" xadvance="8" page="0" chnl="15" />
+ <char id="7808" x="468" y="0" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7809" x="174" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7810" x="24" y="28" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7811" x="195" y="254" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7812" x="444" y="0" width="23" height="23" xoffset="0" yoffset="0" xadvance="22" page="0" chnl="15" />
+ <char id="7813" x="216" y="252" width="20" height="18" xoffset="0" yoffset="5" xadvance="19" page="0" chnl="15" />
+ <char id="7840" x="330" y="25" width="17" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7841" x="39" y="236" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7842" x="348" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7843" x="78" y="236" width="12" height="19" xoffset="1" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7844" x="144" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7845" x="176" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7846" x="36" y="52" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7847" x="150" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7848" x="420" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7849" x="137" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7850" x="432" y="48" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7851" x="40" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7852" x="82" y="0" width="17" height="26" xoffset="0" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7853" x="124" y="123" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7854" x="198" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7855" x="111" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7856" x="384" y="25" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7857" x="98" y="123" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7858" x="456" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7859" x="410" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7860" x="438" y="24" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7861" x="53" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7862" x="118" y="0" width="17" height="26" xoffset="0" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7863" x="397" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7864" x="395" y="97" width="13" height="23" xoffset="2" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7865" x="117" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7866" x="255" y="98" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7867" x="130" y="234" width="12" height="19" xoffset="1" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7868" x="269" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7869" x="377" y="344" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7870" x="381" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7871" x="241" y="122" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7872" x="437" y="97" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7873" x="371" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7874" x="451" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7875" x="358" y="121" width="12" height="23" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="7876" x="465" y="96" width="13" height="23" xoffset="2" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7877" x="66" y="172" width="12" height="22" xoffset="1" yoffset="1" xadvance="13" page="0" chnl="15" />
+ <char id="7878" x="136" y="0" width="13" height="26" xoffset="2" yoffset="2" xadvance="16" page="0" chnl="15" />
+ <char id="7879" x="345" y="121" width="12" height="23" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7880" x="95" y="147" width="6" height="23" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="7881" x="501" y="208" width="6" height="19" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
+ <char id="7882" x="507" y="96" width="4" height="23" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="7883" x="155" y="147" width="4" height="23" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="7884" x="305" y="0" width="17" height="24" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="7885" x="448" y="209" width="13" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="7886" x="180" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7887" x="378" y="210" width="13" height="19" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="7888" x="162" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7889" x="325" y="97" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7890" x="366" y="25" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7891" x="28" y="124" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7892" x="108" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7893" x="42" y="124" width="13" height="23" xoffset="1" yoffset="0" xadvance="14" page="0" chnl="15" />
+ <char id="7894" x="90" y="51" width="17" height="23" xoffset="1" yoffset="0" xadvance="18" page="0" chnl="15" />
+ <char id="7895" x="0" y="172" width="13" height="22" xoffset="1" yoffset="1" xadvance="14" page="0" chnl="15" />
+ <char id="7896" x="100" y="0" width="17" height="26" xoffset="1" yoffset="2" xadvance="18" page="0" chnl="15" />
+ <char id="7897" x="409" y="97" width="13" height="23" xoffset="1" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="7898" x="116" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7899" x="136" y="292" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7900" x="137" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7901" x="102" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7902" x="95" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7903" x="442" y="189" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="7904" x="158" y="27" width="20" height="23" xoffset="1" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7905" x="0" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7906" x="228" y="0" width="20" height="24" xoffset="1" yoffset="4" xadvance="20" page="0" chnl="15" />
+ <char id="7907" x="102" y="214" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="7908" x="60" y="100" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7909" x="143" y="234" width="12" height="19" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="7910" x="90" y="99" width="14" height="23" xoffset="2" yoffset="0" xadvance="17" page="0" chnl="15" />
+ <char id="7911" x="156" y="234" width="12" height="19" xoffset="1" yoffset="4" xadvance="14" page="0" chnl="15" />
+ <char id="7912" x="218" y="26" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7913" x="85" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7914" x="199" y="26" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7915" x="68" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7916" x="237" y="25" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7917" x="34" y="216" width="16" height="19" xoffset="1" yoffset="4" xadvance="17" page="0" chnl="15" />
+ <char id="7918" x="275" y="25" width="18" height="23" xoffset="2" yoffset="0" xadvance="20" page="0" chnl="15" />
+ <char id="7919" x="51" y="294" width="16" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="7920" x="256" y="25" width="18" height="23" xoffset="2" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="7921" x="68" y="216" width="16" height="19" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="7922" x="18" y="52" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7923" x="180" y="99" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="7924" x="216" y="50" width="17" height="23" xoffset="0" yoffset="5" xadvance="16" page="0" chnl="15" />
+ <char id="7925" x="318" y="210" width="14" height="19" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="7926" x="234" y="50" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7927" x="340" y="0" width="14" height="24" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="7928" x="126" y="51" width="17" height="23" xoffset="0" yoffset="0" xadvance="16" page="0" chnl="15" />
+ <char id="7929" x="488" y="72" width="14" height="23" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8204" x="510" y="24" width="1" height="19" xoffset="0" yoffset="7" xadvance="0" page="0" chnl="15" />
+ <char id="8205" x="346" y="168" width="6" height="21" xoffset="-2" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8206" x="24" y="195" width="6" height="20" xoffset="0" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8207" x="31" y="195" width="6" height="20" xoffset="-5" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8211" x="89" y="496" width="14" height="4" xoffset="0" yoffset="14" xadvance="13" page="0" chnl="15" />
+ <char id="8212" x="20" y="498" width="24" height="4" xoffset="0" yoffset="14" xadvance="24" page="0" chnl="15" />
+ <char id="8213" x="45" y="497" width="22" height="4" xoffset="1" yoffset="14" xadvance="24" page="0" chnl="15" />
+ <char id="8215" x="307" y="479" width="14" height="7" xoffset="0" yoffset="21" xadvance="13" page="0" chnl="15" />
+ <char id="8216" x="302" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8217" x="262" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8218" x="267" y="481" width="4" height="8" xoffset="1" yoffset="19" xadvance="6" page="0" chnl="15" />
+ <char id="8219" x="277" y="481" width="4" height="8" xoffset="1" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8220" x="169" y="484" width="10" height="8" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8221" x="191" y="481" width="10" height="8" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8222" x="180" y="481" width="10" height="8" xoffset="1" yoffset="19" xadvance="12" page="0" chnl="15" />
+ <char id="8224" x="14" y="172" width="12" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8225" x="481" y="144" width="13" height="22" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8226" x="241" y="481" width="7" height="8" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8230" x="68" y="496" width="20" height="4" xoffset="2" yoffset="19" xadvance="24" page="0" chnl="15" />
+ <char id="8234" x="81" y="389" width="6" height="18" xoffset="0" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="8235" x="74" y="389" width="6" height="18" xoffset="-5" yoffset="8" xadvance="0" page="0" chnl="15" />
+ <char id="8236" x="38" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8237" x="339" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8238" x="332" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8240" x="345" y="230" width="25" height="18" xoffset="0" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="8242" x="425" y="474" width="4" height="6" xoffset="2" yoffset="5" xadvance="6" page="0" chnl="15" />
+ <char id="8243" x="371" y="475" width="10" height="6" xoffset="1" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="8249" x="422" y="461" width="7" height="12" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8250" x="414" y="461" width="7" height="12" xoffset="1" yoffset="10" xadvance="8" page="0" chnl="15" />
+ <char id="8252" x="358" y="364" width="10" height="18" xoffset="2" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="8254" x="302" y="490" width="9" height="3" xoffset="0" yoffset="6" xadvance="8" page="0" chnl="15" />
+ <char id="8260" x="378" y="306" width="13" height="18" xoffset="-4" yoffset="5" xadvance="4" page="0" chnl="15" />
+ <char id="8298" x="353" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8299" x="367" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8300" x="374" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8301" x="45" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8302" x="52" y="195" width="6" height="20" xoffset="-3" yoffset="6" xadvance="0" page="0" chnl="15" />
+ <char id="8303" x="360" y="168" width="6" height="21" xoffset="-3" yoffset="5" xadvance="0" page="0" chnl="15" />
+ <char id="8319" x="131" y="484" width="8" height="9" xoffset="1" yoffset="8" xadvance="9" page="0" chnl="15" />
+ <char id="8355" x="42" y="332" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8356" x="266" y="309" width="13" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8359" x="291" y="231" width="27" height="18" xoffset="0" yoffset="5" xadvance="27" page="0" chnl="15" />
+ <char id="8362" x="416" y="416" width="17" height="14" xoffset="1" yoffset="9" xadvance="19" page="0" chnl="15" />
+ <char id="8363" x="65" y="425" width="10" height="15" xoffset="1" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8364" x="406" y="209" width="13" height="19" xoffset="0" yoffset="4" xadvance="13" page="0" chnl="15" />
+ <char id="8453" x="23" y="256" width="21" height="18" xoffset="0" yoffset="5" xadvance="21" page="0" chnl="15" />
+ <char id="8467" x="214" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="11" page="0" chnl="15" />
+ <char id="8470" x="371" y="230" width="24" height="18" xoffset="2" yoffset="5" xadvance="27" page="0" chnl="15" />
+ <char id="8482" x="475" y="460" width="19" height="10" xoffset="2" yoffset="5" xadvance="24" page="0" chnl="15" />
+ <char id="8486" x="406" y="189" width="17" height="19" xoffset="0" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="8494" x="322" y="433" width="13" height="14" xoffset="1" yoffset="9" xadvance="14" page="0" chnl="15" />
+ <char id="8531" x="400" y="248" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8532" x="360" y="249" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8539" x="320" y="249" width="19" height="18" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8540" x="300" y="250" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8541" x="0" y="275" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8542" x="20" y="275" width="19" height="18" xoffset="0" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8706" x="202" y="368" width="11" height="18" xoffset="0" yoffset="5" xadvance="12" page="0" chnl="15" />
+ <char id="8710" x="253" y="290" width="15" height="18" xoffset="0" yoffset="5" xadvance="14" page="0" chnl="15" />
+ <char id="8719" x="72" y="51" width="17" height="23" xoffset="1" yoffset="5" xadvance="20" page="0" chnl="15" />
+ <char id="8721" x="203" y="75" width="14" height="23" xoffset="2" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="8722" x="117" y="495" width="12" height="4" xoffset="1" yoffset="12" xadvance="14" page="0" chnl="15" />
+ <char id="8725" x="98" y="332" width="13" height="18" xoffset="-4" yoffset="5" xadvance="4" page="0" chnl="15" />
+ <char id="8729" x="236" y="490" width="4" height="4" xoffset="1" yoffset="12" xadvance="6" page="0" chnl="15" />
+ <char id="8730" x="397" y="0" width="12" height="24" xoffset="1" yoffset="0" xadvance="13" page="0" chnl="15" />
+ <char id="8734" x="40" y="487" width="14" height="9" xoffset="1" yoffset="9" xadvance="17" page="0" chnl="15" />
+ <char id="8735" x="44" y="408" width="16" height="16" xoffset="4" yoffset="7" xadvance="23" page="0" chnl="15" />
+ <char id="8745" x="237" y="290" width="15" height="18" xoffset="1" yoffset="5" xadvance="17" page="0" chnl="15" />
+ <char id="8747" x="220" y="0" width="7" height="25" xoffset="0" yoffset="0" xadvance="6" page="0" chnl="15" />
+ <char id="8776" x="454" y="461" width="12" height="11" xoffset="0" yoffset="9" xadvance="13" page="0" chnl="15" />
+ <char id="8800" x="78" y="351" width="12" height="18" xoffset="0" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8801" x="130" y="455" width="12" height="14" xoffset="1" yoffset="7" xadvance="14" page="0" chnl="15" />
+ <char id="8804" x="0" y="351" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="8805" x="490" y="324" width="12" height="18" xoffset="1" yoffset="5" xadvance="13" page="0" chnl="15" />
+ <char id="9786" x="61" y="408" width="16" height="16" xoffset="4" yoffset="8" xadvance="24" page="0" chnl="15" />
+ <char id="9787" x="78" y="408" width="16" height="16" xoffset="5" yoffset="8" xadvance="25" page="0" chnl="15" />
+ <char id="9788" x="72" y="27" width="22" height="23" xoffset="0" yoffset="3" xadvance="22" page="0" chnl="15" />
+ <char id="9792" x="383" y="0" width="13" height="24" xoffset="2" yoffset="4" xadvance="18" page="0" chnl="15" />
+ <char id="9794" x="153" y="75" width="16" height="23" xoffset="1" yoffset="3" xadvance="18" page="0" chnl="15" />
+ <char id="9824" x="14" y="426" width="12" height="15" xoffset="0" yoffset="8" xadvance="13" page="0" chnl="15" />
+ <char id="9827" x="383" y="400" width="15" height="15" xoffset="0" yoffset="8" xadvance="16" page="0" chnl="15" />
+ <char id="9829" x="0" y="426" width="13" height="15" xoffset="0" yoffset="8" xadvance="14" page="0" chnl="15" />
+ <char id="9830" x="53" y="425" width="11" height="15" xoffset="0" yoffset="8" xadvance="12" page="0" chnl="15" />
+ <char id="9834" x="400" y="382" width="12" height="17" xoffset="0" yoffset="6" xadvance="12" page="0" chnl="15" />
+ <char id="9835" x="400" y="168" width="15" height="20" xoffset="1" yoffset="4" xadvance="18" page="0" chnl="15" />
+ </chars>
+ <kernings count="397">
+ <kerning first="32" second="65" amount="-1" />
+ <kerning first="32" second="902" amount="-1" />
+ <kerning first="32" second="913" amount="-1" />
+ <kerning first="32" second="916" amount="-1" />
+ <kerning first="32" second="923" amount="-1" />
+ <kerning first="49" second="49" amount="-1" />
+ <kerning first="65" second="32" amount="-1" />
+ <kerning first="65" second="84" amount="-2" />
+ <kerning first="65" second="86" amount="-2" />
+ <kerning first="65" second="87" amount="-1" />
+ <kerning first="65" second="89" amount="-2" />
+ <kerning first="65" second="118" amount="-1" />
+ <kerning first="65" second="121" amount="-1" />
+ <kerning first="65" second="8217" amount="-1" />
+ <kerning first="70" second="44" amount="-2" />
+ <kerning first="70" second="46" amount="-2" />
+ <kerning first="70" second="65" amount="-1" />
+ <kerning first="76" second="84" amount="-2" />
+ <kerning first="76" second="86" amount="-2" />
+ <kerning first="76" second="87" amount="-1" />
+ <kerning first="76" second="89" amount="-2" />
+ <kerning first="76" second="121" amount="-1" />
+ <kerning first="76" second="8217" amount="-1" />
+ <kerning first="80" second="44" amount="-3" />
+ <kerning first="80" second="46" amount="-3" />
+ <kerning first="80" second="65" amount="-2" />
+ <kerning first="82" second="89" amount="-1" />
+ <kerning first="84" second="44" amount="-2" />
+ <kerning first="84" second="45" amount="-1" />
+ <kerning first="84" second="46" amount="-2" />
+ <kerning first="84" second="58" amount="-2" />
+ <kerning first="84" second="894" amount="-2" />
+ <kerning first="84" second="65" amount="-2" />
+ <kerning first="84" second="97" amount="-2" />
+ <kerning first="84" second="99" amount="-2" />
+ <kerning first="84" second="101" amount="-2" />
+ <kerning first="84" second="111" amount="-2" />
+ <kerning first="84" second="114" amount="-1" />
+ <kerning first="84" second="115" amount="-2" />
+ <kerning first="84" second="117" amount="-2" />
+ <kerning first="84" second="119" amount="-2" />
+ <kerning first="84" second="121" amount="-2" />
+ <kerning first="86" second="44" amount="-2" />
+ <kerning first="86" second="45" amount="-1" />
+ <kerning first="86" second="46" amount="-2" />
+ <kerning first="86" second="58" amount="-1" />
+ <kerning first="86" second="894" amount="-1" />
+ <kerning first="86" second="65" amount="-2" />
+ <kerning first="86" second="97" amount="-1" />
+ <kerning first="86" second="101" amount="-1" />
+ <kerning first="86" second="111" amount="-2" />
+ <kerning first="86" second="114" amount="-1" />
+ <kerning first="86" second="117" amount="-1" />
+ <kerning first="86" second="121" amount="-1" />
+ <kerning first="87" second="44" amount="-1" />
+ <kerning first="87" second="46" amount="-1" />
+ <kerning first="87" second="65" amount="-1" />
+ <kerning first="87" second="97" amount="-1" />
+ <kerning first="89" second="44" amount="-2" />
+ <kerning first="89" second="45" amount="-1" />
+ <kerning first="89" second="46" amount="-2" />
+ <kerning first="89" second="58" amount="-2" />
+ <kerning first="89" second="894" amount="-2" />
+ <kerning first="89" second="65" amount="-2" />
+ <kerning first="89" second="97" amount="-1" />
+ <kerning first="89" second="101" amount="-1" />
+ <kerning first="89" second="105" amount="-1" />
+ <kerning first="89" second="111" amount="-2" />
+ <kerning first="89" second="112" amount="-1" />
+ <kerning first="89" second="113" amount="-2" />
+ <kerning first="89" second="117" amount="-1" />
+ <kerning first="89" second="118" amount="-1" />
+ <kerning first="114" second="44" amount="-1" />
+ <kerning first="114" second="46" amount="-1" />
+ <kerning first="114" second="8217" amount="1" />
+ <kerning first="118" second="44" amount="-2" />
+ <kerning first="118" second="46" amount="-2" />
+ <kerning first="119" second="44" amount="-1" />
+ <kerning first="119" second="46" amount="-1" />
+ <kerning first="121" second="44" amount="-2" />
+ <kerning first="121" second="46" amount="-2" />
+ <kerning first="8216" second="8216" amount="-1" />
+ <kerning first="8217" second="32" amount="-1" />
+ <kerning first="8217" second="115" amount="-1" />
+ <kerning first="8217" second="8217" amount="-1" />
+ <kerning first="8222" second="1026" amount="-2" />
+ <kerning first="8222" second="1035" amount="-2" />
+ <kerning first="8222" second="1058" amount="-2" />
+ <kerning first="8222" second="1063" amount="-2" />
+ <kerning first="8222" second="1066" amount="-2" />
+ <kerning first="915" second="44" amount="-2" />
+ <kerning first="915" second="46" amount="-2" />
+ <kerning first="915" second="913" amount="-2" />
+ <kerning first="915" second="916" amount="-2" />
+ <kerning first="915" second="923" amount="-2" />
+ <kerning first="915" second="943" amount="-1" />
+ <kerning first="915" second="953" amount="-1" />
+ <kerning first="915" second="970" amount="1" />
+ <kerning first="948" second="967" amount="-1" />
+ <kerning first="966" second="967" amount="-1" />
+ <kerning first="902" second="932" amount="-2" />
+ <kerning first="902" second="933" amount="-2" />
+ <kerning first="902" second="939" amount="-2" />
+ <kerning first="902" second="947" amount="-1" />
+ <kerning first="902" second="957" amount="-1" />
+ <kerning first="902" second="967" amount="-1" />
+ <kerning first="910" second="920" amount="-1" />
+ <kerning first="910" second="934" amount="-1" />
+ <kerning first="910" second="945" amount="-2" />
+ <kerning first="910" second="948" amount="-1" />
+ <kerning first="910" second="963" amount="-2" />
+ <kerning first="910" second="966" amount="-2" />
+ <kerning first="910" second="912" amount="3" />
+ <kerning first="910" second="913" amount="-2" />
+ <kerning first="910" second="916" amount="-2" />
+ <kerning first="910" second="923" amount="-2" />
+ <kerning first="910" second="927" amount="-1" />
+ <kerning first="910" second="937" amount="-1" />
+ <kerning first="910" second="940" amount="-2" />
+ <kerning first="910" second="942" amount="-1" />
+ <kerning first="910" second="943" amount="-1" />
+ <kerning first="910" second="951" amount="-1" />
+ <kerning first="910" second="953" amount="-1" />
+ <kerning first="910" second="954" amount="-1" />
+ <kerning first="910" second="956" amount="-1" />
+ <kerning first="910" second="959" amount="-2" />
+ <kerning first="910" second="970" amount="1" />
+ <kerning first="910" second="972" amount="-2" />
+ <kerning first="913" second="8217" amount="-1" />
+ <kerning first="913" second="932" amount="-2" />
+ <kerning first="913" second="933" amount="-2" />
+ <kerning first="913" second="939" amount="-2" />
+ <kerning first="913" second="947" amount="-1" />
+ <kerning first="913" second="957" amount="-1" />
+ <kerning first="913" second="967" amount="-1" />
+ <kerning first="916" second="932" amount="-2" />
+ <kerning first="916" second="933" amount="-2" />
+ <kerning first="916" second="939" amount="-2" />
+ <kerning first="922" second="920" amount="-1" />
+ <kerning first="922" second="934" amount="-1" />
+ <kerning first="922" second="927" amount="-1" />
+ <kerning first="923" second="932" amount="-2" />
+ <kerning first="923" second="933" amount="-2" />
+ <kerning first="923" second="939" amount="-2" />
+ <kerning first="929" second="44" amount="-3" />
+ <kerning first="929" second="46" amount="-3" />
+ <kerning first="929" second="913" amount="-2" />
+ <kerning first="929" second="916" amount="-2" />
+ <kerning first="929" second="923" amount="-2" />
+ <kerning first="932" second="44" amount="-2" />
+ <kerning first="932" second="45" amount="-1" />
+ <kerning first="932" second="46" amount="-2" />
+ <kerning first="932" second="58" amount="-2" />
+ <kerning first="932" second="894" amount="-2" />
+ <kerning first="932" second="945" amount="-2" />
+ <kerning first="932" second="948" amount="-1" />
+ <kerning first="932" second="949" amount="-2" />
+ <kerning first="932" second="963" amount="-2" />
+ <kerning first="932" second="966" amount="-2" />
+ <kerning first="932" second="912" amount="3" />
+ <kerning first="932" second="913" amount="-2" />
+ <kerning first="932" second="916" amount="-2" />
+ <kerning first="932" second="923" amount="-2" />
+ <kerning first="932" second="940" amount="-2" />
+ <kerning first="932" second="941" amount="-2" />
+ <kerning first="932" second="947" amount="-2" />
+ <kerning first="932" second="951" amount="-2" />
+ <kerning first="932" second="956" amount="-2" />
+ <kerning first="932" second="957" amount="-2" />
+ <kerning first="932" second="959" amount="-2" />
+ <kerning first="932" second="965" amount="-2" />
+ <kerning first="932" second="967" amount="-1" />
+ <kerning first="932" second="968" amount="-2" />
+ <kerning first="932" second="970" amount="1" />
+ <kerning first="932" second="971" amount="-2" />
+ <kerning first="932" second="972" amount="-2" />
+ <kerning first="932" second="973" amount="-2" />
+ <kerning first="933" second="44" amount="-2" />
+ <kerning first="933" second="45" amount="-1" />
+ <kerning first="933" second="46" amount="-2" />
+ <kerning first="933" second="58" amount="-2" />
+ <kerning first="933" second="894" amount="-2" />
+ <kerning first="933" second="920" amount="-1" />
+ <kerning first="933" second="934" amount="-1" />
+ <kerning first="933" second="945" amount="-2" />
+ <kerning first="933" second="948" amount="-1" />
+ <kerning first="933" second="963" amount="-2" />
+ <kerning first="933" second="966" amount="-2" />
+ <kerning first="933" second="912" amount="3" />
+ <kerning first="933" second="913" amount="-2" />
+ <kerning first="933" second="916" amount="-2" />
+ <kerning first="933" second="923" amount="-2" />
+ <kerning first="933" second="927" amount="-1" />
+ <kerning first="933" second="937" amount="-1" />
+ <kerning first="933" second="940" amount="-2" />
+ <kerning first="933" second="942" amount="-1" />
+ <kerning first="933" second="943" amount="-1" />
+ <kerning first="933" second="947" amount="-1" />
+ <kerning first="933" second="951" amount="-1" />
+ <kerning first="933" second="953" amount="-1" />
+ <kerning first="933" second="954" amount="-1" />
+ <kerning first="933" second="956" amount="-1" />
+ <kerning first="933" second="959" amount="-2" />
+ <kerning first="933" second="970" amount="1" />
+ <kerning first="933" second="972" amount="-2" />
+ <kerning first="939" second="920" amount="-1" />
+ <kerning first="939" second="934" amount="-1" />
+ <kerning first="939" second="945" amount="-2" />
+ <kerning first="939" second="948" amount="-1" />
+ <kerning first="939" second="963" amount="-2" />
+ <kerning first="939" second="966" amount="-2" />
+ <kerning first="939" second="912" amount="3" />
+ <kerning first="939" second="913" amount="-2" />
+ <kerning first="939" second="916" amount="-2" />
+ <kerning first="939" second="923" amount="-2" />
+ <kerning first="939" second="927" amount="-1" />
+ <kerning first="939" second="937" amount="-1" />
+ <kerning first="939" second="940" amount="-2" />
+ <kerning first="939" second="942" amount="-1" />
+ <kerning first="939" second="943" amount="-1" />
+ <kerning first="939" second="951" amount="-1" />
+ <kerning first="939" second="953" amount="-1" />
+ <kerning first="939" second="954" amount="-1" />
+ <kerning first="939" second="956" amount="-1" />
+ <kerning first="939" second="959" amount="-2" />
+ <kerning first="939" second="970" amount="1" />
+ <kerning first="939" second="972" amount="-2" />
+ <kerning first="950" second="945" amount="-1" />
+ <kerning first="950" second="948" amount="-1" />
+ <kerning first="950" second="963" amount="-1" />
+ <kerning first="950" second="964" amount="-1" />
+ <kerning first="950" second="966" amount="-1" />
+ <kerning first="950" second="940" amount="-1" />
+ <kerning first="950" second="947" amount="-1" />
+ <kerning first="950" second="952" amount="-1" />
+ <kerning first="950" second="957" amount="-1" />
+ <kerning first="950" second="959" amount="-1" />
+ <kerning first="950" second="969" amount="-1" />
+ <kerning first="950" second="972" amount="-1" />
+ <kerning first="950" second="974" amount="-1" />
+ <kerning first="950" second="960" amount="-1" />
+ <kerning first="954" second="945" amount="-1" />
+ <kerning first="954" second="948" amount="-1" />
+ <kerning first="954" second="963" amount="-1" />
+ <kerning first="954" second="966" amount="-1" />
+ <kerning first="954" second="940" amount="-1" />
+ <kerning first="954" second="950" amount="-1" />
+ <kerning first="954" second="958" amount="-1" />
+ <kerning first="954" second="959" amount="-1" />
+ <kerning first="954" second="962" amount="-1" />
+ <kerning first="954" second="969" amount="-1" />
+ <kerning first="954" second="972" amount="-1" />
+ <kerning first="954" second="974" amount="-1" />
+ <kerning first="959" second="967" amount="-1" />
+ <kerning first="967" second="945" amount="-1" />
+ <kerning first="967" second="948" amount="-1" />
+ <kerning first="967" second="963" amount="-1" />
+ <kerning first="967" second="966" amount="-1" />
+ <kerning first="967" second="940" amount="-1" />
+ <kerning first="967" second="950" amount="-1" />
+ <kerning first="967" second="959" amount="-1" />
+ <kerning first="967" second="962" amount="-1" />
+ <kerning first="967" second="972" amount="-1" />
+ <kerning first="972" second="967" amount="-1" />
+ <kerning first="1027" second="44" amount="-2" />
+ <kerning first="1027" second="46" amount="-3" />
+ <kerning first="1027" second="171" amount="-1" />
+ <kerning first="1027" second="187" amount="-1" />
+ <kerning first="1033" second="8217" amount="-2" />
+ <kerning first="1034" second="8217" amount="-2" />
+ <kerning first="1040" second="8217" amount="-1" />
+ <kerning first="1040" second="1044" amount="1" />
+ <kerning first="1040" second="1058" amount="-1" />
+ <kerning first="1040" second="1059" amount="-1" />
+ <kerning first="1040" second="1063" amount="-2" />
+ <kerning first="1041" second="1040" amount="-1" />
+ <kerning first="1041" second="1063" amount="-1" />
+ <kerning first="1041" second="1066" amount="-1" />
+ <kerning first="1042" second="1040" amount="-1" />
+ <kerning first="1042" second="1046" amount="-1" />
+ <kerning first="1042" second="1057" amount="-1" />
+ <kerning first="1042" second="1058" amount="-1" />
+ <kerning first="1042" second="1059" amount="-1" />
+ <kerning first="1042" second="1061" amount="-1" />
+ <kerning first="1042" second="1063" amount="-1" />
+ <kerning first="1042" second="1066" amount="-1" />
+ <kerning first="1042" second="1095" amount="-1" />
+ <kerning first="1043" second="44" amount="-2" />
+ <kerning first="1043" second="46" amount="-3" />
+ <kerning first="1043" second="171" amount="-1" />
+ <kerning first="1043" second="187" amount="-1" />
+ <kerning first="1043" second="1040" amount="-1" />
+ <kerning first="1043" second="1076" amount="-1" />
+ <kerning first="1043" second="1077" amount="-1" />
+ <kerning first="1043" second="1083" amount="-1" />
+ <kerning first="1043" second="1084" amount="-1" />
+ <kerning first="1043" second="1086" amount="-1" />
+ <kerning first="1043" second="1088" amount="-1" />
+ <kerning first="1043" second="1091" amount="-1" />
+ <kerning first="1043" second="1099" amount="-1" />
+ <kerning first="1043" second="1100" amount="-1" />
+ <kerning first="1043" second="1102" amount="-1" />
+ <kerning first="1043" second="1103" amount="-1" />
+ <kerning first="1046" second="1066" amount="1" />
+ <kerning first="1047" second="1058" amount="-1" />
+ <kerning first="1047" second="1059" amount="-1" />
+ <kerning first="1047" second="1063" amount="-1" />
+ <kerning first="1050" second="1047" amount="1" />
+ <kerning first="1054" second="1061" amount="-1" />
+ <kerning first="1056" second="44" amount="-3" />
+ <kerning first="1056" second="46" amount="-3" />
+ <kerning first="1056" second="1040" amount="-2" />
+ <kerning first="1056" second="1044" amount="-1" />
+ <kerning first="1056" second="1051" amount="-1" />
+ <kerning first="1056" second="1061" amount="-1" />
+ <kerning first="1056" second="1076" amount="-1" />
+ <kerning first="1057" second="1061" amount="-1" />
+ <kerning first="1058" second="44" amount="-2" />
+ <kerning first="1058" second="46" amount="-2" />
+ <kerning first="1058" second="1040" amount="-1" />
+ <kerning first="1058" second="1060" amount="-1" />
+ <kerning first="1058" second="1074" amount="-1" />
+ <kerning first="1058" second="1077" amount="-1" />
+ <kerning first="1058" second="1080" amount="-1" />
+ <kerning first="1058" second="1082" amount="-1" />
+ <kerning first="1058" second="1083" amount="-1" />
+ <kerning first="1058" second="1084" amount="-1" />
+ <kerning first="1058" second="1086" amount="-1" />
+ <kerning first="1058" second="1088" amount="-1" />
+ <kerning first="1058" second="1089" amount="-1" />
+ <kerning first="1058" second="1091" amount="-1" />
+ <kerning first="1058" second="1093" amount="-1" />
+ <kerning first="1059" second="44" amount="-2" />
+ <kerning first="1059" second="46" amount="-3" />
+ <kerning first="1059" second="171" amount="-1" />
+ <kerning first="1059" second="187" amount="-1" />
+ <kerning first="1059" second="1040" amount="-2" />
+ <kerning first="1059" second="1044" amount="-1" />
+ <kerning first="1059" second="1051" amount="-1" />
+ <kerning first="1059" second="1060" amount="-1" />
+ <kerning first="1059" second="1074" amount="-1" />
+ <kerning first="1059" second="1075" amount="-1" />
+ <kerning first="1059" second="1076" amount="-1" />
+ <kerning first="1059" second="1077" amount="-1" />
+ <kerning first="1059" second="1079" amount="-1" />
+ <kerning first="1059" second="1080" amount="-1" />
+ <kerning first="1059" second="1081" amount="-1" />
+ <kerning first="1059" second="1082" amount="-1" />
+ <kerning first="1059" second="1083" amount="-1" />
+ <kerning first="1059" second="1084" amount="-1" />
+ <kerning first="1059" second="1085" amount="-1" />
+ <kerning first="1059" second="1086" amount="-1" />
+ <kerning first="1059" second="1087" amount="-1" />
+ <kerning first="1059" second="1088" amount="-1" />
+ <kerning first="1059" second="1089" amount="-1" />
+ <kerning first="1059" second="1094" amount="-1" />
+ <kerning first="1059" second="1096" amount="-1" />
+ <kerning first="1059" second="1097" amount="-1" />
+ <kerning first="1059" second="1102" amount="-1" />
+ <kerning first="1059" second="1103" amount="-1" />
+ <kerning first="1060" second="1044" amount="-1" />
+ <kerning first="1060" second="1051" amount="-1" />
+ <kerning first="1060" second="1058" amount="-1" />
+ <kerning first="1060" second="1059" amount="-1" />
+ <kerning first="1061" second="1057" amount="-1" />
+ <kerning first="1061" second="1060" amount="-1" />
+ <kerning first="1062" second="1072" amount="1" />
+ <kerning first="1066" second="8217" amount="-2" />
+ <kerning first="1066" second="1071" amount="-1" />
+ <kerning first="1068" second="8217" amount="-2" />
+ <kerning first="1068" second="1046" amount="-1" />
+ <kerning first="1068" second="1051" amount="-1" />
+ <kerning first="1068" second="1058" amount="-2" />
+ <kerning first="1068" second="1061" amount="-1" />
+ <kerning first="1068" second="1063" amount="-2" />
+ <kerning first="1068" second="1069" amount="-1" />
+ <kerning first="1068" second="1071" amount="-1" />
+ <kerning first="1069" second="1051" amount="-1" />
+ <kerning first="1070" second="1051" amount="-1" />
+ <kerning first="1070" second="1061" amount="-1" />
+ <kerning first="1074" second="1095" amount="-1" />
+ <kerning first="1075" second="44" amount="-2" />
+ <kerning first="1075" second="46" amount="-2" />
+ <kerning first="1075" second="1076" amount="-1" />
+ <kerning first="1079" second="1095" amount="-1" />
+ <kerning first="1090" second="44" amount="-2" />
+ <kerning first="1090" second="46" amount="-2" />
+ <kerning first="1091" second="44" amount="-2" />
+ <kerning first="1091" second="46" amount="-2" />
+ <kerning first="1100" second="1090" amount="-2" />
+ <kerning first="1100" second="1095" amount="-2" />
+ <kerning first="1102" second="1095" amount="-1" />
+ <kerning first="1118" second="44" amount="-2" />
+ <kerning first="1118" second="46" amount="-2" />
+ <kerning first="1168" second="44" amount="-1" />
+ <kerning first="1168" second="46" amount="-2" />
+ <kerning first="960" second="955" amount="-1" />
+ </kernings>
+</font>
diff --git a/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png
new file mode 100644
index 0000000..11d37da
--- /dev/null
+++ b/trunk/ardor3d-core/src/main/resources/com/ardor3d/ui/text/arial-24-bold-regular_00.png
Binary files differ
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java
new file mode 100644
index 0000000..e6f98e2
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestBounding.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import org.junit.Test;
+
+import com.ardor3d.math.Vector3;
+
+public class TestBounding {
+ @Test
+ public void testBoundingBoxMerge() throws Exception {
+ final BoundingBox obb = new BoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setXExtent(1);
+ obb.setYExtent(1);
+ obb.setZExtent(1);
+
+ // final ReadOnlyVector3 center = sceneBounds.getCenter();
+ // for (int i = 0; i < _corners.length; i++) {
+ // _corners[i].set(center);
+ // }
+ //
+ // if (sceneBounds instanceof BoundingBox) {
+ // final BoundingBox bbox = (BoundingBox) sceneBounds;
+ // bbox.getExtent(_extents);
+ // } else if (sceneBounds instanceof BoundingSphere) {
+ // final BoundingSphere bsphere = (BoundingSphere) sceneBounds;
+ // _extents.set(bsphere.getRadius(), bsphere.getRadius(), bsphere.getRadius());
+ // }
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java
new file mode 100644
index 0000000..347ff4e
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/bounding/TestRayBounding.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.bounding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.ardor3d.intersection.IntersectionRecord;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Vector3;
+
+public class TestRayBounding {
+ @Test
+ public void testRayAABBIntersection() throws Exception {
+ final BoundingBox obb = new BoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setXExtent(1);
+ obb.setYExtent(1);
+ obb.setZExtent(1);
+
+ Ray3 ray = new Ray3(new Vector3(2, -10, 0), Vector3.UNIT_Y);
+ assertFalse(obb.intersects(ray));
+ IntersectionRecord record = obb.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Quaternion rotation = new Quaternion();
+ rotation.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Z);
+ final Transform transform = new Transform();
+ transform.setRotation(rotation);
+ obb.transform(transform, obb);
+
+ ray = new Ray3(new Vector3(1, -10, 0), Vector3.UNIT_Y);
+ assertTrue(obb.intersects(ray));
+ record = obb.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+
+ @Test
+ public void testRayOBBIntersection() throws Exception {
+ final OrientedBoundingBox obb = new OrientedBoundingBox();
+ obb.setCenter(Vector3.ZERO);
+ obb.setExtent(Vector3.ONE);
+
+ Ray3 ray = new Ray3(new Vector3(1.2, -10, 0), Vector3.UNIT_Y);
+ assertFalse(obb.intersects(ray));
+ IntersectionRecord record = obb.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Quaternion rotation = new Quaternion();
+ rotation.fromAngleAxis(MathUtils.QUARTER_PI, Vector3.UNIT_Z);
+ final Transform transform = new Transform();
+ transform.setRotation(rotation);
+ obb.transform(transform, obb);
+
+ ray = new Ray3(new Vector3(1.2, -10, 0), Vector3.UNIT_Y);
+ assertTrue(obb.intersects(ray));
+ record = obb.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+
+ @Test
+ public void testRaySphereIntersection() throws Exception {
+ final BoundingSphere bs = new BoundingSphere();
+ bs.setCenter(Vector3.ZERO);
+ bs.setRadius(1);
+
+ final Ray3 ray = new Ray3(new Vector3(2, -3, 0), Vector3.UNIT_Y);
+ assertFalse(bs.intersects(ray));
+ IntersectionRecord record = bs.intersectsWhere(ray);
+ assertEquals(null, record);
+
+ final Transform transform = new Transform();
+ transform.setTranslation(2, 0, .5);
+ bs.transform(transform, bs);
+
+ assertTrue(bs.intersects(ray));
+ record = bs.intersectsWhere(ray);
+ assertEquals(2, record.getNumberOfIntersections());
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java
new file mode 100644
index 0000000..741d7de
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestKeyboardState.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.EnumSet;
+
+import org.junit.Test;
+
+public class TestKeyboardState {
+ KeyboardState ks1, ks2;
+
+ @Test
+ public void testKeysReleased1() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.E), KeyEvent.NOTHING);
+
+ final EnumSet<Key> released = ks2.getKeysReleasedSince(ks1);
+
+ assertEquals("1 key", 1, released.size());
+ assertTrue("b released", released.contains(Key.B));
+ }
+
+ @Test
+ public void testKeysReleased2() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+
+ final EnumSet<Key> released = ks2.getKeysReleasedSince(ks1);
+
+ assertEquals("2 key", 2, released.size());
+ assertTrue("a released", released.contains(Key.A));
+ assertTrue("b released", released.contains(Key.B));
+ }
+
+ @Test
+ public void testKeysPressed1() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.B), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("2 key", 2, pressed.size());
+ assertTrue("c pressed", pressed.contains(Key.C));
+ assertTrue("d pressed", pressed.contains(Key.D));
+ }
+
+ @Test
+ public void testKeysPressed2() throws Exception {
+ ks1 = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("2 key", 3, pressed.size());
+ assertTrue("a pressed", pressed.contains(Key.A));
+ assertTrue("c pressed", pressed.contains(Key.C));
+ assertTrue("d pressed", pressed.contains(Key.D));
+ }
+
+ @Test
+ public void testKeysPressed3() throws Exception {
+ ks1 = new KeyboardState(EnumSet.of(Key.A, Key.C, Key.D), KeyEvent.NOTHING);
+ ks2 = new KeyboardState(EnumSet.of(Key.A), KeyEvent.NOTHING);
+
+ final EnumSet<Key> pressed = ks2.getKeysPressedSince(ks1);
+
+ assertEquals("0 key", 0, pressed.size());
+ }
+
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java
new file mode 100644
index 0000000..2b769ea
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/TestPhysicalLayer.java
@@ -0,0 +1,389 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.PeekingIterator;
+
+public class TestPhysicalLayer {
+ KeyboardWrapper keyboardWrapper;
+ MouseWrapper mouseWrapper;
+ FocusWrapper focusWrapper;
+ ControllerWrapper controllerWrapper;
+ PhysicalLayer pl;
+
+ Object[] mocks;
+
+ List<KeyEvent> noKeys = new LinkedList<KeyEvent>();
+ List<KeyEvent> Adown = new LinkedList<KeyEvent>();
+ List<KeyEvent> AdownBdown = new LinkedList<KeyEvent>();
+ List<KeyEvent> AdownAup = new LinkedList<KeyEvent>();
+
+ List<ControllerEvent> nothing = new LinkedList<ControllerEvent>();
+
+ List<MouseState> buttonDown = new LinkedList<MouseState>();
+ List<MouseState> noMice = new LinkedList<MouseState>();
+
+ List<InputState> inputStates;
+ InputState is;
+
+ // @SuppressWarnings( { "unchecked" })
+ @Before
+ public void setup() throws Exception {
+ keyboardWrapper = createMock("KeyboardWrapper", KeyboardWrapper.class);
+ mouseWrapper = createMock("MouseWrapper", MouseWrapper.class);
+ controllerWrapper = createMock("ControllerWrapper", ControllerWrapper.class);
+ focusWrapper = createMock("FocusWrapper", FocusWrapper.class);
+
+ pl = new PhysicalLayer(keyboardWrapper, mouseWrapper, controllerWrapper, focusWrapper);
+
+ mocks = new Object[] { keyboardWrapper, mouseWrapper, controllerWrapper, focusWrapper };
+
+ Adown.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+
+ AdownBdown.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+ AdownBdown.add(new KeyEvent(Key.B, KeyState.DOWN, 'b'));
+
+ AdownAup.add(new KeyEvent(Key.A, KeyState.DOWN, 'a'));
+ AdownAup.add(new KeyEvent(Key.A, KeyState.UP, 'a'));
+
+ buttonDown.add(new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.DOWN, ButtonState.UP,
+ ButtonState.UP), null));
+ }
+
+ @After
+ public void verifyMocks() throws Exception {
+ verify(mocks);
+ }
+
+ @Test
+ public void testKeyboardBasic1() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ controllerWrapper.init();
+ focusWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(3);
+
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("1 state", 1, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testKeyboardBasic2() throws Exception {
+ final PeekingIterator<KeyEvent> adau = Iterators.peekingIterator(AdownAup.iterator());
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ controllerWrapper.init();
+ focusWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(adau);
+ expect(keyboardWrapper.getEvents()).andReturn(adau);
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(4);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(4);
+
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).times(2);
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testKeyboardBasic3() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ final PeekingIterator<KeyEvent> keyIterator = Iterators.peekingIterator(AdownBdown.iterator());
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(keyIterator).times(4);
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(4);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(4);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).times(2);
+
+ replay(mocks);
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertTrue("b down", is.getKeyboardState().isDown(Key.B));
+
+ pl.readState();
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("0 states", 0, inputStates.size());
+ }
+
+ @Test
+ public void testTooManyChanges1() throws Exception {
+ final PeekingIterator<KeyEvent> iter = new NeverEndingKeyIterator();
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(iter).atLeastOnce();
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).atLeastOnce();
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).atLeastOnce();
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ }
+
+ @Test
+ public void testTooManyChanges2() throws Exception {
+ final PeekingIterator<MouseState> iter = new NeverEndingMouseIterator();
+
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator())).atLeastOnce();
+ expect(mouseWrapper.getEvents()).andReturn(iter).atLeastOnce();
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).atLeastOnce();
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false).atLeastOnce();
+
+ replay(mocks);
+
+ pl.readState();
+ }
+
+ @Test
+ public void testLostFocus1() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(2);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(2);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ assertTrue("lost focus", InputState.LOST_FOCUS == inputStates.get(1));
+ }
+
+ @Test
+ public void testLostFocus2() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(Adown.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(3);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertTrue("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+
+ is = inputStates.get(1);
+
+ assertEquals("lost focus", InputState.LOST_FOCUS, is);
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ }
+
+ @Test
+ public void testLostFocus3() throws Exception {
+ keyboardWrapper.init();
+ mouseWrapper.init();
+ focusWrapper.init();
+ controllerWrapper.init();
+
+ expect(controllerWrapper.getBlankState()).andReturn(new ControllerState()).anyTimes();
+
+ expect(keyboardWrapper.getEvents()).andReturn(Iterators.peekingIterator(noKeys.iterator())).times(3);
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(buttonDown.iterator()));
+ expect(mouseWrapper.getEvents()).andReturn(Iterators.peekingIterator(noMice.iterator())).times(2);
+ expect(controllerWrapper.getEvents()).andReturn(Iterators.peekingIterator(nothing.iterator())).times(3);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(false);
+ expect(focusWrapper.getAndClearFocusLost()).andReturn(true);
+
+ replay(mocks);
+
+ pl.readState();
+ pl.readState();
+
+ inputStates = pl.drainAvailableStates();
+
+ assertEquals("2 states", 2, inputStates.size());
+
+ is = inputStates.get(0);
+
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ assertEquals("mb down", ButtonState.DOWN, is.getMouseState().getButtonState(MouseButton.LEFT));
+
+ is = inputStates.get(1);
+
+ assertEquals("lost focus", InputState.LOST_FOCUS, is);
+ assertFalse("a down", is.getKeyboardState().isDown(Key.A));
+ assertFalse("b down", is.getKeyboardState().isDown(Key.B));
+ assertEquals("mb up", ButtonState.UP, is.getMouseState().getButtonState(MouseButton.LEFT));
+ }
+
+ private static class NeverEndingKeyIterator extends AbstractIterator<KeyEvent> implements PeekingIterator<KeyEvent> {
+ final KeyEvent aUp = new KeyEvent(Key.A, KeyState.UP, 'a');
+ final KeyEvent aDown = new KeyEvent(Key.A, KeyState.DOWN, 'a');
+
+ int count = 0;
+
+ @Override
+ protected KeyEvent computeNext() {
+ count++;
+
+ if (count % 2 == 0) {
+ return aUp;
+ }
+
+ return aDown;
+ }
+ }
+
+ private static class NeverEndingMouseIterator extends AbstractIterator<MouseState> implements
+ PeekingIterator<MouseState> {
+ final MouseState m1 = new MouseState(0, 0, 0, 0, 0, null, null);
+ final MouseState m2 = new MouseState(0, 1, 2, 0, 0, null, null);
+
+ int count = 0;
+
+ @Override
+ protected MouseState computeNext() {
+ count++;
+
+ if (count % 2 == 0) {
+ return m1;
+ }
+
+ return m2;
+ }
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java
new file mode 100644
index 0000000..1003e11
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestLogicalLayer.java
@@ -0,0 +1,249 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+
+import java.util.EnumSet;
+import java.util.LinkedList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.KeyEvent;
+import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.PhysicalLayer;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests for the LogicalLayer
+ */
+public class TestLogicalLayer {
+ LogicalLayer ll;
+ PhysicalLayer pl;
+
+ TriggerAction ta1, ta2;
+
+ Predicate<TwoInputStates> p1;
+ Predicate<TwoInputStates> p2;
+
+ KeyboardState ks;
+ MouseState ms;
+ ControllerState cs;
+
+ Canvas canvas;
+
+ Object[] mocks;
+
+ @SuppressWarnings( { "unchecked" })
+ @Before
+ public void setup() throws Exception {
+ pl = createMock("Physicallayer", PhysicalLayer.class);
+
+ ta1 = createMock("TA1", TriggerAction.class);
+ ta2 = createMock("TA2", TriggerAction.class);
+
+ p1 = createMock("P1", Predicate.class);
+ p2 = createMock("P2", Predicate.class);
+
+ canvas = createMock("canvas", Canvas.class);
+
+ ll = new LogicalLayer();
+
+ ll.registerInput(canvas, pl);
+
+ ks = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ ms = new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.UP, ButtonState.UP, ButtonState.UP), null);
+ cs = new ControllerState();
+
+ mocks = new Object[] { pl, ta1, ta2, p1, p2, canvas };
+ }
+
+ @After
+ public void verifyMocks() throws Exception {
+ verify(mocks);
+ }
+
+ @Test
+ public void testTriggers1() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers2() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(state1, state2), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(new InputTrigger(p2, ta2));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers3() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state2), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+
+ replay(mocks);
+
+ final InputTrigger trigger2 = new InputTrigger(p2, ta2);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(trigger2);
+
+ ll.checkTriggers(tpf);
+ ll.deregisterTrigger(trigger2);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testTriggers4() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p2.apply(isA(TwoInputStates.class))).andReturn(true);
+ ta1.perform(canvas, new TwoInputStates(state1, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+ ta2.perform(canvas, new TwoInputStates(state1, state1), tpf);
+
+ replay(mocks);
+
+ final InputTrigger trigger2 = new InputTrigger(p2, ta2);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+ ll.registerTrigger(trigger2);
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+ }
+
+ @Test
+ public void testLostFocus() throws Exception {
+ final InputState state1 = new InputState(ks, ms, cs);
+ final InputState state2 = new InputState(ks, ms, cs);
+
+ final double tpf = 14;
+
+ final LinkedList<InputState> states1 = new LinkedList<InputState>();
+ final LinkedList<InputState> states2 = new LinkedList<InputState>();
+
+ states1.add(state1);
+ states2.add(InputState.LOST_FOCUS);
+ states2.add(state2);
+
+ pl.readState();
+ pl.readState();
+ expect(pl.drainAvailableStates()).andReturn(states1);
+ expect(pl.drainAvailableStates()).andReturn(states2);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(true);
+ expect(p1.apply(isA(TwoInputStates.class))).andReturn(false);
+ ta1.perform(canvas, new TwoInputStates(InputState.EMPTY, state1), tpf);
+
+ replay(mocks);
+
+ ll.registerTrigger(new InputTrigger(p1, ta1));
+
+ ll.checkTriggers(tpf);
+ ll.checkTriggers(tpf);
+
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java
new file mode 100644
index 0000000..8da445d
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/input/logical/TestStandardConditions.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.input.logical;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import java.util.EnumMap;
+import java.util.EnumSet;
+
+import org.junit.Test;
+
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.ControllerState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.KeyEvent;
+import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseState;
+
+public class TestStandardConditions {
+ final KeyboardState ks = new KeyboardState(EnumSet.noneOf(Key.class), KeyEvent.NOTHING);
+ final MouseState ms = new MouseState(0, 0, 0, 0, 0, MouseButton.makeMap(ButtonState.UP, ButtonState.UP,
+ ButtonState.UP), null);
+ final ControllerState cs = new ControllerState();
+ InputState is1, is2, is3, is4, is5;
+
+ KeyboardState aDown = new KeyboardState(EnumSet.of(Key.A), KeyEvent.NOTHING);
+ KeyboardState bDown = new KeyboardState(EnumSet.of(Key.B), KeyEvent.NOTHING);
+
+ EnumMap<MouseButton, ButtonState> bothUp = MouseButton.makeMap(ButtonState.UP, ButtonState.UP, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> upDown = MouseButton.makeMap(ButtonState.UP, ButtonState.DOWN, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> downUp = MouseButton.makeMap(ButtonState.DOWN, ButtonState.UP, ButtonState.UP);
+ EnumMap<MouseButton, ButtonState> bothDown = MouseButton
+ .makeMap(ButtonState.DOWN, ButtonState.DOWN, ButtonState.UP);
+
+ @Test
+ public void testKeyHeld1() throws Exception {
+ final KeyHeldCondition kh = new KeyHeldCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertTrue("down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertTrue("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyHeld2() throws Exception {
+ new KeyHeldCondition(null);
+ }
+
+ @Test
+ public void testKeyPressed() throws Exception {
+ final KeyPressedCondition kh = new KeyPressedCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertTrue("down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyPressedNull() throws Exception {
+ new KeyPressedCondition(null);
+ }
+
+ @Test
+ public void testKeyReleased() throws Exception {
+ final KeyReleasedCondition kh = new KeyReleasedCondition(Key.A);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(aDown, ms, cs);
+ is3 = new InputState(bDown, ms, cs);
+
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is1)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is2)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is1, is3)));
+ assertTrue("not down", kh.apply(new TwoInputStates(is2, is3)));
+ assertFalse("not down", kh.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", kh.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", kh.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", kh.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertFalse("empty4", kh.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testKeyReleasedNull() throws Exception {
+ new KeyReleasedCondition(null);
+ }
+
+ @Test
+ public void testMouseMove() throws Exception {
+ final MouseMovedCondition mm = TriggerConditions.mouseMoved();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, bothDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, bothDown, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertTrue("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertFalse("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertTrue("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertTrue("mm5", mm.apply(new TwoInputStates(is4, is5)));
+ assertFalse("mm6", mm.apply(new TwoInputStates(is2, is2)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is2)));
+ }
+
+ @Test
+ public void testMouseButton1() throws Exception {
+ final MouseButtonCondition mm = TriggerConditions.leftButtonDown();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, upDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, downUp, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertFalse("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertTrue("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertFalse("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertTrue("mm5", mm.apply(new TwoInputStates(is4, is5)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is3)));
+ }
+
+ @Test
+ public void testMouseButton2() throws Exception {
+ final MouseButtonCondition mm = TriggerConditions.rightButtonDown();
+
+ final MouseState ms2 = new MouseState(1, 0, 1, 0, 0, bothUp, null);
+ final MouseState ms3 = new MouseState(1, 0, 0, 0, 0, bothDown, null);
+ final MouseState ms4 = new MouseState(3, 1, 2, 1, 0, upDown, null);
+ final MouseState ms5 = new MouseState(3, 0, 0, -1, 0, downUp, null);
+
+ is1 = new InputState(ks, ms, cs);
+ is2 = new InputState(ks, ms2, cs);
+ is3 = new InputState(ks, ms3, cs);
+ is4 = new InputState(ks, ms4, cs);
+ is5 = new InputState(ks, ms5, cs);
+
+ assertFalse("mm1", mm.apply(new TwoInputStates(is1, is1)));
+ assertFalse("mm2", mm.apply(new TwoInputStates(is1, is2)));
+ assertTrue("mm3", mm.apply(new TwoInputStates(is2, is3)));
+ assertTrue("mm4", mm.apply(new TwoInputStates(is3, is4)));
+ assertFalse("mm5", mm.apply(new TwoInputStates(is4, is5)));
+
+ assertFalse("empty1", mm.apply(new TwoInputStates(InputState.EMPTY, InputState.EMPTY)));
+ assertFalse("empty2", mm.apply(new TwoInputStates(is1, InputState.EMPTY)));
+ assertFalse("empty3", mm.apply(new TwoInputStates(InputState.EMPTY, is1)));
+ assertTrue("empty4", mm.apply(new TwoInputStates(InputState.EMPTY, is3)));
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java
new file mode 100644
index 0000000..c0525b4
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/MockInputStream.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MockInputStream extends InputStream {
+ private int bytesAvailable = 0;
+ private boolean eof = false;
+
+ @Override
+ public int read() throws IOException {
+ while (true) {
+ final int result = returnSomething();
+
+ if (result != 0) {
+ return result;
+ }
+
+ try {
+ Thread.sleep(2);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ private synchronized int returnSomething() {
+ if (eof) {
+ return -1;
+ }
+
+ if (bytesAvailable > 0) {
+ bytesAvailable--;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ @Override
+ public synchronized int available() throws IOException {
+ return bytesAvailable;
+ }
+
+ public synchronized void addBytesAvailable(final int bytesAvailable) {
+ this.bytesAvailable += bytesAvailable;
+ }
+
+ public synchronized void setEof(final boolean eof) {
+ this.eof = eof;
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java
new file mode 100644
index 0000000..b779190
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianDataInput.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * These tests are fairly brittle, since they rely on the implementation of BufferedInputStream.
+ * It is necessary for the bytes available to always be larger than the buffer size of the buffered
+ * input stream for the tests to work. This size is currently 8192, but if it changes, or if the
+ * implementation changes, these tests can break.
+ */
+public class TestLittleEndianDataInput {
+ MockInputStream in;
+ byte[] array;
+ LittleEndianDataInput littleEndien;
+
+
+ @Before
+ public void setup() throws Exception {
+ in = new MockInputStream();
+
+ array = new byte[10];
+
+ littleEndien = new LittleEndianDataInput(in);
+ }
+
+
+ @Test
+ public void testReadFully1() throws Exception {
+ in.addBytesAvailable(11111);
+
+ littleEndien.readFully(array);
+
+ // not caring about whether the bytes were actually copied successfully in this test
+ }
+
+ @Test
+ public void testReadFully2() throws Exception {
+ in.addBytesAvailable(11240);
+
+ littleEndien.readFully(array, 0, 4);
+
+ // not caring about whether the bytes were actually copied successfully in this test
+ }
+
+ @Test
+ public void testReadFully3() throws Exception {
+ array = new byte[30003];
+
+ Thread testThread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ littleEndien.readFully(array);
+ } catch (IOException e) {
+ e.printStackTrace();
+ fail("ioexception");
+ }
+ }
+ });
+
+ testThread.start();
+
+ in.addBytesAvailable(10000);
+
+ assertTrue("still alive", testThread.isAlive());
+
+ Thread.sleep(6);
+
+ in.addBytesAvailable(10001);
+ assertTrue("still alive", testThread.isAlive());
+
+ in.addBytesAvailable(10002); // now the test thread can die
+
+ testThread.join();
+
+ // not caring about whether the bytes were actually copied successfully in this test
+ }
+
+ @Test (expected = EOFException.class)
+ public void testReadFully4() throws Exception {
+ in.setEof(true);
+
+ littleEndien.readFully(array);
+
+ // not caring about whether the bytes were actually copied successfully in this test
+ }
+}
diff --git a/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java
new file mode 100644
index 0000000..dee87f2
--- /dev/null
+++ b/trunk/ardor3d-core/src/test/java/com/ardor3d/util/TestLittleEndianRandomAccessDataInput.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2008-2012 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.util;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+
+import org.junit.Test;
+
+/**
+ * Some tests for our random access little endian data input
+ */
+public class TestLittleEndianRandomAccessDataInput {
+
+ @Test
+ public void testReadUint() throws Exception {
+ // test reading of uint vs int.
+ final byte[] data = new byte[4];
+ data[0] = (byte) 0xff;
+ data[1] = (byte) 0xff;
+ data[2] = (byte) 0xff;
+ data[3] = (byte) 0xff;
+
+ final ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ final LittleEndianRandomAccessDataInput littleEndien = new LittleEndianRandomAccessDataInput(bais);
+
+ final long val = littleEndien.readUnsignedInt();
+ assertTrue(val == 4294967295L);
+
+ littleEndien.seek(0);
+ final int val2 = littleEndien.readInt();
+ assertTrue(val2 == -1);
+ }
+}