aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com')
-rw-r--r--engine/src/core/com/jme3/animation/AnimChannel.java363
-rw-r--r--engine/src/core/com/jme3/animation/AnimControl.java377
-rw-r--r--engine/src/core/com/jme3/animation/AnimEventListener.java63
-rw-r--r--engine/src/core/com/jme3/animation/Animation.java205
-rw-r--r--engine/src/core/com/jme3/animation/AnimationFactory.java494
-rw-r--r--engine/src/core/com/jme3/animation/Bone.java628
-rw-r--r--engine/src/core/com/jme3/animation/BoneAnimation.java44
-rw-r--r--engine/src/core/com/jme3/animation/BoneTrack.java334
-rw-r--r--engine/src/core/com/jme3/animation/CompactArray.java270
-rw-r--r--engine/src/core/com/jme3/animation/CompactQuaternionArray.java100
-rw-r--r--engine/src/core/com/jme3/animation/CompactVector3Array.java99
-rw-r--r--engine/src/core/com/jme3/animation/LoopMode.java60
-rw-r--r--engine/src/core/com/jme3/animation/Pose.java126
-rw-r--r--engine/src/core/com/jme3/animation/PoseTrack.java186
-rw-r--r--engine/src/core/com/jme3/animation/Skeleton.java297
-rw-r--r--engine/src/core/com/jme3/animation/SkeletonControl.java549
-rw-r--r--engine/src/core/com/jme3/animation/SpatialAnimation.java11
-rw-r--r--engine/src/core/com/jme3/animation/SpatialTrack.java242
-rw-r--r--engine/src/core/com/jme3/animation/Track.java63
-rw-r--r--engine/src/core/com/jme3/animation/package.html78
-rw-r--r--engine/src/core/com/jme3/app/AppTask.java167
-rw-r--r--engine/src/core/com/jme3/app/Application.java642
-rw-r--r--engine/src/core/com/jme3/app/DebugKeysAppState.java117
-rw-r--r--engine/src/core/com/jme3/app/FlyCamAppState.java94
-rw-r--r--engine/src/core/com/jme3/app/SimpleApplication.java277
-rw-r--r--engine/src/core/com/jme3/app/StatsAppState.java208
-rw-r--r--engine/src/core/com/jme3/app/StatsView.java132
-rw-r--r--engine/src/core/com/jme3/app/package.html80
-rw-r--r--engine/src/core/com/jme3/app/state/AbstractAppState.java89
-rw-r--r--engine/src/core/com/jme3/app/state/AppState.java119
-rw-r--r--engine/src/core/com/jme3/app/state/AppStateManager.java288
-rw-r--r--engine/src/core/com/jme3/app/state/package.html15
-rw-r--r--engine/src/core/com/jme3/asset/Asset.java73
-rw-r--r--engine/src/core/com/jme3/asset/AssetCache.java131
-rw-r--r--engine/src/core/com/jme3/asset/AssetConfig.java138
-rw-r--r--engine/src/core/com/jme3/asset/AssetEventListener.java77
-rw-r--r--engine/src/core/com/jme3/asset/AssetInfo.java77
-rw-r--r--engine/src/core/com/jme3/asset/AssetKey.java203
-rw-r--r--engine/src/core/com/jme3/asset/AssetLoadException.java17
-rw-r--r--engine/src/core/com/jme3/asset/AssetLoader.java54
-rw-r--r--engine/src/core/com/jme3/asset/AssetLocator.java60
-rw-r--r--engine/src/core/com/jme3/asset/AssetManager.java265
-rw-r--r--engine/src/core/com/jme3/asset/AssetNotFoundException.java17
-rw-r--r--engine/src/core/com/jme3/asset/Desktop.cfg22
-rw-r--r--engine/src/core/com/jme3/asset/DesktopAssetManager.java421
-rw-r--r--engine/src/core/com/jme3/asset/ImplHandler.java209
-rw-r--r--engine/src/core/com/jme3/asset/MaterialKey.java29
-rw-r--r--engine/src/core/com/jme3/asset/ModelKey.java61
-rw-r--r--engine/src/core/com/jme3/asset/TextureKey.java190
-rw-r--r--engine/src/core/com/jme3/asset/ThreadingManager.java103
-rw-r--r--engine/src/core/com/jme3/asset/package.html37
-rw-r--r--engine/src/core/com/jme3/audio/AudioBuffer.java120
-rw-r--r--engine/src/core/com/jme3/audio/AudioContext.java51
-rw-r--r--engine/src/core/com/jme3/audio/AudioData.java110
-rw-r--r--engine/src/core/com/jme3/audio/AudioKey.java137
-rw-r--r--engine/src/core/com/jme3/audio/AudioNode.java810
-rw-r--r--engine/src/core/com/jme3/audio/AudioParam.java19
-rw-r--r--engine/src/core/com/jme3/audio/AudioRenderer.java83
-rw-r--r--engine/src/core/com/jme3/audio/AudioStream.java204
-rw-r--r--engine/src/core/com/jme3/audio/Environment.java256
-rw-r--r--engine/src/core/com/jme3/audio/Filter.java73
-rw-r--r--engine/src/core/com/jme3/audio/Listener.java114
-rw-r--r--engine/src/core/com/jme3/audio/ListenerParam.java40
-rw-r--r--engine/src/core/com/jme3/audio/LowPassFilter.java100
-rw-r--r--engine/src/core/com/jme3/audio/SeekableStream.java15
-rw-r--r--engine/src/core/com/jme3/bounding/BoundingBox.java977
-rw-r--r--engine/src/core/com/jme3/bounding/BoundingSphere.java858
-rw-r--r--engine/src/core/com/jme3/bounding/BoundingVolume.java329
-rw-r--r--engine/src/core/com/jme3/bounding/Intersection.java284
-rw-r--r--engine/src/core/com/jme3/bounding/OrientedBoundingBox.java1522
-rw-r--r--engine/src/core/com/jme3/cinematic/Cinematic.java381
-rw-r--r--engine/src/core/com/jme3/cinematic/KeyFrame.java85
-rw-r--r--engine/src/core/com/jme3/cinematic/MotionPath.java371
-rw-r--r--engine/src/core/com/jme3/cinematic/MotionPathListener.java50
-rw-r--r--engine/src/core/com/jme3/cinematic/PlayState.java48
-rw-r--r--engine/src/core/com/jme3/cinematic/TimeLine.java120
-rw-r--r--engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java320
-rw-r--r--engine/src/core/com/jme3/cinematic/events/AnimationTrack.java175
-rw-r--r--engine/src/core/com/jme3/cinematic/events/CinematicEvent.java143
-rw-r--r--engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java17
-rw-r--r--engine/src/core/com/jme3/cinematic/events/MotionTrack.java440
-rw-r--r--engine/src/core/com/jme3/cinematic/events/PositionTrack.java122
-rw-r--r--engine/src/core/com/jme3/cinematic/events/RotationTrack.java126
-rw-r--r--engine/src/core/com/jme3/cinematic/events/ScaleTrack.java121
-rw-r--r--engine/src/core/com/jme3/cinematic/events/SoundTrack.java183
-rw-r--r--engine/src/core/com/jme3/collision/Collidable.java53
-rw-r--r--engine/src/core/com/jme3/collision/CollisionResult.java138
-rw-r--r--engine/src/core/com/jme3/collision/CollisionResults.java136
-rw-r--r--engine/src/core/com/jme3/collision/MotionAllowedListener.java48
-rw-r--r--engine/src/core/com/jme3/collision/SweepSphere.java440
-rw-r--r--engine/src/core/com/jme3/collision/UnsupportedCollisionException.java60
-rw-r--r--engine/src/core/com/jme3/collision/bih/BIHNode.java430
-rw-r--r--engine/src/core/com/jme3/collision/bih/BIHTree.java482
-rw-r--r--engine/src/core/com/jme3/collision/bih/BIHTriangle.java111
-rw-r--r--engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java63
-rw-r--r--engine/src/core/com/jme3/effect/Particle.java94
-rw-r--r--engine/src/core/com/jme3/effect/ParticleComparator.java77
-rw-r--r--engine/src/core/com/jme3/effect/ParticleEmitter.java1206
-rw-r--r--engine/src/core/com/jme3/effect/ParticleMesh.java86
-rw-r--r--engine/src/core/com/jme3/effect/ParticlePointMesh.java166
-rw-r--r--engine/src/core/com/jme3/effect/ParticleTriMesh.java284
-rw-r--r--engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java92
-rw-r--r--engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java55
-rw-r--r--engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java142
-rw-r--r--engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java61
-rw-r--r--engine/src/core/com/jme3/effect/package.html19
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java118
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java63
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java97
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java158
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java96
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterShape.java64
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java117
-rw-r--r--engine/src/core/com/jme3/export/FormatVersion.java22
-rw-r--r--engine/src/core/com/jme3/export/InputCapsule.java160
-rw-r--r--engine/src/core/com/jme3/export/JmeExporter.java74
-rw-r--r--engine/src/core/com/jme3/export/JmeImporter.java49
-rw-r--r--engine/src/core/com/jme3/export/OutputCapsule.java158
-rw-r--r--engine/src/core/com/jme3/export/ReadListener.java39
-rw-r--r--engine/src/core/com/jme3/export/Savable.java46
-rw-r--r--engine/src/core/com/jme3/export/SavableClassUtil.java205
-rw-r--r--engine/src/core/com/jme3/font/BitmapCharacter.java199
-rw-r--r--engine/src/core/com/jme3/font/BitmapCharacterSet.java223
-rw-r--r--engine/src/core/com/jme3/font/BitmapFont.java286
-rw-r--r--engine/src/core/com/jme3/font/BitmapText.java361
-rw-r--r--engine/src/core/com/jme3/font/BitmapTextPage.java197
-rw-r--r--engine/src/core/com/jme3/font/ColorTags.java91
-rw-r--r--engine/src/core/com/jme3/font/Kerning.java74
-rw-r--r--engine/src/core/com/jme3/font/LetterQuad.java496
-rw-r--r--engine/src/core/com/jme3/font/Letters.java331
-rw-r--r--engine/src/core/com/jme3/font/LineWrapMode.java11
-rw-r--r--engine/src/core/com/jme3/font/Rectangle.java65
-rw-r--r--engine/src/core/com/jme3/font/StringBlock.java200
-rw-r--r--engine/src/core/com/jme3/input/ChaseCamera.java875
-rw-r--r--engine/src/core/com/jme3/input/FlyByCamera.java364
-rw-r--r--engine/src/core/com/jme3/input/Input.java82
-rw-r--r--engine/src/core/com/jme3/input/InputManager.java881
-rw-r--r--engine/src/core/com/jme3/input/JoyInput.java66
-rw-r--r--engine/src/core/com/jme3/input/Joystick.java136
-rw-r--r--engine/src/core/com/jme3/input/KeyInput.java543
-rw-r--r--engine/src/core/com/jme3/input/KeyNames.java153
-rw-r--r--engine/src/core/com/jme3/input/MouseInput.java83
-rw-r--r--engine/src/core/com/jme3/input/RawInputListener.java99
-rw-r--r--engine/src/core/com/jme3/input/TouchInput.java92
-rw-r--r--engine/src/core/com/jme3/input/controls/ActionListener.java58
-rw-r--r--engine/src/core/com/jme3/input/controls/AnalogListener.java53
-rw-r--r--engine/src/core/com/jme3/input/controls/InputListener.java42
-rw-r--r--engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java77
-rw-r--r--engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java73
-rw-r--r--engine/src/core/com/jme3/input/controls/KeyTrigger.java72
-rw-r--r--engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java90
-rw-r--r--engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java79
-rw-r--r--engine/src/core/com/jme3/input/controls/TouchListener.java49
-rw-r--r--engine/src/core/com/jme3/input/controls/TouchTrigger.java73
-rw-r--r--engine/src/core/com/jme3/input/controls/Trigger.java52
-rw-r--r--engine/src/core/com/jme3/input/controls/package.html19
-rw-r--r--engine/src/core/com/jme3/input/dummy/DummyInput.java78
-rw-r--r--engine/src/core/com/jme3/input/dummy/DummyKeyInput.java52
-rw-r--r--engine/src/core/com/jme3/input/dummy/DummyMouseInput.java54
-rw-r--r--engine/src/core/com/jme3/input/dummy/package.html14
-rw-r--r--engine/src/core/com/jme3/input/event/InputEvent.java84
-rw-r--r--engine/src/core/com/jme3/input/event/JoyAxisEvent.java85
-rw-r--r--engine/src/core/com/jme3/input/event/JoyButtonEvent.java89
-rw-r--r--engine/src/core/com/jme3/input/event/KeyInputEvent.java117
-rw-r--r--engine/src/core/com/jme3/input/event/MouseButtonEvent.java111
-rw-r--r--engine/src/core/com/jme3/input/event/MouseMotionEvent.java110
-rw-r--r--engine/src/core/com/jme3/input/event/TouchEvent.java209
-rw-r--r--engine/src/core/com/jme3/input/event/package.html13
-rw-r--r--engine/src/core/com/jme3/input/package.html38
-rw-r--r--engine/src/core/com/jme3/light/AmbientLight.java26
-rw-r--r--engine/src/core/com/jme3/light/DirectionalLight.java104
-rw-r--r--engine/src/core/com/jme3/light/Light.java210
-rw-r--r--engine/src/core/com/jme3/light/LightList.java336
-rw-r--r--engine/src/core/com/jme3/light/PointLight.java157
-rw-r--r--engine/src/core/com/jme3/light/SpotLight.java218
-rw-r--r--engine/src/core/com/jme3/light/package.html23
-rw-r--r--engine/src/core/com/jme3/material/FixedFuncBinding.java80
-rw-r--r--engine/src/core/com/jme3/material/MatParam.java353
-rw-r--r--engine/src/core/com/jme3/material/MatParamTexture.java67
-rw-r--r--engine/src/core/com/jme3/material/Material.java1152
-rw-r--r--engine/src/core/com/jme3/material/MaterialDef.java190
-rw-r--r--engine/src/core/com/jme3/material/MaterialList.java44
-rw-r--r--engine/src/core/com/jme3/material/RenderState.java1070
-rw-r--r--engine/src/core/com/jme3/material/Technique.java261
-rw-r--r--engine/src/core/com/jme3/material/TechniqueDef.java396
-rw-r--r--engine/src/core/com/jme3/material/package.html58
-rw-r--r--engine/src/core/com/jme3/math/AbstractTriangle.java49
-rw-r--r--engine/src/core/com/jme3/math/ColorRGBA.java549
-rw-r--r--engine/src/core/com/jme3/math/CurveAndSurfaceMath.java133
-rw-r--r--engine/src/core/com/jme3/math/Eigen3f.java421
-rw-r--r--engine/src/core/com/jme3/math/FastMath.java987
-rw-r--r--engine/src/core/com/jme3/math/Line.java239
-rw-r--r--engine/src/core/com/jme3/math/LineSegment.java631
-rw-r--r--engine/src/core/com/jme3/math/Matrix3f.java1387
-rw-r--r--engine/src/core/com/jme3/math/Matrix4f.java2305
-rw-r--r--engine/src/core/com/jme3/math/Plane.java284
-rw-r--r--engine/src/core/com/jme3/math/Quaternion.java1345
-rw-r--r--engine/src/core/com/jme3/math/Ray.java521
-rw-r--r--engine/src/core/com/jme3/math/Rectangle.java197
-rw-r--r--engine/src/core/com/jme3/math/Ring.java233
-rw-r--r--engine/src/core/com/jme3/math/Spline.java447
-rw-r--r--engine/src/core/com/jme3/math/Transform.java318
-rw-r--r--engine/src/core/com/jme3/math/Triangle.java302
-rw-r--r--engine/src/core/com/jme3/math/Vector2f.java757
-rw-r--r--engine/src/core/com/jme3/math/Vector3f.java1061
-rw-r--r--engine/src/core/com/jme3/math/Vector4f.java1003
-rw-r--r--engine/src/core/com/jme3/math/package.html53
-rw-r--r--engine/src/core/com/jme3/post/Filter.java433
-rw-r--r--engine/src/core/com/jme3/post/FilterPostProcessor.java500
-rw-r--r--engine/src/core/com/jme3/post/HDRRenderer.java419
-rw-r--r--engine/src/core/com/jme3/post/PreDepthProcessor.java100
-rw-r--r--engine/src/core/com/jme3/post/SceneProcessor.java94
-rw-r--r--engine/src/core/com/jme3/post/package.html24
-rw-r--r--engine/src/core/com/jme3/renderer/Camera.java1436
-rw-r--r--engine/src/core/com/jme3/renderer/Caps.java360
-rw-r--r--engine/src/core/com/jme3/renderer/GL1Renderer.java26
-rw-r--r--engine/src/core/com/jme3/renderer/IDList.java121
-rw-r--r--engine/src/core/com/jme3/renderer/RenderContext.java319
-rw-r--r--engine/src/core/com/jme3/renderer/RenderManager.java1170
-rw-r--r--engine/src/core/com/jme3/renderer/Renderer.java305
-rw-r--r--engine/src/core/com/jme3/renderer/RendererException.java49
-rw-r--r--engine/src/core/com/jme3/renderer/Statistics.java255
-rw-r--r--engine/src/core/com/jme3/renderer/ViewPort.java363
-rw-r--r--engine/src/core/com/jme3/renderer/package.html38
-rw-r--r--engine/src/core/com/jme3/renderer/queue/GeometryComparator.java53
-rw-r--r--engine/src/core/com/jme3/renderer/queue/GeometryList.java143
-rw-r--r--engine/src/core/com/jme3/renderer/queue/GuiComparator.java60
-rw-r--r--engine/src/core/com/jme3/renderer/queue/NullComparator.java51
-rw-r--r--engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java96
-rw-r--r--engine/src/core/com/jme3/renderer/queue/RenderQueue.java377
-rw-r--r--engine/src/core/com/jme3/renderer/queue/TransparentComparator.java102
-rw-r--r--engine/src/core/com/jme3/scene/AssetLinkNode.java185
-rw-r--r--engine/src/core/com/jme3/scene/BatchNode.java653
-rw-r--r--engine/src/core/com/jme3/scene/CameraNode.java93
-rw-r--r--engine/src/core/com/jme3/scene/CollisionData.java52
-rw-r--r--engine/src/core/com/jme3/scene/Geometry.java565
-rw-r--r--engine/src/core/com/jme3/scene/LightNode.java93
-rw-r--r--engine/src/core/com/jme3/scene/Mesh.java1315
-rw-r--r--engine/src/core/com/jme3/scene/Node.java643
-rw-r--r--engine/src/core/com/jme3/scene/SceneGraphVisitor.java16
-rw-r--r--engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java35
-rw-r--r--engine/src/core/com/jme3/scene/SimpleBatchNode.java56
-rw-r--r--engine/src/core/com/jme3/scene/Spatial.java1478
-rw-r--r--engine/src/core/com/jme3/scene/UserData.java156
-rw-r--r--engine/src/core/com/jme3/scene/VertexBuffer.java1025
-rw-r--r--engine/src/core/com/jme3/scene/control/AbstractControl.java112
-rw-r--r--engine/src/core/com/jme3/scene/control/AreaUtils.java85
-rw-r--r--engine/src/core/com/jme3/scene/control/BillboardControl.java307
-rw-r--r--engine/src/core/com/jme3/scene/control/CameraControl.java159
-rw-r--r--engine/src/core/com/jme3/scene/control/Control.java90
-rw-r--r--engine/src/core/com/jme3/scene/control/LightControl.java189
-rw-r--r--engine/src/core/com/jme3/scene/control/LodControl.java204
-rw-r--r--engine/src/core/com/jme3/scene/control/UpdateControl.java96
-rw-r--r--engine/src/core/com/jme3/scene/control/package.html17
-rw-r--r--engine/src/core/com/jme3/scene/debug/Arrow.java142
-rw-r--r--engine/src/core/com/jme3/scene/debug/Grid.java105
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java80
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonPoints.java80
-rw-r--r--engine/src/core/com/jme3/scene/debug/SkeletonWire.java109
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireBox.java108
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireFrustum.java88
-rw-r--r--engine/src/core/com/jme3/scene/debug/WireSphere.java159
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexBuffer.java96
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java71
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java70
-rw-r--r--engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java70
-rw-r--r--engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java106
-rw-r--r--engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java86
-rw-r--r--engine/src/core/com/jme3/scene/mesh/package.html25
-rw-r--r--engine/src/core/com/jme3/scene/package.html26
-rw-r--r--engine/src/core/com/jme3/scene/shape/AbstractBox.java211
-rw-r--r--engine/src/core/com/jme3/scene/shape/Box.java176
-rw-r--r--engine/src/core/com/jme3/scene/shape/Curve.java271
-rw-r--r--engine/src/core/com/jme3/scene/shape/Cylinder.java420
-rw-r--r--engine/src/core/com/jme3/scene/shape/Dome.java339
-rw-r--r--engine/src/core/com/jme3/scene/shape/Line.java123
-rw-r--r--engine/src/core/com/jme3/scene/shape/PQTorus.java239
-rw-r--r--engine/src/core/com/jme3/scene/shape/Quad.java130
-rw-r--r--engine/src/core/com/jme3/scene/shape/Sphere.java420
-rw-r--r--engine/src/core/com/jme3/scene/shape/StripBox.java191
-rw-r--r--engine/src/core/com/jme3/scene/shape/Surface.java283
-rw-r--r--engine/src/core/com/jme3/scene/shape/Torus.java255
-rw-r--r--engine/src/core/com/jme3/shader/Attribute.java42
-rw-r--r--engine/src/core/com/jme3/shader/DefineList.java180
-rw-r--r--engine/src/core/com/jme3/shader/Shader.java443
-rw-r--r--engine/src/core/com/jme3/shader/ShaderKey.java126
-rw-r--r--engine/src/core/com/jme3/shader/ShaderUtils.java50
-rw-r--r--engine/src/core/com/jme3/shader/ShaderVariable.java83
-rw-r--r--engine/src/core/com/jme3/shader/Uniform.java432
-rw-r--r--engine/src/core/com/jme3/shader/UniformBinding.java162
-rw-r--r--engine/src/core/com/jme3/shader/VarType.java81
-rw-r--r--engine/src/core/com/jme3/shadow/BasicShadowRenderer.java216
-rw-r--r--engine/src/core/com/jme3/shadow/PssmShadowRenderer.java554
-rw-r--r--engine/src/core/com/jme3/shadow/PssmShadowUtil.java81
-rw-r--r--engine/src/core/com/jme3/shadow/ShadowCamera.java75
-rw-r--r--engine/src/core/com/jme3/shadow/ShadowUtil.java486
-rw-r--r--engine/src/core/com/jme3/system/Annotations.java72
-rw-r--r--engine/src/core/com/jme3/system/AppSettings.java726
-rw-r--r--engine/src/core/com/jme3/system/JmeContext.java184
-rw-r--r--engine/src/core/com/jme3/system/JmeSystem.java136
-rw-r--r--engine/src/core/com/jme3/system/JmeSystemDelegate.java140
-rw-r--r--engine/src/core/com/jme3/system/JmeVersion.java5
-rw-r--r--engine/src/core/com/jme3/system/NanoTimer.java92
-rw-r--r--engine/src/core/com/jme3/system/NullContext.java230
-rw-r--r--engine/src/core/com/jme3/system/NullRenderer.java152
-rw-r--r--engine/src/core/com/jme3/system/Platform.java65
-rw-r--r--engine/src/core/com/jme3/system/SystemListener.java99
-rw-r--r--engine/src/core/com/jme3/system/Timer.java94
-rw-r--r--engine/src/core/com/jme3/texture/FrameBuffer.java460
-rw-r--r--engine/src/core/com/jme3/texture/Image.java790
-rw-r--r--engine/src/core/com/jme3/texture/Texture.java613
-rw-r--r--engine/src/core/com/jme3/texture/Texture2D.java221
-rw-r--r--engine/src/core/com/jme3/texture/Texture3D.java224
-rw-r--r--engine/src/core/com/jme3/texture/TextureArray.java113
-rw-r--r--engine/src/core/com/jme3/texture/TextureCubeMap.java206
-rw-r--r--engine/src/core/com/jme3/ui/Picture.java160
-rw-r--r--engine/src/core/com/jme3/util/BufferUtils.java1196
-rw-r--r--engine/src/core/com/jme3/util/IntMap.java308
-rw-r--r--engine/src/core/com/jme3/util/JmeFormatter.java93
-rw-r--r--engine/src/core/com/jme3/util/ListMap.java317
-rw-r--r--engine/src/core/com/jme3/util/LittleEndien.java160
-rw-r--r--engine/src/core/com/jme3/util/NativeObject.java172
-rw-r--r--engine/src/core/com/jme3/util/NativeObjectManager.java148
-rw-r--r--engine/src/core/com/jme3/util/PlaceholderAssets.java72
-rw-r--r--engine/src/core/com/jme3/util/SafeArrayList.java402
-rw-r--r--engine/src/core/com/jme3/util/SkyFactory.java214
-rw-r--r--engine/src/core/com/jme3/util/SortUtil.java352
-rw-r--r--engine/src/core/com/jme3/util/TangentBinormalGenerator.java739
-rw-r--r--engine/src/core/com/jme3/util/TempVars.java221
-rw-r--r--engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java92
-rw-r--r--engine/src/core/com/jme3/util/blockparser/Statement.java61
-rw-r--r--engine/src/core/com/jme3/util/xml/SAXUtil.java140
332 files changed, 80657 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/animation/AnimChannel.java b/engine/src/core/com/jme3/animation/AnimChannel.java
new file mode 100644
index 0000000..a2fae7c
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimChannel.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.util.TempVars;
+import java.util.BitSet;
+
+/**
+ * <code>AnimChannel</code> provides controls, such as play, pause,
+ * fast forward, etc, for an animation. The animation
+ * channel may influence the entire model or specific bones of the model's
+ * skeleton. A single model may have multiple animation channels influencing
+ * various parts of its body. For example, a character model may have an
+ * animation channel for its feet, and another for its torso, and
+ * the animations for each channel are controlled independently.
+ *
+ * @author Kirill Vainer
+ */
+public final class AnimChannel {
+
+ private static final float DEFAULT_BLEND_TIME = 0.15f;
+
+ private AnimControl control;
+
+ private BitSet affectedBones;
+
+ private Animation animation;
+ private Animation blendFrom;
+ private float time;
+ private float speed;
+ private float timeBlendFrom;
+ private float speedBlendFrom;
+
+ private LoopMode loopMode, loopModeBlendFrom;
+
+ private float blendAmount = 1f;
+ private float blendRate = 0;
+
+ private static float clampWrapTime(float t, float max, LoopMode loopMode){
+ if (max == Float.POSITIVE_INFINITY)
+ return t;
+
+ if (t < 0f){
+ //float tMod = -(-t % max);
+ switch (loopMode){
+ case DontLoop:
+ return 0;
+ case Cycle:
+ return t;
+ case Loop:
+ return max - t;
+ }
+ }else if (t > max){
+ switch (loopMode){
+ case DontLoop:
+ return max;
+ case Cycle:
+ return /*-max;*/-(2f * max - t);
+ case Loop:
+ return t - max;
+ }
+ }
+
+ return t;
+ }
+
+ AnimChannel(AnimControl control){
+ this.control = control;
+ }
+
+ /**
+ * Returns the parent control of this AnimChannel.
+ *
+ * @return the parent control of this AnimChannel.
+ * @see AnimControl
+ */
+ public AnimControl getControl() {
+ return control;
+ }
+
+ /**
+ * @return The name of the currently playing animation, or null if
+ * none is assigned.
+ *
+ * @see AnimChannel#setAnim(java.lang.String)
+ */
+ public String getAnimationName() {
+ return animation != null ? animation.getName() : null;
+ }
+
+ /**
+ * @return The loop mode currently set for the animation. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode)
+ */
+ public LoopMode getLoopMode() {
+ return loopMode;
+ }
+
+ /**
+ * @param loopMode Set the loop mode for the channel. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ */
+ public void setLoopMode(LoopMode loopMode) {
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * @return The speed that is assigned to the animation channel. The speed
+ * is a scale value starting from 0.0, at 1.0 the animation will play
+ * at its default speed.
+ *
+ * @see AnimChannel#setSpeed(float)
+ */
+ public float getSpeed() {
+ return speed;
+ }
+
+ /**
+ * @param speed Set the speed of the animation channel. The speed
+ * is a scale value starting from 0.0, at 1.0 the animation will play
+ * at its default speed.
+ */
+ public void setSpeed(float speed) {
+ this.speed = speed;
+ }
+
+ /**
+ * @return The time of the currently playing animation. The time
+ * starts at 0 and continues on until getAnimMaxTime().
+ *
+ * @see AnimChannel#setTime(float)
+ */
+ public float getTime() {
+ return time;
+ }
+
+ /**
+ * @param time Set the time of the currently playing animation, the time
+ * is clamped from 0 to {@link #getAnimMaxTime()}.
+ */
+ public void setTime(float time) {
+ this.time = FastMath.clamp(time, 0, getAnimMaxTime());
+ }
+
+ /**
+ * @return The length of the currently playing animation, or zero
+ * if no animation is playing.
+ *
+ * @see AnimChannel#getTime()
+ */
+ public float getAnimMaxTime(){
+ return animation != null ? animation.getLength() : 0f;
+ }
+
+ /**
+ * Set the current animation that is played by this AnimChannel.
+ * <p>
+ * This resets the time to zero, and optionally blends the animation
+ * over <code>blendTime</code> seconds with the currently playing animation.
+ * Notice that this method will reset the control's speed to 1.0.
+ *
+ * @param name The name of the animation to play
+ * @param blendTime The blend time over which to blend the new animation
+ * with the old one. If zero, then no blending will occur and the new
+ * animation will be applied instantly.
+ */
+ public void setAnim(String name, float blendTime){
+ if (name == null)
+ throw new NullPointerException();
+
+ if (blendTime < 0f)
+ throw new IllegalArgumentException("blendTime cannot be less than zero");
+
+ Animation anim = control.animationMap.get(name);
+ if (anim == null)
+ throw new IllegalArgumentException("Cannot find animation named: '"+name+"'");
+
+ control.notifyAnimChange(this, name);
+
+ if (animation != null && blendTime > 0f){
+ // activate blending
+ blendFrom = animation;
+ timeBlendFrom = time;
+ speedBlendFrom = speed;
+ loopModeBlendFrom = loopMode;
+ blendAmount = 0f;
+ blendRate = 1f / blendTime;
+ }
+
+ animation = anim;
+ time = 0;
+ speed = 1f;
+ loopMode = LoopMode.Loop;
+ }
+
+ /**
+ * Set the current animation that is played by this AnimChannel.
+ * <p>
+ * See {@link #setAnim(java.lang.String, float)}.
+ * The blendTime argument by default is 150 milliseconds.
+ *
+ * @param name The name of the animation to play
+ */
+ public void setAnim(String name){
+ setAnim(name, DEFAULT_BLEND_TIME);
+ }
+
+ /**
+ * Add all the bones of the model's skeleton to be
+ * influenced by this animation channel.
+ */
+ public void addAllBones() {
+ affectedBones = null;
+ }
+
+ /**
+ * Add a single bone to be influenced by this animation channel.
+ */
+ public void addBone(String name) {
+ addBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add a single bone to be influenced by this animation channel.
+ */
+ public void addBone(Bone bone) {
+ int boneIndex = control.getSkeleton().getBoneIndex(bone);
+ if(affectedBones == null) {
+ affectedBones = new BitSet(control.getSkeleton().getBoneCount());
+ }
+ affectedBones.set(boneIndex);
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel starting from the
+ * given bone name and going toward the root bone.
+ */
+ public void addToRootBone(String name) {
+ addToRootBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel starting from the
+ * given bone and going toward the root bone.
+ */
+ public void addToRootBone(Bone bone) {
+ addBone(bone);
+ while (bone.getParent() != null) {
+ bone = bone.getParent();
+ addBone(bone);
+ }
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel, starting
+ * from the given named bone and going toward its children.
+ */
+ public void addFromRootBone(String name) {
+ addFromRootBone(control.getSkeleton().getBone(name));
+ }
+
+ /**
+ * Add bones to be influenced by this animation channel, starting
+ * from the given bone and going toward its children.
+ */
+ public void addFromRootBone(Bone bone) {
+ addBone(bone);
+ if (bone.getChildren() == null)
+ return;
+ for (Bone childBone : bone.getChildren()) {
+ addBone(childBone);
+ addFromRootBone(childBone);
+ }
+ }
+
+ BitSet getAffectedBones(){
+ return affectedBones;
+ }
+
+ void update(float tpf, TempVars vars) {
+ if (animation == null)
+ return;
+
+ if (blendFrom != null){
+ blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
+ //blendFrom.setTime(timeBlendFrom, control.skeleton, 1f - blendAmount, affectedBones);
+ timeBlendFrom += tpf * speedBlendFrom;
+ timeBlendFrom = clampWrapTime(timeBlendFrom,
+ blendFrom.getLength(),
+ loopModeBlendFrom);
+ if (timeBlendFrom < 0){
+ timeBlendFrom = -timeBlendFrom;
+ speedBlendFrom = -speedBlendFrom;
+ }
+
+ blendAmount += tpf * blendRate;
+ if (blendAmount > 1f){
+ blendAmount = 1f;
+ blendFrom = null;
+ }
+ }
+
+ animation.setTime(time, blendAmount, control, this, vars);
+ //animation.setTime(time, control.skeleton, blendAmount, affectedBones);
+ time += tpf * speed;
+
+ if (animation.getLength() > 0){
+ if (time >= animation.getLength()) {
+ control.notifyAnimCycleDone(this, animation.getName());
+ } else if (time < 0) {
+ control.notifyAnimCycleDone(this, animation.getName());
+ }
+ }
+
+ time = clampWrapTime(time, animation.getLength(), loopMode);
+ if (time < 0){
+ time = -time;
+ speed = -speed;
+ }
+
+
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/AnimControl.java b/engine/src/core/com/jme3/animation/AnimControl.java
new file mode 100644
index 0000000..590801c
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimControl.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+/**
+ * <code>AnimControl</code> is a Spatial control that allows manipulation
+ * of skeletal animation.
+ *
+ * The control currently supports:
+ * 1) Animation blending/transitions
+ * 2) Multiple animation channels
+ * 3) Multiple skins
+ * 4) Animation event listeners
+ * 5) Animated model cloning
+ * 6) Animated model binary import/export
+ *
+ * Planned:
+ * 1) Hardware skinning
+ * 2) Morph/Pose animation
+ * 3) Attachments
+ * 4) Add/remove skins
+ *
+ * @author Kirill Vainer
+ */
+public final class AnimControl extends AbstractControl implements Cloneable {
+
+ /**
+ * Skeleton object must contain corresponding data for the targets' weight buffers.
+ */
+ Skeleton skeleton;
+ /** only used for backward compatibility */
+ @Deprecated
+ private SkeletonControl skeletonControl;
+ /**
+ * List of animations
+ */
+ HashMap<String, Animation> animationMap;
+ /**
+ * Animation channels
+ */
+ private transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
+ /**
+ * Animation event listeners
+ */
+ private transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();
+
+ /**
+ * Creates a new animation control for the given skeleton.
+ * The method {@link AnimControl#setAnimations(java.util.HashMap) }
+ * must be called after initialization in order for this class to be useful.
+ *
+ * @param skeleton The skeleton to animate
+ */
+ public AnimControl(Skeleton skeleton) {
+ this.skeleton = skeleton;
+ reset();
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public AnimControl() {
+ }
+
+ /**
+ * Internal use only.
+ */
+ public Control cloneForSpatial(Spatial spatial) {
+ try {
+ AnimControl clone = (AnimControl) super.clone();
+ clone.spatial = spatial;
+ clone.channels = new ArrayList<AnimChannel>();
+ clone.listeners = new ArrayList<AnimEventListener>();
+
+ if (skeleton != null) {
+ clone.skeleton = new Skeleton(skeleton);
+ }
+
+ // animationMap is reference-copied, animation data should be shared
+ // to reduce memory usage.
+
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * @param animations Set the animations that this <code>AnimControl</code>
+ * will be capable of playing. The animations should be compatible
+ * with the skeleton given in the constructor.
+ */
+ public void setAnimations(HashMap<String, Animation> animations) {
+ animationMap = animations;
+ }
+
+ /**
+ * Retrieve an animation from the list of animations.
+ * @param name The name of the animation to retrieve.
+ * @return The animation corresponding to the given name, or null, if no
+ * such named animation exists.
+ */
+ public Animation getAnim(String name) {
+ if (animationMap == null) {
+ animationMap = new HashMap<String, Animation>();
+ }
+ return animationMap.get(name);
+ }
+
+ /**
+ * Adds an animation to be available for playing to this
+ * <code>AnimControl</code>.
+ * @param anim The animation to add.
+ */
+ public void addAnim(Animation anim) {
+ if (animationMap == null) {
+ animationMap = new HashMap<String, Animation>();
+ }
+ animationMap.put(anim.getName(), anim);
+ }
+
+ /**
+ * Remove an animation so that it is no longer available for playing.
+ * @param anim The animation to remove.
+ */
+ public void removeAnim(Animation anim) {
+ if (!animationMap.containsKey(anim.getName())) {
+ throw new IllegalArgumentException("Given animation does not exist "
+ + "in this AnimControl");
+ }
+
+ animationMap.remove(anim.getName());
+ }
+
+ /**
+ * Create a new animation channel, by default assigned to all bones
+ * in the skeleton.
+ *
+ * @return A new animation channel for this <code>AnimControl</code>.
+ */
+ public AnimChannel createChannel() {
+ AnimChannel channel = new AnimChannel(this);
+ channels.add(channel);
+ return channel;
+ }
+
+ /**
+ * Return the animation channel at the given index.
+ * @param index The index, starting at 0, to retrieve the <code>AnimChannel</code>.
+ * @return The animation channel at the given index, or throws an exception
+ * if the index is out of bounds.
+ *
+ * @throws IndexOutOfBoundsException If no channel exists at the given index.
+ */
+ public AnimChannel getChannel(int index) {
+ return channels.get(index);
+ }
+
+ /**
+ * @return The number of channels that are controlled by this
+ * <code>AnimControl</code>.
+ *
+ * @see AnimControl#createChannel()
+ */
+ public int getNumChannels() {
+ return channels.size();
+ }
+
+ /**
+ * Clears all the channels that were created.
+ *
+ * @see AnimControl#createChannel()
+ */
+ public void clearChannels() {
+ channels.clear();
+ }
+
+ /**
+ * @return The skeleton of this <code>AnimControl</code>.
+ */
+ public Skeleton getSkeleton() {
+ return skeleton;
+ }
+
+ /**
+ * Adds a new listener to receive animation related events.
+ * @param listener The listener to add.
+ */
+ public void addListener(AnimEventListener listener) {
+ if (listeners.contains(listener)) {
+ throw new IllegalArgumentException("The given listener is already "
+ + "registed at this AnimControl");
+ }
+
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes the given listener from listening to events.
+ * @param listener
+ * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
+ */
+ public void removeListener(AnimEventListener listener) {
+ if (!listeners.remove(listener)) {
+ throw new IllegalArgumentException("The given listener is not "
+ + "registed at this AnimControl");
+ }
+ }
+
+ /**
+ * Clears all the listeners added to this <code>AnimControl</code>
+ *
+ * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
+ */
+ public void clearListeners() {
+ listeners.clear();
+ }
+
+ void notifyAnimChange(AnimChannel channel, String name) {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onAnimChange(this, channel, name);
+ }
+ }
+
+ void notifyAnimCycleDone(AnimChannel channel, String name) {
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).onAnimCycleDone(this, channel, name);
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ @Override
+ public void setSpatial(Spatial spatial) {
+ if (spatial == null && skeletonControl != null) {
+ this.spatial.removeControl(skeletonControl);
+ }
+
+ super.setSpatial(spatial);
+
+ //Backward compatibility.
+ if (spatial != null && skeletonControl != null) {
+ spatial.addControl(skeletonControl);
+ }
+ }
+
+ final void reset() {
+ if (skeleton != null) {
+ skeleton.resetAndUpdate();
+ }
+ }
+
+ /**
+ * @return The names of all animations that this <code>AnimControl</code>
+ * can play.
+ */
+ public Collection<String> getAnimationNames() {
+ return animationMap.keySet();
+ }
+
+ /**
+ * Returns the length of the given named animation.
+ * @param name The name of the animation
+ * @return The length of time, in seconds, of the named animation.
+ */
+ public float getAnimationLength(String name) {
+ Animation a = animationMap.get(name);
+ if (a == null) {
+ throw new IllegalArgumentException("The animation " + name
+ + " does not exist in this AnimControl");
+ }
+
+ return a.getLength();
+ }
+
+ /**
+ * Internal use only.
+ */
+ @Override
+ protected void controlUpdate(float tpf) {
+ if (skeleton != null) {
+ skeleton.reset(); // reset skeleton to bind pose
+ }
+
+ TempVars vars = TempVars.get();
+ for (int i = 0; i < channels.size(); i++) {
+ channels.get(i).update(tpf, vars);
+ }
+ vars.release();
+
+ if (skeleton != null) {
+ skeleton.updateWorldVectors();
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(skeleton, "skeleton", null);
+ oc.writeStringSavableMap(animationMap, "animations", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+ skeleton = (Skeleton) in.readSavable("skeleton", null);
+ animationMap = (HashMap<String, Animation>) in.readStringSavableMap("animations", null);
+
+ if (im.getFormatVersion() == 0) {
+ // Changed for backward compatibility with j3o files generated
+ // before the AnimControl/SkeletonControl split.
+
+ // If we find a target mesh array the AnimControl creates the
+ // SkeletonControl for old files and add it to the spatial.
+ // When backward compatibility won't be needed anymore this can deleted
+ Savable[] sav = in.readSavableArray("targets", null);
+ if (sav != null) {
+ Mesh[] targets = new Mesh[sav.length];
+ System.arraycopy(sav, 0, targets, 0, sav.length);
+ skeletonControl = new SkeletonControl(targets, skeleton);
+ spatial.addControl(skeletonControl);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/AnimEventListener.java b/engine/src/core/com/jme3/animation/AnimEventListener.java
new file mode 100644
index 0000000..22a0b98
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimEventListener.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+/**
+ * <code>AnimEventListener</code> allows user code to receive various
+ * events regarding an AnimControl. For example, when an animation cycle is done.
+ *
+ * @author Kirill Vainer
+ */
+public interface AnimEventListener {
+
+ /**
+ * Invoked when an animation "cycle" is done. For non-looping animations,
+ * this event is invoked when the animation is finished playing. For
+ * looping animations, this even is invoked each time the animation is restarted.
+ *
+ * @param control The control to which the listener is assigned.
+ * @param channel The channel being altered
+ * @param animName The new animation that is done.
+ */
+ public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName);
+
+ /**
+ * Invoked when a animation is set to play by the user on the given channel.
+ *
+ * @param control The control to which the listener is assigned.
+ * @param channel The channel being altered
+ * @param animName The new animation name set.
+ */
+ public void onAnimChange(AnimControl control, AnimChannel channel, String animName);
+
+}
diff --git a/engine/src/core/com/jme3/animation/Animation.java b/engine/src/core/com/jme3/animation/Animation.java
new file mode 100644
index 0000000..0fb505a
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Animation.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * The animation class updates the animation target with the tracks of a given type.
+ *
+ * @author Kirill Vainer, Marcin Roguski (Kaelthas)
+ */
+public class Animation implements Savable, Cloneable {
+
+ /**
+ * The name of the animation.
+ */
+ private String name;
+
+ /**
+ * The length of the animation.
+ */
+ private float length;
+
+ /**
+ * The tracks of the animation.
+ */
+ private Track[] tracks;
+
+ /**
+ * Serialization-only. Do not use.
+ */
+ public Animation() {}
+
+ /**
+ * Creates a new <code>Animation</code> with the given name and length.
+ *
+ * @param name The name of the animation.
+ * @param length Length in seconds of the animation.
+ */
+ public Animation(String name, float length) {
+ this.name = name;
+ this.length = length;
+ }
+
+ /**
+ * The name of the bone animation
+ * @return name of the bone animation
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the length in seconds of this animation
+ *
+ * @return the length in seconds of this animation
+ */
+ public float getLength() {
+ return length;
+ }
+
+ /**
+ * This method sets the current time of the animation.
+ * This method behaves differently for every known track type.
+ * Override this method if you have your own type of track.
+ *
+ * @param time the time of the animation
+ * @param blendAmount the blend amount factor
+ * @param control the animation control
+ * @param channel the animation channel
+ */
+ void setTime(float time, float blendAmount, AnimControl control, AnimChannel channel, TempVars vars) {
+ if (tracks == null)
+ return;
+
+ for (int i = 0; i < tracks.length; i++){
+ tracks[i].setTime(time, blendAmount, control, channel, vars);
+ }
+
+ /*
+ if (tracks != null && tracks.length > 0) {
+ Track<?> trackInstance = tracks[0];
+
+ if (trackInstance instanceof SpatialTrack) {
+ Spatial spatial = control.getSpatial();
+ if (spatial != null) {
+ ((SpatialTrack) tracks[0]).setTime(time, spatial, blendAmount);
+ }
+ } else if (trackInstance instanceof BoneTrack) {
+ BitSet affectedBones = channel.getAffectedBones();
+ Skeleton skeleton = control.getSkeleton();
+ for (int i = 0; i < tracks.length; ++i) {
+ if (affectedBones == null || affectedBones.get(((BoneTrack) tracks[i]).getTargetIndex())) {
+ ((BoneTrack) tracks[i]).setTime(time, skeleton, blendAmount);
+ }
+ }
+ } else if (trackInstance instanceof PoseTrack) {
+ Spatial spatial = control.getSpatial();
+ List<Mesh> meshes = new ArrayList<Mesh>();
+ this.getMeshes(spatial, meshes);
+ if (meshes.size() > 0) {
+ Mesh[] targets = meshes.toArray(new Mesh[meshes.size()]);
+ for (int i = 0; i < tracks.length; ++i) {
+ ((PoseTrack) tracks[i]).setTime(time, targets, blendAmount);
+ }
+ }
+ }
+ }
+ */
+ }
+
+ /**
+ * Set the {@link Track}s to be used by this animation.
+ * <p>
+ * The array should be organized so that the appropriate Track can
+ * be retrieved based on a bone index.
+ *
+ * @param tracks The tracks to set.
+ */
+ public void setTracks(Track[] tracks){
+ this.tracks = tracks;
+ }
+
+ /**
+ * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }.
+ *
+ * @return the tracks set previously
+ */
+ public Track[] getTracks() {
+ return tracks;
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ @Override
+ public Animation clone() {
+ try {
+ Animation result = (Animation) super.clone();
+ result.tracks = tracks.clone();
+ for (int i = 0; i < tracks.length; ++i) {
+ result.tracks[i] = this.tracks[i].clone();
+ }
+ return result;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']';
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(name, "name", null);
+ out.write(length, "length", 0f);
+ out.write(tracks, "tracks", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ name = in.readString("name", null);
+ length = in.readFloat("length", 0f);
+
+ Savable[] arr = in.readSavableArray("tracks", null);
+ tracks = new Track[arr.length];
+ System.arraycopy(arr, 0, tracks, 0, arr.length);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/AnimationFactory.java b/engine/src/core/com/jme3/animation/AnimationFactory.java
new file mode 100644
index 0000000..b994ccb
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/AnimationFactory.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+
+/**
+ * A convenience class to easily setup a spatial keyframed animation
+ * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale.
+ * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames.
+ * <br><br>
+ * Usage is : <br>
+ * - Create the AnimationHelper<br>
+ * - add some keyFrames<br>
+ * - call the buildAnimation() method that will retruna new Animation<br>
+ * - add the generated Animation to any existing AnimationControl<br>
+ * <br><br>
+ * Note that the first keyFrame (index 0) is defaulted with the identy transforms.
+ * If you want to change that you have to replace this keyFrame with any transform you want.
+ *
+ * @author Nehon
+ */
+public class AnimationFactory {
+
+ /**
+ * step for splitting rotation that have a n ange above PI/2
+ */
+ private final static float EULER_STEP = FastMath.QUARTER_PI * 3;
+
+ /**
+ * enum to determine the type of interpolation
+ */
+ private enum Type {
+
+ Translation, Rotation, Scale;
+ }
+
+ /**
+ * Inner Rotation type class to kep track on a rotation Euler angle
+ */
+ protected class Rotation {
+
+ /**
+ * The rotation Quaternion
+ */
+ Quaternion rotation = new Quaternion();
+ /**
+ * This rotation expressed in Euler angles
+ */
+ Vector3f eulerAngles = new Vector3f();
+ /**
+ * the index of the parent key frame is this keyFrame is a splitted rotation
+ */
+ int masterKeyFrame = -1;
+
+ public Rotation() {
+ rotation.loadIdentity();
+ }
+
+ void set(Quaternion rot) {
+ rotation.set(rot);
+ float[] a = new float[3];
+ rotation.toAngles(a);
+ eulerAngles.set(a[0], a[1], a[2]);
+ }
+
+ void set(float x, float y, float z) {
+ float[] a = {x, y, z};
+ rotation.fromAngles(a);
+ eulerAngles.set(x, y, z);
+ }
+ }
+ /**
+ * Name of the animation
+ */
+ protected String name;
+ /**
+ * frames per seconds
+ */
+ protected int fps;
+ /**
+ * Animation duration in seconds
+ */
+ protected float duration;
+ /**
+ * total number of frames
+ */
+ protected int totalFrames;
+ /**
+ * time per frame
+ */
+ protected float tpf;
+ /**
+ * Time array for this animation
+ */
+ protected float[] times;
+ /**
+ * Translation array for this animation
+ */
+ protected Vector3f[] translations;
+ /**
+ * rotation array for this animation
+ */
+ protected Quaternion[] rotations;
+ /**
+ * scales array for this animation
+ */
+ protected Vector3f[] scales;
+ /**
+ * The map of keyFrames to compute the animation. The key is the index of the frame
+ */
+ protected Vector3f[] keyFramesTranslation;
+ protected Vector3f[] keyFramesScale;
+ protected Rotation[] keyFramesRotation;
+
+ /**
+ * Creates and AnimationHelper
+ * @param duration the desired duration for the resulting animation
+ * @param name the name of the resulting animation
+ */
+ public AnimationFactory(float duration, String name) {
+ this(duration, name, 30);
+ }
+
+ /**
+ * Creates and AnimationHelper
+ * @param duration the desired duration for the resulting animation
+ * @param name the name of the resulting animation
+ * @param fps the number of frames per second for this animation (default is 30)
+ */
+ public AnimationFactory(float duration, String name, int fps) {
+ this.name = name;
+ this.duration = duration;
+ this.fps = fps;
+ totalFrames = (int) (fps * duration) + 1;
+ tpf = 1 / (float) fps;
+ times = new float[totalFrames];
+ translations = new Vector3f[totalFrames];
+ rotations = new Quaternion[totalFrames];
+ scales = new Vector3f[totalFrames];
+ keyFramesTranslation = new Vector3f[totalFrames];
+ keyFramesTranslation[0] = new Vector3f();
+ keyFramesScale = new Vector3f[totalFrames];
+ keyFramesScale[0] = new Vector3f(1, 1, 1);
+ keyFramesRotation = new Rotation[totalFrames];
+ keyFramesRotation[0] = new Rotation();
+
+ }
+
+ /**
+ * Adds a key frame for the given Transform at the given time
+ * @param time the time at which the keyFrame must be inserted
+ * @param transform the transforms to use for this keyFrame
+ */
+ public void addTimeTransform(float time, Transform transform) {
+ addKeyFrameTransform((int) (time / tpf), transform);
+ }
+
+ /**
+ * Adds a key frame for the given Transform at the given keyFrame index
+ * @param keyFrameIndex the index at which the keyFrame must be inserted
+ * @param transform the transforms to use for this keyFrame
+ */
+ public void addKeyFrameTransform(int keyFrameIndex, Transform transform) {
+ addKeyFrameTranslation(keyFrameIndex, transform.getTranslation());
+ addKeyFrameScale(keyFrameIndex, transform.getScale());
+ addKeyFrameRotation(keyFrameIndex, transform.getRotation());
+ }
+
+ /**
+ * Adds a key frame for the given translation at the given time
+ * @param time the time at which the keyFrame must be inserted
+ * @param translation the translation to use for this keyFrame
+ */
+ public void addTimeTranslation(float time, Vector3f translation) {
+ addKeyFrameTranslation((int) (time / tpf), translation);
+ }
+
+ /**
+ * Adds a key frame for the given translation at the given keyFrame index
+ * @param keyFrameIndex the index at which the keyFrame must be inserted
+ * @param translation the translation to use for this keyFrame
+ */
+ public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) {
+ Vector3f t = getTranslationForFrame(keyFrameIndex);
+ t.set(translation);
+ }
+
+ /**
+ * Adds a key frame for the given rotation at the given time<br>
+ * This can't be used if the interpolated angle is higher than PI (180°)<br>
+ * Use {@link addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.<br> *
+ * @param time the time at which the keyFrame must be inserted
+ * @param rotation the rotation Quaternion to use for this keyFrame
+ * @see #addTimeRotationAngles(float time, float x, float y, float z)
+ */
+ public void addTimeRotation(float time, Quaternion rotation) {
+ addKeyFrameRotation((int) (time / tpf), rotation);
+ }
+
+ /**
+ * Adds a key frame for the given rotation at the given keyFrame index<br>
+ * This can't be used if the interpolated angle is higher than PI (180°)<br>
+ * Use {@link addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations.
+ * @param keyFrameIndex the index at which the keyFrame must be inserted
+ * @param rotation the rotation Quaternion to use for this keyFrame
+ * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)
+ */
+ public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) {
+ Rotation r = getRotationForFrame(keyFrameIndex);
+ r.set(rotation);
+ }
+
+ /**
+ * Adds a key frame for the given rotation at the given time.<br>
+ * Rotation is expressed by Euler angles values in radians.<br>
+ * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+ * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+ *
+ * @param time the time at which the keyFrame must be inserted
+ * @param x the rotation around the x axis (aka yaw) in radians
+ * @param y the rotation around the y axis (aka roll) in radians
+ * @param z the rotation around the z axis (aka pitch) in radians
+ */
+ public void addTimeRotationAngles(float time, float x, float y, float z) {
+ addKeyFrameRotationAngles((int) (time / tpf), x, y, z);
+ }
+
+ /**
+ * Adds a key frame for the given rotation at the given key frame index.<br>
+ * Rotation is expressed by Euler angles values in radians.<br>
+ * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)<br>
+ * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation<br>
+ *
+ * @param keyFrameIndex the index at which the keyFrame must be inserted
+ * @param x the rotation around the x axis (aka yaw) in radians
+ * @param y the rotation around the y axis (aka roll) in radians
+ * @param z the rotation around the z axis (aka pitch) in radians
+ */
+ public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) {
+ Rotation r = getRotationForFrame(keyFrameIndex);
+ r.set(x, y, z);
+
+ // if the delta of euler angles is higher than PI, we create intermediate keyframes
+ // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI
+ int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation);
+ //previous rotation keyframe
+ Rotation prevRot = keyFramesRotation[prev];
+ //the maximum delta angle (x,y or z)
+ float delta = Math.max(Math.abs(x - prevRot.eulerAngles.x), Math.abs(y - prevRot.eulerAngles.y));
+ delta = Math.max(delta, Math.abs(z - prevRot.eulerAngles.z));
+ //if delta > PI we have to create intermediates key frames
+ if (delta >= FastMath.PI) {
+ //frames delta
+ int dF = keyFrameIndex - prev;
+ //angle per frame for x,y ,z
+ float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF;
+ float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF;
+ float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF;
+
+ // the keyFrame step
+ int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP);
+ // the current keyFrame
+ int cursor = prev + keyStep;
+ while (cursor < keyFrameIndex) {
+ //for each step we create a new rotation by interpolating the angles
+ Rotation dr = getRotationForFrame(cursor);
+ dr.masterKeyFrame = keyFrameIndex;
+ dr.set(prevRot.eulerAngles.x + cursor * dXAngle, prevRot.eulerAngles.y + cursor * dYAngle, prevRot.eulerAngles.z + cursor * dZAngle);
+ cursor += keyStep;
+ }
+
+ }
+
+ }
+
+ /**
+ * Adds a key frame for the given scale at the given time
+ * @param time the time at which the keyFrame must be inserted
+ * @param scale the scale to use for this keyFrame
+ */
+ public void addTimeScale(float time, Vector3f scale) {
+ addKeyFrameScale((int) (time / tpf), scale);
+ }
+
+ /**
+ * Adds a key frame for the given scale at the given keyFrame index
+ * @param keyFrameIndex the index at which the keyFrame must be inserted
+ * @param scale the scale to use for this keyFrame
+ */
+ public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) {
+ Vector3f s = getScaleForFrame(keyFrameIndex);
+ s.set(scale);
+ }
+
+ /**
+ * returns the translation for a given frame index
+ * creates the translation if it doesn't exists
+ * @param keyFrameIndex index
+ * @return the translation
+ */
+ private Vector3f getTranslationForFrame(int keyFrameIndex) {
+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+ }
+ Vector3f v = keyFramesTranslation[keyFrameIndex];
+ if (v == null) {
+ v = new Vector3f();
+ keyFramesTranslation[keyFrameIndex] = v;
+ }
+ return v;
+ }
+
+ /**
+ * returns the scale for a given frame index
+ * creates the scale if it doesn't exists
+ * @param keyFrameIndex index
+ * @return the scale
+ */
+ private Vector3f getScaleForFrame(int keyFrameIndex) {
+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+ }
+ Vector3f v = keyFramesScale[keyFrameIndex];
+ if (v == null) {
+ v = new Vector3f();
+ keyFramesScale[keyFrameIndex] = v;
+ }
+ return v;
+ }
+
+ /**
+ * returns the rotation for a given frame index
+ * creates the rotation if it doesn't exists
+ * @param keyFrameIndex index
+ * @return the rotation
+ */
+ private Rotation getRotationForFrame(int keyFrameIndex) {
+ if (keyFrameIndex < 0 || keyFrameIndex > totalFrames) {
+ throw new ArrayIndexOutOfBoundsException("keyFrameIndex must be between 0 and " + totalFrames + " (received " + keyFrameIndex + ")");
+ }
+ Rotation v = keyFramesRotation[keyFrameIndex];
+ if (v == null) {
+ v = new Rotation();
+ keyFramesRotation[keyFrameIndex] = v;
+ }
+ return v;
+ }
+
+ /**
+ * Creates an Animation based on the keyFrames previously added to the helper.
+ * @return the generated animation
+ */
+ public Animation buildAnimation() {
+ interpolateTime();
+ interpolate(keyFramesTranslation, Type.Translation);
+ interpolate(keyFramesRotation, Type.Rotation);
+ interpolate(keyFramesScale, Type.Scale);
+
+ SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales);
+
+ //creating the animation
+ Animation spatialAnimation = new Animation(name, duration);
+ spatialAnimation.setTracks(new SpatialTrack[]{spatialTrack});
+
+ return spatialAnimation;
+ }
+
+ /**
+ * interpolates time values
+ */
+ private void interpolateTime() {
+ for (int i = 0; i < totalFrames; i++) {
+ times[i] = i * tpf;
+ }
+ }
+
+ /**
+ * Interpolates over the key frames for the given keyFrame array and the given type of transform
+ * @param keyFrames the keyFrames array
+ * @param type the type of transforms
+ */
+ private void interpolate(Object[] keyFrames, Type type) {
+ int i = 0;
+ while (i < totalFrames) {
+ //fetching the next keyFrame index transform in the array
+ int key = getNextKeyFrame(i, keyFrames);
+ if (key != -1) {
+ //computing the frame span to interpolate over
+ int span = key - i;
+ //interating over the frames
+ for (int j = i; j <= key; j++) {
+ // computing interpolation value
+ float val = (float) (j - i) / (float) span;
+ //interpolationg depending on the transform type
+ switch (type) {
+ case Translation:
+ translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+ break;
+ case Rotation:
+ Quaternion rot = new Quaternion();
+ rotations[j] = rot.slerp(((Rotation) keyFrames[i]).rotation, ((Rotation) keyFrames[key]).rotation, val);
+ break;
+ case Scale:
+ scales[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]);
+ break;
+ }
+ }
+ //jumping to the next keyFrame
+ i = key;
+ } else {
+ //No more key frame, filling the array witht he last transform computed.
+ for (int j = i; j < totalFrames; j++) {
+
+ switch (type) {
+ case Translation:
+ translations[j] = ((Vector3f) keyFrames[i]).clone();
+ break;
+ case Rotation:
+ rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone();
+ break;
+ case Scale:
+ scales[j] = ((Vector3f) keyFrames[i]).clone();
+ break;
+ }
+ }
+ //we're done
+ i = totalFrames;
+ }
+ }
+ }
+
+ /**
+ * Get the index of the next keyFrame that as a transform
+ * @param index the start index
+ * @param keyFrames the keyFrames array
+ * @return the index of the next keyFrame
+ */
+ private int getNextKeyFrame(int index, Object[] keyFrames) {
+ for (int i = index + 1; i < totalFrames; i++) {
+ if (keyFrames[i] != null) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Get the index of the previous keyFrame that as a transform
+ * @param index the start index
+ * @param keyFrames the keyFrames array
+ * @return the index of the previous keyFrame
+ */
+ private int getPreviousKeyFrame(int index, Object[] keyFrames) {
+ for (int i = index - 1; i >= 0; i--) {
+ if (keyFrames[i] != null) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/Bone.java b/engine/src/core/com/jme3/animation/Bone.java
new file mode 100644
index 0000000..e7e9e2b
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Bone.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.scene.Node;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * <code>Bone</code> describes a bone in the bone-weight skeletal animation
+ * system. A bone contains a name and an index, as well as relevant
+ * transformation data.
+ *
+ * @author Kirill Vainer
+ */
+public final class Bone implements Savable {
+
+ private String name;
+ private Bone parent;
+ private final ArrayList<Bone> children = new ArrayList<Bone>();
+ /**
+ * If enabled, user can control bone transform with setUserTransforms.
+ * Animation transforms are not applied to this bone when enabled.
+ */
+ private boolean userControl = false;
+ /**
+ * The attachment node.
+ */
+ private Node attachNode;
+ /**
+ * Initial transform is the local bind transform of this bone.
+ * PARENT SPACE -> BONE SPACE
+ */
+ private Vector3f initialPos;
+ private Quaternion initialRot;
+ private Vector3f initialScale;
+ /**
+ * The inverse world bind transform.
+ * BONE SPACE -> MODEL SPACE
+ */
+ private Vector3f worldBindInversePos;
+ private Quaternion worldBindInverseRot;
+ private Vector3f worldBindInverseScale;
+ /**
+ * The local animated transform combined with the local bind transform and parent world transform
+ */
+ private Vector3f localPos = new Vector3f();
+ private Quaternion localRot = new Quaternion();
+ private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f);
+ /**
+ * MODEL SPACE -> BONE SPACE (in animated state)
+ */
+ private Vector3f worldPos = new Vector3f();
+ private Quaternion worldRot = new Quaternion();
+ private Vector3f worldScale = new Vector3f();
+ //used for getCombinedTransform
+ private Transform tmpTransform = new Transform();
+
+ /**
+ * Creates a new bone with the given name.
+ *
+ * @param name Name to give to this bone
+ */
+ public Bone(String name) {
+ if (name == null)
+ throw new IllegalArgumentException("Name cannot be null");
+
+ this.name = name;
+
+ initialPos = new Vector3f();
+ initialRot = new Quaternion();
+ initialScale = new Vector3f(1, 1, 1);
+
+ worldBindInversePos = new Vector3f();
+ worldBindInverseRot = new Quaternion();
+ worldBindInverseScale = new Vector3f();
+ }
+
+ /**
+ * Special-purpose copy constructor.
+ * <p>
+ * Only copies the name and bind pose from the original.
+ * <p>
+ * WARNING: Local bind pose and world inverse bind pose transforms shallow
+ * copied. Modifying that data on the original bone will cause it to
+ * be recomputed on any cloned bones.
+ * <p>
+ * The rest of the data is <em>NOT</em> copied, as it will be
+ * generated automatically when the bone is animated.
+ *
+ * @param source The bone from which to copy the data.
+ */
+ Bone(Bone source) {
+ this.name = source.name;
+
+ userControl = source.userControl;
+
+ initialPos = source.initialPos;
+ initialRot = source.initialRot;
+ initialScale = source.initialScale;
+
+ worldBindInversePos = source.worldBindInversePos;
+ worldBindInverseRot = source.worldBindInverseRot;
+ worldBindInverseScale = source.worldBindInverseScale;
+
+ // parent and children will be assigned manually..
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Bone() {
+ }
+
+ /**
+ * Returns the name of the bone, set in the constructor.
+ *
+ * @return The name of the bone, set in the constructor.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns parent bone of this bone, or null if it is a root bone.
+ * @return The parent bone of this bone, or null if it is a root bone.
+ */
+ public Bone getParent() {
+ return parent;
+ }
+
+ /**
+ * Returns all the children bones of this bone.
+ *
+ * @return All the children bones of this bone.
+ */
+ public ArrayList<Bone> getChildren() {
+ return children;
+ }
+
+ /**
+ * Returns the local position of the bone, relative to the parent bone.
+ *
+ * @return The local position of the bone, relative to the parent bone.
+ */
+ public Vector3f getLocalPosition() {
+ return localPos;
+ }
+
+ /**
+ * Returns the local rotation of the bone, relative to the parent bone.
+ *
+ * @return The local rotation of the bone, relative to the parent bone.
+ */
+ public Quaternion getLocalRotation() {
+ return localRot;
+ }
+
+ /**
+ * Returns the local scale of the bone, relative to the parent bone.
+ *
+ * @return The local scale of the bone, relative to the parent bone.
+ */
+ public Vector3f getLocalScale() {
+ return localScale;
+ }
+
+ /**
+ * Returns the position of the bone in model space.
+ *
+ * @return The position of the bone in model space.
+ */
+ public Vector3f getModelSpacePosition() {
+ return worldPos;
+ }
+
+ /**
+ * Returns the rotation of the bone in model space.
+ *
+ * @return The rotation of the bone in model space.
+ */
+ public Quaternion getModelSpaceRotation() {
+ return worldRot;
+ }
+
+ /**
+ * Returns the scale of the bone in model space.
+ *
+ * @return The scale of the bone in model space.
+ */
+ public Vector3f getModelSpaceScale() {
+ return worldScale;
+ }
+
+ /**
+ * Returns the inverse world bind pose position.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the inverse world bind pose position.
+ */
+ public Vector3f getWorldBindInversePosition() {
+ return worldBindInversePos;
+ }
+
+ /**
+ * Returns the inverse world bind pose rotation.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the inverse world bind pose rotation.
+ */
+ public Quaternion getWorldBindInverseRotation() {
+ return worldBindInverseRot;
+ }
+
+ /**
+ * Returns the inverse world bind pose scale.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the inverse world bind pose scale.
+ */
+ public Vector3f getWorldBindInverseScale() {
+ return worldBindInverseScale;
+ }
+
+ /**
+ * Returns the world bind pose position.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the world bind pose position.
+ */
+ public Vector3f getWorldBindPosition() {
+ return initialPos;
+ }
+
+ /**
+ * Returns the world bind pose rotation.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the world bind pose rotation.
+ */
+ public Quaternion getWorldBindRotation() {
+ return initialRot;
+ }
+
+ /**
+ * Returns the world bind pose scale.
+ * <p>
+ * The bind pose transform of the bone is its "default"
+ * transform with no animation applied.
+ *
+ * @return the world bind pose scale.
+ */
+ public Vector3f getWorldBindScale() {
+ return initialScale;
+ }
+
+ /**
+ * If enabled, user can control bone transform with setUserTransforms.
+ * Animation transforms are not applied to this bone when enabled.
+ */
+ public void setUserControl(boolean enable) {
+ userControl = enable;
+ }
+
+ /**
+ * Add a new child to this bone. Shouldn't be used by user code.
+ * Can corrupt skeleton.
+ *
+ * @param bone The bone to add
+ */
+ public void addChild(Bone bone) {
+ children.add(bone);
+ bone.parent = this;
+ }
+
+ /**
+ * Updates the world transforms for this bone, and, possibly the attach node
+ * if not null.
+ * <p>
+ * The world transform of this bone is computed by combining the parent's
+ * world transform with this bones' local transform.
+ */
+ public final void updateWorldVectors() {
+ if (parent != null) {
+ //rotation
+ parent.worldRot.mult(localRot, worldRot);
+
+ //scale
+ //For scale parent scale is not taken into account!
+ // worldScale.set(localScale);
+ parent.worldScale.mult(localScale, worldScale);
+
+ //translation
+ //scale and rotation of parent affect bone position
+ parent.worldRot.mult(localPos, worldPos);
+ worldPos.multLocal(parent.worldScale);
+ worldPos.addLocal(parent.worldPos);
+ } else {
+ worldRot.set(localRot);
+ worldPos.set(localPos);
+ worldScale.set(localScale);
+ }
+
+ if (attachNode != null) {
+ attachNode.setLocalTranslation(worldPos);
+ attachNode.setLocalRotation(worldRot);
+ attachNode.setLocalScale(worldScale);
+ }
+ }
+
+ /**
+ * Updates world transforms for this bone and it's children.
+ */
+ final void update() {
+ this.updateWorldVectors();
+
+ for (int i = children.size() - 1; i >= 0; i--) {
+ children.get(i).update();
+ }
+ }
+
+ /**
+ * Saves the current bone state as its binding pose, including its children.
+ */
+ void setBindingPose() {
+ initialPos.set(localPos);
+ initialRot.set(localRot);
+ initialScale.set(localScale);
+
+ if (worldBindInversePos == null) {
+ worldBindInversePos = new Vector3f();
+ worldBindInverseRot = new Quaternion();
+ worldBindInverseScale = new Vector3f();
+ }
+
+ // Save inverse derived position/scale/orientation, used for calculate offset transform later
+ worldBindInversePos.set(worldPos);
+ worldBindInversePos.negateLocal();
+
+ worldBindInverseRot.set(worldRot);
+ worldBindInverseRot.inverseLocal();
+
+ worldBindInverseScale.set(Vector3f.UNIT_XYZ);
+ worldBindInverseScale.divideLocal(worldScale);
+
+ for (Bone b : children) {
+ b.setBindingPose();
+ }
+ }
+
+ /**
+ * Reset the bone and it's children to bind pose.
+ */
+ final void reset() {
+ if (!userControl) {
+ localPos.set(initialPos);
+ localRot.set(initialRot);
+ localScale.set(initialScale);
+ }
+
+ for (int i = children.size() - 1; i >= 0; i--) {
+ children.get(i).reset();
+ }
+ }
+
+ /**
+ * Stores the skinning transform in the specified Matrix4f.
+ * The skinning transform applies the animation of the bone to a vertex.
+ *
+ * This assumes that the world transforms for the entire bone hierarchy
+ * have already been computed, otherwise this method will return undefined
+ * results.
+ *
+ * @param outTransform
+ */
+ void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) {
+ // Computing scale
+ Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3);
+
+ // Computing rotation
+ Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1);
+
+ // Computing translation
+ // Translation depend on rotation and scale
+ Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2);
+
+ // Populating the matrix
+ outTransform.loadIdentity();
+ outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4));
+ }
+
+ /**
+ * Sets user transform.
+ */
+ public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+ if (!userControl) {
+ throw new IllegalStateException("User control must be on bone to allow user transforms");
+ }
+
+ localPos.set(initialPos);
+ localRot.set(initialRot);
+ localScale.set(initialScale);
+
+ localPos.addLocal(translation);
+ localRot = localRot.mult(rotation);
+ localScale.multLocal(scale);
+ }
+
+ /**
+ * Must update all bones in skeleton for this to work.
+ * @param translation
+ * @param rotation
+ */
+ public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
+ if (!userControl) {
+ throw new IllegalStateException("User control must be on bone to allow user transforms");
+ }
+
+ // TODO: add scale here ???
+ worldPos.set(translation);
+ worldRot.set(rotation);
+
+ //if there is an attached Node we need to set it's local transforms too.
+ if(attachNode != null){
+ attachNode.setLocalTranslation(translation);
+ attachNode.setLocalRotation(rotation);
+ }
+ }
+
+ /**
+ * Returns the local transform of this bone combined with the given position and rotation
+ * @param position a position
+ * @param rotation a rotation
+ */
+ public Transform getCombinedTransform(Vector3f position, Quaternion rotation) {
+ rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position);
+ tmpTransform.setRotation(rotation).getRotation().multLocal(localRot);
+ return tmpTransform;
+ }
+
+ /**
+ * Returns the attachment node.
+ * Attach models and effects to this node to make
+ * them follow this bone's motions.
+ */
+ Node getAttachmentsNode() {
+ if (attachNode == null) {
+ attachNode = new Node(name + "_attachnode");
+ attachNode.setUserData("AttachedBone", this);
+ }
+ return attachNode;
+ }
+
+ /**
+ * Used internally after model cloning.
+ * @param attachNode
+ */
+ void setAttachmentsNode(Node attachNode) {
+ this.attachNode = attachNode;
+ }
+
+ /**
+ * Sets the local animation transform of this bone.
+ * Bone is assumed to be in bind pose when this is called.
+ */
+ void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+ if (userControl) {
+ return;
+ }
+
+// localPos.addLocal(translation);
+// localRot.multLocal(rotation);
+ //localRot = localRot.mult(rotation);
+
+ localPos.set(initialPos).addLocal(translation);
+ localRot.set(initialRot).multLocal(rotation);
+
+ if (scale != null) {
+ localScale.set(initialScale).multLocal(scale);
+ }
+ }
+
+ void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
+ if (userControl) {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+// assert vars.lock();
+
+ Vector3f tmpV = vars.vect1;
+ Vector3f tmpV2 = vars.vect2;
+ Quaternion tmpQ = vars.quat1;
+
+ //location
+ tmpV.set(initialPos).addLocal(translation);
+ localPos.interpolate(tmpV, weight);
+
+ //rotation
+ tmpQ.set(initialRot).multLocal(rotation);
+ localRot.nlerp(tmpQ, weight);
+
+ //scale
+ if (scale != null) {
+ tmpV2.set(initialScale).multLocal(scale);
+ localScale.interpolate(tmpV2, weight);
+ }
+
+
+ vars.release();
+ }
+
+ /**
+ * Sets local bind transform for bone.
+ * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them.
+ */
+ public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
+ initialPos.set(translation);
+ initialRot.set(rotation);
+ //ogre.xml can have null scale values breaking this if the check is removed
+ if (scale != null) {
+ initialScale.set(scale);
+ }
+
+ localPos.set(translation);
+ localRot.set(rotation);
+ if (scale != null) {
+ localScale.set(scale);
+ }
+ }
+
+ private String toString(int depth) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < depth; i++) {
+ sb.append('-');
+ }
+
+ sb.append(name).append(" bone\n");
+ for (Bone child : children) {
+ sb.append(child.toString(depth + 1));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return this.toString(0);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule input = im.getCapsule(this);
+
+ name = input.readString("name", null);
+ initialPos = (Vector3f) input.readSavable("initialPos", null);
+ initialRot = (Quaternion) input.readSavable("initialRot", null);
+ initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
+ attachNode = (Node) input.readSavable("attachNode", null);
+
+ localPos.set(initialPos);
+ localRot.set(initialRot);
+
+ ArrayList<Bone> childList = input.readSavableArrayList("children", null);
+ for (int i = childList.size() - 1; i >= 0; i--) {
+ this.addChild(childList.get(i));
+ }
+
+ // NOTE: Parent skeleton will call update() then setBindingPose()
+ // after Skeleton has been de-serialized.
+ // Therefore, worldBindInversePos and worldBindInverseRot
+ // will be reconstructed based on that information.
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule output = ex.getCapsule(this);
+
+ output.write(name, "name", null);
+ output.write(attachNode, "attachNode", null);
+ output.write(initialPos, "initialPos", null);
+ output.write(initialRot, "initialRot", null);
+ output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
+ output.writeSavableArrayList(children, "children", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/BoneAnimation.java b/engine/src/core/com/jme3/animation/BoneAnimation.java
new file mode 100644
index 0000000..f5ffd40
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/BoneAnimation.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+/**
+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
+ */
+@Deprecated
+public final class BoneAnimation extends Animation {
+
+ @Deprecated
+ public BoneAnimation(String name, float length) {
+ super(name, length);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/BoneTrack.java b/engine/src/core/com/jme3/animation/BoneTrack.java
new file mode 100644
index 0000000..0d28ddc
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/BoneTrack.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.BitSet;
+
+/**
+ * Contains a list of transforms and times for each keyframe.
+ *
+ * @author Kirill Vainer
+ */
+public final class BoneTrack implements Track {
+
+ /**
+ * Bone index in the skeleton which this track effects.
+ */
+ private int targetBoneIndex;
+
+ /**
+ * Transforms and times for track.
+ */
+ private CompactVector3Array translations;
+ private CompactQuaternionArray rotations;
+ private CompactVector3Array scales;
+ private float[] times;
+
+ /**
+ * Serialization-only. Do not use.
+ */
+ public BoneTrack() {
+ }
+
+ /**
+ * Creates a bone track for the given bone index
+ * @param targetBoneIndex the bone index
+ * @param times a float array with the time of each frame
+ * @param translations the translation of the bone for each frame
+ * @param rotations the rotation of the bone for each frame
+ */
+ public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations) {
+ this.targetBoneIndex = targetBoneIndex;
+ this.setKeyframes(times, translations, rotations);
+ }
+
+ /**
+ * Creates a bone track for the given bone index
+ * @param targetBoneIndex the bone index
+ * @param times a float array with the time of each frame
+ * @param translations the translation of the bone for each frame
+ * @param rotations the rotation of the bone for each frame
+ * @param scales the scale of the bone for each frame
+ */
+ public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+ this.targetBoneIndex = targetBoneIndex;
+ this.setKeyframes(times, translations, rotations, scales);
+ }
+
+ /**
+ * Creates a bone track for the given bone index
+ * @param targetBoneIndex the bone's index
+ */
+ public BoneTrack(int targetBoneIndex) {
+ this.targetBoneIndex = targetBoneIndex;
+ }
+
+ /**
+ * @return the bone index of this bone track.
+ */
+ public int getTargetBoneIndex() {
+ return targetBoneIndex;
+ }
+
+ /**
+ * return the array of rotations of this track
+ * @return
+ */
+ public Quaternion[] getRotations() {
+ return rotations.toObjectArray();
+ }
+
+ /**
+ * returns the array of scales for this track
+ * @return
+ */
+ public Vector3f[] getScales() {
+ return scales == null ? null : scales.toObjectArray();
+ }
+
+ /**
+ * returns the arrays of time for this track
+ * @return
+ */
+ public float[] getTimes() {
+ return times;
+ }
+
+ /**
+ * returns the array of translations of this track
+ * @return
+ */
+ public Vector3f[] getTranslations() {
+ return translations.toObjectArray();
+ }
+
+ /**
+ * Set the translations and rotations for this bone track
+ * @param times a float array with the time of each frame
+ * @param translations the translation of the bone for each frame
+ * @param rotations the rotation of the bone for each frame
+ */
+ public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations) {
+ if (times.length == 0) {
+ throw new RuntimeException("BoneTrack with no keyframes!");
+ }
+
+ assert times.length == translations.length && times.length == rotations.length;
+
+ this.times = times;
+ this.translations = new CompactVector3Array();
+ this.translations.add(translations);
+ this.translations.freeze();
+ this.rotations = new CompactQuaternionArray();
+ this.rotations.add(rotations);
+ this.rotations.freeze();
+ }
+
+ /**
+ * Set the translations, rotations and scales for this bone track
+ * @param times a float array with the time of each frame
+ * @param translations the translation of the bone for each frame
+ * @param rotations the rotation of the bone for each frame
+ * @param scales the scale of the bone for each frame
+ */
+ public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
+ this.setKeyframes(times, translations, rotations);
+ assert times.length == scales.length;
+ if (scales != null) {
+ this.scales = new CompactVector3Array();
+ this.scales.add(scales);
+ this.scales.freeze();
+ }
+ }
+
+ /**
+ *
+ * Modify the bone which this track modifies in the skeleton to contain
+ * the correct animation transforms for a given time.
+ * The transforms can be interpolated in some method from the keyframes.
+ *
+ * @param time the current time of the animation
+ * @param weight the weight of the animation
+ * @param control
+ * @param channel
+ * @param vars
+ */
+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+ BitSet affectedBones = channel.getAffectedBones();
+ if (affectedBones != null && !affectedBones.get(targetBoneIndex)) {
+ return;
+ }
+
+ Bone target = control.getSkeleton().getBone(targetBoneIndex);
+
+ Vector3f tempV = vars.vect1;
+ Vector3f tempS = vars.vect2;
+ Quaternion tempQ = vars.quat1;
+ Vector3f tempV2 = vars.vect3;
+ Vector3f tempS2 = vars.vect4;
+ Quaternion tempQ2 = vars.quat2;
+
+ int lastFrame = times.length - 1;
+ if (time < 0 || lastFrame == 0) {
+ rotations.get(0, tempQ);
+ translations.get(0, tempV);
+ if (scales != null) {
+ scales.get(0, tempS);
+ }
+ } else if (time >= times[lastFrame]) {
+ rotations.get(lastFrame, tempQ);
+ translations.get(lastFrame, tempV);
+ if (scales != null) {
+ scales.get(lastFrame, tempS);
+ }
+ } else {
+ int startFrame = 0;
+ int endFrame = 1;
+ // use lastFrame so we never overflow the array
+ int i;
+ for (i = 0; i < lastFrame && times[i] < time; i++) {
+ startFrame = i;
+ endFrame = i + 1;
+ }
+
+ float blend = (time - times[startFrame])
+ / (times[endFrame] - times[startFrame]);
+
+ rotations.get(startFrame, tempQ);
+ translations.get(startFrame, tempV);
+ if (scales != null) {
+ scales.get(startFrame, tempS);
+ }
+ rotations.get(endFrame, tempQ2);
+ translations.get(endFrame, tempV2);
+ if (scales != null) {
+ scales.get(endFrame, tempS2);
+ }
+ tempQ.nlerp(tempQ2, blend);
+ tempV.interpolate(tempV2, blend);
+ tempS.interpolate(tempS2, blend);
+ }
+
+ if (weight != 1f) {
+ target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight);
+ } else {
+ target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null);
+ }
+ }
+
+ /**
+ * @return the length of the track
+ */
+ public float getLength() {
+ return times == null ? 0 : times[times.length - 1] - times[0];
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ @Override
+ public BoneTrack clone() {
+ int tablesLength = times.length;
+
+ float[] times = this.times.clone();
+ Vector3f[] sourceTranslations = this.getTranslations();
+ Quaternion[] sourceRotations = this.getRotations();
+ Vector3f[] sourceScales = this.getScales();
+
+ Vector3f[] translations = new Vector3f[tablesLength];
+ Quaternion[] rotations = new Quaternion[tablesLength];
+ Vector3f[] scales = new Vector3f[tablesLength];
+ for (int i = 0; i < tablesLength; ++i) {
+ translations[i] = sourceTranslations[i].clone();
+ rotations[i] = sourceRotations[i].clone();
+ scales[i] = sourceScales != null ? sourceScales[i].clone() : new Vector3f(1.0f, 1.0f, 1.0f);
+ }
+
+ // Need to use the constructor here because of the final fields used in this class
+ return new BoneTrack(targetBoneIndex, times, translations, rotations, scales);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(targetBoneIndex, "boneIndex", 0);
+ oc.write(translations, "translations", null);
+ oc.write(rotations, "rotations", null);
+ oc.write(times, "times", null);
+ oc.write(scales, "scales", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ targetBoneIndex = ic.readInt("boneIndex", 0);
+
+ translations = (CompactVector3Array) ic.readSavable("translations", null);
+ rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
+ times = ic.readFloatArray("times", null);
+ scales = (CompactVector3Array) ic.readSavable("scales", null);
+
+ //Backward compatibility for old j3o files generated before revision 6807
+ if (im.getFormatVersion() == 0){
+ if (translations == null) {
+ Savable[] sav = ic.readSavableArray("translations", null);
+ if (sav != null) {
+ translations = new CompactVector3Array();
+ Vector3f[] transCopy = new Vector3f[sav.length];
+ System.arraycopy(sav, 0, transCopy, 0, sav.length);
+ translations.add(transCopy);
+ translations.freeze();
+ }
+ }
+ if (rotations == null) {
+ Savable[] sav = ic.readSavableArray("rotations", null);
+ if (sav != null) {
+ rotations = new CompactQuaternionArray();
+ Quaternion[] rotCopy = new Quaternion[sav.length];
+ System.arraycopy(sav, 0, rotCopy, 0, sav.length);
+ rotations.add(rotCopy);
+ rotations.freeze();
+ }
+ }
+ }
+ }
+
+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/CompactArray.java b/engine/src/core/com/jme3/animation/CompactArray.java
new file mode 100644
index 0000000..6a2a9be
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactArray.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import java.lang.reflect.Array;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Object is indexed and stored in primitive float[]
+ * @author Lim, YongHoon
+ * @param <T>
+ */
+public abstract class CompactArray<T> {
+
+ private Map<T, Integer> indexPool = new HashMap<T, Integer>();
+ protected int[] index;
+ protected float[] array;
+ private boolean invalid;
+
+ /**
+ * Creates a compact array
+ */
+ public CompactArray() {
+ }
+
+ /**
+ * create array using serialized data
+ * @param compressedArray
+ * @param index
+ */
+ public CompactArray(float[] compressedArray, int[] index) {
+ this.array = compressedArray;
+ this.index = index;
+ }
+
+ /**
+ * Add objects.
+ * They are serialized automatically when get() method is called.
+ * @param objArray
+ */
+ public void add(T... objArray) {
+ if (objArray == null || objArray.length == 0) {
+ return;
+ }
+ invalid = true;
+ int base = 0;
+ if (index == null) {
+ index = new int[objArray.length];
+ } else {
+ if (indexPool.isEmpty()) {
+ throw new RuntimeException("Internal is already fixed");
+ }
+ base = index.length;
+
+ int[] tmp = new int[base + objArray.length];
+ System.arraycopy(index, 0, tmp, 0, index.length);
+ index = tmp;
+ //index = Arrays.copyOf(index, base+objArray.length);
+ }
+ for (int j = 0; j < objArray.length; j++) {
+ T obj = objArray[j];
+ if (obj == null) {
+ index[base + j] = -1;
+ } else {
+ Integer i = indexPool.get(obj);
+ if (i == null) {
+ i = indexPool.size();
+ indexPool.put(obj, i);
+ }
+ index[base + j] = i;
+ }
+ }
+ }
+
+ /**
+ * release objects.
+ * add() method call is not allowed anymore.
+ */
+ public void freeze() {
+ serialize();
+ indexPool.clear();
+ }
+
+ /**
+ * @param index
+ * @param value
+ */
+ public final void set(int index, T value) {
+ int j = getCompactIndex(index);
+ serialize(j, value);
+ }
+
+ /**
+ * returns the object for the given index
+ * @param index the index
+ * @param store an object to store the result
+ * @return
+ */
+ public final T get(int index, T store) {
+ serialize();
+ int j = getCompactIndex(index);
+ return deserialize(j, store);
+ }
+
+ /**
+ * return a float array of serialized data
+ * @return
+ */
+ public final float[] getSerializedData() {
+ serialize();
+ return array;
+ }
+
+ /**
+ * serialize this compact array
+ */
+ public final void serialize() {
+ if (invalid) {
+ int newSize = indexPool.size() * getTupleSize();
+ if (array == null || Array.getLength(array) < newSize) {
+ array = ensureCapacity(array, newSize);
+ for (Map.Entry<T, Integer> entry : indexPool.entrySet()) {
+ int i = entry.getValue();
+ T obj = entry.getKey();
+ serialize(i, obj);
+ }
+ }
+ invalid = false;
+ }
+ }
+
+ /**
+ * @return compacted array's primitive size
+ */
+ protected final int getSerializedSize() {
+ return Array.getLength(getSerializedData());
+ }
+
+ /**
+ * Ensure the capacity for the given array and the given size
+ * @param arr the array
+ * @param size the size
+ * @return
+ */
+ protected float[] ensureCapacity(float[] arr, int size) {
+ if (arr == null) {
+ return new float[size];
+ } else if (arr.length >= size) {
+ return arr;
+ } else {
+ float[] tmp = new float[size];
+ System.arraycopy(arr, 0, tmp, 0, arr.length);
+ return tmp;
+ //return Arrays.copyOf(arr, size);
+ }
+ }
+
+ /**
+ * retrun an array of indices for the given objects
+ * @param objArray
+ * @return
+ */
+ public final int[] getIndex(T... objArray) {
+ int[] index = new int[objArray.length];
+ for (int i = 0; i < index.length; i++) {
+ T obj = objArray[i];
+ index[i] = obj != null ? indexPool.get(obj) : -1;
+ }
+ return index;
+ }
+
+ /**
+ * returns the corresponding index in the compact array
+ * @param objIndex
+ * @return object index in the compacted object array
+ */
+ public int getCompactIndex(int objIndex) {
+ return index != null ? index[objIndex] : objIndex;
+ }
+
+ /**
+ * @return uncompressed object size
+ */
+ public final int getTotalObjectSize() {
+ assert getSerializedSize() % getTupleSize() == 0;
+ return index != null ? index.length : getSerializedSize() / getTupleSize();
+ }
+
+ /**
+ * @return compressed object size
+ */
+ public final int getCompactObjectSize() {
+ assert getSerializedSize() % getTupleSize() == 0;
+ return getSerializedSize() / getTupleSize();
+ }
+
+ /**
+ * decompress and return object array
+ * @return decompress and return object array
+ */
+ public final T[] toObjectArray() {
+ try {
+ T[] compactArr = (T[]) Array.newInstance(getElementClass(), getSerializedSize() / getTupleSize());
+ for (int i = 0; i < compactArr.length; i++) {
+ compactArr[i] = getElementClass().newInstance();
+ deserialize(i, compactArr[i]);
+ }
+
+ T[] objArr = (T[]) Array.newInstance(getElementClass(), getTotalObjectSize());
+ for (int i = 0; i < objArr.length; i++) {
+ int compactIndex = getCompactIndex(i);
+ objArr[i] = compactArr[compactIndex];
+ }
+ return objArr;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * serialize object
+ * @param compactIndex compacted object index
+ * @param store
+ */
+ protected abstract void serialize(int compactIndex, T store);
+
+ /**
+ * deserialize object
+ * @param compactIndex compacted object index
+ * @param store
+ */
+ protected abstract T deserialize(int compactIndex, T store);
+
+ /**
+ * serialized size of one object element
+ */
+ protected abstract int getTupleSize();
+
+ protected abstract Class<T> getElementClass();
+}
diff --git a/engine/src/core/com/jme3/animation/CompactQuaternionArray.java b/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
new file mode 100644
index 0000000..7c56c41
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactQuaternionArray.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Quaternion;
+import java.io.IOException;
+
+/**
+ * Serialize and compress {@link Quaternion}[] by indexing same values
+ * It is converted to float[]
+ * @author Lim, YongHoon
+ */
+public class CompactQuaternionArray extends CompactArray<Quaternion> implements Savable {
+
+ /**
+ * creates a compact Quaternion array
+ */
+ public CompactQuaternionArray() {
+ }
+
+ /**
+ * creates a compact Quaternion array
+ * @param dataArray the data array
+ * @param index the indices array
+ */
+ public CompactQuaternionArray(float[] dataArray, int[] index) {
+ super(dataArray, index);
+ }
+
+ @Override
+ protected final int getTupleSize() {
+ return 4;
+ }
+
+ @Override
+ protected final Class<Quaternion> getElementClass() {
+ return Quaternion.class;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ serialize();
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(array, "array", null);
+ out.write(index, "index", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ array = in.readFloatArray("array", null);
+ index = in.readIntArray("index", null);
+ }
+
+ @Override
+ protected void serialize(int i, Quaternion store) {
+ int j = i * getTupleSize();
+ array[j] = store.getX();
+ array[j + 1] = store.getY();
+ array[j + 2] = store.getZ();
+ array[j + 3] = store.getW();
+ }
+
+ @Override
+ protected Quaternion deserialize(int i, Quaternion store) {
+ int j = i * getTupleSize();
+ store.set(array[j], array[j + 1], array[j + 2], array[j + 3]);
+ return store;
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/CompactVector3Array.java b/engine/src/core/com/jme3/animation/CompactVector3Array.java
new file mode 100644
index 0000000..cdfc6e4
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/CompactVector3Array.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * Serialize and compress Vector3f[] by indexing same values
+ * @author Lim, YongHoon
+ */
+public class CompactVector3Array extends CompactArray<Vector3f> implements Savable {
+
+ /**
+ * Creates a compact vector array
+ */
+ public CompactVector3Array() {
+ }
+
+ /**
+ * creates a compact vector array
+ * @param dataArray the data array
+ * @param index the indices
+ */
+ public CompactVector3Array(float[] dataArray, int[] index) {
+ super(dataArray, index);
+ }
+
+ @Override
+ protected final int getTupleSize() {
+ return 3;
+ }
+
+ @Override
+ protected final Class<Vector3f> getElementClass() {
+ return Vector3f.class;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ serialize();
+ OutputCapsule out = ex.getCapsule(this);
+ out.write(array, "array", null);
+ out.write(index, "index", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ array = in.readFloatArray("array", null);
+ index = in.readIntArray("index", null);
+ }
+
+ @Override
+ protected void serialize(int i, Vector3f store) {
+ int j = i*getTupleSize();
+ array[j] = store.getX();
+ array[j+1] = store.getY();
+ array[j+2] = store.getZ();
+ }
+
+ @Override
+ protected Vector3f deserialize(int i, Vector3f store) {
+ int j = i*getTupleSize();
+ store.set(array[j], array[j+1], array[j+2]);
+ return store;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/animation/LoopMode.java b/engine/src/core/com/jme3/animation/LoopMode.java
new file mode 100644
index 0000000..b74461e
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/LoopMode.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+/**
+ * <code>LoopMode</code> determines how animations repeat, or if they
+ * do not repeat.
+ */
+public enum LoopMode {
+ /**
+ * The animation will play repeatedly, when it reaches the end
+ * the animation will play again from the beginning, and so on.
+ */
+ Loop,
+
+ /**
+ * The animation will not loop. It will play until the last frame, and then
+ * freeze at that frame. It is possible to decide to play a new animation
+ * when that happens by using a AnimEventListener.
+ */
+ DontLoop,
+
+ /**
+ * The animation will cycle back and forth. When reaching the end, the
+ * animation will play backwards from the last frame until it reaches
+ * the first frame.
+ */
+ Cycle,
+
+}
diff --git a/engine/src/core/com/jme3/animation/Pose.java b/engine/src/core/com/jme3/animation/Pose.java
new file mode 100644
index 0000000..9ff6ec8
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Pose.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Vector3f;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A pose is a list of offsets that say where a mesh vertices should be for this pose.
+ */
+public final class Pose implements Savable, Cloneable {
+
+ private String name;
+ private int targetMeshIndex;
+
+ private Vector3f[] offsets;
+ private int[] indices;
+
+ private transient final Vector3f tempVec = new Vector3f();
+ private transient final Vector3f tempVec2 = new Vector3f();
+
+ public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices){
+ this.name = name;
+ this.targetMeshIndex = targetMeshIndex;
+ this.offsets = offsets;
+ this.indices = indices;
+ }
+
+ public int getTargetMeshIndex(){
+ return targetMeshIndex;
+ }
+
+
+ /**
+ * Applies the offsets of this pose to the vertex buffer given by the blend factor.
+ *
+ * @param blend Blend factor, 0 = no change to vertex buffer, 1 = apply full offsets
+ * @param vertbuf Vertex buffer to apply this pose to
+ */
+ public void apply(float blend, FloatBuffer vertbuf){
+ for (int i = 0; i < indices.length; i++){
+ Vector3f offset = offsets[i];
+ int vertIndex = indices[i];
+
+ tempVec.set(offset).multLocal(blend);
+
+ // acquire vertex
+ BufferUtils.populateFromBuffer(tempVec2, vertbuf, vertIndex);
+
+ // add offset multiplied by factor
+ tempVec2.addLocal(tempVec);
+
+ // write modified vertex
+ BufferUtils.setInBuffer(tempVec2, vertbuf, vertIndex);
+ }
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ public Pose clone() {
+ try {
+ Pose result = (Pose) super.clone();
+ result.indices = this.indices.clone();
+ if(this.offsets!=null) {
+ result.offsets = new Vector3f[this.offsets.length];
+ for(int i=0;i<this.offsets.length;++i) {
+ result.offsets[i] = this.offsets[i].clone();
+ }
+ }
+ return result;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule out = e.getCapsule(this);
+ out.write(name, "name", "");
+ out.write(targetMeshIndex, "meshIndex", -1);
+ out.write(offsets, "offsets", null);
+ out.write(indices, "indices", null);
+ }
+
+ public void read(JmeImporter i) throws IOException {
+ InputCapsule in = i.getCapsule(this);
+ name = in.readString("name", "");
+ targetMeshIndex = in.readInt("meshIndex", -1);
+ offsets = (Vector3f[]) in.readSavableArray("offsets", null);
+ indices = in.readIntArray("indices", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/PoseTrack.java b/engine/src/core/com/jme3/animation/PoseTrack.java
new file mode 100644
index 0000000..bf1b0ab
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/PoseTrack.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A single track of pose animation associated with a certain mesh.
+ */
+@Deprecated
+public final class PoseTrack implements Track {
+
+ private int targetMeshIndex;
+ private PoseFrame[] frames;
+ private float[] times;
+
+ public static class PoseFrame implements Savable, Cloneable {
+
+ Pose[] poses;
+ float[] weights;
+
+ public PoseFrame(Pose[] poses, float[] weights) {
+ this.poses = poses;
+ this.weights = weights;
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ @Override
+ public PoseFrame clone() {
+ try {
+ PoseFrame result = (PoseFrame) super.clone();
+ result.weights = this.weights.clone();
+ if (this.poses != null) {
+ result.poses = new Pose[this.poses.length];
+ for (int i = 0; i < this.poses.length; ++i) {
+ result.poses[i] = this.poses[i].clone();
+ }
+ }
+ return result;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule out = e.getCapsule(this);
+ out.write(poses, "poses", null);
+ out.write(weights, "weights", null);
+ }
+
+ public void read(JmeImporter i) throws IOException {
+ InputCapsule in = i.getCapsule(this);
+ poses = (Pose[]) in.readSavableArray("poses", null);
+ weights = in.readFloatArray("weights", null);
+ }
+ }
+
+ public PoseTrack(int targetMeshIndex, float[] times, PoseFrame[] frames){
+ this.targetMeshIndex = targetMeshIndex;
+ this.times = times;
+ this.frames = frames;
+ }
+
+ private void applyFrame(Mesh target, int frameIndex, float weight){
+ PoseFrame frame = frames[frameIndex];
+ VertexBuffer pb = target.getBuffer(Type.Position);
+ for (int i = 0; i < frame.poses.length; i++){
+ Pose pose = frame.poses[i];
+ float poseWeight = frame.weights[i] * weight;
+
+ pose.apply(poseWeight, (FloatBuffer) pb.getData());
+ }
+
+ // force to re-upload data to gpu
+ pb.updateData(pb.getData());
+ }
+
+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+ // TODO: When MeshControl is created, it will gather targets
+ // list automatically which is then retrieved here.
+
+ /*
+ Mesh target = targets[targetMeshIndex];
+ if (time < times[0]) {
+ applyFrame(target, 0, weight);
+ } else if (time > times[times.length - 1]) {
+ applyFrame(target, times.length - 1, weight);
+ } else {
+ int startFrame = 0;
+ for (int i = 0; i < times.length; i++) {
+ if (times[i] < time) {
+ startFrame = i;
+ }
+ }
+
+ int endFrame = startFrame + 1;
+ float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);
+ applyFrame(target, startFrame, blend * weight);
+ applyFrame(target, endFrame, (1f - blend) * weight);
+ }
+ */
+ }
+
+ /**
+ * @return the length of the track
+ */
+ public float getLength() {
+ return times == null ? 0 : times[times.length - 1] - times[0];
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ @Override
+ public PoseTrack clone() {
+ try {
+ PoseTrack result = (PoseTrack) super.clone();
+ result.times = this.times.clone();
+ if (this.frames != null) {
+ result.frames = new PoseFrame[this.frames.length];
+ for (int i = 0; i < this.frames.length; ++i) {
+ result.frames[i] = this.frames[i].clone();
+ }
+ }
+ return result;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule out = e.getCapsule(this);
+ out.write(targetMeshIndex, "meshIndex", 0);
+ out.write(frames, "frames", null);
+ out.write(times, "times", null);
+ }
+
+ @Override
+ public void read(JmeImporter i) throws IOException {
+ InputCapsule in = i.getCapsule(this);
+ targetMeshIndex = in.readInt("meshIndex", 0);
+ frames = (PoseFrame[]) in.readSavableArray("frames", null);
+ times = in.readFloatArray("times", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/Skeleton.java b/engine/src/core/com/jme3/animation/Skeleton.java
new file mode 100644
index 0000000..bc36542
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Skeleton.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.Matrix4f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <code>Skeleton</code> is a convenience class for managing a bone hierarchy.
+ * Skeleton updates the world transforms to reflect the current local
+ * animated matrixes.
+ *
+ * @author Kirill Vainer
+ */
+public final class Skeleton implements Savable {
+
+ private Bone[] rootBones;
+ private Bone[] boneList;
+
+ /**
+ * Contains the skinning matrices, multiplying it by a vertex effected by a bone
+ * will cause it to go to the animated position.
+ */
+ private transient Matrix4f[] skinningMatrixes;
+
+ /**
+ * Creates a skeleton from a bone list.
+ * The root bones are found automatically.
+ * <p>
+ * Note that using this constructor will cause the bones in the list
+ * to have their bind pose recomputed based on their local transforms.
+ *
+ * @param boneList The list of bones to manage by this Skeleton
+ */
+ public Skeleton(Bone[] boneList) {
+ this.boneList = boneList;
+
+ List<Bone> rootBoneList = new ArrayList<Bone>();
+ for (int i = boneList.length - 1; i >= 0; i--) {
+ Bone b = boneList[i];
+ if (b.getParent() == null) {
+ rootBoneList.add(b);
+ }
+ }
+ rootBones = rootBoneList.toArray(new Bone[rootBoneList.size()]);
+
+ createSkinningMatrices();
+
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ Bone rootBone = rootBones[i];
+ rootBone.update();
+ rootBone.setBindingPose();
+ }
+ }
+
+ /**
+ * Special-purpose copy constructor.
+ * <p>
+ * Shallow copies bind pose data from the source skeleton, does not
+ * copy any other data.
+ *
+ * @param source The source Skeleton to copy from
+ */
+ public Skeleton(Skeleton source) {
+ Bone[] sourceList = source.boneList;
+ boneList = new Bone[sourceList.length];
+ for (int i = 0; i < sourceList.length; i++) {
+ boneList[i] = new Bone(sourceList[i]);
+ }
+
+ rootBones = new Bone[source.rootBones.length];
+ for (int i = 0; i < rootBones.length; i++) {
+ rootBones[i] = recreateBoneStructure(source.rootBones[i]);
+ }
+ createSkinningMatrices();
+
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ rootBones[i].update();
+ }
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Skeleton() {
+ }
+
+ private void createSkinningMatrices() {
+ skinningMatrixes = new Matrix4f[boneList.length];
+ for (int i = 0; i < skinningMatrixes.length; i++) {
+ skinningMatrixes[i] = new Matrix4f();
+ }
+ }
+
+ private Bone recreateBoneStructure(Bone sourceRoot) {
+ Bone targetRoot = getBone(sourceRoot.getName());
+ List<Bone> children = sourceRoot.getChildren();
+ for (int i = 0; i < children.size(); i++) {
+ Bone sourceChild = children.get(i);
+ // find my version of the child
+ Bone targetChild = getBone(sourceChild.getName());
+ targetRoot.addChild(targetChild);
+ recreateBoneStructure(sourceChild);
+ }
+
+ return targetRoot;
+ }
+
+ /**
+ * Updates world transforms for all bones in this skeleton.
+ * Typically called after setting local animation transforms.
+ */
+ public void updateWorldVectors() {
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ rootBones[i].update();
+ }
+ }
+
+ /**
+ * Saves the current skeleton state as it's binding pose.
+ */
+ public void setBindingPose() {
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ rootBones[i].setBindingPose();
+ }
+ }
+
+ /**
+ * Reset the skeleton to bind pose.
+ */
+ public final void reset() {
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ rootBones[i].reset();
+ }
+ }
+
+ /**
+ * Reset the skeleton to bind pose and updates the bones
+ */
+ public final void resetAndUpdate() {
+ for (int i = rootBones.length - 1; i >= 0; i--) {
+ Bone rootBone = rootBones[i];
+ rootBone.reset();
+ rootBone.update();
+ }
+ }
+
+ /**
+ * returns the array of all root bones of this skeleton
+ * @return
+ */
+ public Bone[] getRoots() {
+ return rootBones;
+ }
+
+ /**
+ * return a bone for the given index
+ * @param index
+ * @return
+ */
+ public Bone getBone(int index) {
+ return boneList[index];
+ }
+
+ /**
+ * returns the bone with the given name
+ * @param name
+ * @return
+ */
+ public Bone getBone(String name) {
+ for (int i = 0; i < boneList.length; i++) {
+ if (boneList[i].getName().equals(name)) {
+ return boneList[i];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * returns the bone index of the given bone
+ * @param bone
+ * @return
+ */
+ public int getBoneIndex(Bone bone) {
+ for (int i = 0; i < boneList.length; i++) {
+ if (boneList[i] == bone) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * returns the bone index of the bone that has the given name
+ * @param name
+ * @return
+ */
+ public int getBoneIndex(String name) {
+ for (int i = 0; i < boneList.length; i++) {
+ if (boneList[i].getName().equals(name)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Compute the skining matrices for each bone of the skeleton that would be used to transform vertices of associated meshes
+ * @return
+ */
+ public Matrix4f[] computeSkinningMatrices() {
+ TempVars vars = TempVars.get();
+ for (int i = 0; i < boneList.length; i++) {
+ boneList[i].getOffsetTransform(skinningMatrixes[i], vars.quat1, vars.vect1, vars.vect2, vars.tempMat3);
+ }
+ vars.release();
+ return skinningMatrixes;
+ }
+
+ /**
+ * returns the number of bones of this skeleton
+ * @return
+ */
+ public int getBoneCount() {
+ return boneList.length;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Skeleton - ").append(boneList.length).append(" bones, ").append(rootBones.length).append(" roots\n");
+ for (Bone rootBone : rootBones) {
+ sb.append(rootBone.toString());
+ }
+ return sb.toString();
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule input = im.getCapsule(this);
+
+ Savable[] boneRootsAsSav = input.readSavableArray("rootBones", null);
+ rootBones = new Bone[boneRootsAsSav.length];
+ System.arraycopy(boneRootsAsSav, 0, rootBones, 0, boneRootsAsSav.length);
+
+ Savable[] boneListAsSavable = input.readSavableArray("boneList", null);
+ boneList = new Bone[boneListAsSavable.length];
+ System.arraycopy(boneListAsSavable, 0, boneList, 0, boneListAsSavable.length);
+
+ createSkinningMatrices();
+
+ for (Bone rootBone : rootBones) {
+ rootBone.update();
+ rootBone.setBindingPose();
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule output = ex.getCapsule(this);
+ output.write(rootBones, "rootBones", null);
+ output.write(boneList, "boneList", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/SkeletonControl.java b/engine/src/core/com/jme3/animation/SkeletonControl.java
new file mode 100644
index 0000000..3c8e117
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SkeletonControl.java
@@ -0,0 +1,549 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+/**
+ * The Skeleton control deforms a model according to a skeleton,
+ * It handles the computation of the deformation matrices and performs
+ * the transformations on the mesh
+ *
+ * @author Rémy Bouquet Based on AnimControl by Kirill Vainer
+ */
+public class SkeletonControl extends AbstractControl implements Cloneable {
+
+ /**
+ * The skeleton of the model
+ */
+ private Skeleton skeleton;
+ /**
+ * List of targets which this controller effects.
+ */
+ private Mesh[] targets;
+ /**
+ * Used to track when a mesh was updated. Meshes are only updated
+ * if they are visible in at least one camera.
+ */
+ private boolean wasMeshUpdated = false;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public SkeletonControl() {
+ }
+
+ /**
+ * Creates a skeleton control.
+ * The list of targets will be acquired automatically when
+ * the control is attached to a node.
+ *
+ * @param skeleton the skeleton
+ */
+ public SkeletonControl(Skeleton skeleton) {
+ this.skeleton = skeleton;
+ }
+
+ /**
+ * Creates a skeleton control.
+ *
+ * @param targets the meshes controlled by the skeleton
+ * @param skeleton the skeleton
+ */
+ @Deprecated
+ SkeletonControl(Mesh[] targets, Skeleton skeleton) {
+ this.skeleton = skeleton;
+ this.targets = targets;
+ }
+
+ private boolean isMeshAnimated(Mesh mesh) {
+ return mesh.getBuffer(Type.BindPosePosition) != null;
+ }
+
+ private Mesh[] findTargets(Node node) {
+ Mesh sharedMesh = null;
+ ArrayList<Mesh> animatedMeshes = new ArrayList<Mesh>();
+
+ for (Spatial child : node.getChildren()) {
+ if (!(child instanceof Geometry)) {
+ continue; // could be an attachment node, ignore.
+ }
+
+ Geometry geom = (Geometry) child;
+
+ // is this geometry using a shared mesh?
+ Mesh childSharedMesh = geom.getUserData(UserData.JME_SHAREDMESH);
+
+ if (childSharedMesh != null) {
+ // Don't bother with non-animated shared meshes
+ if (isMeshAnimated(childSharedMesh)) {
+ // child is using shared mesh,
+ // so animate the shared mesh but ignore child
+ if (sharedMesh == null) {
+ sharedMesh = childSharedMesh;
+ } else if (sharedMesh != childSharedMesh) {
+ throw new IllegalStateException("Two conflicting shared meshes for " + node);
+ }
+ }
+ } else {
+ Mesh mesh = geom.getMesh();
+ if (isMeshAnimated(mesh)) {
+ animatedMeshes.add(mesh);
+ }
+ }
+ }
+
+ if (sharedMesh != null) {
+ animatedMeshes.add(sharedMesh);
+ }
+
+ return animatedMeshes.toArray(new Mesh[animatedMeshes.size()]);
+ }
+
+ @Override
+ public void setSpatial(Spatial spatial) {
+ super.setSpatial(spatial);
+ if (spatial != null) {
+ Node node = (Node) spatial;
+ targets = findTargets(node);
+ } else {
+ targets = null;
+ }
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ if (!wasMeshUpdated) {
+ resetToBind(); // reset morph meshes to bind pose
+
+ Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
+
+ // if hardware skinning is supported, the matrices and weight buffer
+ // will be sent by the SkinningShaderLogic object assigned to the shader
+ for (int i = 0; i < targets.length; i++) {
+ // NOTE: This assumes that code higher up
+ // Already ensured those targets are animated
+ // otherwise a crash will happen in skin update
+ //if (isMeshAnimated(targets[i])) {
+ softwareSkinUpdate(targets[i], offsetMatrices);
+ //}
+ }
+
+ wasMeshUpdated = true;
+ }
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ wasMeshUpdated = false;
+ }
+
+ void resetToBind() {
+ for (Mesh mesh : targets) {
+ if (isMeshAnimated(mesh)) {
+ VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
+ ByteBuffer bib = (ByteBuffer) bi.getData();
+ if (!bib.hasArray()) {
+ mesh.prepareForAnim(true); // prepare for software animation
+ }
+ VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
+ VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
+ VertexBuffer pos = mesh.getBuffer(Type.Position);
+ VertexBuffer norm = mesh.getBuffer(Type.Normal);
+ FloatBuffer pb = (FloatBuffer) pos.getData();
+ FloatBuffer nb = (FloatBuffer) norm.getData();
+ FloatBuffer bpb = (FloatBuffer) bindPos.getData();
+ FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+ pb.clear();
+ nb.clear();
+ bpb.clear();
+ bnb.clear();
+
+ //reseting bind tangents if there is a bind tangent buffer
+ VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent);
+ if (bindTangents != null) {
+ VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+ FloatBuffer tb = (FloatBuffer) tangents.getData();
+ FloatBuffer btb = (FloatBuffer) bindTangents.getData();
+ tb.clear();
+ btb.clear();
+ tb.put(btb).clear();
+ }
+
+
+ pb.put(bpb).clear();
+ nb.put(bnb).clear();
+ }
+ }
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ Node clonedNode = (Node) spatial;
+ AnimControl ctrl = spatial.getControl(AnimControl.class);
+ SkeletonControl clone = new SkeletonControl();
+ clone.setSpatial(clonedNode);
+
+ clone.skeleton = ctrl.getSkeleton();
+ // Fix animated targets for the cloned node
+ clone.targets = findTargets(clonedNode);
+
+ // Fix attachments for the cloned node
+ for (int i = 0; i < clonedNode.getQuantity(); i++) {
+ // go through attachment nodes, apply them to correct bone
+ Spatial child = clonedNode.getChild(i);
+ if (child instanceof Node) {
+ Node clonedAttachNode = (Node) child;
+ Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
+
+ if (originalBone != null) {
+ Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
+
+ clonedAttachNode.setUserData("AttachedBone", clonedBone);
+ clonedBone.setAttachmentsNode(clonedAttachNode);
+ }
+ }
+ }
+
+ return clone;
+ }
+
+ /**
+ *
+ * @param boneName the name of the bone
+ * @return the node attached to this bone
+ */
+ public Node getAttachmentsNode(String boneName) {
+ Bone b = skeleton.getBone(boneName);
+ if (b == null) {
+ throw new IllegalArgumentException("Given bone name does not exist "
+ + "in the skeleton.");
+ }
+
+ Node n = b.getAttachmentsNode();
+ Node model = (Node) spatial;
+ model.attachChild(n);
+ return n;
+ }
+
+ /**
+ * returns the skeleton of this control
+ * @return
+ */
+ public Skeleton getSkeleton() {
+ return skeleton;
+ }
+
+ /**
+ * sets the skeleton for this control
+ * @param skeleton
+ */
+// public void setSkeleton(Skeleton skeleton) {
+// this.skeleton = skeleton;
+// }
+ /**
+ * returns the targets meshes of this control
+ * @return
+ */
+ public Mesh[] getTargets() {
+ return targets;
+ }
+
+ /**
+ * sets the target meshes of this control
+ * @param targets
+ */
+// public void setTargets(Mesh[] targets) {
+// this.targets = targets;
+// }
+ /**
+ * Update the mesh according to the given transformation matrices
+ * @param mesh then mesh
+ * @param offsetMatrices the transformation matrices to apply
+ */
+ private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+
+ VertexBuffer tb = mesh.getBuffer(Type.Tangent);
+ if (tb == null) {
+ //if there are no tangents use the classic skinning
+ applySkinning(mesh, offsetMatrices);
+ } else {
+ //if there are tangents use the skinning with tangents
+ applySkinningTangents(mesh, offsetMatrices, tb);
+ }
+
+
+ }
+
+ /**
+ * Method to apply skinning transforms to a mesh's buffers
+ * @param mesh the mesh
+ * @param offsetMatrices the offset matices to apply
+ */
+ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) {
+ int maxWeightsPerVert = mesh.getMaxNumWeights();
+ if (maxWeightsPerVert <= 0) {
+ throw new IllegalStateException("Max weights per vert is incorrectly set!");
+ }
+
+ int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+ // NOTE: This code assumes the vertex buffer is in bind pose
+ // resetToBind() has been called this frame
+ VertexBuffer vb = mesh.getBuffer(Type.Position);
+ FloatBuffer fvb = (FloatBuffer) vb.getData();
+ fvb.rewind();
+
+ VertexBuffer nb = mesh.getBuffer(Type.Normal);
+ FloatBuffer fnb = (FloatBuffer) nb.getData();
+ fnb.rewind();
+
+ // get boneIndexes and weights for mesh
+ ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+ ib.rewind();
+ wb.rewind();
+
+ float[] weights = wb.array();
+ byte[] indices = ib.array();
+ int idxWeights = 0;
+
+ TempVars vars = TempVars.get();
+
+
+ float[] posBuf = vars.skinPositions;
+ float[] normBuf = vars.skinNormals;
+
+ int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+ int bufLength = posBuf.length;
+ for (int i = iterations - 1; i >= 0; i--) {
+ // read next set of positions and normals from native buffer
+ bufLength = Math.min(posBuf.length, fvb.remaining());
+ fvb.get(posBuf, 0, bufLength);
+ fnb.get(normBuf, 0, bufLength);
+ int verts = bufLength / 3;
+ int idxPositions = 0;
+
+ // iterate vertices and apply skinning transform for each effecting bone
+ for (int vert = verts - 1; vert >= 0; vert--) {
+ float nmx = normBuf[idxPositions];
+ float vtx = posBuf[idxPositions++];
+ float nmy = normBuf[idxPositions];
+ float vty = posBuf[idxPositions++];
+ float nmz = normBuf[idxPositions];
+ float vtz = posBuf[idxPositions++];
+
+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+
+ for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+ float weight = weights[idxWeights];
+ Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+ rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+ ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+ rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+ rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+ rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+ rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+ }
+
+ idxWeights += fourMinusMaxWeights;
+
+ idxPositions -= 3;
+ normBuf[idxPositions] = rnx;
+ posBuf[idxPositions++] = rx;
+ normBuf[idxPositions] = rny;
+ posBuf[idxPositions++] = ry;
+ normBuf[idxPositions] = rnz;
+ posBuf[idxPositions++] = rz;
+ }
+
+ fvb.position(fvb.position() - bufLength);
+ fvb.put(posBuf, 0, bufLength);
+ fnb.position(fnb.position() - bufLength);
+ fnb.put(normBuf, 0, bufLength);
+ }
+
+ vars.release();
+
+ vb.updateData(fvb);
+ nb.updateData(fnb);
+
+ }
+
+ /**
+ * Specific method for skinning with tangents to avoid cluttering the classic skinning calculation with
+ * null checks that would slow down the process even if tangents don't have to be computed.
+ * Also the iteration has additional indexes since tangent has 4 components instead of 3 for pos and norm
+ * @param maxWeightsPerVert maximum number of weights per vertex
+ * @param mesh the mesh
+ * @param offsetMatrices the offsetMaytrices to apply
+ * @param tb the tangent vertexBuffer
+ */
+ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) {
+ int maxWeightsPerVert = mesh.getMaxNumWeights();
+
+ if (maxWeightsPerVert <= 0) {
+ throw new IllegalStateException("Max weights per vert is incorrectly set!");
+ }
+
+ int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+ // NOTE: This code assumes the vertex buffer is in bind pose
+ // resetToBind() has been called this frame
+ VertexBuffer vb = mesh.getBuffer(Type.Position);
+ FloatBuffer fvb = (FloatBuffer) vb.getData();
+ fvb.rewind();
+
+ VertexBuffer nb = mesh.getBuffer(Type.Normal);
+
+ FloatBuffer fnb = (FloatBuffer) nb.getData();
+ fnb.rewind();
+
+
+ FloatBuffer ftb = (FloatBuffer) tb.getData();
+ ftb.rewind();
+
+
+ // get boneIndexes and weights for mesh
+ ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+ FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+ ib.rewind();
+ wb.rewind();
+
+ float[] weights = wb.array();
+ byte[] indices = ib.array();
+ int idxWeights = 0;
+
+ TempVars vars = TempVars.get();
+
+
+ float[] posBuf = vars.skinPositions;
+ float[] normBuf = vars.skinNormals;
+ float[] tanBuf = vars.skinTangents;
+
+ int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+ int bufLength = 0;
+ int tanLength = 0;
+ for (int i = iterations - 1; i >= 0; i--) {
+ // read next set of positions and normals from native buffer
+ bufLength = Math.min(posBuf.length, fvb.remaining());
+ tanLength = Math.min(tanBuf.length, ftb.remaining());
+ fvb.get(posBuf, 0, bufLength);
+ fnb.get(normBuf, 0, bufLength);
+ ftb.get(tanBuf, 0, tanLength);
+ int verts = bufLength / 3;
+ int idxPositions = 0;
+ //tangents has their own index because of the 4 components
+ int idxTangents = 0;
+
+ // iterate vertices and apply skinning transform for each effecting bone
+ for (int vert = verts - 1; vert >= 0; vert--) {
+ float nmx = normBuf[idxPositions];
+ float vtx = posBuf[idxPositions++];
+ float nmy = normBuf[idxPositions];
+ float vty = posBuf[idxPositions++];
+ float nmz = normBuf[idxPositions];
+ float vtz = posBuf[idxPositions++];
+
+ float tnx = tanBuf[idxTangents++];
+ float tny = tanBuf[idxTangents++];
+ float tnz = tanBuf[idxTangents++];
+
+ //skipping the 4th component of the tangent since it doesn't have to be transformed
+ idxTangents++;
+
+ float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0, rtx = 0, rty = 0, rtz = 0;
+
+ for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+ float weight = weights[idxWeights];
+ Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+ rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+ ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+ rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+ rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+ rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+ rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+
+ rtx += (tnx * mat.m00 + tny * mat.m01 + tnz * mat.m02) * weight;
+ rty += (tnx * mat.m10 + tny * mat.m11 + tnz * mat.m12) * weight;
+ rtz += (tnx * mat.m20 + tny * mat.m21 + tnz * mat.m22) * weight;
+ }
+
+ idxWeights += fourMinusMaxWeights;
+
+ idxPositions -= 3;
+
+ normBuf[idxPositions] = rnx;
+ posBuf[idxPositions++] = rx;
+ normBuf[idxPositions] = rny;
+ posBuf[idxPositions++] = ry;
+ normBuf[idxPositions] = rnz;
+ posBuf[idxPositions++] = rz;
+
+ idxTangents -= 4;
+
+ tanBuf[idxTangents++] = rtx;
+ tanBuf[idxTangents++] = rty;
+ tanBuf[idxTangents++] = rtz;
+
+ //once again skipping the 4th component of the tangent
+ idxTangents++;
+ }
+
+ fvb.position(fvb.position() - bufLength);
+ fvb.put(posBuf, 0, bufLength);
+ fnb.position(fnb.position() - bufLength);
+ fnb.put(normBuf, 0, bufLength);
+ ftb.position(ftb.position() - tanLength);
+ ftb.put(tanBuf, 0, tanLength);
+ }
+
+ vars.release();
+
+ vb.updateData(fvb);
+ nb.updateData(fnb);
+ tb.updateData(ftb);
+
+
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(targets, "targets", null);
+ oc.write(skeleton, "skeleton", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+ Savable[] sav = in.readSavableArray("targets", null);
+ if (sav != null) {
+ targets = new Mesh[sav.length];
+ System.arraycopy(sav, 0, targets, 0, sav.length);
+ }
+ skeleton = (Skeleton) in.readSavable("skeleton", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/SpatialAnimation.java b/engine/src/core/com/jme3/animation/SpatialAnimation.java
new file mode 100644
index 0000000..54f5058
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SpatialAnimation.java
@@ -0,0 +1,11 @@
+package com.jme3.animation;
+
+/**
+ * @deprecated use Animation instead with tracks of selected type (ie. BoneTrack, SpatialTrack, MeshTrack)
+ */
+@Deprecated
+public class SpatialAnimation extends Animation {
+ public SpatialAnimation(String name, float length) {
+ super(name, length);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/SpatialTrack.java b/engine/src/core/com/jme3/animation/SpatialTrack.java
new file mode 100644
index 0000000..5704317
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/SpatialTrack.java
@@ -0,0 +1,242 @@
+package com.jme3.animation;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * This class represents the track for spatial animation.
+ *
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class SpatialTrack implements Track {
+
+ /**
+ * Translations of the track.
+ */
+ private CompactVector3Array translations;
+
+ /**
+ * Rotations of the track.
+ */
+ private CompactQuaternionArray rotations;
+
+ /**
+ * Scales of the track.
+ */
+ private CompactVector3Array scales;
+
+ /**
+ * The times of the animations frames.
+ */
+ private float[] times;
+
+ public SpatialTrack() {
+ }
+
+ /**
+ * Creates a spatial track for the given track data.
+ *
+ * @param times
+ * a float array with the time of each frame
+ * @param translations
+ * the translation of the bone for each frame
+ * @param rotations
+ * the rotation of the bone for each frame
+ * @param scales
+ * the scale of the bone for each frame
+ */
+ public SpatialTrack(float[] times, Vector3f[] translations,
+ Quaternion[] rotations, Vector3f[] scales) {
+ setKeyframes(times, translations, rotations, scales);
+ }
+
+ /**
+ *
+ * Modify the spatial which this track modifies.
+ *
+ * @param time
+ * the current time of the animation
+ */
+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
+ Spatial spatial = control.getSpatial();
+
+ Vector3f tempV = vars.vect1;
+ Vector3f tempS = vars.vect2;
+ Quaternion tempQ = vars.quat1;
+ Vector3f tempV2 = vars.vect3;
+ Vector3f tempS2 = vars.vect4;
+ Quaternion tempQ2 = vars.quat2;
+
+ int lastFrame = times.length - 1;
+ if (time < 0 || lastFrame == 0) {
+ if (rotations != null)
+ rotations.get(0, tempQ);
+ if (translations != null)
+ translations.get(0, tempV);
+ if (scales != null) {
+ scales.get(0, tempS);
+ }
+ } else if (time >= times[lastFrame]) {
+ if (rotations != null)
+ rotations.get(lastFrame, tempQ);
+ if (translations != null)
+ translations.get(lastFrame, tempV);
+ if (scales != null) {
+ scales.get(lastFrame, tempS);
+ }
+ } else {
+ int startFrame = 0;
+ int endFrame = 1;
+ // use lastFrame so we never overflow the array
+ for (int i = 0; i < lastFrame && times[i] < time; ++i) {
+ startFrame = i;
+ endFrame = i + 1;
+ }
+
+ float blend = (time - times[startFrame]) / (times[endFrame] - times[startFrame]);
+
+ if (rotations != null)
+ rotations.get(startFrame, tempQ);
+ if (translations != null)
+ translations.get(startFrame, tempV);
+ if (scales != null) {
+ scales.get(startFrame, tempS);
+ }
+ if (rotations != null)
+ rotations.get(endFrame, tempQ2);
+ if (translations != null)
+ translations.get(endFrame, tempV2);
+ if (scales != null) {
+ scales.get(endFrame, tempS2);
+ }
+ tempQ.nlerp(tempQ2, blend);
+ tempV.interpolate(tempV2, blend);
+ tempS.interpolate(tempS2, blend);
+ }
+
+ if (translations != null)
+ spatial.setLocalTranslation(tempV);
+ if (rotations != null)
+ spatial.setLocalRotation(tempQ);
+ if (scales != null) {
+ spatial.setLocalScale(tempS);
+ }
+ }
+
+ /**
+ * Set the translations, rotations and scales for this track.
+ *
+ * @param times
+ * a float array with the time of each frame
+ * @param translations
+ * the translation of the bone for each frame
+ * @param rotations
+ * the rotation of the bone for each frame
+ * @param scales
+ * the scale of the bone for each frame
+ */
+ public void setKeyframes(float[] times, Vector3f[] translations,
+ Quaternion[] rotations, Vector3f[] scales) {
+ if (times.length == 0) {
+ throw new RuntimeException("BoneTrack with no keyframes!");
+ }
+
+ this.times = times;
+ if (translations != null) {
+ assert times.length == translations.length;
+ this.translations = new CompactVector3Array();
+ this.translations.add(translations);
+ this.translations.freeze();
+ }
+ if (rotations != null) {
+ assert times.length == rotations.length;
+ this.rotations = new CompactQuaternionArray();
+ this.rotations.add(rotations);
+ this.rotations.freeze();
+ }
+ if (scales != null) {
+ assert times.length == scales.length;
+ this.scales = new CompactVector3Array();
+ this.scales.add(scales);
+ this.scales.freeze();
+ }
+ }
+
+ /**
+ * @return the array of rotations of this track
+ */
+ public Quaternion[] getRotations() {
+ return rotations == null ? null : rotations.toObjectArray();
+ }
+
+ /**
+ * @return the array of scales for this track
+ */
+ public Vector3f[] getScales() {
+ return scales == null ? null : scales.toObjectArray();
+ }
+
+ /**
+ * @return the arrays of time for this track
+ */
+ public float[] getTimes() {
+ return times;
+ }
+
+ /**
+ * @return the array of translations of this track
+ */
+ public Vector3f[] getTranslations() {
+ return translations == null ? null : translations.toObjectArray();
+ }
+
+ /**
+ * @return the length of the track
+ */
+ public float getLength() {
+ return times == null ? 0 : times[times.length - 1] - times[0];
+ }
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ @Override
+ public SpatialTrack clone() {
+ int tablesLength = times.length;
+
+ float[] timesCopy = this.times.clone();
+ Vector3f[] translationsCopy = this.getTranslations() == null ? null : Arrays.copyOf(this.getTranslations(), tablesLength);
+ Quaternion[] rotationsCopy = this.getRotations() == null ? null : Arrays.copyOf(this.getRotations(), tablesLength);
+ Vector3f[] scalesCopy = this.getScales() == null ? null : Arrays.copyOf(this.getScales(), tablesLength);
+
+ //need to use the constructor here because of the final fields used in this class
+ return new SpatialTrack(timesCopy, translationsCopy, rotationsCopy, scalesCopy);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(translations, "translations", null);
+ oc.write(rotations, "rotations", null);
+ oc.write(times, "times", null);
+ oc.write(scales, "scales", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ translations = (CompactVector3Array) ic.readSavable("translations", null);
+ rotations = (CompactQuaternionArray) ic.readSavable("rotations", null);
+ times = ic.readFloatArray("times", null);
+ scales = (CompactVector3Array) ic.readSavable("scales", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/animation/Track.java b/engine/src/core/com/jme3/animation/Track.java
new file mode 100644
index 0000000..a56807d
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/Track.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.Savable;
+import com.jme3.util.TempVars;
+
+public interface Track extends Savable, Cloneable {
+
+ /**
+ * Sets the time of the animation.
+ *
+ * Internally, the track will retrieve objects from the control
+ * and modify them according to the properties of the channel and the
+ * given parameters.
+ *
+ * @param time The time in the animation
+ * @param weight The weight from 0 to 1 on how much to apply the track
+ * @param control The control which the track should effect
+ * @param channel The channel which the track should effect
+ */
+ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars);
+
+ /**
+ * @return the length of the track
+ */
+ public float getLength();
+
+ /**
+ * This method creates a clone of the current object.
+ * @return a clone of the current object
+ */
+ public Track clone();
+}
diff --git a/engine/src/core/com/jme3/animation/package.html b/engine/src/core/com/jme3/animation/package.html
new file mode 100644
index 0000000..554bab9
--- /dev/null
+++ b/engine/src/core/com/jme3/animation/package.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.animation</code> package contains various classes
+for managing animation inside a jME3 application. Currently, the majority
+of classes are for handling skeletal animation. The primary control class is
+the {@link com.jme3.animation.AnimControl}, through which animations can be played,
+looped, combined, transitioned, etc.
+
+<h3>Usage</h3>
+
+<p>
+<code>
+// Create or load a model with skeletal animation:<br>
+Spatial model = assetManager.loadModel("...");<br>
+<br>
+// Retrieve the AnimControl.<br>
+AnimControl animCtrl = model.getControl(AnimControl.class);<br>
+<br>
+// Create an animation channel, by default assigned to all bones.<br>
+AnimChannel animChan = animCtrl.createChannel();<br>
+<br>
+// Play an animation<br>
+animChan.setAnim("MyAnim");<br>
+</code>
+<br>
+<h3>Skeletal Animation System</h3>
+<br>
+<p>
+jME3 uses a system of bone-weights: A vertex is assigned up to 4 bones by which
+it is influenced and 4 weights that describe how much the bone influences the
+vertex. The maximum weight value being 1.0, and the requirement that all 4 weights
+for a given vertex <em>must</em> sum to 1.0. This data is specified
+for each skin/mesh that is influenced by the animation control via the
+{link com.jme3.scene.VertexBuffer}s <code>BoneWeight</code> and <code>BoneIndex</code>.
+The BoneIndex buffer must be of the format <code>UnsignedByte</code>, thus
+placing the limit of up to 256 bones for a skeleton. The BoneWeight buffer
+should be of the format <code>Float</code>. Both buffers should reference 4
+bones, even if the maximum number of bones any vertex is influenced is less or more
+than 4.<br>
+If a vertex is influenced by less than 4 bones, the indices following the last
+valid bone should be 0 and the weights following the last valid bone should be 0.0.
+The buffers are designed in such a way so as to permit hardware skinning.<br>
+<p>
+The {@link com.jme3.animation.Skeleton} class describes a bone heirarchy with one
+or more root bones having children, thus containing all bones of the skeleton.
+In addition to accessing the bones in the skeleton via the tree heirarchy, it
+is also possible to access bones via index. The index for any given bone is
+arbitrary and does not depend on the bone's location in the tree hierarchy.
+It is this index that is specified in the BoneIndex VertexBuffer mentioned above
+, and is also used to apply transformations to the bones through the animations.<br>
+<p>
+Every bone has a local and model space transformation. The local space
+transformation is relative to its parent, if it has one, otherwise it is relative
+to the model. The model space transformation is relative to model space.
+The bones additionally have a bind pose transformation, which describes
+the transformations for bones when no animated pose is applied to the skeleton.
+All bones <em>must</em> have a bind pose transformation before they can be
+animated. To set the bind pose for the skeleton, set the local (relative
+to parent) transformations for all the bones using the call
+{@link com.jme3.animation.Bone#setBindTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion) }.
+Then call {@link com.jme3.animation.Skeleton#updateWorldVectors() } followed by
+{@link com.jme3.animation.Skeleton#setBindingPose() }. <br>
+<p>
+Animations are stored in a HashMap object, accessed by name. An animation
+is simply a list of tracks, each track describes a timeline with each keyframe
+having a transformation. For bone animations, every track is assigned to a bone,
+while for morph animations, every track is assigned to a mesh.<br>
+<p>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/app/AppTask.java b/engine/src/core/com/jme3/app/AppTask.java
new file mode 100644
index 0000000..1b1b68c
--- /dev/null
+++ b/engine/src/core/com/jme3/app/AppTask.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AppTask</code> is used in <code>AppTaskQueue</code> to manage tasks that have
+ * yet to be accomplished. The AppTask system is used to execute tasks either
+ * in the OpenGL/Render thread, or outside of it.
+ *
+ * @author Matthew D. Hicks, lazloh
+ */
+public class AppTask<V> implements Future<V> {
+ private static final Logger logger = Logger.getLogger(AppTask.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();
+
+ /**
+ * Create an <code>AppTask</code> that will execute the given
+ * {@link Callable}.
+ *
+ * @param callable The callable to be executed
+ */
+ public AppTask(Callable<V> callable) {
+ this.callable = callable;
+ }
+
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ 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(long timeout, 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 (Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "invoke()", "Exception", e);
+
+ stateLock.lock();
+ try {
+ exception = new ExecutionException(e);
+
+ finishedCondition.signalAll();
+ } finally {
+ stateLock.unlock();
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/app/Application.java b/engine/src/core/com/jme3/app/Application.java
new file mode 100644
index 0000000..517ec61
--- /dev/null
+++ b/engine/src/core/com/jme3/app/Application.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app;
+
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioContext;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.Listener;
+import com.jme3.input.*;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The <code>Application</code> class represents an instance of a
+ * real-time 3D rendering jME application.
+ *
+ * An <code>Application</code> provides all the tools that are commonly used in jME3
+ * applications.
+ *
+ * jME3 applications should extend this class and call start() to begin the
+ * application.
+ *
+ */
+public class Application implements SystemListener {
+
+ private static final Logger logger = Logger.getLogger(Application.class.getName());
+
+ protected AssetManager assetManager;
+
+ protected AudioRenderer audioRenderer;
+ protected Renderer renderer;
+ protected RenderManager renderManager;
+ protected ViewPort viewPort;
+ protected ViewPort guiViewPort;
+
+ protected JmeContext context;
+ protected AppSettings settings;
+ protected Timer timer = new NanoTimer();
+ protected Camera cam;
+ protected Listener listener;
+
+ protected boolean inputEnabled = true;
+ protected boolean pauseOnFocus = true;
+ protected float speed = 1f;
+ protected boolean paused = false;
+ protected MouseInput mouseInput;
+ protected KeyInput keyInput;
+ protected JoyInput joyInput;
+ protected TouchInput touchInput;
+ protected InputManager inputManager;
+ protected AppStateManager stateManager;
+
+ private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+
+ /**
+ * Create a new instance of <code>Application</code>.
+ */
+ public Application(){
+ initStateManager();
+ }
+
+ /**
+ * Returns true if pause on lost focus is enabled, false otherwise.
+ *
+ * @return true if pause on lost focus is enabled
+ *
+ * @see #setPauseOnLostFocus(boolean)
+ */
+ public boolean isPauseOnLostFocus() {
+ return pauseOnFocus;
+ }
+
+ /**
+ * Enable or disable pause on lost focus.
+ * <p>
+ * By default, pause on lost focus is enabled.
+ * If enabled, the application will stop updating
+ * when it loses focus or becomes inactive (e.g. alt-tab).
+ * For online or real-time applications, this might not be preferable,
+ * so this feature should be set to disabled. For other applications,
+ * it is best to keep it on so that CPU usage is not used when
+ * not necessary.
+ *
+ * @param pauseOnLostFocus True to enable pause on lost focus, false
+ * otherwise.
+ */
+ public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
+ this.pauseOnFocus = pauseOnLostFocus;
+ }
+
+ @Deprecated
+ public void setAssetManager(AssetManager assetManager){
+ if (this.assetManager != null)
+ throw new IllegalStateException("Can only set asset manager"
+ + " before initialization.");
+
+ this.assetManager = assetManager;
+ }
+
+ private void initAssetManager(){
+ if (settings != null){
+ String assetCfg = settings.getString("AssetConfigURL");
+ if (assetCfg != null){
+ URL url = null;
+ try {
+ url = new URL(assetCfg);
+ } catch (MalformedURLException ex) {
+ }
+ if (url == null) {
+ url = Application.class.getClassLoader().getResource(assetCfg);
+ if (url == null) {
+ logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
+ return;
+ }
+ }
+ assetManager = JmeSystem.newAssetManager(url);
+ }
+ }
+ if (assetManager == null){
+ assetManager = JmeSystem.newAssetManager(
+ Thread.currentThread().getContextClassLoader()
+ .getResource("com/jme3/asset/Desktop.cfg"));
+ }
+ }
+
+ /**
+ * Set the display settings to define the display created.
+ * <p>
+ * Examples of display parameters include display pixel width and height,
+ * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
+ * If this method is called while the application is already running, then
+ * {@link #restart() } must be called to apply the settings to the display.
+ *
+ * @param settings The settings to set.
+ */
+ public void setSettings(AppSettings settings){
+ this.settings = settings;
+ if (context != null && settings.useInput() != inputEnabled){
+ // may need to create or destroy input based
+ // on settings change
+ inputEnabled = !inputEnabled;
+ if (inputEnabled){
+ initInput();
+ }else{
+ destroyInput();
+ }
+ }else{
+ inputEnabled = settings.useInput();
+ }
+ }
+
+ /**
+ * Sets the Timer implementation that will be used for calculating
+ * frame times. By default, Application will use the Timer as returned
+ * by the current JmeContext implementation.
+ */
+ public void setTimer(Timer timer){
+ this.timer = timer;
+
+ if (timer != null) {
+ timer.reset();
+ }
+
+ if (renderManager != null) {
+ renderManager.setTimer(timer);
+ }
+ }
+
+ public Timer getTimer(){
+ return timer;
+ }
+
+ private void initDisplay(){
+ // aquire important objects
+ // from the context
+ settings = context.getSettings();
+
+ // Only reset the timer if a user has not already provided one
+ if (timer == null) {
+ timer = context.getTimer();
+ }
+
+ renderer = context.getRenderer();
+ }
+
+ private void initAudio(){
+ if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
+ audioRenderer = JmeSystem.newAudioRenderer(settings);
+ audioRenderer.initialize();
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ listener = new Listener();
+ audioRenderer.setListener(listener);
+ }
+ }
+
+ /**
+ * Creates the camera to use for rendering. Default values are perspective
+ * projection with 45° field of view, with near and far values 1 and 1000
+ * units respectively.
+ */
+ private void initCamera(){
+ cam = new Camera(settings.getWidth(), settings.getHeight());
+
+ cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
+ cam.setLocation(new Vector3f(0f, 0f, 10f));
+ cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
+
+ renderManager = new RenderManager(renderer);
+ //Remy - 09/14/2010 setted the timer in the renderManager
+ renderManager.setTimer(timer);
+ viewPort = renderManager.createMainView("Default", cam);
+ viewPort.setClearFlags(true, true, true);
+
+ // Create a new cam for the gui
+ Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
+ guiViewPort = renderManager.createPostView("Gui Default", guiCam);
+ guiViewPort.setClearFlags(false, false, false);
+ }
+
+ /**
+ * Initializes mouse and keyboard input. Also
+ * initializes joystick input if joysticks are enabled in the
+ * AppSettings.
+ */
+ private void initInput(){
+ mouseInput = context.getMouseInput();
+ if (mouseInput != null)
+ mouseInput.initialize();
+
+ keyInput = context.getKeyInput();
+ if (keyInput != null)
+ keyInput.initialize();
+
+ touchInput = context.getTouchInput();
+ if (touchInput != null)
+ touchInput.initialize();
+
+ if (!settings.getBoolean("DisableJoysticks")){
+ joyInput = context.getJoyInput();
+ if (joyInput != null)
+ joyInput.initialize();
+ }
+
+ inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
+ }
+
+ private void initStateManager(){
+ stateManager = new AppStateManager(this);
+ }
+
+ /**
+ * @return The {@link AssetManager asset manager} for this application.
+ */
+ public AssetManager getAssetManager(){
+ return assetManager;
+ }
+
+ /**
+ * @return the {@link InputManager input manager}.
+ */
+ public InputManager getInputManager(){
+ return inputManager;
+ }
+
+ /**
+ * @return the {@link AppStateManager app state manager}
+ */
+ public AppStateManager getStateManager() {
+ return stateManager;
+ }
+
+ /**
+ * @return the {@link RenderManager render manager}
+ */
+ public RenderManager getRenderManager() {
+ return renderManager;
+ }
+
+ /**
+ * @return The {@link Renderer renderer} for the application
+ */
+ public Renderer getRenderer(){
+ return renderer;
+ }
+
+ /**
+ * @return The {@link AudioRenderer audio renderer} for the application
+ */
+ public AudioRenderer getAudioRenderer() {
+ return audioRenderer;
+ }
+
+ /**
+ * @return The {@link Listener listener} object for audio
+ */
+ public Listener getListener() {
+ return listener;
+ }
+
+ /**
+ * @return The {@link JmeContext display context} for the application
+ */
+ public JmeContext getContext(){
+ return context;
+ }
+
+ /**
+ * @return The {@link Camera camera} for the application
+ */
+ public Camera getCamera(){
+ return cam;
+ }
+
+ /**
+ * Starts the application in {@link Type#Display display} mode.
+ *
+ * @see #start(com.jme3.system.JmeContext.Type)
+ */
+ public void start(){
+ start(JmeContext.Type.Display);
+ }
+
+ /**
+ * Starts the application.
+ * Creating a rendering context and executing
+ * the main loop in a separate thread.
+ */
+ public void start(JmeContext.Type contextType){
+ if (context != null && context.isCreated()){
+ logger.warning("start() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, contextType);
+ context.setSystemListener(this);
+ context.create(false);
+ }
+
+ /**
+ * Initializes the application's canvas for use.
+ * <p>
+ * After calling this method, cast the {@link #getContext() context} to
+ * {@link JmeCanvasContext},
+ * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
+ * and attach it to an AWT/Swing Frame.
+ * The rendering thread will start when the canvas becomes visible on
+ * screen, however if you wish to start the context immediately you
+ * may call {@link #startCanvas() } to force the rendering thread
+ * to start.
+ *
+ * @see JmeCanvasContext
+ * @see Type#Canvas
+ */
+ public void createCanvas(){
+ if (context != null && context.isCreated()){
+ logger.warning("createCanvas() called when application already created!");
+ return;
+ }
+
+ if (settings == null){
+ settings = new AppSettings(true);
+ }
+
+ logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
+ context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
+ context.setSystemListener(this);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ * <p>
+ * Same as calling startCanvas(false)
+ *
+ * @see #startCanvas(boolean)
+ */
+ public void startCanvas(){
+ startCanvas(false);
+ }
+
+ /**
+ * Starts the rendering thread after createCanvas() has been called.
+ * <p>
+ * Calling this method is optional, the canvas will start automatically
+ * when it becomes visible.
+ *
+ * @param waitFor If true, the current thread will block until the
+ * rendering thread is running
+ */
+ public void startCanvas(boolean waitFor){
+ context.create(waitFor);
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void reshape(int w, int h){
+ renderManager.notifyReshape(w, h);
+ }
+
+ /**
+ * Restarts the context, applying any changed settings.
+ * <p>
+ * Changes to the {@link AppSettings} of this Application are not
+ * applied immediately; calling this method forces the context
+ * to restart, applying the new settings.
+ */
+ public void restart(){
+ context.setSettings(settings);
+ context.restart();
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ *
+ * Same as calling stop(false)
+ *
+ * @see #stop(boolean)
+ */
+ public void stop(){
+ stop(false);
+ }
+
+ /**
+ * Requests the context to close, shutting down the main loop
+ * and making necessary cleanup operations.
+ * After the application has stopped, it cannot be used anymore.
+ */
+ public void stop(boolean waitFor){
+ logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
+ context.destroy(waitFor);
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ * <p>
+ * Initializes the <code>Application</code>, by creating a display and
+ * default camera. If display settings are not specified, a default
+ * 640x480 display is created. Default values are used for the camera;
+ * perspective projection with 45° field of view, with near
+ * and far values 1 and 1000 units respectively.
+ */
+ public void initialize(){
+ if (assetManager == null){
+ initAssetManager();
+ }
+
+ initDisplay();
+ initCamera();
+
+ if (inputEnabled){
+ initInput();
+ }
+ initAudio();
+
+ // update timer so that the next delta is not too large
+// timer.update();
+ timer.reset();
+
+ // user code here..
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void handleError(String errMsg, Throwable t){
+ logger.log(Level.SEVERE, errMsg, t);
+ // user should add additional code to handle the error.
+ stop(); // stop the application
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void gainFocus(){
+ if (pauseOnFocus) {
+ paused = false;
+ context.setAutoFlushFrames(true);
+ if (inputManager != null) {
+ inputManager.reset();
+ }
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void loseFocus(){
+ if (pauseOnFocus){
+ paused = true;
+ context.setAutoFlushFrames(false);
+ }
+ }
+
+ /**
+ * Internal use only.
+ */
+ public void requestClose(boolean esc){
+ context.destroy(false);
+ }
+
+ /**
+ * Enqueues a task/callable object to execute in the jME3
+ * rendering thread.
+ * <p>
+ * Callables are executed right at the beginning of the main loop.
+ * They are executed even if the application is currently paused
+ * or out of focus.
+ */
+ public <V> Future<V> enqueue(Callable<V> callable) {
+ AppTask<V> task = new AppTask<V>(callable);
+ taskQueue.add(task);
+ return task;
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void update(){
+ // Make sure the audio renderer is available to callables
+ AudioContext.setAudioRenderer(audioRenderer);
+
+ AppTask<?> task = taskQueue.poll();
+ toploop: do {
+ if (task == null) break;
+ while (task.isCancelled()) {
+ task = taskQueue.poll();
+ if (task == null) break toploop;
+ }
+ task.invoke();
+ } while (((task = taskQueue.poll()) != null));
+
+ if (speed == 0 || paused)
+ return;
+
+ timer.update();
+
+ if (inputEnabled){
+ inputManager.update(timer.getTimePerFrame());
+ }
+
+ if (audioRenderer != null){
+ audioRenderer.update(timer.getTimePerFrame());
+ }
+
+ // user code here..
+ }
+
+ protected void destroyInput(){
+ if (mouseInput != null)
+ mouseInput.destroy();
+
+ if (keyInput != null)
+ keyInput.destroy();
+
+ if (joyInput != null)
+ joyInput.destroy();
+
+ if (touchInput != null)
+ touchInput.destroy();
+
+ inputManager = null;
+ }
+
+ /**
+ * Do not call manually.
+ * Callback from ContextListener.
+ */
+ public void destroy(){
+ stateManager.cleanup();
+
+ destroyInput();
+ if (audioRenderer != null)
+ audioRenderer.cleanup();
+
+ timer.reset();
+ }
+
+ /**
+ * @return The GUI viewport. Which is used for the on screen
+ * statistics and FPS.
+ */
+ public ViewPort getGuiViewPort() {
+ return guiViewPort;
+ }
+
+ public ViewPort getViewPort() {
+ return viewPort;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/app/DebugKeysAppState.java b/engine/src/core/com/jme3/app/DebugKeysAppState.java
new file mode 100644
index 0000000..2317c28
--- /dev/null
+++ b/engine/src/core/com/jme3/app/DebugKeysAppState.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.input.InputManager;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.util.BufferUtils;
+
+
+/**
+ * Registers a few keys that will dump debug information
+ * to the console.
+ *
+ * @author Paul Speed
+ */
+public class DebugKeysAppState extends AbstractAppState {
+
+ public static final String INPUT_MAPPING_CAMERA_POS = "SIMPLEAPP_CameraPos";
+ public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory";
+
+ private Application app;
+ private DebugKeyListener keyListener = new DebugKeyListener();
+ private InputManager inputManager;
+
+ public DebugKeysAppState() {
+ }
+
+ @Override
+ public void initialize(AppStateManager stateManager, Application app) {
+ super.initialize(stateManager, app);
+
+ this.app = app;
+ this.inputManager = app.getInputManager();
+
+ if (app.getInputManager() != null) {
+
+ inputManager.addMapping(INPUT_MAPPING_CAMERA_POS, new KeyTrigger(KeyInput.KEY_C));
+ inputManager.addMapping(INPUT_MAPPING_MEMORY, new KeyTrigger(KeyInput.KEY_M));
+
+ inputManager.addListener(keyListener,
+ INPUT_MAPPING_CAMERA_POS,
+ INPUT_MAPPING_MEMORY);
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+
+ if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS))
+ inputManager.deleteMapping(INPUT_MAPPING_CAMERA_POS);
+ if (inputManager.hasMapping(INPUT_MAPPING_MEMORY))
+ inputManager.deleteMapping(INPUT_MAPPING_MEMORY);
+
+ inputManager.removeListener(keyListener);
+ }
+
+
+ private class DebugKeyListener implements ActionListener {
+
+ public void onAction(String name, boolean value, float tpf) {
+ if (!value) {
+ return;
+ }
+
+ if (name.equals(INPUT_MAPPING_CAMERA_POS)) {
+ Camera cam = app.getCamera();
+ if (cam != null) {
+ Vector3f loc = cam.getLocation();
+ Quaternion rot = cam.getRotation();
+ System.out.println("Camera Position: ("
+ + loc.x + ", " + loc.y + ", " + loc.z + ")");
+ System.out.println("Camera Rotation: " + rot);
+ System.out.println("Camera Direction: " + cam.getDirection());
+ }
+ } else if (name.equals(INPUT_MAPPING_MEMORY)) {
+ BufferUtils.printCurrentDirectMemory(null);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/app/FlyCamAppState.java b/engine/src/core/com/jme3/app/FlyCamAppState.java
new file mode 100644
index 0000000..5a7b11e
--- /dev/null
+++ b/engine/src/core/com/jme3/app/FlyCamAppState.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.input.FlyByCamera;
+
+
+/**
+ * Manages a FlyByCamera.
+ *
+ * @author Paul Speed
+ */
+public class FlyCamAppState extends AbstractAppState {
+
+ private Application app;
+ private FlyByCamera flyCam;
+
+ public FlyCamAppState() {
+ }
+
+ /**
+ * This is called by SimpleApplication during initialize().
+ */
+ void setCamera( FlyByCamera cam ) {
+ this.flyCam = cam;
+ }
+
+ public FlyByCamera getCamera() {
+ return flyCam;
+ }
+
+ @Override
+ public void initialize(AppStateManager stateManager, Application app) {
+ super.initialize(stateManager, app);
+
+ this.app = app;
+
+ if (app.getInputManager() != null) {
+
+ if (flyCam == null) {
+ flyCam = new FlyByCamera(app.getCamera());
+ }
+
+ flyCam.registerWithInput(app.getInputManager());
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ flyCam.setEnabled(enabled);
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+
+ flyCam.unregisterInput();
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/app/SimpleApplication.java b/engine/src/core/com/jme3/app/SimpleApplication.java
new file mode 100644
index 0000000..c79ce83
--- /dev/null
+++ b/engine/src/core/com/jme3/app/SimpleApplication.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AppState;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.input.FlyByCamera;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+import com.jme3.system.AppSettings;
+import com.jme3.system.JmeContext.Type;
+import com.jme3.system.JmeSystem;
+import com.jme3.util.BufferUtils;
+
+/**
+ * <code>SimpleApplication</code> extends the {@link com.jme3.app.Application}
+ * class to provide default functionality like a first-person camera,
+ * and an accessible root node that is updated and rendered regularly.
+ * Additionally, <code>SimpleApplication</code> will display a statistics view
+ * using the {@link com.jme3.app.StatsView} class. It will display
+ * the current frames-per-second value on-screen in addition to the statistics.
+ * Several keys have special functionality in <code>SimpleApplication</code>:<br/>
+ *
+ * <table>
+ * <tr><td>Esc</td><td>- Close the application</td></tr>
+ * <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr>
+ * <tr><td>M</td><td>- Display memory usage in the console.</td></tr>
+ * </table>
+ */
+public abstract class SimpleApplication extends Application {
+
+ public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
+ public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
+ public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
+ public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
+
+ protected Node rootNode = new Node("Root Node");
+ protected Node guiNode = new Node("Gui Node");
+ protected BitmapText fpsText;
+ protected BitmapFont guiFont;
+ protected FlyByCamera flyCam;
+ protected boolean showSettings = true;
+ private AppActionListener actionListener = new AppActionListener();
+
+ private class AppActionListener implements ActionListener {
+
+ public void onAction(String name, boolean value, float tpf) {
+ if (!value) {
+ return;
+ }
+
+ if (name.equals(INPUT_MAPPING_EXIT)) {
+ stop();
+ }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){
+ if (stateManager.getState(StatsAppState.class) != null) {
+ stateManager.getState(StatsAppState.class).toggleStats();
+ }
+ }
+ }
+ }
+
+ public SimpleApplication() {
+ this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
+ }
+
+ public SimpleApplication( AppState... initialStates ) {
+ super();
+
+ if (initialStates != null) {
+ for (AppState a : initialStates) {
+ if (a != null) {
+ stateManager.attach(a);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void start() {
+ // set some default settings in-case
+ // settings dialog is not shown
+ boolean loadSettings = false;
+ if (settings == null) {
+ setSettings(new AppSettings(true));
+ loadSettings = true;
+ }
+
+ // show settings dialog
+ if (showSettings) {
+ if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
+ return;
+ }
+ }
+ //re-setting settings they can have been merged from the registry.
+ setSettings(settings);
+ super.start();
+ }
+
+ /**
+ * Retrieves flyCam
+ * @return flyCam Camera object
+ *
+ */
+ public FlyByCamera getFlyByCamera() {
+ return flyCam;
+ }
+
+ /**
+ * Retrieves guiNode
+ * @return guiNode Node object
+ *
+ */
+ public Node getGuiNode() {
+ return guiNode;
+ }
+
+ /**
+ * Retrieves rootNode
+ * @return rootNode Node object
+ *
+ */
+ public Node getRootNode() {
+ return rootNode;
+ }
+
+ public boolean isShowSettings() {
+ return showSettings;
+ }
+
+ /**
+ * Toggles settings window to display at start-up
+ * @param showSettings Sets true/false
+ *
+ */
+ public void setShowSettings(boolean showSettings) {
+ this.showSettings = showSettings;
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+
+ // Several things rely on having this
+ guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+
+ guiNode.setQueueBucket(Bucket.Gui);
+ guiNode.setCullHint(CullHint.Never);
+ viewPort.attachScene(rootNode);
+ guiViewPort.attachScene(guiNode);
+
+ if (inputManager != null) {
+
+ // We have to special-case the FlyCamAppState because too
+ // many SimpleApplication subclasses expect it to exist in
+ // simpleInit(). But at least it only gets initialized if
+ // the app state is added.
+ if (stateManager.getState(FlyCamAppState.class) != null) {
+ flyCam = new FlyByCamera(cam);
+ flyCam.setMoveSpeed(1f); // odd to set this here but it did it before
+ stateManager.getState(FlyCamAppState.class).setCamera( flyCam );
+ }
+
+ if (context.getType() == Type.Display) {
+ inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
+ }
+
+ if (stateManager.getState(StatsAppState.class) != null) {
+ inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
+ inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
+ }
+
+ inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
+ }
+
+ if (stateManager.getState(StatsAppState.class) != null) {
+ // Some of the tests rely on having access to fpsText
+ // for quick display. Maybe a different way would be better.
+ stateManager.getState(StatsAppState.class).setFont(guiFont);
+ fpsText = stateManager.getState(StatsAppState.class).getFpsText();
+ }
+
+ // call user code
+ simpleInitApp();
+ }
+
+ @Override
+ public void update() {
+ super.update(); // makes sure to execute AppTasks
+ if (speed == 0 || paused) {
+ return;
+ }
+
+ float tpf = timer.getTimePerFrame() * speed;
+
+ // update states
+ stateManager.update(tpf);
+
+ // simple update and root node
+ simpleUpdate(tpf);
+
+ rootNode.updateLogicalState(tpf);
+ guiNode.updateLogicalState(tpf);
+
+ rootNode.updateGeometricState();
+ guiNode.updateGeometricState();
+
+ // Moving this here to make sure it is always done.
+ // Now the sets are cleared every frame (guaranteed)
+ // and more than one viewer can access the data. This
+ // used to be cleared by StatsView but then only StatsView
+ // could get accurate counts.
+ renderer.getStatistics().clearFrame();
+
+ // render states
+ stateManager.render(renderManager);
+ renderManager.render(tpf, context.isRenderable());
+ simpleRender(renderManager);
+ stateManager.postRender();
+ }
+
+ public void setDisplayFps(boolean show) {
+ if (stateManager.getState(StatsAppState.class) != null) {
+ stateManager.getState(StatsAppState.class).setDisplayFps(show);
+ }
+ }
+
+ public void setDisplayStatView(boolean show) {
+ if (stateManager.getState(StatsAppState.class) != null) {
+ stateManager.getState(StatsAppState.class).setDisplayStatView(show);
+ }
+ }
+
+ public abstract void simpleInitApp();
+
+ public void simpleUpdate(float tpf) {
+ }
+
+ public void simpleRender(RenderManager rm) {
+ }
+}
diff --git a/engine/src/core/com/jme3/app/StatsAppState.java b/engine/src/core/com/jme3/app/StatsAppState.java
new file mode 100644
index 0000000..d4c968b
--- /dev/null
+++ b/engine/src/core/com/jme3/app/StatsAppState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.app;
+
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial.CullHint;
+
+
+/**
+ * Displays stats in SimpleApplication's GUI node or
+ * using the node and font parameters provided.
+ *
+ * @author Paul Speed
+ */
+public class StatsAppState extends AbstractAppState {
+
+ private Application app;
+ protected StatsView statsView;
+ protected boolean showSettings = true;
+ private boolean showFps = true;
+ private boolean showStats = true;
+
+ protected Node guiNode;
+ protected float secondCounter = 0.0f;
+ protected int frameCounter = 0;
+ protected BitmapText fpsText;
+ protected BitmapFont guiFont;
+
+ public StatsAppState() {
+ }
+
+ public StatsAppState( Node guiNode, BitmapFont guiFont ) {
+ this.guiNode = guiNode;
+ this.guiFont = guiFont;
+ }
+
+ /**
+ * Called by SimpleApplication to provide an early font
+ * so that the fpsText can be created before init. This
+ * is because several applications expect to directly access
+ * fpsText... unfortunately.
+ */
+ void setFont( BitmapFont guiFont ) {
+ this.guiFont = guiFont;
+ this.fpsText = new BitmapText(guiFont, false);
+ }
+
+ public BitmapText getFpsText() {
+ return fpsText;
+ }
+
+ public StatsView getStatsView() {
+ return statsView;
+ }
+
+ public float getSecondCounter() {
+ return secondCounter;
+ }
+
+ public void toggleStats() {
+ setDisplayFps( !showFps );
+ setDisplayStatView( !showStats );
+ }
+
+ public void setDisplayFps(boolean show) {
+ showFps = show;
+ if (fpsText != null) {
+ fpsText.setCullHint(show ? CullHint.Never : CullHint.Always);
+ }
+ }
+
+ public void setDisplayStatView(boolean show) {
+ showStats = show;
+ if (statsView != null ) {
+ statsView.setEnabled(show);
+ statsView.setCullHint(show ? CullHint.Never : CullHint.Always);
+ }
+ }
+
+ @Override
+ public void initialize(AppStateManager stateManager, Application app) {
+ super.initialize(stateManager, app);
+ this.app = app;
+
+ if (app instanceof SimpleApplication) {
+ SimpleApplication simpleApp = (SimpleApplication)app;
+ if (guiNode == null)
+ guiNode = simpleApp.guiNode;
+ if (guiFont == null )
+ guiFont = simpleApp.guiFont;
+ }
+
+ if (guiNode == null) {
+ throw new RuntimeException( "No guiNode specific and cannot be automatically determined." );
+ }
+
+ if (guiFont == null) {
+ guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
+ }
+
+ loadFpsText();
+ loadStatsView();
+ }
+
+ /**
+ * Attaches FPS statistics to guiNode and displays it on the screen.
+ *
+ */
+ public void loadFpsText() {
+ if (fpsText == null) {
+ fpsText = new BitmapText(guiFont, false);
+ }
+
+ fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
+ fpsText.setText("Frames per second");
+ fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
+ guiNode.attachChild(fpsText);
+ }
+
+ /**
+ * Attaches Statistics View to guiNode and displays it on the screen
+ * above FPS statistics line.
+ *
+ */
+ public void loadStatsView() {
+ statsView = new StatsView("Statistics View",
+ app.getAssetManager(),
+ app.getRenderer().getStatistics());
+ // move it up so it appears above fps text
+ statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0);
+ statsView.setEnabled(showStats);
+ statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
+ guiNode.attachChild(statsView);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ if (enabled) {
+ fpsText.setCullHint(showFps ? CullHint.Never : CullHint.Always);
+ statsView.setEnabled(showStats);
+ statsView.setCullHint(showStats ? CullHint.Never : CullHint.Always);
+ } else {
+ fpsText.setCullHint(CullHint.Always);
+ statsView.setEnabled(false);
+ statsView.setCullHint(CullHint.Always);
+ }
+ }
+
+ @Override
+ public void update(float tpf) {
+ if (showFps) {
+ secondCounter += app.getTimer().getTimePerFrame();
+ frameCounter ++;
+ if (secondCounter >= 1.0f) {
+ int fps = (int) (frameCounter / secondCounter);
+ fpsText.setText("Frames per second: " + fps);
+ secondCounter = 0.0f;
+ frameCounter = 0;
+ }
+ }
+ }
+
+ @Override
+ public void cleanup() {
+ super.cleanup();
+
+ guiNode.detachChild(statsView);
+ guiNode.detachChild(fpsText);
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/app/StatsView.java b/engine/src/core/com/jme3/app/StatsView.java
new file mode 100644
index 0000000..49eeb13
--- /dev/null
+++ b/engine/src/core/com/jme3/app/StatsView.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Statistics;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+
+/**
+ * The <code>StatsView</code> provides a heads-up display (HUD) of various
+ * statistics of rendering. The data is retrieved every frame from a
+ * {@link com.jme3.renderer.Statistics} and then displayed on screen.<br/>
+ * <br/>
+ * Usage:<br/>
+ * To use the stats view, you need to retrieve the
+ * {@link com.jme3.renderer.Statistics} from the
+ * {@link com.jme3.renderer.Renderer} used by the application. Then, attach
+ * the <code>StatsView</code> to the scene graph.<br/>
+ * <code><br/>
+ * Statistics stats = renderer.getStatistics();<br/>
+ * StatsView statsView = new StatsView("MyStats", assetManager, stats);<br/>
+ * rootNode.attachChild(statsView);<br/>
+ * </code>
+ */
+public class StatsView extends Node implements Control {
+
+ private BitmapText[] labels;
+ private Statistics statistics;
+
+ private String[] statLabels;
+ private int[] statData;
+
+ private boolean enabled = true;
+
+ private final StringBuilder stringBuilder = new StringBuilder();
+
+ public StatsView(String name, AssetManager manager, Statistics stats){
+ super(name);
+
+ setQueueBucket(Bucket.Gui);
+ setCullHint(CullHint.Never);
+
+ statistics = stats;
+
+ statLabels = statistics.getLabels();
+ statData = new int[statLabels.length];
+ labels = new BitmapText[statLabels.length];
+
+ BitmapFont font = manager.loadFont("Interface/Fonts/Console.fnt");
+ for (int i = 0; i < labels.length; i++){
+ labels[i] = new BitmapText(font);
+ labels[i].setLocalTranslation(0, labels[i].getLineHeight() * (i+1), 0);
+ attachChild(labels[i]);
+ }
+
+ addControl(this);
+ }
+
+ public void update(float tpf) {
+
+ if (!isEnabled())
+ return;
+
+ statistics.getData(statData);
+ for (int i = 0; i < labels.length; i++) {
+ stringBuilder.setLength(0);
+ stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]);
+ labels[i].setText(stringBuilder);
+ }
+
+ // Moved to SimpleApplication to make sure it is
+ // done even if there is no StatsView or the StatsView
+ // is disable.
+ //statistics.clearFrame();
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ return (Control) spatial;
+ }
+
+ public void setSpatial(Spatial spatial) {
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ }
+
+}
diff --git a/engine/src/core/com/jme3/app/package.html b/engine/src/core/com/jme3/app/package.html
new file mode 100644
index 0000000..ec6bb9a
--- /dev/null
+++ b/engine/src/core/com/jme3/app/package.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.application</code> provides a toolset for jME3 applications
+to interact with various components of the engine. Typically, the
+{@link com.jme3.app.Application} class will be extended and the update() method
+implemented to provide functionality for the main loop. <br>
+<p>
+An <code>Application</code> will typically provide the following services:
+<ul>
+ <li>{@link com.jme3.asset.AssetManager} - A system for finding and loading
+ data assets included with the application, such as models and textures.</li>
+ <li>{@link com.jme3.renderer.RenderManager} - A high-level rendering
+ interface for 3D graphics, manages viewports and scenes assigned
+ to the viewports, as well as general high-level rendering.</li>
+ <li>{@link com.jme3.input.InputManager} - An interface for handling input
+ from devices such as keyboard, mouse, and gamepad/joystick.</li>
+ <li>{@link com.jme3.app.state.AppStateManager} - Manager for
+ {@link com.jme3.app.state.AppState}s, which are specific application
+ functionality to be executed inside the main loop.</li>
+ <li>{@link com.jme3.audio.AudioRenderer} - Allows playing sound effects and
+ music.</li>
+ <li>{@link com.jme3.system.Timer} - The timer keeps track of time and allows
+ computing the time since the last frame (TPF) that is neccessary
+ for framerate-independent updates and motion.</li>
+ <li>{@link com.jme3.system.AppSettings} - A database containing various
+ settings for the application. These settings may be set by the user
+ or the application itself.</li>
+</ul>
+
+
+<h3>Usage</h3>
+
+An example use of the Application class is as follows<br>
+<br>
+
+<code>
+public class ExampleUse extends Application {<br>
+<br>
+ private Node rootNode = new Node("Root Node");<br>
+<br>
+ public static void main(String[] args){<br>
+ ExampleUse app = new ExampleUse();<br>
+ app.start();<br>
+ }<br>
+<br>
+ @Override<br>
+ public void initialize(){<br>
+ super.initialize();<br>
+<br>
+ // attach root node to viewport<br>
+ viewPort.attachScene(rootNode);<br>
+ }<br>
+<br>
+ @Override<br>
+ public void update(){<br>
+ super.update();<br>
+<br>
+ float tpf = timer.getTimePerFrame();<br>
+<br>
+ // update rootNode<br>
+ rootNode.updateLogicalState(tpf);<br>
+ rootNode.updateGeometricState();<br>
+<br>
+ // render the viewports<br>
+ renderManager.render(tpf);<br>
+ }<br>
+}<br>
+<br>
+</code>
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/app/state/AbstractAppState.java b/engine/src/core/com/jme3/app/state/AbstractAppState.java
new file mode 100644
index 0000000..1ea1230
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AbstractAppState.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+
+/**
+ * <code>AbstractAppState</code> implements some common methods
+ * that make creation of AppStates easier.
+ * @author Kirill Vainer
+ */
+public class AbstractAppState implements AppState {
+
+ /**
+ * <code>initialized</code> is set to true when the method
+ * {@link AbstractAppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) }
+ * is called. When {@link AbstractAppState#cleanup() } is called, <code>initialized</code>
+ * is set back to false.
+ */
+ protected boolean initialized = false;
+ private boolean enabled = true;
+
+ public void initialize(AppStateManager stateManager, Application app) {
+ initialized = true;
+ }
+
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void stateAttached(AppStateManager stateManager) {
+ }
+
+ public void stateDetached(AppStateManager stateManager) {
+ }
+
+ public void update(float tpf) {
+ }
+
+ public void render(RenderManager rm) {
+ }
+
+ public void postRender(){
+ }
+
+ public void cleanup() {
+ initialized = false;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/app/state/AppState.java b/engine/src/core/com/jme3/app/state/AppState.java
new file mode 100644
index 0000000..d94a35f
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AppState.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+
+/**
+ * AppState represents a continously executing code inside the main loop.
+ * An <code>AppState</code> can track when it is attached to the
+ * {@link AppStateManager} or when it is detached. <br/><code>AppState</code>s
+ * are initialized in the render thread, upon a call to {@link AppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) }
+ * and are de-initialized upon a call to {@link AppState#cleanup()}.
+ * Implementations should return the correct value with a call to
+ * {@link AppState#isInitialized() } as specified above.<br/>
+ *
+ *
+ * @author Kirill Vainer
+ */
+public interface AppState {
+
+ /**
+ * Called to initialize the AppState.
+ *
+ * @param stateManager The state manager
+ * @param app
+ */
+ public void initialize(AppStateManager stateManager, Application app);
+
+ /**
+ * @return True if <code>initialize()</code> was called on the state,
+ * false otherwise.
+ */
+ public boolean isInitialized();
+
+ /**
+ * Enable or disable the functionality of the <code>AppState</code>.
+ * The effect of this call depends on implementation. An
+ * <code>AppState</code> starts as being enabled by default.
+ *
+ * @param active activate the AppState or not.
+ */
+ public void setEnabled(boolean active);
+
+ /**
+ * @return True if the <code>AppState</code> is enabled, false otherwise.
+ *
+ * @see AppState#setEnabled(boolean)
+ */
+ public boolean isEnabled();
+ /**
+ * Called when the state was attached.
+ *
+ * @param stateManager State manager to which the state was attached to.
+ */
+ public void stateAttached(AppStateManager stateManager);
+
+ /**
+ * Called when the state was detached.
+ *
+ * @param stateManager The state manager from which the state was detached from.
+ */
+ public void stateDetached(AppStateManager stateManager);
+
+ /**
+ * Called to update the state.
+ *
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf);
+
+ /**
+ * Render the state.
+ *
+ * @param rm RenderManager
+ */
+ public void render(RenderManager rm);
+
+ /**
+ * Called after all rendering commands are flushed.
+ */
+ public void postRender();
+
+ /**
+ * Cleanup the game state.
+ */
+ public void cleanup();
+
+}
diff --git a/engine/src/core/com/jme3/app/state/AppStateManager.java b/engine/src/core/com/jme3/app/state/AppStateManager.java
new file mode 100644
index 0000000..81228af
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/AppStateManager.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.app.state;
+
+import com.jme3.app.Application;
+import com.jme3.renderer.RenderManager;
+import com.jme3.util.SafeArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The <code>AppStateManager</code> holds a list of {@link AppState}s which
+ * it will update and render.<br/>
+ * When an {@link AppState} is attached or detached, the
+ * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and
+ * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods
+ * will be called respectively.
+ *
+ * <p>The lifecycle for an attached AppState is as follows:</p>
+ * <ul>
+ * <li>stateAttached() : called when the state is attached on the thread on which
+ * the state was attached.
+ * <li>initialize() : called ONCE on the render thread at the beginning of the next
+ * AppStateManager.update().
+ * <li>stateDetached() : called when the state is attached on the thread on which
+ * the state was detached. This is not necessarily on the
+ * render thread and it is not necessarily safe to modify
+ * the scene graph, etc..
+ * <li>cleanup() : called ONCE on the render thread at the beginning of the next update
+ * after the state has been detached or when the application is
+ * terminating.
+ * </ul>
+ *
+ * @author Kirill Vainer, Paul Speed
+ */
+public class AppStateManager {
+
+ /**
+ * List holding the attached app states that are pending
+ * initialization. Once initialized they will be added to
+ * the running app states.
+ */
+ private final SafeArrayList<AppState> initializing = new SafeArrayList<AppState>(AppState.class);
+
+ /**
+ * Holds the active states once they are initialized.
+ */
+ private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);
+
+ /**
+ * List holding the detached app states that are pending
+ * cleanup.
+ */
+ private final SafeArrayList<AppState> terminating = new SafeArrayList<AppState>(AppState.class);
+
+ // All of the above lists need to be thread safe but access will be
+ // synchronized separately.... but always on the states list. This
+ // is to avoid deadlocking that may occur and the most common use case
+ // is that they are all modified from the same thread anyway.
+
+ private final Application app;
+ private AppState[] stateArray;
+
+ public AppStateManager(Application app){
+ this.app = app;
+ }
+
+ protected AppState[] getInitializing() {
+ synchronized (states){
+ return initializing.getArray();
+ }
+ }
+
+ protected AppState[] getTerminating() {
+ synchronized (states){
+ return terminating.getArray();
+ }
+ }
+
+ protected AppState[] getStates(){
+ synchronized (states){
+ return states.getArray();
+ }
+ }
+
+ /**
+ * Attach a state to the AppStateManager, the same state cannot be attached
+ * twice.
+ *
+ * @param state The state to attach
+ * @return True if the state was successfully attached, false if the state
+ * was already attached.
+ */
+ public boolean attach(AppState state){
+ synchronized (states){
+ if (!states.contains(state) && !initializing.contains(state)){
+ state.stateAttached(this);
+ initializing.add(state);
+ return true;
+ }else{
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Detaches the state from the AppStateManager.
+ *
+ * @param state The state to detach
+ * @return True if the state was detached successfully, false
+ * if the state was not attached in the first place.
+ */
+ public boolean detach(AppState state){
+ synchronized (states){
+ if (states.contains(state)){
+ state.stateDetached(this);
+ states.remove(state);
+ terminating.add(state);
+ return true;
+ } else if(initializing.contains(state)){
+ state.stateDetached(this);
+ initializing.remove(state);
+ return true;
+ }else{
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Check if a state is attached or not.
+ *
+ * @param state The state to check
+ * @return True if the state is currently attached to this AppStateManager.
+ *
+ * @see AppStateManager#attach(com.jme3.app.state.AppState)
+ */
+ public boolean hasState(AppState state){
+ synchronized (states){
+ return states.contains(state) || initializing.contains(state);
+ }
+ }
+
+ /**
+ * Returns the first state that is an instance of subclass of the specified class.
+ * @param <T>
+ * @param stateClass
+ * @return First attached state that is an instance of stateClass
+ */
+ public <T extends AppState> T getState(Class<T> stateClass){
+ synchronized (states){
+ AppState[] array = getStates();
+ for (AppState state : array) {
+ if (stateClass.isAssignableFrom(state.getClass())){
+ return (T) state;
+ }
+ }
+
+ // This may be more trouble than its worth but I think
+ // it's necessary for proper decoupling of states and provides
+ // similar behavior to before where a state could be looked
+ // up even if it wasn't initialized. -pspeed
+ array = getInitializing();
+ for (AppState state : array) {
+ if (stateClass.isAssignableFrom(state.getClass())){
+ return (T) state;
+ }
+ }
+ }
+ return null;
+ }
+
+ protected void initializePending(){
+ AppState[] array = getInitializing();
+ synchronized( states ) {
+ // Move the states that will be initialized
+ // into the active array. In all but one case the
+ // order doesn't matter but if we do this here then
+ // a state can detach itself in initialize(). If we
+ // did it after then it couldn't.
+ List<AppState> transfer = Arrays.asList(array);
+ states.addAll(transfer);
+ initializing.removeAll(transfer);
+ }
+ for (AppState state : array) {
+ state.initialize(this, app);
+ }
+ }
+
+ protected void terminatePending(){
+ AppState[] array = getTerminating();
+ for (AppState state : array) {
+ state.cleanup();
+ }
+ synchronized( states ) {
+ // Remove just the states that were terminated...
+ // which might now be a subset of the total terminating
+ // list.
+ terminating.removeAll(Arrays.asList(array));
+ }
+ }
+
+ /**
+ * Calls update for attached states, do not call directly.
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf){
+
+ // Cleanup any states pending
+ terminatePending();
+
+ // Initialize any states pending
+ initializePending();
+
+ // Update enabled states
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.update(tpf);
+ }
+ }
+ }
+
+ /**
+ * Calls render for all attached and initialized states, do not call directly.
+ * @param rm The RenderManager
+ */
+ public void render(RenderManager rm){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.render(rm);
+ }
+ }
+ }
+
+ /**
+ * Calls render for all attached and initialized states, do not call directly.
+ */
+ public void postRender(){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ if (state.isEnabled()) {
+ state.postRender();
+ }
+ }
+ }
+
+ /**
+ * Calls cleanup on attached states, do not call directly.
+ */
+ public void cleanup(){
+ AppState[] array = getStates();
+ for (AppState state : array){
+ state.cleanup();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/app/state/package.html b/engine/src/core/com/jme3/app/state/package.html
new file mode 100644
index 0000000..0e93b38
--- /dev/null
+++ b/engine/src/core/com/jme3/app/state/package.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.app.state</code> package provides
+{@link com.jme3.app.state.AppState app states},
+an abstract way of handling application logic.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/asset/Asset.java b/engine/src/core/com/jme3/asset/Asset.java
new file mode 100644
index 0000000..f36f963
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/Asset.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+/**
+ * Implementing the asset interface allows use of smart asset management.
+ * <p>
+ * Smart asset management requires cooperation from the {@link AssetKey}.
+ * In particular, the AssetKey should return true in its
+ * {@link AssetKey#useSmartCache() } method. Also smart assets MUST
+ * create a clone of the asset and cannot return the same reference,
+ * e.g. {@link AssetKey#createClonedInstance(java.lang.Object) createCloneInstance(someAsset)} <code>!= someAsset</code>.
+ * <p>
+ * If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method
+ * is called twice with the same asset key (equals() wise, not necessarily reference wise)
+ * then both assets will have the same asset key set (reference wise) via
+ * {@link Asset#setKey(com.jme3.asset.AssetKey) }, then this asset key
+ * is used to track all instances of that asset. Once all clones of the asset
+ * are garbage collected, the shared asset key becomes unreachable and at that
+ * point it is removed from the smart asset cache.
+ */
+public interface Asset {
+
+ /**
+ * Set by the {@link AssetManager} to track this asset.
+ *
+ * Only clones of the asset has this set, the original copy that
+ * was loaded has this key set to null so that only the clones are tracked
+ * for garbage collection.
+ *
+ * @param key The AssetKey to set
+ */
+ public void setKey(AssetKey key);
+
+ /**
+ * Returns the asset key that is used to track this asset for garbage
+ * collection.
+ *
+ * @return the asset key that is used to track this asset for garbage
+ * collection.
+ */
+ public AssetKey getKey();
+}
diff --git a/engine/src/core/com/jme3/asset/AssetCache.java b/engine/src/core/com/jme3/asset/AssetCache.java
new file mode 100644
index 0000000..5dde799
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetCache.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+import java.util.WeakHashMap;
+
+/**
+ * An <code>AssetCache</code> allows storage of loaded resources in order
+ * to improve their access time if they are requested again in a short period
+ * of time. The AssetCache stores weak references to the resources, allowing
+ * Java's garbage collector to request deletion of rarely used resources
+ * when heap memory is low.
+ */
+public class AssetCache {
+
+ public static final class SmartAssetInfo {
+ public WeakReference<AssetKey> smartKey;
+ public Asset asset;
+ }
+
+ private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
+ = new WeakHashMap<AssetKey, SmartAssetInfo>();
+ private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();
+
+ /**
+ * Adds a resource to the cache.
+ * <br/><br/>
+ * <font color="red">Thread-safe.</font>
+ * @see #getFromCache(java.lang.String)
+ */
+ public void addToCache(AssetKey key, Object obj){
+ synchronized (regularCache){
+ if (obj instanceof Asset && key.useSmartCache()){
+ // put in smart cache
+ Asset asset = (Asset) obj;
+ asset.setKey(null); // no circular references
+ SmartAssetInfo smartInfo = new SmartAssetInfo();
+ smartInfo.asset = asset;
+ // use the original key as smart key
+ smartInfo.smartKey = new WeakReference<AssetKey>(key);
+ smartCache.put(key, smartInfo);
+ }else{
+ // put in regular cache
+ regularCache.put(key, obj);
+ }
+ }
+ }
+
+ /**
+ * Delete an asset from the cache, returns true if it was deleted successfuly.
+ * <br/><br/>
+ * <font color="red">Thread-safe.</font>
+ */
+ public boolean deleteFromCache(AssetKey key){
+ synchronized (regularCache){
+ if (key.useSmartCache()){
+ return smartCache.remove(key) != null;
+ }else{
+ return regularCache.remove(key) != null;
+ }
+ }
+ }
+
+ /**
+ * Gets an object from the cache given an asset key.
+ * <br/><br/>
+ * <font color="red">Thread-safe.</font>
+ * @param key
+ * @return
+ */
+ public Object getFromCache(AssetKey key){
+ synchronized (regularCache){
+ if (key.useSmartCache()) {
+ return smartCache.get(key).asset;
+ } else {
+ return regularCache.get(key);
+ }
+ }
+ }
+
+ /**
+ * Retrieves smart asset info from the cache.
+ * @param key
+ * @return
+ */
+ public SmartAssetInfo getFromSmartCache(AssetKey key){
+ return smartCache.get(key);
+ }
+
+ /**
+ * Deletes all the assets in the regular cache.
+ */
+ public void deleteAllAssets(){
+ synchronized (regularCache){
+ regularCache.clear();
+ smartCache.clear();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/asset/AssetConfig.java b/engine/src/core/com/jme3/asset/AssetConfig.java
new file mode 100644
index 0000000..eaf2036
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetConfig.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AssetConfig</code> loads a config file to configure the asset manager.
+ * <br/><br/>
+ * The config file is specified with the following format:
+ * <code>
+ * "LOADER" <class> : (<extension> ",")* <extension>
+ * "LOCATOR" <path> <class> : (<extension> ",")* <extension>
+ * </code>
+ *
+ * @author Kirill Vainer
+ */
+public class AssetConfig {
+
+ private AssetManager manager;
+
+ public AssetConfig(AssetManager manager){
+ this.manager = manager;
+ }
+
+ public void loadText(InputStream in) throws IOException{
+ Scanner scan = new Scanner(in);
+ while (scan.hasNext()){
+ String cmd = scan.next();
+ if (cmd.equals("LOADER")){
+ String loaderClass = scan.next();
+ String colon = scan.next();
+ if (!colon.equals(":")){
+ throw new IOException("Expected ':', got '"+colon+"'");
+ }
+ String extensionsList = scan.nextLine();
+ String[] extensions = extensionsList.split(",");
+ for (int i = 0; i < extensions.length; i++){
+ extensions[i] = extensions[i].trim();
+ }
+ if (hasClass(loaderClass)) {
+ manager.registerLoader(loaderClass, extensions);
+ } else {
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find loader {0}", loaderClass);
+ }
+ } else if (cmd.equals("LOCATOR")) {
+ String rootPath = scan.next();
+ String locatorClass = scan.nextLine().trim();
+ if (hasClass(locatorClass)) {
+ manager.registerLocator(rootPath, locatorClass);
+ } else {
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot find locator {0}", locatorClass);
+ }
+ } else {
+ throw new IOException("Expected command, got '" + cmd + "'");
+ }
+ }
+ }
+
+ private boolean hasClass(String name) {
+ try {
+ Class clazz = Class.forName(name);
+ return clazz != null;
+ } catch (ClassNotFoundException ex) {
+ return false;
+ }
+ }
+
+ private static String readString(DataInput dataIn) throws IOException{
+ int length = dataIn.readUnsignedShort();
+ char[] chrs = new char[length];
+ for (int i = 0; i < length; i++){
+ chrs[i] = (char) dataIn.readUnsignedByte();
+ }
+ return String.valueOf(chrs);
+ }
+
+ /*
+ public void loadBinary(DataInput dataIn) throws IOException{
+ // read signature and version
+
+ // how many locator entries?
+ int locatorEntries = dataIn.readUnsignedShort();
+ for (int i = 0; i < locatorEntries; i++){
+ String locatorClazz = readString(dataIn);
+ String rootPath = readString(dataIn);
+ manager.registerLocator(rootPath, locatorClazz);
+ }
+
+ int loaderEntries = dataIn.readUnsignedShort();
+ for (int i = 0; i < loaderEntries; i++){
+ String loaderClazz = readString(dataIn);
+ int numExtensions = dataIn.readUnsignedByte();
+ String[] extensions = new String[numExtensions];
+ for (int j = 0; j < numExtensions; j++){
+ extensions[j] = readString(dataIn);
+ }
+
+ manager.registerLoader(loaderClazz, extensions);
+ }
+ }
+ */
+}
diff --git a/engine/src/core/com/jme3/asset/AssetEventListener.java b/engine/src/core/com/jme3/asset/AssetEventListener.java
new file mode 100644
index 0000000..6907bcf
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetEventListener.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+/**
+ * <code>AssetEventListener</code> is an interface for listening to various
+ * events happening inside {@link AssetManager}. For now, it is possible
+ * to receive an event when an asset has been requested
+ * (one of the AssetManager.load***() methods were called), or when
+ * an asset has been loaded.
+ *
+ * @author Kirill Vainer
+ */
+public interface AssetEventListener {
+
+ /**
+ * Called when an asset has been successfully loaded (e.g: loaded from
+ * file system and parsed).
+ *
+ * @param key the AssetKey for the asset loaded.
+ */
+ public void assetLoaded(AssetKey key);
+
+ /**
+ * Called when an asset has been requested (e.g any of the load*** methods
+ * in AssetManager are called).
+ * In contrast to the assetLoaded() method, this one will be called even
+ * if the asset has failed to load, or if it was retrieved from the cache.
+ *
+ * @param key
+ */
+ public void assetRequested(AssetKey key);
+
+ /**
+ * Called when an asset dependency cannot be found for an asset.
+ * When an asset is loaded, each of its dependent assets that
+ * have failed to load due to a {@link AssetNotFoundException}, will cause
+ * an invocation of this callback.
+ *
+ * @param parentKey The key of the parent asset that is being loaded
+ * from within the user application.
+ * @param dependentAssetKey The asset key of the dependent asset that has
+ * failed to load.
+ */
+ public void assetDependencyNotFound(AssetKey parentKey, AssetKey dependentAssetKey);
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetInfo.java b/engine/src/core/com/jme3/asset/AssetInfo.java
new file mode 100644
index 0000000..42ee822
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetInfo.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.io.InputStream;
+
+/**
+ * The result of locating an asset through an AssetKey. Provides
+ * a means to read the asset data through an InputStream.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AssetInfo {
+
+ protected AssetManager manager;
+ protected AssetKey key;
+
+ public AssetInfo(AssetManager manager, AssetKey key) {
+ this.manager = manager;
+ this.key = key;
+ }
+
+ public AssetKey getKey() {
+ return key;
+ }
+
+ public AssetManager getManager() {
+ return manager;
+ }
+
+ @Override
+ public String toString(){
+ return getClass().getName() + "[" + "key=" + key + "]";
+ }
+
+ /**
+ * Implementations of this method should return an {@link InputStream}
+ * allowing access to the data represented by the {@link AssetKey}.
+ * <p>
+ * Each invocation of this method should return a new stream to the
+ * asset data, starting at the beginning of the file.
+ *
+ * @return The asset data.
+ */
+ public abstract InputStream openStream();
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetKey.java b/engine/src/core/com/jme3/asset/AssetKey.java
new file mode 100644
index 0000000..9b6f0cd
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetKey.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.LinkedList;
+
+/**
+ * <code>AssetKey</code> is a key that is used to
+ * look up a resource from a cache.
+ * This class should be immutable.
+ */
+public class AssetKey<T> implements Savable {
+
+ protected String name;
+ protected transient String folder;
+ protected transient String extension;
+
+ public AssetKey(String name){
+ this.name = reducePath(name);
+ this.extension = getExtension(this.name);
+ }
+
+ public AssetKey(){
+ }
+
+ protected static String getExtension(String name){
+ int idx = name.lastIndexOf('.');
+ //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml)
+ if (name.toLowerCase().endsWith(".xml")) {
+ idx = name.substring(0, idx).lastIndexOf('.');
+ if (idx == -1) {
+ idx = name.lastIndexOf('.');
+ }
+ }
+ if (idx <= 0 || idx == name.length() - 1)
+ return "";
+ else
+ return name.substring(idx+1).toLowerCase();
+ }
+
+ protected static String getFolder(String name){
+ int idx = name.lastIndexOf('/');
+ if (idx <= 0 || idx == name.length() - 1)
+ return "";
+ else
+ return name.substring(0, idx+1);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return The extension of the <code>AssetKey</code>'s name. For example,
+ * the name "Interface/Logo/Monkey.png" has an extension of "png".
+ */
+ public String getExtension() {
+ return extension;
+ }
+
+ public String getFolder(){
+ if (folder == null)
+ folder = getFolder(name);
+
+ return folder;
+ }
+
+ /**
+ * Do any post-processing on the resource after it has been loaded.
+ * @param asset
+ */
+ public Object postProcess(Object asset){
+ return asset;
+ }
+
+ /**
+ * Create a new instance of the asset, based on a prototype that is stored
+ * in the cache. Implementations are allowed to return the given parameter
+ * as-is if it is considered that cloning is not necessary for that particular
+ * asset type.
+ *
+ * @param asset The asset to be cloned.
+ * @return The asset, possibly cloned.
+ */
+ public Object createClonedInstance(Object asset){
+ return asset;
+ }
+
+ /**
+ * @return True if the asset for this key should be cached. Subclasses
+ * should override this method if they want to override caching behavior.
+ */
+ public boolean shouldCache(){
+ return true;
+ }
+
+ /**
+ * @return Should return true, if the asset objects implement the "Asset"
+ * interface and want to be removed from the cache when no longer
+ * referenced in user-code.
+ */
+ public boolean useSmartCache(){
+ return false;
+ }
+
+ /**
+ * Removes all relative elements of a path (A/B/../C.png and A/./C.png).
+ * @param path The path containing relative elements
+ * @return A path without relative elements
+ */
+ public static String reducePath(String path) {
+ if (path == null || path.indexOf("./") == -1) {
+ return path;
+ }
+ String[] parts = path.split("/");
+ LinkedList<String> list = new LinkedList<String>();
+ for (int i = 0; i < parts.length; i++) {
+ String string = parts[i];
+ if (string.length() == 0 || string.equals(".")) {
+ //do nothing
+ } else if (string.equals("..")) {
+ if (list.size() > 0) {
+ list.removeLast();
+ } else {
+ throw new IllegalStateException("Relative path is outside assetmanager root!");
+ }
+ } else {
+ list.add(string);
+ }
+ }
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < list.size(); i++) {
+ String string = list.get(i);
+ if (i != 0) {
+ builder.append("/");
+ }
+ builder.append(string);
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(Object other){
+ if (!(other instanceof AssetKey)){
+ return false;
+ }
+ return name.equals(((AssetKey)other).name);
+ }
+
+ @Override
+ public int hashCode(){
+ return name.hashCode();
+ }
+
+ @Override
+ public String toString(){
+ return name;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(name, "name", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ name = reducePath(ic.readString("name", null));
+ extension = getExtension(name);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLoadException.java b/engine/src/core/com/jme3/asset/AssetLoadException.java
new file mode 100644
index 0000000..a0139aa
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLoadException.java
@@ -0,0 +1,17 @@
+package com.jme3.asset;
+
+/**
+ * <code>AssetLoadException</code> is thrown when the {@link AssetManager}
+ * is able to find the requested asset, but there was a problem while loading
+ * it.
+ *
+ * @author Kirill Vainer
+ */
+public class AssetLoadException extends RuntimeException {
+ public AssetLoadException(String message){
+ super(message);
+ }
+ public AssetLoadException(String message, Throwable cause){
+ super(message, cause);
+ }
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLoader.java b/engine/src/core/com/jme3/asset/AssetLoader.java
new file mode 100644
index 0000000..4ebffc5
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLoader.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.io.IOException;
+
+/**
+ * An interface for asset loaders. An <code>AssetLoader</code> is responsible
+ * for loading a certain type of asset associated with file extension(s).
+ * The loader will load the data in the provided {@link AssetInfo} object by
+ * calling {@link AssetInfo#openStream() }, returning an object representing
+ * the parsed data.
+ */
+public interface AssetLoader {
+
+ /**
+ * Loads asset from the given input stream, parsing it into
+ * an application-usable object.
+ *
+ * @return An object representing the resource.
+ * @throws java.io.IOException If an I/O error occurs while loading
+ */
+ public Object load(AssetInfo assetInfo) throws IOException;
+}
diff --git a/engine/src/core/com/jme3/asset/AssetLocator.java b/engine/src/core/com/jme3/asset/AssetLocator.java
new file mode 100644
index 0000000..561e372
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetLocator.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+/**
+ * <code>AssetLocator</code> is used to locate a resource based on an AssetKey.
+ *
+ * @author Kirill Vainer
+ */
+public interface AssetLocator {
+ /**
+ * @param rootPath The root path where to look for assets.
+ * Typically this method will only be called once per
+ * instance of an asset locator.
+ */
+ public void setRootPath(String rootPath);
+
+ /**
+ * Request to locate an asset. The asset key
+ * contains a name identifying the asset.
+ * If an asset was not found, null should be returned.
+ * The {@link AssetInfo} implementation provided should have a proper
+ * return value for its {@link AssetInfo#openStream() } method.
+ *
+ * @param manager
+ * @param key
+ * @return The {@link AssetInfo} that was located, or null if not found.
+ */
+ public AssetInfo locate(AssetManager manager, AssetKey key);
+}
diff --git a/engine/src/core/com/jme3/asset/AssetManager.java b/engine/src/core/com/jme3/asset/AssetManager.java
new file mode 100644
index 0000000..a863ebf
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetManager.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioKey;
+import com.jme3.font.BitmapFont;
+import com.jme3.material.Material;
+import com.jme3.scene.Spatial;
+import com.jme3.shader.Shader;
+import com.jme3.shader.ShaderKey;
+import com.jme3.texture.Texture;
+import java.util.List;
+
+/**
+ * <code>AssetManager</code> provides an interface for managing the data assets
+ * of a jME3 application.
+ */
+public interface AssetManager {
+
+ /**
+ * Adds a ClassLoader that is used to load *Classes* that are needed for Assets like j3o models.
+ * This does *not* allow loading assets from that classpath, use registerLocator for that.
+ * @param loader A ClassLoader that Classes in asset files can be loaded from
+ */
+ public void addClassLoader(ClassLoader loader);
+
+ /**
+ * Remove a ClassLoader from the list of registered ClassLoaders
+ */
+ public void removeClassLoader(ClassLoader loader);
+
+ /**
+ * Retrieve the list of registered ClassLoaders that are used for loading Classes from
+ * asset files.
+ */
+ public List<ClassLoader> getClassLoaders();
+
+ /**
+ * Registers a loader for the given extensions.
+ * @param loaderClassName
+ * @param extensions
+ */
+ public void registerLoader(String loaderClassName, String ... extensions);
+
+ /**
+ * Registers an {@link AssetLocator} by using a class name, instead of
+ * a class instance. See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
+ * method for more information.
+ *
+ * @param rootPath The root path from which to locate assets, implementation
+ * dependent.
+ * @param locatorClassName The full class name of the {@link AssetLocator}
+ * implementation.
+ */
+ public void registerLocator(String rootPath, String locatorClassName);
+
+ /**
+ *
+ * @param loaderClass
+ * @param extensions
+ */
+ public void registerLoader(Class<? extends AssetLoader> loaderClass, String ... extensions);
+
+ /**
+ * Registers the given locator class for locating assets with this
+ * <code>AssetManager</code>. {@link AssetLocator}s are invoked in the order
+ * they were registered, to locate the asset by the {@link AssetKey}.
+ * Once an {@link AssetLocator} returns a non-null AssetInfo, it is sent
+ * to the {@link AssetLoader} to load the asset.
+ * Once a locator is registered, it can be removed via
+ * {@link #unregisterLocator(java.lang.String, java.lang.Class) }.
+ *
+ * @param rootPath Specifies the root path from which to locate assets
+ * for the given {@link AssetLocator}. The purpose of this parameter
+ * depends on the type of the {@link AssetLocator}.
+ * @param locatorClass The class type of the {@link AssetLocator} to register.
+ *
+ * @see AssetLocator#setRootPath(java.lang.String)
+ * @see AssetLocator#locate(com.jme3.asset.AssetManager, com.jme3.asset.AssetKey)
+ * @see #unregisterLocator(java.lang.String, java.lang.Class)
+ */
+ public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
+
+ /**
+ * Unregisters the given locator class. This essentially undoes the operation
+ * done by {@link #registerLocator(java.lang.String, java.lang.Class) }.
+ *
+ * @param rootPath Should be the same as the root path specified in {@link
+ * #registerLocator(java.lang.String, java.lang.Class) }.
+ * @param locatorClass The locator class to unregister
+ */
+ public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
+
+ /**
+ * Set an {@link AssetEventListener} to receive events from this
+ * <code>AssetManager</code>. There can only be one {@link AssetEventListener}
+ * associated with an <code>AssetManager</code>
+ *
+ * @param listener
+ */
+ public void setAssetEventListener(AssetEventListener listener);
+
+ /**
+ * Manually locates an asset with the given {@link AssetKey}. This method
+ * should be used for debugging or internal uses. <br/>
+ * The call will attempt to locate the asset by invoking the
+ * {@link AssetLocator} that are registered with this <code>AssetManager</code>,
+ * in the same way that the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) }
+ * method locates assets.
+ *
+ * @param key The {@link AssetKey} to locate.
+ * @return The {@link AssetInfo} object returned from the {@link AssetLocator}
+ * that located the asset, or null if the asset cannot be located.
+ */
+ public AssetInfo locateAsset(AssetKey<?> key);
+
+ /**
+ * Load an asset from a key, the asset will be located
+ * by one of the {@link AssetLocator} implementations provided in the
+ * {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
+ * call. If located successfully, it will be loaded via the the appropriate
+ * {@link AssetLoader} implementation based on the file's extension, as
+ * specified in the call
+ * {@link AssetManager#registerLoader(java.lang.Class, java.lang.String[]) }.
+ *
+ * @param <T> The object type that will be loaded from the AssetKey instance.
+ * @param key The AssetKey
+ * @return The loaded asset, or null if it was failed to be located
+ * or loaded.
+ */
+ public <T> T loadAsset(AssetKey<T> key);
+
+ /**
+ * Load a named asset by name, calling this method
+ * is the same as calling
+ * <code>
+ * loadAsset(new AssetKey(name)).
+ * </code>
+ *
+ * @param name The name of the asset to load.
+ * @return The loaded asset, or null if failed to be loaded.
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Object loadAsset(String name);
+
+ /**
+ * Loads texture file, supported types are BMP, JPG, PNG, GIF,
+ * TGA and DDS.
+ *
+ * @param key The {@link TextureKey} to use for loading.
+ * @return The loaded texture, or null if failed to be loaded.
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Texture loadTexture(TextureKey key);
+
+ /**
+ * Loads texture file, supported types are BMP, JPG, PNG, GIF,
+ * TGA and DDS.
+ *
+ * @param name The name of the texture to load.
+ * @return The texture that was loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Texture loadTexture(String name);
+
+ /**
+ * Load audio file, supported types are WAV or OGG.
+ * @param key
+ * @return The audio data loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public AudioData loadAudio(AudioKey key);
+
+ /**
+ * Load audio file, supported types are WAV or OGG.
+ * The file is loaded without stream-mode.
+ * @param name
+ * @return The audio data loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public AudioData loadAudio(String name);
+
+ /**
+ * Loads a named model. Models can be jME3 object files (J3O) or
+ * OgreXML/OBJ files.
+ * @param key
+ * @return The model that was loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Spatial loadModel(ModelKey key);
+
+ /**
+ * Loads a named model. Models can be jME3 object files (J3O) or
+ * OgreXML/OBJ files.
+ * @param name
+ * @return The model that was loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Spatial loadModel(String name);
+
+ /**
+ * Load a material (J3M) file.
+ * @param name
+ * @return The material that was loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Material loadMaterial(String name);
+
+ /**
+ * Loads shader file(s), shouldn't be used by end-user in most cases.
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public Shader loadShader(ShaderKey key);
+
+ /**
+ * Load a font file. Font files are in AngelCode text format,
+ * and are with the extension "fnt".
+ *
+ * @param name
+ * @return The font loaded
+ *
+ * @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
+ */
+ public BitmapFont loadFont(String name);
+}
diff --git a/engine/src/core/com/jme3/asset/AssetNotFoundException.java b/engine/src/core/com/jme3/asset/AssetNotFoundException.java
new file mode 100644
index 0000000..e04a7fb
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/AssetNotFoundException.java
@@ -0,0 +1,17 @@
+package com.jme3.asset;
+
+/**
+ * <code>AssetNotFoundException</code> is thrown when the {@link AssetManager}
+ * is unable to locate the requested asset using any of the registered
+ * {@link AssetLocator}s.
+ *
+ * @author Kirill Vainer
+ */
+public class AssetNotFoundException extends RuntimeException {
+ public AssetNotFoundException(String message){
+ super(message);
+ }
+ public AssetNotFoundException(String message, Exception ex){
+ super(message, ex);
+ }
+}
diff --git a/engine/src/core/com/jme3/asset/Desktop.cfg b/engine/src/core/com/jme3/asset/Desktop.cfg
new file mode 100644
index 0000000..93dafb7
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/Desktop.cfg
@@ -0,0 +1,22 @@
+LOCATOR / com.jme3.asset.plugins.ClasspathLocator
+
+LOADER com.jme3.texture.plugins.AWTLoader : jpg, bmp, gif, png, jpeg
+LOADER com.jme3.audio.plugins.WAVLoader : wav
+LOADER com.jme3.audio.plugins.OGGLoader : ogg
+LOADER com.jme3.material.plugins.J3MLoader : j3m
+LOADER com.jme3.material.plugins.J3MLoader : j3md
+LOADER com.jme3.font.plugins.BitmapFontLoader : fnt
+LOADER com.jme3.texture.plugins.DDSLoader : dds
+LOADER com.jme3.texture.plugins.PFMLoader : pfm
+LOADER com.jme3.texture.plugins.HDRLoader : hdr
+LOADER com.jme3.texture.plugins.TGALoader : tga
+LOADER com.jme3.export.binary.BinaryImporter : j3o
+LOADER com.jme3.export.binary.BinaryImporter : j3f
+LOADER com.jme3.scene.plugins.OBJLoader : obj
+LOADER com.jme3.scene.plugins.MTLLoader : mtl
+LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml
+LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml
+LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material
+LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene
+LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend
+LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, glsl, glsllib \ No newline at end of file
diff --git a/engine/src/core/com/jme3/asset/DesktopAssetManager.java b/engine/src/core/com/jme3/asset/DesktopAssetManager.java
new file mode 100644
index 0000000..e2d8ef8
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/DesktopAssetManager.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import com.jme3.asset.AssetCache.SmartAssetInfo;
+import com.jme3.audio.AudioData;
+import com.jme3.audio.AudioKey;
+import com.jme3.font.BitmapFont;
+import com.jme3.material.Material;
+import com.jme3.scene.Spatial;
+import com.jme3.shader.Shader;
+import com.jme3.shader.ShaderKey;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AssetManager</code> is the primary method for managing and loading
+ * assets inside jME.
+ *
+ * @author Kirill Vainer
+ */
+public class DesktopAssetManager implements AssetManager {
+
+ private static final Logger logger = Logger.getLogger(AssetManager.class.getName());
+
+ private final AssetCache cache = new AssetCache();
+ private final ImplHandler handler = new ImplHandler(this);
+
+ private AssetEventListener eventListener = null;
+ private List<ClassLoader> classLoaders;
+
+// private final ThreadingManager threadingMan = new ThreadingManager(this);
+// private final Set<AssetKey> alreadyLoadingSet = new HashSet<AssetKey>();
+
+ public DesktopAssetManager(){
+ this(null);
+ }
+
+ @Deprecated
+ public DesktopAssetManager(boolean loadDefaults){
+ this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"));
+ }
+
+ public DesktopAssetManager(URL configFile){
+ if (configFile != null){
+ InputStream stream = null;
+ try{
+ AssetConfig cfg = new AssetConfig(this);
+ stream = configFile.openStream();
+ cfg.loadText(stream);
+ }catch (IOException ex){
+ logger.log(Level.SEVERE, "Failed to load asset config", ex);
+ }finally{
+ if (stream != null)
+ try{
+ stream.close();
+ }catch (IOException ex){
+ }
+ }
+ }
+ logger.info("DesktopAssetManager created.");
+ }
+
+ public void addClassLoader(ClassLoader loader){
+ if(classLoaders == null)
+ classLoaders = Collections.synchronizedList(new ArrayList<ClassLoader>());
+ synchronized(classLoaders) {
+ classLoaders.add(loader);
+ }
+ }
+
+ public void removeClassLoader(ClassLoader loader){
+ if(classLoaders != null) synchronized(classLoaders) {
+ classLoaders.remove(loader);
+ }
+ }
+
+ public List<ClassLoader> getClassLoaders(){
+ return classLoaders;
+ }
+
+ public void setAssetEventListener(AssetEventListener listener){
+ eventListener = listener;
+ }
+
+ public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){
+ handler.addLoader(loader, extensions);
+ if (logger.isLoggable(Level.FINER)){
+ logger.log(Level.FINER, "Registered loader: {0} for extensions {1}",
+ new Object[]{loader.getSimpleName(), Arrays.toString(extensions)});
+ }
+ }
+
+ public void registerLoader(String clsName, String ... extensions){
+ Class<? extends AssetLoader> clazz = null;
+ try{
+ clazz = (Class<? extends AssetLoader>) Class.forName(clsName);
+ }catch (ClassNotFoundException ex){
+ logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
+ }catch (NoClassDefFoundError ex){
+ logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
+ }
+ if (clazz != null){
+ registerLoader(clazz, extensions);
+ }
+ }
+
+ public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){
+ handler.addLocator(locatorClass, rootPath);
+ if (logger.isLoggable(Level.FINER)){
+ logger.log(Level.FINER, "Registered locator: {0}",
+ locatorClass.getSimpleName());
+ }
+ }
+
+ public void registerLocator(String rootPath, String clsName){
+ Class<? extends AssetLocator> clazz = null;
+ try{
+ clazz = (Class<? extends AssetLocator>) Class.forName(clsName);
+ }catch (ClassNotFoundException ex){
+ logger.log(Level.WARNING, "Failed to find locator: "+clsName, ex);
+ }catch (NoClassDefFoundError ex){
+ logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex);
+ }
+ if (clazz != null){
+ registerLocator(rootPath, clazz);
+ }
+ }
+
+ public void unregisterLocator(String rootPath, Class<? extends AssetLocator> clazz){
+ handler.removeLocator(clazz, rootPath);
+ if (logger.isLoggable(Level.FINER)){
+ logger.log(Level.FINER, "Unregistered locator: {0}",
+ clazz.getSimpleName());
+ }
+ }
+
+ public void clearCache(){
+ cache.deleteAllAssets();
+ }
+
+ /**
+ * Delete an asset from the cache, returns true if it was deleted
+ * successfully.
+ * <br/><br/>
+ * <font color="red">Thread-safe.</font>
+ */
+ public boolean deleteFromCache(AssetKey key){
+ return cache.deleteFromCache(key);
+ }
+
+ /**
+ * Adds a resource to the cache.
+ * <br/><br/>
+ * <font color="red">Thread-safe.</font>
+ */
+ public void addToCache(AssetKey key, Object asset){
+ cache.addToCache(key, asset);
+ }
+
+ public AssetInfo locateAsset(AssetKey<?> key){
+ if (handler.getLocatorCount() == 0){
+ logger.warning("There are no locators currently"+
+ " registered. Use AssetManager."+
+ "registerLocator() to register a"+
+ " locator.");
+ return null;
+ }
+
+ AssetInfo info = handler.tryLocate(key);
+ if (info == null){
+ logger.log(Level.WARNING, "Cannot locate resource: {0}", key);
+ }
+
+ return info;
+ }
+
+ /**
+ * <font color="red">Thread-safe.</font>
+ *
+ * @param <T>
+ * @param key
+ * @return
+ */
+ public <T> T loadAsset(AssetKey<T> key){
+ if (key == null)
+ throw new IllegalArgumentException("key cannot be null");
+
+ if (eventListener != null)
+ eventListener.assetRequested(key);
+
+ AssetKey smartKey = null;
+ Object o = null;
+ if (key.shouldCache()){
+ if (key.useSmartCache()){
+ SmartAssetInfo smartInfo = cache.getFromSmartCache(key);
+ if (smartInfo != null){
+ smartKey = smartInfo.smartKey.get();
+ if (smartKey != null){
+ o = smartInfo.asset;
+ }
+ }
+ }else{
+ o = cache.getFromCache(key);
+ }
+ }
+ if (o == null){
+ AssetLoader loader = handler.aquireLoader(key);
+ if (loader == null){
+ throw new IllegalStateException("No loader registered for type \"" +
+ key.getExtension() + "\"");
+ }
+
+ if (handler.getLocatorCount() == 0){
+ throw new IllegalStateException("There are no locators currently"+
+ " registered. Use AssetManager."+
+ "registerLocator() to register a"+
+ " locator.");
+ }
+
+ AssetInfo info = handler.tryLocate(key);
+ if (info == null){
+ if (handler.getParentKey() != null && eventListener != null){
+ // Inform event listener that an asset has failed to load.
+ // If the parent AssetLoader chooses not to propagate
+ // the exception, this is the only means of finding
+ // that something went wrong.
+ eventListener.assetDependencyNotFound(handler.getParentKey(), key);
+ }
+ throw new AssetNotFoundException(key.toString());
+ }
+
+ try {
+ handler.establishParentKey(key);
+ o = loader.load(info);
+ } catch (IOException ex) {
+ throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);
+ } finally {
+ handler.releaseParentKey(key);
+ }
+ if (o == null){
+ throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using" + loader.getClass().getSimpleName());
+ }else{
+ if (logger.isLoggable(Level.FINER)){
+ logger.log(Level.FINER, "Loaded {0} with {1}",
+ new Object[]{key, loader.getClass().getSimpleName()});
+ }
+
+ // do processing on asset before caching
+ o = key.postProcess(o);
+
+ if (key.shouldCache())
+ cache.addToCache(key, o);
+
+ if (eventListener != null)
+ eventListener.assetLoaded(key);
+ }
+ }
+
+ // object o is the asset
+ // create an instance for user
+ T clone = (T) key.createClonedInstance(o);
+
+ if (key.useSmartCache()){
+ if (smartKey != null){
+ // smart asset was already cached, use original key
+ ((Asset)clone).setKey(smartKey);
+ }else{
+ // smart asset was cached on this call, use our key
+ ((Asset)clone).setKey(key);
+ }
+ }
+
+ return clone;
+ }
+
+ public Object loadAsset(String name){
+ return loadAsset(new AssetKey(name));
+ }
+
+ /**
+ * Loads a texture.
+ *
+ * @return
+ */
+ public Texture loadTexture(TextureKey key){
+ return (Texture) loadAsset(key);
+ }
+
+ public Material loadMaterial(String name){
+ return (Material) loadAsset(new MaterialKey(name));
+ }
+
+ /**
+ * Loads a texture.
+ *
+ * @param name
+ * @param generateMipmaps Enable if applying texture to 3D objects, disable
+ * for GUI/HUD elements.
+ * @return
+ */
+ public Texture loadTexture(String name, boolean generateMipmaps){
+ TextureKey key = new TextureKey(name, true);
+ key.setGenerateMips(generateMipmaps);
+ key.setAsCube(false);
+ return loadTexture(key);
+ }
+
+ public Texture loadTexture(String name, boolean generateMipmaps, boolean flipY, boolean asCube, int aniso){
+ TextureKey key = new TextureKey(name, flipY);
+ key.setGenerateMips(generateMipmaps);
+ key.setAsCube(asCube);
+ key.setAnisotropy(aniso);
+ return loadTexture(key);
+ }
+
+ public Texture loadTexture(String name){
+ return loadTexture(name, true);
+ }
+
+ public AudioData loadAudio(AudioKey key){
+ return (AudioData) loadAsset(key);
+ }
+
+ public AudioData loadAudio(String name){
+ return loadAudio(new AudioKey(name, false));
+ }
+
+ /**
+ * Loads a bitmap font with the given name.
+ *
+ * @param name
+ * @return
+ */
+ public BitmapFont loadFont(String name){
+ return (BitmapFont) loadAsset(new AssetKey(name));
+ }
+
+ public InputStream loadGLSLLibrary(AssetKey key){
+ return (InputStream) loadAsset(key);
+ }
+
+ /**
+ * Load a vertex/fragment shader combo.
+ *
+ * @param key
+ * @return
+ */
+ public Shader loadShader(ShaderKey key){
+ // cache abuse in method
+ // that doesn't use loaders/locators
+ Shader s = (Shader) cache.getFromCache(key);
+ if (s == null){
+ String vertName = key.getVertName();
+ String fragName = key.getFragName();
+
+ String vertSource = (String) loadAsset(new AssetKey(vertName));
+ String fragSource = (String) loadAsset(new AssetKey(fragName));
+
+ s = new Shader(key.getLanguage());
+ s.addSource(Shader.ShaderType.Vertex, vertName, vertSource, key.getDefines().getCompiled());
+ s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());
+
+ cache.addToCache(key, s);
+ }
+ return s;
+ }
+
+ public Spatial loadModel(ModelKey key){
+ return (Spatial) loadAsset(key);
+ }
+
+ /**
+ * Load a model.
+ *
+ * @param name
+ * @return
+ */
+ public Spatial loadModel(String name){
+ return loadModel(new ModelKey(name));
+ }
+
+}
diff --git a/engine/src/core/com/jme3/asset/ImplHandler.java b/engine/src/core/com/jme3/asset/ImplHandler.java
new file mode 100644
index 0000000..9b0b50a
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ImplHandler.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>ImplHandler</code> manages the asset loader and asset locator
+ * implementations in a thread safe way. This allows implementations
+ * which store local persistent data to operate with a multi-threaded system.
+ * This is done by keeping an instance of each asset loader and asset
+ * locator object in a thread local.
+ */
+public class ImplHandler {
+
+ private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
+
+ private final AssetManager owner;
+
+ private final ThreadLocal<AssetKey> parentAssetKey
+ = new ThreadLocal<AssetKey>();
+
+ private final ArrayList<ImplThreadLocal> genericLocators =
+ new ArrayList<ImplThreadLocal>();
+
+ private final HashMap<String, ImplThreadLocal> loaders =
+ new HashMap<String, ImplThreadLocal>();
+
+ public ImplHandler(AssetManager owner){
+ this.owner = owner;
+ }
+
+ protected class ImplThreadLocal extends ThreadLocal {
+
+ private final Class<?> type;
+ private final String path;
+
+ public ImplThreadLocal(Class<?> type){
+ this.type = type;
+ path = null;
+ }
+
+ public ImplThreadLocal(Class<?> type, String path){
+ this.type = type;
+ this.path = path;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public Class<?> getTypeClass(){
+ return type;
+ }
+
+ @Override
+ protected Object initialValue(){
+ try {
+ return type.newInstance();
+ } catch (InstantiationException ex) {
+ logger.log(Level.SEVERE,"Cannot create locator of type {0}, does"
+ + " the class have an empty and publically accessible"+
+ " constructor?", type.getName());
+ logger.throwing(type.getName(), "<init>", ex);
+ } catch (IllegalAccessException ex) {
+ logger.log(Level.SEVERE,"Cannot create locator of type {0}, "
+ + "does the class have an empty and publically "
+ + "accessible constructor?", type.getName());
+ logger.throwing(type.getName(), "<init>", ex);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Establishes the asset key that is used for tracking dependent assets
+ * that have failed to load. When set, the {@link DesktopAssetManager}
+ * gets a hint that it should suppress {@link AssetNotFoundException}s
+ * and instead call the listener callback (if set).
+ *
+ * @param parentKey The parent key
+ */
+ public void establishParentKey(AssetKey parentKey){
+ if (parentAssetKey.get() == null){
+ parentAssetKey.set(parentKey);
+ }
+ }
+
+ public void releaseParentKey(AssetKey parentKey){
+ if (parentAssetKey.get() == parentKey){
+ parentAssetKey.set(null);
+ }
+ }
+
+ public AssetKey getParentKey(){
+ return parentAssetKey.get();
+ }
+
+ /**
+ * Attempts to locate the given resource name.
+ * @param key The full name of the resource.
+ * @return The AssetInfo containing resource information required for
+ * access, or null if not found.
+ */
+ public AssetInfo tryLocate(AssetKey key){
+ synchronized (genericLocators){
+ if (genericLocators.isEmpty())
+ return null;
+
+ for (ImplThreadLocal local : genericLocators){
+ AssetLocator locator = (AssetLocator) local.get();
+ if (local.getPath() != null){
+ locator.setRootPath((String) local.getPath());
+ }
+ AssetInfo info = locator.locate(owner, key);
+ if (info != null)
+ return info;
+ }
+ }
+ return null;
+ }
+
+ public int getLocatorCount(){
+ synchronized (genericLocators){
+ return genericLocators.size();
+ }
+ }
+
+ /**
+ * Returns the AssetLoader registered for the given extension
+ * of the current thread.
+ * @return AssetLoader registered with addLoader.
+ */
+ public AssetLoader aquireLoader(AssetKey key){
+ synchronized (loaders){
+ ImplThreadLocal local = loaders.get(key.getExtension());
+ if (local != null){
+ AssetLoader loader = (AssetLoader) local.get();
+ return loader;
+ }
+ return null;
+ }
+ }
+
+ public void addLoader(final Class<?> loaderType, String ... extensions){
+ ImplThreadLocal local = new ImplThreadLocal(loaderType);
+ for (String extension : extensions){
+ extension = extension.toLowerCase();
+ synchronized (loaders){
+ loaders.put(extension, local);
+ }
+ }
+ }
+
+ public void addLocator(final Class<?> locatorType, String rootPath){
+ ImplThreadLocal local = new ImplThreadLocal(locatorType, rootPath);
+ synchronized (genericLocators){
+ genericLocators.add(local);
+ }
+ }
+
+ public void removeLocator(final Class<?> locatorType, String rootPath){
+ synchronized (genericLocators){
+ Iterator<ImplThreadLocal> it = genericLocators.iterator();
+ while (it.hasNext()){
+ ImplThreadLocal locator = it.next();
+ if (locator.getPath().equals(rootPath) &&
+ locator.getTypeClass().equals(locatorType)){
+ it.remove();
+ }
+ }
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/asset/MaterialKey.java b/engine/src/core/com/jme3/asset/MaterialKey.java
new file mode 100644
index 0000000..cc74fbc
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/MaterialKey.java
@@ -0,0 +1,29 @@
+package com.jme3.asset;
+
+import com.jme3.material.Material;
+
+/**
+ * Used for loading {@link Material materials} only (not material definitions).
+ *
+ * @author Kirill Vainer
+ */
+public class MaterialKey extends AssetKey {
+ public MaterialKey(String name){
+ super(name);
+ }
+
+ public MaterialKey(){
+ super();
+ }
+
+ @Override
+ public boolean useSmartCache(){
+ return true;
+ }
+
+ @Override
+ public Object createClonedInstance(Object asset){
+ Material mat = (Material) asset;
+ return mat.clone();
+ }
+}
diff --git a/engine/src/core/com/jme3/asset/ModelKey.java b/engine/src/core/com/jme3/asset/ModelKey.java
new file mode 100644
index 0000000..fcf5c53
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ModelKey.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import com.jme3.scene.Spatial;
+
+/**
+ *
+ * @author Kirill Vainer
+ */
+public class ModelKey extends AssetKey<Spatial> {
+
+ public ModelKey(String name){
+ super(name);
+ }
+
+ public ModelKey(){
+ super();
+ }
+ @Override
+ public boolean useSmartCache(){
+ return true;
+ }
+
+ @Override
+ public Object createClonedInstance(Object asset){
+ Spatial model = (Spatial) asset;
+ return model.clone();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/asset/TextureKey.java b/engine/src/core/com/jme3/asset/TextureKey.java
new file mode 100644
index 0000000..118c84e
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/TextureKey.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.asset;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.texture.Texture.Type;
+import com.jme3.texture.*;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+public class TextureKey extends AssetKey<Texture> {
+
+ private boolean generateMips;
+ private boolean flipY;
+ private boolean asCube;
+ private boolean asTexture3D;
+ private int anisotropy;
+ private Texture.Type textureTypeHint=Texture.Type.TwoDimensional;
+
+ public TextureKey(String name, boolean flipY) {
+ super(name);
+ this.flipY = flipY;
+ }
+
+ public TextureKey(String name) {
+ super(name);
+ this.flipY = true;
+ }
+
+ public TextureKey() {
+ }
+
+ @Override
+ public String toString() {
+ return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
+ }
+
+ /**
+ * Enable smart caching for textures
+ * @return true to enable smart cache
+ */
+ @Override
+ public boolean useSmartCache() {
+ return true;
+ }
+
+ @Override
+ public Object createClonedInstance(Object asset) {
+ Texture tex = (Texture) asset;
+ return tex.createSimpleClone();
+ }
+
+ @Override
+ public Object postProcess(Object asset) {
+ Image img = (Image) asset;
+ if (img == null) {
+ return null;
+ }
+
+ Texture tex;
+ if (isAsCube()) {
+ if (isFlipY()) {
+ // also flip -y and +y image in cubemap
+ ByteBuffer pos_y = img.getData(2);
+ img.setData(2, img.getData(3));
+ img.setData(3, pos_y);
+ }
+ tex = new TextureCubeMap();
+ } else if (isAsTexture3D()) {
+ tex = new Texture3D();
+ } else {
+ tex = new Texture2D();
+ }
+
+ // enable mipmaps if image has them
+ // or generate them if requested by user
+ if (img.hasMipmaps() || isGenerateMips()) {
+ tex.setMinFilter(Texture.MinFilter.Trilinear);
+ }
+
+ tex.setAnisotropicFilter(getAnisotropy());
+ tex.setName(getName());
+ tex.setImage(img);
+ return tex;
+ }
+
+ public boolean isFlipY() {
+ return flipY;
+ }
+
+ public int getAnisotropy() {
+ return anisotropy;
+ }
+
+ public void setAnisotropy(int anisotropy) {
+ this.anisotropy = anisotropy;
+ }
+
+ public boolean isAsCube() {
+ return asCube;
+ }
+
+ public void setAsCube(boolean asCube) {
+ this.asCube = asCube;
+ }
+
+ public boolean isGenerateMips() {
+ return generateMips;
+ }
+
+ public void setGenerateMips(boolean generateMips) {
+ this.generateMips = generateMips;
+ }
+
+ public boolean isAsTexture3D() {
+ return asTexture3D;
+ }
+
+ public void setAsTexture3D(boolean asTexture3D) {
+ this.asTexture3D = asTexture3D;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof TextureKey)) {
+ return false;
+ }
+ return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
+ }
+
+ public Type getTextureTypeHint() {
+ return textureTypeHint;
+ }
+
+ public void setTextureTypeHint(Type textureTypeHint) {
+ this.textureTypeHint = textureTypeHint;
+ }
+
+
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(flipY, "flip_y", false);
+ oc.write(generateMips, "generate_mips", false);
+ oc.write(asCube, "as_cubemap", false);
+ oc.write(anisotropy, "anisotropy", 0);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ flipY = ic.readBoolean("flip_y", false);
+ generateMips = ic.readBoolean("generate_mips", false);
+ asCube = ic.readBoolean("as_cubemap", false);
+ anisotropy = ic.readInt("anisotropy", 0);
+ }
+}
diff --git a/engine/src/core/com/jme3/asset/ThreadingManager.java b/engine/src/core/com/jme3/asset/ThreadingManager.java
new file mode 100644
index 0000000..6ed2c74
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/ThreadingManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.asset;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * <code>ThreadingManager</code> manages the threads used to load content
+ * within the Content Manager system. A pool of threads and a task queue
+ * is used to load resource data and perform I/O while the application's
+ * render thread is active.
+ */
+public class ThreadingManager {
+
+ protected final ExecutorService executor =
+ Executors.newFixedThreadPool(2,
+ new LoadingThreadFactory());
+
+ protected final AssetManager owner;
+
+ protected int nextThreadId = 0;
+
+ public ThreadingManager(AssetManager owner){
+ this.owner = owner;
+ }
+
+ protected class LoadingThreadFactory implements ThreadFactory {
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, "pool" + (nextThreadId++));
+ t.setDaemon(true);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }
+
+ protected class LoadingTask implements Callable<Object> {
+ private final String resourceName;
+ public LoadingTask(String resourceName){
+ this.resourceName = resourceName;
+ }
+ public Object call() throws Exception {
+ return owner.loadAsset(new AssetKey(resourceName));
+ }
+ }
+
+// protected class MultiLoadingTask implements Callable<Void> {
+// private final String[] resourceNames;
+// public MultiLoadingTask(String[] resourceNames){
+// this.resourceNames = resourceNames;
+// }
+// public Void call(){
+// owner.loadContents(resourceNames);
+// return null;
+// }
+// }
+
+// public Future<Void> loadContents(String ... names){
+// return executor.submit(new MultiLoadingTask(names));
+// }
+
+// public Future<Object> loadContent(String name) {
+// return executor.submit(new LoadingTask(name));
+// }
+
+ public static boolean isLoadingThread() {
+ return Thread.currentThread().getName().startsWith("pool");
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/asset/package.html b/engine/src/core/com/jme3/asset/package.html
new file mode 100644
index 0000000..d9e1913
--- /dev/null
+++ b/engine/src/core/com/jme3/asset/package.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+<code>com.jme3.asset</code> contains the {@link com.jme3.asset.AssetManager},
+a utility class that is used to load assets such as textures, models, and
+sound effects in a jME3 application. <br>
+
+<p>
+
+<h3>AssetLoaders</h3>
+{@link com.jme3.asset.AssetLoader asset loaders} are registered to load
+assets of a particular format. For example, an <code>AssetLoader</code> that
+loads TGA images should read a stream in .tga format and return an
+{@link com.jme3.texture.Image} object as its output.
+<code>AssetLoader</code>s are initialized once a file of that format
+is loaded, there's only one AssetLoader per thread so
+AssetLoader's load() method does not have to be thread safe.
+
+<h3>AssetLocators</h3>
+{@link com.jme3.asset.AssetLocators asset locators} are used to resolve
+an asset name (a string) into an {@link java.io.InputStream} which is
+contained in an {@link com.jme3.asset.AssetInfo} object.
+There are <code>AssetLocators</code> for loading files from the application's
+classpath, the local hard drive, a ZIP file, an HTTP server, and more. The user
+can implement their own AssetLocators and register them with the <code>AssetManager</code>
+to load their resources from their own location.
+
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/audio/AudioBuffer.java b/engine/src/core/com/jme3/audio/AudioBuffer.java
new file mode 100644
index 0000000..7d29c49
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioBuffer.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.audio.AudioData.DataType;
+import com.jme3.util.NativeObject;
+import java.nio.ByteBuffer;
+
+/**
+ * An <code>AudioBuffer</code> is an implementation of AudioData
+ * where the audio is buffered (stored in memory). All parts of it
+ * are accessible at any time. <br/>
+ * AudioBuffers are useful for short sounds, like effects, etc.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioBuffer extends AudioData {
+
+ /**
+ * The audio data buffer. Should be direct and native ordered.
+ */
+ protected ByteBuffer audioData;
+
+ public AudioBuffer(){
+ super();
+ }
+
+ protected AudioBuffer(int id){
+ super(id);
+ }
+
+ public DataType getDataType() {
+ return DataType.Buffer;
+ }
+
+ /**
+ * @return The duration of the audio in seconds. It is expected
+ * that audio is uncompressed.
+ */
+ public float getDuration(){
+ int bytesPerSec = (bitsPerSample / 8) * channels * sampleRate;
+ if (audioData != null)
+ return (float) audioData.capacity() / bytesPerSec;
+ else
+ return Float.NaN; // unknown
+ }
+
+ @Override
+ public String toString(){
+ return getClass().getSimpleName() +
+ "[id="+id+", ch="+channels+", bits="+bitsPerSample +
+ ", rate="+sampleRate+", duration="+getDuration()+"]";
+ }
+
+ /**
+ * Update the data in the buffer with new data.
+ * @param data
+ */
+ public void updateData(ByteBuffer data){
+ this.audioData = data;
+ updateNeeded = true;
+ }
+
+ /**
+ * @return The buffered audio data.
+ */
+ public ByteBuffer getData(){
+ return audioData;
+ }
+
+ public void resetObject() {
+ id = -1;
+ setUpdateNeeded();
+ }
+
+ public void deleteObject(AudioRenderer ar) {
+
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((AudioRenderer)rendererObject).deleteAudioData(this);
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new AudioBuffer(id);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioContext.java b/engine/src/core/com/jme3/audio/AudioContext.java
new file mode 100644
index 0000000..642f832
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioContext.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+/**
+ * Holds render thread specific audio context information.
+ *
+ * @author Paul Speed
+ */
+public class AudioContext {
+
+ private static ThreadLocal<AudioRenderer> audioRenderer = new ThreadLocal<AudioRenderer>();
+
+ public static void setAudioRenderer( AudioRenderer ar ) {
+ audioRenderer.set(ar);
+ }
+
+ public static AudioRenderer getAudioRenderer() {
+ return audioRenderer.get();
+ }
+}
diff --git a/engine/src/core/com/jme3/audio/AudioData.java b/engine/src/core/com/jme3/audio/AudioData.java
new file mode 100644
index 0000000..186a734
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioData.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.util.NativeObject;
+
+/**
+ * <code>AudioData</code> is an abstract representation
+ * of audio data. There are two ways to handle audio data, short audio files
+ * are to be stored entirely in memory, while long audio files (music) are
+ * streamed from the hard drive as they are played.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AudioData extends NativeObject {
+
+ protected int sampleRate;
+ protected int channels;
+ protected int bitsPerSample;
+
+ public enum DataType {
+ Buffer,
+ Stream
+ }
+
+ public AudioData(){
+ super(AudioData.class);
+ }
+
+ protected AudioData(int id){
+ super(AudioData.class, id);
+ }
+
+ /**
+ * @return The data type, either <code>Buffer</code> or <code>Stream</code>.
+ */
+ public abstract DataType getDataType();
+
+ /**
+ * @return the duration in seconds of the audio clip.
+ */
+ public abstract float getDuration();
+
+ /**
+ * @return Bits per single sample from a channel.
+ */
+ public int getBitsPerSample() {
+ return bitsPerSample;
+ }
+
+ /**
+ * @return Number of channels. 1 for mono, 2 for stereo, etc.
+ */
+ public int getChannels() {
+ return channels;
+ }
+
+ /**
+ * @return The sample rate, or how many samples per second.
+ */
+ public int getSampleRate() {
+ return sampleRate;
+ }
+
+ /**
+ * Setup the format of the audio data.
+ * @param channels # of channels, 1 = mono, 2 = stereo
+ * @param bitsPerSample Bits per sample, e.g 8 bits, 16 bits.
+ * @param sampleRate Sample rate, 44100, 22050, etc.
+ */
+ public void setupFormat(int channels, int bitsPerSample, int sampleRate){
+ if (id != -1)
+ throw new IllegalStateException("Already set up");
+
+ this.channels = channels;
+ this.bitsPerSample = bitsPerSample;
+ this.sampleRate = sampleRate;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioKey.java b/engine/src/core/com/jme3/audio/AudioKey.java
new file mode 100644
index 0000000..e9492bb
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioKey.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * <code>AudioKey</code> is extending AssetKey by holding stream flag.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioKey extends AssetKey<AudioData> {
+
+ private boolean stream;
+ private boolean streamCache;
+
+ /**
+ * Create a new AudioKey.
+ *
+ * @param name Name of the asset
+ * @param stream If true, the audio will be streamed from harddrive,
+ * otherwise it will be buffered entirely and then played.
+ * @param streamCache If stream is true, then this specifies if
+ * the stream cache is used. When enabled, the audio stream will
+ * be read entirely but not decoded, allowing features such as
+ * seeking, determining duration and looping.
+ */
+ public AudioKey(String name, boolean stream, boolean streamCache){
+ this(name, stream);
+ this.streamCache = streamCache;
+ }
+
+ /**
+ * Create a new AudioKey
+ *
+ * @param name Name of the asset
+ * @param stream If true, the audio will be streamed from harddrive,
+ * otherwise it will be buffered entirely and then played.
+ */
+ public AudioKey(String name, boolean stream){
+ super(name);
+ this.stream = stream;
+ }
+
+ public AudioKey(String name){
+ super(name);
+ this.stream = false;
+ }
+
+ public AudioKey(){
+ }
+
+ @Override
+ public String toString(){
+ return name + (stream ?
+ (streamCache ?
+ " (Stream/Cache)" :
+ " (Stream)") :
+ " (Buffer)");
+ }
+
+ /**
+ * @return True if the loaded audio should be a {@link AudioStream} or
+ * false if it should be a {@link AudioBuffer}.
+ */
+ public boolean isStream() {
+ return stream;
+ }
+
+ /**
+ * Specifies if the stream cache is used.
+ *
+ * When enabled, the audio stream will
+ * be read entirely but not decoded, allowing features such as
+ * seeking, looping and determining duration.
+ */
+ public boolean useStreamCache(){
+ return streamCache;
+ }
+
+ @Override
+ public boolean shouldCache(){
+ return !stream && !streamCache;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(stream, "do_stream", false);
+ oc.write(streamCache, "use_stream_cache", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ stream = ic.readBoolean("do_stream", false);
+ streamCache = ic.readBoolean("use_stream_cache", false);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/audio/AudioNode.java b/engine/src/core/com/jme3/audio/AudioNode.java
new file mode 100644
index 0000000..bc8b8cd
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioNode.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.util.PlaceholderAssets;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An <code>AudioNode</code> is used in jME3 for playing audio files.
+ * <br/>
+ * First, an {@link AudioNode} is loaded from file, and then assigned
+ * to an audio node for playback. Once the audio node is attached to the
+ * scene, its location will influence the position it is playing from relative
+ * to the {@link Listener}.
+ * <br/>
+ * An audio node can also play in "headspace", meaning its location
+ * or velocity does not influence how it is played.
+ * The "positional" property of an AudioNode can be set via
+ * {@link AudioNode#setPositional(boolean) }.
+ *
+ * @author normenhansen
+ * @author Kirill Vainer
+ */
+public class AudioNode extends Node {
+
+ protected boolean loop = false;
+ protected float volume = 1;
+ protected float pitch = 1;
+ protected float timeOffset = 0;
+ protected Filter dryFilter;
+ protected AudioKey audioKey;
+ protected transient AudioData data = null;
+ protected transient volatile Status status = Status.Stopped;
+ protected transient volatile int channel = -1;
+ protected Vector3f velocity = new Vector3f();
+ protected boolean reverbEnabled = true;
+ protected float maxDistance = 200; // 200 meters
+ protected float refDistance = 10; // 10 meters
+ protected Filter reverbFilter;
+ private boolean directional = false;
+ protected Vector3f direction = new Vector3f(0, 0, 1);
+ protected float innerAngle = 360;
+ protected float outerAngle = 360;
+ protected boolean positional = true;
+
+ /**
+ * <code>Status</code> indicates the current status of the audio node.
+ */
+ public enum Status {
+ /**
+ * The audio node is currently playing. This will be set if
+ * {@link AudioNode#play() } is called.
+ */
+ Playing,
+
+ /**
+ * The audio node is currently paused.
+ */
+ Paused,
+
+ /**
+ * The audio node is currently stopped.
+ * This will be set if {@link AudioNode#stop() } is called
+ * or the audio has reached the end of the file.
+ */
+ Stopped,
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> without any audio data set.
+ */
+ public AudioNode() {
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> without any audio data set.
+ *
+ * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+ *
+ * @deprecated AudioRenderer parameter is ignored.
+ */
+ public AudioNode(AudioRenderer audioRenderer) {
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given data and key.
+ *
+ * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+ * @param audioData The audio data contains the audio track to play.
+ * @param audioKey The audio key that was used to load the AudioData
+ *
+ * @deprecated AudioRenderer parameter is ignored.
+ */
+ public AudioNode(AudioRenderer audioRenderer, AudioData audioData, AudioKey audioKey) {
+ setAudioData(audioData, audioKey);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given data and key.
+ *
+ * @param audioData The audio data contains the audio track to play.
+ * @param audioKey The audio key that was used to load the AudioData
+ */
+ public AudioNode(AudioData audioData, AudioKey audioKey) {
+ setAudioData(audioData, audioKey);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ * @param stream If true, the audio will be streamed gradually from disk,
+ * otherwise, it will be buffered.
+ * @param streamCache If stream is also true, then this specifies if
+ * the stream cache is used. When enabled, the audio stream will
+ * be read entirely but not decoded, allowing features such as
+ * seeking, looping and determining duration.
+ *
+ * @deprecated AudioRenderer parameter is ignored.
+ */
+ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream, boolean streamCache) {
+ this.audioKey = new AudioKey(name, stream, streamCache);
+ this.data = (AudioData) assetManager.loadAsset(audioKey);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ * @param stream If true, the audio will be streamed gradually from disk,
+ * otherwise, it will be buffered.
+ * @param streamCache If stream is also true, then this specifies if
+ * the stream cache is used. When enabled, the audio stream will
+ * be read entirely but not decoded, allowing features such as
+ * seeking, looping and determining duration.
+ */
+ public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) {
+ this.audioKey = new AudioKey(name, stream, streamCache);
+ this.data = (AudioData) assetManager.loadAsset(audioKey);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ * @param stream If true, the audio will be streamed gradually from disk,
+ * otherwise, it will be buffered.
+ *
+ * @deprecated AudioRenderer parameter is ignored.
+ */
+ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name, boolean stream) {
+ this(audioRenderer, assetManager, name, stream, false);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ * @param stream If true, the audio will be streamed gradually from disk,
+ * otherwise, it will be buffered.
+ */
+ public AudioNode(AssetManager assetManager, String name, boolean stream) {
+ this(assetManager, name, stream, false);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param audioRenderer The audio renderer to use for playing. Cannot be null.
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ *
+ * @deprecated AudioRenderer parameter is ignored.
+ */
+ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) {
+ this(assetManager, name, false);
+ }
+
+ /**
+ * Creates a new <code>AudioNode</code> with the given audio file.
+ *
+ * @param assetManager The asset manager to use to load the audio file
+ * @param name The filename of the audio file
+ */
+ public AudioNode(AssetManager assetManager, String name) {
+ this(assetManager, name, false);
+ }
+
+ protected AudioRenderer getRenderer() {
+ AudioRenderer result = AudioContext.getAudioRenderer();
+ if( result == null )
+ throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." );
+ return result;
+ }
+
+ /**
+ * Start playing the audio.
+ */
+ public void play(){
+ getRenderer().playSource(this);
+ }
+
+ /**
+ * Start playing an instance of this audio. This method can be used
+ * to play the same <code>AudioNode</code> multiple times. Note
+ * that changes to the parameters of this AudioNode will not effect the
+ * instances already playing.
+ */
+ public void playInstance(){
+ getRenderer().playSourceInstance(this);
+ }
+
+ /**
+ * Stop playing the audio that was started with {@link AudioNode#play() }.
+ */
+ public void stop(){
+ getRenderer().stopSource(this);
+ }
+
+ /**
+ * Pause the audio that was started with {@link AudioNode#play() }.
+ */
+ public void pause(){
+ getRenderer().pauseSource(this);
+ }
+
+ /**
+ * Do not use.
+ */
+ public final void setChannel(int channel) {
+ if (status != Status.Stopped) {
+ throw new IllegalStateException("Can only set source id when stopped");
+ }
+
+ this.channel = channel;
+ }
+
+ /**
+ * Do not use.
+ */
+ public int getChannel() {
+ return channel;
+ }
+
+ /**
+ * @return The {#link Filter dry filter} that is set.
+ * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
+ */
+ public Filter getDryFilter() {
+ return dryFilter;
+ }
+
+ /**
+ * Set the dry filter to use for this audio node.
+ *
+ * When {@link AudioNode#setReverbEnabled(boolean) reverb} is used,
+ * the dry filter will only influence the "dry" portion of the audio,
+ * e.g. not the reverberated parts of the AudioNode playing.
+ *
+ * See the relevent documentation for the {@link Filter} to determine
+ * the effect.
+ *
+ * @param dryFilter The filter to set, or null to disable dry filter.
+ */
+ public void setDryFilter(Filter dryFilter) {
+ this.dryFilter = dryFilter;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.DryFilter);
+ }
+
+ /**
+ * Set the audio data to use for the audio. Note that this method
+ * can only be called once, if for example the audio node was initialized
+ * without an {@link AudioData}.
+ *
+ * @param audioData The audio data contains the audio track to play.
+ * @param audioKey The audio key that was used to load the AudioData
+ */
+ public void setAudioData(AudioData audioData, AudioKey audioKey) {
+ if (data != null) {
+ throw new IllegalStateException("Cannot change data once its set");
+ }
+
+ data = audioData;
+ this.audioKey = audioKey;
+ }
+
+ /**
+ * @return The {@link AudioData} set previously with
+ * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) }
+ * or any of the constructors that initialize the audio data.
+ */
+ public AudioData getAudioData() {
+ return data;
+ }
+
+ /**
+ * @return The {@link Status} of the audio node.
+ * The status will be changed when either the {@link AudioNode#play() }
+ * or {@link AudioNode#stop() } methods are called.
+ */
+ public Status getStatus() {
+ return status;
+ }
+
+ /**
+ * Do not use.
+ */
+ public final void setStatus(Status status) {
+ this.status = status;
+ }
+
+ /**
+ * @return True if the audio will keep looping after it is done playing,
+ * otherwise, false.
+ * @see AudioNode#setLooping(boolean)
+ */
+ public boolean isLooping() {
+ return loop;
+ }
+
+ /**
+ * Set the looping mode for the audio node. The default is false.
+ *
+ * @param loop True if the audio should keep looping after it is done playing.
+ */
+ public void setLooping(boolean loop) {
+ this.loop = loop;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Looping);
+ }
+
+ /**
+ * @return The pitch of the audio, also the speed of playback.
+ *
+ * @see AudioNode#setPitch(float)
+ */
+ public float getPitch() {
+ return pitch;
+ }
+
+ /**
+ * Set the pitch of the audio, also the speed of playback.
+ * The value must be between 0.5 and 2.0.
+ *
+ * @param pitch The pitch to set.
+ * @throws IllegalArgumentException If pitch is not between 0.5 and 2.0.
+ */
+ public void setPitch(float pitch) {
+ if (pitch < 0.5f || pitch > 2.0f) {
+ throw new IllegalArgumentException("Pitch must be between 0.5 and 2.0");
+ }
+
+ this.pitch = pitch;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Pitch);
+ }
+
+ /**
+ * @return The volume of this audio node.
+ *
+ * @see AudioNode#setVolume(float)
+ */
+ public float getVolume() {
+ return volume;
+ }
+
+ /**
+ * Set the volume of this audio node.
+ *
+ * The volume is specified as gain. 1.0 is the default.
+ *
+ * @param volume The volume to set.
+ * @throws IllegalArgumentException If volume is negative
+ */
+ public void setVolume(float volume) {
+ if (volume < 0f) {
+ throw new IllegalArgumentException("Volume cannot be negative");
+ }
+
+ this.volume = volume;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Volume);
+ }
+
+ /**
+ * @return The time offset in seconds when the sound will start playing.
+ */
+ public float getTimeOffset() {
+ return timeOffset;
+ }
+
+ /**
+ * Set the time offset in seconds when the sound will start playing.
+ *
+ * @param timeOffset The time offset
+ * @throws IllegalArgumentException If timeOffset is negative
+ */
+ public void setTimeOffset(float timeOffset) {
+ if (timeOffset < 0f) {
+ throw new IllegalArgumentException("Time offset cannot be negative");
+ }
+
+ this.timeOffset = timeOffset;
+ if (data instanceof AudioStream) {
+ System.out.println("request setTime");
+ ((AudioStream) data).setTime(timeOffset);
+ }else if(status == Status.Playing){
+ stop();
+ play();
+ }
+ }
+
+ /**
+ * @return The velocity of the audio node.
+ *
+ * @see AudioNode#setVelocity(com.jme3.math.Vector3f)
+ */
+ public Vector3f getVelocity() {
+ return velocity;
+ }
+
+ /**
+ * Set the velocity of the audio node. The velocity is expected
+ * to be in meters. Does nothing if the audio node is not positional.
+ *
+ * @param velocity The velocity to set.
+ * @see AudioNode#setPositional(boolean)
+ */
+ public void setVelocity(Vector3f velocity) {
+ this.velocity.set(velocity);
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Velocity);
+ }
+
+ /**
+ * @return True if reverb is enabled, otherwise false.
+ *
+ * @see AudioNode#setReverbEnabled(boolean)
+ */
+ public boolean isReverbEnabled() {
+ return reverbEnabled;
+ }
+
+ /**
+ * Set to true to enable reverberation effects for this audio node.
+ * Does nothing if the audio node is not positional.
+ * <br/>
+ * When enabled, the audio environment set with
+ * {@link AudioRenderer#setEnvironment(com.jme3.audio.Environment) }
+ * will apply a reverb effect to the audio playing from this audio node.
+ *
+ * @param reverbEnabled True to enable reverb.
+ */
+ public void setReverbEnabled(boolean reverbEnabled) {
+ this.reverbEnabled = reverbEnabled;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.ReverbEnabled);
+ }
+
+ /**
+ * @return Filter for the reverberations of this audio node.
+ *
+ * @see AudioNode#setReverbFilter(com.jme3.audio.Filter)
+ */
+ public Filter getReverbFilter() {
+ return reverbFilter;
+ }
+
+ /**
+ * Set the reverb filter for this audio node.
+ * <br/>
+ * The reverb filter will influence the reverberations
+ * of the audio node playing. This only has an effect if
+ * reverb is enabled.
+ *
+ * @param reverbFilter The reverb filter to set.
+ * @see AudioNode#setDryFilter(com.jme3.audio.Filter)
+ */
+ public void setReverbFilter(Filter reverbFilter) {
+ this.reverbFilter = reverbFilter;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.ReverbFilter);
+ }
+
+ /**
+ * @return Max distance for this audio node.
+ *
+ * @see AudioNode#setMaxDistance(float)
+ */
+ public float getMaxDistance() {
+ return maxDistance;
+ }
+
+ /**
+ * Set the maximum distance for the attenuation of the audio node.
+ * Does nothing if the audio node is not positional.
+ * <br/>
+ * The maximum distance is the distance beyond which the audio
+ * node will no longer be attenuated. Normal attenuation is logarithmic
+ * from refDistance (it reduces by half when the distance doubles).
+ * Max distance sets where this fall-off stops and the sound will never
+ * get any quieter than at that distance. If you want a sound to fall-off
+ * very quickly then set ref distance very short and leave this distance
+ * very long.
+ *
+ * @param maxDistance The maximum playing distance.
+ * @throws IllegalArgumentException If maxDistance is negative
+ */
+ public void setMaxDistance(float maxDistance) {
+ if (maxDistance < 0) {
+ throw new IllegalArgumentException("Max distance cannot be negative");
+ }
+
+ this.maxDistance = maxDistance;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.MaxDistance);
+ }
+
+ /**
+ * @return The reference playing distance for the audio node.
+ *
+ * @see AudioNode#setRefDistance(float)
+ */
+ public float getRefDistance() {
+ return refDistance;
+ }
+
+ /**
+ * Set the reference playing distance for the audio node.
+ * Does nothing if the audio node is not positional.
+ * <br/>
+ * The reference playing distance is the distance at which the
+ * audio node will be exactly half of its volume.
+ *
+ * @param refDistance The reference playing distance.
+ * @throws IllegalArgumentException If refDistance is negative
+ */
+ public void setRefDistance(float refDistance) {
+ if (refDistance < 0) {
+ throw new IllegalArgumentException("Reference distance cannot be negative");
+ }
+
+ this.refDistance = refDistance;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.RefDistance);
+ }
+
+ /**
+ * @return True if the audio node is directional
+ *
+ * @see AudioNode#setDirectional(boolean)
+ */
+ public boolean isDirectional() {
+ return directional;
+ }
+
+ /**
+ * Set the audio node to be directional.
+ * Does nothing if the audio node is not positional.
+ * <br/>
+ * After setting directional, you should call
+ * {@link AudioNode#setDirection(com.jme3.math.Vector3f) }
+ * to set the audio node's direction.
+ *
+ * @param directional If the audio node is directional
+ */
+ public void setDirectional(boolean directional) {
+ this.directional = directional;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.IsDirectional);
+ }
+
+ /**
+ * @return The direction of this audio node.
+ *
+ * @see AudioNode#setDirection(com.jme3.math.Vector3f)
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * Set the direction of this audio node.
+ * Does nothing if the audio node is not directional.
+ *
+ * @param direction
+ * @see AudioNode#setDirectional(boolean)
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction = direction;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Direction);
+ }
+
+ /**
+ * @return The directional audio node, cone inner angle.
+ *
+ * @see AudioNode#setInnerAngle(float)
+ */
+ public float getInnerAngle() {
+ return innerAngle;
+ }
+
+ /**
+ * Set the directional audio node cone inner angle.
+ * Does nothing if the audio node is not directional.
+ *
+ * @param innerAngle The cone inner angle.
+ */
+ public void setInnerAngle(float innerAngle) {
+ this.innerAngle = innerAngle;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.InnerAngle);
+ }
+
+ /**
+ * @return The directional audio node, cone outer angle.
+ *
+ * @see AudioNode#setOuterAngle(float)
+ */
+ public float getOuterAngle() {
+ return outerAngle;
+ }
+
+ /**
+ * Set the directional audio node cone outer angle.
+ * Does nothing if the audio node is not directional.
+ *
+ * @param outerAngle The cone outer angle.
+ */
+ public void setOuterAngle(float outerAngle) {
+ this.outerAngle = outerAngle;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.OuterAngle);
+ }
+
+ /**
+ * @return True if the audio node is positional.
+ *
+ * @see AudioNode#setPositional(boolean)
+ */
+ public boolean isPositional() {
+ return positional;
+ }
+
+ /**
+ * Set the audio node as positional.
+ * The position, velocity, and distance parameters effect positional
+ * audio nodes. Set to false if the audio node should play in "headspace".
+ *
+ * @param positional True if the audio node should be positional, otherwise
+ * false if it should be headspace.
+ */
+ public void setPositional(boolean positional) {
+ this.positional = positional;
+ if (channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.IsPositional);
+ }
+
+ @Override
+ public void updateGeometricState(){
+ boolean updatePos = false;
+ if ((refreshFlags & RF_TRANSFORM) != 0){
+ updatePos = true;
+ }
+
+ super.updateGeometricState();
+
+ if (updatePos && channel >= 0)
+ getRenderer().updateSourceParam(this, AudioParam.Position);
+ }
+
+ @Override
+ public AudioNode clone(){
+ AudioNode clone = (AudioNode) super.clone();
+
+ clone.direction = direction.clone();
+ clone.velocity = velocity.clone();
+
+ return clone;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(audioKey, "audio_key", null);
+ oc.write(loop, "looping", false);
+ oc.write(volume, "volume", 1);
+ oc.write(pitch, "pitch", 1);
+ oc.write(timeOffset, "time_offset", 0);
+ oc.write(dryFilter, "dry_filter", null);
+
+ oc.write(velocity, "velocity", null);
+ oc.write(reverbEnabled, "reverb_enabled", false);
+ oc.write(reverbFilter, "reverb_filter", null);
+ oc.write(maxDistance, "max_distance", 20);
+ oc.write(refDistance, "ref_distance", 10);
+
+ oc.write(directional, "directional", false);
+ oc.write(direction, "direction", null);
+ oc.write(innerAngle, "inner_angle", 360);
+ oc.write(outerAngle, "outer_angle", 360);
+
+ oc.write(positional, "positional", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+
+ // NOTE: In previous versions of jME3, audioKey was actually
+ // written with the name "key". This has been changed
+ // to "audio_key" in case Spatial's key will be written as "key".
+ if (ic.getSavableVersion(AudioNode.class) == 0){
+ audioKey = (AudioKey) ic.readSavable("key", null);
+ }else{
+ audioKey = (AudioKey) ic.readSavable("audio_key", null);
+ }
+
+ loop = ic.readBoolean("looping", false);
+ volume = ic.readFloat("volume", 1);
+ pitch = ic.readFloat("pitch", 1);
+ timeOffset = ic.readFloat("time_offset", 0);
+ dryFilter = (Filter) ic.readSavable("dry_filter", null);
+
+ velocity = (Vector3f) ic.readSavable("velocity", null);
+ reverbEnabled = ic.readBoolean("reverb_enabled", false);
+ reverbFilter = (Filter) ic.readSavable("reverb_filter", null);
+ maxDistance = ic.readFloat("max_distance", 20);
+ refDistance = ic.readFloat("ref_distance", 10);
+
+ directional = ic.readBoolean("directional", false);
+ direction = (Vector3f) ic.readSavable("direction", null);
+ innerAngle = ic.readFloat("inner_angle", 360);
+ outerAngle = ic.readFloat("outer_angle", 360);
+
+ positional = ic.readBoolean("positional", false);
+
+ if (audioKey != null) {
+ try {
+ data = im.getAssetManager().loadAudio(audioKey);
+ } catch (AssetNotFoundException ex){
+ Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key});
+ data = PlaceholderAssets.getPlaceholderAudio();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ String ret = getClass().getSimpleName()
+ + "[status=" + status;
+ if (volume != 1f) {
+ ret += ", vol=" + volume;
+ }
+ if (pitch != 1f) {
+ ret += ", pitch=" + pitch;
+ }
+ return ret + "]";
+ }
+}
diff --git a/engine/src/core/com/jme3/audio/AudioParam.java b/engine/src/core/com/jme3/audio/AudioParam.java
new file mode 100644
index 0000000..bf53c24
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioParam.java
@@ -0,0 +1,19 @@
+package com.jme3.audio;
+
+public enum AudioParam {
+ Volume,
+ Pitch,
+ Looping,
+ Position,
+ IsPositional,
+ Direction,
+ IsDirectional,
+ Velocity,
+ OuterAngle,
+ InnerAngle,
+ RefDistance,
+ MaxDistance,
+ DryFilter,
+ ReverbFilter,
+ ReverbEnabled;
+}
diff --git a/engine/src/core/com/jme3/audio/AudioRenderer.java b/engine/src/core/com/jme3/audio/AudioRenderer.java
new file mode 100644
index 0000000..15ad9c9
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioRenderer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+/**
+ * Interface to be implemented by audio renderers.
+ *
+ * @author Kirill Vainer
+ */
+public interface AudioRenderer {
+
+ /**
+ * @param listener The listener camera, all 3D sounds will be
+ * oriented around the listener.
+ */
+ public void setListener(Listener listener);
+
+ /**
+ * Sets the environment, used for reverb effects.
+ *
+ * @see AudioNode#setReverbEnabled(boolean)
+ * @param env The environment to set.
+ */
+ public void setEnvironment(Environment env);
+
+ public void playSourceInstance(AudioNode src);
+ public void playSource(AudioNode src);
+ public void pauseSource(AudioNode src);
+ public void stopSource(AudioNode src);
+
+ public void updateSourceParam(AudioNode src, AudioParam param);
+ public void updateListenerParam(Listener listener, ListenerParam param);
+
+ public void deleteFilter(Filter filter);
+ public void deleteAudioData(AudioData ad);
+
+ /**
+ * Initializes the renderer. Should be the first method called
+ * before using the system.
+ */
+ public void initialize();
+
+ /**
+ * Update the audio system. Must be called periodically.
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf);
+
+ /**
+ * Cleanup/destroy the audio system. Call this when app closes.
+ */
+ public void cleanup();
+}
diff --git a/engine/src/core/com/jme3/audio/AudioStream.java b/engine/src/core/com/jme3/audio/AudioStream.java
new file mode 100644
index 0000000..6d20d67
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/AudioStream.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.util.NativeObject;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>AudioStream</code> is an implementation of AudioData that
+ * acquires the audio from an InputStream. Audio can be streamed
+ * from network, hard drive etc. It is assumed the data coming
+ * from the input stream is uncompressed.
+ *
+ * @author Kirill Vainer
+ */
+public class AudioStream extends AudioData implements Closeable{
+
+ private final static Logger logger = Logger.getLogger(AudioStream.class.getName());
+ protected InputStream in;
+ protected float duration = -1f;
+ protected boolean open = false;
+ protected int[] ids;
+
+ public AudioStream(){
+ super();
+ }
+
+ protected AudioStream(int[] ids){
+ // Pass some dummy ID so handle
+ // doesn't get created.
+ super(-1);
+ // This is what gets destroyed in reality
+ this.ids = ids;
+ }
+
+ public void updateData(InputStream in, float duration){
+ if (id != -1 || this.in != null)
+ throw new IllegalStateException("Data already set!");
+
+ this.in = in;
+ this.duration = duration;
+ open = true;
+ }
+
+ /**
+ * Reads samples from the stream. The format of the data
+ * depends on the getSampleRate(), getChannels(), getBitsPerSample()
+ * values.
+ *
+ * @param buf Buffer where to read the samples
+ * @param offset The offset in the buffer where to read samples
+ * @param length The length inside the buffer where to read samples
+ * @return number of bytes read.
+ */
+ public int readSamples(byte[] buf, int offset, int length){
+ if (!open)
+ return -1;
+
+ try{
+ return in.read(buf, offset, length);
+ }catch (IOException ex){
+ return -1;
+ }
+ }
+
+ /**
+ * Reads samples from the stream.
+ *
+ * @see AudioStream#readSamples(byte[], int, int)
+ * @param buf Buffer where to read the samples
+ * @return number of bytes read.
+ */
+ public int readSamples(byte[] buf){
+ return readSamples(buf, 0, buf.length);
+ }
+
+ public float getDuration(){
+ return duration;
+ }
+
+ @Override
+ public int getId(){
+ throw new RuntimeException("Don't use getId() on streams");
+ }
+
+ @Override
+ public void setId(int id){
+ throw new RuntimeException("Don't use setId() on streams");
+ }
+
+ public void initIds(int count){
+ ids = new int[count];
+ }
+
+ public int getId(int index){
+ return ids[index];
+ }
+
+ public void setId(int index, int id){
+ ids[index] = id;
+ }
+
+ public int[] getIds(){
+ return ids;
+ }
+
+ public void setIds(int[] ids){
+ this.ids = ids;
+ }
+
+ @Override
+ public DataType getDataType() {
+ return DataType.Stream;
+ }
+
+ @Override
+ public void resetObject() {
+ id = -1;
+ ids = null;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ // It seems that the audio renderer is already doing a good
+ // job at deleting audio streams when they finish playing.
+// ((AudioRenderer)rendererObject).deleteAudioData(this);
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new AudioStream(ids);
+ }
+
+ /**
+ * @return Whether the stream is open or not. Reading from a closed
+ * stream will always return eof.
+ */
+ public boolean isOpen(){
+ return open;
+ }
+
+ /**
+ * Closes the stream, releasing all data relating to it. Reading
+ * from the stream will return eof.
+ * @throws IOException
+ */
+ public void close() {
+ if (in != null && open){
+ try{
+ in.close();
+ }catch (IOException ex){
+ }
+ open = false;
+ }else{
+ throw new RuntimeException("AudioStream is already closed!");
+ }
+ }
+
+
+ public void setTime(float time){
+ if(in instanceof SeekableStream){
+ ((SeekableStream)in).setTime(time);
+ }else{
+ logger.log(Level.WARNING,"Cannot use setTime on a stream that is not seekable. You must load the file with the streamCache option set to true");
+ }
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/audio/Environment.java b/engine/src/core/com/jme3/audio/Environment.java
new file mode 100644
index 0000000..9a1279e
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Environment.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.math.FastMath;
+
+/**
+ * Audio environment, for reverb effects.
+ * @author Kirill
+ */
+public class Environment {
+
+ private float airAbsorbGainHf = 0.99426f;
+ private float roomRolloffFactor = 0;
+
+ private float decayTime = 1.49f;
+ private float decayHFRatio = 0.54f;
+
+ private float density = 1.0f;
+ private float diffusion = 0.3f;
+
+ private float gain = 0.316f;
+ private float gainHf = 0.022f;
+
+ private float lateReverbDelay = 0.088f;
+ private float lateReverbGain = 0.768f;
+
+ private float reflectDelay = 0.162f;
+ private float reflectGain = 0.052f;
+
+ private boolean decayHfLimit = true;
+
+ public static final Environment Garage, Dungeon, Cavern, AcousticLab, Closet;
+
+ static {
+ Garage = new Environment(1, 1, 1, 1, .9f, .5f, .751f, .0039f, .661f, .0137f);
+ Dungeon = new Environment(.75f, 1, 1, .75f, 1.6f, 1, 0.95f, 0.0026f, 0.93f, 0.0103f);
+ Cavern = new Environment(.5f, 1, 1, .5f, 2.25f, 1, .908f, .0103f, .93f, .041f);
+ AcousticLab = new Environment(.5f, 1, 1, 1, .28f, 1, .87f, .002f, .81f, .008f);
+ Closet = new Environment(1, 1, 1, 1, .15f, 1, .6f, .0025f, .5f, .0006f);
+ }
+
+ private static final float eaxDbToAmp(float eaxDb){
+ float dB = eaxDb / 2000f;
+ return FastMath.pow(10f, dB);
+ }
+
+ public Environment(){
+ }
+
+ public Environment(Environment source) {
+ this.airAbsorbGainHf = source.airAbsorbGainHf;
+ this.roomRolloffFactor = source.roomRolloffFactor;
+ this.decayTime = source.decayTime;
+ this.decayHFRatio = source.decayHFRatio;
+ this.density = source.density;
+ this.diffusion = source.diffusion;
+ this.gain = source.gain;
+ this.gainHf = source.gainHf;
+ this.lateReverbDelay = source.lateReverbDelay;
+ this.lateReverbGain = source.lateReverbGain;
+ this.reflectDelay = source.reflectDelay;
+ this.reflectGain = source.reflectGain;
+ this.decayHfLimit = source.decayHfLimit;
+ }
+
+ public Environment(float density, float diffusion, float gain, float gainHf,
+ float decayTime, float decayHf, float reflGain,
+ float reflDelay, float lateGain, float lateDelay){
+ this.decayTime = decayTime;
+ this.decayHFRatio = decayHf;
+ this.density = density;
+ this.diffusion = diffusion;
+ this.gain = gain;
+ this.gainHf = gainHf;
+ this.lateReverbDelay = lateDelay;
+ this.lateReverbGain = lateGain;
+ this.reflectDelay = reflDelay;
+ this.reflectGain = reflGain;
+ }
+
+ public Environment(float[] e){
+ if (e.length != 28)
+ throw new IllegalArgumentException("Not an EAX preset");
+
+ // skip env id
+ // e[0]
+ // skip room size
+ // e[1]
+
+// density = 0;
+ diffusion = e[2];
+ gain = eaxDbToAmp(e[3]); // convert
+ gainHf = eaxDbToAmp(e[4]) / eaxDbToAmp(e[5]); // convert
+ decayTime = e[6];
+ decayHFRatio = e[7] / e[8];
+ reflectGain = eaxDbToAmp(e[9]); // convert
+ reflectDelay = e[10];
+
+ // skip 3 pan values
+ // e[11] e[12] e[13]
+
+ lateReverbGain = eaxDbToAmp(e[14]); // convert
+ lateReverbDelay = e[15];
+
+ // skip 3 pan values
+ // e[16] e[17] e[18]
+
+ // skip echo time, echo damping, mod time, mod damping
+ // e[19] e[20] e[21] e[22]
+
+ airAbsorbGainHf = eaxDbToAmp(e[23]);
+
+ // skip HF Reference and LF Reference
+ // e[24] e[25]
+
+ roomRolloffFactor = e[26];
+
+ // skip flags
+ // e[27]
+ }
+
+ public float getAirAbsorbGainHf() {
+ return airAbsorbGainHf;
+ }
+
+ public void setAirAbsorbGainHf(float airAbsorbGainHf) {
+ this.airAbsorbGainHf = airAbsorbGainHf;
+ }
+
+ public float getDecayHFRatio() {
+ return decayHFRatio;
+ }
+
+ public void setDecayHFRatio(float decayHFRatio) {
+ this.decayHFRatio = decayHFRatio;
+ }
+
+ public boolean isDecayHfLimit() {
+ return decayHfLimit;
+ }
+
+ public void setDecayHfLimit(boolean decayHfLimit) {
+ this.decayHfLimit = decayHfLimit;
+ }
+
+ public float getDecayTime() {
+ return decayTime;
+ }
+
+ public void setDecayTime(float decayTime) {
+ this.decayTime = decayTime;
+ }
+
+ public float getDensity() {
+ return density;
+ }
+
+ public void setDensity(float density) {
+ this.density = density;
+ }
+
+ public float getDiffusion() {
+ return diffusion;
+ }
+
+ public void setDiffusion(float diffusion) {
+ this.diffusion = diffusion;
+ }
+
+ public float getGain() {
+ return gain;
+ }
+
+ public void setGain(float gain) {
+ this.gain = gain;
+ }
+
+ public float getGainHf() {
+ return gainHf;
+ }
+
+ public void setGainHf(float gainHf) {
+ this.gainHf = gainHf;
+ }
+
+ public float getLateReverbDelay() {
+ return lateReverbDelay;
+ }
+
+ public void setLateReverbDelay(float lateReverbDelay) {
+ this.lateReverbDelay = lateReverbDelay;
+ }
+
+ public float getLateReverbGain() {
+ return lateReverbGain;
+ }
+
+ public void setLateReverbGain(float lateReverbGain) {
+ this.lateReverbGain = lateReverbGain;
+ }
+
+ public float getReflectDelay() {
+ return reflectDelay;
+ }
+
+ public void setReflectDelay(float reflectDelay) {
+ this.reflectDelay = reflectDelay;
+ }
+
+ public float getReflectGain() {
+ return reflectGain;
+ }
+
+ public void setReflectGain(float reflectGain) {
+ this.reflectGain = reflectGain;
+ }
+
+ public float getRoomRolloffFactor() {
+ return roomRolloffFactor;
+ }
+
+ public void setRoomRolloffFactor(float roomRolloffFactor) {
+ this.roomRolloffFactor = roomRolloffFactor;
+ }
+}
diff --git a/engine/src/core/com/jme3/audio/Filter.java b/engine/src/core/com/jme3/audio/Filter.java
new file mode 100644
index 0000000..da231d6
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Filter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+
+public abstract class Filter extends NativeObject implements Savable {
+
+ public Filter(){
+ super(Filter.class);
+ }
+
+ protected Filter(int id){
+ super(Filter.class, id);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ // nothing to save
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ // nothing to read
+ }
+
+ @Override
+ public void resetObject() {
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((AudioRenderer)rendererObject).deleteFilter(this);
+ }
+
+ @Override
+ public abstract NativeObject createDestructableClone();
+
+}
diff --git a/engine/src/core/com/jme3/audio/Listener.java b/engine/src/core/com/jme3/audio/Listener.java
new file mode 100644
index 0000000..609b0ca
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/Listener.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+
+public class Listener {
+
+ private Vector3f location;
+ private Vector3f velocity;
+ private Quaternion rotation;
+ private float volume = 1;
+ private AudioRenderer renderer;
+
+ public Listener(){
+ location = new Vector3f();
+ velocity = new Vector3f();
+ rotation = new Quaternion();
+ }
+
+ public Listener(Listener source){
+ location = source.location.clone();
+ velocity = source.velocity.clone();
+ rotation = source.rotation.clone();
+ volume = source.volume;
+ }
+
+ public void setRenderer(AudioRenderer renderer){
+ this.renderer = renderer;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
+ public void setVolume(float volume) {
+ this.volume = volume;
+ if (renderer != null)
+ renderer.updateListenerParam(this, ListenerParam.Volume);
+ }
+
+ public Vector3f getLocation() {
+ return location;
+ }
+
+ public Quaternion getRotation() {
+ return rotation;
+ }
+
+ public Vector3f getVelocity() {
+ return velocity;
+ }
+
+ public Vector3f getLeft(){
+ return rotation.getRotationColumn(0);
+ }
+
+ public Vector3f getUp(){
+ return rotation.getRotationColumn(1);
+ }
+
+ public Vector3f getDirection(){
+ return rotation.getRotationColumn(2);
+ }
+
+ public void setLocation(Vector3f location) {
+ this.location.set(location);
+ if (renderer != null)
+ renderer.updateListenerParam(this, ListenerParam.Position);
+ }
+
+ public void setRotation(Quaternion rotation) {
+ this.rotation.set(rotation);
+ if (renderer != null)
+ renderer.updateListenerParam(this, ListenerParam.Rotation);
+ }
+
+ public void setVelocity(Vector3f velocity) {
+ this.velocity.set(velocity);
+ if (renderer != null)
+ renderer.updateListenerParam(this, ListenerParam.Velocity);
+ }
+}
diff --git a/engine/src/core/com/jme3/audio/ListenerParam.java b/engine/src/core/com/jme3/audio/ListenerParam.java
new file mode 100644
index 0000000..6b3b55e
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/ListenerParam.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+public enum ListenerParam {
+ Position,
+ Velocity,
+ Rotation,
+ Volume;
+}
diff --git a/engine/src/core/com/jme3/audio/LowPassFilter.java b/engine/src/core/com/jme3/audio/LowPassFilter.java
new file mode 100644
index 0000000..58242cf
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/LowPassFilter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.audio;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+
+public class LowPassFilter extends Filter {
+
+ protected float volume, highFreqVolume;
+
+ public LowPassFilter(float volume, float highFreqVolume) {
+ super();
+ setVolume(volume);
+ setHighFreqVolume(highFreqVolume);
+ }
+
+ protected LowPassFilter(int id){
+ super(id);
+ }
+
+ public float getHighFreqVolume() {
+ return highFreqVolume;
+ }
+
+ public void setHighFreqVolume(float highFreqVolume) {
+ if (highFreqVolume < 0 || highFreqVolume > 1)
+ throw new IllegalArgumentException("High freq volume must be between 0 and 1");
+
+ this.highFreqVolume = highFreqVolume;
+ this.updateNeeded = true;
+ }
+
+ public float getVolume() {
+ return volume;
+ }
+
+ public void setVolume(float volume) {
+ if (volume < 0 || volume > 1)
+ throw new IllegalArgumentException("Volume must be between 0 and 1");
+
+ this.volume = volume;
+ this.updateNeeded = true;
+ }
+
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(volume, "volume", 0);
+ oc.write(highFreqVolume, "hf_volume", 0);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ volume = ic.readFloat("volume", 0);
+ highFreqVolume = ic.readFloat("hf_volume", 0);
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new LowPassFilter(id);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/audio/SeekableStream.java b/engine/src/core/com/jme3/audio/SeekableStream.java
new file mode 100644
index 0000000..bb5b9e4
--- /dev/null
+++ b/engine/src/core/com/jme3/audio/SeekableStream.java
@@ -0,0 +1,15 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.audio;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface SeekableStream{
+
+ public void setTime(float time);
+
+}
diff --git a/engine/src/core/com/jme3/bounding/BoundingBox.java b/engine/src/core/com/jme3/bounding/BoundingBox.java
new file mode 100644
index 0000000..bc11411
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingBox.java
@@ -0,0 +1,977 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bounding;
+
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.*;
+import com.jme3.scene.Mesh;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+//import com.jme.scene.TriMesh;
+
+/**
+ * <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>.
+ *
+ * @author Joshua Slack
+ * @version $Id: BoundingBox.java,v 1.50 2007/09/22 16:46:35 irrisor Exp $
+ */
+public class BoundingBox extends BoundingVolume {
+
+ float xExtent, yExtent, zExtent;
+
+ /**
+ * Default constructor instantiates a new <code>BoundingBox</code>
+ * object.
+ */
+ public BoundingBox() {
+ }
+
+ /**
+ * Contstructor instantiates a new <code>BoundingBox</code> object with
+ * given specs.
+ */
+ public BoundingBox(Vector3f c, float x, float y, float z) {
+ this.center.set(c);
+ this.xExtent = x;
+ this.yExtent = y;
+ this.zExtent = z;
+ }
+
+ public BoundingBox(BoundingBox source) {
+ this.center.set(source.center);
+ this.xExtent = source.xExtent;
+ this.yExtent = source.yExtent;
+ this.zExtent = source.zExtent;
+ }
+
+ public BoundingBox(Vector3f min, Vector3f max) {
+ setMinMax(min, max);
+ }
+
+ public Type getType() {
+ return Type.AABB;
+ }
+
+ /**
+ * <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.
+ */
+ public void computeFromPoints(FloatBuffer points) {
+ containAABB(points);
+ }
+
+ /**
+ * <code>computeFromTris</code> creates a new Bounding Box from a given
+ * set of triangles. It is used in OBBTree calculations.
+ *
+ * @param tris
+ * @param start
+ * @param end
+ */
+ public void computeFromTris(Triangle[] tris, int start, int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+
+ Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
+ Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
+
+ Vector3f point;
+ for (int i = start; i < end; i++) {
+ point = tris[i].get(0);
+ checkMinMax(min, max, point);
+ point = tris[i].get(1);
+ checkMinMax(min, max, point);
+ point = tris[i].get(2);
+ checkMinMax(min, max, point);
+ }
+
+ center.set(min.addLocal(max));
+ center.multLocal(0.5f);
+
+ xExtent = max.x - center.x;
+ yExtent = max.y - center.y;
+ zExtent = max.z - center.z;
+
+ vars.release();
+ }
+
+ public void computeFromTris(int[] indices, Mesh mesh, int start, int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+
+ Vector3f vect1 = vars.vect1;
+ Vector3f vect2 = vars.vect2;
+ Triangle triangle = vars.triangle;
+
+ Vector3f min = vect1.set(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
+ Vector3f max = vect2.set(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+ Vector3f point;
+
+ for (int i = start; i < end; i++) {
+ mesh.getTriangle(indices[i], triangle);
+ point = triangle.get(0);
+ checkMinMax(min, max, point);
+ point = triangle.get(1);
+ checkMinMax(min, max, point);
+ point = triangle.get(2);
+ checkMinMax(min, max, point);
+ }
+
+ center.set(min.addLocal(max));
+ center.multLocal(0.5f);
+
+ xExtent = max.x - center.x;
+ yExtent = max.y - center.y;
+ zExtent = max.z - center.z;
+
+ vars.release();
+ }
+
+ public static void checkMinMax(Vector3f min, Vector3f max, Vector3f point) {
+ if (point.x < min.x) {
+ min.x = point.x;
+ }
+ if (point.x > max.x) {
+ max.x = point.x;
+ }
+ if (point.y < min.y) {
+ min.y = point.y;
+ }
+ if (point.y > max.y) {
+ max.y = point.y;
+ }
+ if (point.z < min.z) {
+ min.z = point.z;
+ }
+ if (point.z > max.z) {
+ max.z = point.z;
+ }
+ }
+
+ /**
+ * <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(FloatBuffer points) {
+ if (points == null) {
+ return;
+ }
+
+ points.rewind();
+ if (points.remaining() <= 2) // we need at least a 3 float vector
+ {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+
+ BufferUtils.populateFromBuffer(vars.vect1, points, 0);
+ float minX = vars.vect1.x, minY = vars.vect1.y, minZ = vars.vect1.z;
+ float maxX = vars.vect1.x, maxY = vars.vect1.y, maxZ = vars.vect1.z;
+
+ for (int i = 1, len = points.remaining() / 3; i < len; i++) {
+ BufferUtils.populateFromBuffer(vars.vect1, points, i);
+
+ if (vars.vect1.x < minX) {
+ minX = vars.vect1.x;
+ } else if (vars.vect1.x > maxX) {
+ maxX = vars.vect1.x;
+ }
+
+ if (vars.vect1.y < minY) {
+ minY = vars.vect1.y;
+ } else if (vars.vect1.y > maxY) {
+ maxY = vars.vect1.y;
+ }
+
+ if (vars.vect1.z < minZ) {
+ minZ = vars.vect1.z;
+ } else if (vars.vect1.z > maxZ) {
+ maxZ = vars.vect1.z;
+ }
+ }
+
+ vars.release();
+
+ center.set(minX + maxX, minY + maxY, minZ + maxZ);
+ center.multLocal(0.5f);
+
+ xExtent = maxX - center.x;
+ yExtent = maxY - center.y;
+ zExtent = maxZ - center.z;
+ }
+
+ /**
+ * <code>transform</code> modifies the center of the box to reflect the
+ * change made via a rotation, translation and scale.
+ *
+ * @param trans
+ * the transform to apply
+ * @param store
+ * box to store result in
+ */
+ public BoundingVolume transform(Transform trans, BoundingVolume store) {
+
+ BoundingBox box;
+ if (store == null || store.getType() != Type.AABB) {
+ box = new BoundingBox();
+ } else {
+ box = (BoundingBox) store;
+ }
+
+ center.mult(trans.getScale(), box.center);
+ trans.getRotation().mult(box.center, box.center);
+ box.center.addLocal(trans.getTranslation());
+
+ TempVars vars = TempVars.get();
+
+ Matrix3f transMatrix = vars.tempMat3;
+ transMatrix.set(trans.getRotation());
+ // Make the rotation matrix all positive to get the maximum x/y/z extent
+ transMatrix.absoluteLocal();
+
+ Vector3f scale = trans.getScale();
+ vars.vect1.set(xExtent * scale.x, yExtent * scale.y, zExtent * scale.z);
+ transMatrix.mult(vars.vect1, vars.vect2);
+ // Assign the biggest rotations after scales.
+ box.xExtent = FastMath.abs(vars.vect2.getX());
+ box.yExtent = FastMath.abs(vars.vect2.getY());
+ box.zExtent = FastMath.abs(vars.vect2.getZ());
+
+ vars.release();
+
+ return box;
+ }
+
+ public BoundingVolume transform(Matrix4f trans, BoundingVolume store) {
+ BoundingBox box;
+ if (store == null || store.getType() != Type.AABB) {
+ box = new BoundingBox();
+ } else {
+ box = (BoundingBox) store;
+ }
+ TempVars vars = TempVars.get();
+
+
+ float w = trans.multProj(center, box.center);
+ box.center.divideLocal(w);
+
+ Matrix3f transMatrix = vars.tempMat3;
+ trans.toRotationMatrix(transMatrix);
+
+ // Make the rotation matrix all positive to get the maximum x/y/z extent
+ transMatrix.absoluteLocal();
+
+ vars.vect1.set(xExtent, yExtent, zExtent);
+ transMatrix.mult(vars.vect1, vars.vect1);
+
+ // Assign the biggest rotations after scales.
+ box.xExtent = FastMath.abs(vars.vect1.getX());
+ box.yExtent = FastMath.abs(vars.vect1.getY());
+ box.zExtent = FastMath.abs(vars.vect1.getZ());
+
+ vars.release();
+
+ return box;
+ }
+
+ /**
+ * <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.
+ */
+ public Plane.Side whichSide(Plane plane) {
+ float radius = FastMath.abs(xExtent * plane.getNormal().getX())
+ + FastMath.abs(yExtent * plane.getNormal().getY())
+ + FastMath.abs(zExtent * plane.getNormal().getZ());
+
+ float distance = plane.pseudoDistance(center);
+
+ //changed to < and > to prevent floating point precision problems
+ if (distance < -radius) {
+ return Plane.Side.Negative;
+ } else if (distance > radius) {
+ return Plane.Side.Positive;
+ } else {
+ return Plane.Side.None;
+ }
+ }
+
+ /**
+ * <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
+ */
+ public BoundingVolume merge(BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+ case AABB: {
+ BoundingBox vBox = (BoundingBox) volume;
+ return merge(vBox.center, vBox.xExtent, vBox.yExtent,
+ vBox.zExtent, new BoundingBox(new Vector3f(0, 0, 0), 0,
+ 0, 0));
+ }
+
+ case Sphere: {
+ BoundingSphere vSphere = (BoundingSphere) volume;
+ return merge(vSphere.center, vSphere.radius, vSphere.radius,
+ vSphere.radius, new BoundingBox(new Vector3f(0, 0, 0),
+ 0, 0, 0));
+ }
+
+// case OBB: {
+// OrientedBoundingBox box = (OrientedBoundingBox) volume;
+// 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
+ */
+ public BoundingVolume mergeLocal(BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+ case AABB: {
+ BoundingBox vBox = (BoundingBox) volume;
+ return merge(vBox.center, vBox.xExtent, vBox.yExtent,
+ vBox.zExtent, this);
+ }
+
+ case Sphere: {
+ BoundingSphere vSphere = (BoundingSphere) volume;
+ return merge(vSphere.center, vSphere.radius, vSphere.radius,
+ vSphere.radius, 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(OrientedBoundingBox volume) {
+// if (!volume.correctCorners)
+// volume.computeCorners();
+//
+// TempVars vars = TempVars.get();
+// Vector3f min = vars.compVect1.set(center.x - xExtent, center.y - yExtent,
+// center.z - zExtent);
+// Vector3f max = vars.compVect2.set(center.x + xExtent, center.y + yExtent,
+// center.z + zExtent);
+//
+// for (int i = 1; i < volume.vectorStore.length; i++) {
+// Vector3f temp = volume.vectorStore[i];
+// if (temp.x < min.x)
+// min.x = temp.x;
+// else if (temp.x > max.x)
+// max.x = temp.x;
+//
+// if (temp.y < min.y)
+// min.y = temp.y;
+// else if (temp.y > max.y)
+// max.y = temp.y;
+//
+// if (temp.z < min.z)
+// min.z = temp.z;
+// else if (temp.z > max.z)
+// max.z = temp.z;
+// }
+//
+// center.set(min.addLocal(max));
+// center.multLocal(0.5f);
+//
+// xExtent = max.x - center.x;
+// yExtent = max.y - center.y;
+// zExtent = max.z - center.z;
+// 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 rVal
+ * the resulting merged box.
+ * @return the resulting merged box.
+ */
+ private BoundingBox merge(Vector3f boxCenter, float boxX, float boxY,
+ float boxZ, BoundingBox rVal) {
+
+ TempVars vars = TempVars.get();
+
+ vars.vect1.x = center.x - xExtent;
+ if (vars.vect1.x > boxCenter.x - boxX) {
+ vars.vect1.x = boxCenter.x - boxX;
+ }
+ vars.vect1.y = center.y - yExtent;
+ if (vars.vect1.y > boxCenter.y - boxY) {
+ vars.vect1.y = boxCenter.y - boxY;
+ }
+ vars.vect1.z = center.z - zExtent;
+ if (vars.vect1.z > boxCenter.z - boxZ) {
+ vars.vect1.z = boxCenter.z - boxZ;
+ }
+
+ vars.vect2.x = center.x + xExtent;
+ if (vars.vect2.x < boxCenter.x + boxX) {
+ vars.vect2.x = boxCenter.x + boxX;
+ }
+ vars.vect2.y = center.y + yExtent;
+ if (vars.vect2.y < boxCenter.y + boxY) {
+ vars.vect2.y = boxCenter.y + boxY;
+ }
+ vars.vect2.z = center.z + zExtent;
+ if (vars.vect2.z < boxCenter.z + boxZ) {
+ vars.vect2.z = boxCenter.z + boxZ;
+ }
+
+ center.set(vars.vect2).addLocal(vars.vect1).multLocal(0.5f);
+
+ xExtent = vars.vect2.x - center.x;
+ yExtent = vars.vect2.y - center.y;
+ zExtent = vars.vect2.z - center.z;
+
+ vars.release();
+
+ return rVal;
+ }
+
+ /**
+ * <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
+ */
+ public BoundingVolume clone(BoundingVolume store) {
+ if (store != null && store.getType() == Type.AABB) {
+ BoundingBox rVal = (BoundingBox) store;
+ rVal.center.set(center);
+ rVal.xExtent = xExtent;
+ rVal.yExtent = yExtent;
+ rVal.zExtent = zExtent;
+ rVal.checkPlane = checkPlane;
+ return rVal;
+ }
+
+ BoundingBox rVal = new BoundingBox(center.clone(),
+ xExtent, yExtent, zExtent);
+ 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 getClass().getSimpleName() + " [Center: " + center + " xExtent: "
+ + xExtent + " yExtent: " + yExtent + " zExtent: " + zExtent
+ + "]";
+ }
+
+ /**
+ * intersects determines if this Bounding Box intersects with another given
+ * bounding volume. If so, true is returned, otherwise, false is returned.
+ *
+ * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume)
+ */
+ public boolean intersects(BoundingVolume bv) {
+ return bv.intersectsBoundingBox(this);
+ }
+
+ /**
+ * determines if this bounding box intersects a given bounding sphere.
+ *
+ * @see BoundingVolume#intersectsSphere(com.jme3.bounding.BoundingSphere)
+ */
+ public boolean intersectsSphere(BoundingSphere bs) {
+ assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bs.center);
+
+ if (FastMath.abs(center.x - bs.center.x) < bs.getRadius()
+ + xExtent
+ && FastMath.abs(center.y - bs.center.y) < bs.getRadius()
+ + yExtent
+ && FastMath.abs(center.z - bs.center.z) < bs.getRadius()
+ + zExtent) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * determines if this bounding box intersects a given bounding box. If the
+ * two boxes intersect in any way, true is returned. Otherwise, false is
+ * returned.
+ *
+ * @see BoundingVolume#intersectsBoundingBox(com.jme3.bounding.BoundingBox)
+ */
+ public boolean intersectsBoundingBox(BoundingBox bb) {
+ assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center);
+
+ if (center.x + xExtent < bb.center.x - bb.xExtent
+ || center.x - xExtent > bb.center.x + bb.xExtent) {
+ return false;
+ } else if (center.y + yExtent < bb.center.y - bb.yExtent
+ || center.y - yExtent > bb.center.y + bb.yExtent) {
+ return false;
+ } else if (center.z + zExtent < bb.center.z - bb.zExtent
+ || center.z - zExtent > bb.center.z + bb.zExtent) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * determines if this bounding box intersects with a given oriented bounding
+ * box.
+ *
+ * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)
+ */
+// public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {
+// return obb.intersectsBoundingBox(this);
+// }
+ /**
+ * determines if this bounding box intersects with a given ray object. If an
+ * intersection has occurred, true is returned, otherwise false is returned.
+ *
+ * @see BoundingVolume#intersects(com.jme3.math.Ray)
+ */
+ public boolean intersects(Ray ray) {
+ assert Vector3f.isValidVector(center);
+
+ float rhs;
+
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = ray.origin.subtract(getCenter(vars.vect2), vars.vect1);
+
+ final float[] fWdU = vars.fWdU;
+ final float[] fAWdU = vars.fAWdU;
+ final float[] fDdU = vars.fDdU;
+ final float[] fADdU = vars.fADdU;
+ final float[] fAWxDdU = vars.fAWxDdU;
+
+ fWdU[0] = ray.getDirection().dot(Vector3f.UNIT_X);
+ fAWdU[0] = FastMath.abs(fWdU[0]);
+ fDdU[0] = diff.dot(Vector3f.UNIT_X);
+ fADdU[0] = FastMath.abs(fDdU[0]);
+ if (fADdU[0] > xExtent && fDdU[0] * fWdU[0] >= 0.0) {
+ vars.release();
+ return false;
+ }
+
+ fWdU[1] = ray.getDirection().dot(Vector3f.UNIT_Y);
+ fAWdU[1] = FastMath.abs(fWdU[1]);
+ fDdU[1] = diff.dot(Vector3f.UNIT_Y);
+ fADdU[1] = FastMath.abs(fDdU[1]);
+ if (fADdU[1] > yExtent && fDdU[1] * fWdU[1] >= 0.0) {
+ vars.release();
+ return false;
+ }
+
+ fWdU[2] = ray.getDirection().dot(Vector3f.UNIT_Z);
+ fAWdU[2] = FastMath.abs(fWdU[2]);
+ fDdU[2] = diff.dot(Vector3f.UNIT_Z);
+ fADdU[2] = FastMath.abs(fDdU[2]);
+ if (fADdU[2] > zExtent && fDdU[2] * fWdU[2] >= 0.0) {
+ vars.release();
+ return false;
+ }
+
+ Vector3f wCrossD = ray.getDirection().cross(diff, vars.vect2);
+
+ fAWxDdU[0] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_X));
+ rhs = yExtent * fAWdU[2] + zExtent * fAWdU[1];
+ if (fAWxDdU[0] > rhs) {
+ vars.release();
+ return false;
+ }
+
+ fAWxDdU[1] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Y));
+ rhs = xExtent * fAWdU[2] + zExtent * fAWdU[0];
+ if (fAWxDdU[1] > rhs) {
+ vars.release();
+ return false;
+ }
+
+ fAWxDdU[2] = FastMath.abs(wCrossD.dot(Vector3f.UNIT_Z));
+ rhs = xExtent * fAWdU[1] + yExtent * fAWdU[0];
+ if (fAWxDdU[2] > rhs) {
+ vars.release();
+ return false;
+ }
+
+ vars.release();
+ return true;
+ }
+
+ /**
+ * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)
+ */
+ private int collideWithRay(Ray ray, CollisionResults results) {
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center);
+ Vector3f direction = vars.vect2.set(ray.direction);
+
+ float[] t = {0f, Float.POSITIVE_INFINITY};
+
+ float saveT0 = t[0], saveT1 = t[1];
+ boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t)
+ && clip(-direction.x, +diff.x - xExtent, t)
+ && clip(+direction.y, -diff.y - yExtent, t)
+ && clip(-direction.y, +diff.y - yExtent, t)
+ && clip(+direction.z, -diff.z - zExtent, t)
+ && clip(-direction.z, +diff.z - zExtent, t);
+ vars.release();
+
+ if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) {
+ if (t[1] > t[0]) {
+ float[] distances = t;
+ Vector3f[] points = new Vector3f[]{
+ new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),
+ new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin)
+ };
+
+ CollisionResult result = new CollisionResult(points[0], distances[0]);
+ results.addCollision(result);
+ result = new CollisionResult(points[1], distances[1]);
+ results.addCollision(result);
+ return 2;
+ }
+
+ Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin);
+ CollisionResult result = new CollisionResult(point, t[0]);
+ results.addCollision(result);
+ return 1;
+ }
+ return 0;
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ if (other instanceof Ray) {
+ Ray ray = (Ray) other;
+ return collideWithRay(ray, results);
+ } else if (other instanceof Triangle) {
+ Triangle t = (Triangle) other;
+ if (intersects(t.get1(), t.get2(), t.get3())) {
+ CollisionResult r = new CollisionResult();
+ results.addCollision(r);
+ return 1;
+ }
+ return 0;
+ } else {
+ throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName());
+ }
+ }
+
+ /**
+ * C code ported from <a href="http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt">
+ * http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/tribox3.txt</a>
+ *
+ * @param v1 The first point in the triangle
+ * @param v2 The second point in the triangle
+ * @param v3 The third point in the triangle
+ * @return True if the bounding box intersects the triangle, false
+ * otherwise.
+ */
+ public boolean intersects(Vector3f v1, Vector3f v2, Vector3f v3) {
+ return Intersection.intersect(this, v1, v2, v3);
+ }
+
+ @Override
+ public boolean contains(Vector3f point) {
+ return FastMath.abs(center.x - point.x) < xExtent
+ && FastMath.abs(center.y - point.y) < yExtent
+ && FastMath.abs(center.z - point.z) < zExtent;
+ }
+
+ @Override
+ public boolean intersects(Vector3f point) {
+ return FastMath.abs(center.x - point.x) <= xExtent
+ && FastMath.abs(center.y - point.y) <= yExtent
+ && FastMath.abs(center.z - point.z) <= zExtent;
+ }
+
+ public float distanceToEdge(Vector3f point) {
+ // compute coordinates of point in box coordinate system
+ TempVars vars= TempVars.get();
+ Vector3f closest = vars.vect1;
+
+ point.subtract(center,closest);
+
+ // project test point onto box
+ float sqrDistance = 0.0f;
+ float delta;
+
+ if (closest.x < -xExtent) {
+ delta = closest.x + xExtent;
+ sqrDistance += delta * delta;
+ closest.x = -xExtent;
+ } else if (closest.x > xExtent) {
+ delta = closest.x - xExtent;
+ sqrDistance += delta * delta;
+ closest.x = xExtent;
+ }
+
+ if (closest.y < -yExtent) {
+ delta = closest.y + yExtent;
+ sqrDistance += delta * delta;
+ closest.y = -yExtent;
+ } else if (closest.y > yExtent) {
+ delta = closest.y - yExtent;
+ sqrDistance += delta * delta;
+ closest.y = yExtent;
+ }
+
+ if (closest.z < -zExtent) {
+ delta = closest.z + zExtent;
+ sqrDistance += delta * delta;
+ closest.z = -zExtent;
+ } else if (closest.z > zExtent) {
+ delta = closest.z - zExtent;
+ sqrDistance += delta * delta;
+ closest.z = zExtent;
+ }
+
+ vars.release();
+ return FastMath.sqrt(sqrDistance);
+ }
+
+ /**
+ * <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(float denom, float numer, float[] 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.0f) {
+ if (numer > denom * t[1]) {
+ return false;
+ }
+ if (numer > denom * t[0]) {
+ t[0] = numer / denom;
+ }
+ return true;
+ } else if (denom < 0.0f) {
+ 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 Vector3f getExtent(Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.set(xExtent, yExtent, zExtent);
+ return store;
+ }
+
+ public float getXExtent() {
+ return xExtent;
+ }
+
+ public float getYExtent() {
+ return yExtent;
+ }
+
+ public float getZExtent() {
+ return zExtent;
+ }
+
+ public void setXExtent(float xExtent) {
+ if (xExtent < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ this.xExtent = xExtent;
+ }
+
+ public void setYExtent(float yExtent) {
+ if (yExtent < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ this.yExtent = yExtent;
+ }
+
+ public void setZExtent(float zExtent) {
+ if (zExtent < 0) {
+ throw new IllegalArgumentException();
+ }
+
+ this.zExtent = zExtent;
+ }
+
+ public Vector3f getMin(Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.set(center).subtractLocal(xExtent, yExtent, zExtent);
+ return store;
+ }
+
+ public Vector3f getMax(Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.set(center).addLocal(xExtent, yExtent, zExtent);
+ return store;
+ }
+
+ public void setMinMax(Vector3f min, Vector3f max) {
+ this.center.set(max).addLocal(min).multLocal(0.5f);
+ xExtent = FastMath.abs(max.x - center.x);
+ yExtent = FastMath.abs(max.y - center.y);
+ zExtent = FastMath.abs(max.z - center.z);
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(xExtent, "xExtent", 0);
+ capsule.write(yExtent, "yExtent", 0);
+ capsule.write(zExtent, "zExtent", 0);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ xExtent = capsule.readFloat("xExtent", 0);
+ yExtent = capsule.readFloat("yExtent", 0);
+ zExtent = capsule.readFloat("zExtent", 0);
+ }
+
+ @Override
+ public float getVolume() {
+ return (8 * xExtent * yExtent * zExtent);
+ }
+}
diff --git a/engine/src/core/com/jme3/bounding/BoundingSphere.java b/engine/src/core/com/jme3/bounding/BoundingSphere.java
new file mode 100644
index 0000000..12be035
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingSphere.java
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bounding;
+
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <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>.
+ *
+ * @author Mark Powell
+ * @version $Id: BoundingSphere.java,v 1.59 2007/08/17 10:34:26 rherlitz Exp $
+ */
+public class BoundingSphere extends BoundingVolume {
+
+ private static final Logger logger =
+ Logger.getLogger(BoundingSphere.class.getName());
+ float radius;
+ private static final float RADIUS_EPSILON = 1f + 0.00001f;
+
+ /**
+ * Default contstructor 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(float r, Vector3f c) {
+ this.center.set(c);
+ this.radius = r;
+ }
+
+ public Type getType() {
+ return Type.Sphere;
+ }
+
+ /**
+ * <code>getRadius</code> returns the radius of the bounding sphere.
+ *
+ * @return the radius of the bounding sphere.
+ */
+ public float 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(float radius) {
+ this.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.
+ */
+ public void computeFromPoints(FloatBuffer points) {
+ calcWelzl(points);
+ }
+
+ /**
+ * <code>computeFromTris</code> creates a new Bounding Box from a given
+ * set of triangles. It is used in OBBTree calculations.
+ *
+ * @param tris
+ * @param start
+ * @param end
+ */
+ public void computeFromTris(Triangle[] tris, int start, int end) {
+ if (end - start <= 0) {
+ return;
+ }
+
+ Vector3f[] vertList = new Vector3f[(end - start) * 3];
+
+ int count = 0;
+ for (int i = start; i < end; i++) {
+ vertList[count++] = tris[i].get(0);
+ vertList[count++] = tris[i].get(1);
+ vertList[count++] = tris[i].get(2);
+ }
+ averagePoints(vertList);
+ }
+//
+// /**
+// * <code>computeFromTris</code> creates a new Bounding Box from a given
+// * set of triangles. It is used in OBBTree calculations.
+// *
+// * @param indices
+// * @param mesh
+// * @param start
+// * @param end
+// */
+// public void computeFromTris(int[] indices, Mesh mesh, int start, int end) {
+// if (end - start <= 0) {
+// return;
+// }
+//
+// Vector3f[] vertList = new Vector3f[(end - start) * 3];
+//
+// int count = 0;
+// for (int i = start; i < end; i++) {
+// mesh.getTriangle(indices[i], verts);
+// vertList[count++] = new Vector3f(verts[0]);
+// vertList[count++] = new Vector3f(verts[1]);
+// vertList[count++] = new Vector3f(verts[2]);
+// }
+//
+// averagePoints(vertList);
+// }
+
+ /**
+ * Calculates a minimum bounding sphere for the set of points. The algorithm
+ * was originally found in C++ at
+ * <p><a href="http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1">
+ * http://www.flipcode.com/cgi-bin/msg.cgi?showThread=COTD-SmallestEnclosingSpheres&forum=cotd&id=-1</a><br><strong>broken link</strong></p>
+ * <p>and translated to java by Cep21</p>
+ *
+ * @param points
+ * The points to calculate the minimum bounds from.
+ */
+ public void calcWelzl(FloatBuffer points) {
+ if (center == null) {
+ center = new Vector3f();
+ }
+ FloatBuffer buf = BufferUtils.createFloatBuffer(points.limit());
+ points.rewind();
+ buf.put(points);
+ buf.flip();
+ recurseMini(buf, buf.limit() / 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 b
+ * The number of points currently considering to include with the
+ * sphere.
+ * @param ap
+ * A variable simulating pointer arithmatic from C++, and offset
+ * in <code>points</code>.
+ */
+ private void recurseMini(FloatBuffer points, int p, int b, int ap) {
+ //TempVars vars = TempVars.get();
+
+ Vector3f tempA = new Vector3f(); //vars.vect1;
+ Vector3f tempB = new Vector3f(); //vars.vect2;
+ Vector3f tempC = new Vector3f(); //vars.vect3;
+ Vector3f tempD = new Vector3f(); //vars.vect4;
+
+ switch (b) {
+ case 0:
+ this.radius = 0;
+ this.center.set(0, 0, 0);
+ break;
+ case 1:
+ this.radius = 1f - RADIUS_EPSILON;
+ BufferUtils.populateFromBuffer(center, points, ap - 1);
+ break;
+ case 2:
+ BufferUtils.populateFromBuffer(tempA, points, ap - 1);
+ BufferUtils.populateFromBuffer(tempB, points, ap - 2);
+ setSphere(tempA, tempB);
+ break;
+ case 3:
+ BufferUtils.populateFromBuffer(tempA, points, ap - 1);
+ BufferUtils.populateFromBuffer(tempB, points, ap - 2);
+ BufferUtils.populateFromBuffer(tempC, points, ap - 3);
+ setSphere(tempA, tempB, tempC);
+ break;
+ case 4:
+ BufferUtils.populateFromBuffer(tempA, points, ap - 1);
+ BufferUtils.populateFromBuffer(tempB, points, ap - 2);
+ BufferUtils.populateFromBuffer(tempC, points, ap - 3);
+ BufferUtils.populateFromBuffer(tempD, points, ap - 4);
+ setSphere(tempA, tempB, tempC, tempD);
+ //vars.release();
+ return;
+ }
+ for (int i = 0; i < p; i++) {
+ BufferUtils.populateFromBuffer(tempA, points, i + ap);
+ if (tempA.distanceSquared(center) - (radius * radius) > RADIUS_EPSILON - 1f) {
+ for (int j = i; j > 0; j--) {
+ BufferUtils.populateFromBuffer(tempB, points, j + ap);
+ BufferUtils.populateFromBuffer(tempC, points, j - 1 + ap);
+ BufferUtils.setInBuffer(tempC, points, j + ap);
+ BufferUtils.setInBuffer(tempB, points, j - 1 + ap);
+ }
+ recurseMini(points, i, b + 1, ap + 1);
+ }
+ }
+ //vars.release();
+ }
+
+ /**
+ * 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(Vector3f O, Vector3f A, Vector3f B, Vector3f C) {
+ Vector3f a = A.subtract(O);
+ Vector3f b = B.subtract(O);
+ Vector3f c = C.subtract(O);
+
+ float Denominator = 2.0f * (a.x * (b.y * c.z - c.y * b.z) - b.x
+ * (a.y * c.z - c.y * a.z) + c.x * (a.y * b.z - b.y * a.z));
+ if (Denominator == 0) {
+ center.set(0, 0, 0);
+ radius = 0;
+ } else {
+ Vector3f o = a.cross(b).multLocal(c.lengthSquared()).addLocal(
+ c.cross(a).multLocal(b.lengthSquared())).addLocal(
+ b.cross(c).multLocal(a.lengthSquared())).divideLocal(
+ Denominator);
+
+ radius = o.length() * RADIUS_EPSILON;
+ 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(Vector3f O, Vector3f A, Vector3f B) {
+ Vector3f a = A.subtract(O);
+ Vector3f b = B.subtract(O);
+ Vector3f acrossB = a.cross(b);
+
+ float Denominator = 2.0f * acrossB.dot(acrossB);
+
+ if (Denominator == 0) {
+ center.set(0, 0, 0);
+ radius = 0;
+ } else {
+
+ Vector3f o = acrossB.cross(a).multLocal(b.lengthSquared()).addLocal(b.cross(acrossB).multLocal(a.lengthSquared())).divideLocal(Denominator);
+ radius = o.length() * RADIUS_EPSILON;
+ 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(Vector3f O, Vector3f A) {
+ radius = FastMath.sqrt(((A.x - O.x) * (A.x - O.x) + (A.y - O.y)
+ * (A.y - O.y) + (A.z - O.z) * (A.z - O.z)) / 4f) + RADIUS_EPSILON - 1f;
+ center.interpolate(O, A, .5f);
+ }
+
+ /**
+ * <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(Vector3f[] points) {
+ logger.info("Bounding Sphere calculated using average points.");
+ center = points[0];
+
+ for (int i = 1; i < points.length; i++) {
+ center.addLocal(points[i]);
+ }
+
+ float quantity = 1.0f / points.length;
+ center.multLocal(quantity);
+
+ float maxRadiusSqr = 0;
+ for (int i = 0; i < points.length; i++) {
+ Vector3f diff = points[i].subtract(center);
+ float radiusSqr = diff.lengthSquared();
+ if (radiusSqr > maxRadiusSqr) {
+ maxRadiusSqr = radiusSqr;
+ }
+ }
+
+ radius = (float) Math.sqrt(maxRadiusSqr) + RADIUS_EPSILON - 1f;
+
+ }
+
+ /**
+ * <code>transform</code> modifies the center of the sphere to reflect the
+ * change made via a rotation, translation and scale.
+ *
+ * @param trans
+ * the transform to apply
+ * @param store
+ * sphere to store result in
+ * @return BoundingVolume
+ * @return ref
+ */
+ public BoundingVolume transform(Transform trans, BoundingVolume store) {
+ BoundingSphere sphere;
+ if (store == null || store.getType() != BoundingVolume.Type.Sphere) {
+ sphere = new BoundingSphere(1, new Vector3f(0, 0, 0));
+ } else {
+ sphere = (BoundingSphere) store;
+ }
+
+ center.mult(trans.getScale(), sphere.center);
+ trans.getRotation().mult(sphere.center, sphere.center);
+ sphere.center.addLocal(trans.getTranslation());
+ sphere.radius = FastMath.abs(getMaxAxis(trans.getScale()) * radius) + RADIUS_EPSILON - 1f;
+ return sphere;
+ }
+
+ public BoundingVolume transform(Matrix4f trans, BoundingVolume store) {
+ BoundingSphere sphere;
+ if (store == null || store.getType() != BoundingVolume.Type.Sphere) {
+ sphere = new BoundingSphere(1, new Vector3f(0, 0, 0));
+ } else {
+ sphere = (BoundingSphere) store;
+ }
+
+ trans.mult(center, sphere.center);
+ Vector3f axes = new Vector3f(1, 1, 1);
+ trans.mult(axes, axes);
+ float ax = getMaxAxis(axes);
+ sphere.radius = FastMath.abs(ax * radius) + RADIUS_EPSILON - 1f;
+ return sphere;
+ }
+
+ private float getMaxAxis(Vector3f scale) {
+ float x = FastMath.abs(scale.x);
+ float y = FastMath.abs(scale.y);
+ float z = FastMath.abs(scale.z);
+
+ if (x >= y) {
+ if (x >= z) {
+ return x;
+ }
+ return z;
+ }
+
+ if (y >= z) {
+ return y;
+ }
+
+ return z;
+ }
+
+ /**
+ * <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
+ */
+ public Plane.Side whichSide(Plane plane) {
+ float distance = plane.pseudoDistance(center);
+
+ if (distance <= -radius) {
+ return Plane.Side.Negative;
+ } else if (distance >= radius) {
+ return Plane.Side.Positive;
+ } else {
+ return Plane.Side.None;
+ }
+ }
+
+ /**
+ * <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
+ */
+ public BoundingVolume merge(BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+
+ case Sphere: {
+ BoundingSphere sphere = (BoundingSphere) volume;
+ float temp_radius = sphere.getRadius();
+ Vector3f temp_center = sphere.center;
+ BoundingSphere rVal = new BoundingSphere();
+ return merge(temp_radius, temp_center, rVal);
+ }
+
+ case AABB: {
+ BoundingBox box = (BoundingBox) volume;
+ Vector3f radVect = new Vector3f(box.xExtent, box.yExtent,
+ box.zExtent);
+ Vector3f temp_center = box.center;
+ BoundingSphere rVal = new BoundingSphere();
+ return merge(radVect.length(), temp_center, rVal);
+ }
+
+// case OBB: {
+// OrientedBoundingBox box = (OrientedBoundingBox) volume;
+// BoundingSphere rVal = (BoundingSphere) 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
+ */
+ public BoundingVolume mergeLocal(BoundingVolume volume) {
+ if (volume == null) {
+ return this;
+ }
+
+ switch (volume.getType()) {
+
+ case Sphere: {
+ BoundingSphere sphere = (BoundingSphere) volume;
+ float temp_radius = sphere.getRadius();
+ Vector3f temp_center = sphere.center;
+ return merge(temp_radius, temp_center, this);
+ }
+
+ case AABB: {
+ BoundingBox box = (BoundingBox) volume;
+ TempVars vars = TempVars.get();
+ Vector3f radVect = vars.vect1;
+ radVect.set(box.xExtent, box.yExtent, box.zExtent);
+ Vector3f temp_center = box.center;
+ float len = radVect.length();
+ vars.release();
+ return merge(len, temp_center, this);
+ }
+
+// case OBB: {
+// return mergeOBB((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 mergeOBB(OrientedBoundingBox volume) {
+// // compute edge points from the obb
+// if (!volume.correctCorners)
+// volume.computeCorners();
+// _mergeBuf.rewind();
+// for (int i = 0; i < 8; i++) {
+// _mergeBuf.put(volume.vectorStore[i].x);
+// _mergeBuf.put(volume.vectorStore[i].y);
+// _mergeBuf.put(volume.vectorStore[i].z);
+// }
+//
+// // remember old radius and center
+// float oldRadius = radius;
+// Vector3f oldCenter = _compVect2.set( center );
+//
+// // compute new radius and center from obb points
+// computeFromPoints(_mergeBuf);
+// Vector3f newCenter = _compVect3.set( center );
+// float newRadius = radius;
+//
+// // restore old center and radius
+// center.set( oldCenter );
+// radius = oldRadius;
+//
+// //merge obb points result
+// merge( newRadius, newCenter, this );
+//
+// return this;
+// }
+ private BoundingVolume merge(float temp_radius, Vector3f temp_center,
+ BoundingSphere rVal) {
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = temp_center.subtract(center, vars.vect1);
+ float lengthSquared = diff.lengthSquared();
+ float radiusDiff = temp_radius - radius;
+
+ float fRDiffSqr = radiusDiff * radiusDiff;
+
+ if (fRDiffSqr >= lengthSquared) {
+ if (radiusDiff <= 0.0f) {
+ vars.release();
+ return this;
+ }
+
+ Vector3f rCenter = rVal.center;
+ if (rCenter == null) {
+ rVal.setCenter(rCenter = new Vector3f());
+ }
+ rCenter.set(temp_center);
+ rVal.setRadius(temp_radius);
+ vars.release();
+ return rVal;
+ }
+
+ float length = (float) Math.sqrt(lengthSquared);
+
+ Vector3f rCenter = rVal.center;
+ if (rCenter == null) {
+ rVal.setCenter(rCenter = new Vector3f());
+ }
+ if (length > RADIUS_EPSILON) {
+ float coeff = (length + radiusDiff) / (2.0f * length);
+ rCenter.set(center.addLocal(diff.multLocal(coeff)));
+ } else {
+ rCenter.set(center);
+ }
+
+ rVal.setRadius(0.5f * (length + radius + temp_radius));
+ vars.release();
+ return rVal;
+ }
+
+ /**
+ * <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
+ */
+ public BoundingVolume clone(BoundingVolume store) {
+ if (store != null && store.getType() == Type.Sphere) {
+ BoundingSphere rVal = (BoundingSphere) store;
+ if (null == rVal.center) {
+ rVal.center = new Vector3f();
+ }
+ rVal.center.set(center);
+ rVal.radius = radius;
+ rVal.checkPlane = checkPlane;
+ return rVal;
+ }
+
+ return new BoundingSphere(radius,
+ (center != null ? (Vector3f) center.clone() : null));
+ }
+
+ /**
+ * <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 getClass().getSimpleName() + " [Radius: " + radius + " Center: "
+ + center + "]";
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume)
+ */
+ public boolean intersects(BoundingVolume bv) {
+ return bv.intersectsSphere(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.bounding.BoundingSphere)
+ */
+ public boolean intersectsSphere(BoundingSphere bs) {
+ assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bs.center);
+
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = center.subtract(bs.center, vars.vect1);
+ float rsum = getRadius() + bs.getRadius();
+ boolean eq = (diff.dot(diff) <= rsum * rsum);
+ vars.release();
+ return eq;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)
+ */
+ public boolean intersectsBoundingBox(BoundingBox bb) {
+ assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center);
+
+ if (FastMath.abs(bb.center.x - center.x) < getRadius()
+ + bb.xExtent
+ && FastMath.abs(bb.center.y - center.y) < getRadius()
+ + bb.yExtent
+ && FastMath.abs(bb.center.z - center.z) < getRadius()
+ + bb.zExtent) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox)
+ */
+// public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {
+// return obb.intersectsSphere(this);
+// }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray)
+ */
+ public boolean intersects(Ray ray) {
+ assert Vector3f.isValidVector(center);
+
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal(center);
+ float radiusSquared = getRadius() * getRadius();
+ float a = diff.dot(diff) - radiusSquared;
+ if (a <= 0.0) {
+ // in sphere
+ return true;
+ }
+
+ // outside sphere
+ float b = ray.getDirection().dot(diff);
+ vars.release();
+ if (b >= 0.0) {
+ return false;
+ }
+ return b * b >= a;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)
+ */
+ private int collideWithRay(Ray ray, CollisionResults results) {
+ TempVars vars = TempVars.get();
+
+ Vector3f diff = vars.vect1.set(ray.getOrigin()).subtractLocal(
+ center);
+ float a = diff.dot(diff) - (getRadius() * getRadius());
+ float a1, discr, root;
+ if (a <= 0.0) {
+ // inside sphere
+ a1 = ray.direction.dot(diff);
+ discr = (a1 * a1) - a;
+ root = FastMath.sqrt(discr);
+
+ float distance = root - a1;
+ Vector3f point = new Vector3f(ray.direction).multLocal(distance).addLocal(ray.origin);
+
+ CollisionResult result = new CollisionResult(point, distance);
+ results.addCollision(result);
+ vars.release();
+ return 1;
+ }
+
+ a1 = ray.direction.dot(diff);
+ vars.release();
+ if (a1 >= 0.0) {
+ return 0;
+ }
+
+ discr = a1 * a1 - a;
+ if (discr < 0.0) {
+ return 0;
+ } else if (discr >= FastMath.ZERO_TOLERANCE) {
+ root = FastMath.sqrt(discr);
+ float dist = -a1 - root;
+ Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);
+ results.addCollision(new CollisionResult(point, dist));
+
+ dist = -a1 + root;
+ point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);
+ results.addCollision(new CollisionResult(point, dist));
+ return 2;
+ } else {
+ float dist = -a1;
+ Vector3f point = new Vector3f(ray.direction).multLocal(dist).addLocal(ray.origin);
+ results.addCollision(new CollisionResult(point, dist));
+ return 1;
+ }
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ if (other instanceof Ray) {
+ Ray ray = (Ray) other;
+ return collideWithRay(ray, results);
+ } else if (other instanceof Triangle){
+ Triangle t = (Triangle) other;
+
+ float r2 = radius * radius;
+ float d1 = center.distanceSquared(t.get1());
+ float d2 = center.distanceSquared(t.get2());
+ float d3 = center.distanceSquared(t.get3());
+
+ if (d1 <= r2 || d2 <= r2 || d3 <= r2) {
+ CollisionResult r = new CollisionResult();
+ r.setDistance(FastMath.sqrt(Math.min(Math.min(d1, d2), d3)) - radius);
+ results.addCollision(r);
+ return 1;
+ }
+
+ return 0;
+ } else {
+ throw new UnsupportedCollisionException();
+ }
+ }
+
+ @Override
+ public boolean contains(Vector3f point) {
+ return center.distanceSquared(point) < (getRadius() * getRadius());
+ }
+
+ @Override
+ public boolean intersects(Vector3f point) {
+ return center.distanceSquared(point) <= (getRadius() * getRadius());
+ }
+
+ public float distanceToEdge(Vector3f point) {
+ return center.distance(point) - radius;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ try {
+ e.getCapsule(this).write(radius, "radius", 0);
+ } catch (IOException ex) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "write(JMEExporter)", "Exception", ex);
+ }
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ try {
+ radius = e.getCapsule(this).readFloat("radius", 0);
+ } catch (IOException ex) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "read(JMEImporter)", "Exception", ex);
+ }
+ }
+
+ @Override
+ public float getVolume() {
+ return 4 * FastMath.ONE_THIRD * FastMath.PI * radius * radius * radius;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/bounding/BoundingVolume.java b/engine/src/core/com/jme3/bounding/BoundingVolume.java
new file mode 100644
index 0000000..8db2e57
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/BoundingVolume.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.bounding;
+
+import com.jme3.collision.Collidable;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.math.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * <code>BoundingVolume</code> defines an interface for dealing with
+ * containment of a collection of points.
+ *
+ * @author Mark Powell
+ * @version $Id: BoundingVolume.java,v 1.24 2007/09/21 15:45:32 nca Exp $
+ */
+public abstract class BoundingVolume implements Savable, Cloneable, Collidable {
+
+ /**
+ * The type of bounding volume being used.
+ */
+ public enum Type {
+ /**
+ * {@link BoundingSphere}
+ */
+ Sphere,
+
+ /**
+ * {@link BoundingBox}.
+ */
+ AABB,
+
+ /**
+ * {@link com.jme3.bounding.OrientedBoundingBox}
+ */
+ OBB,
+
+ /**
+ * Currently unsupported by jME3.
+ */
+ Capsule;
+ }
+
+ protected int checkPlane = 0;
+ protected Vector3f center = new Vector3f();
+
+ public BoundingVolume() {
+ }
+
+ public BoundingVolume(Vector3f center) {
+ this.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(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
+ * rotation, translation and a scalar.
+ *
+ * @param trans
+ * the transform to affect the bound.
+ * @return the new bounding volume.
+ */
+ public final BoundingVolume transform(Transform trans) {
+ return transform(trans, null);
+ }
+
+ /**
+ *
+ * <code>transform</code> alters the location of the bounding volume by a
+ * rotation, translation and a scalar.
+ *
+ * @param trans
+ * the transform to affect the bound.
+ * @param store
+ * sphere to store result in
+ * @return the new bounding volume.
+ */
+ public abstract BoundingVolume transform(Transform trans, BoundingVolume store);
+
+ public abstract BoundingVolume transform(Matrix4f trans, 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 Plane.Side whichSide(Plane 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);
+
+ public final Vector3f getCenter() {
+ return center;
+ }
+
+ public final Vector3f getCenter(Vector3f store) {
+ store.set(center);
+ return store;
+ }
+
+ public final void setCenter(Vector3f newCenter) {
+ center.set(newCenter);
+ }
+
+ /**
+ * 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 float distanceTo(Vector3f 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 float distanceSquaredTo(Vector3f 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 float distanceToEdge(Vector3f 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(Ray 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.
+ * If the point is on the edge of the bounding volume, this method will
+ * return false. Use intersects(Vector3f) to check for edge intersection.
+ *
+ * @param point
+ * the point to check
+ * @return true if the point lies within this bounding volume.
+ */
+ public abstract boolean contains(Vector3f point);
+
+ /**
+ * Determines if a given point intersects (touches or is inside) this bounding volume.
+ * @param point the point to check
+ * @return true if the point lies within this bounding volume.
+ */
+ public abstract boolean intersects(Vector3f point);
+
+ public abstract float getVolume();
+
+ @Override
+ public BoundingVolume clone() {
+ try{
+ BoundingVolume clone = (BoundingVolume) super.clone();
+ clone.center = center.clone();
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ e.getCapsule(this).write(center, "center", Vector3f.ZERO);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ center = (Vector3f) e.getCapsule(this).readSavable("center", Vector3f.ZERO.clone());
+ }
+
+}
+
diff --git a/engine/src/core/com/jme3/bounding/Intersection.java b/engine/src/core/com/jme3/bounding/Intersection.java
new file mode 100644
index 0000000..c53b792
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/Intersection.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.bounding;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Plane;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * This class includes some utility methods for computing intersection
+ * between bounding volumes and triangles.
+ * @author Kirill
+ */
+public class Intersection {
+
+ private static final void findMinMax(float x0, float x1, float x2, Vector3f minMax) {
+ minMax.set(x0, x0, 0);
+ if (x1 < minMax.x) {
+ minMax.setX(x1);
+ }
+ if (x1 > minMax.y) {
+ minMax.setY(x1);
+ }
+ if (x2 < minMax.x) {
+ minMax.setX(x2);
+ }
+ if (x2 > minMax.y) {
+ minMax.setY(x2);
+ }
+ }
+
+// private boolean axisTest(float a, float b, float fa, float fb, Vector3f v0, Vector3f v1, )
+// private boolean axisTestX01(float a, float b, float fa, float fb,
+// Vector3f center, Vector3f ext,
+// Vector3f v1, Vector3f v2, Vector3f v3){
+// float p0 = a * v0.y - b * v0.z;
+// float p2 = a * v2.y - b * v2.z;
+// if(p0 < p2){
+// min = p0;
+// max = p2;
+// } else {
+// min = p2;
+// max = p0;
+// }
+// float rad = fa * boxhalfsize.y + fb * boxhalfsize.z;
+// if(min > rad || max < -rad)
+// return false;
+// }
+ public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vector3f v3) {
+ // use separating axis theorem to test overlap between triangle and box
+ // need to test for overlap in these directions:
+ // 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle
+ // we do not even need to test these)
+ // 2) normal of the triangle
+ // 3) crossproduct(edge from tri, {x,y,z}-directin)
+ // this gives 3x3=9 more tests
+
+ TempVars vars = TempVars.get();
+
+
+ Vector3f tmp0 = vars.vect1,
+ tmp1 = vars.vect2,
+ tmp2 = vars.vect3;
+
+ Vector3f e0 = vars.vect4,
+ e1 = vars.vect5,
+ e2 = vars.vect6;
+
+ Vector3f center = bbox.getCenter();
+ Vector3f extent = bbox.getExtent(null);
+
+// float min,max,p0,p1,p2,rad,fex,fey,fez;
+// float normal[3]
+
+ // This is the fastest branch on Sun
+ // move everything so that the boxcenter is in (0,0,0)
+ v1.subtract(center, tmp0);
+ v2.subtract(center, tmp1);
+ v3.subtract(center, tmp2);
+
+ // compute triangle edges
+ tmp1.subtract(tmp0, e0); // tri edge 0
+ tmp2.subtract(tmp1, e1); // tri edge 1
+ tmp0.subtract(tmp2, e2); // tri edge 2
+
+ // Bullet 3:
+ // test the 9 tests first (this was faster)
+ float min, max;
+ float p0, p1, p2, rad;
+ float fex = FastMath.abs(e0.x);
+ float fey = FastMath.abs(e0.y);
+ float fez = FastMath.abs(e0.z);
+
+
+
+ //AXISTEST_X01(e0[Z], e0[Y], fez, fey);
+ p0 = e0.z * tmp0.y - e0.y * tmp0.z;
+ p2 = e0.z * tmp2.y - e0.y * tmp2.z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * extent.y + fey * extent.z;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // AXISTEST_Y02(e0[Z], e0[X], fez, fex);
+ p0 = -e0.z * tmp0.x + e0.x * tmp0.z;
+ p2 = -e0.z * tmp2.x + e0.x * tmp2.z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * extent.x + fex * extent.z;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // AXISTEST_Z12(e0[Y], e0[X], fey, fex);
+ p1 = e0.y * tmp1.x - e0.x * tmp1.y;
+ p2 = e0.y * tmp2.x - e0.x * tmp2.y;
+ min = min(p1, p2);
+ max = max(p1, p2);
+ rad = fey * extent.x + fex * extent.y;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ fex = FastMath.abs(e1.x);
+ fey = FastMath.abs(e1.y);
+ fez = FastMath.abs(e1.z);
+
+// AXISTEST_X01(e1[Z], e1[Y], fez, fey);
+ p0 = e1.z * tmp0.y - e1.y * tmp0.z;
+ p2 = e1.z * tmp2.y - e1.y * tmp2.z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * extent.y + fey * extent.z;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // AXISTEST_Y02(e1[Z], e1[X], fez, fex);
+ p0 = -e1.z * tmp0.x + e1.x * tmp0.z;
+ p2 = -e1.z * tmp2.x + e1.x * tmp2.z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * extent.x + fex * extent.z;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // AXISTEST_Z0(e1[Y], e1[X], fey, fex);
+ p0 = e1.y * tmp0.x - e1.x * tmp0.y;
+ p1 = e1.y * tmp1.x - e1.x * tmp1.y;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fey * extent.x + fex * extent.y;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+//
+ fex = FastMath.abs(e2.x);
+ fey = FastMath.abs(e2.y);
+ fez = FastMath.abs(e2.z);
+
+ // AXISTEST_X2(e2[Z], e2[Y], fez, fey);
+ p0 = e2.z * tmp0.y - e2.y * tmp0.z;
+ p1 = e2.z * tmp1.y - e2.y * tmp1.z;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fez * extent.y + fey * extent.z;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // AXISTEST_Y1(e2[Z], e2[X], fez, fex);
+ p0 = -e2.z * tmp0.x + e2.x * tmp0.z;
+ p1 = -e2.z * tmp1.x + e2.x * tmp1.z;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fez * extent.x + fex * extent.y;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+// AXISTEST_Z12(e2[Y], e2[X], fey, fex);
+ p1 = e2.y * tmp1.x - e2.x * tmp1.y;
+ p2 = e2.y * tmp2.x - e2.x * tmp2.y;
+ min = min(p1, p2);
+ max = max(p1, p2);
+ rad = fey * extent.x + fex * extent.y;
+ if (min > rad || max < -rad) {
+ vars.release();
+ return false;
+ }
+
+ // Bullet 1:
+ // first test overlap in the {x,y,z}-directions
+ // find min, max of the triangle each direction, and test for overlap in
+ // that direction -- this is equivalent to testing a minimal AABB around
+ // the triangle against the AABB
+
+
+ Vector3f minMax = vars.vect7;
+
+ // test in X-direction
+ findMinMax(tmp0.x, tmp1.x, tmp2.x, minMax);
+ if (minMax.x > extent.x || minMax.y < -extent.x) {
+ vars.release();
+ return false;
+ }
+
+ // test in Y-direction
+ findMinMax(tmp0.y, tmp1.y, tmp2.y, minMax);
+ if (minMax.x > extent.y || minMax.y < -extent.y) {
+ vars.release();
+ return false;
+ }
+
+ // test in Z-direction
+ findMinMax(tmp0.z, tmp1.z, tmp2.z, minMax);
+ if (minMax.x > extent.z || minMax.y < -extent.z) {
+ vars.release();
+ return false;
+ }
+
+// // Bullet 2:
+// // test if the box intersects the plane of the triangle
+// // compute plane equation of triangle: normal * x + d = 0
+// Vector3f normal = new Vector3f();
+// e0.cross(e1, normal);
+ Plane p = vars.plane;
+
+ p.setPlanePoints(v1, v2, v3);
+ if (bbox.whichSide(p) == Plane.Side.Negative) {
+ vars.release();
+ return false;
+ }
+//
+// if(!planeBoxOverlap(normal,v0,boxhalfsize)) return false;
+
+ vars.release();
+
+ return true; /* box and triangle overlaps */
+ }
+}
diff --git a/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java b/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java
new file mode 100644
index 0000000..f383a94
--- /dev/null
+++ b/engine/src/core/com/jme3/bounding/OrientedBoundingBox.java
@@ -0,0 +1,1522 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.bounding;
+
+/**
+ * NOTE: This class has been commented out as it has too many dependencies.
+ */
+
+
+//
+//import java.io.IOException;
+//import java.nio.FloatBuffer;
+//
+////import com.jme.scene.TriMesh;
+//
+///**
+// * Started Date: Sep 5, 2004 <br>
+// * <br>
+// *
+// * @author Jack Lindamood
+// * @author Joshua Slack (alterations for .9)
+// * @version $Id: OrientedBoundingBox.java,v 1.35 2007/09/21 15:45:31 nca Exp $
+// */
+//public class OrientedBoundingBox extends BoundingVolume {
+//
+// private static final long serialVersionUID = 1L;
+//
+// static private final Vector3f _compVect3 = new Vector3f();
+//
+// static private final Vector3f _compVect4 = new Vector3f();
+//
+// static private final Vector3f _compVect5 = new Vector3f();
+//
+// static private final Vector3f _compVect6 = new Vector3f();
+//
+// static private final Vector3f _compVect7 = new Vector3f();
+//
+// static private final Vector3f _compVect8 = new Vector3f();
+//
+// static private final Vector3f _compVect9 = new Vector3f();
+//
+// static private final Vector3f _compVect10 = new Vector3f();
+//
+// static private final Vector3f tempVe = new Vector3f();
+//
+// static private final Matrix3f tempMa = new Matrix3f();
+//
+// static private final Quaternion tempQa = new Quaternion();
+//
+// static private final Quaternion tempQb = new Quaternion();
+//
+// private static final float[] fWdU = new float[3];
+//
+// private static final float[] fAWdU = new float[3];
+//
+// private static final float[] fDdU = new float[3];
+//
+// private static final float[] fADdU = new float[3];
+//
+// private static final float[] fAWxDdU = new float[3];
+//
+// private static final float[] tempFa = new float[3];
+//
+// private static final float[] tempFb = new float[3];
+//
+// /** X axis of the Oriented Box. */
+// public final Vector3f xAxis = new Vector3f(1, 0, 0);
+//
+// /** Y axis of the Oriented Box. */
+// public final Vector3f yAxis = new Vector3f(0, 1, 0);
+//
+// /** Z axis of the Oriented Box. */
+// public final Vector3f zAxis = new Vector3f(0, 0, 1);
+//
+// /** Extents of the box along the x,y,z axis. */
+// public final Vector3f extent = new Vector3f(0, 0, 0);
+//
+// /** Vector array used to store the array of 8 corners the box has. */
+// public final Vector3f[] vectorStore = new Vector3f[8];
+//
+// private final Vector3f tempVk = new Vector3f();
+// private final Vector3f tempForword = new Vector3f(0, 0, 1);
+// private final Vector3f tempLeft = new Vector3f(1, 0, 0);
+// private final Vector3f tempUp = new Vector3f(0, 1, 0);
+//
+// static private final FloatBuffer _mergeBuf = BufferUtils
+// .createVector3Buffer(16);
+//
+// /**
+// * If true, the box's vectorStore array correctly represents the box's
+// * corners.
+// */
+// public boolean correctCorners = false;
+//
+// public OrientedBoundingBox() {
+// for (int x = 0; x < 8; x++)
+// vectorStore[x] = new Vector3f();
+// }
+//
+// public Type getType() {
+// return Type.OBB;
+// }
+//
+// public BoundingVolume transform(Quaternion rotate, Vector3f translate,
+// Vector3f scale, BoundingVolume store) {
+// rotate.toRotationMatrix(tempMa);
+// return transform(tempMa, translate, scale, store);
+// }
+//
+// public BoundingVolume transform(Matrix3f rotate, Vector3f translate,
+// Vector3f scale, BoundingVolume store) {
+// if (store == null || store.getType() != Type.OBB) {
+// store = new OrientedBoundingBox();
+// }
+// OrientedBoundingBox toReturn = (OrientedBoundingBox) store;
+// toReturn.extent.set(FastMath.abs(extent.x * scale.x),
+// FastMath.abs(extent.y * scale.y),
+// FastMath.abs(extent.z * scale.z));
+// rotate.mult(xAxis, toReturn.xAxis);
+// rotate.mult(yAxis, toReturn.yAxis);
+// rotate.mult(zAxis, toReturn.zAxis);
+// center.mult(scale, toReturn.center);
+// rotate.mult(toReturn.center, toReturn.center);
+// toReturn.center.addLocal(translate);
+// toReturn.correctCorners = false;
+// return toReturn;
+// }
+//
+// public int whichSide(Plane plane) {
+// float fRadius = FastMath.abs(extent.x * (plane.getNormal().dot(xAxis)))
+// + FastMath.abs(extent.y * (plane.getNormal().dot(yAxis)))
+// + FastMath.abs(extent.z * (plane.getNormal().dot(zAxis)));
+// float fDistance = plane.pseudoDistance(center);
+// if (fDistance <= -fRadius)
+// return Plane.NEGATIVE_SIDE;
+// else if (fDistance >= fRadius)
+// return Plane.POSITIVE_SIDE;
+// else
+// return Plane.NO_SIDE;
+// }
+//
+// public void computeFromPoints(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(FloatBuffer points) {
+// if (points == null || points.limit() <= 2) { // we need at least a 3
+// // float vector
+// return;
+// }
+//
+// BufferUtils.populateFromBuffer(_compVect1, points, 0);
+// float minX = _compVect1.x, minY = _compVect1.y, minZ = _compVect1.z;
+// float maxX = _compVect1.x, maxY = _compVect1.y, maxZ = _compVect1.z;
+//
+// for (int i = 1, len = points.limit() / 3; i < len; i++) {
+// BufferUtils.populateFromBuffer(_compVect1, points, i);
+//
+// if (_compVect1.x < minX)
+// minX = _compVect1.x;
+// else if (_compVect1.x > maxX)
+// maxX = _compVect1.x;
+//
+// if (_compVect1.y < minY)
+// minY = _compVect1.y;
+// else if (_compVect1.y > maxY)
+// maxY = _compVect1.y;
+//
+// if (_compVect1.z < minZ)
+// minZ = _compVect1.z;
+// else if (_compVect1.z > maxZ)
+// maxZ = _compVect1.z;
+// }
+//
+// center.set(minX + maxX, minY + maxY, minZ + maxZ);
+// center.multLocal(0.5f);
+//
+// extent.set(maxX - center.x, maxY - center.y, maxZ - center.z);
+//
+// xAxis.set(1, 0, 0);
+// yAxis.set(0, 1, 0);
+// zAxis.set(0, 0, 1);
+//
+// correctCorners = false;
+// }
+//
+// public BoundingVolume merge(BoundingVolume volume) {
+// // clone ourselves into a new bounding volume, then merge.
+// return clone(new OrientedBoundingBox()).mergeLocal(volume);
+// }
+//
+// public BoundingVolume mergeLocal(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(BoundingSphere volume) {
+// BoundingSphere mergeSphere = volume;
+// if (!correctCorners)
+// this.computeCorners();
+//
+// _mergeBuf.rewind();
+// for (int i = 0; i < 8; i++) {
+// _mergeBuf.put(vectorStore[i].x);
+// _mergeBuf.put(vectorStore[i].y);
+// _mergeBuf.put(vectorStore[i].z);
+// }
+// _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(
+// mergeSphere.center.y + mergeSphere.radius).put(
+// mergeSphere.center.z + mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(
+// mergeSphere.center.y + mergeSphere.radius).put(
+// mergeSphere.center.z + mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(
+// mergeSphere.center.y - mergeSphere.radius).put(
+// mergeSphere.center.z + mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(
+// mergeSphere.center.y + mergeSphere.radius).put(
+// mergeSphere.center.z - mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(
+// mergeSphere.center.y - mergeSphere.radius).put(
+// mergeSphere.center.z + mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(
+// mergeSphere.center.y + mergeSphere.radius).put(
+// mergeSphere.center.z - mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x + mergeSphere.radius).put(
+// mergeSphere.center.y - mergeSphere.radius).put(
+// mergeSphere.center.z - mergeSphere.radius);
+// _mergeBuf.put(mergeSphere.center.x - mergeSphere.radius).put(
+// mergeSphere.center.y - mergeSphere.radius).put(
+// mergeSphere.center.z - mergeSphere.radius);
+// containAABB(_mergeBuf);
+// correctCorners = false;
+// return this;
+// }
+//
+// private BoundingVolume mergeAABB(BoundingBox volume) {
+// BoundingBox mergeBox = volume;
+// if (!correctCorners)
+// this.computeCorners();
+//
+// _mergeBuf.rewind();
+// for (int i = 0; i < 8; i++) {
+// _mergeBuf.put(vectorStore[i].x);
+// _mergeBuf.put(vectorStore[i].y);
+// _mergeBuf.put(vectorStore[i].z);
+// }
+// _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(
+// mergeBox.center.y + mergeBox.yExtent).put(
+// mergeBox.center.z + mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(
+// mergeBox.center.y + mergeBox.yExtent).put(
+// mergeBox.center.z + mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(
+// mergeBox.center.y - mergeBox.yExtent).put(
+// mergeBox.center.z + mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(
+// mergeBox.center.y + mergeBox.yExtent).put(
+// mergeBox.center.z - mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(
+// mergeBox.center.y - mergeBox.yExtent).put(
+// mergeBox.center.z + mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(
+// mergeBox.center.y + mergeBox.yExtent).put(
+// mergeBox.center.z - mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x + mergeBox.xExtent).put(
+// mergeBox.center.y - mergeBox.yExtent).put(
+// mergeBox.center.z - mergeBox.zExtent);
+// _mergeBuf.put(mergeBox.center.x - mergeBox.xExtent).put(
+// mergeBox.center.y - mergeBox.yExtent).put(
+// mergeBox.center.z - mergeBox.zExtent);
+// containAABB(_mergeBuf);
+// correctCorners = false;
+// return this;
+// }
+//
+// private BoundingVolume mergeOBB(OrientedBoundingBox volume) {
+// // OrientedBoundingBox mergeBox=(OrientedBoundingBox) volume;
+// // if (!correctCorners) this.computeCorners();
+// // if (!mergeBox.correctCorners) mergeBox.computeCorners();
+// // Vector3f[] mergeArray=new Vector3f[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;
+// OrientedBoundingBox rkBox0 = this;
+// 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.
+// Vector3f kBoxCenter = (rkBox0.center.add(rkBox1.center, _compVect7))
+// .multLocal(.5f);
+//
+// // 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.
+// Quaternion kQ0 = tempQa, kQ1 = tempQb;
+// kQ0.fromAxes(rkBox0.xAxis, rkBox0.yAxis, rkBox0.zAxis);
+// kQ1.fromAxes(rkBox1.xAxis, rkBox1.yAxis, rkBox1.zAxis);
+//
+// if (kQ0.dot(kQ1) < 0.0f)
+// kQ1.negate();
+//
+// Quaternion kQ = kQ0.addLocal(kQ1);
+// kQ.normalize();
+//
+// Matrix3f kBoxaxis = kQ.toRotationMatrix(tempMa);
+// Vector3f newXaxis = kBoxaxis.getColumn(0, _compVect8);
+// Vector3f newYaxis = kBoxaxis.getColumn(1, _compVect9);
+// Vector3f newZaxis = kBoxaxis.getColumn(2, _compVect10);
+//
+// // 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;
+// float fDot;
+// Vector3f kDiff = _compVect4;
+// Vector3f kMin = _compVect5;
+// Vector3f kMax = _compVect6;
+// kMin.zero();
+// kMax.zero();
+//
+// if (!rkBox0.correctCorners)
+// rkBox0.computeCorners();
+// for (i = 0; i < 8; i++) {
+// rkBox0.vectorStore[i].subtract(kBoxCenter, kDiff);
+//
+// fDot = kDiff.dot(newXaxis);
+// if (fDot > kMax.x)
+// kMax.x = fDot;
+// else if (fDot < kMin.x)
+// kMin.x = fDot;
+//
+// fDot = kDiff.dot(newYaxis);
+// if (fDot > kMax.y)
+// kMax.y = fDot;
+// else if (fDot < kMin.y)
+// kMin.y = fDot;
+//
+// fDot = kDiff.dot(newZaxis);
+// if (fDot > kMax.z)
+// kMax.z = fDot;
+// else if (fDot < kMin.z)
+// kMin.z = 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.x)
+// kMax.x = fDot;
+// else if (fDot < kMin.x)
+// kMin.x = fDot;
+//
+// fDot = kDiff.dot(newYaxis);
+// if (fDot > kMax.y)
+// kMax.y = fDot;
+// else if (fDot < kMin.y)
+// kMin.y = fDot;
+//
+// fDot = kDiff.dot(newZaxis);
+// if (fDot > kMax.z)
+// kMax.z = fDot;
+// else if (fDot < kMin.z)
+// kMin.z = fDot;
+// }
+//
+// this.xAxis.set(newXaxis);
+// this.yAxis.set(newYaxis);
+// this.zAxis.set(newZaxis);
+//
+// this.extent.x = .5f * (kMax.x - kMin.x);
+// kBoxCenter.addLocal(this.xAxis.mult(.5f * (kMax.x + kMin.x), tempVe));
+//
+// this.extent.y = .5f * (kMax.y - kMin.y);
+// kBoxCenter.addLocal(this.yAxis.mult(.5f * (kMax.y + kMin.y), tempVe));
+//
+// this.extent.z = .5f * (kMax.z - kMin.z);
+// kBoxCenter.addLocal(this.zAxis.mult(.5f * (kMax.z + kMin.z), tempVe));
+//
+// this.center.set(kBoxCenter);
+//
+// this.correctCorners = false;
+// return this;
+// }
+//
+// public BoundingVolume clone(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 = this.correctCorners;
+// return toReturn;
+// }
+//
+// /**
+// * Sets the vectorStore information to the 8 corners of the box.
+// */
+// public void computeCorners() {
+// Vector3f akEAxis0 = xAxis.mult(extent.x, _compVect1);
+// Vector3f akEAxis1 = yAxis.mult(extent.y, _compVect2);
+// Vector3f akEAxis2 = zAxis.mult(extent.z, _compVect3);
+//
+// vectorStore[0].set(center).subtractLocal(akEAxis0).subtractLocal(akEAxis1).subtractLocal(akEAxis2);
+// vectorStore[1].set(center).addLocal(akEAxis0).subtractLocal(akEAxis1).subtractLocal(akEAxis2);
+// vectorStore[2].set(center).addLocal(akEAxis0).addLocal(akEAxis1).subtractLocal(akEAxis2);
+// vectorStore[3].set(center).subtractLocal(akEAxis0).addLocal(akEAxis1).subtractLocal(akEAxis2);
+// vectorStore[4].set(center).subtractLocal(akEAxis0).subtractLocal(akEAxis1).addLocal(akEAxis2);
+// vectorStore[5].set(center).addLocal(akEAxis0).subtractLocal(akEAxis1).addLocal(akEAxis2);
+// vectorStore[6].set(center).addLocal(akEAxis0).addLocal(akEAxis1).addLocal(akEAxis2);
+// vectorStore[7].set(center).subtractLocal(akEAxis0).addLocal(akEAxis1).addLocal(akEAxis2);
+// correctCorners = true;
+// }
+//
+//// public void computeFromTris(int[] indices, TriMesh mesh, int start, int end) {
+//// if (end - start <= 0) {
+//// return;
+//// }
+//// Vector3f[] verts = new Vector3f[3];
+//// Vector3f min = _compVect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
+//// Vector3f max = _compVect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
+//// Vector3f point;
+//// for (int i = start; i < end; i++) {
+//// mesh.getTriangle(indices[i], verts);
+//// point = verts[0];
+//// if (point.x < min.x)
+//// min.x = point.x;
+//// else if (point.x > max.x)
+//// max.x = point.x;
+//// if (point.y < min.y)
+//// min.y = point.y;
+//// else if (point.y > max.y)
+//// max.y = point.y;
+//// if (point.z < min.z)
+//// min.z = point.z;
+//// else if (point.z > max.z)
+//// max.z = point.z;
+////
+//// point = verts[1];
+//// if (point.x < min.x)
+//// min.x = point.x;
+//// else if (point.x > max.x)
+//// max.x = point.x;
+//// if (point.y < min.y)
+//// min.y = point.y;
+//// else if (point.y > max.y)
+//// max.y = point.y;
+//// if (point.z < min.z)
+//// min.z = point.z;
+//// else if (point.z > max.z)
+//// max.z = point.z;
+////
+//// point = verts[2];
+//// if (point.x < min.x)
+//// min.x = point.x;
+//// else if (point.x > max.x)
+//// max.x = point.x;
+////
+//// if (point.y < min.y)
+//// min.y = point.y;
+//// else if (point.y > max.y)
+//// max.y = point.y;
+////
+//// if (point.z < min.z)
+//// min.z = point.z;
+//// else if (point.z > max.z)
+//// max.z = point.z;
+//// }
+////
+//// center.set(min.addLocal(max));
+//// center.multLocal(0.5f);
+////
+//// extent.set(max.x - center.x, max.y - center.y, max.z - center.z);
+////
+//// xAxis.set(1, 0, 0);
+//// yAxis.set(0, 1, 0);
+//// zAxis.set(0, 0, 1);
+////
+//// correctCorners = false;
+//// }
+//
+// public void computeFromTris(Triangle[] tris, int start, int end) {
+// if (end - start <= 0) {
+// return;
+// }
+//
+// Vector3f min = _compVect1.set(tris[start].get(0));
+// Vector3f max = _compVect2.set(min);
+// Vector3f point;
+// for (int i = start; i < end; i++) {
+//
+// point = tris[i].get(0);
+// if (point.x < min.x)
+// min.x = point.x;
+// else if (point.x > max.x)
+// max.x = point.x;
+// if (point.y < min.y)
+// min.y = point.y;
+// else if (point.y > max.y)
+// max.y = point.y;
+// if (point.z < min.z)
+// min.z = point.z;
+// else if (point.z > max.z)
+// max.z = point.z;
+//
+// point = tris[i].get(1);
+// if (point.x < min.x)
+// min.x = point.x;
+// else if (point.x > max.x)
+// max.x = point.x;
+// if (point.y < min.y)
+// min.y = point.y;
+// else if (point.y > max.y)
+// max.y = point.y;
+// if (point.z < min.z)
+// min.z = point.z;
+// else if (point.z > max.z)
+// max.z = point.z;
+//
+// point = tris[i].get(2);
+// if (point.x < min.x)
+// min.x = point.x;
+// else if (point.x > max.x)
+// max.x = point.x;
+//
+// if (point.y < min.y)
+// min.y = point.y;
+// else if (point.y > max.y)
+// max.y = point.y;
+//
+// if (point.z < min.z)
+// min.z = point.z;
+// else if (point.z > max.z)
+// max.z = point.z;
+// }
+//
+// center.set(min.addLocal(max));
+// center.multLocal(0.5f);
+//
+// extent.set(max.x - center.x, max.y - center.y, max.z - center.z);
+//
+// xAxis.set(1, 0, 0);
+// yAxis.set(0, 1, 0);
+// zAxis.set(0, 0, 1);
+//
+// correctCorners = false;
+// }
+//
+// public boolean intersection(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.
+// OrientedBoundingBox box0 = this;
+// float cutoff = 0.999999f;
+// boolean parallelPairExists = false;
+// int i;
+//
+// // convenience variables
+// Vector3f akA[] = new Vector3f[] { box0.xAxis, box0.yAxis, box0.zAxis };
+// Vector3f[] akB = new Vector3f[] { box1.xAxis, box1.yAxis, box1.zAxis };
+// Vector3f afEA = box0.extent;
+// Vector3f afEB = box1.extent;
+//
+// // compute difference of box centers, D = C1-C0
+// Vector3f kD = box1.center.subtract(box0.center, _compVect1);
+//
+// float[][] aafC = { fWdU, fAWdU, fDdU };
+//
+// float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };
+//
+// float[] afAD = tempFb;
+// float fR0, fR1, fR; // interval radii and distance between centers
+// float fR01; // = R0 + R1
+//
+// // axis C0+t*A0
+// for (i = 0; i < 3; i++) {
+// aafC[0][i] = akA[0].dot(akB[i]);
+// aafAbsC[0][i] = FastMath.abs(aafC[0][i]);
+// if (aafAbsC[0][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[0] = akA[0].dot(kD);
+// fR = FastMath.abs(afAD[0]);
+// fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z
+// * aafAbsC[0][2];
+// fR01 = afEA.x + 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] = FastMath.abs(aafC[1][i]);
+// if (aafAbsC[1][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[1] = akA[1].dot(kD);
+// fR = FastMath.abs(afAD[1]);
+// fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z
+// * aafAbsC[1][2];
+// fR01 = afEA.y + 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] = FastMath.abs(aafC[2][i]);
+// if (aafAbsC[2][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[2] = akA[2].dot(kD);
+// fR = FastMath.abs(afAD[2]);
+// fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z
+// * aafAbsC[2][2];
+// fR01 = afEA.z + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B0
+// fR = FastMath.abs(akB[0].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z
+// * aafAbsC[2][0];
+// fR01 = fR0 + afEB.x;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B1
+// fR = FastMath.abs(akB[1].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z
+// * aafAbsC[2][1];
+// fR01 = fR0 + afEB.y;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B2
+// fR = FastMath.abs(akB[2].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z
+// * aafAbsC[2][2];
+// fR01 = fR0 + afEB.z;
+// 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 = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+// fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];
+// fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB1
+// fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+// fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];
+// fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB2
+// fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+// fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];
+// fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB0
+// fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+// fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB1
+// fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+// fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB2
+// fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+// fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB0
+// fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+// fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB1
+// fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+// fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB2
+// fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+// fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// return true;
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume)
+// */
+// public boolean intersects(BoundingVolume bv) {
+// if (bv == null)
+// return false;
+//
+// return bv.intersectsOrientedBoundingBox(this);
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.bounding.BoundingSphere)
+// */
+// public boolean intersectsSphere(BoundingSphere bs) {
+// if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(bs.center)) return false;
+//
+// _compVect1.set(bs.getCenter()).subtractLocal(center);
+// tempMa.fromAxes(xAxis, yAxis, zAxis);
+//
+// tempMa.mult(_compVect1, _compVect2);
+//
+// if (FastMath.abs(_compVect2.x) < bs.getRadius() + extent.x
+// && FastMath.abs(_compVect2.y) < bs.getRadius() + extent.y
+// && FastMath.abs(_compVect2.z) < bs.getRadius() + extent.z)
+// return true;
+//
+// return false;
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox)
+// */
+// public boolean intersectsBoundingBox(BoundingBox bb) {
+// if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(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.
+// float cutoff = 0.999999f;
+// boolean parallelPairExists = false;
+// int i;
+//
+// // convenience variables
+// Vector3f akA[] = new Vector3f[] { xAxis, yAxis, zAxis };
+// Vector3f[] akB = new Vector3f[] { tempForword, tempLeft, tempUp };
+// Vector3f afEA = extent;
+// Vector3f afEB = tempVk.set(bb.xExtent, bb.yExtent, bb.zExtent);
+//
+// // compute difference of box centers, D = C1-C0
+// Vector3f kD = bb.getCenter().subtract(center, _compVect1);
+//
+// float[][] aafC = { fWdU, fAWdU, fDdU };
+//
+// float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };
+//
+// float[] afAD = tempFb;
+// float fR0, fR1, fR; // interval radii and distance between centers
+// float fR01; // = R0 + R1
+//
+// // axis C0+t*A0
+// for (i = 0; i < 3; i++) {
+// aafC[0][i] = akA[0].dot(akB[i]);
+// aafAbsC[0][i] = FastMath.abs(aafC[0][i]);
+// if (aafAbsC[0][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[0] = akA[0].dot(kD);
+// fR = FastMath.abs(afAD[0]);
+// fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z
+// * aafAbsC[0][2];
+// fR01 = afEA.x + 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] = FastMath.abs(aafC[1][i]);
+// if (aafAbsC[1][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[1] = akA[1].dot(kD);
+// fR = FastMath.abs(afAD[1]);
+// fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z
+// * aafAbsC[1][2];
+// fR01 = afEA.y + 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] = FastMath.abs(aafC[2][i]);
+// if (aafAbsC[2][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[2] = akA[2].dot(kD);
+// fR = FastMath.abs(afAD[2]);
+// fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z
+// * aafAbsC[2][2];
+// fR01 = afEA.z + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B0
+// fR = FastMath.abs(akB[0].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z
+// * aafAbsC[2][0];
+// fR01 = fR0 + afEB.x;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B1
+// fR = FastMath.abs(akB[1].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z
+// * aafAbsC[2][1];
+// fR01 = fR0 + afEB.y;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B2
+// fR = FastMath.abs(akB[2].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z
+// * aafAbsC[2][2];
+// fR01 = fR0 + afEB.z;
+// 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 = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+// fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];
+// fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB1
+// fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+// fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];
+// fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB2
+// fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+// fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];
+// fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB0
+// fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+// fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB1
+// fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+// fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB2
+// fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+// fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB0
+// fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+// fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB1
+// fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+// fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB2
+// fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+// fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// return true;
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see com.jme.bounding.BoundingVolume#intersectsOBB2(com.jme.bounding.OBB2)
+// */
+// public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) {
+// if (!Vector3f.isValidVector(center) || !Vector3f.isValidVector(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.
+// float cutoff = 0.999999f;
+// boolean parallelPairExists = false;
+// int i;
+//
+// // convenience variables
+// Vector3f akA[] = new Vector3f[] { xAxis, yAxis, zAxis };
+// Vector3f[] akB = new Vector3f[] { obb.xAxis, obb.yAxis, obb.zAxis };
+// Vector3f afEA = extent;
+// Vector3f afEB = obb.extent;
+//
+// // compute difference of box centers, D = C1-C0
+// Vector3f kD = obb.center.subtract(center, _compVect1);
+//
+// float[][] aafC = { fWdU, fAWdU, fDdU };
+//
+// float[][] aafAbsC = { fADdU, fAWxDdU, tempFa };
+//
+// float[] afAD = tempFb;
+// float fR0, fR1, fR; // interval radii and distance between centers
+// float fR01; // = R0 + R1
+//
+// // axis C0+t*A0
+// for (i = 0; i < 3; i++) {
+// aafC[0][i] = akA[0].dot(akB[i]);
+// aafAbsC[0][i] = FastMath.abs(aafC[0][i]);
+// if (aafAbsC[0][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[0] = akA[0].dot(kD);
+// fR = FastMath.abs(afAD[0]);
+// fR1 = afEB.x * aafAbsC[0][0] + afEB.y * aafAbsC[0][1] + afEB.z
+// * aafAbsC[0][2];
+// fR01 = afEA.x + 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] = FastMath.abs(aafC[1][i]);
+// if (aafAbsC[1][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[1] = akA[1].dot(kD);
+// fR = FastMath.abs(afAD[1]);
+// fR1 = afEB.x * aafAbsC[1][0] + afEB.y * aafAbsC[1][1] + afEB.z
+// * aafAbsC[1][2];
+// fR01 = afEA.y + 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] = FastMath.abs(aafC[2][i]);
+// if (aafAbsC[2][i] > cutoff) {
+// parallelPairExists = true;
+// }
+// }
+// afAD[2] = akA[2].dot(kD);
+// fR = FastMath.abs(afAD[2]);
+// fR1 = afEB.x * aafAbsC[2][0] + afEB.y * aafAbsC[2][1] + afEB.z
+// * aafAbsC[2][2];
+// fR01 = afEA.z + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B0
+// fR = FastMath.abs(akB[0].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][0] + afEA.y * aafAbsC[1][0] + afEA.z
+// * aafAbsC[2][0];
+// fR01 = fR0 + afEB.x;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B1
+// fR = FastMath.abs(akB[1].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][1] + afEA.y * aafAbsC[1][1] + afEA.z
+// * aafAbsC[2][1];
+// fR01 = fR0 + afEB.y;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*B2
+// fR = FastMath.abs(akB[2].dot(kD));
+// fR0 = afEA.x * aafAbsC[0][2] + afEA.y * aafAbsC[1][2] + afEA.z
+// * aafAbsC[2][2];
+// fR01 = fR0 + afEB.z;
+// 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 = FastMath.abs(afAD[2] * aafC[1][0] - afAD[1] * aafC[2][0]);
+// fR0 = afEA.y * aafAbsC[2][0] + afEA.z * aafAbsC[1][0];
+// fR1 = afEB.y * aafAbsC[0][2] + afEB.z * aafAbsC[0][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB1
+// fR = FastMath.abs(afAD[2] * aafC[1][1] - afAD[1] * aafC[2][1]);
+// fR0 = afEA.y * aafAbsC[2][1] + afEA.z * aafAbsC[1][1];
+// fR1 = afEB.x * aafAbsC[0][2] + afEB.z * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A0xB2
+// fR = FastMath.abs(afAD[2] * aafC[1][2] - afAD[1] * aafC[2][2]);
+// fR0 = afEA.y * aafAbsC[2][2] + afEA.z * aafAbsC[1][2];
+// fR1 = afEB.x * aafAbsC[0][1] + afEB.y * aafAbsC[0][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB0
+// fR = FastMath.abs(afAD[0] * aafC[2][0] - afAD[2] * aafC[0][0]);
+// fR0 = afEA.x * aafAbsC[2][0] + afEA.z * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[1][2] + afEB.z * aafAbsC[1][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB1
+// fR = FastMath.abs(afAD[0] * aafC[2][1] - afAD[2] * aafC[0][1]);
+// fR0 = afEA.x * aafAbsC[2][1] + afEA.z * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[1][2] + afEB.z * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A1xB2
+// fR = FastMath.abs(afAD[0] * aafC[2][2] - afAD[2] * aafC[0][2]);
+// fR0 = afEA.x * aafAbsC[2][2] + afEA.z * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[1][1] + afEB.y * aafAbsC[1][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB0
+// fR = FastMath.abs(afAD[1] * aafC[0][0] - afAD[0] * aafC[1][0]);
+// fR0 = afEA.x * aafAbsC[1][0] + afEA.y * aafAbsC[0][0];
+// fR1 = afEB.y * aafAbsC[2][2] + afEB.z * aafAbsC[2][1];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB1
+// fR = FastMath.abs(afAD[1] * aafC[0][1] - afAD[0] * aafC[1][1]);
+// fR0 = afEA.x * aafAbsC[1][1] + afEA.y * aafAbsC[0][1];
+// fR1 = afEB.x * aafAbsC[2][2] + afEB.z * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// // axis C0+t*A2xB2
+// fR = FastMath.abs(afAD[1] * aafC[0][2] - afAD[0] * aafC[1][2]);
+// fR0 = afEA.x * aafAbsC[1][2] + afEA.y * aafAbsC[0][2];
+// fR1 = afEB.x * aafAbsC[2][1] + afEB.y * aafAbsC[2][0];
+// fR01 = fR0 + fR1;
+// if (fR > fR01) {
+// return false;
+// }
+//
+// return true;
+// }
+//
+// /*
+// * (non-Javadoc)
+// *
+// * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray)
+// */
+// public boolean intersects(Ray ray) {
+// if (!Vector3f.isValidVector(center)) return false;
+//
+// float rhs;
+// Vector3f diff = ray.origin.subtract(getCenter(_compVect2), _compVect1);
+//
+// fWdU[0] = ray.getDirection().dot(xAxis);
+// fAWdU[0] = FastMath.abs(fWdU[0]);
+// fDdU[0] = diff.dot(xAxis);
+// fADdU[0] = FastMath.abs(fDdU[0]);
+// if (fADdU[0] > extent.x && fDdU[0] * fWdU[0] >= 0.0) {
+// return false;
+// }
+//
+// fWdU[1] = ray.getDirection().dot(yAxis);
+// fAWdU[1] = FastMath.abs(fWdU[1]);
+// fDdU[1] = diff.dot(yAxis);
+// fADdU[1] = FastMath.abs(fDdU[1]);
+// if (fADdU[1] > extent.y && fDdU[1] * fWdU[1] >= 0.0) {
+// return false;
+// }
+//
+// fWdU[2] = ray.getDirection().dot(zAxis);
+// fAWdU[2] = FastMath.abs(fWdU[2]);
+// fDdU[2] = diff.dot(zAxis);
+// fADdU[2] = FastMath.abs(fDdU[2]);
+// if (fADdU[2] > extent.z && fDdU[2] * fWdU[2] >= 0.0) {
+// return false;
+// }
+//
+// Vector3f wCrossD = ray.getDirection().cross(diff, _compVect2);
+//
+// fAWxDdU[0] = FastMath.abs(wCrossD.dot(xAxis));
+// rhs = extent.y * fAWdU[2] + extent.z * fAWdU[1];
+// if (fAWxDdU[0] > rhs) {
+// return false;
+// }
+//
+// fAWxDdU[1] = FastMath.abs(wCrossD.dot(yAxis));
+// rhs = extent.x * fAWdU[2] + extent.z * fAWdU[0];
+// if (fAWxDdU[1] > rhs) {
+// return false;
+// }
+//
+// fAWxDdU[2] = FastMath.abs(wCrossD.dot(zAxis));
+// rhs = extent.x * fAWdU[1] + extent.y * fAWdU[0];
+// if (fAWxDdU[2] > rhs) {
+// return false;
+//
+// }
+//
+// return true;
+// }
+//
+// /**
+// * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray)
+// */
+// public IntersectionRecord intersectsWhere(Ray ray) {
+// Vector3f diff = _compVect1.set(ray.origin).subtractLocal(center);
+// // convert ray to box coordinates
+// Vector3f direction = _compVect2.set(ray.direction.x, ray.direction.y,
+// ray.direction.z);
+// float[] t = { 0f, Float.POSITIVE_INFINITY };
+//
+// float saveT0 = t[0], saveT1 = t[1];
+// boolean notEntirelyClipped = clip(+direction.x, -diff.x - extent.x, t)
+// && clip(-direction.x, +diff.x - extent.x, t)
+// && clip(+direction.y, -diff.y - extent.y, t)
+// && clip(-direction.y, +diff.y - extent.y, t)
+// && clip(+direction.z, -diff.z - extent.z, t)
+// && clip(-direction.z, +diff.z - extent.z, t);
+//
+// if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) {
+// if (t[1] > t[0]) {
+// float[] distances = t;
+// Vector3f[] points = new Vector3f[] {
+// new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),
+// new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin)
+// };
+// IntersectionRecord record = new IntersectionRecord(distances, points);
+// return record;
+// }
+//
+// float[] distances = new float[] { t[0] };
+// Vector3f[] points = new Vector3f[] {
+// new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin),
+// };
+// IntersectionRecord record = new IntersectionRecord(distances, points);
+// return record;
+// }
+//
+// return new IntersectionRecord();
+//
+// }
+//
+// /**
+// * <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(float denom, float numer, float[] 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.0f) {
+// if (numer > denom * t[1])
+// return false;
+// if (numer > denom * t[0])
+// t[0] = numer / denom;
+// return true;
+// } else if (denom < 0.0f) {
+// 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(Vector3f axis) {
+// xAxis.set(axis);
+// correctCorners = false;
+// }
+//
+// public void setYAxis(Vector3f axis) {
+// yAxis.set(axis);
+// correctCorners = false;
+// }
+//
+// public void setZAxis(Vector3f axis) {
+// zAxis.set(axis);
+// correctCorners = false;
+// }
+//
+// public void setExtent(Vector3f ext) {
+// extent.set(ext);
+// correctCorners = false;
+// }
+//
+// public Vector3f getXAxis() {
+// return xAxis;
+// }
+//
+// public Vector3f getYAxis() {
+// return yAxis;
+// }
+//
+// public Vector3f getZAxis() {
+// return zAxis;
+// }
+//
+// public Vector3f getExtent() {
+// return extent;
+// }
+//
+// @Override
+// public boolean contains(Vector3f point) {
+// _compVect1.set(point).subtractLocal(center);
+// float coeff = _compVect1.dot(xAxis);
+// if (FastMath.abs(coeff) > extent.x) return false;
+//
+// coeff = _compVect1.dot(yAxis);
+// if (FastMath.abs(coeff) > extent.y) return false;
+//
+// coeff = _compVect1.dot(zAxis);
+// if (FastMath.abs(coeff) > extent.z) return false;
+//
+// return true;
+// }
+//
+// @Override
+// public float distanceToEdge(Vector3f point) {
+// // compute coordinates of point in box coordinate system
+// Vector3f diff = point.subtract(center);
+// Vector3f closest = new Vector3f(diff.dot(xAxis), diff.dot(yAxis), diff
+// .dot(zAxis));
+//
+// // project test point onto box
+// float sqrDistance = 0.0f;
+// float delta;
+//
+// if (closest.x < -extent.x) {
+// delta = closest.x + extent.x;
+// sqrDistance += delta * delta;
+// closest.x = -extent.x;
+// } else if (closest.x > extent.x) {
+// delta = closest.x - extent.x;
+// sqrDistance += delta * delta;
+// closest.x = extent.x;
+// }
+//
+// if (closest.y < -extent.y) {
+// delta = closest.y + extent.y;
+// sqrDistance += delta * delta;
+// closest.y = -extent.y;
+// } else if (closest.y > extent.y) {
+// delta = closest.y - extent.y;
+// sqrDistance += delta * delta;
+// closest.y = extent.y;
+// }
+//
+// if (closest.z < -extent.z) {
+// delta = closest.z + extent.z;
+// sqrDistance += delta * delta;
+// closest.z = -extent.z;
+// } else if (closest.z > extent.z) {
+// delta = closest.z - extent.z;
+// sqrDistance += delta * delta;
+// closest.z = extent.z;
+// }
+//
+// return FastMath.sqrt(sqrDistance);
+// }
+//
+// public void write(JMEExporter e) throws IOException {
+// super.write(e);
+// OutputCapsule capsule = e.getCapsule(this);
+// capsule.write(xAxis, "xAxis", Vector3f.UNIT_X);
+// capsule.write(yAxis, "yAxis", Vector3f.UNIT_Y);
+// capsule.write(zAxis, "zAxis", Vector3f.UNIT_Z);
+// capsule.write(extent, "extent", Vector3f.ZERO);
+// }
+//
+// public void read(JMEImporter e) throws IOException {
+// super.read(e);
+// InputCapsule capsule = e.getCapsule(this);
+// xAxis.set((Vector3f) capsule.readSavable("xAxis", Vector3f.UNIT_X.clone()));
+// yAxis.set((Vector3f) capsule.readSavable("yAxis", Vector3f.UNIT_Y.clone()));
+// zAxis.set((Vector3f) capsule.readSavable("zAxis", Vector3f.UNIT_Z.clone()));
+// extent.set((Vector3f) capsule.readSavable("extent", Vector3f.ZERO.clone()));
+// correctCorners = false;
+// }
+//
+// @Override
+// public float getVolume() {
+// return (8*extent.x*extent.y*extent.z);
+// }
+//} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/cinematic/Cinematic.java b/engine/src/core/com/jme3/cinematic/Cinematic.java
new file mode 100644
index 0000000..3477c22
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/Cinematic.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.app.state.AppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.cinematic.events.AbstractCinematicEvent;
+import com.jme3.cinematic.events.CinematicEvent;
+import com.jme3.cinematic.events.CinematicEventListener;
+import com.jme3.export.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.CameraNode;
+import com.jme3.scene.Node;
+import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+import java.io.IOException;
+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;
+
+/**
+ *
+ * @author Nehon
+ */
+public class Cinematic extends AbstractCinematicEvent implements AppState {
+
+ private static final Logger logger = Logger.getLogger(Application.class.getName());
+ private Node scene;
+ protected TimeLine timeLine = new TimeLine();
+ private int lastFetchedKeyFrame = -1;
+ private List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>();
+ private Map<String, CameraNode> cameras = new HashMap<String, CameraNode>();
+ private CameraNode currentCam;
+ private boolean initialized = false;
+ private Map<String, Map<String, Object>> eventsData;
+
+ public Cinematic() {
+ }
+
+ public Cinematic(Node scene) {
+ this.scene = scene;
+ }
+
+ public Cinematic(Node scene, float initialDuration) {
+ super(initialDuration);
+ this.scene = scene;
+ }
+
+ public Cinematic(Node scene, LoopMode loopMode) {
+ super(loopMode);
+ this.scene = scene;
+ }
+
+ public Cinematic(Node scene, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.scene = scene;
+ }
+
+ @Override
+ public void onPlay() {
+ if (isInitialized()) {
+ if (playState == PlayState.Paused) {
+ for (int i = 0; i < cinematicEvents.size(); i++) {
+ CinematicEvent ce = cinematicEvents.get(i);
+ if (ce.getPlayState() == PlayState.Paused) {
+ ce.play();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onStop() {
+ time = 0;
+ lastFetchedKeyFrame = -1;
+ for (int i = 0; i < cinematicEvents.size(); i++) {
+ CinematicEvent ce = cinematicEvents.get(i);
+ ce.stop();
+ }
+ enableCurrentCam(false);
+ }
+
+ @Override
+ public void onPause() {
+ for (int i = 0; i < cinematicEvents.size(); i++) {
+ CinematicEvent ce = cinematicEvents.get(i);
+ if (ce.getPlayState() == PlayState.Playing) {
+ ce.pause();
+ }
+ }
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+
+ oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
+ oc.writeStringSavableMap(cameras, "cameras", null);
+ oc.write(timeLine, "timeLine", null);
+
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+
+ cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
+ cameras = (Map<String, CameraNode>) ic.readStringSavableMap("cameras", null);
+ timeLine = (TimeLine) ic.readSavable("timeLine", null);
+ }
+
+ @Override
+ public void setSpeed(float speed) {
+ super.setSpeed(speed);
+ for (int i = 0; i < cinematicEvents.size(); i++) {
+ CinematicEvent ce = cinematicEvents.get(i);
+ ce.setSpeed(speed);
+ }
+
+
+ }
+
+ public void initialize(AppStateManager stateManager, Application app) {
+ initEvent(app, this);
+ for (CinematicEvent cinematicEvent : cinematicEvents) {
+ cinematicEvent.initEvent(app, this);
+ }
+
+ initialized = true;
+ }
+
+ public boolean isInitialized() {
+ return initialized;
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (enabled) {
+ play();
+ }
+ }
+
+ public boolean isEnabled() {
+ return playState == PlayState.Playing;
+ }
+
+ public void stateAttached(AppStateManager stateManager) {
+ }
+
+ public void stateDetached(AppStateManager stateManager) {
+ stop();
+ }
+
+ public void update(float tpf) {
+ if (isInitialized()) {
+ internalUpdate(tpf);
+ }
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ for (int i = 0; i < cinematicEvents.size(); i++) {
+ CinematicEvent ce = cinematicEvents.get(i);
+ ce.internalUpdate(tpf);
+ }
+
+ int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
+
+ //iterate to make sure every key frame is triggered
+ for (int i = lastFetchedKeyFrame + 1; i <= keyFrameIndex; i++) {
+ KeyFrame keyFrame = timeLine.get(i);
+ if (keyFrame != null) {
+ keyFrame.trigger();
+ }
+ }
+
+ lastFetchedKeyFrame = keyFrameIndex;
+ }
+
+ @Override
+ public void setTime(float time) {
+ super.setTime(time);
+ int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time);
+
+ //triggering all the event from start to "time"
+ //then computing timeOffset for each event
+ for (int i = 0; i <= keyFrameIndex; i++) {
+ KeyFrame keyFrame = timeLine.get(i);
+ if (keyFrame != null) {
+ for (CinematicEvent ce : keyFrame.getCinematicEvents()) {
+ ce.play();
+ ce.setTime(time - timeLine.getKeyFrameTime(keyFrame));
+ }
+ }
+ }
+ if (playState != PlayState.Playing) {
+ pause();
+ }
+
+ // step();
+ }
+
+ public KeyFrame addCinematicEvent(float timeStamp, CinematicEvent cinematicEvent) {
+ KeyFrame keyFrame = timeLine.getKeyFrameAtTime(timeStamp);
+ if (keyFrame == null) {
+ keyFrame = new KeyFrame();
+ timeLine.addKeyFrameAtTime(timeStamp, keyFrame);
+ }
+ keyFrame.cinematicEvents.add(cinematicEvent);
+ cinematicEvents.add(cinematicEvent);
+ return keyFrame;
+ }
+
+ public void render(RenderManager rm) {
+ }
+
+ public void postRender() {
+ }
+
+ public void cleanup() {
+ }
+
+ /**
+ * fits the duration of the cinamatic to the duration of all its child cinematic events
+ */
+ public void fitDuration() {
+ KeyFrame kf = timeLine.getKeyFrameAtTime(timeLine.getLastKeyFrameIndex());
+ float d = 0;
+ for (int i = 0; i < kf.getCinematicEvents().size(); i++) {
+ CinematicEvent ce = kf.getCinematicEvents().get(i);
+ if (d < (ce.getDuration() * ce.getSpeed())) {
+ d = (ce.getDuration() * ce.getSpeed());
+ }
+ }
+
+ initialDuration = d;
+ }
+
+ public CameraNode bindCamera(String cameraName, Camera cam) {
+ CameraNode node = new CameraNode(cameraName, cam);
+ node.setControlDir(ControlDirection.SpatialToCamera);
+ node.getControl(CameraControl.class).setEnabled(false);
+ cameras.put(cameraName, node);
+ scene.attachChild(node);
+ return node;
+ }
+
+ public CameraNode getCamera(String cameraName) {
+ return cameras.get(cameraName);
+ }
+
+ private void enableCurrentCam(boolean enabled) {
+ if (currentCam != null) {
+ currentCam.getControl(CameraControl.class).setEnabled(enabled);
+ }
+ }
+
+ public void setActiveCamera(String cameraName) {
+ enableCurrentCam(false);
+ currentCam = cameras.get(cameraName);
+ if (currentCam == null) {
+ logger.log(Level.WARNING, "{0} is not a camera bond to the cinematic, cannot activate", cameraName);
+ }
+ enableCurrentCam(true);
+ }
+
+ public void activateCamera(final float timeStamp, final String cameraName) {
+ addCinematicEvent(timeStamp, new AbstractCinematicEvent() {
+
+ @Override
+ public void play() {
+ super.play();
+ stop();
+ }
+
+ @Override
+ public void onPlay() {
+ setActiveCamera(cameraName);
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ }
+
+ @Override
+ public void onStop() {
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ @Override
+ public void setTime(float time) {
+ play();
+ }
+ });
+ }
+
+ public void setScene(Node scene) {
+ this.scene = scene;
+ }
+
+ private Map<String, Map<String, Object>> getEventsData() {
+ if (eventsData == null) {
+ eventsData = new HashMap<String, Map<String, Object>>();
+ }
+ return eventsData;
+ }
+
+ public void putEventData(String type, String name, Object object) {
+ Map<String, Map<String, Object>> data = getEventsData();
+ Map<String, Object> row = data.get(type);
+ if (row == null) {
+ row = new HashMap<String, Object>();
+ }
+ row.put(name, object);
+ }
+
+ public Object getEventData(String type, String name) {
+ if (eventsData != null) {
+ Map<String, Object> row = eventsData.get(type);
+ if (row != null) {
+ return row.get(name);
+ }
+ }
+ return null;
+ }
+
+ public Savable removeEventData(String type, String name) {
+ if (eventsData != null) {
+ Map<String, Object> row = eventsData.get(type);
+ if (row != null) {
+ row.remove(name);
+ }
+ }
+ return null;
+ }
+
+ public Node getScene() {
+ return scene;
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/KeyFrame.java b/engine/src/core/com/jme3/cinematic/KeyFrame.java
new file mode 100644
index 0000000..2a4b200
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/KeyFrame.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic;
+
+import com.jme3.cinematic.events.CinematicEvent;
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Nehon
+ */
+public class KeyFrame implements Savable {
+
+ List<CinematicEvent> cinematicEvents = new ArrayList<CinematicEvent>();
+ private int index;
+
+ public List<CinematicEvent> getCinematicEvents() {
+ return cinematicEvents;
+ }
+
+ public void setCinematicEvents(List<CinematicEvent> cinematicEvents) {
+ this.cinematicEvents = cinematicEvents;
+ }
+
+ public List<CinematicEvent> trigger() {
+ for (CinematicEvent event : cinematicEvents) {
+ event.play();
+ }
+ return cinematicEvents;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null);
+ oc.write(index, "index", 0);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ cinematicEvents = ic.readSavableArrayList("cinematicEvents", null);
+ index=ic.readInt("index", 0);
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/cinematic/MotionPath.java b/engine/src/core/com/jme3/cinematic/MotionPath.java
new file mode 100644
index 0000000..cdb31d0
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/MotionPath.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.cinematic.events.MotionTrack;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Spline;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import com.jme3.scene.shape.Curve;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Motion path is used to create a path between way points.
+ * @author Nehon
+ */
+public class MotionPath implements Savable {
+
+ private Node debugNode;
+ private AssetManager assetManager;
+ private List<MotionPathListener> listeners;
+ private Spline spline = new Spline();
+ private float eps = 0.0001f;
+
+ /**
+ * Create a motion Path
+ */
+ public MotionPath() {
+ }
+
+ /**
+ * interpolate the path giving the time since the beginnin and the motionControl
+ * this methods sets the new localTranslation to the spatial of the motionTrack control.
+ * @param time the time since the animation started
+ * @param control the ocntrol over the moving spatial
+ */
+ public float interpolatePath(float time, MotionTrack control) {
+
+ float traveledDistance = 0;
+ TempVars vars = TempVars.get();
+ Vector3f temp = vars.vect1;
+ Vector3f tmpVector = vars.vect2;
+ //computing traveled distance according to new time
+ traveledDistance = time * (getLength() / control.getInitialDuration());
+
+ //getting waypoint index and current value from new traveled distance
+ Vector2f v = getWayPointIndexForDistance(traveledDistance);
+
+ //setting values
+ control.setCurrentWayPoint((int) v.x);
+ control.setCurrentValue(v.y);
+
+ //interpolating new position
+ getSpline().interpolate(control.getCurrentValue(), control.getCurrentWayPoint(), temp);
+ if (control.needsDirection()) {
+ tmpVector.set(temp);
+ control.setDirection(tmpVector.subtractLocal(control.getSpatial().getLocalTranslation()).normalizeLocal());
+ }
+
+ control.getSpatial().setLocalTranslation(temp);
+ vars.release();
+ return traveledDistance;
+ }
+
+ private void attachDebugNode(Node root) {
+ if (debugNode == null) {
+ debugNode = new Node();
+ Material m = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+ for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+ Vector3f cp = it.next();
+ Geometry geo = new Geometry("box", new Box(cp, 0.3f, 0.3f, 0.3f));
+ geo.setMaterial(m);
+ debugNode.attachChild(geo);
+
+ }
+ switch (spline.getType()) {
+ case CatmullRom:
+ debugNode.attachChild(CreateCatmullRomPath());
+ break;
+ case Linear:
+ debugNode.attachChild(CreateLinearPath());
+ break;
+ default:
+ debugNode.attachChild(CreateLinearPath());
+ break;
+ }
+
+ root.attachChild(debugNode);
+ }
+ }
+
+ private Geometry CreateLinearPath() {
+
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.getAdditionalRenderState().setWireframe(true);
+ mat.setColor("Color", ColorRGBA.Blue);
+ Geometry lineGeometry = new Geometry("line", new Curve(spline, 0));
+ lineGeometry.setMaterial(mat);
+ return lineGeometry;
+ }
+
+ private Geometry CreateCatmullRomPath() {
+
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.getAdditionalRenderState().setWireframe(true);
+ mat.setColor("Color", ColorRGBA.Blue);
+ Geometry lineGeometry = new Geometry("line", new Curve(spline, 10));
+ lineGeometry.setMaterial(mat);
+ return lineGeometry;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(spline, "spline", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ spline = (Spline) in.readSavable("spline", null);
+
+ }
+
+ /**
+ * compute the index of the waypoint and the interpolation value according to a distance
+ * returns a vector 2 containing the index in the x field and the interpolation value in the y field
+ * @param distance the distance traveled on this path
+ * @return the waypoint index and the interpolation value in a vector2
+ */
+ public Vector2f getWayPointIndexForDistance(float distance) {
+ float sum = 0;
+ distance = distance % spline.getTotalLength();
+ int i = 0;
+ for (Float len : spline.getSegmentsLength()) {
+ if (sum + len >= distance) {
+ return new Vector2f((float) i, (distance - sum) / len);
+ }
+ sum += len;
+ i++;
+ }
+ return new Vector2f((float) spline.getControlPoints().size() - 1, 1.0f);
+ }
+
+ /**
+ * Addsa waypoint to the path
+ * @param wayPoint a position in world space
+ */
+ public void addWayPoint(Vector3f wayPoint) {
+ spline.addControlPoint(wayPoint);
+ }
+
+ /**
+ * retruns the length of the path in world units
+ * @return the length
+ */
+ public float getLength() {
+ return spline.getTotalLength();
+ }
+
+ /**
+ * returns the waypoint at the given index
+ * @param i the index
+ * @return returns the waypoint position
+ */
+ public Vector3f getWayPoint(int i) {
+ return spline.getControlPoints().get(i);
+ }
+
+ /**
+ * remove the waypoint from the path
+ * @param wayPoint the waypoint to remove
+ */
+ public void removeWayPoint(Vector3f wayPoint) {
+ spline.removeControlPoint(wayPoint);
+ }
+
+ /**
+ * remove the waypoint at the given index from the path
+ * @param i the index of the waypoint to remove
+ */
+ public void removeWayPoint(int i) {
+ removeWayPoint(spline.getControlPoints().get(i));
+ }
+
+ /**
+ * returns an iterator on the waypoints collection
+ * @return
+ */
+ public Iterator<Vector3f> iterator() {
+ return spline.getControlPoints().iterator();
+ }
+
+ /**
+ * return the type of spline used for the path interpolation for this path
+ * @return the path interpolation spline type
+ */
+ public SplineType getPathSplineType() {
+ return spline.getType();
+ }
+
+ /**
+ * sets the type of spline used for the path interpolation for this path
+ * @param pathSplineType
+ */
+ public void setPathSplineType(SplineType pathSplineType) {
+ spline.setType(pathSplineType);
+ if (debugNode != null) {
+ Node parent = debugNode.getParent();
+ debugNode.removeFromParent();
+ debugNode.detachAllChildren();
+ debugNode = null;
+ attachDebugNode(parent);
+ }
+ }
+
+ /**
+ * disable the display of the path and the waypoints
+ */
+ public void disableDebugShape() {
+
+ debugNode.detachAllChildren();
+ debugNode = null;
+ assetManager = null;
+ }
+
+ /**
+ * enable the display of the path and the waypoints
+ * @param manager the assetManager
+ * @param rootNode the node where the debug shapes must be attached
+ */
+ public void enableDebugShape(AssetManager manager, Node rootNode) {
+ assetManager = manager;
+ // computeTotalLentgh();
+ attachDebugNode(rootNode);
+ }
+
+ /**
+ * Adds a motion pathListener to the path
+ * @param listener the MotionPathListener to attach
+ */
+ public void addListener(MotionPathListener listener) {
+ if (listeners == null) {
+ listeners = new ArrayList<MotionPathListener>();
+ }
+ listeners.add(listener);
+ }
+
+ /**
+ * remove the given listener
+ * @param listener the listener to remove
+ */
+ public void removeListener(MotionPathListener listener) {
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+ }
+
+ /**
+ * return the number of waypoints of this path
+ * @return
+ */
+ public int getNbWayPoints() {
+ return spline.getControlPoints().size();
+ }
+
+ public void triggerWayPointReach(int wayPointIndex, MotionTrack control) {
+ if (listeners != null) {
+ for (Iterator<MotionPathListener> it = listeners.iterator(); it.hasNext();) {
+ MotionPathListener listener = it.next();
+ listener.onWayPointReach(control, wayPointIndex);
+ }
+ }
+ }
+
+ /**
+ * Returns the curve tension
+ * @return
+ */
+ public float getCurveTension() {
+ return spline.getCurveTension();
+ }
+
+ /**
+ * sets the tension of the curve (only for catmull rom) 0.0 will give a linear curve, 1.0 a round curve
+ * @param curveTension
+ */
+ public void setCurveTension(float curveTension) {
+ spline.setCurveTension(curveTension);
+ if (debugNode != null) {
+ Node parent = debugNode.getParent();
+ debugNode.removeFromParent();
+ debugNode.detachAllChildren();
+ debugNode = null;
+ attachDebugNode(parent);
+ }
+ }
+
+ public void clearWayPoints() {
+ spline.clearControlPoints();
+ }
+
+ /**
+ * Sets the path to be a cycle
+ * @param cycle
+ */
+ public void setCycle(boolean cycle) {
+
+ spline.setCycle(cycle);
+ if (debugNode != null) {
+ Node parent = debugNode.getParent();
+ debugNode.removeFromParent();
+ debugNode.detachAllChildren();
+ debugNode = null;
+ attachDebugNode(parent);
+ }
+
+ }
+
+ /**
+ * returns true if the path is a cycle
+ * @return
+ */
+ public boolean isCycle() {
+ return spline.isCycle();
+ }
+
+ public Spline getSpline() {
+ return spline;
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/MotionPathListener.java b/engine/src/core/com/jme3/cinematic/MotionPathListener.java
new file mode 100644
index 0000000..99a3dcb
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/MotionPathListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.cinematic;
+
+import com.jme3.cinematic.events.MotionTrack;
+
+/**
+ * Trigger the events appening on an motion path
+ * @author Nehon
+ */
+public interface MotionPathListener {
+
+ /**
+ * Triggers every time the target reach a waypoint on the path
+ * @param motionControl the MotionTrack objects that reached the waypoint
+ * @param wayPointIndex the index of the way point reached
+ */
+ public void onWayPointReach(MotionTrack motionControl,int wayPointIndex);
+
+}
diff --git a/engine/src/core/com/jme3/cinematic/PlayState.java b/engine/src/core/com/jme3/cinematic/PlayState.java
new file mode 100644
index 0000000..648dc3d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/PlayState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.cinematic;
+
+/**
+ * The play state of a cinematic event
+ * @author Nehon
+ */
+public enum PlayState {
+
+ /**The CinematicEvent is currently beeing played*/
+ Playing,
+ /**The animatable has been paused*/
+ Paused,
+ /**the animatable is stoped*/
+ Stopped
+}
+
diff --git a/engine/src/core/com/jme3/cinematic/TimeLine.java b/engine/src/core/com/jme3/cinematic/TimeLine.java
new file mode 100644
index 0000000..ae3e06d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/TimeLine.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ *
+ * @author Nehon
+ */
+public class TimeLine extends HashMap<Integer, KeyFrame> implements Savable {
+
+ protected int keyFramesPerSeconds = 30;
+ protected int lastKeyFrameIndex = 0;
+
+ public TimeLine() {
+ super();
+ }
+
+ public KeyFrame getKeyFrameAtTime(float time) {
+ return get(getKeyFrameIndexFromTime(time));
+ }
+
+ public KeyFrame getKeyFrameAtIndex(int keyFrameIndex) {
+ return get(keyFrameIndex);
+ }
+
+ public void addKeyFrameAtTime(float time, KeyFrame keyFrame) {
+ addKeyFrameAtIndex(getKeyFrameIndexFromTime(time), keyFrame);
+ }
+
+ public void addKeyFrameAtIndex(int keyFrameIndex, KeyFrame keyFrame) {
+ put(keyFrameIndex, keyFrame);
+ keyFrame.setIndex(keyFrameIndex);
+ if (lastKeyFrameIndex < keyFrameIndex) {
+ lastKeyFrameIndex = keyFrameIndex;
+ }
+ }
+
+ public void removeKeyFrame(int keyFrameIndex) {
+ remove(keyFrameIndex);
+ if (lastKeyFrameIndex == keyFrameIndex) {
+ KeyFrame kf = null;
+ for (int i = keyFrameIndex; kf == null && i >= 0; i--) {
+ kf = getKeyFrameAtIndex(i);
+ lastKeyFrameIndex = i;
+ }
+ }
+ }
+
+ public void removeKeyFrame(float time) {
+ removeKeyFrame(getKeyFrameIndexFromTime(time));
+ }
+
+ public int getKeyFrameIndexFromTime(float time) {
+ return Math.round(time * keyFramesPerSeconds);
+ }
+
+ public float getKeyFrameTime(KeyFrame keyFrame) {
+ return (float)keyFrame.getIndex()/(float)keyFramesPerSeconds;
+ }
+
+ public Collection<KeyFrame> getAllKeyFrames() {
+ return values();
+ }
+
+ public int getLastKeyFrameIndex() {
+ return lastKeyFrameIndex;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ ArrayList list = new ArrayList();
+ list.addAll(values());
+ oc.writeSavableArrayList(list, "keyFrames", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ ArrayList list = ic.readSavableArrayList("keyFrames", null);
+ for (Iterator it = list.iterator(); it.hasNext();) {
+ KeyFrame keyFrame = (KeyFrame) it.next();
+ addKeyFrameAtIndex(keyFrame.getIndex(), keyFrame);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
new file mode 100644
index 0000000..ea2d132
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/AbstractCinematicEvent.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This calls contains basic behavior of a cinematic event
+ * every cinematic event must extend this class
+ *
+ * A cinematic event must be given an inital duration in seconds (duration of the event at speed = 1) (default is 10)
+ * @author Nehon
+ */
+public abstract class AbstractCinematicEvent implements CinematicEvent {
+
+ protected PlayState playState = PlayState.Stopped;
+ protected float speed = 1;
+ protected float initialDuration = 10;
+ protected LoopMode loopMode = LoopMode.DontLoop;
+ protected float time = 0;
+ protected boolean resuming = false;
+
+ /**
+ * the list of listeners
+ */
+ protected List<CinematicEventListener> listeners;
+
+ /**
+ * contruct a cinematic event
+ */
+ public AbstractCinematicEvent() {
+ }
+
+ /**
+ * contruct a cinematic event wwith the given initial duration
+ * @param initialDuration
+ */
+ public AbstractCinematicEvent(float initialDuration) {
+ this.initialDuration = initialDuration;
+ }
+
+ /**
+ * contruct a cinematic event with the given loopMode
+ * @param loopMode
+ */
+ public AbstractCinematicEvent(LoopMode loopMode) {
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * contruct a cinematic event with the given loopMode and the given initialDuration
+ * @param initialDuration the duration of the event at speed = 1
+ * @param loopMode the loop mode of the event
+ */
+ public AbstractCinematicEvent(float initialDuration, LoopMode loopMode) {
+ this.initialDuration = initialDuration;
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * Play this event
+ */
+ public void play() {
+ onPlay();
+ playState = PlayState.Playing;
+ if (listeners != null) {
+ for (int i = 0; i < listeners.size(); i++) {
+ CinematicEventListener cel = listeners.get(i);
+ cel.onPlay(this);
+ }
+ }
+ }
+
+ /**
+ * Place here the code you want to execute when the event is started
+ */
+ protected abstract void onPlay();
+
+ /**
+ * should be used internally only
+ * @param tpf time per frame
+ */
+ public void internalUpdate(float tpf) {
+ if (playState == PlayState.Playing) {
+ time = time + (tpf * speed);
+ //time = elapsedTimePause + (timer.getTimeInSeconds() - start) * speed;
+
+ onUpdate(tpf);
+ if (time >= initialDuration && loopMode == loopMode.DontLoop) {
+ stop();
+ }
+ }
+
+ }
+
+ /**
+ * Place here the code you want to execute on update (only called when the event is playing)
+ * @param tpf time per frame
+ */
+ protected abstract void onUpdate(float tpf);
+
+ /**
+ * stops the animation, next time play() is called the animation will start from the begining.
+ */
+ public void stop() {
+ onStop();
+ time = 0;
+ playState = PlayState.Stopped;
+ if (listeners != null) {
+ for (int i = 0; i < listeners.size(); i++) {
+ CinematicEventListener cel = listeners.get(i);
+ cel.onStop(this);
+ }
+ }
+ }
+
+ /**
+ * Place here the code you want to execute when the event is stoped.
+ */
+ protected abstract void onStop();
+
+ /**
+ * pause this event
+ */
+ public void pause() {
+ onPause();
+ playState = PlayState.Paused;
+ if (listeners != null) {
+ for (int i = 0; i < listeners.size(); i++) {
+ CinematicEventListener cel = listeners.get(i);
+ cel.onPause(this);
+ }
+ }
+ }
+
+ /**
+ * place here the code you want to execute when the event is paused
+ */
+ public abstract void onPause();
+
+ /**
+ * returns the actual duration of the animtion (initialDuration/speed)
+ * @return
+ */
+ public float getDuration() {
+ return initialDuration / speed;
+ }
+
+ /**
+ * Sets the speed of the animation.
+ * At speed = 1, the animation will last initialDuration seconds,
+ * At speed = 2 the animation will last initialDuraiton/2...
+ * @param speed
+ */
+ public void setSpeed(float speed) {
+ this.speed = speed;
+ }
+
+ /**
+ * returns the speed of the animation.
+ * @return
+ */
+ public float getSpeed() {
+ return speed;
+ }
+
+ /**
+ * Returns the current playstate of the animation
+ * @return
+ */
+ public PlayState getPlayState() {
+ return playState;
+ }
+
+ /**
+ * returns the initial duration of the animation at speed = 1 in seconds.
+ * @return
+ */
+ public float getInitialDuration() {
+ return initialDuration;
+ }
+
+ /**
+ * Sets the duration of the antionamtion at speed = 1 in seconds
+ * @param initialDuration
+ */
+ public void setInitialDuration(float initialDuration) {
+ this.initialDuration = initialDuration;
+ }
+
+ /**
+ * retursthe loopMode of the animation
+ * @see LoopMode
+ * @return
+ */
+ public LoopMode getLoopMode() {
+ return loopMode;
+ }
+
+ /**
+ * Sets the loopMode of the animation
+ * @see LoopMode
+ * @param loopMode
+ */
+ public void setLoopMode(LoopMode loopMode) {
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * for serialization only
+ * @param ex exporter
+ * @throws IOException
+ */
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(playState, "playState", PlayState.Stopped);
+ oc.write(speed, "speed", 1);
+ oc.write(initialDuration, "initalDuration", 10);
+ oc.write(loopMode, "loopMode", LoopMode.DontLoop);
+ }
+
+ /**
+ * for serialization only
+ * @param im importer
+ * @throws IOException
+ */
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ playState = ic.readEnum("playState", PlayState.class, PlayState.Stopped);
+ speed = ic.readFloat("speed", 1);
+ initialDuration = ic.readFloat("initalDuration", 10);
+ loopMode = ic.readEnum("loopMode", LoopMode.class, LoopMode.DontLoop);
+ }
+
+ /**
+ * initialize this event (should be called internally only)
+ * @param app
+ * @param cinematic
+ */
+ public void initEvent(Application app, Cinematic cinematic) {
+ }
+
+ /**
+ * return a list of CinematicEventListener added on this event
+ * @return
+ */
+ private List<CinematicEventListener> getListeners() {
+ if (listeners == null) {
+ listeners = new ArrayList<CinematicEventListener>();
+ }
+ return listeners;
+ }
+
+ /**
+ * Add a CinematicEventListener to this event
+ * @param listener CinematicEventListener
+ */
+ public void addListener(CinematicEventListener listener) {
+ getListeners().add(listener);
+ }
+
+ /**
+ * remove a CinematicEventListener from this event
+ * @param listener CinematicEventListener
+ */
+ public void removeListener(CinematicEventListener listener) {
+ getListeners().remove(listener);
+ }
+
+ /**
+ * When this method is invoked, the event should fast forward to the given time according tim 0 is the start of the event.
+ * @param time the time to fast forward to
+ */
+ public void setTime(float time) {
+ this.time = time / speed;
+ }
+
+ public float getTime() {
+ return time;
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java b/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java
new file mode 100644
index 0000000..e857706
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/AnimationTrack.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ */
+public class AnimationTrack extends AbstractCinematicEvent {
+
+ private static final Logger log = Logger.getLogger(AnimationTrack.class.getName());
+ protected AnimChannel channel;
+ protected String animationName;
+ protected String modelName;
+
+ public AnimationTrack() {
+ }
+
+ public AnimationTrack(Spatial model, String animationName) {
+ modelName = model.getName();
+ this.animationName = animationName;
+ initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
+ }
+
+ public AnimationTrack(Spatial model, String animationName, float initialDuration) {
+ super(initialDuration);
+ modelName = model.getName();
+ this.animationName = animationName;
+ }
+
+ public AnimationTrack(Spatial model, String animationName, LoopMode loopMode) {
+ super(loopMode);
+ initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName);
+ modelName = model.getName();
+ this.animationName = animationName;
+ }
+
+ public AnimationTrack(Spatial model, String animationName, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ modelName = model.getName();
+ this.animationName = animationName;
+ }
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ if (channel == null) {
+ Object s = cinematic.getEventData("modelChannels", modelName);
+ if (s != null && s instanceof AnimChannel) {
+ this.channel = (AnimChannel) s;
+ } else if (s == null) {
+ Spatial model = cinematic.getScene().getChild(modelName);
+ if (model != null) {
+ channel = model.getControl(AnimControl.class).createChannel();
+ cinematic.putEventData("modelChannels", modelName, channel);
+ } else {
+ log.log(Level.WARNING, "spatial {0} not found in the scene, cannot perform animation", modelName);
+ }
+ }
+
+ }
+ }
+
+ @Override
+ public void setTime(float time) {
+ super.setTime(time);
+ float t = time;
+ if(loopMode == loopMode.Loop){
+ t = t % channel.getAnimMaxTime();
+ }
+ if(loopMode == loopMode.Cycle){
+ float parity = (float)Math.ceil(time / channel.getAnimMaxTime());
+ if(parity >0 && parity%2 ==0){
+ t = channel.getAnimMaxTime() - t % channel.getAnimMaxTime();
+ }else{
+ t = t % channel.getAnimMaxTime();
+ }
+
+ }
+ channel.setTime(t);
+ channel.getControl().update(0);
+ }
+
+ @Override
+ public void onPlay() {
+ channel.getControl().setEnabled(true);
+ if (playState == PlayState.Stopped) {
+ channel.setAnim(animationName);
+ channel.setSpeed(speed);
+ channel.setLoopMode(loopMode);
+ channel.setTime(time);
+ }
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ }
+
+ @Override
+ public void onStop() {
+ channel.getControl().setEnabled(false);
+ channel.setTime(0);
+ }
+
+ @Override
+ public void onPause() {
+ channel.getControl().setEnabled(false);
+ }
+
+ @Override
+ public void setLoopMode(LoopMode loopMode) {
+ super.setLoopMode(loopMode);
+ channel.setLoopMode(loopMode);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(modelName, "modelName", "");
+ oc.write(animationName, "animationName", "");
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ modelName = ic.readString("modelName", "");
+ animationName = ic.readString("animationName", "");
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
new file mode 100644
index 0000000..37e70f9
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/CinematicEvent.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.Savable;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface CinematicEvent extends Savable {
+
+ /**
+ * Starts the animation
+ */
+ public void play();
+
+ /**
+ * Stops the animation
+ */
+ public void stop();
+
+ /**
+ * Pauses the animation
+ */
+ public void pause();
+
+ /**
+ * Returns the actual duration of the animation
+ * @return the duration
+ */
+ public float getDuration();
+
+ /**
+ * Sets the speed of the animation (1 is normal speed, 2 is twice faster)
+ * @param speed
+ */
+ public void setSpeed(float speed);
+
+ /**
+ * returns the speed of the animation
+ * @return the speed
+ */
+ public float getSpeed();
+
+ /**
+ * returns the PlayState of the animation
+ * @return the plat state
+ */
+ public PlayState getPlayState();
+
+ /**
+ * @param loop Set the loop mode for the channel. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ */
+ public void setLoopMode(LoopMode loop);
+
+ /**
+ * @return The loop mode currently set for the animation. The loop mode
+ * determines what will happen to the animation once it finishes
+ * playing.
+ *
+ * For more information, see the LoopMode enum class.
+ * @see LoopMode
+ */
+ public LoopMode getLoopMode();
+
+ /**
+ * returns the initial duration of the animation at speed = 1 in seconds.
+ * @return the initial duration
+ */
+ public float getInitialDuration();
+
+ /**
+ * Sets the duration of the antionamtion at speed = 1 in seconds
+ * @param initialDuration
+ */
+ public void setInitialDuration(float initialDuration);
+
+ /**
+ * called internally in the update method, place here anything you want to run in the update loop
+ * @param tpf time per frame
+ */
+ public void internalUpdate(float tpf);
+
+ /**
+ * initialize this event
+ * @param app the application
+ * @param cinematic the cinematic
+ */
+ public void initEvent(Application app, Cinematic cinematic);
+
+ /**
+ * When this method is invoked, the event should fast forward to the given time according tim 0 is the start of the event.
+ * @param time the time to fast forward to
+ */
+ public void setTime(float time);
+
+ /**
+ * returns the current time of the cinematic event
+ * @return the time
+ */
+ public float getTime();
+
+
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java b/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java
new file mode 100644
index 0000000..9a5f5a6
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/CinematicEventListener.java
@@ -0,0 +1,17 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package com.jme3.cinematic.events;
+
+/**
+ *
+ * @author Nehon
+ */
+public interface CinematicEventListener {
+
+ public void onPlay(CinematicEvent cinematic);
+ public void onPause(CinematicEvent cinematic);
+ public void onStop(CinematicEvent cinematic);
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/MotionTrack.java b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java
new file mode 100644
index 0000000..2995452
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/MotionTrack.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.cinematic.MotionPath;
+import com.jme3.cinematic.PlayState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * A MotionTrack is a control over the spatial that manage the position and direction of the spatial while following a motion Path
+ *
+ * You must first create a MotionPath and then create a MotionTrack to associate a spatial and the path.
+ *
+ * @author Nehon
+ */
+public class MotionTrack extends AbstractCinematicEvent implements Control {
+
+ protected Spatial spatial;
+ protected int currentWayPoint;
+ protected float currentValue;
+ protected Vector3f direction = new Vector3f();
+ protected Vector3f lookAt;
+ protected Vector3f upVector;
+ protected Quaternion rotation;
+ protected Direction directionType = Direction.None;
+ protected MotionPath path;
+ private boolean isControl = true;
+ /**
+ * the distance traveled by the spatial on the path
+ */
+ protected float traveledDistance = 0;
+
+ /**
+ * Enum for the different type of target direction behavior
+ */
+ public enum Direction {
+
+ /**
+ * the target stay in the starting direction
+ */
+ None,
+ /**
+ * The target rotates with the direction of the path
+ */
+ Path,
+ /**
+ * The target rotates with the direction of the path but with the additon of a rtotation
+ * you need to use the setRotation mathod when using this Direction
+ */
+ PathAndRotation,
+ /**
+ * The target rotates with the given rotation
+ */
+ Rotation,
+ /**
+ * The target looks at a point
+ * You need to use the setLookAt method when using this direction
+ */
+ LookAt
+ }
+
+ /**
+ * Create MotionTrack,
+ * when using this constructor don't forget to assign spatial and path
+ */
+ public MotionTrack() {
+ super();
+ }
+
+ /**
+ * Creates a MotionPath for the given spatial on the given motion path
+ * @param spatial
+ * @param path
+ */
+ public MotionTrack(Spatial spatial, MotionPath path) {
+ super();
+ this.spatial = spatial;
+ spatial.addControl(this);
+ this.path = path;
+ }
+
+ /**
+ * Creates a MotionPath for the given spatial on the given motion path
+ * @param spatial
+ * @param path
+ */
+ public MotionTrack(Spatial spatial, MotionPath path, float initialDuration) {
+ super(initialDuration);
+ this.spatial = spatial;
+ spatial.addControl(this);
+ this.path = path;
+ }
+
+ /**
+ * Creates a MotionPath for the given spatial on the given motion path
+ * @param spatial
+ * @param path
+ */
+ public MotionTrack(Spatial spatial, MotionPath path, LoopMode loopMode) {
+ super();
+ this.spatial = spatial;
+ spatial.addControl(this);
+ this.path = path;
+ this.loopMode = loopMode;
+ }
+
+ /**
+ * Creates a MotionPath for the given spatial on the given motion path
+ * @param spatial
+ * @param path
+ */
+ public MotionTrack(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) {
+ super(initialDuration);
+ this.spatial = spatial;
+ spatial.addControl(this);
+ this.path = path;
+ this.loopMode = loopMode;
+ }
+
+ public void update(float tpf) {
+ if (isControl) {
+
+ if (playState == PlayState.Playing) {
+ time = time + (tpf * speed);
+
+ if (time >= initialDuration && loopMode == loopMode.DontLoop) {
+ stop();
+ } else {
+ onUpdate(tpf);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ isControl = false;
+ }
+
+ @Override
+ public void setTime(float time) {
+ super.setTime(time);
+ onUpdate(0);
+ }
+
+ public void onUpdate(float tpf) {
+ traveledDistance = path.interpolatePath(time, this);
+ computeTargetDirection();
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(lookAt, "lookAt", Vector3f.ZERO);
+ oc.write(upVector, "upVector", Vector3f.UNIT_Y);
+ oc.write(rotation, "rotation", Quaternion.IDENTITY);
+ oc.write(directionType, "directionType", Direction.None);
+ oc.write(path, "path", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+ lookAt = (Vector3f) in.readSavable("lookAt", Vector3f.ZERO);
+ upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y);
+ rotation = (Quaternion) in.readSavable("rotation", Quaternion.IDENTITY);
+ directionType = in.readEnum("directionType", Direction.class, Direction.None);
+ path = (MotionPath) in.readSavable("path", null);
+ }
+
+ /**
+ * this method is meant to be called by the motion path only
+ * @return
+ */
+ public boolean needsDirection() {
+ return directionType == Direction.Path || directionType == Direction.PathAndRotation;
+ }
+
+ private void computeTargetDirection() {
+ switch (directionType) {
+ case Path:
+ Quaternion q = new Quaternion();
+ q.lookAt(direction, Vector3f.UNIT_Y);
+ spatial.setLocalRotation(q);
+ break;
+ case LookAt:
+ if (lookAt != null) {
+ spatial.lookAt(lookAt, upVector);
+ }
+ break;
+ case PathAndRotation:
+ if (rotation != null) {
+ Quaternion q2 = new Quaternion();
+ q2.lookAt(direction, Vector3f.UNIT_Y);
+ q2.multLocal(rotation);
+ spatial.setLocalRotation(q2);
+ }
+ break;
+ case Rotation:
+ if (rotation != null) {
+ spatial.setLocalRotation(rotation);
+ }
+ break;
+ case None:
+ break;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * Clone this control for the given spatial
+ * @param spatial
+ * @return
+ */
+ public Control cloneForSpatial(Spatial spatial) {
+ MotionTrack control = new MotionTrack(spatial, path);
+ control.playState = playState;
+ control.currentWayPoint = currentWayPoint;
+ control.currentValue = currentValue;
+ control.direction = direction.clone();
+ control.lookAt = lookAt.clone();
+ control.upVector = upVector.clone();
+ control.rotation = rotation.clone();
+ control.initialDuration = initialDuration;
+ control.speed = speed;
+ control.loopMode = loopMode;
+ control.directionType = directionType;
+
+ return control;
+ }
+
+ @Override
+ public void onPlay() {
+ traveledDistance = 0;
+ }
+
+ @Override
+ public void onStop() {
+ currentWayPoint = 0;
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ /**
+ * this method is meant to be called by the motion path only
+ * @return
+ */
+ public float getCurrentValue() {
+ return currentValue;
+ }
+
+ /**
+ * this method is meant to be called by the motion path only
+ *
+ */
+ public void setCurrentValue(float currentValue) {
+ this.currentValue = currentValue;
+ }
+
+ /**
+ * this method is meant to be called by the motion path only
+ * @return
+ */
+ public int getCurrentWayPoint() {
+ return currentWayPoint;
+ }
+
+ /**
+ * this method is meant to be called by the motion path only
+ *
+ */
+ public void setCurrentWayPoint(int currentWayPoint) {
+ if (this.currentWayPoint != currentWayPoint) {
+ this.currentWayPoint = currentWayPoint;
+ path.triggerWayPointReach(currentWayPoint, this);
+ }
+ }
+
+ /**
+ * returns the direction the spatial is moving
+ * @return
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * Sets the direction of the spatial
+ * This method is used by the motion path.
+ * @param direction
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction);
+ }
+
+ /**
+ * returns the direction type of the target
+ * @return the direction type
+ */
+ public Direction getDirectionType() {
+ return directionType;
+ }
+
+ /**
+ * Sets the direction type of the target
+ * On each update the direction given to the target can have different behavior
+ * See the Direction Enum for explanations
+ * @param directionType the direction type
+ */
+ public void setDirectionType(Direction directionType) {
+ this.directionType = directionType;
+ }
+
+ /**
+ * Set the lookAt for the target
+ * This can be used only if direction Type is Direction.LookAt
+ * @param lookAt the position to look at
+ * @param upVector the up vector
+ */
+ public void setLookAt(Vector3f lookAt, Vector3f upVector) {
+ this.lookAt = lookAt;
+ this.upVector = upVector;
+ }
+
+ /**
+ * returns the rotation of the target
+ * @return the rotation quaternion
+ */
+ public Quaternion getRotation() {
+ return rotation;
+ }
+
+ /**
+ * sets the rotation of the target
+ * This can be used only if direction Type is Direction.PathAndRotation or Direction.Rotation
+ * With PathAndRotation the target will face the direction of the path multiplied by the given Quaternion.
+ * With Rotation the rotation of the target will be set with the given Quaternion.
+ * @param rotation the rotation quaternion
+ */
+ public void setRotation(Quaternion rotation) {
+ this.rotation = rotation;
+ }
+
+ /**
+ * retun the motion path this control follows
+ * @return
+ */
+ public MotionPath getPath() {
+ return path;
+ }
+
+ /**
+ * Sets the motion path to follow
+ * @param path
+ */
+ public void setPath(MotionPath path) {
+ this.path = path;
+ }
+
+ public void setEnabled(boolean enabled) {
+ if (enabled) {
+ play();
+ } else {
+ pause();
+ }
+ }
+
+ public boolean isEnabled() {
+ return playState != PlayState.Stopped;
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ }
+
+ public void setSpatial(Spatial spatial) {
+ this.spatial = spatial;
+ }
+
+ public Spatial getSpatial() {
+ return spatial;
+ }
+
+ /**
+ * return the distance traveled by the spatial on the path
+ * @return
+ */
+ public float getTraveledDistance() {
+ return traveledDistance;
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/PositionTrack.java b/engine/src/core/com/jme3/cinematic/events/PositionTrack.java
new file mode 100644
index 0000000..abab712
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/PositionTrack.java
@@ -0,0 +1,122 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class PositionTrack extends AbstractCinematicEvent {
+
+ private static final Logger log = Logger.getLogger(PositionTrack.class.getName());
+ private Vector3f startPosition;
+ private Vector3f endPosition;
+ private Spatial spatial;
+ private String spatialName = "";
+ private float value = 0;
+
+ public PositionTrack() {
+ }
+
+ public PositionTrack(Spatial spatial, Vector3f endPosition) {
+ this.endPosition = endPosition;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ if (spatial == null) {
+ spatial = cinematic.getScene().getChild(spatialName);
+ if (spatial == null) {
+ } else {
+ log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+ }
+ }
+ }
+
+ public PositionTrack(Spatial spatial, Vector3f endPosition, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.endPosition = endPosition;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public PositionTrack(Spatial spatial, Vector3f endPosition, LoopMode loopMode) {
+ super(loopMode);
+ this.endPosition = endPosition;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public PositionTrack(Spatial spatial, Vector3f endPosition, float initialDuration) {
+ super(initialDuration);
+ this.endPosition = endPosition;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ @Override
+ public void onPlay() {
+ if (playState != playState.Paused) {
+ startPosition = spatial.getWorldTranslation().clone();
+ }
+ if (initialDuration == 0 && spatial != null) {
+
+ spatial.setLocalTranslation(endPosition);
+ }
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ if (spatial != null) {
+ value = Math.min(time / initialDuration, 1.0f);
+ Vector3f pos = FastMath.interpolateLinear(value, startPosition, endPosition);
+ spatial.setLocalTranslation(pos);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ value = 0;
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(spatialName, "spatialName", "");
+ oc.write(endPosition, "endPosition", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ spatialName = ic.readString("spatialName", "");
+ endPosition = (Vector3f) ic.readSavable("endPosition", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/RotationTrack.java b/engine/src/core/com/jme3/cinematic/events/RotationTrack.java
new file mode 100644
index 0000000..60acf9d
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/RotationTrack.java
@@ -0,0 +1,126 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class RotationTrack extends AbstractCinematicEvent {
+
+ private static final Logger log = Logger.getLogger(RotationTrack.class.getName());
+ private Quaternion startRotation = new Quaternion();
+ private Quaternion endRotation = new Quaternion();
+ private Spatial spatial;
+ private String spatialName = "";
+ private float value = 0;
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ if (spatial == null) {
+ spatial = cinematic.getScene().getChild(spatialName);
+ if (spatial == null) {
+ } else {
+ log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+ }
+ }
+ }
+
+ public RotationTrack() {
+ }
+
+ public RotationTrack(Spatial spatial, Quaternion endRotation) {
+ this.endRotation.set(endRotation);
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public RotationTrack(Spatial spatial, Quaternion endRotation, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.endRotation.set(endRotation);
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public RotationTrack(Spatial spatial, Quaternion endRotation, LoopMode loopMode) {
+ super(loopMode);
+ this.endRotation.set(endRotation);
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public RotationTrack(Spatial spatial, Quaternion endRotation, float initialDuration) {
+ super(initialDuration);
+ this.endRotation.set(endRotation);
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ @Override
+ public void onPlay() {
+ if (playState != playState.Paused) {
+ startRotation.set(spatial.getWorldRotation());
+ }
+ if (initialDuration == 0 && spatial != null) {
+ spatial.setLocalRotation(endRotation);
+ stop();
+ }
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ if (spatial != null) {
+ value = Math.min(time / initialDuration, 1.0f);
+ TempVars vars = TempVars.get();
+ Quaternion q = vars.quat1;
+ q.set(startRotation).slerp(endRotation, value);
+
+ spatial.setLocalRotation(q);
+ vars.release();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ value = 0;
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(spatialName, "spatialName", "");
+ oc.write(endRotation, "endRotation", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ spatialName = ic.readString("spatialName", "");
+ endRotation = (Quaternion) ic.readSavable("endRotation", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java b/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java
new file mode 100644
index 0000000..582adb5
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/ScaleTrack.java
@@ -0,0 +1,121 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Nehon
+ * @deprecated use spatial animation instead.
+ */
+@Deprecated
+public class ScaleTrack extends AbstractCinematicEvent {
+
+ private static final Logger log = Logger.getLogger(RotationTrack.class.getName());
+ private Vector3f startScale;
+ private Vector3f endScale;
+ private Spatial spatial;
+ private String spatialName = "";
+ private float value = 0;
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ if (spatial == null) {
+ spatial = cinematic.getScene().getChild(spatialName);
+ if (spatial == null) {
+ } else {
+ log.log(Level.WARNING, "spatial {0} not found in the scene", spatialName);
+ }
+ }
+ }
+
+ public ScaleTrack() {
+ }
+
+ public ScaleTrack(Spatial spatial, Vector3f endScale) {
+ this.endScale = endScale;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public ScaleTrack(Spatial spatial, Vector3f endScale, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.endScale = endScale;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public ScaleTrack(Spatial spatial, Vector3f endScale, LoopMode loopMode) {
+ super(loopMode);
+ this.endScale = endScale;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ public ScaleTrack(Spatial spatial, Vector3f endScale, float initialDuration) {
+ super(initialDuration);
+ this.endScale = endScale;
+ this.spatial = spatial;
+ spatialName = spatial.getName();
+ }
+
+ @Override
+ public void onPlay() {
+ if (playState != playState.Paused) {
+ startScale = spatial.getWorldScale().clone();
+ }
+ if (initialDuration == 0 && spatial != null) {
+ spatial.setLocalScale(endScale);
+ stop();
+ }
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ if (spatial != null) {
+ value = Math.min(time / initialDuration, 1.0f);
+ spatial.setLocalScale(FastMath.interpolateLinear(value, startScale, endScale));
+ }
+ }
+
+ @Override
+ public void onStop() {
+ value = 0;
+ }
+
+ @Override
+ public void onPause() {
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(spatialName, "spatialName", "");
+ oc.write(endScale, "endScale", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ spatialName = ic.readString("spatialName", "");
+ endScale = (Vector3f) ic.readSavable("endScale", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/cinematic/events/SoundTrack.java b/engine/src/core/com/jme3/cinematic/events/SoundTrack.java
new file mode 100644
index 0000000..77aae31
--- /dev/null
+++ b/engine/src/core/com/jme3/cinematic/events/SoundTrack.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.cinematic.events;
+
+import com.jme3.animation.LoopMode;
+import com.jme3.app.Application;
+import com.jme3.audio.AudioNode;
+import com.jme3.cinematic.Cinematic;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * A sound track to be played in a cinematic.
+ * @author Nehon
+ */
+public class SoundTrack extends AbstractCinematicEvent {
+
+ protected String path;
+ protected AudioNode audioNode;
+ protected boolean stream = false;
+
+ /**
+ * creates a sound track from the given resource path
+ * @param path the path to an audi file (ie : "Sounds/mySound.wav")
+ */
+ public SoundTrack(String path) {
+ this.path = path;
+ }
+
+ /**
+ * creates a sound track from the given resource path
+ * @param path the path to an audi file (ie : "Sounds/mySound.wav")
+ * @param stream true to make the audio data streamed
+ */
+ public SoundTrack(String path, boolean stream) {
+ this(path);
+ this.stream = stream;
+ }
+
+ public SoundTrack(String path, boolean stream, float initialDuration) {
+ super(initialDuration);
+ this.path = path;
+ this.stream = stream;
+ }
+
+ public SoundTrack(String path, boolean stream, LoopMode loopMode) {
+ super(loopMode);
+ this.path = path;
+ this.stream = stream;
+ }
+
+ public SoundTrack(String path, boolean stream, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.path = path;
+ this.stream = stream;
+ }
+
+ public SoundTrack(String path, float initialDuration) {
+ super(initialDuration);
+ this.path = path;
+ }
+
+ public SoundTrack(String path, LoopMode loopMode) {
+ super(loopMode);
+ this.path = path;
+ }
+
+ public SoundTrack(String path, float initialDuration, LoopMode loopMode) {
+ super(initialDuration, loopMode);
+ this.path = path;
+ }
+
+ public SoundTrack() {
+ }
+
+ @Override
+ public void initEvent(Application app, Cinematic cinematic) {
+ super.initEvent(app, cinematic);
+ audioNode = new AudioNode(app.getAssetManager(), path, stream);
+ setLoopMode(loopMode);
+ }
+
+ @Override
+ public void setTime(float time) {
+ super.setTime(time);
+ //can occur on rewind
+ if (time < 0) {
+ stop();
+ }
+ audioNode.setTimeOffset(time);
+ }
+
+ @Override
+ public void onPlay() {
+ audioNode.play();
+ }
+
+ @Override
+ public void onStop() {
+ audioNode.stop();
+
+ }
+
+ @Override
+ public void onPause() {
+ audioNode.pause();
+ }
+
+ @Override
+ public void onUpdate(float tpf) {
+ if (audioNode.getStatus() == AudioNode.Status.Stopped) {
+ stop();
+ }
+ }
+
+ /**
+ * Returns the underlying audion node of this sound track
+ * @return
+ */
+ public AudioNode getAudioNode() {
+ return audioNode;
+ }
+
+ @Override
+ public void setLoopMode(LoopMode loopMode) {
+ super.setLoopMode(loopMode);
+
+ if (loopMode != LoopMode.DontLoop) {
+ audioNode.setLooping(true);
+ } else {
+ audioNode.setLooping(false);
+ }
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(path, "path", "");
+ oc.write(stream, "stream", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ path = ic.readString("path", "");
+ stream = ic.readBoolean("stream", false);
+
+ }
+}
diff --git a/engine/src/core/com/jme3/collision/Collidable.java b/engine/src/core/com/jme3/collision/Collidable.java
new file mode 100644
index 0000000..65c0631
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/Collidable.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+/**
+ * Interface for Collidable objects.
+ * Classes that implement this interface are marked as collidable, meaning
+ * they support collision detection between other objects that are also
+ * collidable.
+ *
+ * @author Kirill Vainer
+ */
+public interface Collidable {
+
+ /**
+ * Check collision with another Collidable.
+ *
+ * @param other The object to check collision against
+ * @param results Will contain the list of {@link CollisionResult}s.
+ * @return how many collisions were found between this and other
+ */
+ public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException;
+}
diff --git a/engine/src/core/com/jme3/collision/CollisionResult.java b/engine/src/core/com/jme3/collision/CollisionResult.java
new file mode 100644
index 0000000..aef3936
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/CollisionResult.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+
+/**
+ * A <code>CollisionResult</code> represents a single collision instance
+ * between two {@link Collidable}. A collision check can result in many
+ * collision instances (places where collision has occured).
+ *
+ * @author Kirill Vainer
+ */
+public class CollisionResult implements Comparable<CollisionResult> {
+
+ private Geometry geometry;
+ private Vector3f contactPoint;
+ private Vector3f contactNormal;
+ private float distance;
+ private int triangleIndex;
+
+ public CollisionResult(Geometry geometry, Vector3f contactPoint, float distance, int triangleIndex) {
+ this.geometry = geometry;
+ this.contactPoint = contactPoint;
+ this.distance = distance;
+ this.triangleIndex = triangleIndex;
+ }
+
+ public CollisionResult(Vector3f contactPoint, float distance) {
+ this.contactPoint = contactPoint;
+ this.distance = distance;
+ }
+
+ public CollisionResult(){
+ }
+
+ public void setGeometry(Geometry geom){
+ this.geometry = geom;
+ }
+
+ public void setContactNormal(Vector3f norm){
+ this.contactNormal = norm;
+ }
+
+ public void setContactPoint(Vector3f point){
+ this.contactPoint = point;
+ }
+
+ public void setDistance(float dist){
+ this.distance = dist;
+ }
+
+ public void setTriangleIndex(int index){
+ this.triangleIndex = index;
+ }
+
+ public Triangle getTriangle(Triangle store){
+ if (store == null)
+ store = new Triangle();
+
+ Mesh m = geometry.getMesh();
+ m.getTriangle(triangleIndex, store);
+ store.calculateCenter();
+ store.calculateNormal();
+ return store;
+ }
+
+ public int compareTo(CollisionResult other) {
+ if (distance < other.distance)
+ return -1;
+ else if (distance > other.distance)
+ return 1;
+ else
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj instanceof CollisionResult){
+ return ((CollisionResult)obj).compareTo(this) == 0;
+ }
+ return super.equals(obj);
+ }
+
+ public Vector3f getContactPoint() {
+ return contactPoint;
+ }
+
+ public Vector3f getContactNormal() {
+ return contactNormal;
+ }
+
+ public float getDistance() {
+ return distance;
+ }
+
+ public Geometry getGeometry() {
+ return geometry;
+ }
+
+ public int getTriangleIndex() {
+ return triangleIndex;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/collision/CollisionResults.java b/engine/src/core/com/jme3/collision/CollisionResults.java
new file mode 100644
index 0000000..ec691c1
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/CollisionResults.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * <code>CollisionResults</code> is a collection returned as a result of a
+ * collision detection operation done by {@link Collidable}.
+ *
+ * @author Kirill Vainer
+ */
+public class CollisionResults implements Iterable<CollisionResult> {
+
+ private final ArrayList<CollisionResult> results = new ArrayList<CollisionResult>();
+ private boolean sorted = true;
+
+ /**
+ * Clears all collision results added to this list
+ */
+ public void clear(){
+ results.clear();
+ }
+
+ /**
+ * Iterator for iterating over the collision results.
+ *
+ * @return the iterator
+ */
+ public Iterator<CollisionResult> iterator() {
+ if (!sorted){
+ Collections.sort(results);
+ sorted = true;
+ }
+
+ return results.iterator();
+ }
+
+ public void addCollision(CollisionResult result){
+ results.add(result);
+ sorted = false;
+ }
+
+ public int size(){
+ return results.size();
+ }
+
+ public CollisionResult getClosestCollision(){
+ if (size() == 0)
+ return null;
+
+ if (!sorted){
+ Collections.sort(results);
+ sorted = true;
+ }
+
+ return results.get(0);
+ }
+
+ public CollisionResult getFarthestCollision(){
+ if (size() == 0)
+ return null;
+
+ if (!sorted){
+ Collections.sort(results);
+ sorted = true;
+ }
+
+ return results.get(size()-1);
+ }
+
+ public CollisionResult getCollision(int index){
+ if (!sorted){
+ Collections.sort(results);
+ sorted = true;
+ }
+
+ return results.get(index);
+ }
+
+ /**
+ * Internal use only.
+ * @param index
+ * @return
+ */
+ public CollisionResult getCollisionDirect(int index){
+ return results.get(index);
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append("CollisionResults[");
+ for (CollisionResult result : results){
+ sb.append(result).append(", ");
+ }
+ if (results.size() > 0)
+ sb.setLength(sb.length()-2);
+
+ sb.append("]");
+ return sb.toString();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/collision/MotionAllowedListener.java b/engine/src/core/com/jme3/collision/MotionAllowedListener.java
new file mode 100644
index 0000000..7703e5a
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/MotionAllowedListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+import com.jme3.math.Vector3f;
+
+public interface MotionAllowedListener {
+
+ /**
+ * Check if motion allowed. Modify position and velocity vectors
+ * appropriately if not allowed..
+ *
+ * @param position
+ * @param velocity
+ */
+ public void checkMotionAllowed(Vector3f position, Vector3f velocity);
+
+}
diff --git a/engine/src/core/com/jme3/collision/SweepSphere.java b/engine/src/core/com/jme3/collision/SweepSphere.java
new file mode 100644
index 0000000..9d43f89
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/SweepSphere.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+import com.jme3.math.*;
+
+/**
+ * No longer public ..
+ *
+ * @author Kirill Vainer
+ */
+@Deprecated
+class SweepSphere implements Collidable {
+
+ private Vector3f velocity = new Vector3f();
+ private Vector3f center = new Vector3f();
+ private Vector3f dimension = new Vector3f();
+ private Vector3f invDim = new Vector3f();
+
+ private final Triangle scaledTri = new Triangle();
+ private final Plane triPlane = new Plane();
+ private final Vector3f temp1 = new Vector3f(),
+ temp2 = new Vector3f(),
+ temp3 = new Vector3f();
+ private final Vector3f sVelocity = new Vector3f(),
+ sCenter = new Vector3f();
+
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ public void setCenter(Vector3f center) {
+ this.center.set(center);
+ }
+
+ public Vector3f getDimension() {
+ return dimension;
+ }
+
+ public void setDimension(Vector3f dimension) {
+ this.dimension.set(dimension);
+ this.invDim.set(1,1,1).divideLocal(dimension);
+ }
+
+ public void setDimension(float x, float y, float z){
+ this.dimension.set(x,y,z);
+ this.invDim.set(1,1,1).divideLocal(dimension);
+ }
+
+ public void setDimension(float dim){
+ this.dimension.set(dim, dim, dim);
+ this.invDim.set(1,1,1).divideLocal(dimension);
+ }
+
+ public Vector3f getVelocity() {
+ return velocity;
+ }
+
+ public void setVelocity(Vector3f velocity) {
+ this.velocity.set(velocity);
+ }
+
+ private boolean pointsOnSameSide(Vector3f p1, Vector3f p2, Vector3f line1, Vector3f line2) {
+ // V1 = (line2 - line1) x (p1 - line1)
+ // V2 = (p2 - line1) x (line2 - line1)
+
+ temp1.set(line2).subtractLocal(line1);
+ temp3.set(temp1);
+ temp2.set(p1).subtractLocal(line1);
+ temp1.crossLocal(temp2);
+
+ temp2.set(p2).subtractLocal(line1);
+ temp3.crossLocal(temp2);
+
+ // V1 . V2 >= 0
+ return temp1.dot(temp3) >= 0;
+ }
+
+ private boolean isPointInTriangle(Vector3f point, AbstractTriangle tri) {
+ if (pointsOnSameSide(point, tri.get1(), tri.get2(), tri.get3())
+ && pointsOnSameSide(point, tri.get2(), tri.get1(), tri.get3())
+ && pointsOnSameSide(point, tri.get3(), tri.get1(), tri.get2()))
+ return true;
+ return false;
+ }
+
+ private static float getLowestRoot(float a, float b, float c, float maxR) {
+ float determinant = b * b - 4f * a * c;
+ if (determinant < 0){
+ return Float.NaN;
+ }
+
+ float sqrtd = FastMath.sqrt(determinant);
+ float r1 = (-b - sqrtd) / (2f * a);
+ float r2 = (-b + sqrtd) / (2f * a);
+
+ if (r1 > r2){
+ float temp = r2;
+ r2 = r1;
+ r1 = temp;
+ }
+
+ if (r1 > 0 && r1 < maxR){
+ return r1;
+ }
+
+ if (r2 > 0 && r2 < maxR){
+ return r2;
+ }
+
+ return Float.NaN;
+ }
+
+ private float collideWithVertex(Vector3f sCenter, Vector3f sVelocity,
+ float velocitySquared, Vector3f vertex, float t) {
+ // A = velocity * velocity
+ // B = 2 * (velocity . (center - vertex));
+ // C = ||vertex - center||^2 - 1f;
+
+ temp1.set(sCenter).subtractLocal(vertex);
+ float a = velocitySquared;
+ float b = 2f * sVelocity.dot(temp1);
+ float c = temp1.negateLocal().lengthSquared() - 1f;
+ float newT = getLowestRoot(a, b, c, t);
+
+// float A = velocitySquared;
+// float B = sCenter.subtract(vertex).dot(sVelocity) * 2f;
+// float C = vertex.subtract(sCenter).lengthSquared() - 1f;
+//
+// float newT = getLowestRoot(A, B, C, Float.MAX_VALUE);
+// if (newT > 1.0f)
+// newT = Float.NaN;
+
+ return newT;
+ }
+
+ private float collideWithSegment(Vector3f sCenter,
+ Vector3f sVelocity,
+ float velocitySquared,
+ Vector3f l1,
+ Vector3f l2,
+ float t,
+ Vector3f store) {
+ Vector3f edge = temp1.set(l2).subtractLocal(l1);
+ Vector3f base = temp2.set(l1).subtractLocal(sCenter);
+
+ float edgeSquared = edge.lengthSquared();
+ float baseSquared = base.lengthSquared();
+
+ float EdotV = edge.dot(sVelocity);
+ float EdotB = edge.dot(base);
+
+ float a = (edgeSquared * -velocitySquared) + EdotV * EdotV;
+ float b = (edgeSquared * 2f * sVelocity.dot(base))
+ - (2f * EdotV * EdotB);
+ float c = (edgeSquared * (1f - baseSquared)) + EdotB * EdotB;
+ float newT = getLowestRoot(a, b, c, t);
+ if (!Float.isNaN(newT)){
+ float f = (EdotV * newT - EdotB) / edgeSquared;
+ if (f >= 0f && f < 1f){
+ store.scaleAdd(f, edge, l1);
+ return newT;
+ }
+ }
+ return Float.NaN;
+ }
+
+ private CollisionResult collideWithTriangle(AbstractTriangle tri){
+ // scale scaledTriangle based on dimension
+ scaledTri.get1().set(tri.get1()).multLocal(invDim);
+ scaledTri.get2().set(tri.get2()).multLocal(invDim);
+ scaledTri.get3().set(tri.get3()).multLocal(invDim);
+// Vector3f sVelocity = velocity.mult(invDim);
+// Vector3f sCenter = center.mult(invDim);
+ velocity.mult(invDim, sVelocity);
+ center.mult(invDim, sCenter);
+
+ triPlane.setPlanePoints(scaledTri);
+
+ float normalDotVelocity = triPlane.getNormal().dot(sVelocity);
+ // XXX: sVelocity.normalize() needed?
+ // back facing scaledTriangles not considered
+ if (normalDotVelocity > 0f)
+ return null;
+
+ float t0, t1;
+ boolean embedded = false;
+
+ float signedDistanceToPlane = triPlane.pseudoDistance(sCenter);
+ if (normalDotVelocity == 0.0f){
+ // we are travelling exactly parrallel to the plane
+ if (FastMath.abs(signedDistanceToPlane) >= 1.0f){
+ // no collision possible
+ return null;
+ }else{
+ // we are embedded
+ t0 = 0;
+ t1 = 1;
+ embedded = true;
+ System.out.println("EMBEDDED");
+ return null;
+ }
+ }else{
+ t0 = (-1f - signedDistanceToPlane) / normalDotVelocity;
+ t1 = ( 1f - signedDistanceToPlane) / normalDotVelocity;
+
+ if (t0 > t1){
+ float tf = t1;
+ t1 = t0;
+ t0 = tf;
+ }
+
+ if (t0 > 1.0f || t1 < 0.0f){
+ // collision is out of this sVelocity range
+ return null;
+ }
+
+ // clamp the interval to [0, 1]
+ t0 = Math.max(t0, 0.0f);
+ t1 = Math.min(t1, 1.0f);
+ }
+
+ boolean foundCollision = false;
+ float minT = 1f;
+
+ Vector3f contactPoint = new Vector3f();
+ Vector3f contactNormal = new Vector3f();
+
+// if (!embedded){
+ // check against the inside of the scaledTriangle
+ // contactPoint = sCenter - p.normal + t0 * sVelocity
+ contactPoint.set(sVelocity);
+ contactPoint.multLocal(t0);
+ contactPoint.addLocal(sCenter);
+ contactPoint.subtractLocal(triPlane.getNormal());
+
+ // test to see if the collision is on a scaledTriangle interior
+ if (isPointInTriangle(contactPoint, scaledTri) && !embedded){
+ foundCollision = true;
+
+ minT = t0;
+
+ // scale collision point back into R3
+ contactPoint.multLocal(dimension);
+ contactNormal.set(velocity).multLocal(t0);
+ contactNormal.addLocal(center);
+ contactNormal.subtractLocal(contactPoint).normalizeLocal();
+
+// contactNormal.set(triPlane.getNormal());
+
+ CollisionResult result = new CollisionResult();
+ result.setContactPoint(contactPoint);
+ result.setContactNormal(contactNormal);
+ result.setDistance(minT * velocity.length());
+ return result;
+ }
+// }
+
+ float velocitySquared = sVelocity.lengthSquared();
+
+ Vector3f v1 = scaledTri.get1();
+ Vector3f v2 = scaledTri.get2();
+ Vector3f v3 = scaledTri.get3();
+
+ // vertex 1
+ float newT;
+ newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v1, minT);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ contactPoint.set(v1);
+ foundCollision = true;
+ }
+
+ // vertex 2
+ newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v2, minT);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ contactPoint.set(v2);
+ foundCollision = true;
+ }
+
+ // vertex 3
+ newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v3, minT);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ contactPoint.set(v3);
+ foundCollision = true;
+ }
+
+ // edge 1-2
+ newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v1, v2, minT, contactPoint);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ foundCollision = true;
+ }
+
+ // edge 2-3
+ newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v2, v3, minT, contactPoint);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ foundCollision = true;
+ }
+
+ // edge 3-1
+ newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v3, v1, minT, contactPoint);
+ if (!Float.isNaN(newT)){
+ minT = newT;
+ foundCollision = true;
+ }
+
+ if (foundCollision){
+ // compute contact normal based on minimum t
+ contactPoint.multLocal(dimension);
+ contactNormal.set(velocity).multLocal(t0);
+ contactNormal.addLocal(center);
+ contactNormal.subtractLocal(contactPoint).normalizeLocal();
+
+ CollisionResult result = new CollisionResult();
+ result.setContactPoint(contactPoint);
+ result.setContactNormal(contactNormal);
+ result.setDistance(minT * velocity.length());
+
+ return result;
+ }else{
+ return null;
+ }
+ }
+
+ public CollisionResult collideWithSweepSphere(SweepSphere other){
+ temp1.set(velocity).subtractLocal(other.velocity);
+ temp2.set(center).subtractLocal(other.center);
+ temp3.set(dimension).addLocal(other.dimension);
+ // delta V
+ // delta C
+ // Rsum
+
+ float a = temp1.lengthSquared();
+ float b = 2f * temp1.dot(temp2);
+ float c = temp2.lengthSquared() - temp3.getX() * temp3.getX();
+ float t = getLowestRoot(a, b, c, 1);
+
+ // no collision
+ if (Float.isNaN(t))
+ return null;
+
+ CollisionResult result = new CollisionResult();
+ result.setDistance(velocity.length() * t);
+
+ temp1.set(velocity).multLocal(t).addLocal(center);
+ temp2.set(other.velocity).multLocal(t).addLocal(other.center);
+ temp3.set(temp2).subtractLocal(temp1);
+ // temp3 contains center to other.center vector
+
+ // normalize it to get normal
+ temp2.set(temp3).normalizeLocal();
+ result.setContactNormal(new Vector3f(temp2));
+
+ // temp3 is contact point
+ temp3.set(temp2).multLocal(dimension).addLocal(temp1);
+ result.setContactPoint(new Vector3f(temp3));
+
+ return result;
+ }
+
+ public static void main(String[] args){
+ SweepSphere ss = new SweepSphere();
+ ss.setCenter(Vector3f.ZERO);
+ ss.setDimension(1);
+ ss.setVelocity(new Vector3f(10, 10, 10));
+
+ SweepSphere ss2 = new SweepSphere();
+ ss2.setCenter(new Vector3f(5, 5, 5));
+ ss2.setDimension(1);
+ ss2.setVelocity(new Vector3f(-10, -10, -10));
+
+ CollisionResults cr = new CollisionResults();
+ ss.collideWith(ss2, cr);
+ if (cr.size() > 0){
+ CollisionResult c = cr.getClosestCollision();
+ System.out.println("D = "+c.getDistance());
+ System.out.println("P = "+c.getContactPoint());
+ System.out.println("N = "+c.getContactNormal());
+ }
+ }
+
+ public int collideWith(Collidable other, CollisionResults results)
+ throws UnsupportedCollisionException {
+ if (other instanceof AbstractTriangle){
+ AbstractTriangle tri = (AbstractTriangle) other;
+ CollisionResult result = collideWithTriangle(tri);
+ if (result != null){
+ results.addCollision(result);
+ return 1;
+ }
+ return 0;
+ }else if (other instanceof SweepSphere){
+ SweepSphere sph = (SweepSphere) other;
+
+ CollisionResult result = collideWithSweepSphere(sph);
+ if (result != null){
+ results.addCollision(result);
+ return 1;
+ }
+ return 0;
+ }else{
+ throw new UnsupportedCollisionException();
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java b/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java
new file mode 100644
index 0000000..ddd6779
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/UnsupportedCollisionException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision;
+
+/**
+ * Thrown by {@link Collidable} when the requested collision query could not
+ * be completed because one of the collidables does not support colliding with
+ * the other.
+ *
+ * @author Kirill Vainer
+ */
+public class UnsupportedCollisionException extends UnsupportedOperationException {
+
+ public UnsupportedCollisionException(Throwable arg0) {
+ super(arg0);
+ }
+
+ public UnsupportedCollisionException(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+
+ public UnsupportedCollisionException(String arg0) {
+ super(arg0);
+ }
+
+ public UnsupportedCollisionException() {
+ super();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHNode.java b/engine/src/core/com/jme3/collision/bih/BIHNode.java
new file mode 100644
index 0000000..0f5c1c8
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHNode.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.collision.bih;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.*;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector3f;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+
+/**
+ * Bounding Interval Hierarchy.
+ * Based on:
+ *
+ * Instant Ray Tracing: The Bounding Interval Hierarchy
+ * By Carsten Wächter and Alexander Keller
+ */
+public final class BIHNode implements Savable {
+
+ private int leftIndex, rightIndex;
+ private BIHNode left;
+ private BIHNode right;
+ private float leftPlane;
+ private float rightPlane;
+ private int axis;
+
+ //Do not do this: It increases memory usage of each BIHNode by at least 56 bytes!
+ //
+ //private Triangle tmpTriangle = new Triangle();
+
+ public BIHNode(int l, int r) {
+ leftIndex = l;
+ rightIndex = r;
+ axis = 3; // indicates leaf
+ }
+
+ public BIHNode(int axis) {
+ this.axis = axis;
+ }
+
+ public BIHNode() {
+ }
+
+ public BIHNode getLeftChild() {
+ return left;
+ }
+
+ public void setLeftChild(BIHNode left) {
+ this.left = left;
+ }
+
+ public float getLeftPlane() {
+ return leftPlane;
+ }
+
+ public void setLeftPlane(float leftPlane) {
+ this.leftPlane = leftPlane;
+ }
+
+ public BIHNode getRightChild() {
+ return right;
+ }
+
+ public void setRightChild(BIHNode right) {
+ this.right = right;
+ }
+
+ public float getRightPlane() {
+ return rightPlane;
+ }
+
+ public void setRightPlane(float rightPlane) {
+ this.rightPlane = rightPlane;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(leftIndex, "left_index", 0);
+ oc.write(rightIndex, "right_index", 0);
+ oc.write(leftPlane, "left_plane", 0);
+ oc.write(rightPlane, "right_plane", 0);
+ oc.write(axis, "axis", 0);
+ oc.write(left, "left_node", null);
+ oc.write(right, "right_node", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ leftIndex = ic.readInt("left_index", 0);
+ rightIndex = ic.readInt("right_index", 0);
+ leftPlane = ic.readFloat("left_plane", 0);
+ rightPlane = ic.readFloat("right_plane", 0);
+ axis = ic.readInt("axis", 0);
+ left = (BIHNode) ic.readSavable("left_node", null);
+ right = (BIHNode) ic.readSavable("right_node", null);
+ }
+
+ public static final class BIHStackData {
+
+ private final BIHNode node;
+ private final float min, max;
+
+ BIHStackData(BIHNode node, float min, float max) {
+ this.node = node;
+ this.min = min;
+ this.max = max;
+ }
+ }
+
+ public final int intersectWhere(Collidable col,
+ BoundingBox box,
+ Matrix4f worldMatrix,
+ BIHTree tree,
+ CollisionResults results) {
+
+ TempVars vars = TempVars.get();
+ ArrayList<BIHStackData> stack = vars.bihStack;
+ stack.clear();
+
+ float[] minExts = {box.getCenter().x - box.getXExtent(),
+ box.getCenter().y - box.getYExtent(),
+ box.getCenter().z - box.getZExtent()};
+
+ float[] maxExts = {box.getCenter().x + box.getXExtent(),
+ box.getCenter().y + box.getYExtent(),
+ box.getCenter().z + box.getZExtent()};
+
+ stack.add(new BIHStackData(this, 0, 0));
+
+ Triangle t = new Triangle();
+ int cols = 0;
+
+ stackloop:
+ while (stack.size() > 0) {
+ BIHNode node = stack.remove(stack.size() - 1).node;
+
+ while (node.axis != 3) {
+ int a = node.axis;
+
+ float maxExt = maxExts[a];
+ float minExt = minExts[a];
+
+ if (node.leftPlane < node.rightPlane) {
+ // means there's a gap in the middle
+ // if the box is in that gap, we stop there
+ if (minExt > node.leftPlane
+ && maxExt < node.rightPlane) {
+ continue stackloop;
+ }
+ }
+
+ if (maxExt < node.rightPlane) {
+ node = node.left;
+ } else if (minExt > node.leftPlane) {
+ node = node.right;
+ } else {
+ stack.add(new BIHStackData(node.right, 0, 0));
+ node = node.left;
+ }
+// if (maxExt < node.leftPlane
+// && maxExt < node.rightPlane){
+// node = node.left;
+// }else if (minExt > node.leftPlane
+// && minExt > node.rightPlane){
+// node = node.right;
+// }else{
+
+// }
+ }
+
+ for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+ tree.getTriangle(i, t.get1(), t.get2(), t.get3());
+ if (worldMatrix != null) {
+ worldMatrix.mult(t.get1(), t.get1());
+ worldMatrix.mult(t.get2(), t.get2());
+ worldMatrix.mult(t.get3(), t.get3());
+ }
+
+ int added = col.collideWith(t, results);
+
+ if (added > 0) {
+ int index = tree.getTriangleIndex(i);
+ int start = results.size() - added;
+
+ for (int j = start; j < results.size(); j++) {
+ CollisionResult cr = results.getCollisionDirect(j);
+ cr.setTriangleIndex(index);
+ }
+
+ cols += added;
+ }
+ }
+ }
+ vars.release();
+ return cols;
+ }
+
+ public final int intersectBrute(Ray r,
+ Matrix4f worldMatrix,
+ BIHTree tree,
+ float sceneMin,
+ float sceneMax,
+ CollisionResults results) {
+ float tHit = Float.POSITIVE_INFINITY;
+
+ TempVars vars = TempVars.get();
+
+ Vector3f v1 = vars.vect1,
+ v2 = vars.vect2,
+ v3 = vars.vect3;
+
+ int cols = 0;
+
+ ArrayList<BIHStackData> stack = vars.bihStack;
+ stack.clear();
+ stack.add(new BIHStackData(this, 0, 0));
+ stackloop:
+ while (stack.size() > 0) {
+
+ BIHStackData data = stack.remove(stack.size() - 1);
+ BIHNode node = data.node;
+
+ leafloop:
+ while (node.axis != 3) { // while node is not a leaf
+ BIHNode nearNode, farNode;
+ nearNode = node.left;
+ farNode = node.right;
+
+ stack.add(new BIHStackData(farNode, 0, 0));
+ node = nearNode;
+ }
+
+ // a leaf
+ for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+ tree.getTriangle(i, v1, v2, v3);
+
+ if (worldMatrix != null) {
+ worldMatrix.mult(v1, v1);
+ worldMatrix.mult(v2, v2);
+ worldMatrix.mult(v3, v3);
+ }
+
+ float t = r.intersects(v1, v2, v3);
+ if (t < tHit) {
+ tHit = t;
+ Vector3f contactPoint = new Vector3f(r.direction).multLocal(tHit).addLocal(r.origin);
+ CollisionResult cr = new CollisionResult(contactPoint, tHit);
+ cr.setTriangleIndex(tree.getTriangleIndex(i));
+ results.addCollision(cr);
+ cols++;
+ }
+ }
+ }
+ vars.release();
+ return cols;
+ }
+
+ public final int intersectWhere(Ray r,
+ Matrix4f worldMatrix,
+ BIHTree tree,
+ float sceneMin,
+ float sceneMax,
+ CollisionResults results) {
+
+ TempVars vars = TempVars.get();
+ ArrayList<BIHStackData> stack = vars.bihStack;
+ stack.clear();
+
+// float tHit = Float.POSITIVE_INFINITY;
+
+ Vector3f o = vars.vect1.set(r.getOrigin());
+ Vector3f d = vars.vect2.set(r.getDirection());
+
+ Matrix4f inv =vars.tempMat4.set(worldMatrix).invertLocal();
+
+ inv.mult(r.getOrigin(), r.getOrigin());
+
+ // Fixes rotation collision bug
+ inv.multNormal(r.getDirection(), r.getDirection());
+// inv.multNormalAcross(r.getDirection(), r.getDirection());
+
+ float[] origins = {r.getOrigin().x,
+ r.getOrigin().y,
+ r.getOrigin().z};
+
+ float[] invDirections = {1f / r.getDirection().x,
+ 1f / r.getDirection().y,
+ 1f / r.getDirection().z};
+
+ r.getDirection().normalizeLocal();
+
+ Vector3f v1 = vars.vect3,
+ v2 = vars.vect4,
+ v3 = vars.vect5;
+ int cols = 0;
+
+ stack.add(new BIHStackData(this, sceneMin, sceneMax));
+ stackloop:
+ while (stack.size() > 0) {
+
+ BIHStackData data = stack.remove(stack.size() - 1);
+ BIHNode node = data.node;
+ float tMin = data.min,
+ tMax = data.max;
+
+ if (tMax < tMin) {
+ continue;
+ }
+
+ leafloop:
+ while (node.axis != 3) { // while node is not a leaf
+ int a = node.axis;
+
+ // find the origin and direction value for the given axis
+ float origin = origins[a];
+ float invDirection = invDirections[a];
+
+ float tNearSplit, tFarSplit;
+ BIHNode nearNode, farNode;
+
+ tNearSplit = (node.leftPlane - origin) * invDirection;
+ tFarSplit = (node.rightPlane - origin) * invDirection;
+ nearNode = node.left;
+ farNode = node.right;
+
+ if (invDirection < 0) {
+ float tmpSplit = tNearSplit;
+ tNearSplit = tFarSplit;
+ tFarSplit = tmpSplit;
+
+ BIHNode tmpNode = nearNode;
+ nearNode = farNode;
+ farNode = tmpNode;
+ }
+
+ if (tMin > tNearSplit && tMax < tFarSplit) {
+ continue stackloop;
+ }
+
+ if (tMin > tNearSplit) {
+ tMin = max(tMin, tFarSplit);
+ node = farNode;
+ } else if (tMax < tFarSplit) {
+ tMax = min(tMax, tNearSplit);
+ node = nearNode;
+ } else {
+ stack.add(new BIHStackData(farNode, max(tMin, tFarSplit), tMax));
+ tMax = min(tMax, tNearSplit);
+ node = nearNode;
+ }
+ }
+
+// if ( (node.rightIndex - node.leftIndex) > minTrisPerNode){
+// // on demand subdivision
+// node.subdivide();
+// stack.add(new BIHStackData(node, tMin, tMax));
+// continue stackloop;
+// }
+
+ // a leaf
+ for (int i = node.leftIndex; i <= node.rightIndex; i++) {
+ tree.getTriangle(i, v1, v2, v3);
+
+ float t = r.intersects(v1, v2, v3);
+ if (!Float.isInfinite(t)) {
+ if (worldMatrix != null) {
+ worldMatrix.mult(v1, v1);
+ worldMatrix.mult(v2, v2);
+ worldMatrix.mult(v3, v3);
+ float t_world = new Ray(o, d).intersects(v1, v2, v3);
+ t = t_world;
+ }
+
+ Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null);
+ Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o);
+ float worldSpaceDist = o.distance(contactPoint);
+
+ CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist);
+ cr.setContactNormal(contactNormal);
+ cr.setTriangleIndex(tree.getTriangleIndex(i));
+ results.addCollision(cr);
+ cols++;
+ }
+ }
+ }
+ vars.release();
+ r.setOrigin(o);
+ r.setDirection(d);
+
+ return cols;
+ }
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHTree.java b/engine/src/core/com/jme3/collision/bih/BIHTree.java
new file mode 100644
index 0000000..3660922
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHTree.java
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.collision.bih;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Ray;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.CollisionData;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.scene.mesh.VirtualIndexBuffer;
+import com.jme3.scene.mesh.WrappedIndexBuffer;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import static java.lang.Math.max;
+import java.nio.FloatBuffer;
+
+public class BIHTree implements CollisionData {
+
+ public static final int MAX_TREE_DEPTH = 100;
+ public static final int MAX_TRIS_PER_NODE = 21;
+ private Mesh mesh;
+ private BIHNode root;
+ private int maxTrisPerNode;
+ private int numTris;
+ private float[] pointData;
+ private int[] triIndices;
+
+ private transient CollisionResults boundResults = new CollisionResults();
+ private transient float[] bihSwapTmp;
+
+ private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[]
+ {
+ new TriangleAxisComparator(0),
+ new TriangleAxisComparator(1),
+ new TriangleAxisComparator(2)
+ };
+
+ private void initTriList(FloatBuffer vb, IndexBuffer ib) {
+ pointData = new float[numTris * 3 * 3];
+ int p = 0;
+ for (int i = 0; i < numTris * 3; i += 3) {
+ int vert = ib.get(i) * 3;
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert);
+
+ vert = ib.get(i + 1) * 3;
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert);
+
+ vert = ib.get(i + 2) * 3;
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert++);
+ pointData[p++] = vb.get(vert);
+ }
+
+ triIndices = new int[numTris];
+ for (int i = 0; i < numTris; i++) {
+ triIndices[i] = i;
+ }
+ }
+
+ public BIHTree(Mesh mesh, int maxTrisPerNode) {
+ this.mesh = mesh;
+ this.maxTrisPerNode = maxTrisPerNode;
+
+ if (maxTrisPerNode < 1 || mesh == null) {
+ throw new IllegalArgumentException();
+ }
+
+ bihSwapTmp = new float[9];
+
+ FloatBuffer vb = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ IndexBuffer ib = mesh.getIndexBuffer();
+ if (ib == null) {
+ ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode());
+ } else if (mesh.getMode() != Mode.Triangles) {
+ ib = new WrappedIndexBuffer(mesh);
+ }
+
+ numTris = ib.size() / 3;
+ initTriList(vb, ib);
+ }
+
+ public BIHTree(Mesh mesh) {
+ this(mesh, MAX_TRIS_PER_NODE);
+ }
+
+ public BIHTree() {
+ }
+
+ public void construct() {
+ BoundingBox sceneBbox = createBox(0, numTris - 1);
+ root = createNode(0, numTris - 1, sceneBbox, 0);
+ }
+
+ private BoundingBox createBox(int l, int r) {
+ TempVars vars = TempVars.get();
+
+ Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY));
+ Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY));
+
+ Vector3f v1 = vars.vect3,
+ v2 = vars.vect4,
+ v3 = vars.vect5;
+
+ for (int i = l; i <= r; i++) {
+ getTriangle(i, v1, v2, v3);
+ BoundingBox.checkMinMax(min, max, v1);
+ BoundingBox.checkMinMax(min, max, v2);
+ BoundingBox.checkMinMax(min, max, v3);
+ }
+
+ BoundingBox bbox = new BoundingBox(min, max);
+ vars.release();
+ return bbox;
+ }
+
+ int getTriangleIndex(int triIndex) {
+ return triIndices[triIndex];
+ }
+
+ private int sortTriangles(int l, int r, float split, int axis) {
+ int pivot = l;
+ int j = r;
+
+ TempVars vars = TempVars.get();
+
+ Vector3f v1 = vars.vect1,
+ v2 = vars.vect2,
+ v3 = vars.vect3;
+
+ while (pivot <= j) {
+ getTriangle(pivot, v1, v2, v3);
+ v1.addLocal(v2).addLocal(v3).multLocal(FastMath.ONE_THIRD);
+ if (v1.get(axis) > split) {
+ swapTriangles(pivot, j);
+ --j;
+ } else {
+ ++pivot;
+ }
+ }
+
+ vars.release();
+ pivot = (pivot == l && j < pivot) ? j : pivot;
+ return pivot;
+ }
+
+ private void setMinMax(BoundingBox bbox, boolean doMin, int axis, float value) {
+ Vector3f min = bbox.getMin(null);
+ Vector3f max = bbox.getMax(null);
+
+ if (doMin) {
+ min.set(axis, value);
+ } else {
+ max.set(axis, value);
+ }
+
+ bbox.setMinMax(min, max);
+ }
+
+ private float getMinMax(BoundingBox bbox, boolean doMin, int axis) {
+ if (doMin) {
+ return bbox.getMin(null).get(axis);
+ } else {
+ return bbox.getMax(null).get(axis);
+ }
+ }
+
+// private BIHNode createNode2(int l, int r, BoundingBox nodeBbox, int depth){
+// if ((r - l) < maxTrisPerNode || depth > 100)
+// return createLeaf(l, r);
+//
+// BoundingBox currentBox = createBox(l, r);
+// int axis = depth % 3;
+// float split = currentBox.getCenter().get(axis);
+//
+// TriangleAxisComparator comparator = comparators[axis];
+// Arrays.sort(tris, l, r, comparator);
+// int splitIndex = -1;
+//
+// float leftPlane, rightPlane = Float.POSITIVE_INFINITY;
+// leftPlane = tris[l].getExtreme(axis, false);
+// for (int i = l; i <= r; i++){
+// BIHTriangle tri = tris[i];
+// if (splitIndex == -1){
+// float v = tri.getCenter().get(axis);
+// if (v > split){
+// if (i == 0){
+// // no left plane
+// splitIndex = -2;
+// }else{
+// splitIndex = i;
+// // first triangle assigned to right
+// rightPlane = tri.getExtreme(axis, true);
+// }
+// }else{
+// // triangle assigned to left
+// float ex = tri.getExtreme(axis, false);
+// if (ex > leftPlane)
+// leftPlane = ex;
+// }
+// }else{
+// float ex = tri.getExtreme(axis, true);
+// if (ex < rightPlane)
+// rightPlane = ex;
+// }
+// }
+//
+// if (splitIndex < 0){
+// splitIndex = (r - l) / 2;
+//
+// leftPlane = Float.NEGATIVE_INFINITY;
+// rightPlane = Float.POSITIVE_INFINITY;
+//
+// for (int i = l; i < splitIndex; i++){
+// float ex = tris[i].getExtreme(axis, false);
+// if (ex > leftPlane){
+// leftPlane = ex;
+// }
+// }
+// for (int i = splitIndex; i <= r; i++){
+// float ex = tris[i].getExtreme(axis, true);
+// if (ex < rightPlane){
+// rightPlane = ex;
+// }
+// }
+// }
+//
+// BIHNode node = new BIHNode(axis);
+// node.leftPlane = leftPlane;
+// node.rightPlane = rightPlane;
+//
+// node.leftIndex = l;
+// node.rightIndex = r;
+//
+// BoundingBox leftBbox = new BoundingBox(currentBox);
+// setMinMax(leftBbox, false, axis, split);
+// node.left = createNode2(l, splitIndex-1, leftBbox, depth+1);
+//
+// BoundingBox rightBbox = new BoundingBox(currentBox);
+// setMinMax(rightBbox, true, axis, split);
+// node.right = createNode2(splitIndex, r, rightBbox, depth+1);
+//
+// return node;
+// }
+ private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) {
+ if ((r - l) < maxTrisPerNode || depth > MAX_TREE_DEPTH) {
+ return new BIHNode(l, r);
+ }
+
+ BoundingBox currentBox = createBox(l, r);
+
+ Vector3f exteriorExt = nodeBbox.getExtent(null);
+ Vector3f interiorExt = currentBox.getExtent(null);
+ exteriorExt.subtractLocal(interiorExt);
+
+ int axis = 0;
+ if (exteriorExt.x > exteriorExt.y) {
+ if (exteriorExt.x > exteriorExt.z) {
+ axis = 0;
+ } else {
+ axis = 2;
+ }
+ } else {
+ if (exteriorExt.y > exteriorExt.z) {
+ axis = 1;
+ } else {
+ axis = 2;
+ }
+ }
+ if (exteriorExt.equals(Vector3f.ZERO)) {
+ axis = 0;
+ }
+
+// Arrays.sort(tris, l, r, comparators[axis]);
+ float split = currentBox.getCenter().get(axis);
+ int pivot = sortTriangles(l, r, split, axis);
+ if (pivot == l || pivot == r) {
+ pivot = (r + l) / 2;
+ }
+
+ //If one of the partitions is empty, continue with recursion: same level but different bbox
+ if (pivot < l) {
+ //Only right
+ BoundingBox rbbox = new BoundingBox(currentBox);
+ setMinMax(rbbox, true, axis, split);
+ return createNode(l, r, rbbox, depth + 1);
+ } else if (pivot > r) {
+ //Only left
+ BoundingBox lbbox = new BoundingBox(currentBox);
+ setMinMax(lbbox, false, axis, split);
+ return createNode(l, r, lbbox, depth + 1);
+ } else {
+ //Build the node
+ BIHNode node = new BIHNode(axis);
+
+ //Left child
+ BoundingBox lbbox = new BoundingBox(currentBox);
+ setMinMax(lbbox, false, axis, split);
+
+ //The left node right border is the plane most right
+ node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis));
+ node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call
+
+ //Right Child
+ BoundingBox rbbox = new BoundingBox(currentBox);
+ setMinMax(rbbox, true, axis, split);
+ //The right node left border is the plane most left
+ node.setRightPlane(getMinMax(createBox(pivot, r), true, axis));
+ node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call
+
+ return node;
+ }
+ }
+
+ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) {
+ int pointIndex = index * 9;
+
+ v1.x = pointData[pointIndex++];
+ v1.y = pointData[pointIndex++];
+ v1.z = pointData[pointIndex++];
+
+ v2.x = pointData[pointIndex++];
+ v2.y = pointData[pointIndex++];
+ v2.z = pointData[pointIndex++];
+
+ v3.x = pointData[pointIndex++];
+ v3.y = pointData[pointIndex++];
+ v3.z = pointData[pointIndex++];
+ }
+
+ public void swapTriangles(int index1, int index2) {
+ int p1 = index1 * 9;
+ int p2 = index2 * 9;
+
+ // store p1 in tmp
+ System.arraycopy(pointData, p1, bihSwapTmp, 0, 9);
+
+ // copy p2 to p1
+ System.arraycopy(pointData, p2, pointData, p1, 9);
+
+ // copy tmp to p2
+ System.arraycopy(bihSwapTmp, 0, pointData, p2, 9);
+
+ // swap indices
+ int tmp2 = triIndices[index1];
+ triIndices[index1] = triIndices[index2];
+ triIndices[index2] = tmp2;
+ }
+
+ private int collideWithRay(Ray r,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results) {
+
+ boundResults.clear();
+ worldBound.collideWith(r, boundResults);
+ if (boundResults.size() > 0) {
+ float tMin = boundResults.getClosestCollision().getDistance();
+ float tMax = boundResults.getFarthestCollision().getDistance();
+
+ if (tMax <= 0) {
+ tMax = Float.POSITIVE_INFINITY;
+ } else if (tMin == tMax) {
+ tMin = 0;
+ }
+
+ if (tMin <= 0) {
+ tMin = 0;
+ }
+
+ if (r.getLimit() < Float.POSITIVE_INFINITY) {
+ tMax = Math.min(tMax, r.getLimit());
+ if (tMin > tMax){
+ return 0;
+ }
+ }
+
+// return root.intersectBrute(r, worldMatrix, this, tMin, tMax, results);
+ return root.intersectWhere(r, worldMatrix, this, tMin, tMax, results);
+ }
+ return 0;
+ }
+
+ private int collideWithBoundingVolume(BoundingVolume bv,
+ Matrix4f worldMatrix,
+ CollisionResults results) {
+ BoundingBox bbox;
+ if (bv instanceof BoundingSphere) {
+ BoundingSphere sphere = (BoundingSphere) bv;
+ bbox = new BoundingBox(bv.getCenter().clone(), sphere.getRadius(),
+ sphere.getRadius(),
+ sphere.getRadius());
+ } else if (bv instanceof BoundingBox) {
+ bbox = new BoundingBox((BoundingBox) bv);
+ } else {
+ throw new UnsupportedCollisionException();
+ }
+
+ bbox.transform(worldMatrix.invert(), bbox);
+ return root.intersectWhere(bv, bbox, worldMatrix, this, results);
+ }
+
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results) {
+
+ if (other instanceof Ray) {
+ Ray ray = (Ray) other;
+ return collideWithRay(ray, worldMatrix, worldBound, results);
+ } else if (other instanceof BoundingVolume) {
+ BoundingVolume bv = (BoundingVolume) other;
+ return collideWithBoundingVolume(bv, worldMatrix, results);
+ } else {
+ throw new UnsupportedCollisionException();
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(mesh, "mesh", null);
+ oc.write(root, "root", null);
+ oc.write(maxTrisPerNode, "tris_per_node", 0);
+ oc.write(pointData, "points", null);
+ oc.write(triIndices, "indices", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ mesh = (Mesh) ic.readSavable("mesh", null);
+ root = (BIHNode) ic.readSavable("root", null);
+ maxTrisPerNode = ic.readInt("tris_per_node", 0);
+ pointData = ic.readFloatArray("points", null);
+ triIndices = ic.readIntArray("indices", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/collision/bih/BIHTriangle.java b/engine/src/core/com/jme3/collision/bih/BIHTriangle.java
new file mode 100644
index 0000000..2229da0
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/BIHTriangle.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision.bih;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+
+public final class BIHTriangle {
+
+ private final Vector3f pointa = new Vector3f();
+ private final Vector3f pointb = new Vector3f();
+ private final Vector3f pointc = new Vector3f();
+ private final Vector3f center = new Vector3f();
+
+ public BIHTriangle(Vector3f p1, Vector3f p2, Vector3f p3) {
+ pointa.set(p1);
+ pointb.set(p2);
+ pointc.set(p3);
+ center.set(pointa);
+ center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD);
+ }
+
+ public Vector3f get1(){
+ return pointa;
+ }
+
+ public Vector3f get2(){
+ return pointb;
+ }
+
+ public Vector3f get3(){
+ return pointc;
+ }
+
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ public Vector3f getNormal(){
+ Vector3f normal = new Vector3f(pointb);
+ normal.subtractLocal(pointa).crossLocal(pointc.x-pointa.x, pointc.y-pointa.y, pointc.z-pointa.z);
+ normal.normalizeLocal();
+ return normal;
+ }
+
+ public float getExtreme(int axis, boolean left){
+ float v1, v2, v3;
+ switch (axis){
+ case 0: v1 = pointa.x; v2 = pointb.x; v3 = pointc.x; break;
+ case 1: v1 = pointa.y; v2 = pointb.y; v3 = pointc.y; break;
+ case 2: v1 = pointa.z; v2 = pointb.z; v3 = pointc.z; break;
+ default: assert false; return 0;
+ }
+ if (left){
+ if (v1 < v2){
+ if (v1 < v3)
+ return v1;
+ else
+ return v3;
+ }else{
+ if (v2 < v3)
+ return v2;
+ else
+ return v3;
+ }
+ }else{
+ if (v1 > v2){
+ if (v1 > v3)
+ return v1;
+ else
+ return v3;
+ }else{
+ if (v2 > v3)
+ return v2;
+ else
+ return v3;
+ }
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java b/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java
new file mode 100644
index 0000000..895dd5a
--- /dev/null
+++ b/engine/src/core/com/jme3/collision/bih/TriangleAxisComparator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.collision.bih;
+
+import com.jme3.math.Vector3f;
+import java.util.Comparator;
+
+public class TriangleAxisComparator implements Comparator<BIHTriangle> {
+
+ private final int axis;
+
+ public TriangleAxisComparator(int axis){
+ this.axis = axis;
+ }
+
+ public int compare(BIHTriangle o1, BIHTriangle o2) {
+ float v1, v2;
+ Vector3f c1 = o1.getCenter();
+ Vector3f c2 = o2.getCenter();
+ switch (axis){
+ case 0: v1 = c1.x; v2 = c2.x; break;
+ case 1: v1 = c1.y; v2 = c2.y; break;
+ case 2: v1 = c1.z; v2 = c2.z; break;
+ default: assert false; return 0;
+ }
+ if (v1 > v2)
+ return 1;
+ else if (v1 < v2)
+ return -1;
+ else
+ return 0;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/effect/Particle.java b/engine/src/core/com/jme3/effect/Particle.java
new file mode 100644
index 0000000..8e976f9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/Particle.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.effect;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+
+/**
+ * Represents a single particle in a {@link ParticleEmitter}.
+ *
+ * @author Kirill Vainer
+ */
+public class Particle {
+
+ /**
+ * Particle velocity.
+ */
+ public final Vector3f velocity = new Vector3f();
+
+ /**
+ * Current particle position
+ */
+ public final Vector3f position = new Vector3f();
+
+ /**
+ * Particle color
+ */
+ public final ColorRGBA color = new ColorRGBA(0,0,0,0);
+
+ /**
+ * Particle size or radius.
+ */
+ public float size;
+
+ /**
+ * Particle remaining life, in seconds.
+ */
+ public float life;
+
+ /**
+ * The initial particle life
+ */
+ public float startlife;
+
+ /**
+ * Particle rotation angle (in radians).
+ */
+ public float angle;
+
+ /**
+ * Particle rotation angle speed (in radians).
+ */
+ public float rotateSpeed;
+
+ /**
+ * Particle image index.
+ */
+ public int imageIndex = 0;
+
+ /**
+ * Distance to camera. Only used for sorted particles.
+ */
+ //public float distToCam;
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleComparator.java b/engine/src/core/com/jme3/effect/ParticleComparator.java
new file mode 100644
index 0000000..dbe52dd
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleComparator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.effect;
+
+import com.jme3.renderer.Camera;
+import java.util.Comparator;
+
+@Deprecated
+class ParticleComparator implements Comparator<Particle> {
+
+ private Camera cam;
+
+ public void setCamera(Camera cam){
+ this.cam = cam;
+ }
+
+ public int compare(Particle p1, Particle p2) {
+ return 0; // unused
+ /*
+ if (p1.life <= 0 || p2.life <= 0)
+ return 0;
+
+// if (p1.life <= 0)
+// return 1;
+// else if (p2.life <= 0)
+// return -1;
+
+ float d1 = p1.distToCam, d2 = p2.distToCam;
+
+ if (d1 == -1){
+ d1 = cam.distanceToNearPlane(p1.position);
+ p1.distToCam = d1;
+ }
+ if (d2 == -1){
+ d2 = cam.distanceToNearPlane(p2.position);
+ p2.distToCam = d2;
+ }
+
+ if (d1 < d2)
+ return 1;
+ else if (d1 > d2)
+ return -1;
+ else
+ return 0;
+ */
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/effect/ParticleEmitter.java b/engine/src/core/com/jme3/effect/ParticleEmitter.java
new file mode 100644
index 0000000..c00e66e
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleEmitter.java
@@ -0,0 +1,1206 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.influencers.DefaultParticleInfluencer;
+import com.jme3.effect.influencers.ParticleInfluencer;
+import com.jme3.effect.shapes.EmitterPointShape;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <code>ParticleEmitter</code> is a special kind of geometry which simulates
+ * a particle system.
+ * <p>
+ * Particle emitters can be used to simulate various kinds of phenomena,
+ * such as fire, smoke, explosions and much more.
+ * <p>
+ * Particle emitters have many properties which are used to control the
+ * simulation. The interpretation of these properties depends on the
+ * {@link ParticleInfluencer} that has been assigned to the emitter via
+ * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
+ * By default the implementation {@link DefaultParticleInfluencer} is used.
+ *
+ * @author Kirill Vainer
+ */
+public class ParticleEmitter extends Geometry {
+
+ private boolean enabled = true;
+ private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO);
+ private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer();
+ private ParticleEmitterControl control;
+ private EmitterShape shape = DEFAULT_SHAPE;
+ private ParticleMesh particleMesh;
+ private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
+ private ParticleMesh.Type meshType;
+ private Particle[] particles;
+ private int firstUnUsed;
+ private int lastUsed;
+// private int next = 0;
+// private ArrayList<Integer> unusedIndices = new ArrayList<Integer>();
+ private boolean randomAngle;
+ private boolean selectRandomImage;
+ private boolean facingVelocity;
+ private float particlesPerSec = 20;
+ private float timeDifference = 0;
+ private float lowLife = 3f;
+ private float highLife = 7f;
+ private Vector3f gravity = new Vector3f(0.0f, 0.1f, 0.0f);
+ private float rotateSpeed;
+ private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
+ private int imagesX = 1;
+ private int imagesY = 1;
+
+ private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
+ private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
+ private float startSize = 0.2f;
+ private float endSize = 2f;
+ private boolean worldSpace = true;
+ //variable that helps with computations
+ private transient Vector3f temp = new Vector3f();
+
+ public static class ParticleEmitterControl implements Control {
+
+ ParticleEmitter parentEmitter;
+
+ public ParticleEmitterControl() {
+ }
+
+ public ParticleEmitterControl(ParticleEmitter parentEmitter) {
+ this.parentEmitter = parentEmitter;
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ return this; // WARNING: Sets wrong control on spatial. Will be
+ // fixed automatically by ParticleEmitter.clone() method.
+ }
+
+ public void setSpatial(Spatial spatial) {
+ }
+
+ public void setEnabled(boolean enabled) {
+ parentEmitter.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return parentEmitter.isEnabled();
+ }
+
+ public void update(float tpf) {
+ parentEmitter.updateFromControl(tpf);
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ parentEmitter.renderFromControl(rm, vp);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ }
+ }
+
+ @Override
+ public ParticleEmitter clone() {
+ return clone(true);
+ }
+
+ @Override
+ public ParticleEmitter clone(boolean cloneMaterial) {
+ ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
+ clone.shape = shape.deepClone();
+
+ // Reinitialize particle list
+ clone.setNumParticles(particles.length);
+
+ clone.faceNormal = faceNormal.clone();
+ clone.startColor = startColor.clone();
+ clone.endColor = endColor.clone();
+ clone.particleInfluencer = particleInfluencer.clone();
+
+ // remove wrong control
+ clone.controls.remove(control);
+
+ // put correct control
+ clone.controls.add(new ParticleEmitterControl(clone));
+
+ // Reinitialize particle mesh
+ switch (meshType) {
+ case Point:
+ clone.particleMesh = new ParticlePointMesh();
+ clone.setMesh(clone.particleMesh);
+ break;
+ case Triangle:
+ clone.particleMesh = new ParticleTriMesh();
+ clone.setMesh(clone.particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ clone.particleMesh.initParticleData(clone, clone.particles.length);
+ clone.particleMesh.setImagesXY(clone.imagesX, clone.imagesY);
+
+ return clone;
+ }
+
+ public ParticleEmitter(String name, Type type, int numParticles) {
+ super(name);
+
+ // ignore world transform, unless user sets inLocalSpace
+ this.setIgnoreTransform(true);
+
+ // particles neither receive nor cast shadows
+ this.setShadowMode(ShadowMode.Off);
+
+ // particles are usually transparent
+ this.setQueueBucket(Bucket.Transparent);
+
+ meshType = type;
+
+ // Must create clone of shape/influencer so that a reference to a static is
+ // not maintained
+ shape = shape.deepClone();
+ particleInfluencer = particleInfluencer.clone();
+
+ control = new ParticleEmitterControl(this);
+ controls.add(control);
+
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(numParticles);
+// particleMesh.initParticleData(this, particles.length);
+ }
+
+ /**
+ * For serialization only. Do not use.
+ */
+ public ParticleEmitter() {
+ super();
+ }
+
+ public void setShape(EmitterShape shape) {
+ this.shape = shape;
+ }
+
+ public EmitterShape getShape() {
+ return shape;
+ }
+
+ /**
+ * Set the {@link ParticleInfluencer} to influence this particle emitter.
+ *
+ * @param particleInfluencer the {@link ParticleInfluencer} to influence
+ * this particle emitter.
+ *
+ * @see ParticleInfluencer
+ */
+ public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
+ this.particleInfluencer = particleInfluencer;
+ }
+
+ /**
+ * Returns the {@link ParticleInfluencer} that influences this
+ * particle emitter.
+ *
+ * @return the {@link ParticleInfluencer} that influences this
+ * particle emitter.
+ *
+ * @see ParticleInfluencer
+ */
+ public ParticleInfluencer getParticleInfluencer() {
+ return particleInfluencer;
+ }
+
+ /**
+ * Returns the mesh type used by the particle emitter.
+ *
+ *
+ * @return the mesh type used by the particle emitter.
+ *
+ * @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
+ * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
+ */
+ public ParticleMesh.Type getMeshType() {
+ return meshType;
+ }
+
+ /**
+ * Sets the type of mesh used by the particle emitter.
+ * @param meshType The mesh type to use
+ */
+ public void setMeshType(ParticleMesh.Type meshType) {
+ this.meshType = meshType;
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(particles.length);
+ }
+
+ /**
+ * Returns true if particles should spawn in world space.
+ *
+ * @return true if particles should spawn in world space.
+ *
+ * @see ParticleEmitter#setInWorldSpace(boolean)
+ */
+ public boolean isInWorldSpace() {
+ return worldSpace;
+ }
+
+ /**
+ * Set to true if particles should spawn in world space.
+ *
+ * <p>If set to true and the particle emitter is moved in the scene,
+ * then particles that have already spawned won't be effected by this
+ * motion. If set to false, the particles will emit in local space
+ * and when the emitter is moved, so are all the particles that
+ * were emitted previously.
+ *
+ * @param worldSpace true if particles should spawn in world space.
+ */
+ public void setInWorldSpace(boolean worldSpace) {
+ this.setIgnoreTransform(worldSpace);
+ this.worldSpace = worldSpace;
+ }
+
+ /**
+ * Returns the number of visible particles (spawned but not dead).
+ *
+ * @return the number of visible particles
+ */
+ public int getNumVisibleParticles() {
+// return unusedIndices.size() + next;
+ return lastUsed + 1;
+ }
+
+ /**
+ * Set the maximum amount of particles that
+ * can exist at the same time with this emitter.
+ * Calling this method many times is not recommended.
+ *
+ * @param numParticles the maximum amount of particles that
+ * can exist at the same time with this emitter.
+ */
+ public final void setNumParticles(int numParticles) {
+ particles = new Particle[numParticles];
+ for (int i = 0; i < numParticles; i++) {
+ particles[i] = new Particle();
+ }
+ //We have to reinit the mesh's buffers with the new size
+ particleMesh.initParticleData(this, particles.length);
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ firstUnUsed = 0;
+ lastUsed = -1;
+ }
+
+ public int getMaxNumParticles() {
+ return particles.length;
+ }
+
+ /**
+ * Returns a list of all particles (shouldn't be used in most cases).
+ *
+ * <p>
+ * This includes both existing and non-existing particles.
+ * The size of the array is set to the <code>numParticles</code> value
+ * specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
+ * method.
+ *
+ * @return a list of all particles.
+ */
+ public Particle[] getParticles() {
+ return particles;
+ }
+
+ /**
+ * Get the normal which particles are facing.
+ *
+ * @return the normal which particles are facing.
+ *
+ * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
+ */
+ public Vector3f getFaceNormal() {
+ if (Vector3f.isValidVector(faceNormal)) {
+ return faceNormal;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the normal which particles are facing.
+ *
+ * <p>By default, particles
+ * will face the camera, but for some effects (e.g shockwave) it may
+ * be necessary to face a specific direction instead. To restore
+ * normal functionality, provide <code>null</code> as the argument for
+ * <code>faceNormal</code>.
+ *
+ * @param faceNormal The normals particles should face, or <code>null</code>
+ * if particles should face the camera.
+ */
+ public void setFaceNormal(Vector3f faceNormal) {
+ if (faceNormal == null || !Vector3f.isValidVector(faceNormal)) {
+ this.faceNormal.set(Vector3f.NAN);
+ } else {
+ this.faceNormal = faceNormal;
+ }
+ }
+
+ /**
+ * Returns the rotation speed in radians/sec for particles.
+ *
+ * @return the rotation speed in radians/sec for particles.
+ *
+ * @see ParticleEmitter#setRotateSpeed(float)
+ */
+ public float getRotateSpeed() {
+ return rotateSpeed;
+ }
+
+ /**
+ * Set the rotation speed in radians/sec for particles
+ * spawned after the invocation of this method.
+ *
+ * @param rotateSpeed the rotation speed in radians/sec for particles
+ * spawned after the invocation of this method.
+ */
+ public void setRotateSpeed(float rotateSpeed) {
+ this.rotateSpeed = rotateSpeed;
+ }
+
+ /**
+ * Returns true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @return true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @see ParticleEmitter#setRandomAngle(boolean)
+ */
+ public boolean isRandomAngle() {
+ return randomAngle;
+ }
+
+ /**
+ * Set to true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @param randomAngle if every particle spawned
+ * should have a random facing angle.
+ */
+ public void setRandomAngle(boolean randomAngle) {
+ this.randomAngle = randomAngle;
+ }
+
+ /**
+ * Returns true if every particle spawned should get a random
+ * image.
+ *
+ * @return True if every particle spawned should get a random
+ * image.
+ *
+ * @see ParticleEmitter#setSelectRandomImage(boolean)
+ */
+ public boolean isSelectRandomImage() {
+ return selectRandomImage;
+ }
+
+ /**
+ * Set to true if every particle spawned
+ * should get a random image from a pool of images constructed from
+ * the texture, with X by Y possible images.
+ *
+ * <p>By default, X and Y are equal
+ * to 1, thus allowing only 1 possible image to be selected, but if the
+ * particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
+ * and {#link ParticleEmitter#setImagesY(int) } methods, then multiple images
+ * can be selected. Setting to false will cause each particle to have an animation
+ * of images displayed, starting at image 1, and going until image X*Y when
+ * the particle reaches its end of life.
+ *
+ * @param selectRandomImage True if every particle spawned should get a random
+ * image.
+ */
+ public void setSelectRandomImage(boolean selectRandomImage) {
+ this.selectRandomImage = selectRandomImage;
+ }
+
+ /**
+ * Check if particles spawned should face their velocity.
+ *
+ * @return True if particles spawned should face their velocity.
+ *
+ * @see ParticleEmitter#setFacingVelocity(boolean)
+ */
+ public boolean isFacingVelocity() {
+ return facingVelocity;
+ }
+
+ /**
+ * Set to true if particles spawned should face
+ * their velocity (or direction to which they are moving towards).
+ *
+ * <p>This is typically used for e.g spark effects.
+ *
+ * @param followVelocity True if particles spawned should face their velocity.
+ *
+ */
+ public void setFacingVelocity(boolean followVelocity) {
+ this.facingVelocity = followVelocity;
+ }
+
+ /**
+ * Get the end color of the particles spawned.
+ *
+ * @return the end color of the particles spawned.
+ *
+ * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
+ */
+ public ColorRGBA getEndColor() {
+ return endColor;
+ }
+
+ /**
+ * Set the end color of the particles spawned.
+ *
+ * <p>The
+ * particle color at any time is determined by blending the start color
+ * and end color based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param endColor the end color of the particles spawned.
+ */
+ public void setEndColor(ColorRGBA endColor) {
+ this.endColor.set(endColor);
+ }
+
+ /**
+ * Get the end size of the particles spawned.
+ *
+ * @return the end size of the particles spawned.
+ *
+ * @see ParticleEmitter#setEndSize(float)
+ */
+ public float getEndSize() {
+ return endSize;
+ }
+
+ /**
+ * Set the end size of the particles spawned.
+ *
+ * <p>The
+ * particle size at any time is determined by blending the start size
+ * and end size based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param endSize the end size of the particles spawned.
+ */
+ public void setEndSize(float endSize) {
+ this.endSize = endSize;
+ }
+
+ /**
+ * Get the gravity vector.
+ *
+ * @return the gravity vector.
+ *
+ * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
+ */
+ public Vector3f getGravity() {
+ return gravity;
+ }
+
+ /**
+ * This method sets the gravity vector.
+ *
+ * @param gravity the gravity vector
+ */
+ public void setGravity(Vector3f gravity) {
+ this.gravity.set(gravity);
+ }
+
+ /**
+ * Sets the gravity vector.
+ *
+ * @param x the x component of the gravity vector
+ * @param y the y component of the gravity vector
+ * @param z the z component of the gravity vector
+ */
+ public void setGravity(float x, float y, float z) {
+ this.gravity.x = x;
+ this.gravity.y = y;
+ this.gravity.z = z;
+ }
+
+ /**
+ * Get the high value of life.
+ *
+ * @return the high value of life.
+ *
+ * @see ParticleEmitter#setHighLife(float)
+ */
+ public float getHighLife() {
+ return highLife;
+ }
+
+ /**
+ * Set the high value of life.
+ *
+ * <p>The particle's lifetime/expiration
+ * is determined by randomly selecting a time between low life and high life.
+ *
+ * @param highLife the high value of life.
+ */
+ public void setHighLife(float highLife) {
+ this.highLife = highLife;
+ }
+
+ /**
+ * Get the number of images along the X axis (width).
+ *
+ * @return the number of images along the X axis (width).
+ *
+ * @see ParticleEmitter#setImagesX(int)
+ */
+ public int getImagesX() {
+ return imagesX;
+ }
+
+ /**
+ * Set the number of images along the X axis (width).
+ *
+ * <p>To determine
+ * how multiple particle images are selected and used, see the
+ * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+ *
+ * @param imagesX the number of images along the X axis (width).
+ */
+ public void setImagesX(int imagesX) {
+ this.imagesX = imagesX;
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ }
+
+ /**
+ * Get the number of images along the Y axis (height).
+ *
+ * @return the number of images along the Y axis (height).
+ *
+ * @see ParticleEmitter#setImagesY(int)
+ */
+ public int getImagesY() {
+ return imagesY;
+ }
+
+ /**
+ * Set the number of images along the Y axis (height).
+ *
+ * <p>To determine how multiple particle images are selected and used, see the
+ * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+ *
+ * @param imagesY the number of images along the Y axis (height).
+ */
+ public void setImagesY(int imagesY) {
+ this.imagesY = imagesY;
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ }
+
+ /**
+ * Get the low value of life.
+ *
+ * @return the low value of life.
+ *
+ * @see ParticleEmitter#setLowLife(float)
+ */
+ public float getLowLife() {
+ return lowLife;
+ }
+
+ /**
+ * Set the low value of life.
+ *
+ * <p>The particle's lifetime/expiration
+ * is determined by randomly selecting a time between low life and high life.
+ *
+ * @param lowLife the low value of life.
+ */
+ public void setLowLife(float lowLife) {
+ this.lowLife = lowLife;
+ }
+
+ /**
+ * Get the number of particles to spawn per
+ * second.
+ *
+ * @return the number of particles to spawn per
+ * second.
+ *
+ * @see ParticleEmitter#setParticlesPerSec(float)
+ */
+ public float getParticlesPerSec() {
+ return particlesPerSec;
+ }
+
+ /**
+ * Set the number of particles to spawn per
+ * second.
+ *
+ * @param particlesPerSec the number of particles to spawn per
+ * second.
+ */
+ public void setParticlesPerSec(float particlesPerSec) {
+ this.particlesPerSec = particlesPerSec;
+ }
+
+ /**
+ * Get the start color of the particles spawned.
+ *
+ * @return the start color of the particles spawned.
+ *
+ * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
+ */
+ public ColorRGBA getStartColor() {
+ return startColor;
+ }
+
+ /**
+ * Set the start color of the particles spawned.
+ *
+ * <p>The particle color at any time is determined by blending the start color
+ * and end color based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param startColor the start color of the particles spawned
+ */
+ public void setStartColor(ColorRGBA startColor) {
+ this.startColor.set(startColor);
+ }
+
+ /**
+ * Get the start color of the particles spawned.
+ *
+ * @return the start color of the particles spawned.
+ *
+ * @see ParticleEmitter#setStartSize(float)
+ */
+ public float getStartSize() {
+ return startSize;
+ }
+
+ /**
+ * Set the start size of the particles spawned.
+ *
+ * <p>The particle size at any time is determined by blending the start size
+ * and end size based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param startSize the start size of the particles spawned.
+ */
+ public void setStartSize(float startSize) {
+ this.startSize = startSize;
+ }
+
+ /**
+ * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
+ */
+ @Deprecated
+ public Vector3f getInitialVelocity() {
+ return particleInfluencer.getInitialVelocity();
+ }
+
+ /**
+ * @param initialVelocity Set the initial velocity a particle is spawned with,
+ * the initial velocity given in the parameter will be varied according
+ * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
+ * A particle will move toward its velocity unless it is effected by the
+ * gravity.
+ *
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
+ *
+ * @see ParticleEmitter#setVelocityVariation(float)
+ * @see ParticleEmitter#setGravity(float)
+ */
+ @Deprecated
+ public void setInitialVelocity(Vector3f initialVelocity) {
+ this.particleInfluencer.setInitialVelocity(initialVelocity);
+ }
+
+ /**
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
+ * @return the initial velocity variation factor
+ */
+ @Deprecated
+ public float getVelocityVariation() {
+ return particleInfluencer.getVelocityVariation();
+ }
+
+ /**
+ * @param variation Set the variation by which the initial velocity
+ * of the particle is determined. <code>variation</code> should be a value
+ * from 0 to 1, where 0 means particles are to spawn with exactly
+ * the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
+ * and 1 means particles are to spawn with a completely random velocity.
+ *
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
+ */
+ @Deprecated
+ public void setVelocityVariation(float variation) {
+ this.particleInfluencer.setVelocityVariation(variation);
+ }
+
+ private Particle emitParticle(Vector3f min, Vector3f max) {
+ int idx = lastUsed + 1;
+ if (idx >= particles.length) {
+ return null;
+ }
+
+ Particle p = particles[idx];
+ if (selectRandomImage) {
+ p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1);
+ }
+
+ p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife);
+ p.life = p.startlife;
+ p.color.set(startColor);
+ p.size = startSize;
+ //shape.getRandomPoint(p.position);
+ particleInfluencer.influenceParticle(p, shape);
+ if (worldSpace) {
+ worldTransform.transformVector(p.position, p.position);
+ worldTransform.getRotation().mult(p.velocity, p.velocity);
+ // TODO: Make scale relevant somehow??
+ }
+ if (randomAngle) {
+ p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI;
+ }
+ if (rotateSpeed != 0) {
+ p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
+ }
+
+ temp.set(p.position).addLocal(p.size, p.size, p.size);
+ max.maxLocal(temp);
+ temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+ min.minLocal(temp);
+
+ ++lastUsed;
+ firstUnUsed = idx + 1;
+ return p;
+ }
+
+ /**
+ * Instantly emits all the particles possible to be emitted. Any particles
+ * which are currently inactive will be spawned immediately.
+ */
+ public void emitAllParticles() {
+ // Force world transform to update
+ this.getWorldTransform();
+
+ TempVars vars = TempVars.get();
+
+ BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+
+ Vector3f min = vars.vect1;
+ Vector3f max = vars.vect2;
+
+ bbox.getMin(min);
+ bbox.getMax(max);
+
+ if (!Vector3f.isValidVector(min)) {
+ min.set(Vector3f.POSITIVE_INFINITY);
+ }
+ if (!Vector3f.isValidVector(max)) {
+ max.set(Vector3f.NEGATIVE_INFINITY);
+ }
+
+ while (emitParticle(min, max) != null);
+
+ bbox.setMinMax(min, max);
+ this.setBoundRefresh();
+
+ vars.release();
+ }
+
+ /**
+ * Instantly kills all active particles, after this method is called, all
+ * particles will be dead and no longer visible.
+ */
+ public void killAllParticles() {
+ for (int i = 0; i < particles.length; ++i) {
+ if (particles[i].life > 0) {
+ this.freeParticle(i);
+ }
+ }
+ }
+
+ /**
+ * Kills the particle at the given index.
+ *
+ * @param index The index of the particle to kill
+ * @see #getParticles()
+ */
+ public void killParticle(int index){
+ freeParticle(index);
+ }
+
+ private void freeParticle(int idx) {
+ Particle p = particles[idx];
+ p.life = 0;
+ p.size = 0f;
+ p.color.set(0, 0, 0, 0);
+ p.imageIndex = 0;
+ p.angle = 0;
+ p.rotateSpeed = 0;
+
+ if (idx == lastUsed) {
+ while (lastUsed >= 0 && particles[lastUsed].life == 0) {
+ lastUsed--;
+ }
+ }
+ if (idx < firstUnUsed) {
+ firstUnUsed = idx;
+ }
+ }
+
+ private void swap(int idx1, int idx2) {
+ Particle p1 = particles[idx1];
+ particles[idx1] = particles[idx2];
+ particles[idx2] = p1;
+ }
+
+ private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){
+ // applying gravity
+ p.velocity.x -= gravity.x * tpf;
+ p.velocity.y -= gravity.y * tpf;
+ p.velocity.z -= gravity.z * tpf;
+ temp.set(p.velocity).multLocal(tpf);
+ p.position.addLocal(temp);
+
+ // affecting color, size and angle
+ float b = (p.startlife - p.life) / p.startlife;
+ p.color.interpolate(startColor, endColor, b);
+ p.size = FastMath.interpolateLinear(b, startSize, endSize);
+ p.angle += p.rotateSpeed * tpf;
+
+ // Computing bounding volume
+ temp.set(p.position).addLocal(p.size, p.size, p.size);
+ max.maxLocal(temp);
+ temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+ min.minLocal(temp);
+
+ if (!selectRandomImage) {
+ p.imageIndex = (int) (b * imagesX * imagesY);
+ }
+ }
+
+ private void updateParticleState(float tpf) {
+ // Force world transform to update
+ this.getWorldTransform();
+
+ TempVars vars = TempVars.get();
+
+ Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY);
+
+ for (int i = 0; i < particles.length; ++i) {
+ Particle p = particles[i];
+ if (p.life == 0) { // particle is dead
+// assert i <= firstUnUsed;
+ continue;
+ }
+
+ p.life -= tpf;
+ if (p.life <= 0) {
+ this.freeParticle(i);
+ continue;
+ }
+
+ updateParticle(p, tpf, min, max);
+
+ if (firstUnUsed < i) {
+ this.swap(firstUnUsed, i);
+ if (i == lastUsed) {
+ lastUsed = firstUnUsed;
+ }
+ firstUnUsed++;
+ }
+ }
+
+ // Spawns particles within the tpf timeslot with proper age
+ float interval = 1f / particlesPerSec;
+ tpf += timeDifference;
+ while (tpf > interval){
+ tpf -= interval;
+ Particle p = emitParticle(min, max);
+ if (p != null){
+ p.life -= tpf;
+ if (p.life <= 0){
+ freeParticle(lastUsed);
+ }else{
+ updateParticle(p, tpf, min, max);
+ }
+ }
+ }
+ timeDifference = tpf;
+
+ BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+ bbox.setMinMax(min, max);
+ this.setBoundRefresh();
+
+ vars.release();
+ }
+
+ /**
+ * Set to enable or disable the particle emitter
+ *
+ * <p>When a particle is
+ * disabled, it will be "frozen in time" and not update.
+ *
+ * @param enabled True to enable the particle emitter
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Check if a particle emitter is enabled for update.
+ *
+ * @return True if a particle emitter is enabled for update.
+ *
+ * @see ParticleEmitter#setEnabled(boolean)
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Callback from Control.update(), do not use.
+ * @param tpf
+ */
+ public void updateFromControl(float tpf) {
+ if (enabled) {
+ this.updateParticleState(tpf);
+ }
+ }
+
+ /**
+ * Callback from Control.render(), do not use.
+ *
+ * @param rm
+ * @param vp
+ */
+ private void renderFromControl(RenderManager rm, ViewPort vp) {
+ Camera cam = vp.getCamera();
+
+ if (meshType == ParticleMesh.Type.Point) {
+ float C = cam.getProjectionMatrix().m00;
+ C *= cam.getWidth() * 0.5f;
+
+ // send attenuation params
+ this.getMaterial().setFloat("Quadratic", C);
+ }
+
+ Matrix3f inverseRotation = Matrix3f.IDENTITY;
+ TempVars vars = null;
+ if (!worldSpace) {
+ vars = TempVars.get();
+
+ inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
+ }
+ particleMesh.updateParticleData(particles, cam, inverseRotation);
+ if (!worldSpace) {
+ vars.release();
+ }
+ }
+
+ public void preload(RenderManager rm, ViewPort vp) {
+ this.updateParticleState(0);
+ particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(shape, "shape", DEFAULT_SHAPE);
+ oc.write(meshType, "meshType", ParticleMesh.Type.Triangle);
+ oc.write(enabled, "enabled", true);
+ oc.write(particles.length, "numParticles", 0);
+ oc.write(particlesPerSec, "particlesPerSec", 0);
+ oc.write(lowLife, "lowLife", 0);
+ oc.write(highLife, "highLife", 0);
+ oc.write(gravity, "gravity", null);
+ oc.write(imagesX, "imagesX", 1);
+ oc.write(imagesY, "imagesY", 1);
+
+ oc.write(startColor, "startColor", null);
+ oc.write(endColor, "endColor", null);
+ oc.write(startSize, "startSize", 0);
+ oc.write(endSize, "endSize", 0);
+ oc.write(worldSpace, "worldSpace", false);
+ oc.write(facingVelocity, "facingVelocity", false);
+ oc.write(faceNormal, "faceNormal", new Vector3f(Vector3f.NAN));
+ oc.write(selectRandomImage, "selectRandomImage", false);
+ oc.write(randomAngle, "randomAngle", false);
+ oc.write(rotateSpeed, "rotateSpeed", 0);
+
+ oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE);
+
+ if (shape == DEFAULT_SHAPE) {
+ // Prevent reference to static
+ shape = shape.deepClone();
+ }
+
+ meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle);
+ int numParticles = ic.readInt("numParticles", 0);
+
+
+ enabled = ic.readBoolean("enabled", true);
+ particlesPerSec = ic.readFloat("particlesPerSec", 0);
+ lowLife = ic.readFloat("lowLife", 0);
+ highLife = ic.readFloat("highLife", 0);
+ gravity = (Vector3f) ic.readSavable("gravity", null);
+ imagesX = ic.readInt("imagesX", 1);
+ imagesY = ic.readInt("imagesY", 1);
+
+ startColor = (ColorRGBA) ic.readSavable("startColor", null);
+ endColor = (ColorRGBA) ic.readSavable("endColor", null);
+ startSize = ic.readFloat("startSize", 0);
+ endSize = ic.readFloat("endSize", 0);
+ worldSpace = ic.readBoolean("worldSpace", false);
+ this.setIgnoreTransform(worldSpace);
+ facingVelocity = ic.readBoolean("facingVelocity", false);
+ faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN));
+ selectRandomImage = ic.readBoolean("selectRandomImage", false);
+ randomAngle = ic.readBoolean("randomAngle", false);
+ rotateSpeed = ic.readFloat("rotateSpeed", 0);
+
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(numParticles);
+// particleMesh.initParticleData(this, particles.length);
+// particleMesh.setImagesXY(imagesX, imagesY);
+
+ particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
+ if (particleInfluencer == DEFAULT_INFLUENCER) {
+ particleInfluencer = particleInfluencer.clone();
+ }
+
+ if (im.getFormatVersion() == 0) {
+ // compatibility before the control inside particle emitter
+ // was changed:
+ // find it in the controls and take it out, then add the proper one in
+ for (int i = 0; i < controls.size(); i++) {
+ Object obj = controls.get(i);
+ if (obj instanceof ParticleEmitter) {
+ controls.remove(i);
+ // now add the proper one in
+ controls.add(new ParticleEmitterControl(this));
+ break;
+ }
+ }
+
+ // compatability before gravity was not a vector but a float
+ if (gravity == null) {
+ gravity = new Vector3f();
+ gravity.y = ic.readFloat("gravity", 0);
+ }
+ } else {
+ // since the parentEmitter is not loaded, it must be
+ // loaded separately
+ control = getControl(ParticleEmitterControl.class);
+ control.parentEmitter = this;
+
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleMesh.java b/engine/src/core/com/jme3/effect/ParticleMesh.java
new file mode 100644
index 0000000..adace45
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleMesh.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.effect;
+
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Mesh;
+
+/**
+ * The <code>ParticleMesh</code> is the underlying visual implementation of a
+ * {@link ParticleEmitter particle emitter}.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class ParticleMesh extends Mesh {
+
+ /**
+ * Type of particle mesh
+ */
+ public enum Type {
+ /**
+ * The particle mesh is composed of points. Each particle is a point.
+ * This can be used in conjuction with {@link RenderState#setPointSprite(boolean) point sprites}
+ * to render particles the usual way.
+ */
+ Point,
+
+ /**
+ * The particle mesh is composed of triangles. Each particle is
+ * two triangles making a single quad.
+ */
+ Triangle;
+ }
+
+ /**
+ * Initialize mesh data.
+ *
+ * @param emitter The emitter which will use this <code>ParticleMesh</code>.
+ * @param numParticles The maxmimum number of particles to simulate
+ */
+ public abstract void initParticleData(ParticleEmitter emitter, int numParticles);
+
+ /**
+ * Set the images on the X and Y coordinates
+ * @param imagesX Images on the X coordinate
+ * @param imagesY Images on the Y coordinate
+ */
+ public abstract void setImagesXY(int imagesX, int imagesY);
+
+ /**
+ * Update the particle visual data. Typically called every frame.
+ */
+ public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation);
+
+}
diff --git a/engine/src/core/com/jme3/effect/ParticlePointMesh.java b/engine/src/core/com/jme3/effect/ParticlePointMesh.java
new file mode 100644
index 0000000..28db3e6
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticlePointMesh.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.effect;
+
+import com.jme3.math.Matrix3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+public class ParticlePointMesh extends ParticleMesh {
+
+ private ParticleEmitter emitter;
+
+ private int imagesX = 1;
+ private int imagesY = 1;
+
+ @Override
+ public void setImagesXY(int imagesX, int imagesY) {
+ this.imagesX = imagesX;
+ this.imagesY = imagesY;
+ }
+
+ @Override
+ public void initParticleData(ParticleEmitter emitter, int numParticles) {
+ setMode(Mode.Points);
+
+ this.emitter = emitter;
+
+ // set positions
+ FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles);
+ VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
+ pvb.setupData(Usage.Stream, 3, Format.Float, pb);
+
+ //if the buffer is already set only update the data
+ VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
+ if (buf != null) {
+ buf.updateData(pb);
+ } else {
+ setBuffer(pvb);
+ }
+
+ // set colors
+ ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4);
+ VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
+ cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
+ cvb.setNormalized(true);
+
+ buf = getBuffer(VertexBuffer.Type.Color);
+ if (buf != null) {
+ buf.updateData(cb);
+ } else {
+ setBuffer(cvb);
+ }
+
+ // set sizes
+ FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles);
+ VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size);
+ svb.setupData(Usage.Stream, 1, Format.Float, sb);
+
+ buf = getBuffer(VertexBuffer.Type.Size);
+ if (buf != null) {
+ buf.updateData(sb);
+ } else {
+ setBuffer(svb);
+ }
+
+ // set UV-scale
+ FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4);
+ VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
+ tvb.setupData(Usage.Stream, 4, Format.Float, tb);
+
+ buf = getBuffer(VertexBuffer.Type.TexCoord);
+ if (buf != null) {
+ buf.updateData(tb);
+ } else {
+ setBuffer(tvb);
+ }
+ }
+
+ @Override
+ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
+ VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
+ FloatBuffer positions = (FloatBuffer) pvb.getData();
+
+ VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
+ ByteBuffer colors = (ByteBuffer) cvb.getData();
+
+ VertexBuffer svb = getBuffer(VertexBuffer.Type.Size);
+ FloatBuffer sizes = (FloatBuffer) svb.getData();
+
+ VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
+ FloatBuffer texcoords = (FloatBuffer) tvb.getData();
+
+ float sizeScale = emitter.getWorldScale().x;
+
+ // update data in vertex buffers
+ positions.rewind();
+ colors.rewind();
+ sizes.rewind();
+ texcoords.rewind();
+ for (int i = 0; i < particles.length; i++){
+ Particle p = particles[i];
+
+ positions.put(p.position.x)
+ .put(p.position.y)
+ .put(p.position.z);
+
+ sizes.put(p.size * sizeScale);
+ colors.putInt(p.color.asIntABGR());
+
+ int imgX = p.imageIndex % imagesX;
+ int imgY = (p.imageIndex - imgX) / imagesY;
+
+ float startX = ((float) imgX) / imagesX;
+ float startY = ((float) imgY) / imagesY;
+ float endX = startX + (1f / imagesX);
+ float endY = startY + (1f / imagesY);
+
+ texcoords.put(startX).put(startY).put(endX).put(endY);
+ }
+ positions.flip();
+ colors.flip();
+ sizes.flip();
+ texcoords.flip();
+
+ // force renderer to re-send data to GPU
+ pvb.updateData(positions);
+ cvb.updateData(colors);
+ svb.updateData(sizes);
+ tvb.updateData(texcoords);
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/ParticleTriMesh.java b/engine/src/core/com/jme3/effect/ParticleTriMesh.java
new file mode 100644
index 0000000..8d27838
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleTriMesh.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.effect;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.SortUtil;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class ParticleTriMesh extends ParticleMesh {
+
+ private int imagesX = 1;
+ private int imagesY = 1;
+ private boolean uniqueTexCoords = false;
+ private ParticleComparator comparator = new ParticleComparator();
+ private ParticleEmitter emitter;
+ private Particle[] particlesCopy;
+
+ @Override
+ public void initParticleData(ParticleEmitter emitter, int numParticles) {
+ setMode(Mode.Triangles);
+
+ this.emitter = emitter;
+
+ particlesCopy = new Particle[numParticles];
+
+ // set positions
+ FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles * 4);
+ VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position);
+ pvb.setupData(Usage.Stream, 3, Format.Float, pb);
+
+ //if the buffer is already set only update the data
+ VertexBuffer buf = getBuffer(VertexBuffer.Type.Position);
+ if (buf != null) {
+ buf.updateData(pb);
+ } else {
+ setBuffer(pvb);
+ }
+
+ // set colors
+ ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4 * 4);
+ VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color);
+ cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb);
+ cvb.setNormalized(true);
+
+ buf = getBuffer(VertexBuffer.Type.Color);
+ if (buf != null) {
+ buf.updateData(cb);
+ } else {
+ setBuffer(cvb);
+ }
+
+ // set texcoords
+ VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord);
+ FloatBuffer tb = BufferUtils.createVector2Buffer(numParticles * 4);
+
+ uniqueTexCoords = false;
+ for (int i = 0; i < numParticles; i++){
+ tb.put(0f).put(1f);
+ tb.put(1f).put(1f);
+ tb.put(0f).put(0f);
+ tb.put(1f).put(0f);
+ }
+ tb.flip();
+ tvb.setupData(Usage.Static, 2, Format.Float, tb);
+
+ buf = getBuffer(VertexBuffer.Type.TexCoord);
+ if (buf != null) {
+ buf.updateData(tb);
+ } else {
+ setBuffer(tvb);
+ }
+
+ // set indices
+ ShortBuffer ib = BufferUtils.createShortBuffer(numParticles * 6);
+ for (int i = 0; i < numParticles; i++){
+ int startIdx = (i * 4);
+
+ // triangle 1
+ ib.put((short)(startIdx + 1))
+ .put((short)(startIdx + 0))
+ .put((short)(startIdx + 2));
+
+ // triangle 2
+ ib.put((short)(startIdx + 1))
+ .put((short)(startIdx + 2))
+ .put((short)(startIdx + 3));
+ }
+ ib.flip();
+
+ VertexBuffer ivb = new VertexBuffer(VertexBuffer.Type.Index);
+ ivb.setupData(Usage.Static, 3, Format.UnsignedShort, ib);
+
+ buf = getBuffer(VertexBuffer.Type.Index);
+ if (buf != null) {
+ buf.updateData(ib);
+ } else {
+ setBuffer(ivb);
+ }
+
+ }
+
+ @Override
+ public void setImagesXY(int imagesX, int imagesY) {
+ this.imagesX = imagesX;
+ this.imagesY = imagesY;
+ if (imagesX != 1 || imagesY != 1){
+ uniqueTexCoords = true;
+ getBuffer(VertexBuffer.Type.TexCoord).setUsage(Usage.Stream);
+ }
+ }
+
+ @Override
+ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) {
+ System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length);
+ comparator.setCamera(cam);
+// Arrays.sort(particlesCopy, comparator);
+// SortUtil.qsort(particlesCopy, comparator);
+ SortUtil.msort(particles, particlesCopy, comparator);
+ particles = particlesCopy;
+
+ VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position);
+ FloatBuffer positions = (FloatBuffer) pvb.getData();
+
+ VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color);
+ ByteBuffer colors = (ByteBuffer) cvb.getData();
+
+ VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord);
+ FloatBuffer texcoords = (FloatBuffer) tvb.getData();
+
+ Vector3f camUp = cam.getUp();
+ Vector3f camLeft = cam.getLeft();
+ Vector3f camDir = cam.getDirection();
+
+ inverseRotation.multLocal(camUp);
+ inverseRotation.multLocal(camLeft);
+ inverseRotation.multLocal(camDir);
+
+ boolean facingVelocity = emitter.isFacingVelocity();
+
+ Vector3f up = new Vector3f(),
+ left = new Vector3f();
+
+ if (!facingVelocity){
+ up.set(camUp);
+ left.set(camLeft);
+ }
+
+ // update data in vertex buffers
+ positions.clear();
+ colors.clear();
+ texcoords.clear();
+ Vector3f faceNormal = emitter.getFaceNormal();
+
+ for (int i = 0; i < particles.length; i++){
+ Particle p = particles[i];
+ boolean dead = p.life == 0;
+ if (dead){
+ positions.put(0).put(0).put(0);
+ positions.put(0).put(0).put(0);
+ positions.put(0).put(0).put(0);
+ positions.put(0).put(0).put(0);
+ continue;
+ }
+
+ if (facingVelocity){
+ left.set(p.velocity).normalizeLocal();
+ camDir.cross(left, up);
+ up.multLocal(p.size);
+ left.multLocal(p.size);
+ }else if (faceNormal != null){
+ up.set(faceNormal).crossLocal(Vector3f.UNIT_X);
+ faceNormal.cross(up, left);
+ up.multLocal(p.size);
+ left.multLocal(p.size);
+ }else if (p.angle != 0){
+ float cos = FastMath.cos(p.angle) * p.size;
+ float sin = FastMath.sin(p.angle) * p.size;
+
+ left.x = camLeft.x * cos + camUp.x * sin;
+ left.y = camLeft.y * cos + camUp.y * sin;
+ left.z = camLeft.z * cos + camUp.z * sin;
+
+ up.x = camLeft.x * -sin + camUp.x * cos;
+ up.y = camLeft.y * -sin + camUp.y * cos;
+ up.z = camLeft.z * -sin + camUp.z * cos;
+ }else{
+ up.set(camUp);
+ left.set(camLeft);
+ up.multLocal(p.size);
+ left.multLocal(p.size);
+ }
+
+ positions.put(p.position.x + left.x + up.x)
+ .put(p.position.y + left.y + up.y)
+ .put(p.position.z + left.z + up.z);
+
+ positions.put(p.position.x - left.x + up.x)
+ .put(p.position.y - left.y + up.y)
+ .put(p.position.z - left.z + up.z);
+
+ positions.put(p.position.x + left.x - up.x)
+ .put(p.position.y + left.y - up.y)
+ .put(p.position.z + left.z - up.z);
+
+ positions.put(p.position.x - left.x - up.x)
+ .put(p.position.y - left.y - up.y)
+ .put(p.position.z - left.z - up.z);
+
+ if (uniqueTexCoords){
+ int imgX = p.imageIndex % imagesX;
+ int imgY = (p.imageIndex - imgX) / imagesY;
+
+ float startX = ((float) imgX) / imagesX;
+ float startY = ((float) imgY) / imagesY;
+ float endX = startX + (1f / imagesX);
+ float endY = startY + (1f / imagesY);
+
+ texcoords.put(startX).put(endY);
+ texcoords.put(endX).put(endY);
+ texcoords.put(startX).put(startY);
+ texcoords.put(endX).put(startY);
+ }
+
+ int abgr = p.color.asIntABGR();
+ colors.putInt(abgr);
+ colors.putInt(abgr);
+ colors.putInt(abgr);
+ colors.putInt(abgr);
+ }
+
+ positions.clear();
+ colors.clear();
+ if (!uniqueTexCoords)
+ texcoords.clear();
+ else{
+ texcoords.clear();
+ tvb.updateData(texcoords);
+ }
+
+ // force renderer to re-send data to GPU
+ pvb.updateData(positions);
+ cvb.updateData(colors);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java
new file mode 100644
index 0000000..80f52d9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java
@@ -0,0 +1,92 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * This emitter influences the particles so that they move all in the same direction.
+ * The direction may vary a little if the velocity variation is non zero.
+ * This influencer is default for the particle emitter.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class DefaultParticleInfluencer implements ParticleInfluencer {
+
+ /** Temporary variable used to help with calculations. */
+ protected transient Vector3f temp = new Vector3f();
+ /** The initial velocity of the particles. */
+ protected Vector3f startVelocity = new Vector3f();
+ /** The velocity's variation of the particles. */
+ protected float velocityVariation = 0.2f;
+
+ @Override
+ public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+ emitterShape.getRandomPoint(particle.position);
+ this.applyVelocityVariation(particle);
+ }
+
+ /**
+ * This method applies the variation to the particle with already set velocity.
+ * @param particle
+ * the particle to be affected
+ */
+ protected void applyVelocityVariation(Particle particle) {
+ particle.velocity.set(startVelocity);
+ temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat());
+ temp.multLocal(2f);
+ temp.subtractLocal(1f, 1f, 1f);
+ temp.multLocal(startVelocity.length());
+ particle.velocity.interpolate(temp, velocityVariation);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(startVelocity, "startVelocity", Vector3f.ZERO);
+ oc.write(velocityVariation, "variation", 0.2f);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ startVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone());
+ velocityVariation = ic.readFloat("variation", 0.2f);
+ }
+
+ @Override
+ public ParticleInfluencer clone() {
+ try {
+ DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone();
+ clone.startVelocity = startVelocity.clone();
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void setInitialVelocity(Vector3f initialVelocity) {
+ this.startVelocity.set(initialVelocity);
+ }
+
+ @Override
+ public Vector3f getInitialVelocity() {
+ return startVelocity;
+ }
+
+ @Override
+ public void setVelocityVariation(float variation) {
+ this.velocityVariation = variation;
+ }
+
+ @Override
+ public float getVelocityVariation() {
+ return velocityVariation;
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java
new file mode 100644
index 0000000..013a5db
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java
@@ -0,0 +1,55 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+/**
+ * This influencer does not influence particle at all.
+ * It makes particles not to move.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmptyParticleInfluencer implements ParticleInfluencer {
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ }
+
+ @Override
+ public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+ }
+
+ @Override
+ public void setInitialVelocity(Vector3f initialVelocity) {
+ }
+
+ @Override
+ public Vector3f getInitialVelocity() {
+ return null;
+ }
+
+ @Override
+ public void setVelocityVariation(float variation) {
+ }
+
+ @Override
+ public float getVelocityVariation() {
+ return 0;
+ }
+
+ @Override
+ public ParticleInfluencer clone() {
+ try {
+ return (ParticleInfluencer) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
new file mode 100644
index 0000000..a2701be
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
@@ -0,0 +1,142 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import java.io.IOException;
+
+/**
+ * This influencer calculates initial velocity with the use of the emitter's shape.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class NewtonianParticleInfluencer extends DefaultParticleInfluencer {
+
+ /** Normal to emitter's shape factor. */
+ protected float normalVelocity;
+ /** Emitter's surface tangent factor. */
+ protected float surfaceTangentFactor;
+ /** Emitters tangent rotation factor. */
+ protected float surfaceTangentRotation;
+
+ /**
+ * Constructor. Sets velocity variation to 0.0f.
+ */
+ public NewtonianParticleInfluencer() {
+ this.velocityVariation = 0.0f;
+ }
+
+ @Override
+ public void influenceParticle(Particle particle, EmitterShape emitterShape) {
+ emitterShape.getRandomPointAndNormal(particle.position, particle.velocity);
+ // influencing the particle's velocity
+ if (surfaceTangentFactor == 0.0f) {
+ particle.velocity.multLocal(normalVelocity);
+ } else {
+ // calculating surface tangent (velocity contains the 'normal' value)
+ temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor);
+ if (surfaceTangentRotation != 0.0f) {// rotating the tangent
+ Matrix3f m = new Matrix3f();
+ m.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity);
+ temp = m.multLocal(temp);
+ }
+ // applying normal factor (this must be done first)
+ particle.velocity.multLocal(normalVelocity);
+ // adding tangent vector
+ particle.velocity.addLocal(temp);
+ }
+ if (velocityVariation != 0.0f) {
+ this.applyVelocityVariation(particle);
+ }
+ }
+
+ /**
+ * This method returns the normal velocity factor.
+ * @return the normal velocity factor
+ */
+ public float getNormalVelocity() {
+ return normalVelocity;
+ }
+
+ /**
+ * This method sets the normal velocity factor.
+ * @param normalVelocity
+ * the normal velocity factor
+ */
+ public void setNormalVelocity(float normalVelocity) {
+ this.normalVelocity = normalVelocity;
+ }
+
+ /**
+ * This method sets the surface tangent factor.
+ * @param surfaceTangentFactor
+ * the surface tangent factor
+ */
+ public void setSurfaceTangentFactor(float surfaceTangentFactor) {
+ this.surfaceTangentFactor = surfaceTangentFactor;
+ }
+
+ /**
+ * This method returns the surface tangent factor.
+ * @return the surface tangent factor
+ */
+ public float getSurfaceTangentFactor() {
+ return surfaceTangentFactor;
+ }
+
+ /**
+ * This method sets the surface tangent rotation factor.
+ * @param surfaceTangentRotation
+ * the surface tangent rotation factor
+ */
+ public void setSurfaceTangentRotation(float surfaceTangentRotation) {
+ this.surfaceTangentRotation = surfaceTangentRotation;
+ }
+
+ /**
+ * This method returns the surface tangent rotation factor.
+ * @return the surface tangent rotation factor
+ */
+ public float getSurfaceTangentRotation() {
+ return surfaceTangentRotation;
+ }
+
+ @Override
+ protected void applyVelocityVariation(Particle particle) {
+ temp.set(FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation);
+ particle.velocity.addLocal(temp);
+ }
+
+ @Override
+ public ParticleInfluencer clone() {
+ NewtonianParticleInfluencer result = new NewtonianParticleInfluencer();
+ result.normalVelocity = normalVelocity;
+ result.startVelocity = startVelocity;
+ result.velocityVariation = velocityVariation;
+ result.surfaceTangentFactor = surfaceTangentFactor;
+ result.surfaceTangentRotation = surfaceTangentRotation;
+ return result;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(normalVelocity, "normalVelocity", 0.0f);
+ oc.write(surfaceTangentFactor, "surfaceTangentFactor", 0.0f);
+ oc.write(surfaceTangentRotation, "surfaceTangentRotation", 0.0f);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ normalVelocity = ic.readFloat("normalVelocity", 0.0f);
+ surfaceTangentFactor = ic.readFloat("surfaceTangentFactor", 0.0f);
+ surfaceTangentRotation = ic.readFloat("surfaceTangentRotation", 0.0f);
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java
new file mode 100644
index 0000000..56c8cf9
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java
@@ -0,0 +1,61 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.Particle;
+import com.jme3.effect.ParticleEmitter;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ * An interface that defines the methods to affect initial velocity of the particles.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public interface ParticleInfluencer extends Savable, Cloneable {
+
+ /**
+ * This method influences the particle.
+ * @param particle
+ * particle to be influenced
+ * @param emitterShape
+ * the shape of it emitter
+ */
+ void influenceParticle(Particle particle, EmitterShape emitterShape);
+
+ /**
+ * This method clones the influencer instance.
+ * @return cloned instance
+ */
+ public ParticleInfluencer clone();
+
+ /**
+ * @param initialVelocity
+ * Set the initial velocity a particle is spawned with,
+ * the initial velocity given in the parameter will be varied according
+ * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
+ * A particle will move toward its velocity unless it is effected by the
+ * gravity.
+ */
+ void setInitialVelocity(Vector3f initialVelocity);
+
+ /**
+ * This method returns the initial velocity.
+ * @return the initial velocity
+ */
+ Vector3f getInitialVelocity();
+
+ /**
+ * @param variation
+ * Set the variation by which the initial velocity
+ * of the particle is determined. <code>variation</code> should be a value
+ * from 0 to 1, where 0 means particles are to spawn with exactly
+ * the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
+ * and 1 means particles are to spawn with a completely random velocity.
+ */
+ void setVelocityVariation(float variation);
+
+ /**
+ * This method returns the velocity variation.
+ * @return the velocity variation
+ */
+ float getVelocityVariation();
+}
diff --git a/engine/src/core/com/jme3/effect/package.html b/engine/src/core/com/jme3/effect/package.html
new file mode 100644
index 0000000..dd16da7
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.effect</code> package allows particle emitter effects to be
+used with a jME3 application. <br/>
+<p>
+ The <code>ParticleEmitter</code> class is the primary class used to create
+ particle emitter effects. See the <code>jme3test.effect</code> package
+ for examples on how to use <code>ParticleEmitter</code>s.
+
+</body>
+</html>
+
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
new file mode 100644
index 0000000..9838dd2
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterBoxShape implements EmitterShape {
+
+ private Vector3f min, len;
+
+ public EmitterBoxShape() {
+ }
+
+ public EmitterBoxShape(Vector3f min, Vector3f max) {
+ if (min == null || max == null) {
+ throw new NullPointerException();
+ }
+
+ this.min = min;
+ this.len = new Vector3f();
+ this.len.set(max).subtractLocal(min);
+ }
+
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ store.x = min.x + len.x * FastMath.nextRandomFloat();
+ store.y = min.y + len.y * FastMath.nextRandomFloat();
+ store.z = min.z + len.z * FastMath.nextRandomFloat();
+ }
+
+ /**
+ * This method fills the point with data.
+ * It does not fill the normal.
+ * @param store the variable to store the point data
+ * @param normal not used in this class
+ */
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ this.getRandomPoint(store);
+ }
+
+ @Override
+ public EmitterShape deepClone() {
+ try {
+ EmitterBoxShape clone = (EmitterBoxShape) super.clone();
+ clone.min = min.clone();
+ clone.len = len.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ public Vector3f getMin() {
+ return min;
+ }
+
+ public void setMin(Vector3f min) {
+ this.min = min;
+ }
+
+ public Vector3f getLen() {
+ return len;
+ }
+
+ public void setLen(Vector3f len) {
+ this.len = len;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(min, "min", null);
+ oc.write(len, "length", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ min = (Vector3f) ic.readSavable("min", null);
+ len = (Vector3f) ic.readSavable("length", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java
new file mode 100644
index 0000000..1c5d687
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java
@@ -0,0 +1,63 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.util.List;
+
+/**
+ * This emiter shape emits the particles from the given shape's interior constrained by its convex hull
+ * (a geometry that tightly wraps the mesh). So in case of multiple meshes some vertices may appear
+ * in a space between them.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshConvexHullShape extends EmitterMeshFaceShape {
+
+ /**
+ * Empty constructor. Sets nothing.
+ */
+ public EmitterMeshConvexHullShape() {
+ }
+
+ /**
+ * Constructor. It stores a copy of vertex list of all meshes.
+ * @param meshes
+ * a list of meshes that will form the emitter's shape
+ */
+ public EmitterMeshConvexHullShape(List<Mesh> meshes) {
+ super(meshes);
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected point inside a convex hull
+ * of randomly selected mesh.
+ * @param store
+ * the variable to store with coordinates of randomly selected selected point inside a convex hull
+ * of randomly selected mesh
+ */
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ super.getRandomPoint(store);
+ // now move the point from the meshe's face towards the center of the mesh
+ // the center is in (0, 0, 0) in the local coordinates
+ store.multLocal(FastMath.nextRandomFloat());
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected point inside a convex hull
+ * of randomly selected mesh.
+ * The normal param is not used.
+ * @param store
+ * the variable to store with coordinates of randomly selected selected point inside a convex hull
+ * of randomly selected mesh
+ * @param normal
+ * not used in this class
+ */
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ super.getRandomPointAndNormal(store, normal);
+ // now move the point from the meshe's face towards the center of the mesh
+ // the center is in (0, 0, 0) in the local coordinates
+ store.multLocal(FastMath.nextRandomFloat());
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java
new file mode 100644
index 0000000..023ca5b
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java
@@ -0,0 +1,97 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This emiter shape emits the particles from the given shape's faces.
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshFaceShape extends EmitterMeshVertexShape {
+
+ /**
+ * Empty constructor. Sets nothing.
+ */
+ public EmitterMeshFaceShape() {
+ }
+
+ /**
+ * Constructor. It stores a copy of vertex list of all meshes.
+ * @param meshes
+ * a list of meshes that will form the emitter's shape
+ */
+ public EmitterMeshFaceShape(List<Mesh> meshes) {
+ super(meshes);
+ }
+
+ @Override
+ public void setMeshes(List<Mesh> meshes) {
+ this.vertices = new ArrayList<List<Vector3f>>(meshes.size());
+ this.normals = new ArrayList<List<Vector3f>>(meshes.size());
+ for (Mesh mesh : meshes) {
+ Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position));
+ int[] indices = new int[3];
+ List<Vector3f> vertices = new ArrayList<Vector3f>(mesh.getTriangleCount() * 3);
+ List<Vector3f> normals = new ArrayList<Vector3f>(mesh.getTriangleCount());
+ for (int i = 0; i < mesh.getTriangleCount(); ++i) {
+ mesh.getTriangle(i, indices);
+ vertices.add(vertexTable[indices[0]]);
+ vertices.add(vertexTable[indices[1]]);
+ vertices.add(vertexTable[indices[2]]);
+ normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]]));
+ }
+ this.vertices.add(vertices);
+ this.normals.add(normals);
+ }
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected point on a random face.
+ * @param store
+ * the variable to store with coordinates of randomly selected selected point on a random face
+ */
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+ // the index of the first vertex of a face (must be dividable by 3)
+ int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3;
+ // put the point somewhere between the first and the second vertex of a face
+ float moveFactor = FastMath.nextRandomFloat();
+ store.set(Vector3f.ZERO);
+ store.addLocal(vertices.get(meshIndex).get(vertIndex));
+ store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
+ // move the result towards the last face vertex
+ moveFactor = FastMath.nextRandomFloat();
+ store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected point on a random face.
+ * The normal param is filled with selected face's normal.
+ * @param store
+ * the variable to store with coordinates of randomly selected selected point on a random face
+ * @param normal
+ * filled with selected face's normal
+ */
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+ // the index of the first vertex of a face (must be dividable by 3)
+ int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1);
+ int vertIndex = faceIndex * 3;
+ // put the point somewhere between the first and the second vertex of a face
+ float moveFactor = FastMath.nextRandomFloat();
+ store.set(Vector3f.ZERO);
+ store.addLocal(vertices.get(meshIndex).get(vertIndex));
+ store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor);
+ // move the result towards the last face vertex
+ moveFactor = FastMath.nextRandomFloat();
+ store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor);
+ normal.set(normals.get(meshIndex).get(faceIndex));
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
new file mode 100644
index 0000000..28ee8b4
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
@@ -0,0 +1,158 @@
+package com.jme3.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * This emiter shape emits the particles from the given shape's vertices
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class EmitterMeshVertexShape implements EmitterShape {
+
+ protected List<List<Vector3f>> vertices;
+ protected List<List<Vector3f>> normals;
+
+ /**
+ * Empty constructor. Sets nothing.
+ */
+ public EmitterMeshVertexShape() {
+ }
+
+ /**
+ * Constructor. It stores a copy of vertex list of all meshes.
+ * @param meshes
+ * a list of meshes that will form the emitter's shape
+ */
+ public EmitterMeshVertexShape(List<Mesh> meshes) {
+ this.setMeshes(meshes);
+ }
+
+ /**
+ * This method sets the meshes that will form the emiter's shape.
+ * @param meshes
+ * a list of meshes that will form the emitter's shape
+ */
+ public void setMeshes(List<Mesh> meshes) {
+ Map<Vector3f, Vector3f> vertToNormalMap = new HashMap<Vector3f, Vector3f>();
+
+ this.vertices = new ArrayList<List<Vector3f>>(meshes.size());
+ this.normals = new ArrayList<List<Vector3f>>(meshes.size());
+ for (Mesh mesh : meshes) {
+ // fetching the data
+ float[] vertexTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position));
+ float[] normalTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal));
+
+ // unifying normals
+ for (int i = 0; i < vertexTable.length; i += 3) {// the tables should have the same size and be dividable by 3
+ Vector3f vert = new Vector3f(vertexTable[i], vertexTable[i + 1], vertexTable[i + 2]);
+ Vector3f norm = vertToNormalMap.get(vert);
+ if (norm == null) {
+ norm = new Vector3f(normalTable[i], normalTable[i + 1], normalTable[i + 2]);
+ vertToNormalMap.put(vert, norm);
+ } else {
+ norm.addLocal(normalTable[i], normalTable[i + 1], normalTable[i + 2]);
+ }
+ }
+
+ // adding data to vertices and normals
+ List<Vector3f> vertices = new ArrayList<Vector3f>(vertToNormalMap.size());
+ List<Vector3f> normals = new ArrayList<Vector3f>(vertToNormalMap.size());
+ for (Entry<Vector3f, Vector3f> entry : vertToNormalMap.entrySet()) {
+ vertices.add(entry.getKey());
+ normals.add(entry.getValue().normalizeLocal());
+ }
+ this.vertices.add(vertices);
+ this.normals.add(normals);
+ }
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected mesh vertex.
+ * @param store
+ * the variable to store with coordinates of randomly selected mesh vertex
+ */
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+ int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1);
+ store.set(vertices.get(meshIndex).get(vertIndex));
+ }
+
+ /**
+ * This method fills the point with coordinates of randomly selected mesh vertex.
+ * The normal param is filled with selected vertex's normal.
+ * @param store
+ * the variable to store with coordinates of randomly selected mesh vertex
+ * @param normal
+ * filled with selected vertex's normal
+ */
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1);
+ int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1);
+ store.set(vertices.get(meshIndex).get(vertIndex));
+ normal.set(normals.get(meshIndex).get(vertIndex));
+ }
+
+ @Override
+ public EmitterShape deepClone() {
+ try {
+ EmitterMeshVertexShape clone = (EmitterMeshVertexShape) super.clone();
+ if (this.vertices != null) {
+ clone.vertices = new ArrayList<List<Vector3f>>(vertices.size());
+ for (List<Vector3f> list : vertices) {
+ List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size());
+ for (Vector3f vector : list) {
+ vectorList.add(vector.clone());
+ }
+ clone.vertices.add(vectorList);
+ }
+ }
+ if (this.normals != null) {
+ clone.normals = new ArrayList<List<Vector3f>>(normals.size());
+ for (List<Vector3f> list : normals) {
+ List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size());
+ for (Vector3f vector : list) {
+ vectorList.add(vector.clone());
+ }
+ clone.normals.add(vectorList);
+ }
+ }
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.writeSavableArrayList((ArrayList<List<Vector3f>>) vertices, "vertices", null);
+ oc.writeSavableArrayList((ArrayList<List<Vector3f>>) normals, "normals", null);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ this.vertices = ic.readSavableArrayList("vertices", null);
+
+ List<List<Vector3f>> tmpNormals = ic.readSavableArrayList("normals", null);
+ if (tmpNormals != null){
+ this.normals = tmpNormals;
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java
new file mode 100644
index 0000000..f8ba70e
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect.shapes;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterPointShape implements EmitterShape {
+
+ private Vector3f point;
+
+ public EmitterPointShape() {
+ }
+
+ public EmitterPointShape(Vector3f point) {
+ this.point = point;
+ }
+
+ @Override
+ public EmitterShape deepClone() {
+ try {
+ EmitterPointShape clone = (EmitterPointShape) super.clone();
+ clone.point = point.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ store.set(point);
+ }
+
+ /**
+ * This method fills the point with data.
+ * It does not fill the normal.
+ * @param store the variable to store the point data
+ * @param normal not used in this class
+ */
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ store.set(point);
+ }
+
+ public Vector3f getPoint() {
+ return point;
+ }
+
+ public void setPoint(Vector3f point) {
+ this.point = point;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(point, "point", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ this.point = (Vector3f) im.getCapsule(this).readSavable("point", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java
new file mode 100644
index 0000000..c23c19d
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect.shapes;
+
+import com.jme3.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ * This interface declares methods used by all shapes that represent particle emitters.
+ * @author Kirill
+ */
+public interface EmitterShape extends Savable, Cloneable {
+
+ /**
+ * This method fills in the initial position of the particle.
+ * @param store
+ * store variable for initial position
+ */
+ public void getRandomPoint(Vector3f store);
+
+ /**
+ * This method fills in the initial position of the particle and its normal vector.
+ * @param store
+ * store variable for initial position
+ * @param normal
+ * store variable for initial normal
+ */
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal);
+
+ /**
+ * This method creates a deep clone of the current instance of the emitter shape.
+ * @return deep clone of the current instance of the emitter shape
+ */
+ public EmitterShape deepClone();
+}
diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
new file mode 100644
index 0000000..642b279
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect.shapes;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+
+public class EmitterSphereShape implements EmitterShape {
+
+ private Vector3f center;
+ private float radius;
+
+ public EmitterSphereShape() {
+ }
+
+ public EmitterSphereShape(Vector3f center, float radius) {
+ if (center == null) {
+ throw new NullPointerException();
+ }
+
+ if (radius <= 0) {
+ throw new IllegalArgumentException("Radius must be greater than 0");
+ }
+
+ this.center = center;
+ this.radius = radius;
+ }
+
+ @Override
+ public EmitterShape deepClone() {
+ try {
+ EmitterSphereShape clone = (EmitterSphereShape) super.clone();
+ clone.center = center.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ public void getRandomPoint(Vector3f store) {
+ do {
+ store.x = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+ store.y = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+ store.z = (FastMath.nextRandomFloat() * 2f - 1f) * radius;
+ } while (store.distance(center) > radius);
+ }
+
+ @Override
+ public void getRandomPointAndNormal(Vector3f store, Vector3f normal) {
+ this.getRandomPoint(store);
+ }
+
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ public void setCenter(Vector3f center) {
+ this.center = center;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public void setRadius(float radius) {
+ this.radius = radius;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(center, "center", null);
+ oc.write(radius, "radius", 0);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ center = (Vector3f) ic.readSavable("center", null);
+ radius = ic.readFloat("radius", 0);
+ }
+}
diff --git a/engine/src/core/com/jme3/export/FormatVersion.java b/engine/src/core/com/jme3/export/FormatVersion.java
new file mode 100644
index 0000000..843248d
--- /dev/null
+++ b/engine/src/core/com/jme3/export/FormatVersion.java
@@ -0,0 +1,22 @@
+package com.jme3.export;
+
+/**
+ * Specifies the version of the format for jME3 object (j3o) files.
+ *
+ * @author Kirill Vainer
+ */
+public final class FormatVersion {
+
+ /**
+ * Version number of the format
+ */
+ public static final int VERSION = 2;
+
+ /**
+ * Signature of the format. Currently "JME3" as ASCII
+ */
+ public static final int SIGNATURE = 0x4A4D4533;
+
+ private FormatVersion(){
+ }
+}
diff --git a/engine/src/core/com/jme3/export/InputCapsule.java b/engine/src/core/com/jme3/export/InputCapsule.java
new file mode 100644
index 0000000..c7c7e16
--- /dev/null
+++ b/engine/src/core/com/jme3/export/InputCapsule.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+import com.jme3.util.IntMap;
+import java.io.IOException;
+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.Map;
+
+/**
+ * @author Joshua Slack
+ */
+public interface InputCapsule {
+
+ public int getSavableVersion(Class<? extends Savable> clazz);
+
+ // byte primitive
+
+ public byte readByte(String name, byte defVal) throws IOException;
+ public byte[] readByteArray(String name, byte[] defVal) throws IOException;
+ public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException;
+
+ // int primitive
+
+ public int readInt(String name, int defVal) throws IOException;
+ public int[] readIntArray(String name, int[] defVal) throws IOException;
+ public int[][] readIntArray2D(String name, int[][] defVal) throws IOException;
+
+
+ // float primitive
+
+ public float readFloat(String name, float defVal) throws IOException;
+ public float[] readFloatArray(String name, float[] defVal) throws IOException;
+ public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException;
+
+
+ // double primitive
+
+ public double readDouble(String name, double defVal) throws IOException;
+ public double[] readDoubleArray(String name, double[] defVal) throws IOException;
+ public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException;
+
+
+ // long primitive
+
+ public long readLong(String name, long defVal) throws IOException;
+ public long[] readLongArray(String name, long[] defVal) throws IOException;
+ public long[][] readLongArray2D(String name, long[][] defVal) throws IOException;
+
+
+ // short primitive
+
+ public short readShort(String name, short defVal) throws IOException;
+ public short[] readShortArray(String name, short[] defVal) throws IOException;
+ public short[][] readShortArray2D(String name, short[][] defVal) throws IOException;
+
+
+ // boolean primitive
+
+ public boolean readBoolean(String name, boolean defVal) throws IOException;
+ public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException;
+ public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException;
+
+
+ // String
+
+ public String readString(String name, String defVal) throws IOException;
+ public String[] readStringArray(String name, String[] defVal) throws IOException;
+ public String[][] readStringArray2D(String name, String[][] defVal) throws IOException;
+
+
+ // BitSet
+
+ public BitSet readBitSet(String name, BitSet defVal) throws IOException;
+
+
+ // BinarySavable
+
+ public Savable readSavable(String name, Savable defVal) throws IOException;
+ public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException;
+ public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException;
+
+
+ // ArrayLists
+
+ public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException;
+ public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException;
+ public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException;
+
+ public ArrayList<FloatBuffer> readFloatBufferArrayList(String name, ArrayList<FloatBuffer> defVal) throws IOException;
+ public ArrayList<ByteBuffer> readByteBufferArrayList(String name, ArrayList<ByteBuffer> defVal) throws IOException;
+
+
+ // Maps
+
+ public Map<? extends Savable, ? extends Savable> readSavableMap(String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException;
+ public Map<String, ? extends Savable> readStringSavableMap(String name, Map<String, ? extends Savable> defVal) throws IOException;
+ public IntMap<? extends Savable> readIntSavableMap(String name, IntMap<? extends Savable> defVal) throws IOException;
+
+ // NIO BUFFERS
+ // float buffer
+
+ public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException;
+
+
+ // int buffer
+
+ public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException;
+
+
+ // byte buffer
+
+ public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException;
+
+
+ // short buffer
+
+ public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException;
+
+
+ // enums
+
+ public <T extends Enum<T>> T readEnum(String name, Class<T> enumType, T defVal) throws IOException;
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/export/JmeExporter.java b/engine/src/core/com/jme3/export/JmeExporter.java
new file mode 100644
index 0000000..956dfd8
--- /dev/null
+++ b/engine/src/core/com/jme3/export/JmeExporter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * <code>JmeExporter</code> specifies an export implementation for jME3
+ * data.
+ */
+public interface JmeExporter {
+
+ /**
+ * Export the {@link Savable} to an OutputStream.
+ *
+ * @param object The savable to export
+ * @param f The output stream
+ * @return Always returns true. If an error occurs during export,
+ * an exception is thrown
+ * @throws IOException If an io exception occurs during export
+ */
+ public boolean save(Savable object, OutputStream f) throws IOException;
+
+ /**
+ * Export the {@link Savable} to a file.
+ *
+ * @param object The savable to export
+ * @param f The file to export to
+ * @return Always returns true. If an error occurs during export,
+ * an exception is thrown
+ * @throws IOException If an io exception occurs during export
+ */
+ public boolean save(Savable object, File f) throws IOException;
+
+ /**
+ * Returns the {@link OutputCapsule} for the given savable object.
+ *
+ * @param object The object to retrieve an output capsule for.
+ * @return
+ */
+ public OutputCapsule getCapsule(Savable object);
+}
diff --git a/engine/src/core/com/jme3/export/JmeImporter.java b/engine/src/core/com/jme3/export/JmeImporter.java
new file mode 100644
index 0000000..4986c85
--- /dev/null
+++ b/engine/src/core/com/jme3/export/JmeImporter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+import com.jme3.asset.AssetLoader;
+import com.jme3.asset.AssetManager;
+
+public interface JmeImporter extends AssetLoader {
+ public InputCapsule getCapsule(Savable id);
+ public AssetManager getAssetManager();
+
+ /**
+ * Returns the version number written in the header of the J3O/XML
+ * file.
+ *
+ * @return Global version number for the file
+ */
+ public int getFormatVersion();
+}
diff --git a/engine/src/core/com/jme3/export/OutputCapsule.java b/engine/src/core/com/jme3/export/OutputCapsule.java
new file mode 100644
index 0000000..fecda9b
--- /dev/null
+++ b/engine/src/core/com/jme3/export/OutputCapsule.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+import com.jme3.util.IntMap;
+import java.io.IOException;
+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.Map;
+
+/**
+ * @author Joshua Slack
+ */
+public interface OutputCapsule {
+
+ // byte primitive
+
+ public void write(byte value, String name, byte defVal) throws IOException;
+ public void write(byte[] value, String name, byte[] defVal) throws IOException;
+ public void write(byte[][] value, String name, byte[][] defVal) throws IOException;
+
+
+ // int primitive
+
+ public void write(int value, String name, int defVal) throws IOException;
+ public void write(int[] value, String name, int[] defVal) throws IOException;
+ public void write(int[][] value, String name, int[][] defVal) throws IOException;
+
+
+ // float primitive
+
+ public void write(float value, String name, float defVal) throws IOException;
+ public void write(float[] value, String name, float[] defVal) throws IOException;
+ public void write(float[][] value, String name, float[][] defVal) throws IOException;
+
+
+ // double primitive
+
+ public void write(double value, String name, double defVal) throws IOException;
+ public void write(double[] value, String name, double[] defVal) throws IOException;
+ public void write(double[][] value, String name, double[][] defVal) throws IOException;
+
+
+ // long primitive
+
+ public void write(long value, String name, long defVal) throws IOException;
+ public void write(long[] value, String name, long[] defVal) throws IOException;
+ public void write(long[][] value, String name, long[][] defVal) throws IOException;
+
+
+ // short primitive
+
+ public void write(short value, String name, short defVal) throws IOException;
+ public void write(short[] value, String name, short[] defVal) throws IOException;
+ public void write(short[][] value, String name, short[][] defVal) throws IOException;
+
+
+ // boolean primitive
+
+ public void write(boolean value, String name, boolean defVal) throws IOException;
+ public void write(boolean[] value, String name, boolean[] defVal) throws IOException;
+ public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException;
+
+
+ // String
+
+ public void write(String value, String name, String defVal) throws IOException;
+ public void write(String[] value, String name, String[] defVal) throws IOException;
+ public void write(String[][] value, String name, String[][] defVal) throws IOException;
+
+
+ // BitSet
+
+ public void write(BitSet value, String name, BitSet defVal) throws IOException;
+
+
+ // BinarySavable
+
+ public void write(Savable object, String name, Savable defVal) throws IOException;
+ public void write(Savable[] objects, String name, Savable[] defVal) throws IOException;
+ public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException;
+
+
+ // ArrayLists
+
+ public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException;
+ public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException;
+ public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException;
+
+ public void writeFloatBufferArrayList(ArrayList<FloatBuffer> array, String name, ArrayList<FloatBuffer> defVal) throws IOException;
+ public void writeByteBufferArrayList(ArrayList<ByteBuffer> array, String name, ArrayList<ByteBuffer> defVal) throws IOException;
+
+
+ // Maps
+
+ public void writeSavableMap(Map<? extends Savable, ? extends Savable> map, String name, Map<? extends Savable, ? extends Savable> defVal) throws IOException;
+ public void writeStringSavableMap(Map<String, ? extends Savable> map, String name, Map<String, ? extends Savable> defVal) throws IOException;
+ public void writeIntSavableMap(IntMap<? extends Savable> map, String name, IntMap<? extends Savable> defVal) throws IOException;
+
+ // NIO BUFFERS
+ // float buffer
+
+ public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException;
+
+
+ // int buffer
+
+ public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException;
+
+
+ // byte buffer
+
+ public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException;
+
+
+ // short buffer
+
+ public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException;
+
+
+ // enums
+
+ public void write(Enum value, String name, Enum defVal) throws IOException;
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/export/ReadListener.java b/engine/src/core/com/jme3/export/ReadListener.java
new file mode 100644
index 0000000..bb87b3a
--- /dev/null
+++ b/engine/src/core/com/jme3/export/ReadListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+public interface ReadListener {
+
+ public void readBytes(int bytes);
+
+}
diff --git a/engine/src/core/com/jme3/export/Savable.java b/engine/src/core/com/jme3/export/Savable.java
new file mode 100644
index 0000000..0e90e30
--- /dev/null
+++ b/engine/src/core/com/jme3/export/Savable.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.export;
+
+import java.io.IOException;
+
+/**
+ * <code>Savable</code> is an interface for objects that can be serialized
+ * using jME's serialization system.
+ *
+ * @author Kirill Vainer
+ */
+public interface Savable {
+ void write(JmeExporter ex) throws IOException;
+ void read(JmeImporter im) throws IOException;
+}
diff --git a/engine/src/core/com/jme3/export/SavableClassUtil.java b/engine/src/core/com/jme3/export/SavableClassUtil.java
new file mode 100644
index 0000000..cda328a
--- /dev/null
+++ b/engine/src/core/com/jme3/export/SavableClassUtil.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.export;
+
+import com.jme3.animation.Animation;
+import com.jme3.effect.shapes.*;
+import com.jme3.material.MatParamTexture;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>SavableClassUtil</code> contains various utilities to handle
+ * Savable classes. The methods are general enough to not be specific to any
+ * particular implementation.
+ * Currently it will remap any classes from old paths to new paths
+ * so that old J3O models can still be loaded.
+ *
+ * @author mpowell
+ * @author Kirill Vainer
+ */
+public class SavableClassUtil {
+
+ private final static HashMap<String, String> classRemappings = new HashMap<String, String>();
+
+ private static void addRemapping(String oldClass, Class<? extends Savable> newClass){
+ classRemappings.put(oldClass, newClass.getName());
+ }
+
+ static {
+ addRemapping("com.jme3.effect.EmitterSphereShape", EmitterSphereShape.class);
+ addRemapping("com.jme3.effect.EmitterBoxShape", EmitterBoxShape.class);
+ addRemapping("com.jme3.effect.EmitterMeshConvexHullShape", EmitterMeshConvexHullShape.class);
+ addRemapping("com.jme3.effect.EmitterMeshFaceShape", EmitterMeshFaceShape.class);
+ addRemapping("com.jme3.effect.EmitterMeshVertexShape", EmitterMeshVertexShape.class);
+ addRemapping("com.jme3.effect.EmitterPointShape", EmitterPointShape.class);
+ addRemapping("com.jme3.material.Material$MatParamTexture", MatParamTexture.class);
+ addRemapping("com.jme3.animation.BoneAnimation", Animation.class);
+ addRemapping("com.jme3.animation.SpatialAnimation", Animation.class);
+ }
+
+ private static String remapClass(String className) throws ClassNotFoundException {
+ String result = classRemappings.get(className);
+ if (result == null) {
+ return className;
+ } else {
+ return result;
+ }
+ }
+
+ public static boolean isImplementingSavable(Class clazz){
+ boolean result = Savable.class.isAssignableFrom(clazz);
+ return result;
+ }
+
+ public static int[] getSavableVersions(Class<? extends Savable> clazz) throws IOException{
+ ArrayList<Integer> versionList = new ArrayList<Integer>();
+ Class superclass = clazz;
+ do {
+ versionList.add(getSavableVersion(superclass));
+ superclass = superclass.getSuperclass();
+ } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass));
+
+ int[] versions = new int[versionList.size()];
+ for (int i = 0; i < versionList.size(); i++){
+ versions[i] = versionList.get(i);
+ }
+ return versions;
+ }
+
+ public static int getSavableVersion(Class<? extends Savable> clazz) throws IOException{
+ try {
+ Field field = clazz.getField("SAVABLE_VERSION");
+ Class<? extends Savable> declaringClass = (Class<? extends Savable>) field.getDeclaringClass();
+ if (declaringClass == clazz){
+ return field.getInt(null);
+ }else{
+ return 0; // This class doesn't declare this field, e.g. version == 0
+ }
+ } catch (IllegalAccessException ex) {
+ IOException ioEx = new IOException();
+ ioEx.initCause(ex);
+ throw ioEx;
+ } catch (IllegalArgumentException ex) {
+ throw ex; // can happen if SAVABLE_VERSION is not static
+ } catch (NoSuchFieldException ex) {
+ return 0; // not using versions
+ }
+ }
+
+ public static int getSavedSavableVersion(Object savable, Class<? extends Savable> desiredClass, int[] versions, int formatVersion){
+ Class thisClass = savable.getClass();
+ int count = 0;
+
+ while (thisClass != desiredClass) {
+ thisClass = thisClass.getSuperclass();
+ if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)){
+ count ++;
+ }else{
+ break;
+ }
+ }
+
+ if (thisClass == null){
+ throw new IllegalArgumentException(savable.getClass().getName() +
+ " does not extend " +
+ desiredClass.getName() + "!");
+ }else if (count >= versions.length){
+ if (formatVersion <= 1){
+ return 0; // for buggy versions of j3o
+ }else{
+ throw new IllegalArgumentException(savable.getClass().getName() +
+ " cannot access version of " +
+ desiredClass.getName() +
+ " because it doesn't implement Savable");
+ }
+ }
+ return versions[count];
+ }
+
+ /**
+ * fromName creates a new Savable from the provided class name. First registered modules
+ * are checked to handle special cases, if the modules do not handle the class name, the
+ * class is instantiated directly.
+ * @param className the class name to create.
+ * @param inputCapsule the InputCapsule that will be used for loading the Savable (to look up ctor parameters)
+ * @return the Savable instance of the class.
+ * @throws InstantiationException thrown if the class does not have an empty constructor.
+ * @throws IllegalAccessException thrown if the class is not accessable.
+ * @throws ClassNotFoundException thrown if the class name is not in the classpath.
+ * @throws IOException when loading ctor parameters fails
+ */
+ public static Savable fromName(String className) throws InstantiationException,
+ IllegalAccessException, ClassNotFoundException, IOException {
+
+ className = remapClass(className);
+ try {
+ return (Savable) Class.forName(className).newInstance();
+ } catch (InstantiationException e) {
+ Logger.getLogger(SavableClassUtil.class.getName()).log(
+ Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n"
+ + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className);
+ throw e;
+ } catch (IllegalAccessException e) {
+ Logger.getLogger(SavableClassUtil.class.getName()).log(
+ Level.SEVERE, "{0} \n"
+ + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage());
+ throw e;
+ }
+ }
+
+ public static Savable fromName(String className, List<ClassLoader> loaders) throws InstantiationException,
+ IllegalAccessException, ClassNotFoundException, IOException {
+ if (loaders == null) {
+ return fromName(className);
+ }
+
+ String newClassName = remapClass(className);
+ synchronized(loaders) {
+ for (ClassLoader classLoader : loaders){
+ try {
+ return (Savable) classLoader.loadClass(newClassName).newInstance();
+ } catch (InstantiationException e) {
+ } catch (IllegalAccessException e) {
+ }
+
+ }
+ }
+
+ return fromName(className);
+ }
+}
diff --git a/engine/src/core/com/jme3/font/BitmapCharacter.java b/engine/src/core/com/jme3/font/BitmapCharacter.java
new file mode 100644
index 0000000..8e06b87
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapCharacter.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+import com.jme3.export.*;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.io.IOException;
+
+/**
+ * Represents a single bitmap character.
+ */
+public class BitmapCharacter implements Savable, Cloneable {
+ private char c;
+ private int x;
+ private int y;
+ private int width;
+ private int height;
+ private int xOffset;
+ private int yOffset;
+ private int xAdvance;
+ private IntMap<Integer> kerning = new IntMap<Integer>();
+ private int page;
+
+ public BitmapCharacter() {}
+
+ public BitmapCharacter(char c) {
+ this.c = c;
+ }
+
+ @Override
+ public BitmapCharacter clone() {
+ try {
+ BitmapCharacter result = (BitmapCharacter) super.clone();
+ result.kerning = kerning.clone();
+ return result;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public void setX(int x) {
+ this.x = x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public void setY(int y) {
+ this.y = y;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getXOffset() {
+ return xOffset;
+ }
+
+ public void setXOffset(int offset) {
+ xOffset = offset;
+ }
+
+ public int getYOffset() {
+ return yOffset;
+ }
+
+ public void setYOffset(int offset) {
+ yOffset = offset;
+ }
+
+ public int getXAdvance() {
+ return xAdvance;
+ }
+
+ public void setXAdvance(int advance) {
+ xAdvance = advance;
+ }
+
+ public void setPage(int page) {
+ this.page = page;
+ }
+
+ public int getPage() {
+ return page;
+ }
+
+ public char getChar() {
+ return c;
+ }
+
+ public void setChar(char c) {
+ this.c = c;
+ }
+
+ public void addKerning(int second, int amount){
+ kerning.put(second, amount);
+ }
+
+ public int getKerning(int second){
+ Integer i = kerning.get(second);
+ if (i == null)
+ return 0;
+ else
+ return i.intValue();
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(c, "c", 0);
+ oc.write(x, "x", 0);
+ oc.write(y, "y", 0);
+ oc.write(width, "width", 0);
+ oc.write(height, "height", 0);
+ oc.write(xOffset, "xOffset", 0);
+ oc.write(yOffset, "yOffset", 0);
+ oc.write(xAdvance, "xAdvance", 0);
+
+ int[] seconds = new int[kerning.size()];
+ int[] amounts = new int[seconds.length];
+
+ int i = 0;
+ for (Entry<Integer> entry : kerning){
+ seconds[i] = entry.getKey();
+ amounts[i] = entry.getValue();
+ i++;
+ }
+
+ oc.write(seconds, "seconds", null);
+ oc.write(amounts, "amounts", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ c = (char) ic.readInt("c", 0);
+ x = ic.readInt("x", 0);
+ y = ic.readInt("y", 0);
+ width = ic.readInt("width", 0);
+ height = ic.readInt("height", 0);
+ xOffset = ic.readInt("xOffset", 0);
+ yOffset = ic.readInt("yOffset", 0);
+ xAdvance = ic.readInt("xAdvance", 0);
+
+ int[] seconds = ic.readIntArray("seconds", null);
+ int[] amounts = ic.readIntArray("amounts", null);
+
+ for (int i = 0; i < seconds.length; i++){
+ kerning.put(seconds[i], amounts[i]);
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapCharacterSet.java b/engine/src/core/com/jme3/font/BitmapCharacterSet.java
new file mode 100644
index 0000000..be51554
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapCharacterSet.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+import com.jme3.export.*;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.io.IOException;
+
+public class BitmapCharacterSet implements Savable {
+
+ private int lineHeight;
+ private int base;
+ private int renderedSize;
+ private int width;
+ private int height;
+ private IntMap<IntMap<BitmapCharacter>> characters;
+ private int pageSize;
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(lineHeight, "lineHeight", 0);
+ oc.write(base, "base", 0);
+ oc.write(renderedSize, "renderedSize", 0);
+ oc.write(width, "width", 0);
+ oc.write(height, "height", 0);
+ oc.write(pageSize, "pageSize", 0);
+
+ int[] styles = new int[characters.size()];
+ int index = 0;
+ for (Entry<IntMap<BitmapCharacter>> entry : characters) {
+ int style = entry.getKey();
+ styles[index] = style;
+ index++;
+ IntMap<BitmapCharacter> charset = entry.getValue();
+ writeCharset(oc, style, charset);
+ }
+ oc.write(styles, "styles", null);
+ }
+
+ protected void writeCharset(OutputCapsule oc, int style, IntMap<BitmapCharacter> charset) throws IOException {
+ int size = charset.size();
+ short[] indexes = new short[size];
+ BitmapCharacter[] chars = new BitmapCharacter[size];
+ int i = 0;
+ for (Entry<BitmapCharacter> chr : charset){
+ indexes[i] = (short) chr.getKey();
+ chars[i] = chr.getValue();
+ i++;
+ }
+
+ oc.write(indexes, "indexes"+style, null);
+ oc.write(chars, "chars"+style, null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ lineHeight = ic.readInt("lineHeight", 0);
+ base = ic.readInt("base", 0);
+ renderedSize = ic.readInt("renderedSize", 0);
+ width = ic.readInt("width", 0);
+ height = ic.readInt("height", 0);
+ pageSize = ic.readInt("pageSize", 0);
+ int[] styles = ic.readIntArray("styles", null);
+
+ for (int style : styles) {
+ characters.put(style, readCharset(ic, style));
+ }
+ }
+
+ private IntMap<BitmapCharacter> readCharset(InputCapsule ic, int style) throws IOException {
+ IntMap<BitmapCharacter> charset = new IntMap<BitmapCharacter>();
+ short[] indexes = ic.readShortArray("indexes"+style, null);
+ Savable[] chars = ic.readSavableArray("chars"+style, null);
+
+ for (int i = 0; i < indexes.length; i++){
+ int index = indexes[i] & 0xFFFF;
+ BitmapCharacter chr = (BitmapCharacter) chars[i];
+ charset.put(index, chr);
+ }
+ return charset;
+ }
+
+ public BitmapCharacterSet() {
+ characters = new IntMap<IntMap<BitmapCharacter>>();
+ }
+
+ public BitmapCharacter getCharacter(int index){
+ return getCharacter(index, 0);
+ }
+
+ public BitmapCharacter getCharacter(int index, int style){
+ IntMap<BitmapCharacter> map = getCharacterSet(style);
+ return map.get(index);
+ }
+
+ private IntMap<BitmapCharacter> getCharacterSet(int style) {
+ if (characters.size() == 0) {
+ characters.put(style, new IntMap<BitmapCharacter>());
+ }
+ return characters.get(style);
+ }
+
+ public void addCharacter(int index, BitmapCharacter ch){
+ getCharacterSet(0).put(index, ch);
+ }
+
+ public int getLineHeight() {
+ return lineHeight;
+ }
+
+ public void setLineHeight(int lineHeight) {
+ this.lineHeight = lineHeight;
+ }
+
+ public int getBase() {
+ return base;
+ }
+
+ public void setBase(int base) {
+ this.base = base;
+ }
+
+ public int getRenderedSize() {
+ return renderedSize;
+ }
+
+ public void setRenderedSize(int renderedSize) {
+ this.renderedSize = renderedSize;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ /**
+ * Merge two fonts.
+ * If two font have the same style, merge will fail.
+ * @param styleSet Style must be assigned to this.
+ * @author Yonghoon
+ */
+ public void merge(BitmapCharacterSet styleSet) {
+ if (this.renderedSize != styleSet.renderedSize) {
+ throw new RuntimeException("Only support same font size");
+ }
+ for (Entry<IntMap<BitmapCharacter>> entry : styleSet.characters) {
+ int style = entry.getKey();
+ if (style == 0) {
+ throw new RuntimeException("Style must be set first. use setStyle(int)");
+ }
+ IntMap<BitmapCharacter> charset = entry.getValue();
+ this.lineHeight = Math.max(this.lineHeight, styleSet.lineHeight);
+ IntMap<BitmapCharacter> old = this.characters.put(style, charset);
+ if (old != null) {
+ throw new RuntimeException("Can't override old style");
+ }
+
+ for (Entry<BitmapCharacter> charEntry : charset) {
+ BitmapCharacter ch = charEntry.getValue();
+ ch.setPage(ch.getPage() + this.pageSize);
+ }
+ }
+ this.pageSize += styleSet.pageSize;
+ }
+
+ public void setStyle(int style) {
+ if (characters.size() > 1) {
+ throw new RuntimeException("Applicable only for single style font");
+ }
+ Entry<IntMap<BitmapCharacter>> entry = characters.iterator().next();
+ IntMap<BitmapCharacter> charset = entry.getValue();
+ characters.remove(entry.getKey());
+ characters.put(style, charset);
+ }
+
+ void setPageSize(int pageSize) {
+ this.pageSize = pageSize;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapFont.java b/engine/src/core/com/jme3/font/BitmapFont.java
new file mode 100644
index 0000000..ce532e9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapFont.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import java.io.IOException;
+
+/**
+ * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator
+ * @author dhdd
+ */
+public class BitmapFont implements Savable {
+
+ /**
+ * Specifies horizontal alignment for text.
+ *
+ * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align)
+ */
+ public enum Align {
+
+ /**
+ * Align text on the left of the text block
+ */
+ Left,
+
+ /**
+ * Align text in the center of the text block
+ */
+ Center,
+
+ /**
+ * Align text on the right of the text block
+ */
+ Right
+ }
+
+ /**
+ * Specifies vertical alignment for text.
+ *
+ * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign)
+ */
+ public enum VAlign {
+ /**
+ * Align text on the top of the text block
+ */
+ Top,
+
+ /**
+ * Align text in the center of the text block
+ */
+ Center,
+
+ /**
+ * Align text at the bottom of the text block
+ */
+ Bottom
+ }
+
+ private BitmapCharacterSet charSet;
+ private Material[] pages;
+
+ public BitmapFont() {
+ }
+
+ public BitmapText createLabel(String content){
+ BitmapText label = new BitmapText(this);
+ label.setSize(getCharSet().getRenderedSize());
+ label.setText(content);
+ return label;
+ }
+
+ public float getPreferredSize(){
+ return getCharSet().getRenderedSize();
+ }
+
+ public void setCharSet(BitmapCharacterSet charSet) {
+ this.charSet = charSet;
+ }
+
+ public void setPages(Material[] pages) {
+ this.pages = pages;
+ charSet.setPageSize(pages.length);
+ }
+
+ public Material getPage(int index) {
+ return pages[index];
+ }
+
+ public int getPageSize() {
+ return pages.length;
+ }
+
+ public BitmapCharacterSet getCharSet() {
+ return charSet;
+ }
+
+ /**
+ * Gets the line height of a StringBlock.
+ * @param sb
+ * @return
+ */
+ public float getLineHeight(StringBlock sb) {
+ return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize());
+ }
+
+ public float getCharacterAdvance(char curChar, char nextChar, float size){
+ BitmapCharacter c = charSet.getCharacter(curChar);
+ if (c == null)
+ return 0f;
+
+ float advance = size * c.getXAdvance();
+ advance += c.getKerning(nextChar) * size;
+ return advance;
+ }
+
+ private int findKerningAmount(int newLineLastChar, int nextChar) {
+ BitmapCharacter c = charSet.getCharacter(newLineLastChar);
+ if (c == null)
+ return 0;
+ return c.getKerning(nextChar);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(charSet, "charSet", null);
+ oc.write(pages, "pages", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ charSet = (BitmapCharacterSet) ic.readSavable("charSet", null);
+ Savable[] pagesSavable = ic.readSavableArray("pages", null);
+ pages = new Material[pagesSavable.length];
+ System.arraycopy(pagesSavable, 0, pages, 0, pages.length);
+ }
+
+ public float getLineWidth(CharSequence text){
+
+ // This method will probably always be a bit of a maintenance
+ // nightmare since it basis its calculation on a different
+ // routine than the Letters class. The ideal situation would
+ // be to abstract out letter position and size into its own
+ // class that both BitmapFont and Letters could use for
+ // positioning.
+ // If getLineWidth() here ever again returns a different value
+ // than Letters does with the same text then it might be better
+ // just to create a Letters object for the sole purpose of
+ // getting a text size. It's less efficient but at least it
+ // would be accurate.
+
+ // And here I am mucking around in here again...
+ //
+ // A font character has a few values that are pertinent to the
+ // line width:
+ // xOffset
+ // xAdvance
+ // kerningAmount(nextChar)
+ //
+ // The way BitmapText ultimately works is that the first character
+ // starts with xOffset included (ie: it is rendered at -xOffset).
+ // Its xAdvance is wider to accomodate that initial offset.
+ // The cursor position is advanced by xAdvance each time.
+ //
+ // So, a width should be calculated in a similar way. Start with
+ // -xOffset + xAdvance for the first character and then each subsequent
+ // character is just xAdvance more 'width'.
+ //
+ // The kerning amount from one character to the next affects the
+ // cursor position of that next character and thus the ultimate width
+ // and so must be factored in also.
+
+ float lineWidth = 0f;
+ float maxLineWidth = 0f;
+ char lastChar = 0;
+ boolean firstCharOfLine = true;
+// float sizeScale = (float) block.getSize() / charSet.getRenderedSize();
+ float sizeScale = 1f;
+ for (int i = 0; i < text.length(); i++){
+ char theChar = text.charAt(i);
+ if (theChar == '\n'){
+ maxLineWidth = Math.max(maxLineWidth, lineWidth);
+ lineWidth = 0f;
+ firstCharOfLine = true;
+ continue;
+ }
+ BitmapCharacter c = charSet.getCharacter((int) theChar);
+ if (c != null){
+ if (theChar == '\\' && i<text.length()-1 && text.charAt(i+1)=='#'){
+ if (i+5<text.length() && text.charAt(i+5)=='#'){
+ i+=5;
+ continue;
+ }else if (i+8<text.length() && text.charAt(i+8)=='#'){
+ i+=8;
+ continue;
+ }
+ }
+ if (!firstCharOfLine){
+ lineWidth += findKerningAmount(lastChar, theChar) * sizeScale;
+ } else {
+ // The first character needs to add in its xOffset but it
+ // is the only one... and negative offsets = postive width
+ // because we're trying to account for the part that hangs
+ // over the left. So we subtract.
+ lineWidth -= c.getXOffset() * sizeScale;
+ firstCharOfLine = false;
+ }
+ float xAdvance = c.getXAdvance() * sizeScale;
+
+ // If this is the last character, then we really should have
+ // only add its width. The advance may include extra spacing
+ // that we don't care about.
+ if (i == text.length() - 1) {
+ lineWidth += c.getWidth() * sizeScale;
+
+ // Since theh width includes the xOffset then we need
+ // to take it out again by adding it, ie: offset the width
+ // we just added by the appropriate amount.
+ lineWidth += c.getXOffset() * sizeScale;
+ } else {
+ lineWidth += xAdvance;
+ }
+ }
+ }
+ return Math.max(maxLineWidth, lineWidth);
+ }
+
+
+ /**
+ * Merge two fonts.
+ * If two font have the same style, merge will fail.
+ * @param styleSet Style must be assigned to this.
+ * @author Yonghoon
+ */
+ public void merge(BitmapFont newFont) {
+ charSet.merge(newFont.charSet);
+ final int size1 = this.pages.length;
+ final int size2 = newFont.pages.length;
+
+ Material[] tmp = new Material[size1+size2];
+ System.arraycopy(this.pages, 0, tmp, 0, size1);
+ System.arraycopy(newFont.pages, 0, tmp, size1, size2);
+
+ this.pages = tmp;
+
+// this.pages = Arrays.copyOf(this.pages, size1+size2);
+// System.arraycopy(newFont.pages, 0, this.pages, size1, size2);
+ }
+
+ public void setStyle(int style) {
+ charSet.setStyle(style);
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/BitmapText.java b/engine/src/core/com/jme3/font/BitmapText.java
new file mode 100644
index 0000000..7601324
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapText.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.font;
+
+import com.jme3.font.BitmapFont.Align;
+import com.jme3.font.BitmapFont.VAlign;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Node;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author YongHoon
+ */
+public class BitmapText extends Node {
+ private BitmapFont font;
+ private StringBlock block;
+ private boolean needRefresh = true;
+ private final BitmapTextPage[] textPages;
+ private Letters letters;
+
+ public BitmapText(BitmapFont font) {
+ this(font, false, false);
+ }
+
+ public BitmapText(BitmapFont font, boolean rightToLeft) {
+ this(font, rightToLeft, false);
+ }
+
+ public BitmapText(BitmapFont font, boolean rightToLeft, boolean arrayBased) {
+ textPages = new BitmapTextPage[font.getPageSize()];
+ for (int page = 0; page < textPages.length; page++) {
+ textPages[page] = new BitmapTextPage(font, arrayBased, page);
+ attachChild(textPages[page]);
+ }
+
+ this.font = font;
+ this.block = new StringBlock();
+ block.setSize(font.getPreferredSize());
+ letters = new Letters(font, block, rightToLeft);
+ }
+
+ @Override
+ public BitmapText clone() {
+ BitmapText clone = (BitmapText) super.clone();
+ for (int i = 0; i < textPages.length; i++) {
+ clone.textPages[i] = textPages[i].clone();
+ }
+ clone.block = block.clone();
+ clone.needRefresh = true;
+ return clone;
+ }
+
+ public BitmapFont getFont() {
+ return font;
+ }
+
+ /**
+ * Changes text size
+ * @param size text size
+ */
+ public void setSize(float size) {
+ block.setSize(size);
+ needRefresh = true;
+ letters.invalidate();
+ }
+
+ /**
+ *
+ * @param text charsequence to change text to
+ */
+ public void setText(CharSequence text) {
+ // note: text.toString() is free if text is already a java.lang.String.
+ setText( text != null ? text.toString() : null );
+ }
+
+ /**
+ *
+ * @param text String to change text to
+ */
+ public void setText(String text) {
+ text = text == null ? "" : text;
+ if (text == block.getText() || block.getText().equals(text)) {
+ return;
+ }
+
+ block.setText(text);
+ letters.setText(text);
+ needRefresh = true;
+ }
+
+ /**
+ * @return returns text
+ */
+ public String getText() {
+ return block.getText();
+ }
+
+ /**
+ * @return color of the text
+ */
+ public ColorRGBA getColor() {
+ return letters.getBaseColor();
+ }
+
+ /**
+ * changes text color. all substring colors are deleted.
+ * @param color new color of text
+ */
+ public void setColor(ColorRGBA color) {
+ letters.setColor(color);
+ letters.invalidate(); // TODO: Don't have to align.
+ needRefresh = true;
+ }
+
+ /**
+ * Define area where bitmaptext will be rendered
+ * @param rect position and size box where text is rendered
+ */
+ public void setBox(Rectangle rect) {
+ block.setTextBox(rect);
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ /**
+ * @return height of the line
+ */
+ public float getLineHeight() {
+ return font.getLineHeight(block);
+ }
+
+ /**
+ * @return height of whole textblock
+ */
+ public float getHeight() {
+ if (needRefresh) {
+ assemble();
+ }
+ float height = getLineHeight()*block.getLineCount();
+ Rectangle textBox = block.getTextBox();
+ if (textBox != null) {
+ return Math.max(height, textBox.height);
+ }
+ return height;
+ }
+
+ /**
+ * @return width of line
+ */
+ public float getLineWidth() {
+ if (needRefresh) {
+ assemble();
+ }
+ Rectangle textBox = block.getTextBox();
+ if (textBox != null) {
+ return Math.max(letters.getTotalWidth(), textBox.width);
+ }
+ return letters.getTotalWidth();
+ }
+
+ /**
+ * @return line count
+ */
+ public int getLineCount() {
+ if (needRefresh) {
+ assemble();
+ }
+ return block.getLineCount();
+ }
+
+ public LineWrapMode getLineWrapMode() {
+ return block.getLineWrapMode();
+ }
+
+ /**
+ * Set horizontal alignment. Applicable only when text bound is set.
+ * @param align
+ */
+ public void setAlignment(BitmapFont.Align align) {
+ if (block.getTextBox() == null && align != Align.Left) {
+ throw new RuntimeException("Bound is not set");
+ }
+ block.setAlignment(align);
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ /**
+ * Set vertical alignment. Applicable only when text bound is set.
+ * @param align
+ */
+ public void setVerticalAlignment(BitmapFont.VAlign align) {
+ if (block.getTextBox() == null && align != VAlign.Top) {
+ throw new RuntimeException("Bound is not set");
+ }
+ block.setVerticalAlignment(align);
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ public BitmapFont.Align getAlignment() {
+ return block.getAlignment();
+ }
+
+ public BitmapFont.VAlign getVerticalAlignment() {
+ return block.getVerticalAlignment();
+ }
+
+ /**
+ * Set the font style of substring. If font doesn't contain style, default style is used
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param style
+ */
+ public void setStyle(int start, int end, int style) {
+ letters.setStyle(start, end, style);
+ }
+
+ /**
+ * Set the font style of substring. If font doesn't contain style, default style is applied
+ * @param regexp regular expression
+ * @param style
+ */
+ public void setStyle(String regexp, int style) {
+ Pattern p = Pattern.compile(regexp);
+ Matcher m = p.matcher(block.getText());
+ while (m.find()) {
+ setStyle(m.start(), m.end(), style);
+ }
+ }
+
+ /**
+ * Set the color of substring.
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param color
+ */
+ public void setColor(int start, int end, ColorRGBA color) {
+ letters.setColor(start, end, color);
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ /**
+ * Set the color of substring.
+ * @param regexp regular expression
+ * @param color
+ */
+ public void setColor(String regexp, ColorRGBA color) {
+ Pattern p = Pattern.compile(regexp);
+ Matcher m = p.matcher(block.getText());
+ while (m.find()) {
+ letters.setColor(m.start(), m.end(), color);
+ }
+ letters.invalidate();
+ needRefresh = true;
+ }
+
+ /**
+ * @param tabs tab positions
+ */
+ public void setTabPosition(float... tabs) {
+ block.setTabPosition(tabs);
+ letters.invalidate();
+ needRefresh = false;
+ }
+
+ /**
+ * used for the tabs over the last tab position.
+ * @param width tab size
+ */
+ public void setTabWidth(float width) {
+ block.setTabWidth(width);
+ letters.invalidate();
+ needRefresh = false;
+ }
+
+ /**
+ * for setLineWrapType(LineWrapType.NoWrap),
+ * set the last character when the text exceeds the bound.
+ * @param c
+ */
+ public void setEllipsisChar(char c) {
+ block.setEllipsisChar(c);
+ letters.invalidate();
+ needRefresh = false;
+ }
+
+ /**
+ * Available only when bounding is set. <code>setBox()</code> method call is needed in advance.
+ * true when
+ * @param wrap NoWrap : Letters over the text bound is not shown. the last character is set to '...'(0x2026)
+ * Character: Character is split at the end of the line.
+ * Word : Word is split at the end of the line.
+ */
+ public void setLineWrapMode(LineWrapMode wrap) {
+ if (block.getLineWrapMode() != wrap) {
+ block.setLineWrapMode(wrap);
+ letters.invalidate();
+ needRefresh = true;
+ }
+ }
+
+ @Override
+ public void updateLogicalState(float tpf) {
+ super.updateLogicalState(tpf);
+ if (needRefresh) {
+ assemble();
+ }
+ }
+
+ private void assemble() {
+ // first generate quadlist
+ letters.update();
+
+ for (int i = 0; i < textPages.length; i++) {
+ textPages[i].assemble(letters);
+ }
+ needRefresh = false;
+ }
+
+ public void render(RenderManager rm) {
+ for (BitmapTextPage page : textPages) {
+ Material mat = page.getMaterial();
+ mat.setTexture("Texture", page.getTexture());
+ mat.render(page, rm);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/font/BitmapTextPage.java b/engine/src/core/com/jme3/font/BitmapTextPage.java
new file mode 100644
index 0000000..f49b5c9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/BitmapTextPage.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.font;
+
+import com.jme3.material.Material;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.texture.Texture2D;
+import com.jme3.util.BufferUtils;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.LinkedList;
+
+/**
+ * One page per BitmapText Font Texture.
+ * @author Lim, YongHoon
+ */
+class BitmapTextPage extends Geometry {
+
+ private final float[] pos;
+ private final float[] tc;
+ private final short[] idx;
+ private final byte[] color;
+ private final int page;
+ private final Texture2D texture;
+ private final LinkedList<LetterQuad> pageQuads = new LinkedList<LetterQuad>();
+
+ BitmapTextPage(BitmapFont font, boolean arrayBased, int page) {
+ super("BitmapFont", new Mesh());
+
+ if (font == null) {
+ throw new NullPointerException("'font' cannot be null.");
+ }
+
+ this.page = page;
+
+ Material mat = font.getPage(page);
+ if (mat == null) {
+ throw new IllegalStateException("The font's texture was not found!");
+ }
+
+ setMaterial(mat);
+ this.texture = (Texture2D) mat.getTextureParam("ColorMap").getTextureValue();
+
+ // initialize buffers
+ Mesh m = getMesh();
+ m.setBuffer(Type.Position, 3, new float[0]);
+ m.setBuffer(Type.TexCoord, 2, new float[0]);
+ m.setBuffer(Type.Color, 4, new byte[0]);
+ m.setBuffer(Type.Index, 3, new short[0]);
+
+ // scale colors from 0 - 255 range into 0 - 1
+ m.getBuffer(Type.Color).setNormalized(true);
+
+ arrayBased = true;
+
+ if (arrayBased) {
+ pos = new float[4 * 3]; // 4 verticies * 3 floats
+ tc = new float[4 * 2]; // 4 verticies * 2 floats
+ idx = new short[2 * 3]; // 2 triangles * 3 indices
+ color = new byte[4 * 4]; // 4 verticies * 4 bytes
+ } else {
+ pos = null;
+ tc = null;
+ idx = null;
+ color = null;
+ }
+ }
+
+ BitmapTextPage(BitmapFont font, boolean arrayBased) {
+ this(font, arrayBased, 0);
+ }
+
+ BitmapTextPage(BitmapFont font) {
+ this(font, false, 0);
+ }
+
+ Texture2D getTexture() {
+ return texture;
+ }
+
+ @Override
+ public BitmapTextPage clone() {
+ BitmapTextPage clone = (BitmapTextPage) super.clone();
+ clone.mesh = mesh.deepClone();
+ return clone;
+ }
+
+ void assemble(Letters quads) {
+ pageQuads.clear();
+ quads.rewind();
+
+ while (quads.nextCharacter()) {
+ if (quads.isPrintable()) {
+ if (quads.getCharacterSetPage() == page) {
+ pageQuads.add(quads.getQuad());
+ }
+ }
+ }
+
+ Mesh m = getMesh();
+ int vertCount = pageQuads.size() * 4;
+ int triCount = pageQuads.size() * 2;
+
+ VertexBuffer pb = m.getBuffer(Type.Position);
+ VertexBuffer tb = m.getBuffer(Type.TexCoord);
+ VertexBuffer ib = m.getBuffer(Type.Index);
+ VertexBuffer cb = m.getBuffer(Type.Color);
+
+ FloatBuffer fpb = (FloatBuffer) pb.getData();
+ FloatBuffer ftb = (FloatBuffer) tb.getData();
+ ShortBuffer sib = (ShortBuffer) ib.getData();
+ ByteBuffer bcb = (ByteBuffer) cb.getData();
+
+ // increase capacity of buffers as needed
+ fpb.rewind();
+ fpb = BufferUtils.ensureLargeEnough(fpb, vertCount * 3);
+ fpb.limit(vertCount * 3);
+ pb.updateData(fpb);
+
+ ftb.rewind();
+ ftb = BufferUtils.ensureLargeEnough(ftb, vertCount * 2);
+ ftb.limit(vertCount * 2);
+ tb.updateData(ftb);
+
+ bcb.rewind();
+ bcb = BufferUtils.ensureLargeEnough(bcb, vertCount * 4);
+ bcb.limit(vertCount * 4);
+ cb.updateData(bcb);
+
+ sib.rewind();
+ sib = BufferUtils.ensureLargeEnough(sib, triCount * 3);
+ sib.limit(triCount * 3);
+ ib.updateData(sib);
+
+ m.updateCounts();
+
+ // go for each quad and append it to the buffers
+ if (pos != null) {
+ for (int i = 0; i < pageQuads.size(); i++) {
+ LetterQuad fq = pageQuads.get(i);
+ fq.storeToArrays(pos, tc, idx, color, i);
+ fpb.put(pos);
+ ftb.put(tc);
+ sib.put(idx);
+ bcb.put(color);
+ }
+ } else {
+ for (int i = 0; i < pageQuads.size(); i++) {
+ LetterQuad fq = pageQuads.get(i);
+ fq.appendPositions(fpb);
+ fq.appendTexCoords(ftb);
+ fq.appendIndices(sib, i);
+ fq.appendColors(bcb);
+ }
+ }
+
+ fpb.rewind();
+ ftb.rewind();
+ sib.rewind();
+ bcb.rewind();
+
+ updateModelBound();
+ }
+}
diff --git a/engine/src/core/com/jme3/font/ColorTags.java b/engine/src/core/com/jme3/font/ColorTags.java
new file mode 100644
index 0000000..01f15c3
--- /dev/null
+++ b/engine/src/core/com/jme3/font/ColorTags.java
@@ -0,0 +1,91 @@
+package com.jme3.font;
+
+import com.jme3.math.ColorRGBA;
+import java.util.LinkedList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Contains the color information tagged in a text string
+ * Format: \#rgb#
+ * \#rgba#
+ * \#rrggbb#
+ * \#rrggbbaa#
+ * @author YongHoon
+ */
+class ColorTags {
+ private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" +
+ "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#");
+ private LinkedList<Range> colors = new LinkedList<Range>();
+ private String text;
+
+ ColorTags() { }
+
+ ColorTags(String seq) {
+ setText(seq);
+ }
+
+ /**
+ * @return text without color tags
+ */
+ String getPlainText() {
+ return text;
+ }
+
+ LinkedList<Range> getTags() {
+ return colors;
+ }
+
+ void setText(final String charSeq) {
+ colors.clear();
+ if (charSeq == null) {
+ return;
+ }
+ Matcher m = colorPattern.matcher(charSeq);
+ if (m.find()) {
+ StringBuilder builder = new StringBuilder(charSeq.length()-7);
+ int startIndex = 0;
+ do {
+ String colorStr = null;
+ for (int i = 1; i <= 4 && colorStr==null; i++) {
+ colorStr = m.group(i);
+ }
+ builder.append(charSeq.subSequence(startIndex, m.start()));
+ Range range = new Range(builder.length(), colorStr);
+ startIndex = m.end();
+ colors.add(range);
+ } while (m.find());
+ builder.append(charSeq.subSequence(startIndex, charSeq.length()));
+ text = builder.toString();
+ } else {
+ text = charSeq;
+ }
+ }
+
+ class Range {
+ int start;
+ ColorRGBA color;
+ Range(int start, String colorStr) {
+ this.start = start;
+ this.color = new ColorRGBA();
+ if (colorStr.length() >= 6) {
+ color.set(Integer.parseInt(colorStr.subSequence(0,2).toString(), 16) / 255f,
+ Integer.parseInt(colorStr.subSequence(2,4).toString(), 16) / 255f,
+ Integer.parseInt(colorStr.subSequence(4,6).toString(), 16) / 255f,
+ 1);
+ if (colorStr.length() == 8) {
+ color.a = Integer.parseInt(colorStr.subSequence(6,8).toString(), 16) / 255f;
+ }
+ } else {
+ color.set(Integer.parseInt(Character.toString(colorStr.charAt(0)), 16) / 15f,
+ Integer.parseInt(Character.toString(colorStr.charAt(1)), 16) / 15f,
+ Integer.parseInt(Character.toString(colorStr.charAt(2)), 16) / 15f,
+ 1);
+ if (colorStr.length() == 4) {
+ color.a = Integer.parseInt(Character.toString(colorStr.charAt(3)), 16) / 15f;
+ }
+ }
+
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/font/Kerning.java b/engine/src/core/com/jme3/font/Kerning.java
new file mode 100644
index 0000000..e4be815
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Kerning.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+
+/**
+ * Represents kerning information for a character.
+ */
+public class Kerning implements Savable {
+
+ private int second;
+ private int amount;
+
+ public int getSecond() {
+ return second;
+ }
+
+ public void setSecond(int second) {
+ this.second = second;
+ }
+
+ public int getAmount() {
+ return amount;
+ }
+
+ public void setAmount(int amount) {
+ this.amount = amount;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(second, "second", 0);
+ oc.write(amount, "amount", 0);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ second = ic.readInt("second", 0);
+ amount = ic.readInt("amount", 0);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/LetterQuad.java b/engine/src/core/com/jme3/font/LetterQuad.java
new file mode 100644
index 0000000..09f0e24
--- /dev/null
+++ b/engine/src/core/com/jme3/font/LetterQuad.java
@@ -0,0 +1,496 @@
+package com.jme3.font;
+
+import com.jme3.math.ColorRGBA;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * LetterQuad contains the position, color, uv texture information for a character in text.
+ * @author YongHoon
+ */
+class LetterQuad {
+ private static final Rectangle UNBOUNDED = new Rectangle(0, 0, Float.MAX_VALUE, Float.MAX_VALUE);
+ private static final float LINE_DIR = -1;
+
+ private final BitmapFont font;
+ private final char c;
+ private final int index;
+ private int style;
+
+ private BitmapCharacter bitmapChar = null;
+ private float x0 = Integer.MIN_VALUE;
+ private float y0 = Integer.MIN_VALUE;
+ private float width = Integer.MIN_VALUE;
+ private float height = Integer.MIN_VALUE;
+ private float xAdvance = 0;
+ private float u0;
+ private float v0;
+ private float u1;
+ private float v1;
+ private float lineY;
+ private boolean eol;
+
+ private LetterQuad previous;
+ private LetterQuad next;
+ private int colorInt = 0xFFFFFFFF;
+
+ private boolean rightToLeft;
+ private float alignX;
+ private float alignY;
+ private float sizeScale = 1;
+
+ /**
+ * create head / tail
+ * @param font
+ * @param rightToLeft
+ */
+ protected LetterQuad(BitmapFont font, boolean rightToLeft) {
+ this.font = font;
+ this.c = Character.MIN_VALUE;
+ this.rightToLeft = rightToLeft;
+ this.index = -1;
+ setBitmapChar(null);
+ }
+
+ /**
+ * create letter and append to previous LetterQuad
+ *
+ * @param c
+ * @param prev previous character
+ */
+ protected LetterQuad(char c, LetterQuad prev) {
+ this.font = prev.font;
+ this.rightToLeft = prev.rightToLeft;
+ this.c = c;
+ this.index = prev.index+1;
+ this.eol = isLineFeed();
+ setBitmapChar(c);
+ prev.insert(this);
+ }
+
+ LetterQuad addNextCharacter(char c) {
+ LetterQuad n = new LetterQuad(c, this);
+ return n;
+ }
+
+ BitmapCharacter getBitmapChar() {
+ return bitmapChar;
+ }
+
+ char getChar() {
+ return c;
+ }
+
+ int getIndex() {
+ return index;
+ }
+
+ private Rectangle getBound(StringBlock block) {
+ if (block.getTextBox() != null) {
+ return block.getTextBox();
+ }
+ return UNBOUNDED;
+ }
+
+ LetterQuad getPrevious() {
+ return previous;
+ }
+
+ LetterQuad getNext() {
+ return next;
+ }
+
+ public float getU0() {
+ return u0;
+ }
+
+ float getU1() {
+ return u1;
+ }
+
+ float getV0() {
+ return v0;
+ }
+
+ float getV1() {
+ return v1;
+ }
+
+ boolean isInvalid() {
+ return x0 == Integer.MIN_VALUE;
+ }
+
+ boolean isInvalid(StringBlock block) {
+ return isInvalid(block, 0);
+ }
+
+ boolean isInvalid(StringBlock block, float gap) {
+ if (isHead() || isTail())
+ return false;
+ if (x0 == Integer.MIN_VALUE || y0 == Integer.MIN_VALUE) {
+ return true;
+ }
+ Rectangle bound = block.getTextBox();
+ if (bound == null) {
+ return false;
+ }
+ return x0 > 0 && bound.x+bound.width-gap < getX1();
+ }
+
+ float getX0() {
+ return x0;
+ }
+
+ float getX1() {
+ return x0+width;
+ }
+
+ float getNextX() {
+ return x0+xAdvance;
+ }
+
+ float getNextLine() {
+ return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale;
+ }
+
+ float getY0() {
+ return y0;
+ }
+
+ float getY1() {
+ return y0-height;
+ }
+
+ float getWidth() {
+ return width;
+ }
+
+ float getHeight() {
+ return height;
+ }
+
+ void insert(LetterQuad ins) {
+ LetterQuad n = next;
+ next = ins;
+ ins.next = n;
+ ins.previous = this;
+ n.previous = ins;
+ }
+
+ void invalidate() {
+ eol = isLineFeed();
+ setBitmapChar(font.getCharSet().getCharacter(c, style));
+ }
+
+ boolean isTail() {
+ return next == null;
+ }
+
+ boolean isHead() {
+ return previous == null;
+ }
+
+ /**
+ * @return next letter
+ */
+ LetterQuad remove() {
+ this.previous.next = next;
+ this.next.previous = previous;
+ return next;
+ }
+
+ void setPrevious(LetterQuad before) {
+ this.previous = before;
+ }
+
+ void setStyle(int style) {
+ this.style = style;
+ invalidate();
+ }
+
+ void setColor(ColorRGBA color) {
+ this.colorInt = color.asIntRGBA();
+ invalidate();
+ }
+
+ void setBitmapChar(char c) {
+ BitmapCharacterSet charSet = font.getCharSet();
+ BitmapCharacter bm = charSet.getCharacter(c, style);
+ setBitmapChar(bm);
+ }
+
+ void setBitmapChar(BitmapCharacter bitmapChar) {
+ x0 = Integer.MIN_VALUE;
+ y0 = Integer.MIN_VALUE;
+ width = Integer.MIN_VALUE;
+ height = Integer.MIN_VALUE;
+ alignX = 0;
+ alignY = 0;
+
+ BitmapCharacterSet charSet = font.getCharSet();
+ this.bitmapChar = bitmapChar;
+ if (bitmapChar != null) {
+ u0 = (float) bitmapChar.getX() / charSet.getWidth();
+ v0 = (float) bitmapChar.getY() / charSet.getHeight();
+ u1 = u0 + (float) bitmapChar.getWidth() / charSet.getWidth();
+ v1 = v0 + (float) bitmapChar.getHeight() / charSet.getHeight();
+ } else {
+ u0 = 0;
+ v0 = 0;
+ u1 = 0;
+ v1 = 0;
+ }
+ }
+
+ void setNext(LetterQuad next) {
+ this.next = next;
+ }
+
+ void update(StringBlock block) {
+ final float[] tabs = block.getTabPosition();
+ final float tabWidth = block.getTabWidth();
+ final Rectangle bound = getBound(block);
+ sizeScale = block.getSize() / font.getCharSet().getRenderedSize();
+ lineY = computeLineY(block);
+
+ if (isHead()) {
+ x0 = getBound(block).x;
+ y0 = lineY;
+ width = 0;
+ height = 0;
+ xAdvance = 0;
+ } else if (isTab()) {
+ x0 = previous.getNextX();
+ width = tabWidth;
+ y0 = lineY;
+ height = 0;
+ if (tabs != null && x0 < tabs[tabs.length-1]) {
+ for (int i = 0; i < tabs.length-1; i++) {
+ if (x0 > tabs[i] && x0 < tabs[i+1]) {
+ width = tabs[i+1] - x0;
+ }
+ }
+ }
+ xAdvance = width;
+ } else if (bitmapChar == null) {
+ x0 = getPrevious().getX1();
+ y0 = lineY;
+ width = 0;
+ height = 0;
+ xAdvance = 0;
+ } else {
+ float xOffset = bitmapChar.getXOffset() * sizeScale;
+ float yOffset = bitmapChar.getYOffset() * sizeScale;
+ xAdvance = bitmapChar.getXAdvance() * sizeScale;
+ width = bitmapChar.getWidth() * sizeScale;
+ height = bitmapChar.getHeight() * sizeScale;
+ float incrScale = rightToLeft ? -1f : 1f;
+ float kernAmount = 0f;
+
+ if (previous.isHead() || previous.eol) {
+ x0 = bound.x;
+
+ // The first letter quad will be drawn right at the first
+ // position... but it does not offset by the characters offset
+ // amount. This means that we've potentially accumulated extra
+ // pixels and the next letter won't get drawn far enough unless
+ // we add this offset back into xAdvance.. by subtracting it.
+ // This is the same thing that's done below because we've
+ // technically baked the offset in just like below. It doesn't
+ // look like it at first glance so I'm keeping it separate with
+ // this comment.
+ xAdvance -= xOffset * incrScale;
+
+ } else {
+ x0 = previous.getNextX() + xOffset * incrScale;
+
+ // Since x0 will have offset baked into it then we
+ // need to counteract that in xAdvance. This is better
+ // than removing it in getNextX() because we also need
+ // to take kerning into account below... which will also
+ // get baked in.
+ // Without this, getNextX() will return values too far to
+ // the left, for example.
+ xAdvance -= xOffset * incrScale;
+ }
+ y0 = lineY + LINE_DIR*yOffset;
+
+ // Adjust for kerning
+ BitmapCharacter lastChar = previous.getBitmapChar();
+ if (lastChar != null && block.isKerning()) {
+ kernAmount = lastChar.getKerning(c) * sizeScale;
+ x0 += kernAmount * incrScale;
+
+ // Need to unbake the kerning from xAdvance since it
+ // is baked into x0... see above.
+ //xAdvance -= kernAmount * incrScale;
+ // No, kerning is an inter-character spacing and _does_ affect
+ // all subsequent cursor positions.
+ }
+ }
+ if (isEndOfLine()) {
+ xAdvance = bound.x-x0;
+ }
+ }
+
+ /**
+ * add temporary linewrap indicator
+ */
+ void setEndOfLine() {
+ this.eol = true;
+ }
+
+ boolean isEndOfLine() {
+ return eol;
+ }
+
+ boolean isLineWrap() {
+ return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE;
+ }
+
+ private float computeLineY(StringBlock block) {
+ if (isHead()) {
+ return getBound(block).y;
+ } else if (previous.eol) {
+ return previous.getNextLine();
+ } else {
+ return previous.lineY;
+ }
+ }
+
+
+ boolean isLineStart() {
+ return x0 == 0 || (previous != null && previous.eol);
+ }
+
+ boolean isBlank() {
+ return c == ' ' || isTab();
+ }
+
+ public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){
+ float x = x0+alignX;
+ float y = y0-alignY;
+ float xpw = x+width;
+ float ymh = y-height;
+
+ pos[0] = x; pos[1] = y; pos[2] = 0;
+ pos[3] = x; pos[4] = ymh; pos[5] = 0;
+ pos[6] = xpw; pos[7] = ymh; pos[8] = 0;
+ pos[9] = xpw; pos[10] = y; pos[11] = 0;
+
+ float v0 = 1f - this.v0;
+ float v1 = 1f - this.v1;
+
+ tc[0] = u0; tc[1] = v0;
+ tc[2] = u0; tc[3] = v1;
+ tc[4] = u1; tc[5] = v1;
+ tc[6] = u1; tc[7] = v0;
+
+ colors[3] = (byte) (colorInt & 0xff);
+ colors[2] = (byte) ((colorInt >> 8) & 0xff);
+ colors[1] = (byte) ((colorInt >> 16) & 0xff);
+ colors[0] = (byte) ((colorInt >> 24) & 0xff);
+ System.arraycopy(colors, 0, colors, 4, 4);
+ System.arraycopy(colors, 0, colors, 8, 4);
+ System.arraycopy(colors, 0, colors, 12, 4);
+
+ short i0 = (short) (quadIdx * 4);
+ short i1 = (short) (i0 + 1);
+ short i2 = (short) (i0 + 2);
+ short i3 = (short) (i0 + 3);
+
+ idx[0] = i0; idx[1] = i1; idx[2] = i2;
+ idx[3] = i0; idx[4] = i2; idx[5] = i3;
+ }
+
+ public void appendPositions(FloatBuffer fb){
+ float sx = x0+alignX;
+ float sy = y0-alignY;
+ float ex = sx+width;
+ float ey = sy-height;
+ // NOTE: subtracting the height here
+ // because OGL's Ortho origin is at lower-left
+ fb.put(sx).put(sy).put(0f);
+ fb.put(sx).put(ey).put(0f);
+ fb.put(ex).put(ey).put(0f);
+ fb.put(ex).put(sy).put(0f);
+ }
+
+ public void appendPositions(ShortBuffer sb){
+ final float x1 = getX1();
+ final float y1 = getY1();
+ short x = (short) x0;
+ short y = (short) y0;
+ short xpw = (short) (x1);
+ short ymh = (short) (y1);
+
+ sb.put(x).put(y).put((short)0);
+ sb.put(x).put(ymh).put((short)0);
+ sb.put(xpw).put(ymh).put((short)0);
+ sb.put(xpw).put(y).put((short)0);
+ }
+
+ public void appendTexCoords(FloatBuffer fb){
+ // flip coords to be compatible with OGL
+ float v0 = 1 - this.v0;
+ float v1 = 1 - this.v1;
+
+ // upper left
+ fb.put(u0).put(v0);
+ // lower left
+ fb.put(u0).put(v1);
+ // lower right
+ fb.put(u1).put(v1);
+ // upper right
+ fb.put(u1).put(v0);
+ }
+
+ public void appendColors(ByteBuffer bb){
+ bb.putInt(colorInt);
+ bb.putInt(colorInt);
+ bb.putInt(colorInt);
+ bb.putInt(colorInt);
+ }
+
+ public void appendIndices(ShortBuffer sb, int quadIndex){
+ // each quad has 4 indices
+ short v0 = (short) (quadIndex * 4);
+ short v1 = (short) (v0 + 1);
+ short v2 = (short) (v0 + 2);
+ short v3 = (short) (v0 + 3);
+
+ sb.put(v0).put(v1).put(v2);
+ sb.put(v0).put(v2).put(v3);
+// sb.put(new short[]{ v0, v1, v2,
+// v0, v2, v3 });
+ }
+
+
+ @Override
+ public String toString() {
+ return String.valueOf(c);
+ }
+
+ void setAlignment(float alignX, float alignY) {
+ this.alignX = alignX;
+ this.alignY = alignY;
+ }
+
+ float getAlignX() {
+ return alignX;
+ }
+
+ float getAlignY() {
+ return alignY;
+ }
+
+ boolean isLineFeed() {
+ return c == '\n';
+ }
+
+ boolean isTab() {
+ return c == '\t';
+ }
+
+}
diff --git a/engine/src/core/com/jme3/font/Letters.java b/engine/src/core/com/jme3/font/Letters.java
new file mode 100644
index 0000000..dbb9ef9
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Letters.java
@@ -0,0 +1,331 @@
+package com.jme3.font;
+
+import com.jme3.font.BitmapFont.Align;
+import com.jme3.font.BitmapFont.VAlign;
+import com.jme3.font.ColorTags.Range;
+import com.jme3.math.ColorRGBA;
+import java.util.LinkedList;
+
+/**
+ * Manage and align LetterQuads
+ * @author YongHoon
+ */
+class Letters {
+ private final LetterQuad head;
+ private final LetterQuad tail;
+ private final BitmapFont font;
+ private LetterQuad current;
+ private StringBlock block;
+ private float totalWidth;
+ private float totalHeight;
+ private ColorTags colorTags = new ColorTags();
+ private ColorRGBA baseColor = null;
+
+ Letters(BitmapFont font, StringBlock bound, boolean rightToLeft) {
+ final String text = bound.getText();
+ this.block = bound;
+ this.font = font;
+ head = new LetterQuad(font, rightToLeft);
+ tail = new LetterQuad(font, rightToLeft);
+ setText(text);
+ }
+
+ void setText(final String text) {
+ colorTags.setText(text);
+ String plainText = colorTags.getPlainText();
+
+ head.setNext(tail);
+ tail.setPrevious(head);
+ current = head;
+ if (text != null && plainText.length() > 0) {
+ LetterQuad l = head;
+ for (int i = 0; i < plainText.length(); i++) {
+ l = l.addNextCharacter(plainText.charAt(i));
+ if (baseColor != null) {
+ // Give the letter a default color if
+ // one has been provided.
+ l.setColor( baseColor );
+ }
+ }
+ }
+
+ LinkedList<Range> ranges = colorTags.getTags();
+ if (!ranges.isEmpty()) {
+ for (int i = 0; i < ranges.size()-1; i++) {
+ Range start = ranges.get(i);
+ Range end = ranges.get(i+1);
+ setColor(start.start, end.start, start.color);
+ }
+ Range end = ranges.getLast();
+ setColor(end.start, plainText.length(), end.color);
+ }
+
+ invalidate();
+ }
+
+ LetterQuad getHead() {
+ return head;
+ }
+
+ LetterQuad getTail() {
+ return tail;
+ }
+
+ void update() {
+ LetterQuad l = head;
+ int lineCount = 1;
+ BitmapCharacter ellipsis = font.getCharSet().getCharacter(block.getEllipsisChar());
+ float ellipsisWidth = ellipsis!=null? ellipsis.getWidth()*getScale(): 0;
+
+ while (!l.isTail()) {
+ if (l.isInvalid()) {
+ l.update(block);
+
+ if (l.isInvalid(block)) {
+ switch (block.getLineWrapMode()) {
+ case Character:
+ lineWrap(l);
+ lineCount++;
+ break;
+ case Word:
+ if (!l.isBlank()) {
+ // search last blank character before this word
+ LetterQuad blank = l;
+ while (!blank.isBlank()) {
+ if (blank.isLineStart() || blank.isHead()) {
+ lineWrap(l);
+ lineCount++;
+ blank = null;
+ break;
+ }
+ blank = blank.getPrevious();
+ }
+ if (blank != null) {
+ blank.setEndOfLine();
+ lineCount++;
+ while (blank != l) {
+ blank = blank.getNext();
+ blank.invalidate();
+ blank.update(block);
+ }
+ }
+ }
+ break;
+ case NoWrap:
+ // search last blank character before this word
+ LetterQuad cursor = l.getPrevious();
+ while (cursor.isInvalid(block, ellipsisWidth) && !cursor.isLineStart()) {
+ cursor = cursor.getPrevious();
+ }
+ cursor.setBitmapChar(ellipsis);
+ cursor.update(block);
+ cursor = cursor.getNext();
+ while (!cursor.isTail() && !cursor.isLineFeed()) {
+ cursor.setBitmapChar(null);
+ cursor.update(block);
+ cursor = cursor.getNext();
+ }
+ break;
+ }
+ }
+ } else if (current.isInvalid(block)) {
+ invalidate(current);
+ }
+ if (l.isEndOfLine()) {
+ lineCount++;
+ }
+ l = l.getNext();
+ }
+
+ align();
+ block.setLineCount(lineCount);
+ rewind();
+ }
+
+ private void align() {
+ final Align alignment = block.getAlignment();
+ final VAlign valignment = block.getVerticalAlignment();
+ if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top))
+ return;
+ LetterQuad cursor = tail.getPrevious();
+ cursor.setEndOfLine();
+ final float width = block.getTextBox().width;
+ final float height = block.getTextBox().height;
+ float lineWidth = 0;
+ float gapX = 0;
+ float gapY = 0;
+ validateSize();
+ if (totalHeight < height) { // align vertically only for no overflow
+ switch (valignment) {
+ case Top:
+ gapY = 0;
+ break;
+ case Center:
+ gapY = (height-totalHeight)*0.5f;
+ break;
+ case Bottom:
+ gapY = height-totalHeight;
+ break;
+ }
+ }
+ while (!cursor.isHead()) {
+ if (cursor.isEndOfLine()) {
+ lineWidth = cursor.getX1()-block.getTextBox().x;
+ if (alignment == Align.Center) {
+ gapX = (width-lineWidth)/2;
+ } else if (alignment == Align.Right) {
+ gapX = width-lineWidth;
+ } else {
+ gapX = 0;
+ }
+ }
+ cursor.setAlignment(gapX, gapY);
+ cursor = cursor.getPrevious();
+ }
+ }
+
+ private void lineWrap(LetterQuad l) {
+ if (l.isHead() || l.isBlank())
+ return;
+ l.getPrevious().setEndOfLine();
+ l.invalidate();
+ l.update(block); // TODO: update from l
+ }
+
+ float getCharacterX0() {
+ return current.getX0();
+ }
+
+ float getCharacterY0() {
+ return current.getY0();
+ }
+
+ float getCharacterX1() {
+ return current.getX1();
+ }
+
+ float getCharacterY1() {
+ return current.getY1();
+ }
+
+ float getCharacterAlignX() {
+ return current.getAlignX();
+ }
+
+ float getCharacterAlignY() {
+ return current.getAlignY();
+ }
+
+ float getCharacterWidth() {
+ return current.getWidth();
+ }
+
+ float getCharacterHeight() {
+ return current.getHeight();
+ }
+
+ public boolean nextCharacter() {
+ if (current.isTail())
+ return false;
+ current = current.getNext();
+ return true;
+ }
+
+ public int getCharacterSetPage() {
+ return current.getBitmapChar().getPage();
+ }
+
+ public LetterQuad getQuad() {
+ return current;
+ }
+
+ public void rewind() {
+ current = head;
+ }
+
+ public void invalidate() {
+ invalidate(head);
+ }
+
+ public void invalidate(LetterQuad cursor) {
+ totalWidth = -1;
+ totalHeight = -1;
+
+ while (!cursor.isTail() && !cursor.isInvalid()) {
+ cursor.invalidate();
+ cursor = cursor.getNext();
+ }
+ }
+
+ float getScale() {
+ return block.getSize() / font.getCharSet().getRenderedSize();
+ }
+
+ public boolean isPrintable() {
+ return current.getBitmapChar() != null;
+ }
+
+ float getTotalWidth() {
+ validateSize();
+ return totalWidth;
+ }
+
+ float getTotalHeight() {
+ validateSize();
+ return totalHeight;
+ }
+
+ void validateSize() {
+ if (totalWidth < 0) {
+ LetterQuad l = head;
+ while (!l.isTail()) {
+ totalWidth = Math.max(totalWidth, l.getX1());
+ l = l.getNext();
+ totalHeight = Math.max(totalHeight, -l.getY1());
+ }
+ }
+ }
+
+ /**
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param style
+ */
+ void setStyle(int start, int end, int style) {
+ LetterQuad cursor = head.getNext();
+ while (!cursor.isTail()) {
+ if (cursor.getIndex() >= start && cursor.getIndex() < end) {
+ cursor.setStyle(style);
+ }
+ cursor = cursor.getNext();
+ }
+ }
+
+ /**
+ * Sets the base color for all new letter quads and resets
+ * the color of existing letter quads.
+ */
+ void setColor( ColorRGBA color ) {
+ baseColor = color;
+ setColor( 0, block.getText().length(), color );
+ }
+
+ ColorRGBA getBaseColor() {
+ return baseColor;
+ }
+
+ /**
+ * @param start start index to set style. inclusive.
+ * @param end end index to set style. EXCLUSIVE.
+ * @param color
+ */
+ void setColor(int start, int end, ColorRGBA color) {
+ LetterQuad cursor = head.getNext();
+ while (!cursor.isTail()) {
+ if (cursor.getIndex() >= start && cursor.getIndex() < end) {
+ cursor.setColor(color);
+ }
+ cursor = cursor.getNext();
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/LineWrapMode.java b/engine/src/core/com/jme3/font/LineWrapMode.java
new file mode 100644
index 0000000..3c77d6e
--- /dev/null
+++ b/engine/src/core/com/jme3/font/LineWrapMode.java
@@ -0,0 +1,11 @@
+package com.jme3.font;
+
+/**
+ * Line-wrap type for BitmapText
+ * @author YongHoon
+ */
+public enum LineWrapMode {
+ NoWrap,
+ Character,
+ Word
+}
diff --git a/engine/src/core/com/jme3/font/Rectangle.java b/engine/src/core/com/jme3/font/Rectangle.java
new file mode 100644
index 0000000..33b47c5
--- /dev/null
+++ b/engine/src/core/com/jme3/font/Rectangle.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+/**
+ * Defines a rectangle that can constrict a text paragraph.
+ * @author dhdd
+ */
+public class Rectangle implements Cloneable {
+
+ public final float x, y, width, height;
+
+ /**
+ *
+ * @param x the X value of the upper left corner of the rectangle
+ * @param y the Y value of the upper left corner of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public Rectangle(float x, float y, float width, float height) {
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public Rectangle clone(){
+ try {
+ return (Rectangle) super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/font/StringBlock.java b/engine/src/core/com/jme3/font/StringBlock.java
new file mode 100644
index 0000000..9f6f055
--- /dev/null
+++ b/engine/src/core/com/jme3/font/StringBlock.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.font;
+
+import com.jme3.font.BitmapFont.Align;
+import com.jme3.font.BitmapFont.VAlign;
+import com.jme3.math.ColorRGBA;
+
+/**
+ * Defines a String that is to be drawn in one block that can be constrained by a {@link Rectangle}. Also holds
+ * formatting information for the StringBlock
+ *
+ * @author dhdd
+ */
+class StringBlock implements Cloneable {
+
+ private String text;
+ private Rectangle textBox;
+ private Align alignment = Align.Left;
+ private VAlign valignment = VAlign.Top;
+ private float size;
+ private ColorRGBA color = new ColorRGBA(ColorRGBA.White);
+ private boolean kerning;
+ private int lineCount;
+ private LineWrapMode wrapType = LineWrapMode.Word;
+ private float[] tabPos;
+ private float tabWidth = 50;
+ private char ellipsisChar = 0x2026;
+
+ /**
+ *
+ * @param text the text that the StringBlock will hold
+ * @param textBox the rectangle that constrains the text
+ * @param alignment the initial alignment of the text
+ * @param size the size in pixels (vertical size of a single line)
+ * @param color the initial color of the text
+ * @param kerning
+ */
+ StringBlock(String text, Rectangle textBox, BitmapFont.Align alignment, float size, ColorRGBA color,
+ boolean kerning) {
+ this.text = text;
+ this.textBox = textBox;
+ this.alignment = alignment;
+ this.size = size;
+ this.color.set(color);
+ this.kerning = kerning;
+ }
+
+ StringBlock(){
+ this.text = "";
+ this.textBox = null;
+ this.alignment = Align.Left;
+ this.size = 100;
+ this.color.set(ColorRGBA.White);
+ this.kerning = true;
+ }
+
+ @Override
+ public StringBlock clone(){
+ try {
+ StringBlock clone = (StringBlock) super.clone();
+ clone.color = color.clone();
+ if (textBox != null)
+ clone.textBox = textBox.clone();
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ String getText() {
+ return text;
+ }
+
+ void setText(String text){
+ this.text = text == null ? "" : text;
+ }
+
+ Rectangle getTextBox() {
+ return textBox;
+ }
+
+ void setTextBox(Rectangle textBox) {
+ this.textBox = textBox;
+ }
+
+ BitmapFont.Align getAlignment() {
+ return alignment;
+ }
+
+ BitmapFont.VAlign getVerticalAlignment() {
+ return valignment;
+ }
+
+ void setAlignment(BitmapFont.Align alignment) {
+ this.alignment = alignment;
+ }
+
+ void setVerticalAlignment(BitmapFont.VAlign alignment) {
+ this.valignment = alignment;
+ }
+
+ float getSize() {
+ return size;
+ }
+
+ void setSize(float size) {
+ this.size = size;
+ }
+
+ ColorRGBA getColor() {
+ return color;
+ }
+
+ void setColor(ColorRGBA color) {
+ this.color.set(color);
+ }
+
+ boolean isKerning() {
+ return kerning;
+ }
+
+ void setKerning(boolean kerning) {
+ this.kerning = kerning;
+ }
+
+ int getLineCount() {
+ return lineCount;
+ }
+
+ void setLineCount(int lineCount) {
+ this.lineCount = lineCount;
+ }
+
+ LineWrapMode getLineWrapMode() {
+ return wrapType;
+ }
+
+ /**
+ * available only when bounding is set. <code>setBox()</code> method call is needed in advance.
+ * @param wrap true when word need not be split at the end of the line.
+ */
+ void setLineWrapMode(LineWrapMode wrap) {
+ this.wrapType = wrap;
+ }
+
+ void setTabWidth(float tabWidth) {
+ this.tabWidth = tabWidth;
+ }
+
+ void setTabPosition(float[] tabs) {
+ this.tabPos = tabs;
+ }
+
+ float getTabWidth() {
+ return tabWidth;
+ }
+
+ float[] getTabPosition() {
+ return tabPos;
+ }
+
+ void setEllipsisChar(char c) {
+ this.ellipsisChar = c;
+ }
+
+ int getEllipsisChar() {
+ return ellipsisChar;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/ChaseCamera.java b/engine/src/core/com/jme3/input/ChaseCamera.java
new file mode 100644
index 0000000..b3b649d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/ChaseCamera.java
@@ -0,0 +1,875 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.input.controls.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import java.io.IOException;
+
+/**
+ * A camera that follows a spatial and can turn around it by dragging the mouse
+ * @author nehon
+ */
+public class ChaseCamera implements ActionListener, AnalogListener, Control {
+
+ protected Spatial target = null;
+ protected float minVerticalRotation = 0.00f;
+ protected float maxVerticalRotation = FastMath.PI / 2;
+ protected float minDistance = 1.0f;
+ protected float maxDistance = 40.0f;
+ protected float distance = 20;
+ protected float zoomSpeed = 2f;
+ protected float rotationSpeed = 1.0f;
+ protected float rotation = 0;
+ protected float trailingRotationInertia = 0.05f;
+ protected float zoomSensitivity = 5f;
+ protected float rotationSensitivity = 5f;
+ protected float chasingSensitivity = 5f;
+ protected float trailingSensitivity = 0.5f;
+ protected float vRotation = FastMath.PI / 6;
+ protected boolean smoothMotion = false;
+ protected boolean trailingEnabled = true;
+ protected float rotationLerpFactor = 0;
+ protected float trailingLerpFactor = 0;
+ protected boolean rotating = false;
+ protected boolean vRotating = false;
+ protected float targetRotation = rotation;
+ protected InputManager inputManager;
+ protected Vector3f initialUpVec;
+ protected float targetVRotation = vRotation;
+ protected float vRotationLerpFactor = 0;
+ protected float targetDistance = distance;
+ protected float distanceLerpFactor = 0;
+ protected boolean zooming = false;
+ protected boolean trailing = false;
+ protected boolean chasing = false;
+ protected boolean canRotate;
+ protected float offsetDistance = 0.002f;
+ protected Vector3f prevPos;
+ protected boolean targetMoves = false;
+ protected boolean enabled = true;
+ protected Camera cam = null;
+ protected final Vector3f targetDir = new Vector3f();
+ protected float previousTargetRotation;
+ protected final Vector3f pos = new Vector3f();
+ protected Vector3f targetLocation = new Vector3f(0, 0, 0);
+ protected boolean dragToRotate = true;
+ protected Vector3f lookAtOffset = new Vector3f(0, 0, 0);
+ protected boolean leftClickRotate = true;
+ protected boolean rightClickRotate = true;
+ protected Vector3f temp = new Vector3f(0, 0, 0);
+ protected boolean invertYaxis = false;
+ protected boolean invertXaxis = false;
+ protected final static String ChaseCamDown = "ChaseCamDown";
+ protected final static String ChaseCamUp = "ChaseCamUp";
+ protected final static String ChaseCamZoomIn = "ChaseCamZoomIn";
+ protected final static String ChaseCamZoomOut = "ChaseCamZoomOut";
+ protected final static String ChaseCamMoveLeft = "ChaseCamMoveLeft";
+ protected final static String ChaseCamMoveRight = "ChaseCamMoveRight";
+ protected final static String ChaseCamToggleRotate = "ChaseCamToggleRotate";
+
+ /**
+ * Constructs the chase camera
+ * @param cam the application camera
+ * @param target the spatial to follow
+ */
+ public ChaseCamera(Camera cam, final Spatial target) {
+ this(cam);
+ target.addControl(this);
+ }
+
+ /**
+ * Constructs the chase camera
+ * if you use this constructor you have to attach the cam later to a spatial
+ * doing spatial.addControl(chaseCamera);
+ * @param cam the application camera
+ */
+ public ChaseCamera(Camera cam) {
+ this.cam = cam;
+ initialUpVec = cam.getUp().clone();
+ }
+
+ /**
+ * Constructs the chase camera, and registers inputs
+ * if you use this constructor you have to attach the cam later to a spatial
+ * doing spatial.addControl(chaseCamera);
+ * @param cam the application camera
+ * @param inputManager the inputManager of the application to register inputs
+ */
+ public ChaseCamera(Camera cam, InputManager inputManager) {
+ this(cam);
+ registerWithInput(inputManager);
+ }
+
+ /**
+ * Constructs the chase camera, and registers inputs
+ * @param cam the application camera
+ * @param target the spatial to follow
+ * @param inputManager the inputManager of the application to register inputs
+ */
+ public ChaseCamera(Camera cam, final Spatial target, InputManager inputManager) {
+ this(cam, target);
+ registerWithInput(inputManager);
+ }
+
+ public void onAction(String name, boolean keyPressed, float tpf) {
+ if (dragToRotate) {
+ if (name.equals(ChaseCamToggleRotate) && enabled) {
+ if (keyPressed) {
+ canRotate = true;
+ inputManager.setCursorVisible(false);
+ } else {
+ canRotate = false;
+ inputManager.setCursorVisible(true);
+ }
+ }
+ }
+
+ }
+ private boolean zoomin;
+
+ public void onAnalog(String name, float value, float tpf) {
+ if (name.equals(ChaseCamMoveLeft)) {
+ rotateCamera(-value);
+ } else if (name.equals(ChaseCamMoveRight)) {
+ rotateCamera(value);
+ } else if (name.equals(ChaseCamUp)) {
+ vRotateCamera(value);
+ } else if (name.equals(ChaseCamDown)) {
+ vRotateCamera(-value);
+ } else if (name.equals(ChaseCamZoomIn)) {
+ zoomCamera(-value);
+ if (zoomin == false) {
+ distanceLerpFactor = 0;
+ }
+ zoomin = true;
+ } else if (name.equals(ChaseCamZoomOut)) {
+ zoomCamera(+value);
+ if (zoomin == true) {
+ distanceLerpFactor = 0;
+ }
+ zoomin = false;
+ }
+ }
+
+ /**
+ * Registers inputs with the input manager
+ * @param inputManager
+ */
+ public final void registerWithInput(InputManager inputManager) {
+
+ String[] inputs = {ChaseCamToggleRotate,
+ ChaseCamDown,
+ ChaseCamUp,
+ ChaseCamMoveLeft,
+ ChaseCamMoveRight,
+ ChaseCamZoomIn,
+ ChaseCamZoomOut};
+
+ this.inputManager = inputManager;
+ if (!invertYaxis) {
+ inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+ inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+ } else {
+ inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+ inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+ }
+ inputManager.addMapping(ChaseCamZoomIn, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
+ inputManager.addMapping(ChaseCamZoomOut, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
+ if(!invertXaxis){
+ inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+ inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+ }else{
+ inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+ inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+ }
+ inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+ inputManager.addMapping(ChaseCamToggleRotate, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
+
+ inputManager.addListener(this, inputs);
+ }
+
+ /**
+ * Sets custom triggers for toggleing the rotation of the cam
+ * deafult are
+ * new MouseButtonTrigger(MouseInput.BUTTON_LEFT) left mouse button
+ * new MouseButtonTrigger(MouseInput.BUTTON_RIGHT) right mouse button
+ * @param triggers
+ */
+ public void setToggleRotationTrigger(Trigger... triggers) {
+ inputManager.deleteMapping(ChaseCamToggleRotate);
+ inputManager.addMapping(ChaseCamToggleRotate, triggers);
+ inputManager.addListener(this, ChaseCamToggleRotate);
+ }
+
+ /**
+ * Sets custom triggers for zomming in the cam
+ * default is
+ * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true) mouse wheel up
+ * @param triggers
+ */
+ public void setZoomInTrigger(Trigger... triggers) {
+ inputManager.deleteMapping(ChaseCamZoomIn);
+ inputManager.addMapping(ChaseCamZoomIn, triggers);
+ inputManager.addListener(this, ChaseCamZoomIn);
+ }
+
+ /**
+ * Sets custom triggers for zomming out the cam
+ * default is
+ * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false) mouse wheel down
+ * @param triggers
+ */
+ public void setZoomOutTrigger(Trigger... triggers) {
+ inputManager.deleteMapping(ChaseCamZoomOut);
+ inputManager.addMapping(ChaseCamZoomOut, triggers);
+ inputManager.addListener(this, ChaseCamZoomOut);
+ }
+
+ private void computePosition() {
+
+ float hDistance = (distance) * FastMath.sin((FastMath.PI / 2) - vRotation);
+ pos.set(hDistance * FastMath.cos(rotation), (distance) * FastMath.sin(vRotation), hDistance * FastMath.sin(rotation));
+ pos.addLocal(target.getWorldTranslation());
+ }
+
+ //rotate the camera around the target on the horizontal plane
+ private void rotateCamera(float value) {
+ if (!canRotate || !enabled) {
+ return;
+ }
+ rotating = true;
+ targetRotation += value * rotationSpeed;
+
+
+ }
+
+ //move the camera toward or away the target
+ private void zoomCamera(float value) {
+ if (!enabled) {
+ return;
+ }
+
+ zooming = true;
+ targetDistance += value * zoomSpeed;
+ if (targetDistance > maxDistance) {
+ targetDistance = maxDistance;
+ }
+ if (targetDistance < minDistance) {
+ targetDistance = minDistance;
+ }
+ if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
+ targetVRotation = minVerticalRotation;
+ }
+ }
+
+ //rotate the camera around the target on the vertical plane
+ private void vRotateCamera(float value) {
+ if (!canRotate || !enabled) {
+ return;
+ }
+ vRotating = true;
+ targetVRotation += value * rotationSpeed;
+ if (targetVRotation > maxVerticalRotation) {
+ targetVRotation = maxVerticalRotation;
+ }
+ if ((targetVRotation < minVerticalRotation) && (targetDistance > (minDistance + 1.0f))) {
+ targetVRotation = minVerticalRotation;
+ }
+ }
+
+ /**
+ * Updates the camera, should only be called internally
+ */
+ protected void updateCamera(float tpf) {
+ if (enabled) {
+ targetLocation.set(target.getWorldTranslation()).addLocal(lookAtOffset);
+ if (smoothMotion) {
+
+ //computation of target direction
+ targetDir.set(targetLocation).subtractLocal(prevPos);
+ float dist = targetDir.length();
+
+ //Low pass filtering on the target postition to avoid shaking when physics are enabled.
+ if (offsetDistance < dist) {
+ //target moves, start chasing.
+ chasing = true;
+ //target moves, start trailing if it has to.
+ if (trailingEnabled) {
+ trailing = true;
+ }
+ //target moves...
+ targetMoves = true;
+ } else {
+ //if target was moving, we compute a slight offset in rotation to avoid a rought stop of the cam
+ //We do not if the player is rotationg the cam
+ if (targetMoves && !canRotate) {
+ if (targetRotation - rotation > trailingRotationInertia) {
+ targetRotation = rotation + trailingRotationInertia;
+ } else if (targetRotation - rotation < -trailingRotationInertia) {
+ targetRotation = rotation - trailingRotationInertia;
+ }
+ }
+ //Target stops
+ targetMoves = false;
+ }
+
+ //the user is rotating the cam by dragging the mouse
+ if (canRotate) {
+ //reseting the trailing lerp factor
+ trailingLerpFactor = 0;
+ //stop trailing user has the control
+ trailing = false;
+ }
+
+
+ if (trailingEnabled && trailing) {
+ if (targetMoves) {
+ //computation if the inverted direction of the target
+ Vector3f a = targetDir.negate().normalizeLocal();
+ //the x unit vector
+ Vector3f b = Vector3f.UNIT_X;
+ //2d is good enough
+ a.y = 0;
+ //computation of the rotation angle between the x axis and the trail
+ if (targetDir.z > 0) {
+ targetRotation = FastMath.TWO_PI - FastMath.acos(a.dot(b));
+ } else {
+ targetRotation = FastMath.acos(a.dot(b));
+ }
+ if (targetRotation - rotation > FastMath.PI || targetRotation - rotation < -FastMath.PI) {
+ targetRotation -= FastMath.TWO_PI;
+ }
+
+ //if there is an important change in the direction while trailing reset of the lerp factor to avoid jumpy movements
+ if (targetRotation != previousTargetRotation && FastMath.abs(targetRotation - previousTargetRotation) > FastMath.PI / 8) {
+ trailingLerpFactor = 0;
+ }
+ previousTargetRotation = targetRotation;
+ }
+ //computing lerp factor
+ trailingLerpFactor = Math.min(trailingLerpFactor + tpf * tpf * trailingSensitivity, 1);
+ //computing rotation by linear interpolation
+ rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation);
+
+ //if the rotation is near the target rotation we're good, that's over
+ if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {
+ trailing = false;
+ trailingLerpFactor = 0;
+ }
+ }
+
+ //linear interpolation of the distance while chasing
+ if (chasing) {
+ distance = temp.set(targetLocation).subtractLocal(cam.getLocation()).length();
+ distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * chasingSensitivity * 0.05f), 1);
+ distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);
+ if (targetDistance + 0.01f >= distance && targetDistance - 0.01f <= distance) {
+ distanceLerpFactor = 0;
+ chasing = false;
+ }
+ }
+
+ //linear interpolation of the distance while zooming
+ if (zooming) {
+ distanceLerpFactor = Math.min(distanceLerpFactor + (tpf * tpf * zoomSensitivity), 1);
+ distance = FastMath.interpolateLinear(distanceLerpFactor, distance, targetDistance);
+ if (targetDistance + 0.1f >= distance && targetDistance - 0.1f <= distance) {
+ zooming = false;
+ distanceLerpFactor = 0;
+ }
+ }
+
+ //linear interpolation of the rotation while rotating horizontally
+ if (rotating) {
+ rotationLerpFactor = Math.min(rotationLerpFactor + tpf * tpf * rotationSensitivity, 1);
+ rotation = FastMath.interpolateLinear(rotationLerpFactor, rotation, targetRotation);
+ if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) {
+ rotating = false;
+ rotationLerpFactor = 0;
+ }
+ }
+
+ //linear interpolation of the rotation while rotating vertically
+ if (vRotating) {
+ vRotationLerpFactor = Math.min(vRotationLerpFactor + tpf * tpf * rotationSensitivity, 1);
+ vRotation = FastMath.interpolateLinear(vRotationLerpFactor, vRotation, targetVRotation);
+ if (targetVRotation + 0.01f >= vRotation && targetVRotation - 0.01f <= vRotation) {
+ vRotating = false;
+ vRotationLerpFactor = 0;
+ }
+ }
+ //computing the position
+ computePosition();
+ //setting the position at last
+ cam.setLocation(pos.addLocal(lookAtOffset));
+ } else {
+ //easy no smooth motion
+ vRotation = targetVRotation;
+ rotation = targetRotation;
+ distance = targetDistance;
+ computePosition();
+ cam.setLocation(pos.addLocal(lookAtOffset));
+ }
+ //keeping track on the previous position of the target
+ prevPos.set(targetLocation);
+
+ //the cam looks at the target
+ cam.lookAt(targetLocation, initialUpVec);
+
+ }
+ }
+
+ /**
+ * Return the enabled/disabled state of the camera
+ * @return true if the camera is enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Enable or disable the camera
+ * @param enabled true to enable
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ if (!enabled) {
+ canRotate = false; // reset this flag in-case it was on before
+ }
+ }
+
+ /**
+ * Returns the max zoom distance of the camera (default is 40)
+ * @return maxDistance
+ */
+ public float getMaxDistance() {
+ return maxDistance;
+ }
+
+ /**
+ * Sets the max zoom distance of the camera (default is 40)
+ * @param maxDistance
+ */
+ public void setMaxDistance(float maxDistance) {
+ this.maxDistance = maxDistance;
+ }
+
+ /**
+ * Returns the min zoom distance of the camera (default is 1)
+ * @return minDistance
+ */
+ public float getMinDistance() {
+ return minDistance;
+ }
+
+ /**
+ * Sets the min zoom distance of the camera (default is 1)
+ * @return minDistance
+ */
+ public void setMinDistance(float minDistance) {
+ this.minDistance = minDistance;
+ }
+
+ /**
+ * clone this camera for a spatial
+ * @param spatial
+ * @return
+ */
+ public Control cloneForSpatial(Spatial spatial) {
+ ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager);
+ cc.setMaxDistance(getMaxDistance());
+ cc.setMinDistance(getMinDistance());
+ return cc;
+ }
+
+ /**
+ * Sets the spacial for the camera control, should only be used internally
+ * @param spatial
+ */
+ public void setSpatial(Spatial spatial) {
+ target = spatial;
+ if (spatial == null) {
+ return;
+ }
+ computePosition();
+ prevPos = new Vector3f(target.getWorldTranslation());
+ cam.setLocation(pos);
+ }
+
+ /**
+ * update the camera control, should only be used internally
+ * @param tpf
+ */
+ public void update(float tpf) {
+ updateCamera(tpf);
+ }
+
+ /**
+ * renders the camera control, should only be used internally
+ * @param rm
+ * @param vp
+ */
+ public void render(RenderManager rm, ViewPort vp) {
+ //nothing to render
+ }
+
+ /**
+ * Write the camera
+ * @param ex the exporter
+ * @throws IOException
+ */
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule capsule = ex.getCapsule(this);
+ capsule.write(maxDistance, "maxDistance", 40);
+ capsule.write(minDistance, "minDistance", 1);
+ }
+
+ /**
+ * Read the camera
+ * @param im
+ * @throws IOException
+ */
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ maxDistance = ic.readFloat("maxDistance", 40);
+ minDistance = ic.readFloat("minDistance", 1);
+ }
+
+ /**
+ * returns the maximal vertical rotation angle of the camera around the target
+ * @return
+ */
+ public float getMaxVerticalRotation() {
+ return maxVerticalRotation;
+ }
+
+ /**
+ * sets the maximal vertical rotation angle of the camera around the target default is Pi/2;
+ * @param maxVerticalRotation
+ */
+ public void setMaxVerticalRotation(float maxVerticalRotation) {
+ this.maxVerticalRotation = maxVerticalRotation;
+ }
+
+ /**
+ * returns the minimal vertical rotation angle of the camera around the target
+ * @return
+ */
+ public float getMinVerticalRotation() {
+ return minVerticalRotation;
+ }
+
+ /**
+ * sets the minimal vertical rotation angle of the camera around the target default is 0;
+ * @param minHeight
+ */
+ public void setMinVerticalRotation(float minHeight) {
+ this.minVerticalRotation = minHeight;
+ }
+
+ /**
+ * returns true is smmoth motion is enabled for this chase camera
+ * @return
+ */
+ public boolean isSmoothMotion() {
+ return smoothMotion;
+ }
+
+ /**
+ * Enables smooth motion for this chase camera
+ * @param smoothMotion
+ */
+ public void setSmoothMotion(boolean smoothMotion) {
+ this.smoothMotion = smoothMotion;
+ }
+
+ /**
+ * returns the chasing sensitivity
+ * @return
+ */
+ public float getChasingSensitivity() {
+ return chasingSensitivity;
+ }
+
+ /**
+ *
+ * Sets the chasing sensitivity, the lower the value the slower the camera will follow the target when it moves
+ * default is 5
+ * Only has an effect if smoothMotion is set to true and trailing is enabled
+ * @param chasingSensitivity
+ */
+ public void setChasingSensitivity(float chasingSensitivity) {
+ this.chasingSensitivity = chasingSensitivity;
+ }
+
+ /**
+ * Returns the rotation sensitivity
+ * @return
+ */
+ public float getRotationSensitivity() {
+ return rotationSensitivity;
+ }
+
+ /**
+ * Sets the rotation sensitivity, the lower the value the slower the camera will rotates around the target when draging with the mouse
+ * default is 5, values over 5 should have no effect.
+ * If you want a significant slow down try values below 1.
+ * Only has an effect if smoothMotion is set to true
+ * @param rotationSensitivity
+ */
+ public void setRotationSensitivity(float rotationSensitivity) {
+ this.rotationSensitivity = rotationSensitivity;
+ }
+
+ /**
+ * returns true if the trailing is enabled
+ * @return
+ */
+ public boolean isTrailingEnabled() {
+ return trailingEnabled;
+ }
+
+ /**
+ * Enable the camera trailing : The camera smoothly go in the targets trail when it moves.
+ * Only has an effect if smoothMotion is set to true
+ * @param trailingEnabled
+ */
+ public void setTrailingEnabled(boolean trailingEnabled) {
+ this.trailingEnabled = trailingEnabled;
+ }
+
+ /**
+ *
+ * returns the trailing rotation inertia
+ * @return
+ */
+ public float getTrailingRotationInertia() {
+ return trailingRotationInertia;
+ }
+
+ /**
+ * Sets the trailing rotation inertia : default is 0.1. This prevent the camera to roughtly stop when the target stops moving
+ * before the camera reached the trail position.
+ * Only has an effect if smoothMotion is set to true and trailing is enabled
+ * @param trailingRotationInertia
+ */
+ public void setTrailingRotationInertia(float trailingRotationInertia) {
+ this.trailingRotationInertia = trailingRotationInertia;
+ }
+
+ /**
+ * returns the trailing sensitivity
+ * @return
+ */
+ public float getTrailingSensitivity() {
+ return trailingSensitivity;
+ }
+
+ /**
+ * Only has an effect if smoothMotion is set to true and trailing is enabled
+ * Sets the trailing sensitivity, the lower the value, the slower the camera will go in the target trail when it moves.
+ * default is 0.5;
+ * @param trailingSensitivity
+ */
+ public void setTrailingSensitivity(float trailingSensitivity) {
+ this.trailingSensitivity = trailingSensitivity;
+ }
+
+ /**
+ * returns the zoom sensitivity
+ * @return
+ */
+ public float getZoomSensitivity() {
+ return zoomSensitivity;
+ }
+
+ /**
+ * Sets the zoom sensitivity, the lower the value, the slower the camera will zoom in and out.
+ * default is 5.
+ * @param zoomSensitivity
+ */
+ public void setZoomSensitivity(float zoomSensitivity) {
+ this.zoomSensitivity = zoomSensitivity;
+ }
+
+ /**
+ * Sets the default distance at start of applicaiton
+ * @param defaultDistance
+ */
+ public void setDefaultDistance(float defaultDistance) {
+ distance = defaultDistance;
+ targetDistance = distance;
+ }
+
+ /**
+ * sets the default horizontal rotation of the camera at start of the application
+ * @param angle
+ */
+ public void setDefaultHorizontalRotation(float angle) {
+ rotation = angle;
+ targetRotation = angle;
+ }
+
+ /**
+ * sets the default vertical rotation of the camera at start of the application
+ * @param angle
+ */
+ public void setDefaultVerticalRotation(float angle) {
+ vRotation = angle;
+ targetVRotation = angle;
+ }
+
+ /**
+ * @return If drag to rotate feature is enabled.
+ *
+ * @see FlyByCamera#setDragToRotate(boolean)
+ */
+ public boolean isDragToRotate() {
+ return dragToRotate;
+ }
+
+ /**
+ * @param dragToRotate When true, the user must hold the mouse button
+ * and drag over the screen to rotate the camera, and the cursor is
+ * visible until dragged. Otherwise, the cursor is invisible at all times
+ * and holding the mouse button is not needed to rotate the camera.
+ * This feature is disabled by default.
+ */
+ public void setDragToRotate(boolean dragToRotate) {
+ this.dragToRotate = dragToRotate;
+ this.canRotate = !dragToRotate;
+ inputManager.setCursorVisible(dragToRotate);
+ }
+
+ /**
+ * return the current distance from the camera to the target
+ * @return
+ */
+ public float getDistanceToTarget() {
+ return distance;
+ }
+
+ /**
+ * returns the current horizontal rotation around the target in radians
+ * @return
+ */
+ public float getHorizontalRotation() {
+ return rotation;
+ }
+
+ /**
+ * returns the current vertical rotation around the target in radians.
+ * @return
+ */
+ public float getVerticalRotation() {
+ return vRotation;
+ }
+
+ /**
+ * returns the offset from the target's position where the camera looks at
+ * @return
+ */
+ public Vector3f getLookAtOffset() {
+ return lookAtOffset;
+ }
+
+ /**
+ * Sets the offset from the target's position where the camera looks at
+ * @param lookAtOffset
+ */
+ public void setLookAtOffset(Vector3f lookAtOffset) {
+ this.lookAtOffset = lookAtOffset;
+ }
+
+ /**
+ * Sets the up vector of the camera used for the lookAt on the target
+ * @param up
+ */
+ public void setUpVector(Vector3f up){
+ initialUpVec=up;
+ }
+
+ /**
+ * Returns the up vector of the camera used for the lookAt on the target
+ * @return
+ */
+ public Vector3f getUpVector(){
+ return initialUpVec;
+ }
+
+ /**
+ * invert the vertical axis movement of the mouse
+ * @param invertYaxis
+ */
+ public void setInvertVerticalAxis(boolean invertYaxis) {
+ this.invertYaxis = invertYaxis;
+ inputManager.deleteMapping(ChaseCamDown);
+ inputManager.deleteMapping(ChaseCamUp);
+ if (!invertYaxis) {
+ inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+ inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+ } else {
+ inputManager.addMapping(ChaseCamDown, new MouseAxisTrigger(MouseInput.AXIS_Y, false));
+ inputManager.addMapping(ChaseCamUp, new MouseAxisTrigger(MouseInput.AXIS_Y, true));
+ }
+ inputManager.addListener(this, ChaseCamDown, ChaseCamUp);
+ }
+
+ /**
+ * invert the Horizontal axis movement of the mouse
+ * @param invertXaxis
+ */
+ public void setInvertHorizontalAxis(boolean invertXaxis) {
+ this.invertXaxis = invertXaxis;
+ inputManager.deleteMapping(ChaseCamMoveLeft);
+ inputManager.deleteMapping(ChaseCamMoveRight);
+ if(!invertXaxis){
+ inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+ inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+ }else{
+ inputManager.addMapping(ChaseCamMoveLeft, new MouseAxisTrigger(MouseInput.AXIS_X, false));
+ inputManager.addMapping(ChaseCamMoveRight, new MouseAxisTrigger(MouseInput.AXIS_X, true));
+ }
+ inputManager.addListener(this, ChaseCamMoveLeft, ChaseCamMoveRight);
+ }
+}
diff --git a/engine/src/core/com/jme3/input/FlyByCamera.java b/engine/src/core/com/jme3/input/FlyByCamera.java
new file mode 100644
index 0000000..7b439ef
--- /dev/null
+++ b/engine/src/core/com/jme3/input/FlyByCamera.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+import com.jme3.collision.MotionAllowedListener;
+import com.jme3.input.controls.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+
+/**
+ * A first person view camera controller.
+ * After creation, you must register the camera controller with the
+ * dispatcher using #registerWithDispatcher().
+ *
+ * Controls:
+ * - Move the mouse to rotate the camera
+ * - Mouse wheel for zooming in or out
+ * - WASD keys for moving forward/backward and strafing
+ * - QZ keys raise or lower the camera
+ */
+public class FlyByCamera implements AnalogListener, ActionListener {
+
+ private static String[] mappings = new String[]{
+ "FLYCAM_Left",
+ "FLYCAM_Right",
+ "FLYCAM_Up",
+ "FLYCAM_Down",
+
+ "FLYCAM_StrafeLeft",
+ "FLYCAM_StrafeRight",
+ "FLYCAM_Forward",
+ "FLYCAM_Backward",
+
+ "FLYCAM_ZoomIn",
+ "FLYCAM_ZoomOut",
+ "FLYCAM_RotateDrag",
+
+ "FLYCAM_Rise",
+ "FLYCAM_Lower"
+ };
+
+ protected Camera cam;
+ protected Vector3f initialUpVec;
+ protected float rotationSpeed = 1f;
+ protected float moveSpeed = 3f;
+ protected MotionAllowedListener motionAllowed = null;
+ protected boolean enabled = true;
+ protected boolean dragToRotate = false;
+ protected boolean canRotate = false;
+ protected InputManager inputManager;
+
+ /**
+ * Creates a new FlyByCamera to control the given Camera object.
+ * @param cam
+ */
+ public FlyByCamera(Camera cam){
+ this.cam = cam;
+ initialUpVec = cam.getUp().clone();
+ }
+
+ /**
+ * Sets the up vector that should be used for the camera.
+ * @param upVec
+ */
+ public void setUpVector(Vector3f upVec) {
+ initialUpVec.set(upVec);
+ }
+
+ public void setMotionAllowedListener(MotionAllowedListener listener){
+ this.motionAllowed = listener;
+ }
+
+ /**
+ * Sets the move speed. The speed is given in world units per second.
+ * @param moveSpeed
+ */
+ public void setMoveSpeed(float moveSpeed){
+ this.moveSpeed = moveSpeed;
+ }
+
+ /**
+ * Sets the rotation speed.
+ * @param rotationSpeed
+ */
+ public void setRotationSpeed(float rotationSpeed){
+ this.rotationSpeed = rotationSpeed;
+ }
+
+ /**
+ * @param enable If false, the camera will ignore input.
+ */
+ public void setEnabled(boolean enable){
+ if (enabled && !enable){
+ if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){
+ inputManager.setCursorVisible(true);
+ }
+ }
+ enabled = enable;
+ }
+
+ /**
+ * @return If enabled
+ * @see FlyByCamera#setEnabled(boolean)
+ */
+ public boolean isEnabled(){
+ return enabled;
+ }
+
+ /**
+ * @return If drag to rotate feature is enabled.
+ *
+ * @see FlyByCamera#setDragToRotate(boolean)
+ */
+ public boolean isDragToRotate() {
+ return dragToRotate;
+ }
+
+ /**
+ * Set if drag to rotate mode is enabled.
+ *
+ * When true, the user must hold the mouse button
+ * and drag over the screen to rotate the camera, and the cursor is
+ * visible until dragged. Otherwise, the cursor is invisible at all times
+ * and holding the mouse button is not needed to rotate the camera.
+ * This feature is disabled by default.
+ *
+ * @param dragToRotate True if drag to rotate mode is enabled.
+ */
+ public void setDragToRotate(boolean dragToRotate) {
+ this.dragToRotate = dragToRotate;
+ if (inputManager != null) {
+ inputManager.setCursorVisible(dragToRotate);
+ }
+ }
+
+ /**
+ * Registers the FlyByCamera to receive input events from the provided
+ * Dispatcher.
+ * @param inputManager
+ */
+ public void registerWithInput(InputManager inputManager){
+ this.inputManager = inputManager;
+
+ // both mouse and button - rotation of cam
+ inputManager.addMapping("FLYCAM_Left", new MouseAxisTrigger(MouseInput.AXIS_X, true),
+ new KeyTrigger(KeyInput.KEY_LEFT));
+
+ inputManager.addMapping("FLYCAM_Right", new MouseAxisTrigger(MouseInput.AXIS_X, false),
+ new KeyTrigger(KeyInput.KEY_RIGHT));
+
+ inputManager.addMapping("FLYCAM_Up", new MouseAxisTrigger(MouseInput.AXIS_Y, false),
+ new KeyTrigger(KeyInput.KEY_UP));
+
+ inputManager.addMapping("FLYCAM_Down", new MouseAxisTrigger(MouseInput.AXIS_Y, true),
+ new KeyTrigger(KeyInput.KEY_DOWN));
+
+ // mouse only - zoom in/out with wheel, and rotate drag
+ inputManager.addMapping("FLYCAM_ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
+ inputManager.addMapping("FLYCAM_ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
+ inputManager.addMapping("FLYCAM_RotateDrag", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+
+ // keyboard only WASD for movement and WZ for rise/lower height
+ inputManager.addMapping("FLYCAM_StrafeLeft", new KeyTrigger(KeyInput.KEY_A));
+ inputManager.addMapping("FLYCAM_StrafeRight", new KeyTrigger(KeyInput.KEY_D));
+ inputManager.addMapping("FLYCAM_Forward", new KeyTrigger(KeyInput.KEY_W));
+ inputManager.addMapping("FLYCAM_Backward", new KeyTrigger(KeyInput.KEY_S));
+ inputManager.addMapping("FLYCAM_Rise", new KeyTrigger(KeyInput.KEY_Q));
+ inputManager.addMapping("FLYCAM_Lower", new KeyTrigger(KeyInput.KEY_Z));
+
+ inputManager.addListener(this, mappings);
+ inputManager.setCursorVisible(dragToRotate || !isEnabled());
+
+ Joystick[] joysticks = inputManager.getJoysticks();
+ if (joysticks != null && joysticks.length > 0){
+ Joystick joystick = joysticks[0];
+ joystick.assignAxis("FLYCAM_StrafeRight", "FLYCAM_StrafeLeft", JoyInput.AXIS_POV_X);
+ joystick.assignAxis("FLYCAM_Forward", "FLYCAM_Backward", JoyInput.AXIS_POV_Y);
+ joystick.assignAxis("FLYCAM_Right", "FLYCAM_Left", joystick.getXAxisIndex());
+ joystick.assignAxis("FLYCAM_Down", "FLYCAM_Up", joystick.getYAxisIndex());
+ }
+ }
+
+ /**
+ * Registers the FlyByCamera to receive input events from the provided
+ * Dispatcher.
+ * @param inputManager
+ */
+ public void unregisterInput(){
+
+ if (inputManager == null) {
+ return;
+ }
+
+ for (String s : mappings) {
+ if (inputManager.hasMapping(s)) {
+ inputManager.deleteMapping( s );
+ }
+ }
+
+ inputManager.removeListener(this);
+ inputManager.setCursorVisible(!dragToRotate);
+
+ Joystick[] joysticks = inputManager.getJoysticks();
+ if (joysticks != null && joysticks.length > 0){
+ Joystick joystick = joysticks[0];
+
+ // No way to unassing axis
+ }
+ }
+
+ protected void rotateCamera(float value, Vector3f axis){
+ if (dragToRotate){
+ if (canRotate){
+// value = -value;
+ }else{
+ return;
+ }
+ }
+
+ Matrix3f mat = new Matrix3f();
+ mat.fromAngleNormalAxis(rotationSpeed * value, axis);
+
+ Vector3f up = cam.getUp();
+ Vector3f left = cam.getLeft();
+ Vector3f dir = cam.getDirection();
+
+ mat.mult(up, up);
+ mat.mult(left, left);
+ mat.mult(dir, dir);
+
+ Quaternion q = new Quaternion();
+ q.fromAxes(left, up, dir);
+ q.normalize();
+
+ cam.setAxes(q);
+ }
+
+ protected void zoomCamera(float value){
+ // derive fovY value
+ float h = cam.getFrustumTop();
+ float w = cam.getFrustumRight();
+ float aspect = w / h;
+
+ float near = cam.getFrustumNear();
+
+ float fovY = FastMath.atan(h / near)
+ / (FastMath.DEG_TO_RAD * .5f);
+ fovY += value * 0.1f;
+
+ h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near;
+ w = h * aspect;
+
+ cam.setFrustumTop(h);
+ cam.setFrustumBottom(-h);
+ cam.setFrustumLeft(-w);
+ cam.setFrustumRight(w);
+ }
+
+ protected void riseCamera(float value){
+ Vector3f vel = new Vector3f(0, value * moveSpeed, 0);
+ Vector3f pos = cam.getLocation().clone();
+
+ if (motionAllowed != null)
+ motionAllowed.checkMotionAllowed(pos, vel);
+ else
+ pos.addLocal(vel);
+
+ cam.setLocation(pos);
+ }
+
+ protected void moveCamera(float value, boolean sideways){
+ Vector3f vel = new Vector3f();
+ Vector3f pos = cam.getLocation().clone();
+
+ if (sideways){
+ cam.getLeft(vel);
+ }else{
+ cam.getDirection(vel);
+ }
+ vel.multLocal(value * moveSpeed);
+
+ if (motionAllowed != null)
+ motionAllowed.checkMotionAllowed(pos, vel);
+ else
+ pos.addLocal(vel);
+
+ cam.setLocation(pos);
+ }
+
+ public void onAnalog(String name, float value, float tpf) {
+ if (!enabled)
+ return;
+
+ if (name.equals("FLYCAM_Left")){
+ rotateCamera(value, initialUpVec);
+ }else if (name.equals("FLYCAM_Right")){
+ rotateCamera(-value, initialUpVec);
+ }else if (name.equals("FLYCAM_Up")){
+ rotateCamera(-value, cam.getLeft());
+ }else if (name.equals("FLYCAM_Down")){
+ rotateCamera(value, cam.getLeft());
+ }else if (name.equals("FLYCAM_Forward")){
+ moveCamera(value, false);
+ }else if (name.equals("FLYCAM_Backward")){
+ moveCamera(-value, false);
+ }else if (name.equals("FLYCAM_StrafeLeft")){
+ moveCamera(value, true);
+ }else if (name.equals("FLYCAM_StrafeRight")){
+ moveCamera(-value, true);
+ }else if (name.equals("FLYCAM_Rise")){
+ riseCamera(value);
+ }else if (name.equals("FLYCAM_Lower")){
+ riseCamera(-value);
+ }else if (name.equals("FLYCAM_ZoomIn")){
+ zoomCamera(value);
+ }else if (name.equals("FLYCAM_ZoomOut")){
+ zoomCamera(-value);
+ }
+ }
+
+ public void onAction(String name, boolean value, float tpf) {
+ if (!enabled)
+ return;
+
+ if (name.equals("FLYCAM_RotateDrag") && dragToRotate){
+ canRotate = value;
+ inputManager.setCursorVisible(!value);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/Input.java b/engine/src/core/com/jme3/input/Input.java
new file mode 100644
index 0000000..1cece68
--- /dev/null
+++ b/engine/src/core/com/jme3/input/Input.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * Abstract interface for an input device.
+ *
+ * @see MouseInput
+ * @see KeyInput
+ * @see JoyInput
+ */
+public interface Input {
+
+ /**
+ * Initializes the native side to listen into events from the device.
+ */
+ public void initialize();
+
+ /**
+ * Queries the device for input. All events should be sent to the
+ * RawInputListener set with setInputListener.
+ *
+ * @see #setInputListener(com.jme3.input.RawInputListener)
+ */
+ public void update();
+
+ /**
+ * Ceases listening to events from the device.
+ */
+ public void destroy();
+
+ /**
+ * @return True if the device has been initialized and not destroyed.
+ * @see #initialize()
+ * @see #destroy()
+ */
+ public boolean isInitialized();
+
+ /**
+ * Sets the input listener to receive events from this device. The
+ * appropriate events should be dispatched through the callbacks
+ * in RawInputListener.
+ * @param listener
+ */
+ public void setInputListener(RawInputListener listener);
+
+ /**
+ * @return The current absolute time as nanoseconds. This time is expected
+ * to be relative to the time given in InputEvents time property.
+ */
+ public long getInputTimeNanos();
+}
diff --git a/engine/src/core/com/jme3/input/InputManager.java b/engine/src/core/com/jme3/input/InputManager.java
new file mode 100644
index 0000000..23f2988
--- /dev/null
+++ b/engine/src/core/com/jme3/input/InputManager.java
@@ -0,0 +1,881 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input;
+
+import com.jme3.app.Application;
+import com.jme3.input.controls.*;
+import com.jme3.input.event.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The <code>InputManager</code> is responsible for converting input events
+ * received from the Key, Mouse and Joy Input implementations into an
+ * abstract, input device independent representation that user code can use.
+ * <p>
+ * By default an <code>InputManager</code> is included with every Application instance for use
+ * in user code to query input, unless the Application is created as headless
+ * or with input explicitly disabled.
+ * <p>
+ * The input manager has two concepts, a {@link Trigger} and a mapping.
+ * A trigger represents a specific input trigger, such as a key button,
+ * or a mouse axis. A mapping represents a link onto one or several triggers,
+ * when the appropriate trigger is activated (e.g. a key is pressed), the
+ * mapping will be invoked. Any listeners registered to receive an event
+ * from the mapping will have an event raised.
+ * <p>
+ * There are two types of events that {@link InputListener input listeners}
+ * can receive, one is {@link ActionListener#onAction(java.lang.String, boolean, float) action}
+ * events and another is {@link AnalogListener#onAnalog(java.lang.String, float, float) analog}
+ * events.
+ * <p>
+ * <code>onAction</code> events are raised when the specific input
+ * activates or deactivates. For a digital input such as key press, the <code>onAction()</code>
+ * event will be raised with the <code>isPressed</code> argument equal to true,
+ * when the key is released, <code>onAction</code> is called again but this time
+ * with the <code>isPressed</code> argument set to false.
+ * For analog inputs, the <code>onAction</code> method will be called any time
+ * the input is non-zero, however an exception to this is for joystick axis inputs,
+ * which are only called when the input is above the {@link InputManager#setAxisDeadZone(float) dead zone}.
+ * <p>
+ * <code>onAnalog</code> events are raised every frame while the input is activated.
+ * For digital inputs, every frame that the input is active will cause the
+ * <code>onAnalog</code> method to be called, the argument <code>value</code>
+ * argument will equal to the frame's time per frame (TPF) value but only
+ * for digital inputs. For analog inputs however, the <code>value</code> argument
+ * will equal the actual analog value.
+ */
+public class InputManager implements RawInputListener {
+
+ private static final Logger logger = Logger.getLogger(InputManager.class.getName());
+ private final KeyInput keys;
+ private final MouseInput mouse;
+ private final JoyInput joystick;
+ private final TouchInput touch;
+ private float frameTPF;
+ private long lastLastUpdateTime = 0;
+ private long lastUpdateTime = 0;
+ private long frameDelta = 0;
+ private long firstTime = 0;
+ private boolean eventsPermitted = false;
+ private boolean mouseVisible = true;
+ private boolean safeMode = false;
+ private float axisDeadZone = 0.05f;
+ private Vector2f cursorPos = new Vector2f();
+ private Joystick[] joysticks;
+ private final IntMap<ArrayList<Mapping>> bindings = new IntMap<ArrayList<Mapping>>();
+ private final HashMap<String, Mapping> mappings = new HashMap<String, Mapping>();
+ private final IntMap<Long> pressedButtons = new IntMap<Long>();
+ private final IntMap<Float> axisValues = new IntMap<Float>();
+ private ArrayList<RawInputListener> rawListeners = new ArrayList<RawInputListener>();
+ private RawInputListener[] rawListenerArray = null;
+ private ArrayList<InputEvent> inputQueue = new ArrayList<InputEvent>();
+
+ private static class Mapping {
+
+ private final String name;
+ private final ArrayList<Integer> triggers = new ArrayList<Integer>();
+ private final ArrayList<InputListener> listeners = new ArrayList<InputListener>();
+
+ public Mapping(String name) {
+ this.name = name;
+ }
+ }
+
+ /**
+ * Initializes the InputManager.
+ *
+ * <p>This should only be called internally in {@link Application}.
+ *
+ * @param mouse
+ * @param keys
+ * @param joystick
+ * @param touch
+ * @throws IllegalArgumentException If either mouseInput or keyInput are null.
+ */
+ public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) {
+ if (keys == null || mouse == null) {
+ throw new NullPointerException("Mouse or keyboard cannot be null");
+ }
+
+ this.keys = keys;
+ this.mouse = mouse;
+ this.joystick = joystick;
+ this.touch = touch;
+
+ keys.setInputListener(this);
+ mouse.setInputListener(this);
+ if (joystick != null) {
+ joystick.setInputListener(this);
+ joysticks = joystick.loadJoysticks(this);
+ }
+ if (touch != null) {
+ touch.setInputListener(this);
+ }
+
+ firstTime = keys.getInputTimeNanos();
+ }
+
+ private void invokeActions(int hash, boolean pressed) {
+ ArrayList<Mapping> maps = bindings.get(hash);
+ if (maps == null) {
+ return;
+ }
+
+ int size = maps.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Mapping mapping = maps.get(i);
+ ArrayList<InputListener> listeners = mapping.listeners;
+ int listenerSize = listeners.size();
+ for (int j = listenerSize - 1; j >= 0; j--) {
+ InputListener listener = listeners.get(j);
+ if (listener instanceof ActionListener) {
+ ((ActionListener) listener).onAction(mapping.name, pressed, frameTPF);
+ }
+ }
+ }
+ }
+
+ private float computeAnalogValue(long timeDelta) {
+ if (safeMode || frameDelta == 0) {
+ return 1f;
+ } else {
+ return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1);
+ }
+ }
+
+ private void invokeTimedActions(int hash, long time, boolean pressed) {
+ if (!bindings.containsKey(hash)) {
+ return;
+ }
+
+ if (pressed) {
+ pressedButtons.put(hash, time);
+ } else {
+ Long pressTimeObj = pressedButtons.remove(hash);
+ if (pressTimeObj == null) {
+ return; // under certain circumstances it can be null, ignore
+ } // the event then.
+
+ long pressTime = pressTimeObj;
+ long lastUpdate = lastLastUpdateTime;
+ long releaseTime = time;
+ long timeDelta = releaseTime - Math.max(pressTime, lastUpdate);
+
+ if (timeDelta > 0) {
+ invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
+ }
+ }
+ }
+
+ private void invokeUpdateActions() {
+ for (Entry<Long> pressedButton : pressedButtons) {
+ int hash = pressedButton.getKey();
+
+ long pressTime = pressedButton.getValue();
+ long timeDelta = lastUpdateTime - Math.max(lastLastUpdateTime, pressTime);
+
+ if (timeDelta > 0) {
+ invokeAnalogs(hash, computeAnalogValue(timeDelta), false);
+ }
+ }
+
+ for (Entry<Float> axisValue : axisValues) {
+ int hash = axisValue.getKey();
+ float value = axisValue.getValue();
+ invokeAnalogs(hash, value * frameTPF, true);
+ }
+ }
+
+ private void invokeAnalogs(int hash, float value, boolean isAxis) {
+ ArrayList<Mapping> maps = bindings.get(hash);
+ if (maps == null) {
+ return;
+ }
+
+ if (!isAxis) {
+ value *= frameTPF;
+ }
+
+ int size = maps.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Mapping mapping = maps.get(i);
+ ArrayList<InputListener> listeners = mapping.listeners;
+ int listenerSize = listeners.size();
+ for (int j = listenerSize - 1; j >= 0; j--) {
+ InputListener listener = listeners.get(j);
+ if (listener instanceof AnalogListener) {
+ // NOTE: multiply by TPF for any button bindings
+ ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
+ }
+ }
+ }
+ }
+
+ private void invokeAnalogsAndActions(int hash, float value, boolean applyTpf) {
+ if (value < axisDeadZone) {
+ invokeAnalogs(hash, value, !applyTpf);
+ return;
+ }
+
+ ArrayList<Mapping> maps = bindings.get(hash);
+ if (maps == null) {
+ return;
+ }
+
+ boolean valueChanged = !axisValues.containsKey(hash);
+ if (applyTpf) {
+ value *= frameTPF;
+ }
+
+ int size = maps.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Mapping mapping = maps.get(i);
+ ArrayList<InputListener> listeners = mapping.listeners;
+ int listenerSize = listeners.size();
+ for (int j = listenerSize - 1; j >= 0; j--) {
+ InputListener listener = listeners.get(j);
+
+ if (listener instanceof ActionListener && valueChanged) {
+ ((ActionListener) listener).onAction(mapping.name, true, frameTPF);
+ }
+
+ if (listener instanceof AnalogListener) {
+ ((AnalogListener) listener).onAnalog(mapping.name, value, frameTPF);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void beginInput() {
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void endInput() {
+ }
+
+ private void onJoyAxisEventQueued(JoyAxisEvent evt) {
+// for (int i = 0; i < rawListeners.size(); i++){
+// rawListeners.get(i).onJoyAxisEvent(evt);
+// }
+
+ int joyId = evt.getJoyIndex();
+ int axis = evt.getAxisIndex();
+ float value = evt.getValue();
+ if (value < axisDeadZone && value > -axisDeadZone) {
+ int hash1 = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+ int hash2 = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+
+ Float val1 = axisValues.get(hash1);
+ Float val2 = axisValues.get(hash2);
+
+ if (val1 != null && val1.floatValue() > axisDeadZone) {
+ invokeActions(hash1, false);
+ }
+ if (val2 != null && val2.floatValue() > axisDeadZone) {
+ invokeActions(hash2, false);
+ }
+
+ axisValues.remove(hash1);
+ axisValues.remove(hash2);
+
+ } else if (value < 0) {
+ int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+ int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+ invokeAnalogsAndActions(hash, -value, true);
+ axisValues.put(hash, -value);
+ axisValues.remove(otherHash);
+ } else {
+ int hash = JoyAxisTrigger.joyAxisHash(joyId, axis, false);
+ int otherHash = JoyAxisTrigger.joyAxisHash(joyId, axis, true);
+ invokeAnalogsAndActions(hash, value, true);
+ axisValues.put(hash, value);
+ axisValues.remove(otherHash);
+ }
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void onJoyAxisEvent(JoyAxisEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
+ }
+
+ inputQueue.add(evt);
+ }
+
+ private void onJoyButtonEventQueued(JoyButtonEvent evt) {
+// for (int i = 0; i < rawListeners.size(); i++){
+// rawListeners.get(i).onJoyButtonEvent(evt);
+// }
+
+ int hash = JoyButtonTrigger.joyButtonHash(evt.getJoyIndex(), evt.getButtonIndex());
+ invokeActions(hash, evt.isPressed());
+ invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void onJoyButtonEvent(JoyButtonEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("JoyInput has raised an event at an illegal time.");
+ }
+
+ inputQueue.add(evt);
+ }
+
+ private void onMouseMotionEventQueued(MouseMotionEvent evt) {
+// for (int i = 0; i < rawListeners.size(); i++){
+// rawListeners.get(i).onMouseMotionEvent(evt);
+// }
+
+ if (evt.getDX() != 0) {
+ float val = Math.abs(evt.getDX()) / 1024f;
+ invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_X, evt.getDX() < 0), val, false);
+ }
+ if (evt.getDY() != 0) {
+ float val = Math.abs(evt.getDY()) / 1024f;
+ invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_Y, evt.getDY() < 0), val, false);
+ }
+ if (evt.getDeltaWheel() != 0) {
+ float val = Math.abs(evt.getDeltaWheel()) / 100f;
+ invokeAnalogsAndActions(MouseAxisTrigger.mouseAxisHash(MouseInput.AXIS_WHEEL, evt.getDeltaWheel() < 0), val, false);
+ }
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void onMouseMotionEvent(MouseMotionEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
+ }
+
+ cursorPos.set(evt.getX(), evt.getY());
+ inputQueue.add(evt);
+ }
+
+ private void onMouseButtonEventQueued(MouseButtonEvent evt) {
+ int hash = MouseButtonTrigger.mouseButtonHash(evt.getButtonIndex());
+ invokeActions(hash, evt.isPressed());
+ invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void onMouseButtonEvent(MouseButtonEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time.");
+ }
+ //updating cursor pos on click, so that non android touch events can properly update cursor position.
+ cursorPos.set(evt.getX(), evt.getY());
+ inputQueue.add(evt);
+ }
+
+ private void onKeyEventQueued(KeyInputEvent evt) {
+ if (evt.isRepeating()) {
+ return; // repeat events not used for bindings
+ }
+
+ int hash = KeyTrigger.keyHash(evt.getKeyCode());
+ invokeActions(hash, evt.isPressed());
+ invokeTimedActions(hash, evt.getTime(), evt.isPressed());
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ public void onKeyEvent(KeyInputEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("KeyInput has raised an event at an illegal time.");
+ }
+
+ inputQueue.add(evt);
+ }
+
+ /**
+ * Set the deadzone for joystick axes.
+ *
+ * <p>{@link ActionListener#onAction(java.lang.String, boolean, float) }
+ * events will only be raised if the joystick axis value is greater than
+ * the <code>deadZone</code>.
+ *
+ * @param deadZone the deadzone for joystick axes.
+ */
+ public void setAxisDeadZone(float deadZone) {
+ this.axisDeadZone = deadZone;
+ }
+
+ /**
+ * Returns the deadzone for joystick axes.
+ *
+ * @return the deadzone for joystick axes.
+ */
+ public float getAxisDeadZone() {
+ return axisDeadZone;
+ }
+
+ /**
+ * Adds a new listener to receive events on the given mappings.
+ *
+ * <p>The given InputListener will be registered to receive events
+ * on the specified mapping names. When a mapping raises an event, the
+ * listener will have its appropriate method invoked, either
+ * {@link ActionListener#onAction(java.lang.String, boolean, float) }
+ * or {@link AnalogListener#onAnalog(java.lang.String, float, float) }
+ * depending on which interface the <code>listener</code> implements.
+ * If the listener implements both interfaces, then it will receive the
+ * appropriate event for each method.
+ *
+ * @param listener The listener to register to receive input events.
+ * @param mappingNames The mapping names which the listener will receive
+ * events from.
+ *
+ * @see InputManager#removeListener(com.jme3.input.controls.InputListener)
+ */
+ public void addListener(InputListener listener, String... mappingNames) {
+ for (String mappingName : mappingNames) {
+ Mapping mapping = mappings.get(mappingName);
+ if (mapping == null) {
+ mapping = new Mapping(mappingName);
+ mappings.put(mappingName, mapping);
+ }
+ if (!mapping.listeners.contains(listener)) {
+ mapping.listeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Removes a listener from receiving events.
+ *
+ * <p>This will unregister the listener from any mappings that it
+ * was previously registered with via
+ * {@link InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[]) }.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @see InputManager#addListener(com.jme3.input.controls.InputListener, java.lang.String[])
+ */
+ public void removeListener(InputListener listener) {
+ for (Mapping mapping : mappings.values()) {
+ mapping.listeners.remove(listener);
+ }
+ }
+
+ /**
+ * Create a new mapping to the given triggers.
+ *
+ * <p>
+ * The given mapping will be assigned to the given triggers, when
+ * any of the triggers given raise an event, the listeners
+ * registered to the mappings will receive appropriate events.
+ *
+ * @param mappingName The mapping name to assign.
+ * @param triggers The triggers to which the mapping is to be registered.
+ *
+ * @see InputManager#deleteMapping(java.lang.String)
+ */
+ public void addMapping(String mappingName, Trigger... triggers) {
+ Mapping mapping = mappings.get(mappingName);
+ if (mapping == null) {
+ mapping = new Mapping(mappingName);
+ mappings.put(mappingName, mapping);
+ }
+
+ for (Trigger trigger : triggers) {
+ int hash = trigger.triggerHashCode();
+ ArrayList<Mapping> names = bindings.get(hash);
+ if (names == null) {
+ names = new ArrayList<Mapping>();
+ bindings.put(hash, names);
+ }
+ if (!names.contains(mapping)) {
+ names.add(mapping);
+ mapping.triggers.add(hash);
+ } else {
+ logger.log(Level.WARNING, "Attempted to add mapping \"{0}\" twice to trigger.", mappingName);
+ }
+ }
+ }
+
+ /**
+ * Returns true if this InputManager has a mapping registered
+ * for the given mappingName.
+ *
+ * @param mappingName The mapping name to check.
+ *
+ * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
+ * @see InputManager#deleteMapping(java.lang.String)
+ */
+ public boolean hasMapping(String mappingName) {
+ return mappings.containsKey(mappingName);
+ }
+
+ /**
+ * Deletes a mapping from receiving trigger events.
+ *
+ * <p>
+ * The given mapping will no longer be assigned to receive trigger
+ * events.
+ *
+ * @param mappingName The mapping name to unregister.
+ *
+ * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[])
+ */
+ public void deleteMapping(String mappingName) {
+ Mapping mapping = mappings.remove(mappingName);
+ if (mapping == null) {
+ throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
+ }
+
+ ArrayList<Integer> triggers = mapping.triggers;
+ for (int i = triggers.size() - 1; i >= 0; i--) {
+ int hash = triggers.get(i);
+ ArrayList<Mapping> maps = bindings.get(hash);
+ maps.remove(mapping);
+ }
+ }
+
+ /**
+ * Deletes a specific trigger registered to a mapping.
+ *
+ * <p>
+ * The given mapping will no longer receive events raised by the
+ * trigger.
+ *
+ * @param mappingName The mapping name to cease receiving events from the
+ * trigger.
+ * @param trigger The trigger to no longer invoke events on the mapping.
+ */
+ public void deleteTrigger(String mappingName, Trigger trigger) {
+ Mapping mapping = mappings.get(mappingName);
+ if (mapping == null) {
+ throw new IllegalArgumentException("Cannot find mapping: " + mappingName);
+ }
+
+ ArrayList<Mapping> maps = bindings.get(trigger.triggerHashCode());
+ maps.remove(mapping);
+
+ }
+
+ /**
+ * Clears all the input mappings from this InputManager.
+ * Consequently, also clears all of the
+ * InputListeners as well.
+ */
+ public void clearMappings() {
+ mappings.clear();
+ bindings.clear();
+ reset();
+ }
+
+ /**
+ * Do not use.
+ * Called to reset pressed keys or buttons when focus is restored.
+ */
+ public void reset() {
+ pressedButtons.clear();
+ axisValues.clear();
+ }
+
+ /**
+ * Returns whether the mouse cursor is visible or not.
+ *
+ * <p>By default the cursor is visible.
+ *
+ * @return whether the mouse cursor is visible or not.
+ *
+ * @see InputManager#setCursorVisible(boolean)
+ */
+ public boolean isCursorVisible() {
+ return mouseVisible;
+ }
+
+ /**
+ * Set whether the mouse cursor should be visible or not.
+ *
+ * @param visible whether the mouse cursor should be visible or not.
+ */
+ public void setCursorVisible(boolean visible) {
+ if (mouseVisible != visible) {
+ mouseVisible = visible;
+ mouse.setCursorVisible(mouseVisible);
+ }
+ }
+
+ /**
+ * Returns the current cursor position. The position is relative to the
+ * bottom-left of the screen and is in pixels.
+ *
+ * @return the current cursor position
+ */
+ public Vector2f getCursorPosition() {
+ return cursorPos;
+ }
+
+ /**
+ * Returns an array of all joysticks installed on the system.
+ *
+ * @return an array of all joysticks installed on the system.
+ */
+ public Joystick[] getJoysticks() {
+ return joysticks;
+ }
+
+ /**
+ * Adds a {@link RawInputListener} to receive raw input events.
+ *
+ * <p>
+ * Any raw input listeners registered to this <code>InputManager</code>
+ * will receive raw input events first, before they get handled
+ * by the <code>InputManager</code> itself. The listeners are
+ * each processed in the order they were added, e.g. FIFO.
+ * <p>
+ * If a raw input listener has handled the event and does not wish
+ * other listeners down the list to process the event, it may set the
+ * {@link InputEvent#setConsumed() consumed flag} to indicate the
+ * event was consumed and shouldn't be processed any further.
+ * The listener may do this either at each of the event callbacks
+ * or at the {@link RawInputListener#endInput() } method.
+ *
+ * @param listener A listener to receive raw input events.
+ *
+ * @see RawInputListener
+ */
+ public void addRawInputListener(RawInputListener listener) {
+ rawListeners.add(listener);
+ rawListenerArray = null;
+ }
+
+ /**
+ * Removes a {@link RawInputListener} so that it no longer
+ * receives raw input events.
+ *
+ * @param listener The listener to cease receiving raw input events.
+ *
+ * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
+ */
+ public void removeRawInputListener(RawInputListener listener) {
+ rawListeners.remove(listener);
+ rawListenerArray = null;
+ }
+
+ /**
+ * Clears all {@link RawInputListener}s.
+ *
+ * @see InputManager#addRawInputListener(com.jme3.input.RawInputListener)
+ */
+ public void clearRawInputListeners() {
+ rawListeners.clear();
+ rawListenerArray = null;
+ }
+
+ private RawInputListener[] getRawListenerArray() {
+ if (rawListenerArray == null)
+ rawListenerArray = rawListeners.toArray(new RawInputListener[rawListeners.size()]);
+ return rawListenerArray;
+ }
+
+ /**
+ * Enable simulation of mouse events. Used for touchscreen input only.
+ *
+ * @param value True to enable simulation of mouse events
+ */
+ public void setSimulateMouse(boolean value) {
+ if (touch != null) {
+ touch.setSimulateMouse(value);
+ }
+ }
+
+ /**
+ * Enable simulation of keyboard events. Used for touchscreen input only.
+ *
+ * @param value True to enable simulation of keyboard events
+ */
+ public void setSimulateKeyboard(boolean value) {
+ if (touch != null) {
+ touch.setSimulateKeyboard(value);
+ }
+ }
+
+ private void processQueue() {
+ int queueSize = inputQueue.size();
+ RawInputListener[] array = getRawListenerArray();
+
+ for (RawInputListener listener : array) {
+ listener.beginInput();
+
+ for (int j = 0; j < queueSize; j++) {
+ InputEvent event = inputQueue.get(j);
+ if (event.isConsumed()) {
+ continue;
+ }
+
+ if (event instanceof MouseMotionEvent) {
+ listener.onMouseMotionEvent((MouseMotionEvent) event);
+ } else if (event instanceof KeyInputEvent) {
+ listener.onKeyEvent((KeyInputEvent) event);
+ } else if (event instanceof MouseButtonEvent) {
+ listener.onMouseButtonEvent((MouseButtonEvent) event);
+ } else if (event instanceof JoyAxisEvent) {
+ listener.onJoyAxisEvent((JoyAxisEvent) event);
+ } else if (event instanceof JoyButtonEvent) {
+ listener.onJoyButtonEvent((JoyButtonEvent) event);
+ } else if (event instanceof TouchEvent) {
+ listener.onTouchEvent((TouchEvent) event);
+ } else {
+ assert false;
+ }
+ }
+
+ listener.endInput();
+ }
+
+ for (int i = 0; i < queueSize; i++) {
+ InputEvent event = inputQueue.get(i);
+ if (event.isConsumed()) {
+ continue;
+ }
+
+ if (event instanceof MouseMotionEvent) {
+ onMouseMotionEventQueued((MouseMotionEvent) event);
+ } else if (event instanceof KeyInputEvent) {
+ onKeyEventQueued((KeyInputEvent) event);
+ } else if (event instanceof MouseButtonEvent) {
+ onMouseButtonEventQueued((MouseButtonEvent) event);
+ } else if (event instanceof JoyAxisEvent) {
+ onJoyAxisEventQueued((JoyAxisEvent) event);
+ } else if (event instanceof JoyButtonEvent) {
+ onJoyButtonEventQueued((JoyButtonEvent) event);
+ } else if (event instanceof TouchEvent) {
+ onTouchEventQueued((TouchEvent) event);
+ } else {
+ assert false;
+ }
+ // larynx, 2011.06.10 - flag event as reusable because
+ // the android input uses a non-allocating ringbuffer which
+ // needs to know when the event is not anymore in inputQueue
+ // and therefor can be reused.
+ event.setConsumed();
+ }
+
+ inputQueue.clear();
+ }
+
+ /**
+ * Updates the <code>InputManager</code>.
+ * This will query current input devices and send
+ * appropriate events to registered listeners.
+ *
+ * @param tpf Time per frame value.
+ */
+ public void update(float tpf) {
+ frameTPF = tpf;
+
+ // Activate safemode if the TPF value is so small
+ // that rounding errors are inevitable
+ safeMode = tpf < 0.015f;
+
+ long currentTime = keys.getInputTimeNanos();
+ frameDelta = currentTime - lastUpdateTime;
+
+ eventsPermitted = true;
+
+ keys.update();
+ mouse.update();
+ if (joystick != null) {
+ joystick.update();
+ }
+ if (touch != null) {
+ touch.update();
+ }
+
+ eventsPermitted = false;
+
+ processQueue();
+ invokeUpdateActions();
+
+ lastLastUpdateTime = lastUpdateTime;
+ lastUpdateTime = currentTime;
+ }
+
+ /**
+ * Dispatches touch events to touch listeners
+ * @param evt The touch event to be dispatched to all onTouch listeners
+ */
+ public void onTouchEventQueued(TouchEvent evt) {
+ ArrayList<Mapping> maps = bindings.get(TouchTrigger.touchHash(evt.getKeyCode()));
+ if (maps == null) {
+ return;
+ }
+
+ int size = maps.size();
+ for (int i = size - 1; i >= 0; i--) {
+ Mapping mapping = maps.get(i);
+ ArrayList<InputListener> listeners = mapping.listeners;
+ int listenerSize = listeners.size();
+ for (int j = listenerSize - 1; j >= 0; j--) {
+ InputListener listener = listeners.get(j);
+ if (listener instanceof TouchListener) {
+ ((TouchListener) listener).onTouch(mapping.name, evt, frameTPF);
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback from RawInputListener. Do not use.
+ */
+ @Override
+ public void onTouchEvent(TouchEvent evt) {
+ if (!eventsPermitted) {
+ throw new UnsupportedOperationException("TouchInput has raised an event at an illegal time.");
+ }
+ inputQueue.add(evt);
+ }
+}
diff --git a/engine/src/core/com/jme3/input/JoyInput.java b/engine/src/core/com/jme3/input/JoyInput.java
new file mode 100644
index 0000000..77c5539
--- /dev/null
+++ b/engine/src/core/com/jme3/input/JoyInput.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with joysticks or gaming controllers.
+ */
+public interface JoyInput extends Input {
+
+ /**
+ * The X axis for POV (point of view hat).
+ */
+ public static final int AXIS_POV_X = 254;
+
+ /**
+ * The Y axis for POV (point of view hat).
+ */
+ public static final int AXIS_POV_Y = 255;
+
+ /**
+ * Causes the joystick at <code>joyId</code> index to rumble with
+ * the given amount.
+ *
+ * @param joyId The joystick index
+ * @param amount Rumble amount. Should be between 0 and 1.
+ */
+ public void setJoyRumble(int joyId, float amount);
+
+ /**
+ * Loads a list of joysticks from the system.
+ *
+ * @param inputManager The input manager requesting to load joysticks
+ * @return A list of joysticks that are installed.
+ */
+ public Joystick[] loadJoysticks(InputManager inputManager);
+}
diff --git a/engine/src/core/com/jme3/input/Joystick.java b/engine/src/core/com/jme3/input/Joystick.java
new file mode 100644
index 0000000..658781f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/Joystick.java
@@ -0,0 +1,136 @@
+package com.jme3.input;
+
+import com.jme3.input.controls.JoyAxisTrigger;
+import com.jme3.input.controls.JoyButtonTrigger;
+
+/**
+ * A joystick represents a single joystick that is installed in the system.
+ *
+ * @author Kirill Vainer
+ */
+public final class Joystick {
+
+ private InputManager inputManager;
+ private JoyInput joyInput;
+ private int joyId;
+ private int buttonCount;
+ private int axisCount;
+ private int axisXIndex, axisYIndex;
+ private String name;
+
+ /**
+ * Creates a new joystick instance. Only used internally.
+ */
+ public Joystick(InputManager inputManager, JoyInput joyInput,
+ int joyId, String name, int buttonCount, int axisCount,
+ int xAxis, int yAxis){
+ this.inputManager = inputManager;
+ this.joyInput = joyInput;
+ this.joyId = joyId;
+ this.name = name;
+ this.buttonCount = buttonCount;
+ this.axisCount = axisCount;
+
+ this.axisXIndex = xAxis;
+ this.axisYIndex = yAxis;
+ }
+
+ /**
+ * Rumbles the joystick for the given amount/magnitude.
+ *
+ * @param amount The amount to rumble. Should be between 0 and 1.
+ */
+ public void rumble(float amount){
+ joyInput.setJoyRumble(joyId, amount);
+ }
+
+ /**
+ * Assign the mapping name to receive events from the given button index
+ * on the joystick.
+ *
+ * @param mappingName The mapping to receive joystick button events.
+ * @param buttonId The button index.
+ *
+ * @see Joystick#getButtonCount()
+ */
+ public void assignButton(String mappingName, int buttonId){
+ if (buttonId < 0 || buttonId >= buttonCount)
+ throw new IllegalArgumentException();
+
+ inputManager.addMapping(mappingName, new JoyButtonTrigger(joyId, buttonId));
+ }
+
+ /**
+ * Assign the mappings to receive events from the given joystick axis.
+ *
+ * @param positiveMapping The mapping to receive events when the axis is negative
+ * @param negativeMapping The mapping to receive events when the axis is positive
+ * @param axisId The axis index.
+ *
+ * @see Joystick#getAxisCount()
+ */
+ public void assignAxis(String positiveMapping, String negativeMapping, int axisId){
+ inputManager.addMapping(positiveMapping, new JoyAxisTrigger(joyId, axisId, false));
+ inputManager.addMapping(negativeMapping, new JoyAxisTrigger(joyId, axisId, true));
+ }
+
+ /**
+ * Gets the index number for the X axis on the joystick.
+ *
+ * <p>E.g. for most gamepads, the left control stick X axis will be returned.
+ *
+ * @return The axis index for the X axis for this joystick.
+ *
+ * @see Joystick#assignAxis(java.lang.String, java.lang.String, int)
+ */
+ public int getXAxisIndex(){
+ return axisXIndex;
+ }
+
+ /**
+ * Gets the index number for the Y axis on the joystick.
+ *
+ * <p>E.g. for most gamepads, the left control stick Y axis will be returned.
+ *
+ * @return The axis index for the Y axis for this joystick.
+ *
+ * @see Joystick#assignAxis(java.lang.String, java.lang.String, int)
+ */
+ public int getYAxisIndex(){
+ return axisYIndex;
+ }
+
+ /**
+ * Returns the number of axes on this joystick.
+ *
+ * @return the number of axes on this joystick.
+ */
+ public int getAxisCount() {
+ return axisCount;
+ }
+
+ /**
+ * Returns the number of buttons on this joystick.
+ *
+ * @return the number of buttons on this joystick.
+ */
+ public int getButtonCount() {
+ return buttonCount;
+ }
+
+ /**
+ * Returns the name of this joystick.
+ *
+ * @return the name of this joystick.
+ */
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString(){
+ return "Joystick[name=" + name + ", id=" + joyId + ", buttons=" + buttonCount
+ + ", axes=" + axisCount + "]";
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/KeyInput.java b/engine/src/core/com/jme3/input/KeyInput.java
new file mode 100644
index 0000000..a26d23a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/KeyInput.java
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with the keyboard.
+ */
+public interface KeyInput extends Input {
+
+ /**
+ * escape key.
+ */
+ public static final int KEY_ESCAPE = 0x01;
+ /**
+ * 1 key.
+ */
+ public static final int KEY_1 = 0x02;
+ /**
+ * 2 key.
+ */
+ public static final int KEY_2 = 0x03;
+ /**
+ * 3 key.
+ */
+ public static final int KEY_3 = 0x04;
+ /**
+ * 4 key.
+ */
+ public static final int KEY_4 = 0x05;
+ /**
+ * 5 key.
+ */
+ public static final int KEY_5 = 0x06;
+ /**
+ * 6 key.
+ */
+ public static final int KEY_6 = 0x07;
+ /**
+ * 7 key.
+ */
+ public static final int KEY_7 = 0x08;
+ /**
+ * 8 key.
+ */
+ public static final int KEY_8 = 0x09;
+ /**
+ * 9 key.
+ */
+ public static final int KEY_9 = 0x0A;
+ /**
+ * 0 key.
+ */
+ public static final int KEY_0 = 0x0B;
+ /**
+ * - key.
+ */
+ public static final int KEY_MINUS = 0x0C;
+ /**
+ * = key.
+ */
+ public static final int KEY_EQUALS = 0x0D;
+ /**
+ * back key.
+ */
+ public static final int KEY_BACK = 0x0E;
+ /**
+ * tab key.
+ */
+ public static final int KEY_TAB = 0x0F;
+ /**
+ * q key.
+ */
+ public static final int KEY_Q = 0x10;
+ /**
+ * w key.
+ */
+ public static final int KEY_W = 0x11;
+ /**
+ * e key.
+ */
+ public static final int KEY_E = 0x12;
+ /**
+ * r key.
+ */
+ public static final int KEY_R = 0x13;
+ /**
+ * t key.
+ */
+ public static final int KEY_T = 0x14;
+ /**
+ * y key.
+ */
+ public static final int KEY_Y = 0x15;
+ /**
+ * u key.
+ */
+ public static final int KEY_U = 0x16;
+ /**
+ * i key.
+ */
+ public static final int KEY_I = 0x17;
+ /**
+ * o key.
+ */
+ public static final int KEY_O = 0x18;
+ /**
+ * p key.
+ */
+ public static final int KEY_P = 0x19;
+ /**
+ * [ key.
+ */
+ public static final int KEY_LBRACKET = 0x1A;
+ /**
+ * ] key.
+ */
+ public static final int KEY_RBRACKET = 0x1B;
+ /**
+ * enter (main keyboard) key.
+ */
+ public static final int KEY_RETURN = 0x1C;
+ /**
+ * left control key.
+ */
+ public static final int KEY_LCONTROL = 0x1D;
+ /**
+ * a key.
+ */
+ public static final int KEY_A = 0x1E;
+ /**
+ * s key.
+ */
+ public static final int KEY_S = 0x1F;
+ /**
+ * d key.
+ */
+ public static final int KEY_D = 0x20;
+ /**
+ * f key.
+ */
+ public static final int KEY_F = 0x21;
+ /**
+ * g key.
+ */
+ public static final int KEY_G = 0x22;
+ /**
+ * h key.
+ */
+ public static final int KEY_H = 0x23;
+ /**
+ * j key.
+ */
+ public static final int KEY_J = 0x24;
+ /**
+ * k key.
+ */
+ public static final int KEY_K = 0x25;
+ /**
+ * l key.
+ */
+ public static final int KEY_L = 0x26;
+ /**
+ * ; key.
+ */
+ public static final int KEY_SEMICOLON = 0x27;
+ /**
+ * ' key.
+ */
+ public static final int KEY_APOSTROPHE = 0x28;
+ /**
+ * ` key.
+ */
+ public static final int KEY_GRAVE = 0x29;
+ /**
+ * left shift key.
+ */
+ public static final int KEY_LSHIFT = 0x2A;
+ /**
+ * \ key.
+ */
+ public static final int KEY_BACKSLASH = 0x2B;
+ /**
+ * z key.
+ */
+ public static final int KEY_Z = 0x2C;
+ /**
+ * x key.
+ */
+ public static final int KEY_X = 0x2D;
+ /**
+ * c key.
+ */
+ public static final int KEY_C = 0x2E;
+ /**
+ * v key.
+ */
+ public static final int KEY_V = 0x2F;
+ /**
+ * b key.
+ */
+ public static final int KEY_B = 0x30;
+ /**
+ * n key.
+ */
+ public static final int KEY_N = 0x31;
+ /**
+ * m key.
+ */
+ public static final int KEY_M = 0x32;
+ /**
+ * , key.
+ */
+ public static final int KEY_COMMA = 0x33;
+ /**
+ * . key (main keyboard).
+ */
+ public static final int KEY_PERIOD = 0x34;
+ /**
+ * / key (main keyboard).
+ */
+ public static final int KEY_SLASH = 0x35;
+ /**
+ * right shift key.
+ */
+ public static final int KEY_RSHIFT = 0x36;
+ /**
+ * * key (on keypad).
+ */
+ public static final int KEY_MULTIPLY = 0x37;
+ /**
+ * left alt key.
+ */
+ public static final int KEY_LMENU = 0x38;
+ /**
+ * space key.
+ */
+ public static final int KEY_SPACE = 0x39;
+ /**
+ * caps lock key.
+ */
+ public static final int KEY_CAPITAL = 0x3A;
+ /**
+ * F1 key.
+ */
+ public static final int KEY_F1 = 0x3B;
+ /**
+ * F2 key.
+ */
+ public static final int KEY_F2 = 0x3C;
+ /**
+ * F3 key.
+ */
+ public static final int KEY_F3 = 0x3D;
+ /**
+ * F4 key.
+ */
+ public static final int KEY_F4 = 0x3E;
+ /**
+ * F5 key.
+ */
+ public static final int KEY_F5 = 0x3F;
+ /**
+ * F6 key.
+ */
+ public static final int KEY_F6 = 0x40;
+ /**
+ * F7 key.
+ */
+ public static final int KEY_F7 = 0x41;
+ /**
+ * F8 key.
+ */
+ public static final int KEY_F8 = 0x42;
+ /**
+ * F9 key.
+ */
+ public static final int KEY_F9 = 0x43;
+ /**
+ * F10 key.
+ */
+ public static final int KEY_F10 = 0x44;
+ /**
+ * NumLK key.
+ */
+ public static final int KEY_NUMLOCK = 0x45;
+ /**
+ * Scroll lock key.
+ */
+ public static final int KEY_SCROLL = 0x46;
+ /**
+ * 7 key (num pad).
+ */
+ public static final int KEY_NUMPAD7 = 0x47;
+ /**
+ * 8 key (num pad).
+ */
+ public static final int KEY_NUMPAD8 = 0x48;
+ /**
+ * 9 key (num pad).
+ */
+ public static final int KEY_NUMPAD9 = 0x49;
+ /**
+ * - key (num pad).
+ */
+ public static final int KEY_SUBTRACT = 0x4A;
+ /**
+ * 4 key (num pad).
+ */
+ public static final int KEY_NUMPAD4 = 0x4B;
+ /**
+ * 5 key (num pad).
+ */
+ public static final int KEY_NUMPAD5 = 0x4C;
+ /**
+ * 6 key (num pad).
+ */
+ public static final int KEY_NUMPAD6 = 0x4D;
+ /**
+ * + key (num pad).
+ */
+ public static final int KEY_ADD = 0x4E;
+ /**
+ * 1 key (num pad).
+ */
+ public static final int KEY_NUMPAD1 = 0x4F;
+ /**
+ * 2 key (num pad).
+ */
+ public static final int KEY_NUMPAD2 = 0x50;
+ /**
+ * 3 key (num pad).
+ */
+ public static final int KEY_NUMPAD3 = 0x51;
+ /**
+ * 0 key (num pad).
+ */
+ public static final int KEY_NUMPAD0 = 0x52;
+ /**
+ * . key (num pad).
+ */
+ public static final int KEY_DECIMAL = 0x53;
+ /**
+ * F11 key.
+ */
+ public static final int KEY_F11 = 0x57;
+ /**
+ * F12 key.
+ */
+ public static final int KEY_F12 = 0x58;
+ /**
+ * F13 key.
+ */
+ public static final int KEY_F13 = 0x64;
+ /**
+ * F14 key.
+ */
+ public static final int KEY_F14 = 0x65;
+ /**
+ * F15 key.
+ */
+ public static final int KEY_F15 = 0x66;
+ /**
+ * kana key (Japanese).
+ */
+ public static final int KEY_KANA = 0x70;
+ /**
+ * convert key (Japanese).
+ */
+ public static final int KEY_CONVERT = 0x79;
+ /**
+ * noconvert key (Japanese).
+ */
+ public static final int KEY_NOCONVERT = 0x7B;
+ /**
+ * yen key (Japanese).
+ */
+ public static final int KEY_YEN = 0x7D;
+ /**
+ * = on num pad (NEC PC98).
+ */
+ public static final int KEY_NUMPADEQUALS = 0x8D;
+ /**
+ * circum flex key (Japanese).
+ */
+ public static final int KEY_CIRCUMFLEX = 0x90;
+ /**
+ * &#064; key (NEC PC98).
+ */
+ public static final int KEY_AT = 0x91;
+ /**
+ * : key (NEC PC98)
+ */
+ public static final int KEY_COLON = 0x92;
+ /**
+ * _ key (NEC PC98).
+ */
+ public static final int KEY_UNDERLINE = 0x93;
+ /**
+ * kanji key (Japanese).
+ */
+ public static final int KEY_KANJI = 0x94;
+ /**
+ * stop key (NEC PC98).
+ */
+ public static final int KEY_STOP = 0x95;
+ /**
+ * ax key (Japanese).
+ */
+ public static final int KEY_AX = 0x96;
+ /**
+ * (J3100).
+ */
+ public static final int KEY_UNLABELED = 0x97;
+ /**
+ * Enter key (num pad).
+ */
+ public static final int KEY_NUMPADENTER = 0x9C;
+ /**
+ * right control key.
+ */
+ public static final int KEY_RCONTROL = 0x9D;
+ /**
+ * , key on num pad (NEC PC98).
+ */
+ public static final int KEY_NUMPADCOMMA = 0xB3;
+ /**
+ * / key (num pad).
+ */
+ public static final int KEY_DIVIDE = 0xB5;
+ /**
+ * SysRq key.
+ */
+ public static final int KEY_SYSRQ = 0xB7;
+ /**
+ * right alt key.
+ */
+ public static final int KEY_RMENU = 0xB8;
+ /**
+ * pause key.
+ */
+ public static final int KEY_PAUSE = 0xC5;
+ /**
+ * home key.
+ */
+ public static final int KEY_HOME = 0xC7;
+ /**
+ * up arrow key.
+ */
+ public static final int KEY_UP = 0xC8;
+ /**
+ * PgUp key.
+ */
+ public static final int KEY_PRIOR = 0xC9;
+ /**
+ * PgUp key.
+ */
+ public static final int KEY_PGUP = KEY_PRIOR;
+
+ /**
+ * left arrow key.
+ */
+ public static final int KEY_LEFT = 0xCB;
+ /**
+ * right arrow key.
+ */
+ public static final int KEY_RIGHT = 0xCD;
+ /**
+ * end key.
+ */
+ public static final int KEY_END = 0xCF;
+ /**
+ * down arrow key.
+ */
+ public static final int KEY_DOWN = 0xD0;
+ /**
+ * PgDn key.
+ */
+ public static final int KEY_NEXT = 0xD1;
+ /**
+ * PgDn key.
+ */
+ public static final int KEY_PGDN = KEY_NEXT;
+
+ /**
+ * insert key.
+ */
+ public static final int KEY_INSERT = 0xD2;
+ /**
+ * delete key.
+ */
+ public static final int KEY_DELETE = 0xD3;
+
+ /**
+ * Left "Windows" key on PC keyboards, left "Option" key on Mac keyboards.
+ */
+ public static final int KEY_LMETA = 0xDB;
+
+ /**
+ * Right "Windows" key on PC keyboards, right "Option" key on Mac keyboards.
+ */
+ public static final int KEY_RMETA = 0xDC;
+
+ public static final int KEY_APPS = 0xDD;
+ /**
+ * power key.
+ */
+ public static final int KEY_POWER = 0xDE;
+ /**
+ * sleep key.
+ */
+ public static final int KEY_SLEEP = 0xDF;
+
+}
diff --git a/engine/src/core/com/jme3/input/KeyNames.java b/engine/src/core/com/jme3/input/KeyNames.java
new file mode 100644
index 0000000..89490c0
--- /dev/null
+++ b/engine/src/core/com/jme3/input/KeyNames.java
@@ -0,0 +1,153 @@
+package com.jme3.input;
+
+import static com.jme3.input.KeyInput.*;
+
+public class KeyNames {
+
+ private static final String[] KEY_NAMES = new String[0xFF];
+
+ static {
+ KEY_NAMES[KEY_0] = "0";
+ KEY_NAMES[KEY_1] = "1";
+ KEY_NAMES[KEY_2] = "2";
+ KEY_NAMES[KEY_3] = "3";
+ KEY_NAMES[KEY_4] = "4";
+ KEY_NAMES[KEY_5] = "5";
+ KEY_NAMES[KEY_6] = "6";
+ KEY_NAMES[KEY_7] = "7";
+ KEY_NAMES[KEY_8] = "8";
+ KEY_NAMES[KEY_9] = "9";
+
+ KEY_NAMES[KEY_Q] = "Q";
+ KEY_NAMES[KEY_W] = "W";
+ KEY_NAMES[KEY_E] = "E";
+ KEY_NAMES[KEY_R] = "R";
+ KEY_NAMES[KEY_T] = "T";
+ KEY_NAMES[KEY_Y] = "Y";
+ KEY_NAMES[KEY_U] = "U";
+ KEY_NAMES[KEY_I] = "I";
+ KEY_NAMES[KEY_O] = "O";
+ KEY_NAMES[KEY_P] = "P";
+ KEY_NAMES[KEY_A] = "A";
+ KEY_NAMES[KEY_S] = "S";
+ KEY_NAMES[KEY_D] = "D";
+ KEY_NAMES[KEY_F] = "F";
+ KEY_NAMES[KEY_G] = "G";
+ KEY_NAMES[KEY_H] = "H";
+ KEY_NAMES[KEY_J] = "J";
+ KEY_NAMES[KEY_K] = "K";
+ KEY_NAMES[KEY_L] = "L";
+ KEY_NAMES[KEY_Z] = "Z";
+ KEY_NAMES[KEY_X] = "X";
+ KEY_NAMES[KEY_C] = "C";
+ KEY_NAMES[KEY_V] = "V";
+ KEY_NAMES[KEY_B] = "B";
+ KEY_NAMES[KEY_N] = "N";
+ KEY_NAMES[KEY_M] = "M";
+
+ KEY_NAMES[KEY_F1] = "F1";
+ KEY_NAMES[KEY_F2] = "F2";
+ KEY_NAMES[KEY_F3] = "F3";
+ KEY_NAMES[KEY_F4] = "F4";
+ KEY_NAMES[KEY_F5] = "F5";
+ KEY_NAMES[KEY_F6] = "F6";
+ KEY_NAMES[KEY_F7] = "F7";
+ KEY_NAMES[KEY_F8] = "F8";
+ KEY_NAMES[KEY_F9] = "F9";
+ KEY_NAMES[KEY_F10] = "F10";
+ KEY_NAMES[KEY_F11] = "F11";
+ KEY_NAMES[KEY_F12] = "F12";
+ KEY_NAMES[KEY_F13] = "F13";
+ KEY_NAMES[KEY_F14] = "F14";
+ KEY_NAMES[KEY_F15] = "F15";
+
+ KEY_NAMES[KEY_NUMPAD0] = "Numpad 0";
+ KEY_NAMES[KEY_NUMPAD1] = "Numpad 1";
+ KEY_NAMES[KEY_NUMPAD2] = "Numpad 2";
+ KEY_NAMES[KEY_NUMPAD3] = "Numpad 3";
+ KEY_NAMES[KEY_NUMPAD4] = "Numpad 4";
+ KEY_NAMES[KEY_NUMPAD5] = "Numpad 5";
+ KEY_NAMES[KEY_NUMPAD6] = "Numpad 6";
+ KEY_NAMES[KEY_NUMPAD7] = "Numpad 7";
+ KEY_NAMES[KEY_NUMPAD8] = "Numpad 8";
+ KEY_NAMES[KEY_NUMPAD9] = "Numpad 9";
+
+ KEY_NAMES[KEY_NUMPADEQUALS] = "Numpad =";
+ KEY_NAMES[KEY_NUMPADENTER] = "Numpad Enter";
+ KEY_NAMES[KEY_NUMPADCOMMA] = "Numpad .";
+ KEY_NAMES[KEY_DIVIDE] = "Numpad /";
+
+
+ KEY_NAMES[KEY_LMENU] = "Left Alt";
+ KEY_NAMES[KEY_RMENU] = "Right Alt";
+
+ KEY_NAMES[KEY_LCONTROL] = "Left Ctrl";
+ KEY_NAMES[KEY_RCONTROL] = "Right Ctrl";
+
+ KEY_NAMES[KEY_LSHIFT] = "Left Shift";
+ KEY_NAMES[KEY_RSHIFT] = "Right Shift";
+
+ KEY_NAMES[KEY_LMETA] = "Left Option";
+ KEY_NAMES[KEY_RMETA] = "Right Option";
+
+ KEY_NAMES[KEY_MINUS] = "-";
+ KEY_NAMES[KEY_EQUALS] = "=";
+ KEY_NAMES[KEY_LBRACKET] = "[";
+ KEY_NAMES[KEY_RBRACKET] = "]";
+ KEY_NAMES[KEY_SEMICOLON] = ";";
+ KEY_NAMES[KEY_APOSTROPHE] = "'";
+ KEY_NAMES[KEY_GRAVE] = "`";
+ KEY_NAMES[KEY_BACKSLASH] = "\\";
+ KEY_NAMES[KEY_COMMA] = ",";
+ KEY_NAMES[KEY_PERIOD] = ".";
+ KEY_NAMES[KEY_SLASH] = "/";
+ KEY_NAMES[KEY_MULTIPLY] = "*";
+ KEY_NAMES[KEY_ADD] = "+";
+ KEY_NAMES[KEY_COLON] = ":";
+ KEY_NAMES[KEY_UNDERLINE] = "_";
+ KEY_NAMES[KEY_AT] = "@";
+
+ KEY_NAMES[KEY_APPS] = "Apps";
+ KEY_NAMES[KEY_POWER] = "Power";
+ KEY_NAMES[KEY_SLEEP] = "Sleep";
+
+ KEY_NAMES[KEY_STOP] = "Stop";
+ KEY_NAMES[KEY_ESCAPE] = "Esc";
+ KEY_NAMES[KEY_RETURN] = "Enter";
+ KEY_NAMES[KEY_SPACE] = "Space";
+ KEY_NAMES[KEY_BACK] = "Backspace";
+ KEY_NAMES[KEY_TAB] = "Tab";
+
+ KEY_NAMES[KEY_SYSRQ] = "SysEq";
+ KEY_NAMES[KEY_PAUSE] = "Pause";
+
+ KEY_NAMES[KEY_HOME] = "Home";
+ KEY_NAMES[KEY_PGUP] = "Page Up";
+ KEY_NAMES[KEY_PGDN] = "Page Down";
+ KEY_NAMES[KEY_END] = "End";
+ KEY_NAMES[KEY_INSERT] = "Insert";
+ KEY_NAMES[KEY_DELETE] = "Delete";
+
+ KEY_NAMES[KEY_UP] = "Up";
+ KEY_NAMES[KEY_LEFT] = "Left";
+ KEY_NAMES[KEY_RIGHT] = "Right";
+ KEY_NAMES[KEY_DOWN] = "Down";
+
+ KEY_NAMES[KEY_NUMLOCK] = "Num Lock";
+ KEY_NAMES[KEY_CAPITAL] = "Caps Lock";
+ KEY_NAMES[KEY_SCROLL] = "Scroll Lock";
+
+ KEY_NAMES[KEY_KANA] = "Kana";
+ KEY_NAMES[KEY_CONVERT] = "Convert";
+ KEY_NAMES[KEY_NOCONVERT] = "No Convert";
+ KEY_NAMES[KEY_YEN] = "Yen";
+ KEY_NAMES[KEY_CIRCUMFLEX] = "Circumflex";
+ KEY_NAMES[KEY_KANJI] = "Kanji";
+ KEY_NAMES[KEY_AX] = "Ax";
+ KEY_NAMES[KEY_UNLABELED] = "Unlabeled";
+ }
+
+ public String getName(int keyId){
+ return KEY_NAMES[keyId];
+ }
+}
diff --git a/engine/src/core/com/jme3/input/MouseInput.java b/engine/src/core/com/jme3/input/MouseInput.java
new file mode 100644
index 0000000..f4a1f36
--- /dev/null
+++ b/engine/src/core/com/jme3/input/MouseInput.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with the mouse.
+ */
+public interface MouseInput extends Input {
+
+ /**
+ * Mouse X axis.
+ */
+ public static final int AXIS_X = 0;
+
+ /**
+ * Mouse Y axis.
+ */
+ public static final int AXIS_Y = 1;
+
+ /**
+ * Mouse wheel axis.
+ */
+ public static final int AXIS_WHEEL = 2;
+
+ /**
+ * Left mouse button.
+ */
+ public static final int BUTTON_LEFT = 0;
+
+ /**
+ * Right mouse button.
+ */
+ public static final int BUTTON_RIGHT = 1;
+
+ /**
+ * Middle mouse button.
+ */
+ public static final int BUTTON_MIDDLE = 2;
+
+ /**
+ * Set whether the mouse cursor should be visible or not.
+ *
+ * @param visible Whether the mouse cursor should be visible or not.
+ */
+ public void setCursorVisible(boolean visible);
+
+ /**
+ * Returns the number of buttons the mouse has. Typically 3 for most mice.
+ *
+ * @return the number of buttons the mouse has.
+ */
+ public int getButtonCount();
+}
diff --git a/engine/src/core/com/jme3/input/RawInputListener.java b/engine/src/core/com/jme3/input/RawInputListener.java
new file mode 100644
index 0000000..dfdd69e
--- /dev/null
+++ b/engine/src/core/com/jme3/input/RawInputListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+import com.jme3.input.event.*;
+
+/**
+ * An interface used for receiving raw input from devices.
+ */
+public interface RawInputListener {
+
+ /**
+ * Called before a batch of input will be sent to this
+ * <code>RawInputListener</code>.
+ */
+ public void beginInput();
+
+ /**
+ * Called after a batch of input was sent to this
+ * <code>RawInputListener</code>.
+ *
+ * The listener should set the {@link InputEvent#setConsumed() consumed flag}
+ * on any events that have been consumed either at this call or previous calls.
+ */
+ public void endInput();
+
+ /**
+ * Invoked on joystick axis events.
+ *
+ * @param evt
+ */
+ public void onJoyAxisEvent(JoyAxisEvent evt);
+
+ /**
+ * Invoked on joystick button presses.
+ *
+ * @param evt
+ */
+ public void onJoyButtonEvent(JoyButtonEvent evt);
+
+ /**
+ * Invoked on mouse movement/motion events.
+ *
+ * @param evt
+ */
+ public void onMouseMotionEvent(MouseMotionEvent evt);
+
+ /**
+ * Invoked on mouse button events.
+ *
+ * @param evt
+ */
+ public void onMouseButtonEvent(MouseButtonEvent evt);
+
+ /**
+ * Invoked on keyboard key press or release events.
+ *
+ * @param evt
+ */
+ public void onKeyEvent(KeyInputEvent evt);
+
+
+ /**
+ * Invoked on touchscreen touch events.
+ *
+ * @param evt
+ */
+ public void onTouchEvent(TouchEvent evt);
+}
diff --git a/engine/src/core/com/jme3/input/TouchInput.java b/engine/src/core/com/jme3/input/TouchInput.java
new file mode 100644
index 0000000..2f45b44
--- /dev/null
+++ b/engine/src/core/com/jme3/input/TouchInput.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input;
+
+/**
+ * A specific API for interfacing with smartphone touch devices
+ */
+public interface TouchInput extends Input {
+
+ /**
+ * No filter, get all events
+ */
+ public static final int ALL = 0x00;
+ /**
+ * Home key
+ */
+ public static final int KEYCODE_HOME = 0x03;
+ /**
+ * Escape key.
+ */
+ public static final int KEYCODE_BACK = 0x04;
+ /**
+ * Context Menu key.
+ */
+ public static final int KEYCODE_MENU = 0x52;
+ /**
+ * Search key.
+ */
+ public static final int KEYCODE_SEARCH = 0x54;
+ /**
+ * Volume up key.
+ */
+ public static final int KEYCODE_VOLUME_UP = 0x18;
+ /**
+ * Volume down key.
+ */
+ public static final int KEYCODE_VOLUME_DOWN = 0x19;
+
+
+ /**
+ * Set if mouse events should be generated
+ *
+ * @param simulate if mouse events should be generated
+ */
+ public void setSimulateMouse(boolean simulate);
+
+ /**
+ * Set if keyboard events should be generated
+ *
+ * @param simulate if keyboard events should be generated
+ */
+ public void setSimulateKeyboard(boolean simulate);
+
+ /**
+ * Set if historic android events should be transmitted, can be used to get better performance and less mem
+ * @see <a href="http://developer.android.com/reference/android/view/MotionEvent.html#getHistoricalX%28int,%20int%29">
+ * http://developer.android.com/reference/android/view/MotionEvent.html#getHistoricalX%28int,%20int%29</a>
+ * @param dontSendHistory turn of historic events if true, false else and default
+ */
+ public void setOmitHistoricEvents(boolean dontSendHistory);
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/controls/ActionListener.java b/engine/src/core/com/jme3/input/controls/ActionListener.java
new file mode 100644
index 0000000..8deed3c
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/ActionListener.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * <code>ActionListener</code> is used to receive input events in "digital" style.
+ * <p>
+ * Generally all button inputs, such as keyboard, mouse button, and joystick button,
+ * will be represented exactly. Analog inputs will be converted into digital.
+ * <p>
+ * When an action listener is registered to a natively digital input, such as a button,
+ * the event will be invoked when the button is pressed, with <code>value</code>
+ * set to <code>true</code>, and will be invoked again when the button is released,
+ * with <code>value</code> set to <code>false</code>.
+ *
+ * @author Kirill Vainer
+ */
+public interface ActionListener extends InputListener {
+
+ /**
+ * Called when an input to which this listener is registered to is invoked.
+ *
+ * @param name The name of the mapping that was invoked
+ * @param isPressed True if the action is "pressed", false otherwise
+ * @param tpf The time per frame value.
+ */
+ public void onAction(String name, boolean isPressed, float tpf);
+}
diff --git a/engine/src/core/com/jme3/input/controls/AnalogListener.java b/engine/src/core/com/jme3/input/controls/AnalogListener.java
new file mode 100644
index 0000000..b28796a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/AnalogListener.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * <code>AnalogListener</code> is used to receive events of inputs
+ * in analog format.
+ *
+ * @author Kirill Vainer
+ */
+public interface AnalogListener extends InputListener {
+ /**
+ * Called to notify the implementation that an analog event has occurred.
+ *
+ * The results of KeyTrigger and MouseButtonTrigger events will have tpf
+ * == value.
+ *
+ * @param name The name of the mapping that was invoked
+ * @param value Value of the axis, from 0 to 1.
+ * @param tpf The time per frame value.
+ */
+ public void onAnalog(String name, float value, float tpf);
+}
diff --git a/engine/src/core/com/jme3/input/controls/InputListener.java b/engine/src/core/com/jme3/input/controls/InputListener.java
new file mode 100644
index 0000000..ce140a9
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/InputListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * A generic interface for input listeners, the {@link AnalogListener} and
+ * {@link ActionListener} interfaces extend this interface.
+ *
+ * @author Kirill Vainer
+ */
+public interface InputListener {
+}
diff --git a/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java b/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java
new file mode 100644
index 0000000..539d83e
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/JoyAxisTrigger.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.Joystick;
+
+public class JoyAxisTrigger implements Trigger {
+
+ private final int joyId, axisId;
+ private final boolean negative;
+
+ /**
+ * Use {@link Joystick#assignAxis(java.lang.String, java.lang.String, int) }
+ * instead.
+ */
+ public JoyAxisTrigger(int joyId, int axisId, boolean negative) {
+ this.joyId = joyId;
+ this.axisId = axisId;
+ this.negative = negative;
+ }
+
+ public static int joyAxisHash(int joyId, int joyAxis, boolean negative){
+ assert joyAxis >= 0 && joyAxis <= 255;
+ return (2048 * joyId) | (negative ? 1280 : 1024) | (joyAxis & 0xff);
+ }
+
+ public int getAxisId() {
+ return axisId;
+ }
+
+ public int getJoyId() {
+ return joyId;
+ }
+
+ public boolean isNegative() {
+ return negative;
+ }
+
+ public String getName() {
+ return "JoyAxis[joyId="+joyId+", axisId="+axisId+", neg="+negative+"]";
+ }
+
+ public int triggerHashCode() {
+ return joyAxisHash(joyId, axisId, negative);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java b/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java
new file mode 100644
index 0000000..5e2cfd6
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/JoyButtonTrigger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.Joystick;
+
+public class JoyButtonTrigger implements Trigger {
+
+ private final int joyId, buttonId;
+
+ /**
+ * Use {@link Joystick#assignButton(java.lang.String, int) } instead.
+ *
+ * @param joyId
+ * @param axisId
+ */
+ public JoyButtonTrigger(int joyId, int axisId) {
+ this.joyId = joyId;
+ this.buttonId = axisId;
+ }
+
+ public static int joyButtonHash(int joyId, int joyButton){
+ assert joyButton >= 0 && joyButton <= 255;
+ return (2048 * joyId) | 1536 | (joyButton & 0xff);
+ }
+
+ public int getAxisId() {
+ return buttonId;
+ }
+
+ public int getJoyId() {
+ return joyId;
+ }
+
+ public String getName() {
+ return "JoyButton[joyId="+joyId+", axisId="+buttonId+"]";
+ }
+
+ public int triggerHashCode() {
+ return joyButtonHash(joyId, buttonId);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/KeyTrigger.java b/engine/src/core/com/jme3/input/controls/KeyTrigger.java
new file mode 100644
index 0000000..993b4e7
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/KeyTrigger.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * A <code>KeyTrigger</code> is used as a mapping to keyboard keys.
+ *
+ * @author Kirill Vainer
+ */
+public class KeyTrigger implements Trigger {
+
+ private final int keyCode;
+
+ /**
+ * Create a new <code>KeyTrigger</code> for the given keycode.
+ *
+ * @param keyCode the code for the key, see constants in {@link KeyInput}.
+ */
+ public KeyTrigger(int keyCode){
+ this.keyCode = keyCode;
+ }
+
+ public String getName() {
+ return "KeyCode " + keyCode;
+ }
+
+ public int getKeyCode(){
+ return keyCode;
+ }
+
+ public static int keyHash(int keyCode){
+ assert keyCode >= 0 && keyCode <= 255;
+ return keyCode & 0xff;
+ }
+
+ public int triggerHashCode() {
+ return keyHash(keyCode);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java b/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java
new file mode 100644
index 0000000..49de745
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/MouseAxisTrigger.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * A <code>MouseAxisTrigger</code> is used as a mapping to mouse axis,
+ * a mouse axis is movement along the X axis (left/right), Y axis (up/down)
+ * and the mouse wheel (scroll up/down).
+ *
+ * @author Kirill Vainer
+ */
+public class MouseAxisTrigger implements Trigger {
+
+ private int mouseAxis;
+ private boolean negative;
+
+ /**
+ * Create a new <code>MouseAxisTrigger</code>.
+ * <p>
+ * @param mouseAxis Mouse axis. See AXIS_*** constants in {@link MouseInput}
+ * @param negative True if listen to negative axis events, false if
+ * listen to positive axis events.
+ */
+ public MouseAxisTrigger(int mouseAxis, boolean negative){
+ if (mouseAxis < 0 || mouseAxis > 2)
+ throw new IllegalArgumentException("Mouse Axis must be between 0 and 2");
+
+ this.mouseAxis = mouseAxis;
+ this.negative = negative;
+ }
+
+ public int getMouseAxis(){
+ return mouseAxis;
+ }
+
+ public boolean isNegative() {
+ return negative;
+ }
+
+ public String getName() {
+ String sign = negative ? "Negative" : "Positive";
+ switch (mouseAxis){
+ case MouseInput.AXIS_X: return "Mouse X Axis " + sign;
+ case MouseInput.AXIS_Y: return "Mouse Y Axis " + sign;
+ case MouseInput.AXIS_WHEEL: return "Mouse Wheel " + sign;
+ default: throw new AssertionError();
+ }
+ }
+
+ public static int mouseAxisHash(int mouseAxis, boolean negative){
+ assert mouseAxis >= 0 && mouseAxis <= 255;
+ return (negative ? 768 : 512) | (mouseAxis & 0xff);
+ }
+
+ public int triggerHashCode() {
+ return mouseAxisHash(mouseAxis, negative);
+ }
+}
diff --git a/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java b/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java
new file mode 100644
index 0000000..ff013d6
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/MouseButtonTrigger.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * A <code>MouseButtonTrigger</code> is used as a mapping to receive events
+ * from mouse buttons. It is generally expected for a mouse to have at least
+ * a left and right mouse button, but some mice may have a lot more buttons
+ * than that.
+ *
+ * @author Kirill Vainer
+ */
+public class MouseButtonTrigger implements Trigger {
+
+ private final int mouseButton;
+
+ /**
+ * Create a new <code>MouseButtonTrigger</code> to receive mouse button events.
+ *
+ * @param mouseButton Mouse button index. See BUTTON_*** constants in
+ * {@link MouseInput}.
+ */
+ public MouseButtonTrigger(int mouseButton) {
+ if (mouseButton < 0)
+ throw new IllegalArgumentException("Mouse Button cannot be negative");
+
+ this.mouseButton = mouseButton;
+ }
+
+ public int getMouseButton() {
+ return mouseButton;
+ }
+
+ public String getName() {
+ return "Mouse Button " + mouseButton;
+ }
+
+ public static int mouseButtonHash(int mouseButton){
+ assert mouseButton >= 0 && mouseButton <= 255;
+ return 256 | (mouseButton & 0xff);
+ }
+
+ public int triggerHashCode() {
+ return mouseButtonHash(mouseButton);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/controls/TouchListener.java b/engine/src/core/com/jme3/input/controls/TouchListener.java
new file mode 100644
index 0000000..21c100f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/TouchListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+import com.jme3.input.event.TouchEvent;
+
+/**
+ * <code>TouchListener</code> is used to receive events of inputs from smartphone touch devices
+ *
+ * @author larynx
+ */
+public interface TouchListener extends InputListener {
+ /**
+ * @param name the name of the event
+ * @param event the touch event
+ * @param tpf how much time has passed since the last frame
+ */
+ public void onTouch(String name, TouchEvent event, float tpf);
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/input/controls/TouchTrigger.java b/engine/src/core/com/jme3/input/controls/TouchTrigger.java
new file mode 100644
index 0000000..25124ab
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/TouchTrigger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * Class to trigger TouchEvents, keycode can be TouchInput.ALL(=0) or TouchInput.KEYCODE_*
+ * @author larynx
+ *
+ */
+public class TouchTrigger implements Trigger {
+
+ private final int keyCode;
+
+ /**
+ * Constructor
+ * @param keyCode can be zero to get all events or TouchInput.KEYCODE_*
+ */
+ public TouchTrigger(int keyCode) {
+ super();
+ this.keyCode = keyCode;
+ }
+
+ @Override
+ public String getName() {
+ if (keyCode != 0)
+ return "TouchInput";
+ else
+ return "TouchInput KeyCode " + keyCode;
+ }
+
+ public static int touchHash(int keyCode){
+ assert keyCode >= 0 && keyCode <= 255;
+ return 0xfedcba98 + keyCode;
+ }
+
+ public int triggerHashCode() {
+ return touchHash(keyCode);
+ }
+
+ public int getKeyCode(){
+ return keyCode;
+ }
+}
diff --git a/engine/src/core/com/jme3/input/controls/Trigger.java b/engine/src/core/com/jme3/input/controls/Trigger.java
new file mode 100644
index 0000000..9f059e8
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/Trigger.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.controls;
+
+/**
+ * A trigger represents a physical input, such as a keyboard key, a mouse
+ * button, or joystick axis.
+ */
+public interface Trigger {
+
+ /**
+ * @return A user friendly name for the trigger.
+ */
+ public String getName();
+
+ /**
+ * Returns the hash code for the trigger.
+ *
+ * @return the hash code for the trigger.
+ */
+ public int triggerHashCode();
+}
diff --git a/engine/src/core/com/jme3/input/controls/package.html b/engine/src/core/com/jme3/input/controls/package.html
new file mode 100644
index 0000000..2303ea9
--- /dev/null
+++ b/engine/src/core/com/jme3/input/controls/package.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.controls</code> package allows user code to listen
+to input events regardless of the type of input used.
+<p>
+Users will receive input in one of two forms, either
+{@link com.jme3.input.controls.AnalogListener analog input} or
+{@link com.jme3.input.controls.Action digital/action input}.
+
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/dummy/DummyInput.java b/engine/src/core/com/jme3/input/dummy/DummyInput.java
new file mode 100644
index 0000000..3ecb509
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyInput.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.Input;
+import com.jme3.input.RawInputListener;
+
+/**
+ * DummyInput as an implementation of <code>Input</code> that raises no
+ * input events.
+ *
+ * @author Kirill Vainer.
+ */
+public class DummyInput implements Input {
+
+ protected boolean inited = false;
+
+ public void initialize() {
+ if (inited)
+ throw new IllegalStateException("Input already initialized.");
+
+ inited = true;
+ }
+
+ public void update() {
+ if (!inited)
+ throw new IllegalStateException("Input not initialized.");
+ }
+
+ public void destroy() {
+ if (!inited)
+ throw new IllegalStateException("Input not initialized.");
+
+ inited = false;
+ }
+
+ public boolean isInitialized() {
+ return inited;
+ }
+
+ public void setInputListener(RawInputListener listener) {
+ }
+
+ public long getInputTimeNanos() {
+ return System.currentTimeMillis() * 1000000;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java b/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java
new file mode 100644
index 0000000..4d4efcb
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyKeyInput.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * DummyKeyInput as an implementation of <code>KeyInput</code> that raises no
+ * input events.
+ *
+ * @author Kirill Vainer.
+ */
+public class DummyKeyInput extends DummyInput implements KeyInput {
+
+ public int getKeyCount() {
+ if (!inited)
+ throw new IllegalStateException("Input not initialized.");
+
+ return 0;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java b/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java
new file mode 100644
index 0000000..b862061
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/DummyMouseInput.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.dummy;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * DummyMouseInput as an implementation of <code>MouseInput</code> that raises no
+ * input events.
+ *
+ * @author Kirill Vainer.
+ */
+public class DummyMouseInput extends DummyInput implements MouseInput {
+
+ public void setCursorVisible(boolean visible) {
+ if (!inited)
+ throw new IllegalStateException("Input not initialized.");
+ }
+
+ public int getButtonCount() {
+ return 0;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/dummy/package.html b/engine/src/core/com/jme3/input/dummy/package.html
new file mode 100644
index 0000000..13f5c55
--- /dev/null
+++ b/engine/src/core/com/jme3/input/dummy/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.dummy</code> package provides "dummy" or "null"
+implementations of the input interfaces.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/event/InputEvent.java b/engine/src/core/com/jme3/input/event/InputEvent.java
new file mode 100644
index 0000000..bb3867d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/InputEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.Input;
+
+/**
+ * An abstract input event.
+ */
+public abstract class InputEvent {
+
+ protected long time;
+
+
+ protected boolean consumed = false;
+
+ /**
+ * The time when the event occurred. This is relative to
+ * {@link Input#getInputTimeNanos() }.
+ *
+ * @return time when the event occured
+ */
+ public long getTime(){
+ return time;
+ }
+
+ /**
+ * Set the time when the event occurred.
+ *
+ * @param time time when the event occurred.
+ */
+ public void setTime(long time){
+ this.time = time;
+ }
+
+ /**
+ * Returns true if the input event has been consumed, meaning it is no longer valid
+ * and should not be forwarded to input listeners.
+ *
+ * @return true if the input event has been consumed
+ */
+ public boolean isConsumed() {
+ return consumed;
+ }
+
+ /**
+ * Call to mark this input event as consumed, meaning it is no longer valid
+ * and should not be forwarded to input listeners.
+ */
+ public void setConsumed() {
+ this.consumed = true;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/event/JoyAxisEvent.java b/engine/src/core/com/jme3/input/event/JoyAxisEvent.java
new file mode 100644
index 0000000..2896b0b
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/JoyAxisEvent.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.InputManager;
+import com.jme3.input.Joystick;
+
+/**
+ * Joystick axis event.
+ *
+ * @author Kirill Vainer
+ */
+public class JoyAxisEvent extends InputEvent {
+
+ private int joyIdx;
+ private int axisIdx;
+ private float value;
+
+ public JoyAxisEvent(int joyIdx, int axisIdx, float value) {
+ this.joyIdx = joyIdx;
+ this.axisIdx = axisIdx;
+ this.value = value;
+ }
+
+ /**
+ * Returns the joystick axis index.
+ *
+ * @return joystick axis index.
+ *
+ * @see Joystick#assignAxis(java.lang.String, java.lang.String, int)
+ */
+ public int getAxisIndex() {
+ return axisIdx;
+ }
+
+ /**
+ * The joystick index.
+ *
+ * @return joystick index.
+ *
+ * @see InputManager#getJoysticks()
+ */
+ public int getJoyIndex() {
+ return joyIdx;
+ }
+
+ /**
+ * The value of the axis.
+ *
+ * @return value of the axis.
+ */
+ public float getValue() {
+ return value;
+ }
+}
diff --git a/engine/src/core/com/jme3/input/event/JoyButtonEvent.java b/engine/src/core/com/jme3/input/event/JoyButtonEvent.java
new file mode 100644
index 0000000..906847f
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/JoyButtonEvent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.Joystick;
+
+/**
+ * Joystick button event.
+ *
+ * @author Kirill Vainer
+ */
+public class JoyButtonEvent extends InputEvent {
+
+ private int joyIdx;
+ private int btnIdx;
+ private boolean pressed;
+
+ public JoyButtonEvent(int joyIdx, int btnIdx, boolean pressed) {
+ this.joyIdx = joyIdx;
+ this.btnIdx = btnIdx;
+ this.pressed = pressed;
+ }
+
+ /**
+ * The button index.
+ *
+ * @return button index.
+ *
+ * @see Joystick#assignButton(java.lang.String, int)
+ */
+ public int getButtonIndex() {
+ return btnIdx;
+ }
+
+ /**
+ * The joystick index.
+ *
+ * @return joystick index.
+ *
+ * @see InputManager#getJoysticks()
+ */
+ public int getJoyIndex() {
+ return joyIdx;
+ }
+
+ /**
+ * Returns true if the event was a button press,
+ * returns false if the event was a button release.
+ *
+ * @return true if the event was a button press,
+ * false if the event was a button release.
+ */
+ public boolean isPressed() {
+ return pressed;
+ }
+
+
+
+}
diff --git a/engine/src/core/com/jme3/input/event/KeyInputEvent.java b/engine/src/core/com/jme3/input/event/KeyInputEvent.java
new file mode 100644
index 0000000..403635c
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/KeyInputEvent.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.KeyInput;
+
+/**
+ * Keyboard key event.
+ *
+ * @author Kirill Vainer
+ */
+public class KeyInputEvent extends InputEvent {
+
+ private int keyCode;
+ private char keyChar;
+ private boolean pressed;
+ private boolean repeating;
+
+ public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) {
+ this.keyCode = keyCode;
+ this.keyChar = keyChar;
+ this.pressed = pressed;
+ this.repeating = repeating;
+ }
+
+ /**
+ * Returns the key character. Returns 0 if the key has no character.
+ *
+ * @return the key character. 0 if the key has no character.
+ */
+ public char getKeyChar() {
+ return keyChar;
+ }
+
+ /**
+ * The key code.
+ * <p>
+ * See KEY_*** constants in {@link KeyInput}.
+ *
+ * @return key code.
+ */
+ public int getKeyCode() {
+ return keyCode;
+ }
+
+ /**
+ * Returns true if this event is key press, false is it was a key release.
+ *
+ * @return true if this event is key press, false is it was a key release.
+ */
+ public boolean isPressed() {
+ return pressed;
+ }
+
+ /**
+ * Returns true if this event is a repeat event. Not used anymore.
+ *
+ * @return true if this event is a repeat event
+ */
+ public boolean isRepeating() {
+ return repeating;
+ }
+
+ /**
+ * Returns true if this event is a key release, false if it was a key press.
+ *
+ * @return true if this event is a key release, false if it was a key press.
+ */
+ public boolean isReleased() {
+ return !pressed;
+ }
+
+ @Override
+ public String toString(){
+ String str = "Key(CODE="+keyCode;
+ if (keyChar != '\0')
+ str = str + ", CHAR=" + keyChar;
+
+ if (repeating){
+ return str + ", REPEATING)";
+ }else if (pressed){
+ return str + ", PRESSED)";
+ }else{
+ return str + ", RELEASED)";
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/input/event/MouseButtonEvent.java b/engine/src/core/com/jme3/input/event/MouseButtonEvent.java
new file mode 100644
index 0000000..66af54d
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/MouseButtonEvent.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+import com.jme3.input.MouseInput;
+
+/**
+ * Mouse button press/release event.
+ *
+ * @author Kirill Vainer
+ */
+public class MouseButtonEvent extends InputEvent {
+
+ private int x;
+ private int y;
+ private int btnIndex;
+ private boolean pressed;
+
+ public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) {
+ this.btnIndex = btnIndex;
+ this.pressed = pressed;
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Returns the mouse button index.
+ * <p>
+ * See constants in {@link MouseInput}.
+ *
+ * @return the mouse button index.
+ */
+ public int getButtonIndex() {
+ return btnIndex;
+ }
+
+ /**
+ * Returns true if the mouse button was pressed, false if it was released.
+ *
+ * @return true if the mouse button was pressed, false if it was released.
+ */
+ public boolean isPressed() {
+ return pressed;
+ }
+
+ /**
+ * Returns true if the mouse button was released, false if it was pressed.
+ *
+ * @return true if the mouse button was released, false if it was pressed.
+ */
+ public boolean isReleased() {
+ return !pressed;
+ }
+
+ /**
+ * The X coordinate of the mouse when the event was generated.
+ * @return X coordinate of the mouse when the event was generated.
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * The Y coordinate of the mouse when the event was generated.
+ * @return Y coordinate of the mouse when the event was generated.
+ */
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public String toString(){
+ String str = "MouseButton(BTN="+btnIndex;
+ if (pressed){
+ return str + ", PRESSED)";
+ }else{
+ return str + ", RELEASED)";
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/event/MouseMotionEvent.java b/engine/src/core/com/jme3/input/event/MouseMotionEvent.java
new file mode 100644
index 0000000..7439ecc
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/MouseMotionEvent.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.input.event;
+
+/**
+ * Mouse movement event.
+ * <p>
+ * Movement events are only generated if the mouse is on-screen.
+ *
+ * @author Kirill Vainer
+ */
+public class MouseMotionEvent extends InputEvent {
+
+ private int x, y, dx, dy, wheel, deltaWheel;
+
+ public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) {
+ this.x = x;
+ this.y = y;
+ this.dx = dx;
+ this.dy = dy;
+ this.wheel = wheel;
+ this.deltaWheel = deltaWheel;
+ }
+
+ /**
+ * The change in wheel rotation.
+ *
+ * @return change in wheel rotation.
+ */
+ public int getDeltaWheel() {
+ return deltaWheel;
+ }
+
+ /**
+ * The change in X coordinate
+ * @return change in X coordinate
+ */
+ public int getDX() {
+ return dx;
+ }
+
+ /**
+ * The change in Y coordinate
+ *
+ * @return change in Y coordinate
+ */
+ public int getDY() {
+ return dy;
+ }
+
+ /**
+ * Current mouse wheel value
+ * @return Current mouse wheel value
+ */
+ public int getWheel() {
+ return wheel;
+ }
+
+ /**
+ * Current X coordinate
+ * @return Current X coordinate
+ */
+ public int getX() {
+ return x;
+ }
+
+ /**
+ * Current Y coordinate
+ * @return Current Y coordinate
+ */
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public String toString(){
+ return "MouseMotion(X="+x+", Y="+y+", DX="+dx+", DY="+dy+")";
+ }
+
+}
diff --git a/engine/src/core/com/jme3/input/event/TouchEvent.java b/engine/src/core/com/jme3/input/event/TouchEvent.java
new file mode 100644
index 0000000..8834c85
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/TouchEvent.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.input.event;
+
+/**
+ * <code>TouchEvent</code> represents a single event from multi-touch input devices
+ * @author larynx
+ */
+public class TouchEvent extends InputEvent {
+
+ public enum Type {
+
+ /**
+ * Touch down event, fields: posX, posY, pressure
+ */
+ DOWN,
+ /**
+ * Move/Drag event, fields: posX, posY, deltaX, deltaY, pressure
+ */
+ MOVE,
+ /**
+ * Touch up event, fields: posX, posY, pressure
+ */
+ UP,
+ /**
+ * Virtual keyboard or hardware key event down, fields: keyCode, characters
+ */
+ KEY_DOWN,
+ /**
+ * Virtual keyboard or hardware key event up, fields: keyCode, characters
+ */
+ KEY_UP,
+ // Single finger gestures
+ FLING,
+ TAP,
+ DOUBLETAP,
+ LONGPRESSED,
+ // Two finger scale events
+ /**
+ * Two finger scale event start, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan
+ */
+ SCALE_START,
+ /**
+ * Two finger scale event, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan
+ */
+ SCALE_MOVE,
+ /**
+ * Two finger scale event end, fields: posX/posY = getFocusX/Y, scaleFactor, scaleSpan
+ */
+ SCALE_END,
+ /**
+ * Scroll event
+ */
+ SCROLL,
+ /**
+ * The user has performed a down MotionEvent and not performed a move or up yet. This event is commonly used to provide visual feedback to the user to let them know that their action has been recognized i.e. highlight an element.
+ */
+ SHOWPRESS,
+ // Others
+ OUTSIDE,
+ IDLE
+ }
+ private Type type = Type.IDLE;
+ private int pointerId;
+ private float posX;
+ private float posY;
+ private float deltaX;
+ private float deltaY;
+ private float pressure;
+
+ // Used only with KEY* events
+ private int keyCode;
+ private String characters;
+ // Used only with SCALE* events
+ private float scaleFactor;
+ private float scaleSpan;
+
+ public TouchEvent() {
+ set(Type.IDLE, 0f, 0f, 0f, 0f);
+ }
+
+ public TouchEvent(Type type, float x, float y, float deltax, float deltay) {
+ set(type, x, y, deltax, deltay);
+ }
+
+ public void set(Type type) {
+ set(type, 0f, 0f, 0f, 0f);
+ }
+
+ public void set(Type type, float x, float y, float deltax, float deltay) {
+ this.type = type;
+ this.posX = x;
+ this.posY = y;
+ this.deltaX = deltax;
+ this.deltaY = deltay;
+ pointerId = 0;
+ pressure = 0;
+ keyCode = 0;
+ scaleFactor = 0;
+ scaleSpan = 0;
+ characters = "";
+ consumed = false;
+ }
+
+ /**
+ * Returns the type of touch event.
+ *
+ * @return the type of touch event.
+ */
+ public Type getType() {
+ return type;
+ }
+
+ public float getX() {
+ return posX;
+ }
+
+ public float getY() {
+ return posY;
+ }
+
+ public float getDeltaX() {
+ return deltaX;
+ }
+
+ public float getDeltaY() {
+ return deltaY;
+ }
+
+ public float getPressure()
+ {
+ return pressure;
+ }
+
+ public void setPressure(float pressure)
+ {
+ this.pressure = pressure;
+ }
+
+ public int getPointerId()
+ {
+ return pointerId;
+ }
+
+ public void setPointerId(int pointerId) {
+ this.pointerId = pointerId;
+ }
+
+ public int getKeyCode() {
+ return keyCode;
+ }
+
+ public void setKeyCode(int keyCode) {
+ this.keyCode = keyCode;
+ }
+
+ public String getCharacters() {
+ return characters;
+ }
+
+ public void setCharacters(String characters) {
+ this.characters = characters;
+ }
+
+ public float getScaleFactor() {
+ return scaleFactor;
+ }
+
+ public void setScaleFactor(float scaleFactor) {
+ this.scaleFactor = scaleFactor;
+ }
+
+ public float getScaleSpan() {
+ return scaleSpan;
+ }
+
+ public void setScaleSpan(float scaleSpan) {
+ this.scaleSpan = scaleSpan;
+ }
+}
diff --git a/engine/src/core/com/jme3/input/event/package.html b/engine/src/core/com/jme3/input/event/package.html
new file mode 100644
index 0000000..eaa96fb
--- /dev/null
+++ b/engine/src/core/com/jme3/input/event/package.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input.event</code> package contains low-level input events.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/input/package.html b/engine/src/core/com/jme3/input/package.html
new file mode 100644
index 0000000..998a22a
--- /dev/null
+++ b/engine/src/core/com/jme3/input/package.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input</code> package is used for all input handling in
+jMonkeyEngine. User code should use the {@link com.jme3.input.InputManager} to register
+for and receive input events. The <code>InputManager</code> can be
+retrieved for an application by using {@link com.jme3.app.Application#getInputManager()}.
+
+<h3>Usage</h3>
+
+<p>
+Using ActionListener:<br>
+<code>
+// Retrieve an input manager for the application "app"<br>
+InputManager inputManager = app.getInputManager();<br>
+<br>
+// Adds a new mapping "PrintHello" that will be invoked when the Return/Enter key is pressed<br>
+inputManager.addMapping("PrintHello", new KeyTrigger(KeyInput.KEY_RETURN));<br>
+// Adds a new ActionListener to get an event when enter is pressed.<br>
+inputManager.addListener(new ActionListener() {<br>
+ public void onAction(String name, boolean isPressed, float tpf) {<br>
+ // Only invoke the event when the mapping is "PrintHello" <br>
+ // and isPressed is true, meaning it was a key press and not release.<br>
+ if (name.equals("PrintHello") && isPressed){<br>
+ System.out.println("Hello!");<br>
+ }<br>
+ }<br>
+}, "PrintHello");<br>
+</code>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/light/AmbientLight.java b/engine/src/core/com/jme3/light/AmbientLight.java
new file mode 100644
index 0000000..736cc12
--- /dev/null
+++ b/engine/src/core/com/jme3/light/AmbientLight.java
@@ -0,0 +1,26 @@
+package com.jme3.light;
+
+import com.jme3.scene.Spatial;
+
+/**
+ * An ambient light adds a constant color to the scene.
+ * <p>
+ * Ambient lights are unaffected by the surface normal, and are constant
+ * regardless of the model's location. The material's ambient color is
+ * multiplied by the ambient light color to get the final ambient color of
+ * an object.
+ *
+ * @author Kirill Vainer
+ */
+public class AmbientLight extends Light {
+
+ @Override
+ public void computeLastDistance(Spatial owner) {
+ }
+
+ @Override
+ public Type getType() {
+ return Type.Ambient;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/light/DirectionalLight.java b/engine/src/core/com/jme3/light/DirectionalLight.java
new file mode 100644
index 0000000..e2bcdee
--- /dev/null
+++ b/engine/src/core/com/jme3/light/DirectionalLight.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.light;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * <code>DirectionalLight</code> is a light coming from a certain direction in world space.
+ * E.g sun or moon light.
+ * <p>
+ * Directional lights have no specific position in the scene, they always
+ * come from their direction regardless of where an object is placed.
+ */
+public class DirectionalLight extends Light {
+
+ protected Vector3f direction = new Vector3f(0f, -1f, 0f);
+
+ @Override
+ public void computeLastDistance(Spatial owner) {
+ lastDistance = 0; // directional lights are always closest to their owner
+ }
+
+ /**
+ * Returns the direction vector of the light.
+ *
+ * @return The direction vector of the light.
+ *
+ * @see DirectionalLight#setDirection(com.jme3.math.Vector3f)
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * Sets the direction of the light.
+ * <p>
+ * Represents the vector direction the light is coming from.
+ * (1, 0, 0) would represent a directional light coming from the X axis.
+ *
+ * @param dir the direction of the light.
+ */
+ public void setDirection(Vector3f dir){
+ direction.set(dir);
+ if (!direction.isUnitVector()) {
+ direction.normalizeLocal();
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return Type.Directional;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(direction, "direction", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ direction = (Vector3f) ic.readSavable("direction", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/light/Light.java b/engine/src/core/com/jme3/light/Light.java
new file mode 100644
index 0000000..965eaf9
--- /dev/null
+++ b/engine/src/core/com/jme3/light/Light.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.light;
+
+import com.jme3.export.*;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Abstract class for representing a light source.
+ * <p>
+ * All light source types have a color.
+ */
+public abstract class Light implements Savable, Cloneable {
+
+ /**
+ * Describes the light type.
+ */
+ public enum Type {
+
+ /**
+ * Directional light
+ *
+ * @see DirectionalLight
+ */
+ Directional(0),
+
+ /**
+ * Point light
+ *
+ * @see PointLight
+ */
+ Point(1),
+
+ /**
+ * Spot light.
+ * <p>
+ * Not supported by jMonkeyEngine
+ */
+ Spot(2),
+
+ /**
+ * Ambient light
+ *
+ * @see AmbientLight
+ */
+ Ambient(3);
+
+ private int typeId;
+
+ Type(int type){
+ this.typeId = type;
+ }
+
+ /**
+ * Returns an index for the light type
+ * @return an index for the light type
+ */
+ public int getId(){
+ return typeId;
+ }
+ }
+
+ protected ColorRGBA color = new ColorRGBA(1f,1f,1f,1f);
+
+ /**
+ * Used in LightList for caching the distance
+ * to the owner spatial. Should be reset after the sorting.
+ */
+ protected transient float lastDistance = -1;
+
+ /**
+ * If light is disabled, it will not have any
+ */
+ protected boolean enabled = true;
+
+ /**
+ * The light name.
+ */
+ protected String name;
+
+ /**
+ * Returns the color of the light.
+ *
+ * @return The color of the light.
+ */
+ public ColorRGBA getColor() {
+ return color;
+ }
+
+ /**
+ * This method sets the light name.
+ *
+ * @param name the light name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Return the light name.
+ *
+ * @return the light name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /*
+ public void setLastDistance(float lastDistance){
+ this.lastDistance = lastDistance;
+ }
+
+ public float getLastDistance(){
+ return lastDistance;
+ }
+ */
+
+ /**
+ * Sets the light color.
+ *
+ * @param color the light color.
+ */
+ public void setColor(ColorRGBA color){
+ this.color.set(color);
+ }
+
+
+ /*
+ * Returns true if the light is enabled
+ *
+ * @return true if the light is enabled
+ *
+ * @see Light#setEnabled(boolean)
+ */
+ /*
+ public boolean isEnabled() {
+ return enabled;
+ }
+ */
+
+ @Override
+ public Light clone(){
+ try {
+ return (Light) super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(color, "color", null);
+ oc.write(enabled, "enabled", true);
+ oc.write(name, "name", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ color = (ColorRGBA) ic.readSavable("color", null);
+ enabled = ic.readBoolean("enabled", true);
+ name = ic.readString("name", null);
+ }
+
+ /**
+ * Used internally to compute the last distance value.
+ */
+ protected abstract void computeLastDistance(Spatial owner);
+
+ /**
+ * Returns the light type
+ *
+ * @return the light type
+ *
+ * @see Type
+ */
+ public abstract Type getType();
+
+}
diff --git a/engine/src/core/com/jme3/light/LightList.java b/engine/src/core/com/jme3/light/LightList.java
new file mode 100644
index 0000000..00b6b4d
--- /dev/null
+++ b/engine/src/core/com/jme3/light/LightList.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.light;
+
+import com.jme3.export.*;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SortUtil;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * <code>LightList</code> is used internally by {@link Spatial}s to manage
+ * lights that are attached to them.
+ *
+ * @author Kirill Vainer
+ */
+public final class LightList implements Iterable<Light>, Savable, Cloneable {
+
+ private Light[] list, tlist;
+ private float[] distToOwner;
+ private int listSize;
+ private Spatial owner;
+
+ private static final int DEFAULT_SIZE = 1;
+
+ private static final Comparator<Light> c = new Comparator<Light>() {
+ /**
+ * This assumes lastDistance have been computed in a previous step.
+ */
+ public int compare(Light l1, Light l2) {
+ if (l1.lastDistance < l2.lastDistance)
+ return -1;
+ else if (l1.lastDistance > l2.lastDistance)
+ return 1;
+ else
+ return 0;
+ }
+ };
+
+ /**
+ * Default constructor for serialization. Do not use
+ */
+ public LightList(){
+ }
+
+ /**
+ * Creates a <code>LightList</code> for the given {@link Spatial}.
+ *
+ * @param owner The spatial owner
+ */
+ public LightList(Spatial owner) {
+ listSize = 0;
+ list = new Light[DEFAULT_SIZE];
+ distToOwner = new float[DEFAULT_SIZE];
+ Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
+ this.owner = owner;
+ }
+
+ /**
+ * Set the owner of the LightList. Only used for cloning.
+ * @param owner
+ */
+ public void setOwner(Spatial owner){
+ this.owner = owner;
+ }
+
+ private void doubleSize(){
+ Light[] temp = new Light[list.length * 2];
+ float[] temp2 = new float[list.length * 2];
+ System.arraycopy(list, 0, temp, 0, list.length);
+ System.arraycopy(distToOwner, 0, temp2, 0, list.length);
+ list = temp;
+ distToOwner = temp2;
+ }
+
+ /**
+ * Adds a light to the list. List size is doubled if there is no room.
+ *
+ * @param l
+ * The light to add.
+ */
+ public void add(Light l) {
+ if (listSize == list.length) {
+ doubleSize();
+ }
+ list[listSize] = l;
+ distToOwner[listSize++] = Float.NEGATIVE_INFINITY;
+ }
+
+ /**
+ * Remove the light at the given index.
+ *
+ * @param index
+ */
+ public void remove(int index){
+ if (index >= listSize || index < 0)
+ throw new IndexOutOfBoundsException();
+
+ listSize --;
+ if (index == listSize){
+ list[listSize] = null;
+ return;
+ }
+
+ for (int i = index; i < listSize; i++){
+ list[i] = list[i+1];
+ }
+ list[listSize] = null;
+ }
+
+ /**
+ * Removes the given light from the LightList.
+ *
+ * @param l the light to remove
+ */
+ public void remove(Light l){
+ for (int i = 0; i < listSize; i++){
+ if (list[i] == l){
+ remove(i);
+ return;
+ }
+ }
+ }
+
+ /**
+ * @return The size of the list.
+ */
+ public int size(){
+ return listSize;
+ }
+
+ /**
+ * @return the light at the given index.
+ * @throws IndexOutOfBoundsException If the given index is outside bounds.
+ */
+ public Light get(int num){
+ if (num >= listSize || num < 0)
+ throw new IndexOutOfBoundsException();
+
+ return list[num];
+ }
+
+ /**
+ * Resets list size to 0.
+ */
+ public void clear() {
+ if (listSize == 0)
+ return;
+
+ for (int i = 0; i < listSize; i++)
+ list[i] = null;
+
+ if (tlist != null)
+ Arrays.fill(tlist, null);
+
+ listSize = 0;
+ }
+
+ /**
+ * Sorts the elements in the list acording to their Comparator.
+ * There are two reasons why lights should be resorted.
+ * First, if the lights have moved, that means their distance to
+ * the spatial changed.
+ * Second, if the spatial itself moved, it means the distance from it to
+ * the individual lights might have changed.
+ *
+ *
+ * @param transformChanged Whether the spatial's transform has changed
+ */
+ public void sort(boolean transformChanged) {
+ if (listSize > 1) {
+ // resize or populate our temporary array as necessary
+ if (tlist == null || tlist.length != list.length) {
+ tlist = list.clone();
+ } else {
+ System.arraycopy(list, 0, tlist, 0, list.length);
+ }
+
+ if (transformChanged){
+ // check distance of each light
+ for (int i = 0; i < listSize; i++){
+ list[i].computeLastDistance(owner);
+ }
+ }
+
+ // now merge sort tlist into list
+ SortUtil.msort(tlist, list, 0, listSize - 1, c);
+ }
+ }
+
+ /**
+ * Updates a "world-space" light list, using the spatial's local-space
+ * light list and its parent's world-space light list.
+ *
+ * @param local
+ * @param parent
+ */
+ public void update(LightList local, LightList parent){
+ // clear the list as it will be reconstructed
+ // using the arguments
+ clear();
+
+ while (list.length <= local.listSize){
+ doubleSize();
+ }
+
+ // add the lights from the local list
+ System.arraycopy(local.list, 0, list, 0, local.listSize);
+ for (int i = 0; i < local.listSize; i++){
+// list[i] = local.list[i];
+ distToOwner[i] = Float.NEGATIVE_INFINITY;
+ }
+
+ // if the spatial has a parent node, add the lights
+ // from the parent list as well
+ if (parent != null){
+ int sz = local.listSize + parent.listSize;
+ while (list.length <= sz)
+ doubleSize();
+
+ for (int i = 0; i < parent.listSize; i++){
+ int p = i + local.listSize;
+ list[p] = parent.list[i];
+ distToOwner[p] = Float.NEGATIVE_INFINITY;
+ }
+
+ listSize = local.listSize + parent.listSize;
+ }else{
+ listSize = local.listSize;
+ }
+ }
+
+ /**
+ * Returns an iterator that can be used to iterate over this LightList.
+ *
+ * @return an iterator that can be used to iterate over this LightList.
+ */
+ public Iterator<Light> iterator() {
+ return new Iterator<Light>(){
+
+ int index = 0;
+
+ public boolean hasNext() {
+ return index < size();
+ }
+
+ public Light next() {
+ if (!hasNext())
+ throw new NoSuchElementException();
+
+ return list[index++];
+ }
+
+ public void remove() {
+ LightList.this.remove(--index);
+ }
+ };
+ }
+
+ @Override
+ public LightList clone(){
+ try{
+ LightList clone = (LightList) super.clone();
+
+ clone.owner = null;
+ clone.list = list.clone();
+ clone.distToOwner = distToOwner.clone();
+ clone.tlist = null; // list used for sorting only
+
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+// oc.write(owner, "owner", null);
+
+ ArrayList<Light> lights = new ArrayList<Light>();
+ for (int i = 0; i < listSize; i++){
+ lights.add(list[i]);
+ }
+ oc.writeSavableArrayList(lights, "lights", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+// owner = (Spatial) ic.readSavable("owner", null);
+
+ List<Light> lights = ic.readSavableArrayList("lights", null);
+ listSize = lights.size();
+
+ // NOTE: make sure the array has a length of at least 1
+ int arraySize = Math.max(DEFAULT_SIZE, listSize);
+ list = new Light[arraySize];
+ distToOwner = new float[arraySize];
+
+ for (int i = 0; i < listSize; i++){
+ list[i] = lights.get(i);
+ }
+
+ Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/light/PointLight.java b/engine/src/core/com/jme3/light/PointLight.java
new file mode 100644
index 0000000..e1deeac
--- /dev/null
+++ b/engine/src/core/com/jme3/light/PointLight.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.light;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Represents a point light.
+ * A point light emits light from a given position into all directions in space.
+ * E.g a lamp or a bright effect. Point light positions are in world space.
+ * <p>
+ * In addition to a position, point lights also have a radius which
+ * can be used to attenuate the influence of the light depending on the
+ * distance between the light and the effected object.
+ *
+ */
+public class PointLight extends Light {
+
+ protected Vector3f position = new Vector3f();
+ protected float radius = 0;
+ protected float invRadius = 0;
+
+ @Override
+ public void computeLastDistance(Spatial owner) {
+ if (owner.getWorldBound() != null) {
+ BoundingVolume bv = owner.getWorldBound();
+ lastDistance = bv.distanceSquaredTo(position);
+ } else {
+ lastDistance = owner.getWorldTranslation().distanceSquared(position);
+ }
+ }
+
+ /**
+ * Returns the world space position of the light.
+ *
+ * @return the world space position of the light.
+ *
+ * @see PointLight#setPosition(com.jme3.math.Vector3f)
+ */
+ public Vector3f getPosition() {
+ return position;
+ }
+
+ /**
+ * Set the world space position of the light.
+ *
+ * @param position the world space position of the light.
+ */
+ public void setPosition(Vector3f position) {
+ this.position.set(position);
+ }
+
+ /**
+ * Returns the radius of the light influence. A radius of 0 means
+ * the light has no attenuation.
+ *
+ * @return the radius of the light
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * Set the radius of the light influence.
+ * <p>
+ * Setting a non-zero radius indicates the light should use attenuation.
+ * If a pixel's distance to this light's position
+ * is greater than the light's radius, then the pixel will not be
+ * effected by this light, if the distance is less than the radius, then
+ * the magnitude of the influence is equal to distance / radius.
+ *
+ * @param radius the radius of the light influence.
+ *
+ * @throws IllegalArgumentException If radius is negative
+ */
+ public void setRadius(float radius) {
+ if (radius < 0) {
+ throw new IllegalArgumentException("Light radius cannot be negative");
+ }
+ this.radius = radius;
+ if(radius!=0){
+ this.invRadius = 1 / radius;
+ }else{
+ this.invRadius = 0;
+ }
+ }
+
+ /**
+ * for internal use only
+ * @return the inverse of the radius
+ */
+ public float getInvRadius() {
+ return invRadius;
+ }
+
+ @Override
+ public Light.Type getType() {
+ return Light.Type.Point;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(position, "position", null);
+ oc.write(radius, "radius", 0f);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ position = (Vector3f) ic.readSavable("position", null);
+ radius = ic.readFloat("radius", 0f);
+ if(radius!=0){
+ this.invRadius = 1 / radius;
+ }else{
+ this.invRadius = 0;
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/light/SpotLight.java b/engine/src/core/com/jme3/light/SpotLight.java
new file mode 100644
index 0000000..4c653a5
--- /dev/null
+++ b/engine/src/core/com/jme3/light/SpotLight.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.light;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Represents a spot light.
+ * A spot light emmit a cone of light from a position and in a direction.
+ * It can be used to fake torch lights or car's lights.
+ * <p>
+ * In addition to a position and a direction, spot lights also have a range which
+ * can be used to attenuate the influence of the light depending on the
+ * distance between the light and the effected object.
+ * Also the angle of the cone can be tweaked by changing the spot inner angle and the spot outer angle.
+ * the spot inner angle determin the cone of light where light has full influence.
+ * the spot outer angle determin the cone global cone of light of the spot light.
+ * the light intensity slowly decrease between the inner cone and the outer cone.
+ * @author Nehon
+ */
+public class SpotLight extends Light implements Savable {
+
+ protected Vector3f position = new Vector3f();
+ protected Vector3f direction = new Vector3f(0,-1,0);
+ protected float spotInnerAngle = FastMath.QUARTER_PI / 8;
+ protected float spotOuterAngle = FastMath.QUARTER_PI / 6;
+ protected float spotRange = 100;
+ protected float invSpotRange = 1 / 100;
+ protected float packedAngleCos=0;
+
+ public SpotLight() {
+ super();
+ computePackedCos();
+ }
+
+ private void computePackedCos() {
+ float innerCos=FastMath.cos(spotInnerAngle);
+ float outerCos=FastMath.cos(spotOuterAngle);
+ packedAngleCos=(int)(innerCos*1000);
+ packedAngleCos+=outerCos;
+ }
+
+ @Override
+ protected void computeLastDistance(Spatial owner) {
+ if (owner.getWorldBound() != null) {
+ BoundingVolume bv = owner.getWorldBound();
+ lastDistance = bv.distanceSquaredTo(position);
+ } else {
+ lastDistance = owner.getWorldTranslation().distanceSquared(position);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return Type.Spot;
+ }
+
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction);
+ }
+
+ public Vector3f getPosition() {
+ return position;
+ }
+
+ public void setPosition(Vector3f position) {
+ this.position.set(position);
+ }
+
+ public float getSpotRange() {
+ return spotRange;
+ }
+
+ /**
+ * Set the range of the light influence.
+ * <p>
+ * Setting a non-zero range indicates the light should use attenuation.
+ * If a pixel's distance to this light's position
+ * is greater than the light's range, then the pixel will not be
+ * effected by this light, if the distance is less than the range, then
+ * the magnitude of the influence is equal to distance / range.
+ *
+ * @param spotRange the range of the light influence.
+ *
+ * @throws IllegalArgumentException If spotRange is negative
+ */
+ public void setSpotRange(float spotRange) {
+ if (spotRange < 0) {
+ throw new IllegalArgumentException("SpotLight range cannot be negative");
+ }
+ this.spotRange = spotRange;
+ if (spotRange != 0) {
+ this.invSpotRange = 1 / spotRange;
+ } else {
+ this.invSpotRange = 0;
+ }
+ }
+
+ /**
+ * for internal use only
+ * @return the inverse of the spot range
+ */
+ public float getInvSpotRange() {
+ return invSpotRange;
+ }
+
+ /**
+ * returns the spot inner angle
+ * @return the spot inner angle
+ */
+ public float getSpotInnerAngle() {
+ return spotInnerAngle;
+ }
+
+ /**
+ * Sets the inner angle of the cone of influence.
+ * This angle is the angle between the spot direction axis and the inner border of the cone of influence.
+ * @param spotInnerAngle
+ */
+ public void setSpotInnerAngle(float spotInnerAngle) {
+ this.spotInnerAngle = spotInnerAngle;
+ computePackedCos();
+ }
+
+ /**
+ * returns the spot outer angle
+ * @return the spot outer angle
+ */
+ public float getSpotOuterAngle() {
+ return spotOuterAngle;
+ }
+
+ /**
+ * Sets the outer angle of the cone of influence.
+ * This angle is the angle between the spot direction axis and the outer border of the cone of influence.
+ * this should be greater than the inner angle or the result will be unexpected.
+ * @param spotOuterAngle
+ */
+ public void setSpotOuterAngle(float spotOuterAngle) {
+ this.spotOuterAngle = spotOuterAngle;
+ computePackedCos();
+ }
+
+ /**
+ * for internal use only
+ * @return the cosines of the inner and outter angle packed in a float
+ */
+ public float getPackedAngleCos() {
+ return packedAngleCos;
+ }
+
+
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(direction, "direction", new Vector3f());
+ oc.write(position, "position", new Vector3f());
+ oc.write(spotInnerAngle, "spotInnerAngle", FastMath.QUARTER_PI / 8);
+ oc.write(spotOuterAngle, "spotOuterAngle", FastMath.QUARTER_PI / 6);
+ oc.write(spotRange, "spotRange", 100);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ spotInnerAngle = ic.readFloat("spotInnerAngle", FastMath.QUARTER_PI / 8);
+ spotOuterAngle = ic.readFloat("spotOuterAngle", FastMath.QUARTER_PI / 6);
+ direction = (Vector3f) ic.readSavable("direction", new Vector3f());
+ position = (Vector3f) ic.readSavable("position", new Vector3f());
+ spotRange = ic.readFloat("spotRange", 100);
+ if (spotRange != 0) {
+ this.invSpotRange = 1 / spotRange;
+ } else {
+ this.invSpotRange = 0;
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/light/package.html b/engine/src/core/com/jme3/light/package.html
new file mode 100644
index 0000000..ac65816
--- /dev/null
+++ b/engine/src/core/com/jme3/light/package.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.light</code> package contains various lights that can be placed
+in a scene.
+<p>
+There are 3 types of lights currently supported in jMonkeyEngine:
+<ul>
+ <li>Point Light - Point lights emit from a certain location and have a certain influence radius (optional)</li>
+ <li>Directional Light - Directional lights will always appear to emit from a certain direction
+ regardless of an object's position</li>
+ <li>Ambient Light - Ambient lights have no location or direction;
+ they simply emit a constant color that is applied to the entire scene</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/material/FixedFuncBinding.java b/engine/src/core/com/jme3/material/FixedFuncBinding.java
new file mode 100644
index 0000000..e316ad8
--- /dev/null
+++ b/engine/src/core/com/jme3/material/FixedFuncBinding.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+/**
+ * Fixed function binding is used to specify a binding for a {@link MatParam}
+ * in case that shaders are not supported on the system.
+ *
+ * @author Kirill Vainer
+ */
+public enum FixedFuncBinding {
+ /**
+ * Specifies the material ambient color.
+ * Same as GL_AMBIENT for OpenGL.
+ */
+ MaterialAmbient,
+
+ /**
+ * Specifies the material diffuse color.
+ * Same as GL_DIFFUSE for OpenGL.
+ */
+ MaterialDiffuse,
+
+ /**
+ * Specifies the material specular color.
+ * Same as GL_SPECULAR for OpenGL
+ */
+ MaterialSpecular,
+
+ /**
+ * Specifies the color of the object.
+ * <p>
+ * Used only for non-lit materials.
+ */
+ Color,
+
+ /**
+ * Specifies the material shininess value.
+ *
+ * Same as GL_SHININESS for OpenGL.
+ */
+ MaterialShininess,
+
+ /**
+ * Use vertex color as an additional diffuse color, if lighting is enabled.
+ * If lighting is disabled, vertex color is modulated with
+ * {@link #Color material color}.
+ */
+ UseVertexColor
+}
diff --git a/engine/src/core/com/jme3/material/MatParam.java b/engine/src/core/com/jme3/material/MatParam.java
new file mode 100644
index 0000000..b0ef117
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MatParam.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.TextureKey;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.renderer.GL1Renderer;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.IOException;
+
+/**
+ * Describes a material parameter. This is used for both defining a name and type
+ * as well as a material parameter value.
+ *
+ * @author Kirill Vainer
+ */
+public class MatParam implements Savable, Cloneable {
+
+ protected VarType type;
+ protected String name;
+ protected String prefixedName;
+ protected Object value;
+ protected FixedFuncBinding ffBinding;
+
+ /**
+ * Create a new material parameter. For internal use only.
+ */
+ public MatParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) {
+ this.type = type;
+ this.name = name;
+ this.prefixedName = "m_" + name;
+ this.value = value;
+ this.ffBinding = ffBinding;
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public MatParam() {
+ }
+
+ /**
+ * Returns the fixed function binding.
+ *
+ * @return the fixed function binding.
+ */
+ public FixedFuncBinding getFixedFuncBinding() {
+ return ffBinding;
+ }
+
+ /**
+ * Returns the material parameter type.
+ *
+ * @return the material parameter type.
+ */
+ public VarType getVarType() {
+ return type;
+ }
+
+ /**
+ * Returns the name of the material parameter.
+ * @return the name of the material parameter.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the name with "m_" prefixed to it.
+ *
+ * @return the name with "m_" prefixed to it
+ */
+ public String getPrefixedName() {
+ return prefixedName;
+ }
+
+ /**
+ * Used internally
+ * @param name
+ */
+ void setName(String name) {
+ this.name = name;
+ this.prefixedName = "m_" + name;
+ }
+
+ /**
+ * Returns the value of this material parameter.
+ * <p>
+ * Material parameters that are used for material definitions
+ * will not have a value, unless there's a default value declared
+ * in the definition.
+ *
+ * @return the value of this material parameter.
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of this material parameter.
+ * <p>
+ * It is assumed the value is of the same {@link MatParam#getVarType() type}
+ * as this material parameter.
+ *
+ * @param value the value of this material parameter.
+ */
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ void apply(Renderer r, Technique technique) {
+ TechniqueDef techDef = technique.getDef();
+ if (techDef.isUsingShaders()) {
+ technique.updateUniformParam(getPrefixedName(), getVarType(), getValue(), true);
+ }
+ if (ffBinding != null && r instanceof GL1Renderer) {
+ ((GL1Renderer) r).setFixedFuncBinding(ffBinding, getValue());
+ }
+ }
+
+ /**
+ * Returns the material parameter value as it would appear in a J3M
+ * file. E.g.<br/>
+ * <code>
+ * MaterialParameters {<br/>
+ * ABC : 1 2 3 4<br/>
+ * }<br/>
+ * </code>
+ * Assuming "ABC" is a Vector4 parameter, then the value
+ * "1 2 3 4" would be returned by this method.
+ * <br/><br/>
+ * @return material parameter value as it would appear in a J3M file.
+ */
+ public String getValueAsString() {
+ switch (type) {
+ case Boolean:
+ case Float:
+ case Int:
+ return value.toString();
+ case Vector2:
+ Vector2f v2 = (Vector2f) value;
+ return v2.getX() + " " + v2.getY();
+/*
+This may get used at a later point of time
+When arrays can be inserted in J3M files
+
+ case Vector2Array:
+ Vector2f[] v2Arr = (Vector2f[]) value;
+ String v2str = "";
+ for (int i = 0; i < v2Arr.length ; i++) {
+ v2str += v2Arr[i].getX() + " " + v2Arr[i].getY() + "\n";
+ }
+ return v2str;
+*/
+ case Vector3:
+ Vector3f v3 = (Vector3f) value;
+ return v3.getX() + " " + v3.getY() + " " + v3.getZ();
+/*
+ case Vector3Array:
+ Vector3f[] v3Arr = (Vector3f[]) value;
+ String v3str = "";
+ for (int i = 0; i < v3Arr.length ; i++) {
+ v3str += v3Arr[i].getX() + " "
+ + v3Arr[i].getY() + " "
+ + v3Arr[i].getZ() + "\n";
+ }
+ return v3str;
+ case Vector4Array:
+ // can be either ColorRGBA, Vector4f or Quaternion
+ if (value instanceof Vector4f) {
+ Vector4f[] v4arr = (Vector4f[]) value;
+ String v4str = "";
+ for (int i = 0; i < v4arr.length ; i++) {
+ v4str += v4arr[i].getX() + " "
+ + v4arr[i].getY() + " "
+ + v4arr[i].getZ() + " "
+ + v4arr[i].getW() + "\n";
+ }
+ return v4str;
+ } else if (value instanceof ColorRGBA) {
+ ColorRGBA[] colorArr = (ColorRGBA[]) value;
+ String colStr = "";
+ for (int i = 0; i < colorArr.length ; i++) {
+ colStr += colorArr[i].getRed() + " "
+ + colorArr[i].getGreen() + " "
+ + colorArr[i].getBlue() + " "
+ + colorArr[i].getAlpha() + "\n";
+ }
+ return colStr;
+ } else if (value instanceof Quaternion) {
+ Quaternion[] quatArr = (Quaternion[]) value;
+ String quatStr = "";
+ for (int i = 0; i < quatArr.length ; i++) {
+ quatStr += quatArr[i].getX() + " "
+ + quatArr[i].getY() + " "
+ + quatArr[i].getZ() + " "
+ + quatArr[i].getW() + "\n";
+ }
+ return quatStr;
+ } else {
+ throw new UnsupportedOperationException("Unexpected Vector4Array type: " + value);
+ }
+*/
+ case Vector4:
+ // can be either ColorRGBA, Vector4f or Quaternion
+ if (value instanceof Vector4f) {
+ Vector4f v4 = (Vector4f) value;
+ return v4.getX() + " " + v4.getY() + " "
+ + v4.getZ() + " " + v4.getW();
+ } else if (value instanceof ColorRGBA) {
+ ColorRGBA color = (ColorRGBA) value;
+ return color.getRed() + " " + color.getGreen() + " "
+ + color.getBlue() + " " + color.getAlpha();
+ } else if (value instanceof Quaternion) {
+ Quaternion quat = (Quaternion) value;
+ return quat.getX() + " " + quat.getY() + " "
+ + quat.getZ() + " " + quat.getW();
+ } else {
+ throw new UnsupportedOperationException("Unexpected Vector4 type: " + value);
+ }
+ case Texture2D:
+ case Texture3D:
+ case TextureArray:
+ case TextureBuffer:
+ case TextureCubeMap:
+ Texture texVal = (Texture) value;
+ TextureKey texKey = (TextureKey) texVal.getKey();
+ if (texKey == null){
+ throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M");
+ }
+
+ String ret = "";
+ if (texKey.isFlipY()) {
+ ret += "Flip ";
+ }
+ if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) {
+ ret += "Repeat ";
+ }
+
+ return ret + texKey.getName();
+ default:
+ return null; // parameter type not supported in J3M
+ }
+ }
+
+ @Override
+ public MatParam clone() {
+ try {
+ MatParam param = (MatParam) super.clone();
+ return param;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(type, "varType", null);
+ oc.write(name, "name", null);
+ oc.write(ffBinding, "ff_binding", null);
+ if (value instanceof Savable) {
+ Savable s = (Savable) value;
+ oc.write(s, "value_savable", null);
+ } else if (value instanceof Float) {
+ Float f = (Float) value;
+ oc.write(f.floatValue(), "value_float", 0f);
+ } else if (value instanceof Integer) {
+ Integer i = (Integer) value;
+ oc.write(i.intValue(), "value_int", 0);
+ } else if (value instanceof Boolean) {
+ Boolean b = (Boolean) value;
+ oc.write(b.booleanValue(), "value_bool", false);
+ }
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ type = ic.readEnum("varType", VarType.class, null);
+ name = ic.readString("name", null);
+ ffBinding = ic.readEnum("ff_binding", FixedFuncBinding.class, null);
+ switch (getVarType()) {
+ case Boolean:
+ value = ic.readBoolean("value_bool", false);
+ break;
+ case Float:
+ value = ic.readFloat("value_float", 0f);
+ break;
+ case Int:
+ value = ic.readInt("value_int", 0);
+ break;
+ default:
+ value = ic.readSavable("value_savable", null);
+ break;
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof MatParam)) {
+ return false;
+ }
+
+ MatParam otherParam = (MatParam) other;
+ return otherParam.type == type
+ && otherParam.name.equals(name);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 17 * hash + (this.type != null ? this.type.hashCode() : 0);
+ hash = 17 * hash + (this.name != null ? this.name.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return type.name() + " " + name + " : " + getValueAsString();
+ }
+}
diff --git a/engine/src/core/com/jme3/material/MatParamTexture.java b/engine/src/core/com/jme3/material/MatParamTexture.java
new file mode 100644
index 0000000..fc8b469
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MatParamTexture.java
@@ -0,0 +1,67 @@
+package com.jme3.material;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+
+public class MatParamTexture extends MatParam {
+
+ private Texture texture;
+ private int unit;
+
+ public MatParamTexture(VarType type, String name, Texture texture, int unit) {
+ super(type, name, texture, null);
+ this.texture = texture;
+ this.unit = unit;
+ }
+
+ public MatParamTexture() {
+ }
+
+ public Texture getTextureValue() {
+ return texture;
+ }
+
+ public void setTextureValue(Texture value) {
+ this.value = value;
+ this.texture = value;
+ }
+
+ public void setUnit(int unit) {
+ this.unit = unit;
+ }
+
+ public int getUnit() {
+ return unit;
+ }
+
+ @Override
+ public void apply(Renderer r, Technique technique) {
+ TechniqueDef techDef = technique.getDef();
+ r.setTexture(getUnit(), getTextureValue());
+ if (techDef.isUsingShaders()) {
+ technique.updateUniformParam(getPrefixedName(), getVarType(), getUnit(), true);
+ }
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(unit, "texture_unit", -1);
+ oc.write(texture, "texture", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ unit = ic.readInt("texture_unit", -1);
+ texture = (Texture) ic.readSavable("texture", null);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/material/Material.java b/engine/src/core/com/jme3/material/Material.java
new file mode 100644
index 0000000..8d43853
--- /dev/null
+++ b/engine/src/core/com/jme3/material/Material.java
@@ -0,0 +1,1152 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * <p/>
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * <p/>
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.light.*;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.material.TechniqueDef.LightMode;
+import com.jme3.material.TechniqueDef.ShadowMode;
+import com.jme3.math.*;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.VarType;
+import com.jme3.texture.Texture;
+import com.jme3.util.ListMap;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Material</code> describes the rendering style for a given
+ * {@link Geometry}.
+ * <p>A material is essentially a list of {@link MatParam parameters},
+ * those parameters map to uniforms which are defined in a shader.
+ * Setting the parameters can modify the behavior of a
+ * shader.
+ * <p/>
+ * @author Kirill Vainer
+ */
+public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
+
+ // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
+ public static final int SAVABLE_VERSION = 2;
+
+ private static final Logger logger = Logger.getLogger(Material.class.getName());
+ private static final RenderState additiveLight = new RenderState();
+ private static final RenderState depthOnly = new RenderState();
+ private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
+
+ static {
+ depthOnly.setDepthTest(true);
+ depthOnly.setDepthWrite(true);
+ depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
+ depthOnly.setColorWrite(false);
+
+ additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
+ additiveLight.setDepthWrite(false);
+ }
+ private AssetKey key;
+ private String name;
+ private MaterialDef def;
+ private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
+ private Technique technique;
+ private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
+ private int nextTexUnit = 0;
+ private RenderState additionalState = null;
+ private RenderState mergedRenderState = new RenderState();
+ private boolean transparent = false;
+ private boolean receivesShadows = false;
+ private int sortingId = -1;
+ private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
+
+ public Material(MaterialDef def) {
+ if (def == null) {
+ throw new NullPointerException("Material definition cannot be null");
+ }
+ this.def = def;
+
+ // Load default values from definition (if any)
+ for (MatParam param : def.getMaterialParams()){
+ if (param.getValue() != null){
+ setParam(param.getName(), param.getVarType(), param.getValue());
+ }
+ }
+ }
+
+ public Material(AssetManager contentMan, String defName) {
+ this((MaterialDef) contentMan.loadAsset(new AssetKey(defName)));
+ }
+
+ /**
+ * Do not use this constructor. Serialization purposes only.
+ */
+ public Material() {
+ }
+
+ /**
+ * Returns the asset key name of the asset from which this material was loaded.
+ *
+ * <p>This value will be <code>null</code> unless this material was loaded
+ * from a .j3m file.
+ *
+ * @return Asset key name of the j3m file
+ */
+ public String getAssetName() {
+ return key != null ? key.getName() : null;
+ }
+
+ /**
+ * @return the name of the material (not the same as the asset name), the returned value can be null
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * This method sets the name of the material.
+ * The name is not the same as the asset name.
+ * It can be null and there is no guarantee of its uniqness.
+ * @param name the name of the material
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public void setKey(AssetKey key) {
+ this.key = key;
+ }
+
+ public AssetKey getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the sorting ID or sorting index for this material.
+ *
+ * <p>The sorting ID is used internally by the system to sort rendering
+ * of geometries. It sorted to reduce shader switches, if the shaders
+ * are equal, then it is sorted by textures.
+ *
+ * @return The sorting ID used for sorting geometries for rendering.
+ */
+ public int getSortId() {
+ Technique t = getActiveTechnique();
+ if (sortingId == -1 && t != null && t.getShader() != null) {
+ int texId = -1;
+ for (int i = 0; i < paramValues.size(); i++) {
+ MatParam param = paramValues.getValue(i);
+ if (param instanceof MatParamTexture) {
+ MatParamTexture tex = (MatParamTexture) param;
+ if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
+ if (texId == -1) {
+ texId = 0;
+ }
+ texId += tex.getTextureValue().getImage().getId() % 0xff;
+ }
+ }
+ }
+ sortingId = texId + t.getShader().getId() * 1000;
+ }
+ return sortingId;
+ }
+
+ /**
+ * Uses the sorting ID for each material to compare them.
+ *
+ * @param m The other material to compare to.
+ *
+ * @return zero if the materials are equal, returns a negative value
+ * if <code>this</code> has a lower sorting ID than <code>m</code>,
+ * otherwise returns a positive value.
+ */
+ public int compareTo(Material m) {
+ return m.getSortId() - getSortId();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj instanceof Material){
+ return ((Material)obj).compareTo(this) == 0;
+ }
+ return super.equals(obj);
+ }
+
+ /**
+ * Clones this material. The result is returned.
+ */
+ @Override
+ public Material clone() {
+ try {
+ Material mat = (Material) super.clone();
+
+ if (additionalState != null) {
+ mat.additionalState = additionalState.clone();
+ }
+ mat.technique = null;
+ mat.techniques = new HashMap<String, Technique>();
+
+ mat.paramValues = new ListMap<String, MatParam>();
+ for (int i = 0; i < paramValues.size(); i++) {
+ Map.Entry<String, MatParam> entry = paramValues.getEntry(i);
+ mat.paramValues.put(entry.getKey(), entry.getValue().clone());
+ }
+
+ return mat;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Returns the currently active technique.
+ * <p>
+ * The technique is selected automatically by the {@link RenderManager}
+ * based on system capabilities. Users may select their own
+ * technique by using
+ * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
+ *
+ * @return the currently active technique.
+ *
+ * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
+ */
+ public Technique getActiveTechnique() {
+ return technique;
+ }
+
+ /**
+ * Check if the transparent value marker is set on this material.
+ * @return True if the transparent value marker is set on this material.
+ * @see #setTransparent(boolean)
+ */
+ public boolean isTransparent() {
+ return transparent;
+ }
+
+ /**
+ * Set the transparent value marker.
+ *
+ * <p>This value is merely a marker, by itself it does nothing.
+ * Generally model loaders will use this marker to indicate further
+ * up that the material is transparent and therefore any geometries
+ * using it should be put into the {@link Bucket#Transparent transparent
+ * bucket}.
+ *
+ * @param transparent the transparent value marker.
+ */
+ public void setTransparent(boolean transparent) {
+ this.transparent = transparent;
+ }
+
+ /**
+ * Check if the material should receive shadows or not.
+ *
+ * @return True if the material should receive shadows.
+ *
+ * @see Material#setReceivesShadows(boolean)
+ */
+ public boolean isReceivesShadows() {
+ return receivesShadows;
+ }
+
+ /**
+ * Set if the material should receive shadows or not.
+ *
+ * <p>This value is merely a marker, by itself it does nothing.
+ * Generally model loaders will use this marker to indicate
+ * the material should receive shadows and therefore any
+ * geometries using it should have the {@link ShadowMode#Receive} set
+ * on them.
+ *
+ * @param receivesShadows if the material should receive shadows or not.
+ */
+ public void setReceivesShadows(boolean receivesShadows) {
+ this.receivesShadows = receivesShadows;
+ }
+
+ /**
+ * Acquire the additional {@link RenderState render state} to apply
+ * for this material.
+ *
+ * <p>The first call to this method will create an additional render
+ * state which can be modified by the user to apply any render
+ * states in addition to the ones used by the renderer. Only render
+ * states which are modified in the additional render state will be applied.
+ *
+ * @return The additional render state.
+ */
+ public RenderState getAdditionalRenderState() {
+ if (additionalState == null) {
+ additionalState = RenderState.ADDITIONAL.clone();
+ }
+ return additionalState;
+ }
+
+ /**
+ * Get the material definition (j3md file info) that <code>this</code>
+ * material is implementing.
+ *
+ * @return the material definition this material implements.
+ */
+ public MaterialDef getMaterialDef() {
+ return def;
+ }
+
+ /**
+ * Returns the parameter set on this material with the given name,
+ * returns <code>null</code> if the parameter is not set.
+ *
+ * @param name The parameter name to look up.
+ * @return The MatParam if set, or null if not set.
+ */
+ public MatParam getParam(String name) {
+ MatParam param = paramValues.get(name);
+ return param;
+ }
+
+ /**
+ * Returns the texture parameter set on this material with the given name,
+ * returns <code>null</code> if the parameter is not set.
+ *
+ * @param name The parameter name to look up.
+ * @return The MatParamTexture if set, or null if not set.
+ */
+ public MatParamTexture getTextureParam(String name) {
+ MatParam param = paramValues.get(name);
+ if (param instanceof MatParamTexture) {
+ return (MatParamTexture) param;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a collection of all parameters set on this material.
+ *
+ * @return a collection of all parameters set on this material.
+ *
+ * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
+ */
+ public Collection<MatParam> getParams() {
+ return paramValues.values();
+ }
+
+ private String checkSetParam(VarType type, String name) {
+ MatParam paramDef = def.getMaterialParam(name);
+ String newName = name;
+
+ if (paramDef == null && name.startsWith("m_")) {
+ newName = name.substring(2);
+ paramDef = def.getMaterialParam(newName);
+ if (paramDef == null) {
+ throw new IllegalArgumentException("Material parameter is not defined: " + name);
+ } else {
+ logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName});
+ }
+ } else if (paramDef == null) {
+ throw new IllegalArgumentException("Material parameter is not defined: " + name);
+ }
+
+ if (type != null && paramDef.getVarType() != type) {
+ logger.log(Level.WARNING, "Material parameter being set: {0} with "
+ + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
+ }
+
+ return newName;
+ }
+
+ /**
+ * Pass a parameter to the material shader.
+ *
+ * @param name the name of the parameter defined in the material definition (j3md)
+ * @param type the type of the parameter {@link VarType}
+ * @param value the value of the parameter
+ */
+ public void setParam(String name, VarType type, Object value) {
+ name = checkSetParam(type, name);
+
+ MatParam val = getParam(name);
+ if (technique != null) {
+ technique.notifySetParam(name, type, value);
+ }
+ if (val == null) {
+ MatParam paramDef = def.getMaterialParam(name);
+ paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding()));
+ } else {
+ val.setValue(value);
+ }
+ }
+
+ /**
+ * Clear a parameter from this material. The parameter must exist
+ * @param name the name of the parameter to clear
+ */
+ public void clearParam(String name) {
+ //On removal, we don't check if the param exists in the paramDef, and just go on with the process.
+ // name = checkSetParam(null, name);
+
+ MatParam matParam = getParam(name);
+ if (matParam != null) {
+ paramValues.remove(name);
+ if (technique != null) {
+ technique.notifyClearParam(name);
+ }
+ if (matParam instanceof MatParamTexture) {
+ int texUnit = ((MatParamTexture) matParam).getUnit();
+ nextTexUnit--;
+ for (MatParam param : paramValues.values()) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ if (texParam.getUnit() > texUnit) {
+ texParam.setUnit(texParam.getUnit() - 1);
+ }
+ }
+ }
+ }
+ }
+// else {
+// throw new IllegalArgumentException("The given parameter is not set.");
+// }
+ }
+
+ private void clearTextureParam(String name) {
+ name = checkSetParam(null, name);
+
+ MatParamTexture val = getTextureParam(name);
+ if (val == null) {
+ throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null.");
+ }
+
+ int texUnit = val.getUnit();
+ paramValues.remove(name);
+ nextTexUnit--;
+ for (MatParam param : paramValues.values()) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ if (texParam.getUnit() > texUnit) {
+ texParam.setUnit(texParam.getUnit() - 1);
+ }
+ }
+ }
+
+ sortingId = -1;
+ }
+
+ /**
+ * Set a texture parameter.
+ *
+ * @param name The name of the parameter
+ * @param type The variable type {@link VarType}
+ * @param value The texture value of the parameter.
+ *
+ * @throws IllegalArgumentException is value is null
+ */
+ public void setTextureParam(String name, VarType type, Texture value) {
+ if (value == null) {
+ throw new IllegalArgumentException();
+ }
+
+ name = checkSetParam(type, name);
+ MatParamTexture val = getTextureParam(name);
+ if (val == null) {
+ paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++));
+ } else {
+ val.setTextureValue(value);
+ }
+
+ if (technique != null) {
+ technique.notifySetParam(name, type, nextTexUnit - 1);
+ }
+
+ // need to recompute sort ID
+ sortingId = -1;
+ }
+
+ /**
+ * Pass a texture to the material shader.
+ *
+ * @param name the name of the texture defined in the material definition
+ * (j3md) (for example Texture for Lighting.j3md)
+ * @param value the Texture object previously loaded by the asset manager
+ */
+ public void setTexture(String name, Texture value) {
+ if (value == null) {
+ // clear it
+ clearTextureParam(name);
+ return;
+ }
+
+ VarType paramType = null;
+ switch (value.getType()) {
+ case TwoDimensional:
+ paramType = VarType.Texture2D;
+ break;
+ case TwoDimensionalArray:
+ paramType = VarType.TextureArray;
+ break;
+ case ThreeDimensional:
+ paramType = VarType.Texture3D;
+ break;
+ case CubeMap:
+ paramType = VarType.TextureCubeMap;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
+ }
+
+ setTextureParam(name, paramType, value);
+ }
+
+ /**
+ * Pass a Matrix4f to the material shader.
+ *
+ * @param name the name of the matrix defined in the material definition (j3md)
+ * @param value the Matrix4f object
+ */
+ public void setMatrix4(String name, Matrix4f value) {
+ setParam(name, VarType.Matrix4, value);
+ }
+
+ /**
+ * Pass a boolean to the material shader.
+ *
+ * @param name the name of the boolean defined in the material definition (j3md)
+ * @param value the boolean value
+ */
+ public void setBoolean(String name, boolean value) {
+ setParam(name, VarType.Boolean, value);
+ }
+
+ /**
+ * Pass a float to the material shader.
+ *
+ * @param name the name of the float defined in the material definition (j3md)
+ * @param value the float value
+ */
+ public void setFloat(String name, float value) {
+ setParam(name, VarType.Float, value);
+ }
+
+ /**
+ * Pass an int to the material shader.
+ *
+ * @param name the name of the int defined in the material definition (j3md)
+ * @param value the int value
+ */
+ public void setInt(String name, int value) {
+ setParam(name, VarType.Int, value);
+ }
+
+ /**
+ * Pass a Color to the material shader.
+ *
+ * @param name the name of the color defined in the material definition (j3md)
+ * @param value the ColorRGBA value
+ */
+ public void setColor(String name, ColorRGBA value) {
+ setParam(name, VarType.Vector4, value);
+ }
+
+ /**
+ * Pass a Vector2f to the material shader.
+ *
+ * @param name the name of the Vector2f defined in the material definition (j3md)
+ * @param value the Vector2f value
+ */
+ public void setVector2(String name, Vector2f value) {
+ setParam(name, VarType.Vector2, value);
+ }
+
+ /**
+ * Pass a Vector3f to the material shader.
+ *
+ * @param name the name of the Vector3f defined in the material definition (j3md)
+ * @param value the Vector3f value
+ */
+ public void setVector3(String name, Vector3f value) {
+ setParam(name, VarType.Vector3, value);
+ }
+
+ /**
+ * Pass a Vector4f to the material shader.
+ *
+ * @param name the name of the Vector4f defined in the material definition (j3md)
+ * @param value the Vector4f value
+ */
+ public void setVector4(String name, Vector4f value) {
+ setParam(name, VarType.Vector4, value);
+ }
+
+ private ColorRGBA getAmbientColor(LightList lightList) {
+ ambientLightColor.set(0, 0, 0, 1);
+ for (int j = 0; j < lightList.size(); j++) {
+ Light l = lightList.get(j);
+ if (l instanceof AmbientLight) {
+ ambientLightColor.addLocal(l.getColor());
+ }
+ }
+ ambientLightColor.a = 1.0f;
+ return ambientLightColor;
+ }
+
+ /**
+ * Uploads the lights in the light list as two uniform arrays.<br/><br/>
+ * * <p>
+ * <code>uniform vec4 g_LightColor[numLights];</code><br/>
+ * // g_LightColor.rgb is the diffuse/specular color of the light.<br/>
+ * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/>
+ * // 2 = Spot. <br/>
+ * <br/>
+ * <code>uniform vec4 g_LightPosition[numLights];</code><br/>
+ * // g_LightPosition.xyz is the position of the light (for point lights)<br/>
+ * // or the direction of the light (for directional lights).<br/>
+ * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/>
+ * </p>
+ */
+ protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) {
+ if (numLights == 0) { // this shader does not do lighting, ignore.
+ return;
+ }
+
+ LightList lightList = g.getWorldLightList();
+ Uniform lightColor = shader.getUniform("g_LightColor");
+ Uniform lightPos = shader.getUniform("g_LightPosition");
+ Uniform lightDir = shader.getUniform("g_LightDirection");
+ lightColor.setVector4Length(numLights);
+ lightPos.setVector4Length(numLights);
+ lightDir.setVector4Length(numLights);
+
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+
+ int lightIndex = 0;
+
+ for (int i = 0; i < numLights; i++) {
+ if (lightList.size() <= i) {
+ lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ } else {
+ Light l = lightList.get(i);
+ ColorRGBA color = l.getColor();
+ lightColor.setVector4InArray(color.getRed(),
+ color.getGreen(),
+ color.getBlue(),
+ l.getType().getId(),
+ i);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+ lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+ lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+
+ lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex);
+ lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex);
+ break;
+ case Ambient:
+ // skip this light. Does not increase lightIndex
+ continue;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ }
+
+ lightIndex++;
+ }
+
+ while (lightIndex < numLights) {
+ lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+ lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
+
+ lightIndex++;
+ }
+ }
+
+ protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) {
+
+ Renderer r = rm.getRenderer();
+ LightList lightList = g.getWorldLightList();
+ Uniform lightDir = shader.getUniform("g_LightDirection");
+ Uniform lightColor = shader.getUniform("g_LightColor");
+ Uniform lightPos = shader.getUniform("g_LightPosition");
+ Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
+ boolean isFirstLight = true;
+ boolean isSecondLight = false;
+
+ for (int i = 0; i < lightList.size(); i++) {
+ Light l = lightList.get(i);
+ if (l instanceof AmbientLight) {
+ continue;
+ }
+
+ if (isFirstLight) {
+ // set ambient color for first light only
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+ isFirstLight = false;
+ isSecondLight = true;
+ } else if (isSecondLight) {
+ ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
+ // apply additive blending for 2nd and future lights
+ r.applyRenderState(additiveLight);
+ isSecondLight = false;
+ }
+
+ TempVars vars = TempVars.get();
+ Quaternion tmpLightDirection = vars.quat1;
+ Quaternion tmpLightPosition = vars.quat2;
+ ColorRGBA tmpLightColor = vars.color;
+ Vector4f tmpVec = vars.vect4f;
+
+ ColorRGBA color = l.getColor();
+ tmpLightColor.set(color);
+ tmpLightColor.a = l.getType().getId();
+ lightColor.setValue(VarType.Vector4, tmpLightColor);
+
+ switch (l.getType()) {
+ case Directional:
+ DirectionalLight dl = (DirectionalLight) l;
+ Vector3f dir = dl.getDirection();
+
+ tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+ tmpLightDirection.set(0, 0, 0, 0);
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+ break;
+ case Point:
+ PointLight pl = (PointLight) l;
+ Vector3f pos = pl.getPosition();
+ float invRadius = pl.getInvRadius();
+
+ tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+ tmpLightDirection.set(0, 0, 0, 0);
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+ break;
+ case Spot:
+ SpotLight sl = (SpotLight) l;
+ Vector3f pos2 = sl.getPosition();
+ Vector3f dir2 = sl.getDirection();
+ float invRange = sl.getInvSpotRange();
+ float spotAngleCos = sl.getPackedAngleCos();
+
+ tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
+ lightPos.setValue(VarType.Vector4, tmpLightPosition);
+
+ //We transform the spot directoin in view space here to save 5 varying later in the lighting shader
+ //one vec4 less and a vec4 that becomes a vec3
+ //the downside is that spotAngleCos decoding happen now in the frag shader.
+ tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0);
+ rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
+ tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
+
+ lightDir.setValue(VarType.Vector4, tmpLightDirection);
+
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
+ }
+ vars.release();
+ r.setShader(shader);
+ r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ }
+
+ if (isFirstLight && lightList.size() > 0) {
+ // There are only ambient lights in the scene. Render
+ // a dummy "normal light" so we can see the ambient
+ ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
+ lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
+ lightPos.setValue(VarType.Vector4, nullDirLight);
+ r.setShader(shader);
+ r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
+ }
+ }
+
+ /**
+ * Select the technique to use for rendering this material.
+ * <p>
+ * If <code>name</code> is "Default", then one of the
+ * {@link MaterialDef#getDefaultTechniques() default techniques}
+ * on the material will be selected. Otherwise, the named technique
+ * will be found in the material definition.
+ * <p>
+ * Any candidate technique for selection (either default or named)
+ * must be verified to be compatible with the system, for that, the
+ * <code>renderManager</code> is queried for capabilities.
+ *
+ * @param name The name of the technique to select, pass "Default" to
+ * select one of the default techniques.
+ * @param renderManager The {@link RenderManager render manager}
+ * to query for capabilities.
+ *
+ * @throws IllegalArgumentException If "Default" is passed and no default
+ * techniques are available on the material definition, or if a name
+ * is passed but there's no technique by that name.
+ * @throws UnsupportedOperationException If no candidate technique supports
+ * the system capabilities.
+ */
+ public void selectTechnique(String name, RenderManager renderManager) {
+ // check if already created
+ Technique tech = techniques.get(name);
+ if (tech == null) {
+ // When choosing technique, we choose one that
+ // supports all the caps.
+ EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
+
+ if (name.equals("Default")) {
+ List<TechniqueDef> techDefs = def.getDefaultTechniques();
+ if (techDefs == null || techDefs.isEmpty()) {
+ throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
+ }
+
+ TechniqueDef lastTech = null;
+ for (TechniqueDef techDef : techDefs) {
+ if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
+ // use the first one that supports all the caps
+ tech = new Technique(this, techDef);
+ techniques.put(name, tech);
+ break;
+ }
+ lastTech = techDef;
+ }
+ if (tech == null) {
+ throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
+ + " is supported by the video hardware. The caps "
+ + lastTech.getRequiredCaps() + " are required.");
+ }
+
+ } else {
+ // create "special" technique instance
+ TechniqueDef techDef = def.getTechniqueDef(name);
+ if (techDef == null) {
+ throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
+ }
+
+ if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
+ throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
+ + "requires caps " + techDef.getRequiredCaps() + " which are not "
+ + "supported by the video renderer");
+ }
+
+ tech = new Technique(this, techDef);
+ techniques.put(name, tech);
+ }
+ } else if (technique == tech) {
+ // attempting to switch to an already
+ // active technique.
+ return;
+ }
+
+ technique = tech;
+ tech.makeCurrent(def.getAssetManager());
+
+ // shader was changed
+ sortingId = -1;
+ }
+
+ private void autoSelectTechnique(RenderManager rm) {
+ if (technique == null) {
+ // NOTE: Not really needed anymore since we have technique
+ // selection by caps. Rename all "FixedFunc" techniques to "Default"
+ // and remove this hack.
+ if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) {
+ selectTechnique("FixedFunc", rm);
+ } else {
+ selectTechnique("Default", rm);
+ }
+ } else if (technique.isNeedReload()) {
+ technique.makeCurrent(def.getAssetManager());
+ }
+ }
+
+ /**
+ * Preloads this material for the given render manager.
+ * <p>
+ * Preloading the material can ensure that when the material is first
+ * used for rendering, there won't be any delay since the material has
+ * been already been setup for rendering.
+ *
+ * @param rm The render manager to preload for
+ */
+ public void preload(RenderManager rm) {
+ autoSelectTechnique(rm);
+
+ Renderer r = rm.getRenderer();
+ TechniqueDef techDef = technique.getDef();
+
+ Collection<MatParam> params = paramValues.values();
+ for (MatParam param : params) {
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texParam = (MatParamTexture) param;
+ r.setTexture(0, texParam.getTextureValue());
+ } else {
+ if (!techDef.isUsingShaders()) {
+ continue;
+ }
+
+ technique.updateUniformParam(param.getName(),
+ param.getVarType(),
+ param.getValue(), true);
+ }
+ }
+
+ Shader shader = technique.getShader();
+ if (techDef.isUsingShaders()) {
+ r.setShader(shader);
+ }
+ }
+
+ private void clearUniformsSetByCurrent(Shader shader) {
+ ListMap<String, Uniform> uniforms = shader.getUniformMap();
+ int size = uniforms.size();
+ for (int i = 0; i < size; i++) {
+ Uniform u = uniforms.getValue(i);
+ u.clearSetByCurrentMaterial();
+ }
+ }
+
+ private void resetUniformsNotSetByCurrent(Shader shader) {
+ ListMap<String, Uniform> uniforms = shader.getUniformMap();
+ int size = uniforms.size();
+ for (int i = 0; i < size; i++) {
+ Uniform u = uniforms.getValue(i);
+ if (!u.isSetByCurrentMaterial()) {
+ u.clearValue();
+ }
+ }
+ }
+
+ /**
+ * Called by {@link RenderManager} to render the geometry by
+ * using this material.
+ *
+ * @param geom The geometry to render
+ * @param rm The render manager requesting the rendering
+ */
+ public void render(Geometry geom, RenderManager rm) {
+ autoSelectTechnique(rm);
+
+ Renderer r = rm.getRenderer();
+
+ TechniqueDef techDef = technique.getDef();
+
+ if (techDef.getLightMode() == LightMode.MultiPass
+ && geom.getWorldLightList().size() == 0) {
+ return;
+ }
+
+ if (rm.getForcedRenderState() != null) {
+ r.applyRenderState(rm.getForcedRenderState());
+ } else {
+ if (techDef.getRenderState() != null) {
+ r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
+ } else {
+ r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
+ }
+ }
+
+
+ // update camera and world matrices
+ // NOTE: setWorldTransform should have been called already
+ if (techDef.isUsingShaders()) {
+ // reset unchanged uniform flag
+ clearUniformsSetByCurrent(technique.getShader());
+ rm.updateUniformBindings(technique.getWorldBindUniforms());
+ }
+
+ // setup textures and uniforms
+ for (int i = 0; i < paramValues.size(); i++) {
+ MatParam param = paramValues.getValue(i);
+ param.apply(r, technique);
+ }
+
+ Shader shader = technique.getShader();
+
+ // send lighting information, if needed
+ switch (techDef.getLightMode()) {
+ case Disable:
+ r.setLighting(null);
+ break;
+ case SinglePass:
+ updateLightListUniforms(shader, geom, 4);
+ break;
+ case FixedPipeline:
+ r.setLighting(geom.getWorldLightList());
+ break;
+ case MultiPass:
+ // NOTE: Special case!
+ resetUniformsNotSetByCurrent(shader);
+ renderMultipassLighting(shader, geom, rm);
+ // very important, notice the return statement!
+ return;
+ }
+
+ // upload and bind shader
+ if (techDef.isUsingShaders()) {
+ // any unset uniforms will be set to 0
+ resetUniformsNotSetByCurrent(shader);
+ r.setShader(shader);
+ }
+
+ r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(def.getAssetName(), "material_def", null);
+ oc.write(additionalState, "render_state", null);
+ oc.write(transparent, "is_transparent", false);
+ oc.writeStringSavableMap(paramValues, "parameters", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+
+ additionalState = (RenderState) ic.readSavable("render_state", null);
+ transparent = ic.readBoolean("is_transparent", false);
+
+ // Load the material def
+ String defName = ic.readString("material_def", null);
+ HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null);
+
+ boolean enableVcolor = false;
+ boolean separateTexCoord = false;
+ boolean applyDefaultValues = false;
+ boolean guessRenderStateApply = false;
+
+ int ver = ic.getSavableVersion(Material.class);
+ if (ver < 1){
+ applyDefaultValues = true;
+ }
+ if (ver < 2){
+ guessRenderStateApply = true;
+ }
+ if (im.getFormatVersion() == 0) {
+ // Enable compatibility with old models
+ if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
+ // Using VertexColor, switch to Unshaded and set VertexColor=true
+ enableVcolor = true;
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
+ || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
+ // Using SimpleTextured/SolidColor, just switch to Unshaded
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
+ // Using WireColor, set wireframe renderstate = true and use Unshaded
+ getAdditionalRenderState().setWireframe(true);
+ defName = "Common/MatDefs/Misc/Unshaded.j3md";
+ } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
+ // Uses unshaded, ensure that the proper param is set
+ MatParam value = params.get("SeperateTexCoord");
+ if (value != null && ((Boolean) value.getValue()) == true) {
+ params.remove("SeperateTexCoord");
+ separateTexCoord = true;
+ }
+ }
+ assert applyDefaultValues && guessRenderStateApply;
+ }
+
+ def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName));
+ paramValues = new ListMap<String, MatParam>();
+
+ // load the textures and update nextTexUnit
+ for (Map.Entry<String, MatParam> entry : params.entrySet()) {
+ MatParam param = entry.getValue();
+ if (param instanceof MatParamTexture) {
+ MatParamTexture texVal = (MatParamTexture) param;
+
+ if (nextTexUnit < texVal.getUnit() + 1) {
+ nextTexUnit = texVal.getUnit() + 1;
+ }
+
+ // the texture failed to load for this param
+ // do not add to param values
+ if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
+ continue;
+ }
+ }
+ param.setName(checkSetParam(param.getVarType(), param.getName()));
+ paramValues.put(param.getName(), param);
+ }
+
+ if (applyDefaultValues){
+ // compatability with old versions where default vars were
+ // not available
+ for (MatParam param : def.getMaterialParams()){
+ if (param.getValue() != null && paramValues.get(param.getName()) == null){
+ setParam(param.getName(), param.getVarType(), param.getValue());
+ }
+ }
+ }
+ if (guessRenderStateApply && additionalState != null){
+ // Try to guess values of "apply" render state based on defaults
+ // if value != default then set apply to true
+ additionalState.applyPolyOffset = additionalState.offsetEnabled;
+ additionalState.applyAlphaFallOff = additionalState.alphaTest;
+ additionalState.applyAlphaTest = additionalState.alphaTest;
+ additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
+ additionalState.applyColorWrite = !additionalState.colorWrite;
+ additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
+ additionalState.applyDepthTest = !additionalState.depthTest;
+ additionalState.applyDepthWrite = !additionalState.depthWrite;
+ additionalState.applyPointSprite = additionalState.pointSprite;
+ additionalState.applyStencilTest = additionalState.stencilTest;
+ additionalState.applyWireFrame = additionalState.wireframe;
+ }
+ if (enableVcolor) {
+ setBoolean("VertexColor", true);
+ }
+ if (separateTexCoord) {
+ setBoolean("SeparateTexCoord", true);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/material/MaterialDef.java b/engine/src/core/com/jme3/material/MaterialDef.java
new file mode 100644
index 0000000..e7ec3fc
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MaterialDef.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.shader.VarType;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Describes a J3MD (Material definition).
+ *
+ * @author Kirill Vainer
+ */
+public class MaterialDef {
+
+ private static final Logger logger = Logger.getLogger(MaterialDef.class.getName());
+
+ private String name;
+ private String assetName;
+ private AssetManager assetManager;
+
+ private List<TechniqueDef> defaultTechs;
+ private Map<String, TechniqueDef> techniques;
+ private Map<String, MatParam> matParams;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public MaterialDef(){
+ }
+
+ /**
+ * Creates a new material definition with the given name.
+ *
+ * @param assetManager The asset manager to use to load shaders
+ * @param name The debug name of the material definition
+ */
+ public MaterialDef(AssetManager assetManager, String name){
+ this.assetManager = assetManager;
+ this.name = name;
+ techniques = new HashMap<String, TechniqueDef>();
+ matParams = new HashMap<String, MatParam>();
+ defaultTechs = new ArrayList<TechniqueDef>();
+ logger.log(Level.INFO, "Loaded material definition: {0}", name);
+ }
+
+ /**
+ * Returns the asset key name of the asset from which this material
+ * definition was loaded.
+ *
+ * @return Asset key name of the j3md file
+ */
+ public String getAssetName() {
+ return assetName;
+ }
+
+ /**
+ * Set the asset key name.
+ *
+ * @param assetName the asset key name
+ */
+ public void setAssetName(String assetName) {
+ this.assetName = assetName;
+ }
+
+ /**
+ * Returns the AssetManager passed in the constructor.
+ *
+ * @return the AssetManager passed in the constructor.
+ */
+ public AssetManager getAssetManager(){
+ return assetManager;
+ }
+
+ /**
+ * The debug name of the material definition.
+ *
+ * @return debug name of the material definition.
+ */
+ public String getName(){
+ return name;
+ }
+
+ /**
+ * Adds a new material parameter.
+ *
+ * @param type Type of the parameter
+ * @param name Name of the parameter
+ * @param value Default value of the parameter
+ * @param ffBinding Fixed function binding for the parameter
+ */
+ public void addMaterialParam(VarType type, String name, Object value, FixedFuncBinding ffBinding) {
+ matParams.put(name, new MatParam(type, name, value, ffBinding));
+ }
+
+ /**
+ * Returns the material parameter with the given name.
+ *
+ * @param name The name of the parameter to retrieve
+ *
+ * @return The material parameter, or null if it does not exist.
+ */
+ public MatParam getMaterialParam(String name){
+ return matParams.get(name);
+ }
+
+ /**
+ * Returns a collection of all material parameters declared in this
+ * material definition.
+ * <p>
+ * Modifying the material parameters or the collection will lead
+ * to undefined results.
+ *
+ * @return All material parameters declared in this definition.
+ */
+ public Collection<MatParam> getMaterialParams(){
+ return matParams.values();
+ }
+
+ /**
+ * Adds a new technique definition to this material definition.
+ * <p>
+ * If the technique name is "Default", it will be added
+ * to the list of {@link MaterialDef#getDefaultTechniques() default techniques}.
+ *
+ * @param technique The technique definition to add.
+ */
+ public void addTechniqueDef(TechniqueDef technique){
+ if (technique.getName().equals("Default")){
+ defaultTechs.add(technique);
+ }else{
+ techniques.put(technique.getName(), technique);
+ }
+ }
+
+ /**
+ * Returns a list of all default techniques.
+ *
+ * @return a list of all default techniques.
+ */
+ public List<TechniqueDef> getDefaultTechniques(){
+ return defaultTechs;
+ }
+
+ /**
+ * Returns a technique definition with the given name.
+ * This does not include default techniques which can be
+ * retrieved via {@link MaterialDef#getDefaultTechniques() }.
+ *
+ * @param name The name of the technique definition to find
+ *
+ * @return The technique definition, or null if cannot be found.
+ */
+ public TechniqueDef getTechniqueDef(String name) {
+ return techniques.get(name);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/material/MaterialList.java b/engine/src/core/com/jme3/material/MaterialList.java
new file mode 100644
index 0000000..9f2a512
--- /dev/null
+++ b/engine/src/core/com/jme3/material/MaterialList.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import java.util.HashMap;
+
+/**
+ * A map from material name to a material. Used by loaders to locate
+ * materials for meshes inside a model.
+ *
+ * @author Kirill Vainer
+ */
+public class MaterialList extends HashMap<String, Material> {
+}
diff --git a/engine/src/core/com/jme3/material/RenderState.java b/engine/src/core/com/jme3/material/RenderState.java
new file mode 100644
index 0000000..37897fd
--- /dev/null
+++ b/engine/src/core/com/jme3/material/RenderState.java
@@ -0,0 +1,1070 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.export.*;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import java.io.IOException;
+
+/**
+ * <code>RenderState</code> specifies material rendering properties that cannot
+ * be controlled by a shader on a {@link Material}. The properties
+ * allow manipulation of rendering features such as depth testing, alpha blending,
+ * face culling, stencil operations, and much more.
+ *
+ * @author Kirill Vainer
+ */
+public class RenderState implements Cloneable, Savable {
+
+ /**
+ * The <code>DEFAULT</code> render state is the one used by default
+ * on all materials unless changed otherwise by the user.
+ *
+ * <p>
+ * It has the following properties:
+ * <ul>
+ * <li>Back Face Culling</li>
+ * <li>Depth Testing Enabled</li>
+ * <li>Depth Writing Enabled</li>
+ * </ul>
+ */
+ public static final RenderState DEFAULT = new RenderState();
+
+ /**
+ * The <code>NULL</code> render state is identical to the {@link RenderState#DEFAULT}
+ * render state except that depth testing and face culling are disabled.
+ */
+ public static final RenderState NULL = new RenderState();
+
+ /**
+ * The <code>ADDITIONAL</code> render state is identical to the
+ * {@link RenderState#DEFAULT} render state except that all apply
+ * values are set to false. This allows the <code>ADDITIONAL</code> render
+ * state to be combined with other state but only influencing values
+ * that were changed from the original.
+ */
+ public static final RenderState ADDITIONAL = new RenderState();
+
+ /**
+ * <code>TestFunction</code> specifies the testing function for stencil test
+ * function and alpha test function.
+ *
+ * <p>The functions work similarly as described except that for stencil
+ * test function, the reference value given in the stencil command is
+ * the input value while the reference is the value already in the stencil
+ * buffer.
+ */
+ public enum TestFunction {
+
+ /**
+ * The test always fails
+ */
+ Never,
+ /**
+ * The test succeeds if the input value is equal to the reference value.
+ */
+ Equal,
+ /**
+ * The test succeeds if the input value is less than the reference value.
+ */
+ Less,
+ /**
+ * The test succeeds if the input value is less than or equal to
+ * the reference value.
+ */
+ LessOrEqual,
+ /**
+ * The test succeeds if the input value is greater than the reference value.
+ */
+ Greater,
+ /**
+ * The test succeeds if the input value is greater than or equal to
+ * the reference value.
+ */
+ GreaterOrEqual,
+ /**
+ * The test succeeds if the input value does not equal the
+ * reference value.
+ */
+ NotEqual,
+ /**
+ * The test always passes
+ */
+ Always,}
+
+ /**
+ * <code>BlendMode</code> specifies the blending operation to use.
+ *
+ * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode)
+ */
+ public enum BlendMode {
+
+ /**
+ * No blending mode is used.
+ */
+ Off,
+ /**
+ * Additive blending. For use with glows and particle emitters.
+ * <p>
+ * Result = Source Color + Destination Color -> (GL_ONE, GL_ONE)
+ */
+ Additive,
+ /**
+ * Premultiplied alpha blending, for use with premult alpha textures.
+ * <p>
+ * Result = Source Color + (Dest Color * (1 - Source Alpha) ) -> (GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
+ */
+ PremultAlpha,
+ /**
+ * Additive blending that is multiplied with source alpha.
+ * For use with glows and particle emitters.
+ * <p>
+ * Result = (Source Alpha * Source Color) + Dest Color -> (GL_SRC_ALPHA, GL_ONE)
+ */
+ AlphaAdditive,
+ /**
+ * Color blending, blends in color from dest color
+ * using source color.
+ * <p>
+ * Result = Source Color + (1 - Source Color) * Dest Color -> (GL_ONE, GL_ONE_MINUS_SRC_COLOR)
+ */
+ Color,
+ /**
+ * Alpha blending, interpolates to source color from dest color
+ * using source alpha.
+ * <p>
+ * Result = Source Alpha * Source Color +
+ * (1 - Source Alpha) * Dest Color -> (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+ */
+ Alpha,
+ /**
+ * Multiplies the source and dest colors.
+ * <p>
+ * Result = Source Color * Dest Color -> (GL_DST_COLOR, GL_ZERO)
+ */
+ Modulate,
+ /**
+ * Multiplies the source and dest colors then doubles the result.
+ * <p>
+ * Result = 2 * Source Color * Dest Color -> (GL_DST_COLOR, GL_SRC_COLOR)
+ */
+ ModulateX2
+ }
+
+ /**
+ * <code>FaceCullMode</code> specifies the criteria for faces to be culled.
+ *
+ * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+ */
+ public enum FaceCullMode {
+
+ /**
+ * Face culling is disabled.
+ */
+ Off,
+ /**
+ * Cull front faces
+ */
+ Front,
+ /**
+ * Cull back faces
+ */
+ Back,
+ /**
+ * Cull both front and back faces.
+ */
+ FrontAndBack
+ }
+
+ /**
+ * <code>StencilOperation</code> specifies the stencil operation to use
+ * in a certain scenario as specified in {@link RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilFunction,
+ * com.jme3.material.RenderState.StencilFunction)}
+ */
+ public enum StencilOperation {
+
+ /**
+ * Keep the current value.
+ */
+ Keep,
+ /**
+ * Set the value to 0
+ */
+ Zero,
+ /**
+ * Replace the value in the stencil buffer with the reference value.
+ */
+ Replace,
+
+ /**
+ * Increment the value in the stencil buffer, clamp once reaching
+ * the maximum value.
+ */
+ Increment,
+
+ /**
+ * Increment the value in the stencil buffer and wrap to 0 when
+ * reaching the maximum value.
+ */
+ IncrementWrap,
+ /**
+ * Decrement the value in the stencil buffer and clamp once reaching 0.
+ */
+ Decrement,
+ /**
+ * Decrement the value in the stencil buffer and wrap to the maximum
+ * value when reaching 0.
+ */
+ DecrementWrap,
+
+ /**
+ * Does a bitwise invert of the value in the stencil buffer.
+ */
+ Invert
+ }
+
+ static {
+ NULL.cullMode = FaceCullMode.Off;
+ NULL.depthTest = false;
+ }
+
+ static {
+ ADDITIONAL.applyPointSprite = false;
+ ADDITIONAL.applyWireFrame = false;
+ ADDITIONAL.applyCullMode = false;
+ ADDITIONAL.applyDepthWrite = false;
+ ADDITIONAL.applyDepthTest = false;
+ ADDITIONAL.applyColorWrite = false;
+ ADDITIONAL.applyBlendMode = false;
+ ADDITIONAL.applyAlphaTest = false;
+ ADDITIONAL.applyAlphaFallOff = false;
+ ADDITIONAL.applyPolyOffset = false;
+ }
+
+ boolean pointSprite = false;
+ boolean applyPointSprite = true;
+
+ boolean wireframe = false;
+ boolean applyWireFrame = true;
+
+ FaceCullMode cullMode = FaceCullMode.Back;
+ boolean applyCullMode = true;
+
+ boolean depthWrite = true;
+ boolean applyDepthWrite = true;
+
+ boolean depthTest = true;
+ boolean applyDepthTest = true;
+
+ boolean colorWrite = true;
+ boolean applyColorWrite = true;
+
+ BlendMode blendMode = BlendMode.Off;
+ boolean applyBlendMode = true;
+
+ boolean alphaTest = false;
+ boolean applyAlphaTest = true;
+
+ float alphaFallOff = 0;
+ boolean applyAlphaFallOff = true;
+
+ float offsetFactor = 0;
+ float offsetUnits = 0;
+ boolean offsetEnabled = false;
+ boolean applyPolyOffset = true;
+
+ boolean stencilTest = false;
+ boolean applyStencilTest = false;
+ StencilOperation frontStencilStencilFailOperation = StencilOperation.Keep;
+ StencilOperation frontStencilDepthFailOperation = StencilOperation.Keep;
+ StencilOperation frontStencilDepthPassOperation = StencilOperation.Keep;
+ StencilOperation backStencilStencilFailOperation = StencilOperation.Keep;
+ StencilOperation backStencilDepthFailOperation = StencilOperation.Keep;
+ StencilOperation backStencilDepthPassOperation = StencilOperation.Keep;
+ TestFunction frontStencilFunction = TestFunction.Always;
+ TestFunction backStencilFunction = TestFunction.Always;
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(pointSprite, "pointSprite", false);
+ oc.write(wireframe, "wireframe", false);
+ oc.write(cullMode, "cullMode", FaceCullMode.Back);
+ oc.write(depthWrite, "depthWrite", true);
+ oc.write(depthTest, "depthTest", true);
+ oc.write(colorWrite, "colorWrite", true);
+ oc.write(blendMode, "blendMode", BlendMode.Off);
+ oc.write(alphaTest, "alphaTest", false);
+ oc.write(alphaFallOff, "alphaFallOff", 0);
+ oc.write(offsetEnabled, "offsetEnabled", false);
+ oc.write(offsetFactor, "offsetFactor", 0);
+ oc.write(offsetUnits, "offsetUnits", 0);
+ oc.write(stencilTest, "stencilTest", false);
+ oc.write(frontStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep);
+ oc.write(frontStencilDepthFailOperation, "frontStencilDepthFailOperation", StencilOperation.Keep);
+ oc.write(frontStencilDepthPassOperation, "frontStencilDepthPassOperation", StencilOperation.Keep);
+ oc.write(backStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep);
+ oc.write(backStencilDepthFailOperation, "backStencilDepthFailOperation", StencilOperation.Keep);
+ oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep);
+ oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always);
+ oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always);
+
+ // Only "additional render state" has them set to false by default
+ oc.write(applyPointSprite, "applyPointSprite", true);
+ oc.write(applyWireFrame, "applyWireFrame", true);
+ oc.write(applyCullMode, "applyCullMode", true);
+ oc.write(applyDepthWrite, "applyDepthWrite", true);
+ oc.write(applyDepthTest, "applyDepthTest", true);
+ oc.write(applyColorWrite, "applyColorWrite", true);
+ oc.write(applyBlendMode, "applyBlendMode", true);
+ oc.write(applyAlphaTest, "applyAlphaTest", true);
+ oc.write(applyAlphaFallOff, "applyAlphaFallOff", true);
+ oc.write(applyPolyOffset, "applyPolyOffset", true);
+
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ pointSprite = ic.readBoolean("pointSprite", false);
+ wireframe = ic.readBoolean("wireframe", false);
+ cullMode = ic.readEnum("cullMode", FaceCullMode.class, FaceCullMode.Back);
+ depthWrite = ic.readBoolean("depthWrite", true);
+ depthTest = ic.readBoolean("depthTest", true);
+ colorWrite = ic.readBoolean("colorWrite", true);
+ blendMode = ic.readEnum("blendMode", BlendMode.class, BlendMode.Off);
+ alphaTest = ic.readBoolean("alphaTest", false);
+ alphaFallOff = ic.readFloat("alphaFallOff", 0);
+ offsetEnabled = ic.readBoolean("offsetEnabled", false);
+ offsetFactor = ic.readFloat("offsetFactor", 0);
+ offsetUnits = ic.readFloat("offsetUnits", 0);
+ stencilTest = ic.readBoolean("stencilTest", false);
+ frontStencilStencilFailOperation = ic.readEnum("frontStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep);
+ frontStencilDepthFailOperation = ic.readEnum("frontStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep);
+ frontStencilDepthPassOperation = ic.readEnum("frontStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep);
+ backStencilStencilFailOperation = ic.readEnum("backStencilStencilFailOperation", StencilOperation.class, StencilOperation.Keep);
+ backStencilDepthFailOperation = ic.readEnum("backStencilDepthFailOperation", StencilOperation.class, StencilOperation.Keep);
+ backStencilDepthPassOperation = ic.readEnum("backStencilDepthPassOperation", StencilOperation.class, StencilOperation.Keep);
+ frontStencilFunction = ic.readEnum("frontStencilFunction", TestFunction.class, TestFunction.Always);
+ backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always);
+
+ applyPointSprite = ic.readBoolean("applyPointSprite", true);
+ applyWireFrame = ic.readBoolean("applyWireFrame", true);
+ applyCullMode = ic.readBoolean("applyCullMode", true);
+ applyDepthWrite = ic.readBoolean("applyDepthWrite", true);
+ applyDepthTest = ic.readBoolean("applyDepthTest", true);
+ applyColorWrite = ic.readBoolean("applyColorWrite", true);
+ applyBlendMode = ic.readBoolean("applyBlendMode", true);
+ applyAlphaTest = ic.readBoolean("applyAlphaTest", true);
+ applyAlphaFallOff = ic.readBoolean("applyAlphaFallOff", true);
+ applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
+ }
+
+ /**
+ * Create a clone of this <code>RenderState</code>
+ *
+ * @return Clone of this render state.
+ */
+ @Override
+ public RenderState clone() {
+ try {
+ return (RenderState) super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Enables point sprite mode.
+ *
+ * <p>When point sprite is enabled, any meshes
+ * with the type of {@link Mode#Points} will be rendered as 2D quads
+ * with texturing enabled. Fragment shaders can write to the
+ * <code>gl_PointCoord</code> variable to manipulate the texture coordinate
+ * for each pixel. The size of the 2D quad can be controlled by writing
+ * to the <code>gl_PointSize</code> variable in the vertex shader.
+ *
+ * @param pointSprite Enables Point Sprite mode.
+ */
+ public void setPointSprite(boolean pointSprite) {
+ applyPointSprite = true;
+ this.pointSprite = pointSprite;
+ }
+
+ /**
+ * Sets the alpha fall off value for alpha testing.
+ *
+ * <p>If the pixel's alpha value is greater than the
+ * <code>alphaFallOff</code> then the pixel will be rendered, otherwise
+ * the pixel will be discarded.
+ *
+ * @param alphaFallOff The alpha of all rendered pixels must be higher
+ * than this value to be rendered. This value should be between 0 and 1.
+ *
+ * @see RenderState#setAlphaTest(boolean)
+ */
+ public void setAlphaFallOff(float alphaFallOff) {
+ applyAlphaFallOff = true;
+ this.alphaFallOff = alphaFallOff;
+ }
+
+ /**
+ * Enable alpha testing.
+ *
+ * <p>When alpha testing is enabled, all input pixels' alpha are compared
+ * to the {@link RenderState#setAlphaFallOff(float) constant alpha falloff}.
+ * If the input alpha is greater than the falloff, the pixel will be rendered,
+ * otherwise it will be discarded.
+ *
+ * @param alphaTest Set to true to enable alpha testing.
+ *
+ * @see RenderState#setAlphaFallOff(float)
+ */
+ public void setAlphaTest(boolean alphaTest) {
+ applyAlphaTest = true;
+ this.alphaTest = alphaTest;
+ }
+
+ /**
+ * Enable writing color.
+ *
+ * <p>When color write is enabled, the result of a fragment shader, the
+ * <code>gl_FragColor</code>, will be rendered into the color buffer
+ * (including alpha).
+ *
+ * @param colorWrite Set to true to enable color writing.
+ */
+ public void setColorWrite(boolean colorWrite) {
+ applyColorWrite = true;
+ this.colorWrite = colorWrite;
+ }
+
+ /**
+ * Set the face culling mode.
+ *
+ * <p>See the {@link FaceCullMode} enum on what each value does.
+ * Face culling will project the triangle's points onto the screen
+ * and determine if the triangle is in counter-clockwise order or
+ * clockwise order. If a triangle is in counter-clockwise order, then
+ * it is considered a front-facing triangle, otherwise, it is considered
+ * a back-facing triangle.
+ *
+ * @param cullMode the face culling mode.
+ */
+ public void setFaceCullMode(FaceCullMode cullMode) {
+ applyCullMode = true;
+ this.cullMode = cullMode;
+ }
+
+ /**
+ * Set the blending mode.
+ *
+ * <p>When blending is enabled, (<code>blendMode</code> is not {@link BlendMode#Off})
+ * the input pixel will be blended with the pixel
+ * already in the color buffer. The blending operation is determined
+ * by the {@link BlendMode}. For example, the {@link BlendMode#Additive}
+ * will add the input pixel's color to the color already in the color buffer:
+ * <br/>
+ * <code>Result = Source Color + Destination Color</code>
+ *
+ * @param blendMode The blend mode to use. Set to {@link BlendMode#Off}
+ * to disable blending.
+ */
+ public void setBlendMode(BlendMode blendMode) {
+ applyBlendMode = true;
+ this.blendMode = blendMode;
+ }
+
+ /**
+ * Enable depth testing.
+ *
+ * <p>When depth testing is enabled, a pixel must pass the depth test
+ * before it is written to the color buffer.
+ * The input pixel's depth value must be less than or equal than
+ * the value already in the depth buffer to pass the depth test.
+ *
+ * @param depthTest Enable or disable depth testing.
+ */
+ public void setDepthTest(boolean depthTest) {
+ applyDepthTest = true;
+ this.depthTest = depthTest;
+ }
+
+ /**
+ * Enable depth writing.
+ *
+ * <p>After passing the {@link RenderState#setDepthTest(boolean) depth test},
+ * a pixel's depth value will be written into the depth buffer if
+ * depth writing is enabled.
+ *
+ * @param depthWrite True to enable writing to the depth buffer.
+ */
+ public void setDepthWrite(boolean depthWrite) {
+ applyDepthWrite = true;
+ this.depthWrite = depthWrite;
+ }
+
+ /**
+ * Enables wireframe rendering mode.
+ *
+ * <p>When in wireframe mode, {@link Mesh meshes} rendered in triangle mode
+ * will not be solid, but instead, only the edges of the triangles
+ * will be rendered.
+ *
+ * @param wireframe True to enable wireframe mode.
+ */
+ public void setWireframe(boolean wireframe) {
+ applyWireFrame = true;
+ this.wireframe = wireframe;
+ }
+
+ /**
+ * Offsets the on-screen z-order of the material's polygons, to combat visual artefacts like
+ * stitching, bleeding and z-fighting for overlapping polygons.
+ * Factor and units are summed to produce the depth offset.
+ * This offset is applied in screen space,
+ * typically with positive Z pointing into the screen.
+ * Typical values are (1.0f, 1.0f) or (-1.0f, -1.0f)
+ *
+ * @see <a href="http://www.opengl.org/resources/faq/technical/polygonoffset.htm" rel="nofollow">http://www.opengl.org/resources/faq/technical/polygonoffset.htm</a>
+ * @param factor scales the maximum Z slope, with respect to X or Y of the polygon
+ * @param units scales the minimum resolvable depth buffer value
+ **/
+ public void setPolyOffset(float factor, float units) {
+ applyPolyOffset = true;
+ offsetEnabled = true;
+ offsetFactor = factor;
+ offsetUnits = units;
+ }
+
+ /**
+ * Enable stencil testing.
+ *
+ * <p>Stencil testing can be used to filter pixels according to the stencil
+ * buffer. Objects can be rendered with some stencil operation to manipulate
+ * the values in the stencil buffer, then, other objects can be rendered
+ * to test against the values written previously.
+ *
+ * @param enabled Set to true to enable stencil functionality. If false
+ * all other parameters are ignored.
+ *
+ * @param _frontStencilStencilFailOperation Sets the operation to occur when
+ * a front-facing triangle fails the front stencil function.
+ * @param _frontStencilDepthFailOperation Sets the operation to occur when
+ * a front-facing triangle fails the depth test.
+ * @param _frontStencilDepthPassOperation Set the operation to occur when
+ * a front-facing triangle passes the depth test.
+ * @param _backStencilStencilFailOperation Set the operation to occur when
+ * a back-facing triangle fails the back stencil function.
+ * @param _backStencilDepthFailOperation Set the operation to occur when
+ * a back-facing triangle fails the depth test.
+ * @param _backStencilDepthPassOperation Set the operation to occur when
+ * a back-facing triangle passes the depth test.
+ * @param _frontStencilFunction Set the test function for front-facing triangles.
+ * @param _backStencilFunction Set the test function for back-facing triangles.
+ */
+ public void setStencil(boolean enabled,
+ StencilOperation _frontStencilStencilFailOperation,
+ StencilOperation _frontStencilDepthFailOperation,
+ StencilOperation _frontStencilDepthPassOperation,
+ StencilOperation _backStencilStencilFailOperation,
+ StencilOperation _backStencilDepthFailOperation,
+ StencilOperation _backStencilDepthPassOperation,
+ TestFunction _frontStencilFunction,
+ TestFunction _backStencilFunction) {
+
+ stencilTest = enabled;
+ applyStencilTest = true;
+ this.frontStencilStencilFailOperation = _frontStencilStencilFailOperation;
+ this.frontStencilDepthFailOperation = _frontStencilDepthFailOperation;
+ this.frontStencilDepthPassOperation = _frontStencilDepthPassOperation;
+ this.backStencilStencilFailOperation = _backStencilStencilFailOperation;
+ this.backStencilDepthFailOperation = _backStencilDepthFailOperation;
+ this.backStencilDepthPassOperation = _backStencilDepthPassOperation;
+ this.frontStencilFunction = _frontStencilFunction;
+ this.backStencilFunction = _backStencilFunction;
+ }
+
+ /**
+ * Check if stencil test is enabled.
+ *
+ * @return True if stencil test is enabled.
+ */
+ public boolean isStencilTest() {
+ return stencilTest;
+ }
+
+ /**
+ * Retrieve the front stencil fail operation.
+ *
+ * @return the front stencil fail operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getFrontStencilStencilFailOperation() {
+ return frontStencilStencilFailOperation;
+ }
+
+ /**
+ * Retrieve the front depth test fail operation.
+ *
+ * @return the front depth test fail operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getFrontStencilDepthFailOperation() {
+ return frontStencilDepthFailOperation;
+ }
+
+ /**
+ * Retrieve the front depth test pass operation.
+ *
+ * @return the front depth test pass operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getFrontStencilDepthPassOperation() {
+ return frontStencilDepthPassOperation;
+ }
+
+ /**
+ * Retrieve the back stencil fail operation.
+ *
+ * @return the back stencil fail operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getBackStencilStencilFailOperation() {
+ return backStencilStencilFailOperation;
+ }
+
+ /**
+ * Retrieve the back depth test fail operation.
+ *
+ * @return the back depth test fail operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getBackStencilDepthFailOperation() {
+ return backStencilDepthFailOperation;
+ }
+
+ /**
+ * Retrieve the back depth test pass operation.
+ *
+ * @return the back depth test pass operation.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public StencilOperation getBackStencilDepthPassOperation() {
+ return backStencilDepthPassOperation;
+ }
+
+ /**
+ * Retrieve the front stencil function.
+ *
+ * @return the front stencil function.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public TestFunction getFrontStencilFunction() {
+ return frontStencilFunction;
+ }
+
+ /**
+ * Retrieve the back stencil function.
+ *
+ * @return the back stencil function.
+ *
+ * @see RenderState#setStencil(boolean,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.StencilOperation,
+ * com.jme3.material.RenderState.TestFunction,
+ * com.jme3.material.RenderState.TestFunction)
+ */
+ public TestFunction getBackStencilFunction() {
+ return backStencilFunction;
+ }
+
+ /**
+ * Retrieve the blend mode.
+ *
+ * @return the blend mode.
+ */
+ public BlendMode getBlendMode() {
+ return blendMode;
+ }
+
+ /**
+ * Check if point sprite mode is enabled
+ *
+ * @return True if point sprite mode is enabled.
+ *
+ * @see RenderState#setPointSprite(boolean)
+ */
+ public boolean isPointSprite() {
+ return pointSprite;
+ }
+
+ /**
+ * Check if alpha test is enabled.
+ *
+ * @return True if alpha test is enabled.
+ *
+ * @see RenderState#setAlphaTest(boolean)
+ */
+ public boolean isAlphaTest() {
+ return alphaTest;
+ }
+
+ /**
+ * Retrieve the face cull mode.
+ *
+ * @return the face cull mode.
+ *
+ * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+ */
+ public FaceCullMode getFaceCullMode() {
+ return cullMode;
+ }
+
+ /**
+ * Check if depth test is enabled.
+ *
+ * @return True if depth test is enabled.
+ *
+ * @see RenderState#setDepthTest(boolean)
+ */
+ public boolean isDepthTest() {
+ return depthTest;
+ }
+
+ /**
+ * Check if depth write is enabled.
+ *
+ * @return True if depth write is enabled.
+ *
+ * @see RenderState#setDepthWrite(boolean)
+ */
+ public boolean isDepthWrite() {
+ return depthWrite;
+ }
+
+ /**
+ * Check if wireframe mode is enabled.
+ *
+ * @return True if wireframe mode is enabled.
+ *
+ * @see RenderState#setWireframe(boolean)
+ */
+ public boolean isWireframe() {
+ return wireframe;
+ }
+
+ /**
+ * Check if color writing is enabled.
+ *
+ * @return True if color writing is enabled.
+ *
+ * @see RenderState#setColorWrite(boolean)
+ */
+ public boolean isColorWrite() {
+ return colorWrite;
+ }
+
+ /**
+ * Retrieve the poly offset factor value.
+ *
+ * @return the poly offset factor value.
+ *
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public float getPolyOffsetFactor() {
+ return offsetFactor;
+ }
+
+ /**
+ * Retrieve the poly offset units value.
+ *
+ * @return the poly offset units value.
+ *
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public float getPolyOffsetUnits() {
+ return offsetUnits;
+ }
+
+ /**
+ * Check if polygon offset is enabled.
+ *
+ * @return True if polygon offset is enabled.
+ *
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public boolean isPolyOffset() {
+ return offsetEnabled;
+ }
+
+ /**
+ * Retrieve the alpha falloff value.
+ *
+ * @return the alpha falloff value.
+ *
+ * @see RenderState#setAlphaFallOff(float)
+ */
+ public float getAlphaFallOff() {
+ return alphaFallOff;
+ }
+
+ public boolean isApplyAlphaFallOff() {
+ return applyAlphaFallOff;
+ }
+
+ public boolean isApplyAlphaTest() {
+ return applyAlphaTest;
+ }
+
+ public boolean isApplyBlendMode() {
+ return applyBlendMode;
+ }
+
+ public boolean isApplyColorWrite() {
+ return applyColorWrite;
+ }
+
+ public boolean isApplyCullMode() {
+ return applyCullMode;
+ }
+
+ public boolean isApplyDepthTest() {
+ return applyDepthTest;
+ }
+
+ public boolean isApplyDepthWrite() {
+ return applyDepthWrite;
+ }
+
+ public boolean isApplyPointSprite() {
+ return applyPointSprite;
+ }
+
+ public boolean isApplyPolyOffset() {
+ return applyPolyOffset;
+ }
+
+ public boolean isApplyWireFrame() {
+ return applyWireFrame;
+ }
+
+ /**
+ * Merges <code>this</code> state and <code>additionalState</code> into
+ * the parameter <code>state</code> based on a specific criteria.
+ *
+ * <p>The criteria for this merge is the following:<br/>
+ * For every given property, such as alpha test or depth write, check
+ * if it was modified from the original in the <code>additionalState</code>
+ * if it was modified, then copy the property from the <code>additionalState</code>
+ * into the parameter <code>state</code>, otherwise, copy the property from <code>this</code>
+ * into the parameter <code>state</code>. If <code>additionalState</code>
+ * is <code>null</code>, then no modifications are made and <code>this</code> is returned,
+ * otherwise, the parameter <code>state</code> is returned with the result
+ * of the merge.
+ *
+ * @param additionalState The <code>additionalState</code>, from which data is taken only
+ * if it was modified by the user.
+ * @param state Contains output of the method if <code>additionalState</code>
+ * is not null.
+ * @return <code>state</code> if <code>additionalState</code> is non-null,
+ * otherwise returns <code>this</code>
+ */
+ public RenderState copyMergedTo(RenderState additionalState, RenderState state) {
+ if (additionalState == null) {
+ return this;
+ }
+
+ if (additionalState.applyPointSprite) {
+ state.pointSprite = additionalState.pointSprite;
+ } else {
+ state.pointSprite = pointSprite;
+ }
+ if (additionalState.applyWireFrame) {
+ state.wireframe = additionalState.wireframe;
+ } else {
+ state.wireframe = wireframe;
+ }
+
+ if (additionalState.applyCullMode) {
+ state.cullMode = additionalState.cullMode;
+ } else {
+ state.cullMode = cullMode;
+ }
+ if (additionalState.applyDepthWrite) {
+ state.depthWrite = additionalState.depthWrite;
+ } else {
+ state.depthWrite = depthWrite;
+ }
+ if (additionalState.applyDepthTest) {
+ state.depthTest = additionalState.depthTest;
+ } else {
+ state.depthTest = depthTest;
+ }
+ if (additionalState.applyColorWrite) {
+ state.colorWrite = additionalState.colorWrite;
+ } else {
+ state.colorWrite = colorWrite;
+ }
+ if (additionalState.applyBlendMode) {
+ state.blendMode = additionalState.blendMode;
+ } else {
+ state.blendMode = blendMode;
+ }
+ if (additionalState.applyAlphaTest) {
+ state.alphaTest = additionalState.alphaTest;
+ } else {
+ state.alphaTest = alphaTest;
+ }
+
+ if (additionalState.applyAlphaFallOff) {
+ state.alphaFallOff = additionalState.alphaFallOff;
+ } else {
+ state.alphaFallOff = alphaFallOff;
+ }
+ if (additionalState.applyPolyOffset) {
+ state.offsetEnabled = additionalState.offsetEnabled;
+ state.offsetFactor = additionalState.offsetFactor;
+ state.offsetUnits = additionalState.offsetUnits;
+ } else {
+ state.offsetEnabled = offsetEnabled;
+ state.offsetFactor = offsetFactor;
+ state.offsetUnits = offsetUnits;
+ }
+ if (additionalState.applyStencilTest){
+ state.stencilTest = additionalState.stencilTest;
+
+ state.frontStencilStencilFailOperation = additionalState.frontStencilStencilFailOperation;
+ state.frontStencilDepthFailOperation = additionalState.frontStencilDepthFailOperation;
+ state.frontStencilDepthPassOperation = additionalState.frontStencilDepthPassOperation;
+
+ state.backStencilStencilFailOperation = additionalState.backStencilStencilFailOperation;
+ state.backStencilDepthFailOperation = additionalState.backStencilDepthFailOperation;
+ state.backStencilDepthPassOperation = additionalState.backStencilDepthPassOperation;
+
+ state.frontStencilFunction = additionalState.frontStencilFunction;
+ state.backStencilFunction = additionalState.backStencilFunction;
+ }else{
+ state.stencilTest = stencilTest;
+
+ state.frontStencilStencilFailOperation = frontStencilStencilFailOperation;
+ state.frontStencilDepthFailOperation = frontStencilDepthFailOperation;
+ state.frontStencilDepthPassOperation = frontStencilDepthPassOperation;
+
+ state.backStencilStencilFailOperation = backStencilStencilFailOperation;
+ state.backStencilDepthFailOperation = backStencilDepthFailOperation;
+ state.backStencilDepthPassOperation = backStencilDepthPassOperation;
+
+ state.frontStencilFunction = frontStencilFunction;
+ state.backStencilFunction = backStencilFunction;
+ }
+ return state;
+ }
+
+ @Override
+ public String toString() {
+ return "RenderState[\n" + "pointSprite=" + pointSprite + "\napplyPointSprite=" + applyPointSprite + "\nwireframe=" + wireframe + "\napplyWireFrame=" + applyWireFrame + "\ncullMode=" + cullMode + "\napplyCullMode=" + applyCullMode + "\ndepthWrite=" + depthWrite + "\napplyDepthWrite=" + applyDepthWrite + "\ndepthTest=" + depthTest + "\napplyDepthTest=" + applyDepthTest + "\ncolorWrite=" + colorWrite + "\napplyColorWrite=" + applyColorWrite + "\nblendMode=" + blendMode + "\napplyBlendMode=" + applyBlendMode + "\nalphaTest=" + alphaTest + "\napplyAlphaTest=" + applyAlphaTest + "\nalphaFallOff=" + alphaFallOff + "\napplyAlphaFallOff=" + applyAlphaFallOff + "\noffsetEnabled=" + offsetEnabled + "\napplyPolyOffset=" + applyPolyOffset + "\noffsetFactor=" + offsetFactor + "\noffsetUnits=" + offsetUnits + "\n]";
+ }
+}
diff --git a/engine/src/core/com/jme3/material/Technique.java b/engine/src/core/com/jme3/material/Technique.java
new file mode 100644
index 0000000..5ae20a5
--- /dev/null
+++ b/engine/src/core/com/jme3/material/Technique.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.material;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.shader.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Represents a technique instance.
+ */
+public class Technique implements Savable {
+
+ private static final Logger logger = Logger.getLogger(Technique.class.getName());
+ private TechniqueDef def;
+ private Material owner;
+ private ArrayList<Uniform> worldBindUniforms;
+ private DefineList defines;
+ private Shader shader;
+ private boolean needReload = true;
+
+ /**
+ * Creates a new technique instance that implements the given
+ * technique definition.
+ *
+ * @param owner The material that will own this technique
+ * @param def The technique definition being implemented.
+ */
+ public Technique(Material owner, TechniqueDef def) {
+ this.owner = owner;
+ this.def = def;
+ if (def.isUsingShaders()) {
+ this.worldBindUniforms = new ArrayList<Uniform>();
+ this.defines = new DefineList();
+ }
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Technique() {
+ }
+
+ /**
+ * Returns the technique definition that is implemented by this technique
+ * instance.
+ *
+ * @return the technique definition that is implemented by this technique
+ * instance.
+ */
+ public TechniqueDef getDef() {
+ return def;
+ }
+
+ /**
+ * Returns the shader currently used by this technique instance.
+ * <p>
+ * Shaders are typically loaded dynamically when the technique is first
+ * used, therefore, this variable will most likely be null most of the time.
+ *
+ * @return the shader currently used by this technique instance.
+ */
+ public Shader getShader() {
+ return shader;
+ }
+
+ /**
+ * Returns a list of uniforms that implements the world parameters
+ * that were requested by the material definition.
+ *
+ * @return a list of uniforms implementing the world parameters.
+ */
+ public List<Uniform> getWorldBindUniforms() {
+ return worldBindUniforms;
+ }
+
+ /**
+ * Called by the material to tell the technique a parameter was modified
+ */
+ void notifySetParam(String paramName, VarType type, Object value) {
+ String defineName = def.getShaderParamDefine(paramName);
+ if (defineName != null) {
+ needReload = defines.set(defineName, type, value);
+ }
+ if (shader != null) {
+ updateUniformParam(paramName, type, value);
+ }
+ }
+
+ /**
+ * Called by the material to tell the technique a parameter was cleared
+ */
+ void notifyClearParam(String paramName) {
+ String defineName = def.getShaderParamDefine(paramName);
+ if (defineName != null) {
+ needReload = defines.remove(defineName);
+ }
+ if (shader != null) {
+ if (!paramName.startsWith("m_")) {
+ paramName = "m_" + paramName;
+ }
+ shader.removeUniform(paramName);
+ }
+ }
+
+ void updateUniformParam(String paramName, VarType type, Object value, boolean ifNotOwner) {
+ Uniform u = shader.getUniform(paramName);
+
+// if (ifNotOwner && u.getLastChanger() == owner)
+// return;
+
+ switch (type) {
+ case Texture2D: // fall intentional
+ case Texture3D:
+ case TextureArray:
+ case TextureCubeMap:
+ case Int:
+ u.setValue(VarType.Int, value);
+ break;
+ default:
+ u.setValue(type, value);
+ break;
+ }
+// u.setLastChanger(owner);
+ }
+
+ void updateUniformParam(String paramName, VarType type, Object value) {
+ updateUniformParam(paramName, type, value, false);
+ }
+
+ /**
+ * Returns true if the technique must be reloaded.
+ * <p>
+ * If a technique needs to reload, then the {@link Material} should
+ * call {@link #makeCurrent(com.jme3.asset.AssetManager) } on this
+ * technique.
+ *
+ * @return true if the technique must be reloaded.
+ */
+ public boolean isNeedReload() {
+ return needReload;
+ }
+
+ /**
+ * Prepares the technique for use by loading the shader and setting
+ * the proper defines based on material parameters.
+ *
+ * @param assetManager The asset manager to use for loading shaders.
+ */
+ public void makeCurrent(AssetManager assetManager) {
+ // check if reload is needed..
+ if (def.isUsingShaders()) {
+ DefineList newDefines = new DefineList();
+ Collection<MatParam> params = owner.getParams();
+ for (MatParam param : params) {
+ String defineName = def.getShaderParamDefine(param.getName());
+ if (defineName != null) {
+ newDefines.set(defineName, param.getVarType(), param.getValue());
+ }
+ }
+
+ if (!needReload && defines.getCompiled().equals(newDefines.getCompiled())) {
+ newDefines = null;
+ // defines have not been changed..
+ } else {
+ defines.clear();
+ defines.addFrom(newDefines);
+ // defines changed, recompile needed
+ loadShader(assetManager);
+ }
+ }
+ }
+
+ private void loadShader(AssetManager manager) {
+ // recompute define list
+ DefineList allDefines = new DefineList();
+ allDefines.addFrom(def.getShaderPresetDefines());
+ allDefines.addFrom(defines);
+
+ ShaderKey key = new ShaderKey(def.getVertexShaderName(),
+ def.getFragmentShaderName(),
+ allDefines,
+ def.getShaderLanguage());
+ shader = manager.loadShader(key);
+ if (shader == null) {
+ logger.warning("Failed to reload shader!");
+ return;
+ }
+
+ // refresh the uniform links
+ //owner.updateUniformLinks();
+
+ // register the world bound uniforms
+ worldBindUniforms.clear();
+ if (def.getWorldBindings() != null) {
+ for (UniformBinding binding : def.getWorldBindings()) {
+ Uniform uniform = shader.getUniform("g_" + binding.name());
+ uniform.setBinding(binding);
+ if (uniform != null) {
+ worldBindUniforms.add(uniform);
+ }
+ }
+ }
+
+ needReload = false;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(def, "def", null);
+ // TODO:
+ // oc.write(owner, "owner", null);
+ oc.writeSavableArrayList(worldBindUniforms, "worldBindUniforms", null);
+ oc.write(defines, "defines", null);
+ oc.write(shader, "shader", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ def = (TechniqueDef) ic.readSavable("def", null);
+ worldBindUniforms = ic.readSavableArrayList("worldBindUniforms", null);
+ defines = (DefineList) ic.readSavable("defines", null);
+ shader = (Shader) ic.readSavable("shader", null);
+ //if (shader != null)
+ // owner.updateUniformLinks();
+ }
+}
diff --git a/engine/src/core/com/jme3/material/TechniqueDef.java b/engine/src/core/com/jme3/material/TechniqueDef.java
new file mode 100644
index 0000000..aaeb340
--- /dev/null
+++ b/engine/src/core/com/jme3/material/TechniqueDef.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.material;
+
+import com.jme3.export.*;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.shader.DefineList;
+import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Describes a technique definition.
+ *
+ * @author Kirill Vainer
+ */
+public class TechniqueDef implements Savable {
+
+ /**
+ * Describes light rendering mode.
+ */
+ public enum LightMode {
+ /**
+ * Disable light-based rendering
+ */
+ Disable,
+
+ /**
+ * Enable light rendering by using a single pass.
+ * <p>
+ * An array of light positions and light colors is passed to the shader
+ * containing the world light list for the geometry being rendered.
+ */
+ SinglePass,
+
+ /**
+ * Enable light rendering by using multi-pass rendering.
+ * <p>
+ * The geometry will be rendered once for each light. Each time the
+ * light position and light color uniforms are updated to contain
+ * the values for the current light. The ambient light color uniform
+ * is only set to the ambient light color on the first pass, future
+ * passes have it set to black.
+ */
+ MultiPass,
+
+ /**
+ * Enable light rendering by using the
+ * {@link Renderer#setLighting(com.jme3.light.LightList) renderer's setLighting}
+ * method.
+ * <p>
+ * The specific details of rendering the lighting is up to the
+ * renderer implementation.
+ */
+ FixedPipeline,
+ }
+
+ public enum ShadowMode {
+ Disable,
+ InPass,
+ PostPass,
+ }
+
+ private EnumSet<Caps> requiredCaps = EnumSet.noneOf(Caps.class);
+ private String name;
+
+ private String vertName;
+ private String fragName;
+ private String shaderLang;
+ private DefineList presetDefines;
+ private boolean usesShaders;
+
+ private RenderState renderState;
+ private LightMode lightMode = LightMode.Disable;
+ private ShadowMode shadowMode = ShadowMode.Disable;
+
+ private HashMap<String, String> defineParams;
+ private ArrayList<UniformBinding> worldBinds;
+
+ /**
+ * Creates a new technique definition.
+ * <p>
+ * Used internally by the J3M/J3MD loader.
+ *
+ * @param name The name of the technique, should be set to <code>null</code>
+ * for default techniques.
+ */
+ public TechniqueDef(String name){
+ this.name = name == null ? "Default" : name;
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public TechniqueDef(){
+ }
+
+ /**
+ * Returns the name of this technique as specified in the J3MD file.
+ * Default techniques have the name "Default".
+ *
+ * @return the name of this technique
+ */
+ public String getName(){
+ return name;
+ }
+
+ /**
+ * Returns the light mode.
+ * @return the light mode.
+ * @see LightMode
+ */
+ public LightMode getLightMode() {
+ return lightMode;
+ }
+
+ /**
+ * Set the light mode
+ *
+ * @param lightMode the light mode
+ *
+ * @see LightMode
+ */
+ public void setLightMode(LightMode lightMode) {
+ this.lightMode = lightMode;
+ }
+
+ /**
+ * Returns the shadow mode.
+ * @return the shadow mode.
+ */
+ public ShadowMode getShadowMode() {
+ return shadowMode;
+ }
+
+ /**
+ * Set the shadow mode.
+ *
+ * @param shadowMode the shadow mode.
+ *
+ * @see ShadowMode
+ */
+ public void setShadowMode(ShadowMode shadowMode) {
+ this.shadowMode = shadowMode;
+ }
+
+ /**
+ * Returns the render state that this technique is using
+ * @return the render state that this technique is using
+ * @see #setRenderState(com.jme3.material.RenderState)
+ */
+ public RenderState getRenderState() {
+ return renderState;
+ }
+
+ /**
+ * Sets the render state that this technique is using.
+ *
+ * @param renderState the render state that this technique is using.
+ *
+ * @see RenderState
+ */
+ public void setRenderState(RenderState renderState) {
+ this.renderState = renderState;
+ }
+
+ /**
+ * Returns true if this technique uses shaders, false otherwise.
+ *
+ * @return true if this technique uses shaders, false otherwise.
+ *
+ * @see #setShaderFile(java.lang.String, java.lang.String, java.lang.String)
+ */
+ public boolean isUsingShaders(){
+ return usesShaders;
+ }
+
+ /**
+ * Gets the {@link Caps renderer capabilities} that are required
+ * by this technique.
+ *
+ * @return the required renderer capabilities
+ */
+ public EnumSet<Caps> getRequiredCaps() {
+ return requiredCaps;
+ }
+
+ /**
+ * Sets the shaders that this technique definition will use.
+ *
+ * @param vertexShader The name of the vertex shader
+ * @param fragmentShader The name of the fragment shader
+ * @param shaderLanguage The shader language
+ */
+ public void setShaderFile(String vertexShader, String fragmentShader, String shaderLanguage){
+ this.vertName = vertexShader;
+ this.fragName = fragmentShader;
+ this.shaderLang = shaderLanguage;
+
+ Caps langCap = Caps.valueOf(shaderLanguage);
+ requiredCaps.add(langCap);
+
+ usesShaders = true;
+ }
+
+ /**
+ * Returns the define name which the given material parameter influences.
+ *
+ * @param paramName The parameter name to look up
+ * @return The define name
+ *
+ * @see #addShaderParamDefine(java.lang.String, java.lang.String)
+ */
+ public String getShaderParamDefine(String paramName){
+ if (defineParams == null)
+ return null;
+
+ return defineParams.get(paramName);
+ }
+
+ /**
+ * Adds a define linked to a material parameter.
+ * <p>
+ * Any time the material parameter on the parent material is altered,
+ * the appropriate define on the technique will be modified as well.
+ * See the method
+ * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
+ * on the exact details of how the material parameter changes the define.
+ *
+ * @param paramName The name of the material parameter to link to.
+ * @param defineName The name of the define parameter, e.g. USE_LIGHTING
+ */
+ public void addShaderParamDefine(String paramName, String defineName){
+ if (defineParams == null)
+ defineParams = new HashMap<String, String>();
+
+ defineParams.put(paramName, defineName);
+ }
+
+ /**
+ * Returns the {@link DefineList} for the preset defines.
+ *
+ * @return the {@link DefineList} for the preset defines.
+ *
+ * @see #addShaderPresetDefine(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
+ */
+ public DefineList getShaderPresetDefines() {
+ return presetDefines;
+ }
+
+ /**
+ * Adds a preset define.
+ * <p>
+ * Preset defines do not depend upon any parameters to be activated,
+ * they are always passed to the shader as long as this technique is used.
+ *
+ * @param defineName The name of the define parameter, e.g. USE_LIGHTING
+ * @param type The type of the define. See
+ * {@link DefineList#set(java.lang.String, com.jme3.shader.VarType, java.lang.Object) }
+ * to see why it matters.
+ *
+ * @param value The value of the define
+ */
+ public void addShaderPresetDefine(String defineName, VarType type, Object value){
+ if (presetDefines == null)
+ presetDefines = new DefineList();
+
+ presetDefines.set(defineName, type, value);
+ }
+
+ /**
+ * Returns the name of the fragment shader used by the technique, or null
+ * if no fragment shader is specified.
+ *
+ * @return the name of the fragment shader to be used.
+ */
+ public String getFragmentShaderName() {
+ return fragName;
+ }
+
+
+ /**
+ * Returns the name of the vertex shader used by the technique, or null
+ * if no vertex shader is specified.
+ *
+ * @return the name of the vertex shader to be used.
+ */
+ public String getVertexShaderName() {
+ return vertName;
+ }
+
+ /**
+ * Returns the shader language of the shaders used in this technique.
+ *
+ * @return the shader language of the shaders used in this technique.
+ */
+ public String getShaderLanguage() {
+ return shaderLang;
+ }
+
+ /**
+ * Adds a new world parameter by the given name.
+ *
+ * @param name The world parameter to add.
+ * @return True if the world parameter name was found and added
+ * to the list of world parameters, false otherwise.
+ */
+ public boolean addWorldParam(String name) {
+ if (worldBinds == null){
+ worldBinds = new ArrayList<UniformBinding>();
+ }
+
+ try {
+ worldBinds.add( UniformBinding.valueOf(name) );
+ return true;
+ } catch (IllegalArgumentException ex){
+ return false;
+ }
+ }
+
+ /**
+ * Returns a list of world parameters that are used by this
+ * technique definition.
+ *
+ * @return The list of world parameters
+ */
+ public List<UniformBinding> getWorldBindings() {
+ return worldBinds;
+ }
+
+ public void write(JmeExporter ex) throws IOException{
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(name, "name", null);
+ oc.write(vertName, "vertName", null);
+ oc.write(fragName, "fragName", null);
+ oc.write(shaderLang, "shaderLang", null);
+ oc.write(presetDefines, "presetDefines", null);
+ oc.write(lightMode, "lightMode", LightMode.Disable);
+ oc.write(shadowMode, "shadowMode", ShadowMode.Disable);
+ oc.write(renderState, "renderState", null);
+ oc.write(usesShaders, "usesShaders", false);
+ // TODO: Finish this when Map<String, String> export is available
+// oc.write(defineParams, "defineParams", null);
+ // TODO: Finish this when List<Enum> export is available
+// oc.write(worldBinds, "worldBinds", null);
+ }
+
+ public void read(JmeImporter im) throws IOException{
+ InputCapsule ic = im.getCapsule(this);
+ name = ic.readString("name", null);
+ vertName = ic.readString("vertName", null);
+ fragName = ic.readString("fragName", null);
+ shaderLang = ic.readString("shaderLang", null);
+ presetDefines = (DefineList) ic.readSavable("presetDefines", null);
+ lightMode = ic.readEnum("lightMode", LightMode.class, LightMode.Disable);
+ shadowMode = ic.readEnum("shadowMode", ShadowMode.class, ShadowMode.Disable);
+ renderState = (RenderState) ic.readSavable("renderState", null);
+ usesShaders = ic.readBoolean("usesShaders", false);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/material/package.html b/engine/src/core/com/jme3/material/package.html
new file mode 100644
index 0000000..9af9cc8
--- /dev/null
+++ b/engine/src/core/com/jme3/material/package.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.material</code> package contains classes for manipulating
+jMonkeyEngine materials.
+Materials are applied to {@link com.jme3.scene.Geometry geometries} in the
+scene.
+Each geometry has a single material which is used to render that
+geometry.
+<p>
+Materials (also known as material instances) are extended from
+material definitions.
+
+<h3>Material definitions</h3>
+<p>
+Material definitions provide the "logic" for the material. Usually a shader that
+will handle drawing the object, and corresponding parameters that allow
+configuration of the shader.
+Material definitions can be created through J3MD files.
+The J3MD file abstracts the shader and its configuration away from the user, allowing a
+simple interface where one can simply set a few parameters on the material to change its
+appearance and the way its handled.
+
+<h3>Techniques</h3>
+<p>
+Techniques specify a specific way of rendering a material. Typically
+a technique is used to implement the same material for each configuration
+of the system. For GPUs that do not support shaders, a "fixed function pipeline"
+technique could exist to take care of rendering for that configuration
+
+<h3>Render states</h3>
+<p>
+See {@link com.jme3.material.RenderState}.
+
+<h3>Example Usage</h3>
+<p>
+Creating a textured material
+<code>
+// Create a material instance
+Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+
+// Load the texture.
+Texture tex = assetManager.loadTexture("Textures/Test/Test.jpg");
+
+// Set the parameters
+mat.setTexture("ColorMap", tex);
+</code>
+
+
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/math/AbstractTriangle.java b/engine/src/core/com/jme3/math/AbstractTriangle.java
new file mode 100644
index 0000000..1ea6da5
--- /dev/null
+++ b/engine/src/core/com/jme3/math/AbstractTriangle.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+
+public abstract class AbstractTriangle implements Collidable {
+
+ public abstract Vector3f get1();
+ public abstract Vector3f get2();
+ public abstract Vector3f get3();
+ public abstract void set(Vector3f v1, Vector3f v2, Vector3f v3);
+
+ public int collideWith(Collidable other, CollisionResults results){
+ return other.collideWith(this, results);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/math/ColorRGBA.java b/engine/src/core/com/jme3/math/ColorRGBA.java
new file mode 100644
index 0000000..a22d453
--- /dev/null
+++ b/engine/src/core/com/jme3/math/ColorRGBA.java
@@ -0,0 +1,549 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+/**
+ * <code>ColorRGBA</code> defines a color made from a collection of red, green
+ * and blue values. An alpha value determines is transparency. All values must
+ * be between 0 and 1. If any value is set higher or lower than these
+ * constraints they are clamped to the min or max. That is, if a value smaller
+ * than zero is set the value clamps to zero. If a value higher than 1 is
+ * passed, that value is clamped to 1. However, because the attributes r, g, b,
+ * a are public for efficiency reasons, they can be directly modified with
+ * invalid values. The client should take care when directly addressing the
+ * values. A call to clamp will assure that the values are within the
+ * constraints.
+ *
+ * @author Mark Powell
+ * @version $Id: ColorRGBA.java,v 1.29 2007/09/09 18:25:14 irrisor Exp $
+ */
+public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+ /**
+ * the color black (0,0,0).
+ */
+ public static final ColorRGBA Black = new ColorRGBA(0f, 0f, 0f, 1f);
+ /**
+ * the color white (1,1,1).
+ */
+ public static final ColorRGBA White = new ColorRGBA(1f, 1f, 1f, 1f);
+ /**
+ * the color gray (.2,.2,.2).
+ */
+ public static final ColorRGBA DarkGray = new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f);
+ /**
+ * the color gray (.5,.5,.5).
+ */
+ public static final ColorRGBA Gray = new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f);
+ /**
+ * the color gray (.8,.8,.8).
+ */
+ public static final ColorRGBA LightGray = new ColorRGBA(0.8f, 0.8f, 0.8f, 1.0f);
+ /**
+ * the color red (1,0,0).
+ */
+ public static final ColorRGBA Red = new ColorRGBA(1f, 0f, 0f, 1f);
+ /**
+ * the color green (0,1,0).
+ */
+ public static final ColorRGBA Green = new ColorRGBA(0f, 1f, 0f, 1f);
+ /**
+ * the color blue (0,0,1).
+ */
+ public static final ColorRGBA Blue = new ColorRGBA(0f, 0f, 1f, 1f);
+ /**
+ * the color yellow (1,1,0).
+ */
+ public static final ColorRGBA Yellow = new ColorRGBA(1f, 1f, 0f, 1f);
+ /**
+ * the color magenta (1,0,1).
+ */
+ public static final ColorRGBA Magenta = new ColorRGBA(1f, 0f, 1f, 1f);
+ /**
+ * the color cyan (0,1,1).
+ */
+ public static final ColorRGBA Cyan = new ColorRGBA(0f, 1f, 1f, 1f);
+ /**
+ * the color orange (251/255, 130/255,0).
+ */
+ public static final ColorRGBA Orange = new ColorRGBA(251f / 255f, 130f / 255f, 0f, 1f);
+ /**
+ * the color brown (65/255, 40/255, 25/255).
+ */
+ public static final ColorRGBA Brown = new ColorRGBA(65f / 255f, 40f / 255f, 25f / 255f, 1f);
+ /**
+ * the color pink (1, 0.68, 0.68).
+ */
+ public static final ColorRGBA Pink = new ColorRGBA(1f, 0.68f, 0.68f, 1f);
+ /**
+ * the black color with no alpha (0, 0, 0, 0);
+ */
+ public static final ColorRGBA BlackNoAlpha = new ColorRGBA(0f, 0f, 0f, 0f);
+ /**
+ * The red component of the color.
+ */
+ public float r;
+ /**
+ * The green component of the color.
+ */
+ public float g;
+ /**
+ * the blue component of the color.
+ */
+ public float b;
+ /**
+ * the alpha component of the color. 0 is transparent and 1 is opaque
+ */
+ public float a;
+
+ /**
+ * Constructor instantiates a new <code>ColorRGBA</code> object. This
+ * color is the default "white" with all values 1.
+ *
+ */
+ public ColorRGBA() {
+ r = g = b = a = 1.0f;
+ }
+
+ /**
+ * Constructor instantiates a new <code>ColorRGBA</code> object. The
+ * values are defined as passed parameters. These values are then clamped
+ * to insure that they are between 0 and 1.
+ * @param r the red component of this color.
+ * @param g the green component of this color.
+ * @param b the blue component of this color.
+ * @param a the alpha component of this color.
+ */
+ public ColorRGBA(float r, float g, float b, float a) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ }
+
+ /**
+ * Copy constructor creates a new <code>ColorRGBA</code> object, based on
+ * a provided color.
+ * @param rgba the <code>ColorRGBA</code> object to copy.
+ */
+ public ColorRGBA(ColorRGBA rgba) {
+ this.a = rgba.a;
+ this.r = rgba.r;
+ this.g = rgba.g;
+ this.b = rgba.b;
+ }
+
+ /**
+ *
+ * <code>set</code> sets the RGBA values of this color. The values are then
+ * clamped to insure that they are between 0 and 1.
+ *
+ * @param r the red component of this color.
+ * @param g the green component of this color.
+ * @param b the blue component of this color.
+ * @param a the alpha component of this color.
+ * @return this
+ */
+ public ColorRGBA set(float r, float g, float b, float a) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ return this;
+ }
+
+ /**
+ * <code>set</code> sets the values of this color to those set by a parameter
+ * color.
+ *
+ * @param rgba ColorRGBA the color to set this color to.
+ * @return this
+ */
+ public ColorRGBA set(ColorRGBA rgba) {
+ if (rgba == null) {
+ r = 0;
+ g = 0;
+ b = 0;
+ a = 0;
+ } else {
+ r = rgba.r;
+ g = rgba.g;
+ b = rgba.b;
+ a = rgba.a;
+ }
+ return this;
+ }
+
+ /**
+ * <code>clamp</code> insures that all values are between 0 and 1. If any
+ * are less than 0 they are set to zero. If any are more than 1 they are
+ * set to one.
+ *
+ */
+ public void clamp() {
+ if (r < 0) {
+ r = 0;
+ } else if (r > 1) {
+ r = 1;
+ }
+
+ if (g < 0) {
+ g = 0;
+ } else if (g > 1) {
+ g = 1;
+ }
+
+ if (b < 0) {
+ b = 0;
+ } else if (b > 1) {
+ b = 1;
+ }
+
+ if (a < 0) {
+ a = 0;
+ } else if (a > 1) {
+ a = 1;
+ }
+ }
+
+ /**
+ *
+ * <code>getColorArray</code> retrieves the color values of this object as
+ * a four element float array.
+ * @return the float array that contains the color elements.
+ */
+ public float[] getColorArray() {
+ return new float[]{r, g, b, a};
+ }
+
+ /**
+ * Stores the current r/g/b/a values into the tempf array. The tempf array must have a
+ * length of 4 or greater, or an array index out of bounds exception will be thrown.
+ * @param store The array of floats to store the values into.
+ * @return The float[] after storage.
+ */
+ public float[] getColorArray(float[] store) {
+ store[0] = r;
+ store[1] = g;
+ store[2] = b;
+ store[3] = a;
+ return store;
+ }
+
+ public float getAlpha() {
+ return a;
+ }
+
+ public float getRed() {
+ return r;
+ }
+
+ public float getBlue() {
+ return b;
+ }
+
+ public float getGreen() {
+ return g;
+ }
+
+ /**
+ * Sets this color to the interpolation by changeAmnt from this to the finalColor
+ * this=(1-changeAmnt)*this + changeAmnt * finalColor
+ * @param finalColor The final color to interpolate towards
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from this towards finalColor
+ */
+ public void interpolate(ColorRGBA finalColor, float changeAmnt) {
+ this.r = (1 - changeAmnt) * this.r + changeAmnt * finalColor.r;
+ this.g = (1 - changeAmnt) * this.g + changeAmnt * finalColor.g;
+ this.b = (1 - changeAmnt) * this.b + changeAmnt * finalColor.b;
+ this.a = (1 - changeAmnt) * this.a + changeAmnt * finalColor.a;
+ }
+
+ /**
+ * Sets this color to the interpolation by changeAmnt from beginColor to finalColor
+ * this=(1-changeAmnt)*beginColor + changeAmnt * finalColor
+ * @param beginColor The begining color (changeAmnt=0)
+ * @param finalColor The final color to interpolate towards (changeAmnt=1)
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from beginColor towards finalColor
+ */
+ public void interpolate(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmnt) {
+ this.r = (1 - changeAmnt) * beginColor.r + changeAmnt * finalColor.r;
+ this.g = (1 - changeAmnt) * beginColor.g + changeAmnt * finalColor.g;
+ this.b = (1 - changeAmnt) * beginColor.b + changeAmnt * finalColor.b;
+ this.a = (1 - changeAmnt) * beginColor.a + changeAmnt * finalColor.a;
+ }
+
+ /**
+ *
+ * <code>randomColor</code> is a utility method that generates a random
+ * color.
+ *
+ * @return a random color.
+ */
+ public static ColorRGBA randomColor() {
+ ColorRGBA rVal = new ColorRGBA(0, 0, 0, 1);
+ rVal.r = FastMath.nextRandomFloat();
+ rVal.g = FastMath.nextRandomFloat();
+ rVal.b = FastMath.nextRandomFloat();
+ return rVal;
+ }
+
+ /**
+ * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and
+ * returns the result as a new ColorRGBA. Used as a way of combining colors and lights.
+ * @param c The color to multiply.
+ * @return The new ColorRGBA. this*c
+ */
+ public ColorRGBA mult(ColorRGBA c) {
+ return new ColorRGBA(c.r * r, c.g * g, c.b * b, c.a * a);
+ }
+
+ /**
+ * Multiplies each r/g/b/a of this color by the given scalar and
+ * returns the result as a new ColorRGBA. Used as a way of making colors dimmer
+ * or brighter..
+ * @param scalar The scalar to multiply.
+ * @return The new ColorRGBA. this*scalar
+ */
+ public ColorRGBA mult(float scalar) {
+ return new ColorRGBA(scalar * r, scalar * g, scalar * b, scalar * a);
+ }
+
+ /**
+ * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and
+ * returns the result as a new ColorRGBA. Used as a way of combining colors and lights.
+ * @param scalar scalar to multiply with
+ * @return The new ColorRGBA. this*c
+ */
+ public ColorRGBA multLocal(float scalar) {
+ this.r *= scalar;
+ this.g *= scalar;
+ this.b *= scalar;
+ this.a *= scalar;
+ return this;
+ }
+
+ /**
+ * Adds each r/g/b/a of this color by the r/g/b/a of the given color and
+ * returns the result as a new ColorRGBA.
+ * @param c The color to add.
+ * @return The new ColorRGBA. this+c
+ */
+ public ColorRGBA add(ColorRGBA c) {
+ return new ColorRGBA(c.r + r, c.g + g, c.b + b, c.a + a);
+ }
+
+ /**
+ * Multiplies each r/g/b/a of this color by the r/g/b/a of the given color and
+ * returns the result as a new ColorRGBA. Used as a way of combining colors and lights.
+ * @param c The color to multiply.
+ * @return The new ColorRGBA. this*c
+ */
+ public ColorRGBA addLocal(ColorRGBA c) {
+ set(c.r + r, c.g + g, c.b + b, c.a + a);
+ return this;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this color.
+ * The format of the string is:<br>
+ * <Class Name>: [R=RR.RRRR, G=GG.GGGG, B=BB.BBBB, A=AA.AAAA]
+ * @return the string representation of this color.
+ */
+ public String toString() {
+ return "Color[" + r + ", " + g + ", " + b + ", " + a + "]";
+ }
+
+ @Override
+ public ColorRGBA clone() {
+ try {
+ return (ColorRGBA) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+
+ /**
+ * Saves this ColorRGBA into the given float[] object.
+ *
+ * @param floats
+ * The float[] to take this ColorRGBA. If null, a new float[4] is
+ * created.
+ * @return The array, with R, G, B, A float values in that order
+ */
+ public float[] toArray(float[] floats) {
+ if (floats == null) {
+ floats = new float[4];
+ }
+ floats[0] = r;
+ floats[1] = g;
+ floats[2] = b;
+ floats[3] = a;
+ return floats;
+ }
+
+ /**
+ * <code>equals</code> returns true if this color is logically equivalent
+ * to a given color. That is, if the values of the two colors are the same.
+ * False is returned otherwise.
+ * @param o the object to compare againts.
+ * @return true if the colors are equal, false otherwise.
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof ColorRGBA)) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ ColorRGBA comp = (ColorRGBA) o;
+ if (Float.compare(r, comp.r) != 0) {
+ return false;
+ }
+ if (Float.compare(g, comp.g) != 0) {
+ return false;
+ }
+ if (Float.compare(b, comp.b) != 0) {
+ return false;
+ }
+ if (Float.compare(a, comp.a) != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * <code>hashCode</code> returns a unique code for this color object based
+ * on it's values. If two colors are logically equivalent, they will return
+ * the same hash code value.
+ * @return the hash code value of this color.
+ */
+ public int hashCode() {
+ int hash = 37;
+ hash += 37 * hash + Float.floatToIntBits(r);
+ hash += 37 * hash + Float.floatToIntBits(g);
+ hash += 37 * hash + Float.floatToIntBits(b);
+ hash += 37 * hash + Float.floatToIntBits(a);
+ return hash;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(r, "r", 0);
+ capsule.write(g, "g", 0);
+ capsule.write(b, "b", 0);
+ capsule.write(a, "a", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ r = capsule.readFloat("r", 0);
+ g = capsule.readFloat("g", 0);
+ b = capsule.readFloat("b", 0);
+ a = capsule.readFloat("a", 0);
+ }
+
+ public byte[] asBytesRGBA() {
+ byte[] store = new byte[4];
+ store[0] = (byte) ((int) (r * 255) & 0xFF);
+ store[1] = (byte) ((int) (g * 255) & 0xFF);
+ store[2] = (byte) ((int) (b * 255) & 0xFF);
+ store[3] = (byte) ((int) (a * 255) & 0xFF);
+ return store;
+ }
+
+ public int asIntARGB() {
+ int argb = (((int) (a * 255) & 0xFF) << 24)
+ | (((int) (r * 255) & 0xFF) << 16)
+ | (((int) (g * 255) & 0xFF) << 8)
+ | (((int) (b * 255) & 0xFF));
+ return argb;
+ }
+
+ public int asIntRGBA() {
+ int rgba = (((int) (r * 255) & 0xFF) << 24)
+ | (((int) (g * 255) & 0xFF) << 16)
+ | (((int) (b * 255) & 0xFF) << 8)
+ | (((int) (a * 255) & 0xFF));
+ return rgba;
+ }
+
+ public int asIntABGR() {
+ int abgr = (((int) (a * 255) & 0xFF) << 24)
+ | (((int) (b * 255) & 0xFF) << 16)
+ | (((int) (g * 255) & 0xFF) << 8)
+ | (((int) (r * 255) & 0xFF));
+ return abgr;
+ }
+
+ public void fromIntARGB(int color) {
+ a = ((byte) (color >> 24) & 0xFF) / 255f;
+ r = ((byte) (color >> 16) & 0xFF) / 255f;
+ g = ((byte) (color >> 8) & 0xFF) / 255f;
+ b = ((byte) (color) & 0xFF) / 255f;
+ }
+
+ public void fromIntRGBA(int color) {
+ r = ((byte) (color >> 24) & 0xFF) / 255f;
+ g = ((byte) (color >> 16) & 0xFF) / 255f;
+ b = ((byte) (color >> 8) & 0xFF) / 255f;
+ a = ((byte) (color) & 0xFF) / 255f;
+ }
+
+ /**
+ * Transform the current ColorRGBA to a Vector3f using
+ * x = r, y = g, z = b. The Alpha value is not used.
+ *
+ * This method is useful to use for shaders assignment.
+ * @return A Vector3f containing the RGB value of current color definition.
+ */
+ public Vector3f toVector3f() {
+ return new Vector3f(r, g, b);
+ }
+
+ /**
+ * Transform the current ColorRGBA to a Vector4f using
+ * x = r, y = g, z = b, w = a.
+ *
+ * This method is useful to use for shaders assignment.
+ * @return A Vector4f containing the RGBA value of current color definition.
+ */
+ public Vector4f toVector4f() {
+ return new Vector4f(r, g, b, a);
+ }
+}
diff --git a/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java b/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java
new file mode 100644
index 0000000..079880f
--- /dev/null
+++ b/engine/src/core/com/jme3/math/CurveAndSurfaceMath.java
@@ -0,0 +1,133 @@
+package com.jme3.math;
+
+import com.jme3.math.Spline.SplineType;
+import java.util.List;
+
+/**
+ * This class offers methods to help with curves and surfaces calculations.
+ * @author Marcin Roguski (Kealthas)
+ */
+public class CurveAndSurfaceMath {
+ private static final float KNOTS_MINIMUM_DELTA = 0.0001f;
+
+ /**
+ * A private constructor is defined to avoid instatiation of this class.
+ */
+ private CurveAndSurfaceMath() {}
+
+ /**
+ * This method interpolates tha data for the nurbs curve.
+ * @param u
+ * the u value
+ * @param nurbSpline
+ * the nurbs spline definition
+ * @param store
+ * the resulting point in 3D space
+ */
+ public static void interpolateNurbs(float u, Spline nurbSpline, Vector3f store) {
+ if (nurbSpline.getType() != SplineType.Nurb) {
+ throw new IllegalArgumentException("Given spline is not of a NURB type!");
+ }
+ List<Vector3f> controlPoints = nurbSpline.getControlPoints();
+ float[] weights = nurbSpline.getWeights();
+ List<Float> knots = nurbSpline.getKnots();
+ int controlPointAmount = controlPoints.size();
+
+ store.set(Vector3f.ZERO);
+ float delimeter = 0;
+ for (int i = 0; i < controlPointAmount; ++i) {
+ float val = weights[i] * CurveAndSurfaceMath.computeBaseFunctionValue(i, nurbSpline.getBasisFunctionDegree(), u, knots);
+ store.addLocal(nurbSpline.getControlPoints().get(i)
+ .mult(val));
+ delimeter += val;
+ }
+ store.divideLocal(delimeter);
+ }
+
+ /**
+ * This method interpolates tha data for the nurbs surface.
+ *
+ * @param u
+ * the u value
+ * @param v
+ * the v value
+ * @param controlPoints
+ * the nurbs' control points
+ * @param knots
+ * the nurbs' knots
+ * @param basisUFunctionDegree
+ * the degree of basis U function
+ * @param basisVFunctionDegree
+ * the degree of basis V function
+ * @param store
+ * the resulting point in 3D space
+ */
+ public static void interpolate(float u, float v, List<List<Vector4f>> controlPoints, List<Float>[] knots,
+ int basisUFunctionDegree, int basisVFunctionDegree, Vector3f store) {
+ store.set(Vector3f.ZERO);
+ float delimeter = 0;
+ int vControlPointsAmount = controlPoints.size();
+ int uControlPointsAmount = controlPoints.get(0).size();
+ for (int i = 0; i < vControlPointsAmount; ++i) {
+ for (int j = 0; j < uControlPointsAmount; ++j) {
+ Vector4f controlPoint = controlPoints.get(i).get(j);
+ float val = controlPoint.w
+ * CurveAndSurfaceMath.computeBaseFunctionValue(i, basisVFunctionDegree, v, knots[1])
+ * CurveAndSurfaceMath.computeBaseFunctionValue(j, basisUFunctionDegree, u, knots[0]);
+ store.addLocal(controlPoint.x * val, controlPoint.y * val, controlPoint.z * val);
+ delimeter += val;
+ }
+ }
+ store.divideLocal(delimeter);
+ }
+
+ /**
+ * This method prepares the knots to be used. If the knots represent non-uniform B-splines (first and last knot values are being
+ * repeated) it leads to NaN results during calculations. This method adds a small number to each of such knots to avoid NaN's.
+ * @param knots
+ * the knots to be prepared to use
+ * @param basisFunctionDegree
+ * the degree of basis function
+ */
+ // TODO: improve this; constant delta may lead to errors if the difference between tha last repeated
+ // point and the following one is lower than it
+ public static void prepareNurbsKnots(List<Float> knots, int basisFunctionDegree) {
+ float delta = KNOTS_MINIMUM_DELTA;
+ float prevValue = knots.get(0).floatValue();
+ for(int i=1;i<knots.size();++i) {
+ float value = knots.get(i).floatValue();
+ if(value<=prevValue) {
+ value += delta;
+ knots.set(i, Float.valueOf(value));
+ delta += KNOTS_MINIMUM_DELTA;
+ } else {
+ delta = KNOTS_MINIMUM_DELTA;//reset the delta's value
+ }
+
+ prevValue = value;
+ }
+ }
+
+ /**
+ * This method computes the base function value for the NURB curve.
+ * @param i
+ * the knot index
+ * @param k
+ * the base function degree
+ * @param t
+ * the knot value
+ * @param knots
+ * the knots' values
+ * @return the base function value
+ */
+ private static float computeBaseFunctionValue(int i, int k, float t, List<Float> knots) {
+ if (k == 1) {
+ return knots.get(i) <= t && t < knots.get(i + 1) ? 1.0f : 0.0f;
+ } else {
+ return (t - knots.get(i)) / (knots.get(i + k - 1) - knots.get(i)) *
+ CurveAndSurfaceMath.computeBaseFunctionValue(i, k - 1, t, knots)
+ + (knots.get(i + k) - t) / (knots.get(i + k) - knots.get(i + 1)) *
+ CurveAndSurfaceMath.computeBaseFunctionValue(i + 1, k - 1, t, knots);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Eigen3f.java b/engine/src/core/com/jme3/math/Eigen3f.java
new file mode 100644
index 0000000..e356057
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Eigen3f.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class Eigen3f implements java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Eigen3f.class
+ .getName());
+
+ float[] eigenValues = new float[3];
+ Vector3f[] eigenVectors = new Vector3f[3];
+
+ static final double ONE_THIRD_DOUBLE = 1.0 / 3.0;
+ static final double ROOT_THREE_DOUBLE = Math.sqrt(3.0);
+
+
+ public Eigen3f() {
+
+ }
+
+ public Eigen3f(Matrix3f data) {
+ calculateEigen(data);
+ }
+
+ public void calculateEigen(Matrix3f data) {
+ // prep work...
+ eigenVectors[0] = new Vector3f();
+ eigenVectors[1] = new Vector3f();
+ eigenVectors[2] = new Vector3f();
+
+ Matrix3f scaledData = new Matrix3f(data);
+ float maxMagnitude = scaleMatrix(scaledData);
+
+ // Compute the eigenvalues using double-precision arithmetic.
+ double roots[] = new double[3];
+ computeRoots(scaledData, roots);
+ eigenValues[0] = (float) roots[0];
+ eigenValues[1] = (float) roots[1];
+ eigenValues[2] = (float) roots[2];
+
+ float[] maxValues = new float[3];
+ Vector3f[] maxRows = new Vector3f[3];
+ maxRows[0] = new Vector3f();
+ maxRows[1] = new Vector3f();
+ maxRows[2] = new Vector3f();
+
+ for (int i = 0; i < 3; i++) {
+ Matrix3f tempMatrix = new Matrix3f(scaledData);
+ tempMatrix.m00 -= eigenValues[i];
+ tempMatrix.m11 -= eigenValues[i];
+ tempMatrix.m22 -= eigenValues[i];
+ float[] val = new float[1];
+ val[0] = maxValues[i];
+ if (!positiveRank(tempMatrix, val, maxRows[i])) {
+ // Rank was 0 (or very close to 0), so just return.
+ // Rescale back to the original size.
+ if (maxMagnitude > 1f) {
+ for (int j = 0; j < 3; j++) {
+ eigenValues[j] *= maxMagnitude;
+ }
+ }
+
+ eigenVectors[0].set(Vector3f.UNIT_X);
+ eigenVectors[1].set(Vector3f.UNIT_Y);
+ eigenVectors[2].set(Vector3f.UNIT_Z);
+ return;
+ }
+ maxValues[i] = val[0];
+ }
+
+ float maxCompare = maxValues[0];
+ int i = 0;
+ if (maxValues[1] > maxCompare) {
+ maxCompare = maxValues[1];
+ i = 1;
+ }
+ if (maxValues[2] > maxCompare) {
+ i = 2;
+ }
+
+ // use the largest row to compute and order the eigen vectors.
+ switch (i) {
+ case 0:
+ maxRows[0].normalizeLocal();
+ computeVectors(scaledData, maxRows[0], 1, 2, 0);
+ break;
+ case 1:
+ maxRows[1].normalizeLocal();
+ computeVectors(scaledData, maxRows[1], 2, 0, 1);
+ break;
+ case 2:
+ maxRows[2].normalizeLocal();
+ computeVectors(scaledData, maxRows[2], 0, 1, 2);
+ break;
+ }
+
+ // Rescale the values back to the original size.
+ if (maxMagnitude > 1f) {
+ for (i = 0; i < 3; i++) {
+ eigenValues[i] *= maxMagnitude;
+ }
+ }
+ }
+
+ /**
+ * Scale the matrix so its entries are in [-1,1]. The scaling is applied
+ * only when at least one matrix entry has magnitude larger than 1.
+ *
+ * @return the max magnitude in this matrix
+ */
+ private float scaleMatrix(Matrix3f mat) {
+
+ float max = FastMath.abs(mat.m00);
+ float abs = FastMath.abs(mat.m01);
+
+ if (abs > max) {
+ max = abs;
+ }
+ abs = FastMath.abs(mat.m02);
+ if (abs > max) {
+ max = abs;
+ }
+ abs = FastMath.abs(mat.m11);
+ if (abs > max) {
+ max = abs;
+ }
+ abs = FastMath.abs(mat.m12);
+ if (abs > max) {
+ max = abs;
+ }
+ abs = FastMath.abs(mat.m22);
+ if (abs > max) {
+ max = abs;
+ }
+
+ if (max > 1f) {
+ float fInvMax = 1f / max;
+ mat.multLocal(fInvMax);
+ }
+
+ return max;
+ }
+
+ /**
+ * Compute the eigenvectors of the given Matrix, using the
+ * @param mat
+ * @param vect
+ * @param index1
+ * @param index2
+ * @param index3
+ */
+ private void computeVectors(Matrix3f mat, Vector3f vect, int index1,
+ int index2, int index3) {
+ Vector3f vectorU = new Vector3f(), vectorV = new Vector3f();
+ Vector3f.generateComplementBasis(vectorU, vectorV, vect);
+
+ Vector3f tempVect = mat.mult(vectorU);
+ float p00 = eigenValues[index3] - vectorU.dot(tempVect);
+ float p01 = vectorV.dot(tempVect);
+ float p11 = eigenValues[index3] - vectorV.dot(mat.mult(vectorV));
+ float invLength;
+ float max = FastMath.abs(p00);
+ int row = 0;
+ float fAbs = FastMath.abs(p01);
+ if (fAbs > max) {
+ max = fAbs;
+ }
+ fAbs = FastMath.abs(p11);
+ if (fAbs > max) {
+ max = fAbs;
+ row = 1;
+ }
+
+ if (max >= FastMath.ZERO_TOLERANCE) {
+ if (row == 0) {
+ invLength = FastMath.invSqrt(p00 * p00 + p01 * p01);
+ p00 *= invLength;
+ p01 *= invLength;
+ vectorU.mult(p01, eigenVectors[index3])
+ .addLocal(vectorV.mult(p00));
+ } else {
+ invLength = FastMath.invSqrt(p11 * p11 + p01 * p01);
+ p11 *= invLength;
+ p01 *= invLength;
+ vectorU.mult(p11, eigenVectors[index3])
+ .addLocal(vectorV.mult(p01));
+ }
+ } else {
+ if (row == 0) {
+ eigenVectors[index3] = vectorV;
+ } else {
+ eigenVectors[index3] = vectorU;
+ }
+ }
+
+ Vector3f vectorS = vect.cross(eigenVectors[index3]);
+ mat.mult(vect, tempVect);
+ p00 = eigenValues[index1] - vect.dot(tempVect);
+ p01 = vectorS.dot(tempVect);
+ p11 = eigenValues[index1] - vectorS.dot(mat.mult(vectorS));
+ max = FastMath.abs(p00);
+ row = 0;
+ fAbs = FastMath.abs(p01);
+ if (fAbs > max) {
+ max = fAbs;
+ }
+ fAbs = FastMath.abs(p11);
+ if (fAbs > max) {
+ max = fAbs;
+ row = 1;
+ }
+
+ if (max >= FastMath.ZERO_TOLERANCE) {
+ if (row == 0) {
+ invLength = FastMath.invSqrt(p00 * p00 + p01 * p01);
+ p00 *= invLength;
+ p01 *= invLength;
+ eigenVectors[index1] = vect.mult(p01).add(vectorS.mult(p00));
+ } else {
+ invLength = FastMath.invSqrt(p11 * p11 + p01 * p01);
+ p11 *= invLength;
+ p01 *= invLength;
+ eigenVectors[index1] = vect.mult(p11).add(vectorS.mult(p01));
+ }
+ } else {
+ if (row == 0) {
+ eigenVectors[index1].set(vectorS);
+ } else {
+ eigenVectors[index1].set(vect);
+ }
+ }
+
+ eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]);
+ }
+
+ /**
+ * Check the rank of the given Matrix to determine if it is positive. While
+ * doing so, store the max magnitude entry in the given float store and the
+ * max row of the matrix in the Vector store.
+ *
+ * @param matrix
+ * the Matrix3f to analyze.
+ * @param maxMagnitudeStore
+ * a float array in which to store (in the 0th position) the max
+ * magnitude entry of the matrix.
+ * @param maxRowStore
+ * a Vector3f to store the values of the row of the matrix
+ * containing the max magnitude entry.
+ * @return true if the given matrix has a non 0 rank.
+ */
+ private boolean positiveRank(Matrix3f matrix, float[] maxMagnitudeStore, Vector3f maxRowStore) {
+ // Locate the maximum-magnitude entry of the matrix.
+ maxMagnitudeStore[0] = -1f;
+ int iRow, iCol, iMaxRow = -1;
+ for (iRow = 0; iRow < 3; iRow++) {
+ for (iCol = iRow; iCol < 3; iCol++) {
+ float fAbs = FastMath.abs(matrix.get(iRow, iCol));
+ if (fAbs > maxMagnitudeStore[0]) {
+ maxMagnitudeStore[0] = fAbs;
+ iMaxRow = iRow;
+ }
+ }
+ }
+
+ // Return the row containing the maximum, to be used for eigenvector
+ // construction.
+ maxRowStore.set(matrix.getRow(iMaxRow));
+
+ return maxMagnitudeStore[0] >= FastMath.ZERO_TOLERANCE;
+ }
+
+ /**
+ * Generate the base eigen values of the given matrix using double precision
+ * math.
+ *
+ * @param mat
+ * the Matrix3f to analyze.
+ * @param rootsStore
+ * a double array to store the results in. Must be at least
+ * length 3.
+ */
+ private void computeRoots(Matrix3f mat, double[] rootsStore) {
+ // Convert the unique matrix entries to double precision.
+ double a = mat.m00, b = mat.m01, c = mat.m02,
+ d = mat.m11, e = mat.m12,
+ f = mat.m22;
+
+ // The characteristic equation is x^3 - c2*x^2 + c1*x - c0 = 0. The
+ // eigenvalues are the roots to this equation, all guaranteed to be
+ // real-valued, because the matrix is symmetric.
+ double char0 = a * d * f + 2.0 * b * c * e - a
+ * e * e - d * c * c - f * b * b;
+
+ double char1 = a * d - b * b + a * f - c * c
+ + d * f - e * e;
+
+ double char2 = a + d + f;
+
+ // Construct the parameters used in classifying the roots of the
+ // equation and in solving the equation for the roots in closed form.
+ double char2Div3 = char2 * ONE_THIRD_DOUBLE;
+ double abcDiv3 = (char1 - char2 * char2Div3) * ONE_THIRD_DOUBLE;
+ if (abcDiv3 > 0.0) {
+ abcDiv3 = 0.0;
+ }
+
+ double mbDiv2 = 0.5 * (char0 + char2Div3 * (2.0 * char2Div3 * char2Div3 - char1));
+
+ double q = mbDiv2 * mbDiv2 + abcDiv3 * abcDiv3 * abcDiv3;
+ if (q > 0.0) {
+ q = 0.0;
+ }
+
+ // Compute the eigenvalues by solving for the roots of the polynomial.
+ double magnitude = Math.sqrt(-abcDiv3);
+ double angle = Math.atan2(Math.sqrt(-q), mbDiv2) * ONE_THIRD_DOUBLE;
+ double cos = Math.cos(angle);
+ double sin = Math.sin(angle);
+ double root0 = char2Div3 + 2.0 * magnitude * cos;
+ double root1 = char2Div3 - magnitude
+ * (cos + ROOT_THREE_DOUBLE * sin);
+ double root2 = char2Div3 - magnitude
+ * (cos - ROOT_THREE_DOUBLE * sin);
+
+ // Sort in increasing order.
+ if (root1 >= root0) {
+ rootsStore[0] = root0;
+ rootsStore[1] = root1;
+ } else {
+ rootsStore[0] = root1;
+ rootsStore[1] = root0;
+ }
+
+ if (root2 >= rootsStore[1]) {
+ rootsStore[2] = root2;
+ } else {
+ rootsStore[2] = rootsStore[1];
+ if (root2 >= rootsStore[0]) {
+ rootsStore[1] = root2;
+ } else {
+ rootsStore[1] = rootsStore[0];
+ rootsStore[0] = root2;
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ Matrix3f mat = new Matrix3f(2, 1, 1, 1, 2, 1, 1, 1, 2);
+ Eigen3f eigenSystem = new Eigen3f(mat);
+
+ logger.info("eigenvalues = ");
+ for (int i = 0; i < 3; i++)
+ logger.log(Level.INFO, "{0} ", eigenSystem.getEigenValue(i));
+
+ logger.info("eigenvectors = ");
+ for (int i = 0; i < 3; i++) {
+ Vector3f vector = eigenSystem.getEigenVector(i);
+ logger.info(vector.toString());
+ mat.setColumn(i, vector);
+ }
+ logger.info(mat.toString());
+
+ // eigenvalues =
+ // 1.000000 1.000000 4.000000
+ // eigenvectors =
+ // 0.411953 0.704955 0.577350
+ // 0.404533 -0.709239 0.577350
+ // -0.816485 0.004284 0.577350
+ }
+
+ public float getEigenValue(int i) {
+ return eigenValues[i];
+ }
+
+ public Vector3f getEigenVector(int i) {
+ return eigenVectors[i];
+ }
+
+ public float[] getEigenValues() {
+ return eigenValues;
+ }
+
+ public Vector3f[] getEigenVectors() {
+ return eigenVectors;
+ }
+}
diff --git a/engine/src/core/com/jme3/math/FastMath.java b/engine/src/core/com/jme3/math/FastMath.java
new file mode 100644
index 0000000..6043d57
--- /dev/null
+++ b/engine/src/core/com/jme3/math/FastMath.java
@@ -0,0 +1,987 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import java.util.Random;
+
+/**
+ * <code>FastMath</code> provides 'fast' math approximations and float equivalents of Math
+ * functions. These are all used as static values and functions.
+ *
+ * @author Various
+ * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $
+ */
+final public class FastMath {
+
+ private FastMath() {
+ }
+ /** A "close to zero" double epsilon value for use*/
+ public static final double DBL_EPSILON = 2.220446049250313E-16d;
+ /** A "close to zero" float epsilon value for use*/
+ public static final float FLT_EPSILON = 1.1920928955078125E-7f;
+ /** A "close to zero" float epsilon value for use*/
+ public static final float ZERO_TOLERANCE = 0.0001f;
+ public static final float ONE_THIRD = 1f / 3f;
+ /** The value PI as a float. (180 degrees) */
+ public static final float PI = (float) Math.PI;
+ /** The value 2PI as a float. (360 degrees) */
+ public static final float TWO_PI = 2.0f * PI;
+ /** The value PI/2 as a float. (90 degrees) */
+ public static final float HALF_PI = 0.5f * PI;
+ /** The value PI/4 as a float. (45 degrees) */
+ public static final float QUARTER_PI = 0.25f * PI;
+ /** The value 1/PI as a float. */
+ public static final float INV_PI = 1.0f / PI;
+ /** The value 1/(2PI) as a float. */
+ public static final float INV_TWO_PI = 1.0f / TWO_PI;
+ /** A value to multiply a degree value by, to convert it to radians. */
+ public static final float DEG_TO_RAD = PI / 180.0f;
+ /** A value to multiply a radian value by, to convert it to degrees. */
+ public static final float RAD_TO_DEG = 180.0f / PI;
+ /** A precreated random object for random numbers. */
+ public static final Random rand = new Random(System.currentTimeMillis());
+
+ /**
+ * Returns true if the number is a power of 2 (2,4,8,16...)
+ *
+ * A good implementation found on the Java boards. note: a number is a power
+ * of two if and only if it is the smallest number with that number of
+ * significant bits. Therefore, if you subtract 1, you know that the new
+ * number will have fewer bits, so ANDing the original number with anything
+ * less than it will give 0.
+ *
+ * @param number
+ * The number to test.
+ * @return True if it is a power of two.
+ */
+ public static boolean isPowerOfTwo(int number) {
+ return (number > 0) && (number & (number - 1)) == 0;
+ }
+
+ public static int nearestPowerOfTwo(int number) {
+ return (int) Math.pow(2, Math.ceil(Math.log(number) / Math.log(2)));
+ }
+
+ /**
+ * Linear interpolation from startValue to endValue by the given percent.
+ * Basically: ((1 - percent) * startValue) + (percent * endValue)
+ *
+ * @param scale
+ * scale value to use. if 1, use endValue, if 0, use startValue.
+ * @param startValue
+ * Begining value. 0% of f
+ * @param endValue
+ * ending value. 100% of f
+ * @return The interpolated value between startValue and endValue.
+ */
+ public static float interpolateLinear(float scale, float startValue, float endValue) {
+ if (startValue == endValue) {
+ return startValue;
+ }
+ if (scale <= 0f) {
+ return startValue;
+ }
+ if (scale >= 1f) {
+ return endValue;
+ }
+ return ((1f - scale) * startValue) + (scale * endValue);
+ }
+
+ /**
+ * Linear interpolation from startValue to endValue by the given percent.
+ * Basically: ((1 - percent) * startValue) + (percent * endValue)
+ *
+ * @param scale
+ * scale value to use. if 1, use endValue, if 0, use startValue.
+ * @param startValue
+ * Begining value. 0% of f
+ * @param endValue
+ * ending value. 100% of f
+ * @param store a vector3f to store the result
+ * @return The interpolated value between startValue and endValue.
+ */
+ public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.x = interpolateLinear(scale, startValue.x, endValue.x);
+ store.y = interpolateLinear(scale, startValue.y, endValue.y);
+ store.z = interpolateLinear(scale, startValue.z, endValue.z);
+ return store;
+ }
+
+ /**
+ * Linear interpolation from startValue to endValue by the given percent.
+ * Basically: ((1 - percent) * startValue) + (percent * endValue)
+ *
+ * @param scale
+ * scale value to use. if 1, use endValue, if 0, use startValue.
+ * @param startValue
+ * Begining value. 0% of f
+ * @param endValue
+ * ending value. 100% of f
+ * @return The interpolated value between startValue and endValue.
+ */
+ public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue) {
+ return interpolateLinear(scale, startValue, endValue, null);
+ }
+
+ /**
+ * Linear extrapolation from startValue to endValue by the given scale.
+ * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+ * if the scale is over 1 the value is linearly extrapolated.
+ * Note that the end value is the value for a scale of 1.
+ * @param scale the scale for extrapolation
+ * @param startValue the starting value (scale = 0)
+ * @param endValue the end value (scale = 1)
+ * @return an extrapolation for the given parameters
+ */
+ public static float extrapolateLinear(float scale, float startValue, float endValue) {
+// if (scale <= 0f) {
+// return startValue;
+// }
+ return ((1f - scale) * startValue) + (scale * endValue);
+ }
+
+ /**
+ * Linear extrapolation from startValue to endValue by the given scale.
+ * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+ * if the scale is over 1 the value is linearly extrapolated.
+ * Note that the end value is the value for a scale of 1.
+ * @param scale the scale for extrapolation
+ * @param startValue the starting value (scale = 0)
+ * @param endValue the end value (scale = 1)
+ * @param store an initialized vector to store the return value
+ * @return an extrapolation for the given parameters
+ */
+ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+// if (scale <= 1f) {
+// return interpolateLinear(scale, startValue, endValue, store);
+// }
+ store.x = extrapolateLinear(scale, startValue.x, endValue.x);
+ store.y = extrapolateLinear(scale, startValue.y, endValue.y);
+ store.z = extrapolateLinear(scale, startValue.z, endValue.z);
+ return store;
+ }
+
+ /**
+ * Linear extrapolation from startValue to endValue by the given scale.
+ * if scale is between 0 and 1 this method returns the same result as interpolateLinear
+ * if the scale is over 1 the value is linearly extrapolated.
+ * Note that the end value is the value for a scale of 1.
+ * @param scale the scale for extrapolation
+ * @param startValue the starting value (scale = 0)
+ * @param endValue the end value (scale = 1)
+ * @return an extrapolation for the given parameters
+ */
+ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue) {
+ return extrapolateLinear(scale, startValue, endValue, null);
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+ * here is the interpolation matrix
+ * m = [ 0.0 1.0 0.0 0.0 ]
+ * [-T 0.0 T 0.0 ]
+ * [ 2T T-3 3-2T -T ]
+ * [-T 2-T T-2 T ]
+ * where T is the curve tension
+ * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+ * @param u value from 0 to 1
+ * @param T The tension of the curve
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @return catmull-Rom interpolation
+ */
+ public static float interpolateCatmullRom(float u, float T, float p0, float p1, float p2, float p3) {
+ float c1, c2, c3, c4;
+ c1 = p1;
+ c2 = -1.0f * T * p0 + T * p2;
+ c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3;
+ c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3;
+
+ return (float) (((c4 * u + c3) * u + c2) * u + c1);
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+ * here is the interpolation matrix
+ * m = [ 0.0 1.0 0.0 0.0 ]
+ * [-T 0.0 T 0.0 ]
+ * [ 2T T-3 3-2T -T ]
+ * [-T 2-T T-2 T ]
+ * where T is the tension of the curve
+ * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+ * @param u value from 0 to 1
+ * @param T The tension of the curve
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @param store a Vector3f to store the result
+ * @return catmull-Rom interpolation
+ */
+ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.x = interpolateCatmullRom(u, T, p0.x, p1.x, p2.x, p3.x);
+ store.y = interpolateCatmullRom(u, T, p0.y, p1.y, p2.y, p3.y);
+ store.z = interpolateCatmullRom(u, T, p0.z, p1.z, p2.z, p3.z);
+ return store;
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation.
+ * here is the interpolation matrix
+ * m = [ 0.0 1.0 0.0 0.0 ]
+ * [-T 0.0 T 0.0 ]
+ * [ 2T T-3 3-2T -T ]
+ * [-T 2-T T-2 T ]
+ * where T is the tension of the curve
+ * the result is a value between p1 and p2, t=0 for p1, t=1 for p2
+ * @param u value from 0 to 1
+ * @param T The tension of the curve
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @return catmull-Rom interpolation
+ */
+ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+ return interpolateCatmullRom(u, T, p0, p1, p2, p3, null);
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Bezier equation.
+ * here is the interpolation matrix
+ * m = [ -1.0 3.0 -3.0 1.0 ]
+ * [ 3.0 -6.0 3.0 0.0 ]
+ * [ -3.0 3.0 0.0 0.0 ]
+ * [ 1.0 0.0 0.0 0.0 ]
+ * where T is the curve tension
+ * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+ * @param u value from 0 to 1
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @return Bezier interpolation
+ */
+ public static float interpolateBezier(float u, float p0, float p1, float p2, float p3) {
+ float oneMinusU = 1.0f - u;
+ float oneMinusU2 = oneMinusU * oneMinusU;
+ float u2 = u * u;
+ return p0 * oneMinusU2 * oneMinusU
+ + 3.0f * p1 * u * oneMinusU2
+ + 3.0f * p2 * u2 * oneMinusU
+ + p3 * u2 * u;
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Bezier equation.
+ * here is the interpolation matrix
+ * m = [ -1.0 3.0 -3.0 1.0 ]
+ * [ 3.0 -6.0 3.0 0.0 ]
+ * [ -3.0 3.0 0.0 0.0 ]
+ * [ 1.0 0.0 0.0 0.0 ]
+ * where T is the tension of the curve
+ * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+ * @param u value from 0 to 1
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @param store a Vector3f to store the result
+ * @return Bezier interpolation
+ */
+ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ store.x = interpolateBezier(u, p0.x, p1.x, p2.x, p3.x);
+ store.y = interpolateBezier(u, p0.y, p1.y, p2.y, p3.y);
+ store.z = interpolateBezier(u, p0.z, p1.z, p2.z, p3.z);
+ return store;
+ }
+
+ /**Interpolate a spline between at least 4 control points following the Bezier equation.
+ * here is the interpolation matrix
+ * m = [ -1.0 3.0 -3.0 1.0 ]
+ * [ 3.0 -6.0 3.0 0.0 ]
+ * [ -3.0 3.0 0.0 0.0 ]
+ * [ 1.0 0.0 0.0 0.0 ]
+ * where T is the tension of the curve
+ * the result is a value between p1 and p3, t=0 for p1, t=1 for p3
+ * @param u value from 0 to 1
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @return Bezier interpolation
+ */
+ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+ return interpolateBezier(u, p0, p1, p2, p3, null);
+ }
+
+ /**
+ * Compute the lenght on a catmull rom spline between control point 1 and 2
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @param startRange the starting range on the segment (use 0)
+ * @param endRange the end range on the segment (use 1)
+ * @param curveTension the curve tension
+ * @return the length of the segment
+ */
+ public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, float startRange, float endRange, float curveTension) {
+
+ float epsilon = 0.001f;
+ float middleValue = (startRange + endRange) * 0.5f;
+ Vector3f start = p1.clone();
+ if (startRange != 0) {
+ FastMath.interpolateCatmullRom(startRange, curveTension, p0, p1, p2, p3, start);
+ }
+ Vector3f end = p2.clone();
+ if (endRange != 1) {
+ FastMath.interpolateCatmullRom(endRange, curveTension, p0, p1, p2, p3, end);
+ }
+ Vector3f middle = FastMath.interpolateCatmullRom(middleValue, curveTension, p0, p1, p2, p3);
+ float l = end.subtract(start).length();
+ float l1 = middle.subtract(start).length();
+ float l2 = end.subtract(middle).length();
+ float len = l1 + l2;
+ if (l + epsilon < len) {
+ l1 = getCatmullRomP1toP2Length(p0, p1, p2, p3, startRange, middleValue, curveTension);
+ l2 = getCatmullRomP1toP2Length(p0, p1, p2, p3, middleValue, endRange, curveTension);
+ }
+ l = l1 + l2;
+ return l;
+ }
+
+ /**
+ * Compute the lenght on a bezier spline between control point 1 and 2
+ * @param p0 control point 0
+ * @param p1 control point 1
+ * @param p2 control point 2
+ * @param p3 control point 3
+ * @return the length of the segment
+ */
+ public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) {
+ float delta = 0.02f, t = 0.0f, result = 0.0f;
+ Vector3f v1 = p0.clone(), v2 = new Vector3f();
+ while (t <= 1.0f) {
+ FastMath.interpolateBezier(t, p0, p1, p2, p3, v2);
+ result += v1.subtractLocal(v2).length();
+ v1.set(v2);
+ t += delta;
+ }
+ return result;
+ }
+
+ /**
+ * Returns the arc cosine of an angle given in radians.<br>
+ * Special cases:
+ * <ul><li>If fValue is smaller than -1, then the result is PI.
+ * <li>If the argument is greater than 1, then the result is 0.</ul>
+ * @param fValue The angle, in radians.
+ * @return fValue's acos
+ * @see java.lang.Math#acos(double)
+ */
+ public static float acos(float fValue) {
+ if (-1.0f < fValue) {
+ if (fValue < 1.0f) {
+ return (float) Math.acos(fValue);
+ }
+
+ return 0.0f;
+ }
+
+ return PI;
+ }
+
+ /**
+ * Returns the arc sine of an angle given in radians.<br>
+ * Special cases:
+ * <ul><li>If fValue is smaller than -1, then the result is -HALF_PI.
+ * <li>If the argument is greater than 1, then the result is HALF_PI.</ul>
+ * @param fValue The angle, in radians.
+ * @return fValue's asin
+ * @see java.lang.Math#asin(double)
+ */
+ public static float asin(float fValue) {
+ if (-1.0f < fValue) {
+ if (fValue < 1.0f) {
+ return (float) Math.asin(fValue);
+ }
+
+ return HALF_PI;
+ }
+
+ return -HALF_PI;
+ }
+
+ /**
+ * Returns the arc tangent of an angle given in radians.<br>
+ * @param fValue The angle, in radians.
+ * @return fValue's atan
+ * @see java.lang.Math#atan(double)
+ */
+ public static float atan(float fValue) {
+ return (float) Math.atan(fValue);
+ }
+
+ /**
+ * A direct call to Math.atan2.
+ * @param fY
+ * @param fX
+ * @return Math.atan2(fY,fX)
+ * @see java.lang.Math#atan2(double, double)
+ */
+ public static float atan2(float fY, float fX) {
+ return (float) Math.atan2(fY, fX);
+ }
+
+ /**
+ * Rounds a fValue up. A call to Math.ceil
+ * @param fValue The value.
+ * @return The fValue rounded up
+ * @see java.lang.Math#ceil(double)
+ */
+ public static float ceil(float fValue) {
+ return (float) Math.ceil(fValue);
+ }
+
+ /**
+ * Fast Trig functions for x86. This forces the trig functiosn to stay
+ * within the safe area on the x86 processor (-45 degrees to +45 degrees)
+ * The results may be very slightly off from what the Math and StrictMath
+ * trig functions give due to rounding in the angle reduction but it will be
+ * very very close.
+ *
+ * note: code from wiki posting on java.net by jeffpk
+ */
+ public static float reduceSinAngle(float radians) {
+ radians %= TWO_PI; // put us in -2PI to +2PI space
+ if (Math.abs(radians) > PI) { // put us in -PI to +PI space
+ radians = radians - (TWO_PI);
+ }
+ if (Math.abs(radians) > HALF_PI) {// put us in -PI/2 to +PI/2 space
+ radians = PI - radians;
+ }
+
+ return radians;
+ }
+
+ /**
+ * Returns sine of a value.
+ *
+ * note: code from wiki posting on java.net by jeffpk
+ *
+ * @param fValue
+ * The value to sine, in radians.
+ * @return The sine of fValue.
+ * @see java.lang.Math#sin(double)
+ */
+ public static float sin2(float fValue) {
+ fValue = reduceSinAngle(fValue); // limits angle to between -PI/2 and +PI/2
+ if (Math.abs(fValue) <= Math.PI / 4) {
+ return (float) Math.sin(fValue);
+ }
+
+ return (float) Math.cos(Math.PI / 2 - fValue);
+ }
+
+ /**
+ * Returns cos of a value.
+ *
+ * @param fValue
+ * The value to cosine, in radians.
+ * @return The cosine of fValue.
+ * @see java.lang.Math#cos(double)
+ */
+ public static float cos2(float fValue) {
+ return sin2(fValue + HALF_PI);
+ }
+
+ public static float cos(float v) {
+ return (float) Math.cos(v);
+ }
+
+ public static float sin(float v) {
+ return (float) Math.sin(v);
+ }
+
+ /**
+ * Returns E^fValue
+ * @param fValue Value to raise to a power.
+ * @return The value E^fValue
+ * @see java.lang.Math#exp(double)
+ */
+ public static float exp(float fValue) {
+ return (float) Math.exp(fValue);
+ }
+
+ /**
+ * Returns Absolute value of a float.
+ * @param fValue The value to abs.
+ * @return The abs of the value.
+ * @see java.lang.Math#abs(float)
+ */
+ public static float abs(float fValue) {
+ if (fValue < 0) {
+ return -fValue;
+ }
+ return fValue;
+ }
+
+ /**
+ * Returns a number rounded down.
+ * @param fValue The value to round
+ * @return The given number rounded down
+ * @see java.lang.Math#floor(double)
+ */
+ public static float floor(float fValue) {
+ return (float) Math.floor(fValue);
+ }
+
+ /**
+ * Returns 1/sqrt(fValue)
+ * @param fValue The value to process.
+ * @return 1/sqrt(fValue)
+ * @see java.lang.Math#sqrt(double)
+ */
+ public static float invSqrt(float fValue) {
+ return (float) (1.0f / Math.sqrt(fValue));
+ }
+
+ public static float fastInvSqrt(float x) {
+ float xhalf = 0.5f * x;
+ int i = Float.floatToIntBits(x); // get bits for floating value
+ i = 0x5f375a86 - (i >> 1); // gives initial guess y0
+ x = Float.intBitsToFloat(i); // convert bits back to float
+ x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy
+ return x;
+ }
+
+ /**
+ * Returns the log base E of a value.
+ * @param fValue The value to log.
+ * @return The log of fValue base E
+ * @see java.lang.Math#log(double)
+ */
+ public static float log(float fValue) {
+ return (float) Math.log(fValue);
+ }
+
+ /**
+ * Returns the logarithm of value with given base, calculated as log(value)/log(base),
+ * so that pow(base, return)==value (contributed by vear)
+ * @param value The value to log.
+ * @param base Base of logarithm.
+ * @return The logarithm of value with given base
+ */
+ public static float log(float value, float base) {
+ return (float) (Math.log(value) / Math.log(base));
+ }
+
+ /**
+ * Returns a number raised to an exponent power. fBase^fExponent
+ * @param fBase The base value (IE 2)
+ * @param fExponent The exponent value (IE 3)
+ * @return base raised to exponent (IE 8)
+ * @see java.lang.Math#pow(double, double)
+ */
+ public static float pow(float fBase, float fExponent) {
+ return (float) Math.pow(fBase, fExponent);
+ }
+
+ /**
+ * Returns the value squared. fValue ^ 2
+ * @param fValue The vaule to square.
+ * @return The square of the given value.
+ */
+ public static float sqr(float fValue) {
+ return fValue * fValue;
+ }
+
+ /**
+ * Returns the square root of a given value.
+ * @param fValue The value to sqrt.
+ * @return The square root of the given value.
+ * @see java.lang.Math#sqrt(double)
+ */
+ public static float sqrt(float fValue) {
+ return (float) Math.sqrt(fValue);
+ }
+
+ /**
+ * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an approximate value
+ * is returned. Otherwise, a direct value is used.
+ * @param fValue The value to tangent, in radians.
+ * @return The tangent of fValue.
+ * @see java.lang.Math#tan(double)
+ */
+ public static float tan(float fValue) {
+ return (float) Math.tan(fValue);
+ }
+
+ /**
+ * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
+ * @param iValue The integer to examine.
+ * @return The integer's sign.
+ */
+ public static int sign(int iValue) {
+ if (iValue > 0) {
+ return 1;
+ }
+ if (iValue < 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /**
+ * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise
+ * @param fValue The float to examine.
+ * @return The float's sign.
+ */
+ public static float sign(float fValue) {
+ return Math.signum(fValue);
+ }
+
+ /**
+ * Given 3 points in a 2d plane, this function computes if the points going from A-B-C
+ * are moving counter clock wise.
+ * @param p0 Point 0.
+ * @param p1 Point 1.
+ * @param p2 Point 2.
+ * @return 1 If they are CCW, -1 if they are not CCW, 0 if p2 is between p0 and p1.
+ */
+ public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) {
+ float dx1, dx2, dy1, dy2;
+ dx1 = p1.x - p0.x;
+ dy1 = p1.y - p0.y;
+ dx2 = p2.x - p0.x;
+ dy2 = p2.y - p0.y;
+ if (dx1 * dy2 > dy1 * dx2) {
+ return 1;
+ }
+ if (dx1 * dy2 < dy1 * dx2) {
+ return -1;
+ }
+ if ((dx1 * dx2 < 0) || (dy1 * dy2 < 0)) {
+ return -1;
+ }
+ if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * Test if a point is inside a triangle. 1 if the point is on the ccw side,
+ * -1 if the point is on the cw side, and 0 if it is on neither.
+ * @param t0 First point of the triangle.
+ * @param t1 Second point of the triangle.
+ * @param t2 Third point of the triangle.
+ * @param p The point to test.
+ * @return Value 1 or -1 if inside triangle, 0 otherwise.
+ */
+ public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vector2f p) {
+ int val1 = counterClockwise(t0, t1, p);
+ if (val1 == 0) {
+ return 1;
+ }
+ int val2 = counterClockwise(t1, t2, p);
+ if (val2 == 0) {
+ return 1;
+ }
+ if (val2 != val1) {
+ return 0;
+ }
+ int val3 = counterClockwise(t2, t0, p);
+ if (val3 == 0) {
+ return 1;
+ }
+ if (val3 != val1) {
+ return 0;
+ }
+ return val3;
+ }
+
+ /**
+ * A method that computes normal for a triangle defined by three vertices.
+ * @param v1 first vertex
+ * @param v2 second vertex
+ * @param v3 third vertex
+ * @return a normal for the face
+ */
+ public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) {
+ Vector3f a1 = v1.subtract(v2);
+ Vector3f a2 = v3.subtract(v2);
+ return a2.crossLocal(a1).normalizeLocal();
+ }
+
+ /**
+ * Returns the determinant of a 4x4 matrix.
+ */
+ public static float determinant(double m00, double m01, double m02,
+ double m03, double m10, double m11, double m12, double m13,
+ double m20, double m21, double m22, double m23, double m30,
+ double m31, double m32, double m33) {
+
+ double det01 = m20 * m31 - m21 * m30;
+ double det02 = m20 * m32 - m22 * m30;
+ double det03 = m20 * m33 - m23 * m30;
+ double det12 = m21 * m32 - m22 * m31;
+ double det13 = m21 * m33 - m23 * m31;
+ double det23 = m22 * m33 - m23 * m32;
+ return (float) (m00 * (m11 * det23 - m12 * det13 + m13 * det12) - m01
+ * (m10 * det23 - m12 * det03 + m13 * det02) + m02
+ * (m10 * det13 - m11 * det03 + m13 * det01) - m03
+ * (m10 * det12 - m11 * det02 + m12 * det01));
+ }
+
+ /**
+ * Returns a random float between 0 and 1.
+ *
+ * @return A random float between <tt>0.0f</tt> (inclusive) to
+ * <tt>1.0f</tt> (exclusive).
+ */
+ public static float nextRandomFloat() {
+ return rand.nextFloat();
+ }
+
+ /**
+ * Returns a random float between min and max.
+ *
+ * @return A random int between <tt>min</tt> (inclusive) to
+ * <tt>max</tt> (inclusive).
+ */
+ public static int nextRandomInt(int min, int max) {
+ return (int) (nextRandomFloat() * (max - min + 1)) + min;
+ }
+
+ public static int nextRandomInt() {
+ return rand.nextInt();
+ }
+
+ /**
+ * Converts a point from Spherical coordinates to Cartesian (using positive
+ * Y as up) and stores the results in the store var.
+ */
+ public static Vector3f sphericalToCartesian(Vector3f sphereCoords,
+ Vector3f store) {
+ store.y = sphereCoords.x * FastMath.sin(sphereCoords.z);
+ float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+ store.x = a * FastMath.cos(sphereCoords.y);
+ store.z = a * FastMath.sin(sphereCoords.y);
+
+ return store;
+ }
+
+ /**
+ * Converts a point from Cartesian coordinates (using positive Y as up) to
+ * Spherical and stores the results in the store var. (Radius, Azimuth,
+ * Polar)
+ */
+ public static Vector3f cartesianToSpherical(Vector3f cartCoords,
+ Vector3f store) {
+ float x = cartCoords.x;
+ if (x == 0) {
+ x = FastMath.FLT_EPSILON;
+ }
+ store.x = FastMath.sqrt((x * x)
+ + (cartCoords.y * cartCoords.y)
+ + (cartCoords.z * cartCoords.z));
+ store.y = FastMath.atan(cartCoords.z / x);
+ if (x < 0) {
+ store.y += FastMath.PI;
+ }
+ store.z = FastMath.asin(cartCoords.y / store.x);
+ return store;
+ }
+
+ /**
+ * Converts a point from Spherical coordinates to Cartesian (using positive
+ * Z as up) and stores the results in the store var.
+ */
+ public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords,
+ Vector3f store) {
+ store.z = sphereCoords.x * FastMath.sin(sphereCoords.z);
+ float a = sphereCoords.x * FastMath.cos(sphereCoords.z);
+ store.x = a * FastMath.cos(sphereCoords.y);
+ store.y = a * FastMath.sin(sphereCoords.y);
+
+ return store;
+ }
+
+ /**
+ * Converts a point from Cartesian coordinates (using positive Z as up) to
+ * Spherical and stores the results in the store var. (Radius, Azimuth,
+ * Polar)
+ */
+ public static Vector3f cartesianZToSpherical(Vector3f cartCoords,
+ Vector3f store) {
+ float x = cartCoords.x;
+ if (x == 0) {
+ x = FastMath.FLT_EPSILON;
+ }
+ store.x = FastMath.sqrt((x * x)
+ + (cartCoords.y * cartCoords.y)
+ + (cartCoords.z * cartCoords.z));
+ store.z = FastMath.atan(cartCoords.z / x);
+ if (x < 0) {
+ store.z += FastMath.PI;
+ }
+ store.y = FastMath.asin(cartCoords.y / store.x);
+ return store;
+ }
+
+ /**
+ * Takes an value and expresses it in terms of min to max.
+ *
+ * @param val -
+ * the angle to normalize (in radians)
+ * @return the normalized angle (also in radians)
+ */
+ public static float normalize(float val, float min, float max) {
+ if (Float.isInfinite(val) || Float.isNaN(val)) {
+ return 0f;
+ }
+ float range = max - min;
+ while (val > max) {
+ val -= range;
+ }
+ while (val < min) {
+ val += range;
+ }
+ return val;
+ }
+
+ /**
+ * @param x
+ * the value whose sign is to be adjusted.
+ * @param y
+ * the value whose sign is to be used.
+ * @return x with its sign changed to match the sign of y.
+ */
+ public static float copysign(float x, float y) {
+ if (y >= 0 && x <= -0) {
+ return -x;
+ } else if (y < 0 && x >= 0) {
+ return -x;
+ } else {
+ return x;
+ }
+ }
+
+ /**
+ * Take a float input and clamp it between min and max.
+ *
+ * @param input
+ * @param min
+ * @param max
+ * @return clamped input
+ */
+ public static float clamp(float input, float min, float max) {
+ return (input < min) ? min : (input > max) ? max : input;
+ }
+
+ /**
+ * Clamps the given float to be between 0 and 1.
+ *
+ * @param input
+ * @return input clamped between 0 and 1.
+ */
+ public static float saturate(float input) {
+ return clamp(input, 0f, 1f);
+ }
+
+ /**
+ * Converts a single precision (32 bit) floating point value
+ * into half precision (16 bit).
+ *
+ * <p>Source: <a href="http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf">
+ * http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf</a><br><strong>broken link</strong>
+ *
+ * @param half The half floating point value as a short.
+ * @return floating point value of the half.
+ */
+ public static float convertHalfToFloat(short half) {
+ switch ((int) half) {
+ case 0x0000:
+ return 0f;
+ case 0x8000:
+ return -0f;
+ case 0x7c00:
+ return Float.POSITIVE_INFINITY;
+ case 0xfc00:
+ return Float.NEGATIVE_INFINITY;
+ // TODO: Support for NaN?
+ default:
+ return Float.intBitsToFloat(((half & 0x8000) << 16)
+ | (((half & 0x7c00) + 0x1C000) << 13)
+ | ((half & 0x03FF) << 13));
+ }
+ }
+
+ public static short convertFloatToHalf(float flt) {
+ if (Float.isNaN(flt)) {
+ throw new UnsupportedOperationException("NaN to half conversion not supported!");
+ } else if (flt == Float.POSITIVE_INFINITY) {
+ return (short) 0x7c00;
+ } else if (flt == Float.NEGATIVE_INFINITY) {
+ return (short) 0xfc00;
+ } else if (flt == 0f) {
+ return (short) 0x0000;
+ } else if (flt == -0f) {
+ return (short) 0x8000;
+ } else if (flt > 65504f) {
+ // max value supported by half float
+ return 0x7bff;
+ } else if (flt < -65504f) {
+ return (short) (0x7bff | 0x8000);
+ } else if (flt > 0f && flt < 5.96046E-8f) {
+ return 0x0001;
+ } else if (flt < 0f && flt > -5.96046E-8f) {
+ return (short) 0x8001;
+ }
+
+ int f = Float.floatToIntBits(flt);
+ return (short) (((f >> 16) & 0x8000)
+ | ((((f & 0x7f800000) - 0x38000000) >> 13) & 0x7c00)
+ | ((f >> 13) & 0x03ff));
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Line.java b/engine/src/core/com/jme3/math/Line.java
new file mode 100644
index 0000000..a0fa79b
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Line.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * <code>Line</code> defines a line. Where a line is defined as infinite along
+ * two points. The two points of the line are defined as the origin and direction.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class Line implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private Vector3f origin;
+ private Vector3f direction;
+
+ /**
+ * Constructor instantiates a new <code>Line</code> object. The origin and
+ * direction are set to defaults (0,0,0).
+ *
+ */
+ public Line() {
+ origin = new Vector3f();
+ direction = new Vector3f();
+ }
+
+ /**
+ * Constructor instantiates a new <code>Line</code> object. The origin
+ * and direction are set via the parameters.
+ * @param origin the origin of the line.
+ * @param direction the direction of the line.
+ */
+ public Line(Vector3f origin, Vector3f direction) {
+ this.origin = origin;
+ this.direction = direction;
+ }
+
+ /**
+ *
+ * <code>getOrigin</code> returns the origin of the line.
+ * @return the origin of the line.
+ */
+ public Vector3f getOrigin() {
+ return origin;
+ }
+
+ /**
+ *
+ * <code>setOrigin</code> sets the origin of the line.
+ * @param origin the origin of the line.
+ */
+ public void setOrigin(Vector3f origin) {
+ this.origin = origin;
+ }
+
+ /**
+ *
+ * <code>getDirection</code> returns the direction of the line.
+ * @return the direction of the line.
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ *
+ * <code>setDirection</code> sets the direction of the line.
+ * @param direction the direction of the line.
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction = direction;
+ }
+
+ public float distanceSquared(Vector3f point) {
+ TempVars vars = TempVars.get();
+
+ Vector3f compVec1 = vars.vect1;
+ Vector3f compVec2 = vars.vect2;
+
+ point.subtract(origin, compVec1);
+ float lineParameter = direction.dot(compVec1);
+ origin.add(direction.mult(lineParameter, compVec2), compVec2);
+ compVec2.subtract(point, compVec1);
+ float len = compVec1.lengthSquared();
+ vars.release();
+ return len;
+ }
+
+ public float distance(Vector3f point) {
+ return FastMath.sqrt(distanceSquared(point));
+ }
+
+ public void orthogonalLineFit(FloatBuffer points) {
+ if (points == null) {
+ return;
+ }
+
+ TempVars vars = TempVars.get();
+
+ Vector3f compVec1 = vars.vect1;
+ Vector3f compVec2 = vars.vect2;
+ Matrix3f compMat1 = vars.tempMat3;
+ Eigen3f compEigen1 = vars.eigen;
+
+ points.rewind();
+
+ // compute average of points
+ int length = points.remaining() / 3;
+
+ BufferUtils.populateFromBuffer(origin, points, 0);
+ for (int i = 1; i < length; i++) {
+ BufferUtils.populateFromBuffer(compVec1, points, i);
+ origin.addLocal(compVec1);
+ }
+
+ origin.multLocal(1f / (float) length);
+
+ // compute sums of products
+ float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f;
+ float sumYY = 0.0f, sumYZ = 0.0f, sumZZ = 0.0f;
+
+ points.rewind();
+ for (int i = 0; i < length; i++) {
+ BufferUtils.populateFromBuffer(compVec1, points, i);
+ compVec1.subtract(origin, compVec2);
+ sumXX += compVec2.x * compVec2.x;
+ sumXY += compVec2.x * compVec2.y;
+ sumXZ += compVec2.x * compVec2.z;
+ sumYY += compVec2.y * compVec2.y;
+ sumYZ += compVec2.y * compVec2.z;
+ sumZZ += compVec2.z * compVec2.z;
+ }
+
+ //find the smallest eigen vector for the direction vector
+ compMat1.m00 = sumYY + sumZZ;
+ compMat1.m01 = -sumXY;
+ compMat1.m02 = -sumXZ;
+ compMat1.m10 = -sumXY;
+ compMat1.m11 = sumXX + sumZZ;
+ compMat1.m12 = -sumYZ;
+ compMat1.m20 = -sumXZ;
+ compMat1.m21 = -sumYZ;
+ compMat1.m22 = sumXX + sumYY;
+
+ compEigen1.calculateEigen(compMat1);
+ direction = compEigen1.getEigenVector(0);
+
+ vars.release();
+ }
+
+ /**
+ *
+ * <code>random</code> determines a random point along the line.
+ * @return a random point on the line.
+ */
+ public Vector3f random() {
+ return random(null);
+ }
+
+ /**
+ * <code>random</code> determines a random point along the line.
+ *
+ * @param result Vector to store result in
+ * @return a random point on the line.
+ */
+ public Vector3f random(Vector3f result) {
+ if (result == null) {
+ result = new Vector3f();
+ }
+ float rand = (float) Math.random();
+
+ result.x = (origin.x * (1 - rand)) + (direction.x * rand);
+ result.y = (origin.y * (1 - rand)) + (direction.y * rand);
+ result.z = (origin.z * (1 - rand)) + (direction.z * rand);
+
+ return result;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(origin, "origin", Vector3f.ZERO);
+ capsule.write(direction, "direction", Vector3f.ZERO);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());
+ direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public Line clone() {
+ try {
+ Line line = (Line) super.clone();
+ line.direction = direction.clone();
+ line.origin = origin.clone();
+ return line;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/LineSegment.java b/engine/src/core/com/jme3/math/LineSegment.java
new file mode 100644
index 0000000..c86619c
--- /dev/null
+++ b/engine/src/core/com/jme3/math/LineSegment.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <p>LineSegment represents a segment in the space. This is a portion of a Line
+ * that has a limited start and end points.</p>
+ * <p>A LineSegment is defined by an origin, a direction and an extent (or length).
+ * Direction should be a normalized vector. It is not internally normalized.</p>
+ * <p>This class provides methods to calculate distances between LineSegments, Rays and Vectors.
+ * It is also possible to retrieve both end points of the segment {@link LineSegment#getPositiveEnd(Vector3f)}
+ * and {@link LineSegment#getNegativeEnd(Vector3f)}. There are also methods to check whether
+ * a point is within the segment bounds.</p>
+ *
+ * @see Ray
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class LineSegment implements Cloneable, Savable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private Vector3f origin;
+ private Vector3f direction;
+ private float extent;
+
+ public LineSegment() {
+ origin = new Vector3f();
+ direction = new Vector3f();
+ }
+
+ public LineSegment(LineSegment ls) {
+ this.origin = new Vector3f(ls.getOrigin());
+ this.direction = new Vector3f(ls.getDirection());
+ this.extent = ls.getExtent();
+ }
+
+ /**
+ * <p>Creates a new LineSegment with the given origin, direction and extent.</p>
+ * <p>Note that the origin is not one of the ends of the LineSegment, but its center.</p>
+ */
+ public LineSegment(Vector3f origin, Vector3f direction, float extent) {
+ this.origin = origin;
+ this.direction = direction;
+ this.extent = extent;
+ }
+
+ /**
+ * <p>Creates a new LineSegment with a given origin and end. This constructor will calculate the
+ * center, the direction and the extent.</p>
+ */
+ public LineSegment(Vector3f start, Vector3f end) {
+ this.origin = new Vector3f(0.5f * (start.x + end.x), 0.5f * (start.y + end.y), 0.5f * (start.z + end.z));
+ this.direction = end.subtract(start);
+ this.extent = direction.length() * 0.5f;
+ direction.normalizeLocal();
+ }
+
+ public void set(LineSegment ls) {
+ this.origin = new Vector3f(ls.getOrigin());
+ this.direction = new Vector3f(ls.getDirection());
+ this.extent = ls.getExtent();
+ }
+
+ public float distance(Vector3f point) {
+ return FastMath.sqrt(distanceSquared(point));
+ }
+
+ public float distance(LineSegment ls) {
+ return FastMath.sqrt(distanceSquared(ls));
+ }
+
+ public float distance(Ray r) {
+ return FastMath.sqrt(distanceSquared(r));
+ }
+
+ public float distanceSquared(Vector3f point) {
+ TempVars vars = TempVars.get();
+ Vector3f compVec1 = vars.vect1;
+
+ point.subtract(origin, compVec1);
+ float segmentParameter = direction.dot(compVec1);
+
+ if (-extent < segmentParameter) {
+ if (segmentParameter < extent) {
+ origin.add(direction.mult(segmentParameter, compVec1),
+ compVec1);
+ } else {
+ origin.add(direction.mult(extent, compVec1), compVec1);
+ }
+ } else {
+ origin.subtract(direction.mult(extent, compVec1), compVec1);
+ }
+
+ compVec1.subtractLocal(point);
+ float len = compVec1.lengthSquared();
+ vars.release();
+ return len;
+ }
+
+ public float distanceSquared(LineSegment test) {
+ TempVars vars = TempVars.get();
+ Vector3f compVec1 = vars.vect1;
+
+ origin.subtract(test.getOrigin(), compVec1);
+ float negativeDirectionDot = -(direction.dot(test.getDirection()));
+ float diffThisDot = compVec1.dot(direction);
+ float diffTestDot = -(compVec1.dot(test.getDirection()));
+ float lengthOfDiff = compVec1.lengthSquared();
+ vars.release();
+ float determinant = FastMath.abs(1.0f - negativeDirectionDot
+ * negativeDirectionDot);
+ float s0, s1, squareDistance, extentDeterminant0, extentDeterminant1, tempS0, tempS1;
+
+ if (determinant >= FastMath.FLT_EPSILON) {
+ // segments are not parallel
+ s0 = negativeDirectionDot * diffTestDot - diffThisDot;
+ s1 = negativeDirectionDot * diffThisDot - diffTestDot;
+ extentDeterminant0 = extent * determinant;
+ extentDeterminant1 = test.getExtent() * determinant;
+
+ if (s0 >= -extentDeterminant0) {
+ if (s0 <= extentDeterminant0) {
+ if (s1 >= -extentDeterminant1) {
+ if (s1 <= extentDeterminant1) // region 0 (interior)
+ {
+ // minimum at two interior points of 3D lines
+ float inverseDeterminant = ((float) 1.0)
+ / determinant;
+ s0 *= inverseDeterminant;
+ s1 *= inverseDeterminant;
+ squareDistance = s0
+ * (s0 + negativeDirectionDot * s1 + (2.0f) * diffThisDot)
+ + s1
+ * (negativeDirectionDot * s0 + s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else // region 3 (side)
+ {
+ s1 = test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 < -extent) {
+ s0 = -extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0)
+ + s1 * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else if (tempS0 <= extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else {
+ s0 = extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0)
+ + s1 * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ }
+ }
+ } else // region 7 (side)
+ {
+ s1 = -test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 < -extent) {
+ s0 = -extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else if (tempS0 <= extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else {
+ s0 = extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ }
+ }
+ } else {
+ if (s1 >= -extentDeterminant1) {
+ if (s1 <= extentDeterminant1) // region 1 (side)
+ {
+ s0 = extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 < -test.getExtent()) {
+ s1 = -test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 <= test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ } else // region 2 (corner)
+ {
+ s1 = test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 < -extent) {
+ s0 = -extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0)
+ + s1 * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else if (tempS0 <= extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else {
+ s0 = extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 < -test.getExtent()) {
+ s1 = -test.getExtent();
+ squareDistance = s1
+ * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 <= test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = test.getExtent();
+ squareDistance = s1
+ * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ }
+ }
+ } else // region 8 (corner)
+ {
+ s1 = -test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 < -extent) {
+ s0 = -extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else if (tempS0 <= extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else {
+ s0 = extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 > test.getExtent()) {
+ s1 = test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 >= -test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = -test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ }
+ }
+ }
+ } else {
+ if (s1 >= -extentDeterminant1) {
+ if (s1 <= extentDeterminant1) // region 5 (side)
+ {
+ s0 = -extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 < -test.getExtent()) {
+ s1 = -test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 <= test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ } else // region 4 (corner)
+ {
+ s1 = test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 > extent) {
+ s0 = extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else if (tempS0 >= -extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot)
+ + lengthOfDiff;
+ } else {
+ s0 = -extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 < -test.getExtent()) {
+ s1 = -test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 <= test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1)
+ + s0 * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ }
+ }
+ } else // region 6 (corner)
+ {
+ s1 = -test.getExtent();
+ tempS0 = -(negativeDirectionDot * s1 + diffThisDot);
+ if (tempS0 > extent) {
+ s0 = extent;
+ squareDistance = s0 * (s0 - (2.0f) * tempS0) + s1
+ * (s1 + (2.0f) * diffTestDot) + lengthOfDiff;
+ } else if (tempS0 >= -extent) {
+ s0 = tempS0;
+ squareDistance = -s0 * s0 + s1
+ * (s1 + (2.0f) * diffTestDot) + lengthOfDiff;
+ } else {
+ s0 = -extent;
+ tempS1 = -(negativeDirectionDot * s0 + diffTestDot);
+ if (tempS1 < -test.getExtent()) {
+ s1 = -test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else if (tempS1 <= test.getExtent()) {
+ s1 = tempS1;
+ squareDistance = -s1 * s1 + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ } else {
+ s1 = test.getExtent();
+ squareDistance = s1 * (s1 - (2.0f) * tempS1) + s0
+ * (s0 + (2.0f) * diffThisDot)
+ + lengthOfDiff;
+ }
+ }
+ }
+ }
+ } else {
+ // The segments are parallel. The average b0 term is designed to
+ // ensure symmetry of the function. That is, dist(seg0,seg1) and
+ // dist(seg1,seg0) should produce the same number.get
+ float extentSum = extent + test.getExtent();
+ float sign = (negativeDirectionDot > 0.0f ? -1.0f : 1.0f);
+ float averageB0 = (0.5f) * (diffThisDot - sign * diffTestDot);
+ float lambda = -averageB0;
+ if (lambda < -extentSum) {
+ lambda = -extentSum;
+ } else if (lambda > extentSum) {
+ lambda = extentSum;
+ }
+
+ squareDistance = lambda * (lambda + (2.0f) * averageB0)
+ + lengthOfDiff;
+ }
+
+ return FastMath.abs(squareDistance);
+ }
+
+ public float distanceSquared(Ray r) {
+ Vector3f kDiff = r.getOrigin().subtract(origin);
+ float fA01 = -r.getDirection().dot(direction);
+ float fB0 = kDiff.dot(r.getDirection());
+ float fB1 = -kDiff.dot(direction);
+ float fC = kDiff.lengthSquared();
+ float fDet = FastMath.abs(1.0f - fA01 * fA01);
+ float fS0, fS1, fSqrDist, fExtDet;
+
+ if (fDet >= FastMath.FLT_EPSILON) {
+ // The ray and segment are not parallel.
+ fS0 = fA01 * fB1 - fB0;
+ fS1 = fA01 * fB0 - fB1;
+ fExtDet = extent * fDet;
+
+ if (fS0 >= (float) 0.0) {
+ if (fS1 >= -fExtDet) {
+ if (fS1 <= fExtDet) // region 0
+ {
+ // minimum at interior points of ray and segment
+ float fInvDet = ((float) 1.0) / fDet;
+ fS0 *= fInvDet;
+ fS1 *= fInvDet;
+ fSqrDist = fS0
+ * (fS0 + fA01 * fS1 + ((float) 2.0) * fB0)
+ + fS1
+ * (fA01 * fS0 + fS1 + ((float) 2.0) * fB1) + fC;
+ } else // region 1
+ {
+ fS1 = extent;
+ fS0 = -(fA01 * fS1 + fB0);
+ if (fS0 > (float) 0.0) {
+ fSqrDist = -fS0 * fS0 + fS1
+ * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else {
+ fS0 = (float) 0.0;
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ }
+ }
+ } else // region 5
+ {
+ fS1 = -extent;
+ fS0 = -(fA01 * fS1 + fB0);
+ if (fS0 > (float) 0.0) {
+ fSqrDist = -fS0 * fS0 + fS1
+ * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else {
+ fS0 = (float) 0.0;
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ }
+ }
+ } else {
+ if (fS1 <= -fExtDet) // region 4
+ {
+ fS0 = -(-fA01 * extent + fB0);
+ if (fS0 > (float) 0.0) {
+ fS1 = -extent;
+ fSqrDist = -fS0 * fS0 + fS1
+ * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else {
+ fS0 = (float) 0.0;
+ fS1 = -fB1;
+ if (fS1 < -extent) {
+ fS1 = -extent;
+ } else if (fS1 > extent) {
+ fS1 = extent;
+ }
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ }
+ } else if (fS1 <= fExtDet) // region 3
+ {
+ fS0 = (float) 0.0;
+ fS1 = -fB1;
+ if (fS1 < -extent) {
+ fS1 = -extent;
+ } else if (fS1 > extent) {
+ fS1 = extent;
+ }
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else // region 2
+ {
+ fS0 = -(fA01 * extent + fB0);
+ if (fS0 > (float) 0.0) {
+ fS1 = extent;
+ fSqrDist = -fS0 * fS0 + fS1
+ * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else {
+ fS0 = (float) 0.0;
+ fS1 = -fB1;
+ if (fS1 < -extent) {
+ fS1 = -extent;
+ } else if (fS1 > extent) {
+ fS1 = extent;
+ }
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ }
+ }
+ }
+ } else {
+ // ray and segment are parallel
+ if (fA01 > (float) 0.0) {
+ // opposite direction vectors
+ fS1 = -extent;
+ } else {
+ // same direction vectors
+ fS1 = extent;
+ }
+
+ fS0 = -(fA01 * fS1 + fB0);
+ if (fS0 > (float) 0.0) {
+ fSqrDist = -fS0 * fS0 + fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ } else {
+ fS0 = (float) 0.0;
+ fSqrDist = fS1 * (fS1 + ((float) 2.0) * fB1) + fC;
+ }
+ }
+ return FastMath.abs(fSqrDist);
+ }
+
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ public void setDirection(Vector3f direction) {
+ this.direction = direction;
+ }
+
+ public float getExtent() {
+ return extent;
+ }
+
+ public void setExtent(float extent) {
+ this.extent = extent;
+ }
+
+ public Vector3f getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(Vector3f origin) {
+ this.origin = origin;
+ }
+
+ // P+e*D
+ public Vector3f getPositiveEnd(Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ return origin.add((direction.mult(extent, store)), store);
+ }
+
+ // P-e*D
+ public Vector3f getNegativeEnd(Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ return origin.subtract((direction.mult(extent, store)), store);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(origin, "origin", Vector3f.ZERO);
+ capsule.write(direction, "direction", Vector3f.ZERO);
+ capsule.write(extent, "extent", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());
+ direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());
+ extent = capsule.readFloat("extent", 0);
+ }
+
+ @Override
+ public LineSegment clone() {
+ try {
+ LineSegment segment = (LineSegment) super.clone();
+ segment.direction = direction.clone();
+ segment.origin = origin.clone();
+ return segment;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * <p>Evaluates whether a given point is contained within the axis aligned bounding box
+ * that contains this LineSegment.</p><p>This function is float error aware.</p>
+ */
+ public boolean isPointInsideBounds(Vector3f point) {
+ return isPointInsideBounds(point, Float.MIN_VALUE);
+ }
+
+ /**
+ * <p>Evaluates whether a given point is contained within the axis aligned bounding box
+ * that contains this LineSegment.</p><p>This function accepts an error parameter, which
+ * is added to the extent of the bounding box.</p>
+ */
+ public boolean isPointInsideBounds(Vector3f point, float error) {
+
+ if (FastMath.abs(point.x - origin.x) > FastMath.abs(direction.x * extent) + error) {
+ return false;
+ }
+ if (FastMath.abs(point.y - origin.y) > FastMath.abs(direction.y * extent) + error) {
+ return false;
+ }
+ if (FastMath.abs(point.z - origin.z) > FastMath.abs(direction.z * extent) + error) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Matrix3f.java b/engine/src/core/com/jme3/math/Matrix3f.java
new file mode 100644
index 0000000..96bf1b3
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Matrix3f.java
@@ -0,0 +1,1387 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Logger;
+
+/**
+ * <code>Matrix3f</code> defines a 3x3 matrix. Matrix data is maintained
+ * internally and is accessible via the get and set methods. Convenience methods
+ * are used for matrix operations as well as generating a matrix from a given
+ * set of values.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Matrix3f implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Matrix3f.class.getName());
+ protected float m00, m01, m02;
+ protected float m10, m11, m12;
+ protected float m20, m21, m22;
+ public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0);
+ public static final Matrix3f IDENTITY = new Matrix3f();
+
+ /**
+ * Constructor instantiates a new <code>Matrix3f</code> object. The
+ * initial values for the matrix is that of the identity matrix.
+ *
+ */
+ public Matrix3f() {
+ loadIdentity();
+ }
+
+ /**
+ * constructs a matrix with the given values.
+ *
+ * @param m00
+ * 0x0 in the matrix.
+ * @param m01
+ * 0x1 in the matrix.
+ * @param m02
+ * 0x2 in the matrix.
+ * @param m10
+ * 1x0 in the matrix.
+ * @param m11
+ * 1x1 in the matrix.
+ * @param m12
+ * 1x2 in the matrix.
+ * @param m20
+ * 2x0 in the matrix.
+ * @param m21
+ * 2x1 in the matrix.
+ * @param m22
+ * 2x2 in the matrix.
+ */
+ public Matrix3f(float m00, float m01, float m02, float m10, float m11,
+ float m12, float m20, float m21, float m22) {
+
+ this.m00 = m00;
+ this.m01 = m01;
+ this.m02 = m02;
+ this.m10 = m10;
+ this.m11 = m11;
+ this.m12 = m12;
+ this.m20 = m20;
+ this.m21 = m21;
+ this.m22 = m22;
+ }
+
+ /**
+ * Copy constructor that creates a new <code>Matrix3f</code> object that
+ * is the same as the provided matrix.
+ *
+ * @param mat
+ * the matrix to copy.
+ */
+ public Matrix3f(Matrix3f mat) {
+ set(mat);
+ }
+
+ /**
+ * Takes the absolute value of all matrix fields locally.
+ */
+ public void absoluteLocal() {
+ m00 = FastMath.abs(m00);
+ m01 = FastMath.abs(m01);
+ m02 = FastMath.abs(m02);
+ m10 = FastMath.abs(m10);
+ m11 = FastMath.abs(m11);
+ m12 = FastMath.abs(m12);
+ m20 = FastMath.abs(m20);
+ m21 = FastMath.abs(m21);
+ m22 = FastMath.abs(m22);
+ }
+
+ /**
+ * <code>copy</code> transfers the contents of a given matrix to this
+ * matrix. If a null matrix is supplied, this matrix is set to the identity
+ * matrix.
+ *
+ * @param matrix
+ * the matrix to copy.
+ * @return this
+ */
+ public Matrix3f set(Matrix3f matrix) {
+ if (null == matrix) {
+ loadIdentity();
+ } else {
+ m00 = matrix.m00;
+ m01 = matrix.m01;
+ m02 = matrix.m02;
+ m10 = matrix.m10;
+ m11 = matrix.m11;
+ m12 = matrix.m12;
+ m20 = matrix.m20;
+ m21 = matrix.m21;
+ m22 = matrix.m22;
+ }
+ return this;
+ }
+
+ /**
+ * <code>get</code> retrieves a value from the matrix at the given
+ * position. If the position is invalid a <code>JmeException</code> is
+ * thrown.
+ *
+ * @param i
+ * the row index.
+ * @param j
+ * the colum index.
+ * @return the value at (i, j).
+ */
+ @SuppressWarnings("fallthrough")
+ public float get(int i, int j) {
+ switch (i) {
+ case 0:
+ switch (j) {
+ case 0:
+ return m00;
+ case 1:
+ return m01;
+ case 2:
+ return m02;
+ }
+ case 1:
+ switch (j) {
+ case 0:
+ return m10;
+ case 1:
+ return m11;
+ case 2:
+ return m12;
+ }
+ case 2:
+ switch (j) {
+ case 0:
+ return m20;
+ case 1:
+ return m21;
+ case 2:
+ return m22;
+ }
+ }
+
+ logger.warning("Invalid matrix index.");
+ throw new IllegalArgumentException("Invalid indices into matrix.");
+ }
+
+ /**
+ * <code>get(float[])</code> returns the matrix in row-major or column-major order.
+ *
+ * @param data
+ * The array to return the data into. This array can be 9 or 16 floats in size.
+ * Only the upper 3x3 are assigned to in the case of a 16 element array.
+ * @param rowMajor
+ * True for row major storage in the array (translation in elements 3, 7, 11 for a 4x4),
+ * false for column major (translation in elements 12, 13, 14 for a 4x4).
+ */
+ public void get(float[] data, boolean rowMajor) {
+ if (data.length == 9) {
+ if (rowMajor) {
+ data[0] = m00;
+ data[1] = m01;
+ data[2] = m02;
+ data[3] = m10;
+ data[4] = m11;
+ data[5] = m12;
+ data[6] = m20;
+ data[7] = m21;
+ data[8] = m22;
+ } else {
+ data[0] = m00;
+ data[1] = m10;
+ data[2] = m20;
+ data[3] = m01;
+ data[4] = m11;
+ data[5] = m21;
+ data[6] = m02;
+ data[7] = m12;
+ data[8] = m22;
+ }
+ } else if (data.length == 16) {
+ if (rowMajor) {
+ data[0] = m00;
+ data[1] = m01;
+ data[2] = m02;
+ data[4] = m10;
+ data[5] = m11;
+ data[6] = m12;
+ data[8] = m20;
+ data[9] = m21;
+ data[10] = m22;
+ } else {
+ data[0] = m00;
+ data[1] = m10;
+ data[2] = m20;
+ data[4] = m01;
+ data[5] = m11;
+ data[6] = m21;
+ data[8] = m02;
+ data[9] = m12;
+ data[10] = m22;
+ }
+ } else {
+ throw new IndexOutOfBoundsException("Array size must be 9 or 16 in Matrix3f.get().");
+ }
+ }
+
+ /**
+ * <code>getColumn</code> returns one of three columns specified by the
+ * parameter. This column is returned as a <code>Vector3f</code> object.
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 2.
+ * @return the column specified by the index.
+ */
+ public Vector3f getColumn(int i) {
+ return getColumn(i, null);
+ }
+
+ /**
+ * <code>getColumn</code> returns one of three columns specified by the
+ * parameter. This column is returned as a <code>Vector3f</code> object.
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 2.
+ * @param store
+ * the vector object to store the result in. if null, a new one
+ * is created.
+ * @return the column specified by the index.
+ */
+ public Vector3f getColumn(int i, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ switch (i) {
+ case 0:
+ store.x = m00;
+ store.y = m10;
+ store.z = m20;
+ break;
+ case 1:
+ store.x = m01;
+ store.y = m11;
+ store.z = m21;
+ break;
+ case 2:
+ store.x = m02;
+ store.y = m12;
+ store.z = m22;
+ break;
+ default:
+ logger.warning("Invalid column index.");
+ throw new IllegalArgumentException("Invalid column index. " + i);
+ }
+ return store;
+ }
+
+ /**
+ * <code>getColumn</code> returns one of three rows as specified by the
+ * parameter. This row is returned as a <code>Vector3f</code> object.
+ *
+ * @param i
+ * the row to retrieve. Must be between 0 and 2.
+ * @return the row specified by the index.
+ */
+ public Vector3f getRow(int i) {
+ return getRow(i, null);
+ }
+
+ /**
+ * <code>getRow</code> returns one of three rows as specified by the
+ * parameter. This row is returned as a <code>Vector3f</code> object.
+ *
+ * @param i
+ * the row to retrieve. Must be between 0 and 2.
+ * @param store
+ * the vector object to store the result in. if null, a new one
+ * is created.
+ * @return the row specified by the index.
+ */
+ public Vector3f getRow(int i, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ switch (i) {
+ case 0:
+ store.x = m00;
+ store.y = m01;
+ store.z = m02;
+ break;
+ case 1:
+ store.x = m10;
+ store.y = m11;
+ store.z = m12;
+ break;
+ case 2:
+ store.x = m20;
+ store.y = m21;
+ store.z = m22;
+ break;
+ default:
+ logger.warning("Invalid row index.");
+ throw new IllegalArgumentException("Invalid row index. " + i);
+ }
+ return store;
+ }
+
+ /**
+ * <code>toFloatBuffer</code> returns a FloatBuffer object that contains
+ * the matrix data.
+ *
+ * @return matrix data as a FloatBuffer.
+ */
+ public FloatBuffer toFloatBuffer() {
+ FloatBuffer fb = BufferUtils.createFloatBuffer(9);
+
+ fb.put(m00).put(m01).put(m02);
+ fb.put(m10).put(m11).put(m12);
+ fb.put(m20).put(m21).put(m22);
+ fb.rewind();
+ return fb;
+ }
+
+ /**
+ * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+ * data.
+ *
+ * @param fb
+ * the buffer to fill, starting at current position. Must have
+ * room for 9 more floats.
+ * @return matrix data as a FloatBuffer. (position is advanced by 9 and any
+ * limit set is not changed).
+ */
+ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+// if (columnMajor){
+// fb.put(m00).put(m10).put(m20);
+// fb.put(m01).put(m11).put(m21);
+// fb.put(m02).put(m12).put(m22);
+// }else{
+// fb.put(m00).put(m01).put(m02);
+// fb.put(m10).put(m11).put(m12);
+// fb.put(m20).put(m21).put(m22);
+// }
+
+ TempVars vars = TempVars.get();
+
+
+ fillFloatArray(vars.matrixWrite, columnMajor);
+ fb.put(vars.matrixWrite, 0, 9);
+
+ vars.release();
+
+ return fb;
+ }
+
+ public void fillFloatArray(float[] f, boolean columnMajor) {
+ if (columnMajor) {
+ f[ 0] = m00;
+ f[ 1] = m10;
+ f[ 2] = m20;
+ f[ 3] = m01;
+ f[ 4] = m11;
+ f[ 5] = m21;
+ f[ 6] = m02;
+ f[ 7] = m12;
+ f[ 8] = m22;
+ } else {
+ f[ 0] = m00;
+ f[ 1] = m01;
+ f[ 2] = m02;
+ f[ 3] = m10;
+ f[ 4] = m11;
+ f[ 5] = m12;
+ f[ 6] = m20;
+ f[ 7] = m21;
+ f[ 8] = m22;
+ }
+ }
+
+ /**
+ *
+ * <code>setColumn</code> sets a particular column of this matrix to that
+ * represented by the provided vector.
+ *
+ * @param i
+ * the column to set.
+ * @param column
+ * the data to set.
+ * @return this
+ */
+ public Matrix3f setColumn(int i, Vector3f column) {
+
+ if (column == null) {
+ logger.warning("Column is null. Ignoring.");
+ return this;
+ }
+ switch (i) {
+ case 0:
+ m00 = column.x;
+ m10 = column.y;
+ m20 = column.z;
+ break;
+ case 1:
+ m01 = column.x;
+ m11 = column.y;
+ m21 = column.z;
+ break;
+ case 2:
+ m02 = column.x;
+ m12 = column.y;
+ m22 = column.z;
+ break;
+ default:
+ logger.warning("Invalid column index.");
+ throw new IllegalArgumentException("Invalid column index. " + i);
+ }
+ return this;
+ }
+
+ /**
+ *
+ * <code>setRow</code> sets a particular row of this matrix to that
+ * represented by the provided vector.
+ *
+ * @param i
+ * the row to set.
+ * @param row
+ * the data to set.
+ * @return this
+ */
+ public Matrix3f setRow(int i, Vector3f row) {
+
+ if (row == null) {
+ logger.warning("Row is null. Ignoring.");
+ return this;
+ }
+ switch (i) {
+ case 0:
+ m00 = row.x;
+ m01 = row.y;
+ m02 = row.z;
+ break;
+ case 1:
+ m10 = row.x;
+ m11 = row.y;
+ m12 = row.z;
+ break;
+ case 2:
+ m20 = row.x;
+ m21 = row.y;
+ m22 = row.z;
+ break;
+ default:
+ logger.warning("Invalid row index.");
+ throw new IllegalArgumentException("Invalid row index. " + i);
+ }
+ return this;
+ }
+
+ /**
+ * <code>set</code> places a given value into the matrix at the given
+ * position. If the position is invalid a <code>JmeException</code> is
+ * thrown.
+ *
+ * @param i
+ * the row index.
+ * @param j
+ * the colum index.
+ * @param value
+ * the value for (i, j).
+ * @return this
+ */
+ @SuppressWarnings("fallthrough")
+ public Matrix3f set(int i, int j, float value) {
+ switch (i) {
+ case 0:
+ switch (j) {
+ case 0:
+ m00 = value;
+ return this;
+ case 1:
+ m01 = value;
+ return this;
+ case 2:
+ m02 = value;
+ return this;
+ }
+ case 1:
+ switch (j) {
+ case 0:
+ m10 = value;
+ return this;
+ case 1:
+ m11 = value;
+ return this;
+ case 2:
+ m12 = value;
+ return this;
+ }
+ case 2:
+ switch (j) {
+ case 0:
+ m20 = value;
+ return this;
+ case 1:
+ m21 = value;
+ return this;
+ case 2:
+ m22 = value;
+ return this;
+ }
+ }
+
+ logger.warning("Invalid matrix index.");
+ throw new IllegalArgumentException("Invalid indices into matrix.");
+ }
+
+ /**
+ *
+ * <code>set</code> sets the values of the matrix to those supplied by the
+ * 3x3 two dimenion array.
+ *
+ * @param matrix
+ * the new values of the matrix.
+ * @throws JmeException
+ * if the array is not of size 9.
+ * @return this
+ */
+ public Matrix3f set(float[][] matrix) {
+ if (matrix.length != 3 || matrix[0].length != 3) {
+ throw new IllegalArgumentException(
+ "Array must be of size 9.");
+ }
+
+ m00 = matrix[0][0];
+ m01 = matrix[0][1];
+ m02 = matrix[0][2];
+ m10 = matrix[1][0];
+ m11 = matrix[1][1];
+ m12 = matrix[1][2];
+ m20 = matrix[2][0];
+ m21 = matrix[2][1];
+ m22 = matrix[2][2];
+
+ return this;
+ }
+
+ /**
+ * Recreate Matrix using the provided axis.
+ *
+ * @param uAxis
+ * Vector3f
+ * @param vAxis
+ * Vector3f
+ * @param wAxis
+ * Vector3f
+ */
+ public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) {
+ m00 = uAxis.x;
+ m10 = uAxis.y;
+ m20 = uAxis.z;
+
+ m01 = vAxis.x;
+ m11 = vAxis.y;
+ m21 = vAxis.z;
+
+ m02 = wAxis.x;
+ m12 = wAxis.y;
+ m22 = wAxis.z;
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from an array of
+ * values assuming that the data is rowMajor order;
+ *
+ * @param matrix
+ * the matrix to set the value to.
+ * @return this
+ */
+ public Matrix3f set(float[] matrix) {
+ return set(matrix, true);
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from an array of
+ * values;
+ *
+ * @param matrix
+ * the matrix to set the value to.
+ * @param rowMajor
+ * whether the incoming data is in row or column major order.
+ * @return this
+ */
+ public Matrix3f set(float[] matrix, boolean rowMajor) {
+ if (matrix.length != 9) {
+ throw new IllegalArgumentException(
+ "Array must be of size 9.");
+ }
+
+ if (rowMajor) {
+ m00 = matrix[0];
+ m01 = matrix[1];
+ m02 = matrix[2];
+ m10 = matrix[3];
+ m11 = matrix[4];
+ m12 = matrix[5];
+ m20 = matrix[6];
+ m21 = matrix[7];
+ m22 = matrix[8];
+ } else {
+ m00 = matrix[0];
+ m01 = matrix[3];
+ m02 = matrix[6];
+ m10 = matrix[1];
+ m11 = matrix[4];
+ m12 = matrix[7];
+ m20 = matrix[2];
+ m21 = matrix[5];
+ m22 = matrix[8];
+ }
+ return this;
+ }
+
+ /**
+ *
+ * <code>set</code> defines the values of the matrix based on a supplied
+ * <code>Quaternion</code>. It should be noted that all previous values
+ * will be overridden.
+ *
+ * @param quaternion
+ * the quaternion to create a rotational matrix from.
+ * @return this
+ */
+ public Matrix3f set(Quaternion quaternion) {
+ return quaternion.toRotationMatrix(this);
+ }
+
+ /**
+ * <code>loadIdentity</code> sets this matrix to the identity matrix.
+ * Where all values are zero except those along the diagonal which are one.
+ *
+ */
+ public void loadIdentity() {
+ m01 = m02 = m10 = m12 = m20 = m21 = 0;
+ m00 = m11 = m22 = 1;
+ }
+
+ /**
+ * @return true if this matrix is identity
+ */
+ public boolean isIdentity() {
+ return (m00 == 1 && m01 == 0 && m02 == 0)
+ && (m10 == 0 && m11 == 1 && m12 == 0)
+ && (m20 == 0 && m21 == 0 && m22 == 1);
+ }
+
+ /**
+ * <code>fromAngleAxis</code> sets this matrix4f to the values specified
+ * by an angle and an axis of rotation. This method creates an object, so
+ * use fromAngleNormalAxis if your axis is already normalized.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation.
+ */
+ public void fromAngleAxis(float angle, Vector3f axis) {
+ Vector3f normAxis = axis.normalize();
+ fromAngleNormalAxis(angle, normAxis);
+ }
+
+ /**
+ * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
+ * specified by an angle and a normalized axis of rotation.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation (already normalized).
+ */
+ public void fromAngleNormalAxis(float angle, Vector3f axis) {
+ float fCos = FastMath.cos(angle);
+ float fSin = FastMath.sin(angle);
+ float fOneMinusCos = ((float) 1.0) - fCos;
+ float fX2 = axis.x * axis.x;
+ float fY2 = axis.y * axis.y;
+ float fZ2 = axis.z * axis.z;
+ float fXYM = axis.x * axis.y * fOneMinusCos;
+ float fXZM = axis.x * axis.z * fOneMinusCos;
+ float fYZM = axis.y * axis.z * fOneMinusCos;
+ float fXSin = axis.x * fSin;
+ float fYSin = axis.y * fSin;
+ float fZSin = axis.z * fSin;
+
+ m00 = fX2 * fOneMinusCos + fCos;
+ m01 = fXYM - fZSin;
+ m02 = fXZM + fYSin;
+ m10 = fXYM + fZSin;
+ m11 = fY2 * fOneMinusCos + fCos;
+ m12 = fYZM - fXSin;
+ m20 = fXZM - fYSin;
+ m21 = fYZM + fXSin;
+ m22 = fZ2 * fOneMinusCos + fCos;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix by a given matrix. The result
+ * matrix is returned as a new object. If the given matrix is null, a null
+ * matrix is returned.
+ *
+ * @param mat
+ * the matrix to multiply this matrix by.
+ * @return the result matrix.
+ */
+ public Matrix3f mult(Matrix3f mat) {
+ return mult(mat, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix by a given matrix. The result
+ * matrix is returned as a new object.
+ *
+ * @param mat
+ * the matrix to multiply this matrix by.
+ * @param product
+ * the matrix to store the result in. if null, a new matrix3f is
+ * created. It is safe for mat and product to be the same object.
+ * @return a matrix3f object containing the result of this operation
+ */
+ public Matrix3f mult(Matrix3f mat, Matrix3f product) {
+
+ float temp00, temp01, temp02;
+ float temp10, temp11, temp12;
+ float temp20, temp21, temp22;
+
+ if (product == null) {
+ product = new Matrix3f();
+ }
+ temp00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20;
+ temp01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21;
+ temp02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22;
+ temp10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20;
+ temp11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21;
+ temp12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22;
+ temp20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20;
+ temp21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21;
+ temp22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22;
+
+ product.m00 = temp00;
+ product.m01 = temp01;
+ product.m02 = temp02;
+ product.m10 = temp10;
+ product.m11 = temp11;
+ product.m12 = temp12;
+ product.m20 = temp20;
+ product.m21 = temp21;
+ product.m22 = temp22;
+
+ return product;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix by a given
+ * <code>Vector3f</code> object. The result vector is returned. If the
+ * given vector is null, null will be returned.
+ *
+ * @param vec
+ * the vector to multiply this matrix by.
+ * @return the result vector.
+ */
+ public Vector3f mult(Vector3f vec) {
+ return mult(vec, null);
+ }
+
+ /**
+ * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in
+ * product.
+ *
+ * @param vec
+ * The Vector3f to multiply.
+ * @param product
+ * The Vector3f to store the result, it is safe for this to be
+ * the same as vec.
+ * @return The given product vector.
+ */
+ public Vector3f mult(Vector3f vec, Vector3f product) {
+
+ if (null == product) {
+ product = new Vector3f();
+ }
+
+ float x = vec.x;
+ float y = vec.y;
+ float z = vec.z;
+
+ product.x = m00 * x + m01 * y + m02 * z;
+ product.y = m10 * x + m11 * y + m12 * z;
+ product.z = m20 * x + m21 * y + m22 * z;
+ return product;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this matrix internally by
+ * a given float scale factor.
+ *
+ * @param scale
+ * the value to scale by.
+ * @return this Matrix3f
+ */
+ public Matrix3f multLocal(float scale) {
+ m00 *= scale;
+ m01 *= scale;
+ m02 *= scale;
+ m10 *= scale;
+ m11 *= scale;
+ m12 *= scale;
+ m20 *= scale;
+ m21 *= scale;
+ m22 *= scale;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this matrix by a given
+ * <code>Vector3f</code> object. The result vector is stored inside the
+ * passed vector, then returned . If the given vector is null, null will be
+ * returned.
+ *
+ * @param vec
+ * the vector to multiply this matrix by.
+ * @return The passed vector after multiplication
+ */
+ public Vector3f multLocal(Vector3f vec) {
+ if (vec == null) {
+ return null;
+ }
+ float x = vec.x;
+ float y = vec.y;
+ vec.x = m00 * x + m01 * y + m02 * vec.z;
+ vec.y = m10 * x + m11 * y + m12 * vec.z;
+ vec.z = m20 * x + m21 * y + m22 * vec.z;
+ return vec;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix by a given matrix. The result
+ * matrix is saved in the current matrix. If the given matrix is null,
+ * nothing happens. The current matrix is returned. This is equivalent to
+ * this*=mat
+ *
+ * @param mat
+ * the matrix to multiply this matrix by.
+ * @return This matrix, after the multiplication
+ */
+ public Matrix3f multLocal(Matrix3f mat) {
+ return mult(mat, this);
+ }
+
+ /**
+ * Transposes this matrix in place. Returns this matrix for chaining
+ *
+ * @return This matrix after transpose
+ */
+ public Matrix3f transposeLocal() {
+// float[] tmp = new float[9];
+// get(tmp, false);
+// set(tmp, true);
+
+ float tmp = m01;
+ m01 = m10;
+ m10 = tmp;
+
+ tmp = m02;
+ m02 = m20;
+ m20 = tmp;
+
+ tmp = m12;
+ m12 = m21;
+ m21 = tmp;
+
+ return this;
+ }
+
+ /**
+ * Inverts this matrix as a new Matrix3f.
+ *
+ * @return The new inverse matrix
+ */
+ public Matrix3f invert() {
+ return invert(null);
+ }
+
+ /**
+ * Inverts this matrix and stores it in the given store.
+ *
+ * @return The store
+ */
+ public Matrix3f invert(Matrix3f store) {
+ if (store == null) {
+ store = new Matrix3f();
+ }
+
+ float det = determinant();
+ if (FastMath.abs(det) <= FastMath.FLT_EPSILON) {
+ return store.zero();
+ }
+
+ store.m00 = m11 * m22 - m12 * m21;
+ store.m01 = m02 * m21 - m01 * m22;
+ store.m02 = m01 * m12 - m02 * m11;
+ store.m10 = m12 * m20 - m10 * m22;
+ store.m11 = m00 * m22 - m02 * m20;
+ store.m12 = m02 * m10 - m00 * m12;
+ store.m20 = m10 * m21 - m11 * m20;
+ store.m21 = m01 * m20 - m00 * m21;
+ store.m22 = m00 * m11 - m01 * m10;
+
+ store.multLocal(1f / det);
+ return store;
+ }
+
+ /**
+ * Inverts this matrix locally.
+ *
+ * @return this
+ */
+ public Matrix3f invertLocal() {
+ float det = determinant();
+ if (FastMath.abs(det) <= 0f) {
+ return zero();
+ }
+
+ float f00 = m11 * m22 - m12 * m21;
+ float f01 = m02 * m21 - m01 * m22;
+ float f02 = m01 * m12 - m02 * m11;
+ float f10 = m12 * m20 - m10 * m22;
+ float f11 = m00 * m22 - m02 * m20;
+ float f12 = m02 * m10 - m00 * m12;
+ float f20 = m10 * m21 - m11 * m20;
+ float f21 = m01 * m20 - m00 * m21;
+ float f22 = m00 * m11 - m01 * m10;
+
+ m00 = f00;
+ m01 = f01;
+ m02 = f02;
+ m10 = f10;
+ m11 = f11;
+ m12 = f12;
+ m20 = f20;
+ m21 = f21;
+ m22 = f22;
+
+ multLocal(1f / det);
+ return this;
+ }
+
+ /**
+ * Returns a new matrix representing the adjoint of this matrix.
+ *
+ * @return The adjoint matrix
+ */
+ public Matrix3f adjoint() {
+ return adjoint(null);
+ }
+
+ /**
+ * Places the adjoint of this matrix in store (creates store if null.)
+ *
+ * @param store
+ * The matrix to store the result in. If null, a new matrix is created.
+ * @return store
+ */
+ public Matrix3f adjoint(Matrix3f store) {
+ if (store == null) {
+ store = new Matrix3f();
+ }
+
+ store.m00 = m11 * m22 - m12 * m21;
+ store.m01 = m02 * m21 - m01 * m22;
+ store.m02 = m01 * m12 - m02 * m11;
+ store.m10 = m12 * m20 - m10 * m22;
+ store.m11 = m00 * m22 - m02 * m20;
+ store.m12 = m02 * m10 - m00 * m12;
+ store.m20 = m10 * m21 - m11 * m20;
+ store.m21 = m01 * m20 - m00 * m21;
+ store.m22 = m00 * m11 - m01 * m10;
+
+ return store;
+ }
+
+ /**
+ * <code>determinant</code> generates the determinant of this matrix.
+ *
+ * @return the determinant
+ */
+ public float determinant() {
+ float fCo00 = m11 * m22 - m12 * m21;
+ float fCo10 = m12 * m20 - m10 * m22;
+ float fCo20 = m10 * m21 - m11 * m20;
+ float fDet = m00 * fCo00 + m01 * fCo10 + m02 * fCo20;
+ return fDet;
+ }
+
+ /**
+ * Sets all of the values in this matrix to zero.
+ *
+ * @return this matrix
+ */
+ public Matrix3f zero() {
+ m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f;
+ return this;
+ }
+
+ /**
+ * <code>transpose</code> <b>locally</b> transposes this Matrix.
+ * This is inconsistent with general value vs local semantics, but is
+ * preserved for backwards compatibility. Use transposeNew() to transpose
+ * to a new object (value).
+ *
+ * @return this object for chaining.
+ */
+ public Matrix3f transpose() {
+ return transposeLocal();
+ }
+
+ /**
+ * <code>transposeNew</code> returns a transposed version of this matrix.
+ *
+ * @return The new Matrix3f object.
+ */
+ public Matrix3f transposeNew() {
+ Matrix3f ret = new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22);
+ return ret;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this object.
+ * It is in a format of a 3x3 matrix. For example, an identity matrix would
+ * be represented by the following string. com.jme.math.Matrix3f <br>[<br>
+ * 1.0 0.0 0.0 <br>
+ * 0.0 1.0 0.0 <br>
+ * 0.0 0.0 1.0 <br>]<br>
+ *
+ * @return the string representation of this object.
+ */
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("Matrix3f\n[\n");
+ result.append(" ");
+ result.append(m00);
+ result.append(" ");
+ result.append(m01);
+ result.append(" ");
+ result.append(m02);
+ result.append(" \n");
+ result.append(" ");
+ result.append(m10);
+ result.append(" ");
+ result.append(m11);
+ result.append(" ");
+ result.append(m12);
+ result.append(" \n");
+ result.append(" ");
+ result.append(m20);
+ result.append(" ");
+ result.append(m21);
+ result.append(" ");
+ result.append(m22);
+ result.append(" \n]");
+ return result.toString();
+ }
+
+ /**
+ *
+ * <code>hashCode</code> returns the hash code value as an integer and is
+ * supported for the benefit of hashing based collection classes such as
+ * Hashtable, HashMap, HashSet etc.
+ *
+ * @return the hashcode for this instance of Matrix4f.
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hash = 37;
+ hash = 37 * hash + Float.floatToIntBits(m00);
+ hash = 37 * hash + Float.floatToIntBits(m01);
+ hash = 37 * hash + Float.floatToIntBits(m02);
+
+ hash = 37 * hash + Float.floatToIntBits(m10);
+ hash = 37 * hash + Float.floatToIntBits(m11);
+ hash = 37 * hash + Float.floatToIntBits(m12);
+
+ hash = 37 * hash + Float.floatToIntBits(m20);
+ hash = 37 * hash + Float.floatToIntBits(m21);
+ hash = 37 * hash + Float.floatToIntBits(m22);
+
+ return hash;
+ }
+
+ /**
+ * are these two matrices the same? they are is they both have the same mXX values.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Matrix3f) || o == null) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ Matrix3f comp = (Matrix3f) o;
+ if (Float.compare(m00, comp.m00) != 0) {
+ return false;
+ }
+ if (Float.compare(m01, comp.m01) != 0) {
+ return false;
+ }
+ if (Float.compare(m02, comp.m02) != 0) {
+ return false;
+ }
+
+ if (Float.compare(m10, comp.m10) != 0) {
+ return false;
+ }
+ if (Float.compare(m11, comp.m11) != 0) {
+ return false;
+ }
+ if (Float.compare(m12, comp.m12) != 0) {
+ return false;
+ }
+
+ if (Float.compare(m20, comp.m20) != 0) {
+ return false;
+ }
+ if (Float.compare(m21, comp.m21) != 0) {
+ return false;
+ }
+ if (Float.compare(m22, comp.m22) != 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule cap = e.getCapsule(this);
+ cap.write(m00, "m00", 1);
+ cap.write(m01, "m01", 0);
+ cap.write(m02, "m02", 0);
+ cap.write(m10, "m10", 0);
+ cap.write(m11, "m11", 1);
+ cap.write(m12, "m12", 0);
+ cap.write(m20, "m20", 0);
+ cap.write(m21, "m21", 0);
+ cap.write(m22, "m22", 1);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule cap = e.getCapsule(this);
+ m00 = cap.readFloat("m00", 1);
+ m01 = cap.readFloat("m01", 0);
+ m02 = cap.readFloat("m02", 0);
+ m10 = cap.readFloat("m10", 0);
+ m11 = cap.readFloat("m11", 1);
+ m12 = cap.readFloat("m12", 0);
+ m20 = cap.readFloat("m20", 0);
+ m21 = cap.readFloat("m21", 0);
+ m22 = cap.readFloat("m22", 1);
+ }
+
+ /**
+ * A function for creating a rotation matrix that rotates a vector called
+ * "start" into another vector called "end".
+ *
+ * @param start
+ * normalized non-zero starting vector
+ * @param end
+ * normalized non-zero ending vector
+ * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate \
+ * One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999"
+ */
+ public void fromStartEndVectors(Vector3f start, Vector3f end) {
+ Vector3f v = new Vector3f();
+ float e, h, f;
+
+ start.cross(end, v);
+ e = start.dot(end);
+ f = (e < 0) ? -e : e;
+
+ // if "from" and "to" vectors are nearly parallel
+ if (f > 1.0f - FastMath.ZERO_TOLERANCE) {
+ Vector3f u = new Vector3f();
+ Vector3f x = new Vector3f();
+ float c1, c2, c3; /* coefficients for later use */
+ int i, j;
+
+ x.x = (start.x > 0.0) ? start.x : -start.x;
+ x.y = (start.y > 0.0) ? start.y : -start.y;
+ x.z = (start.z > 0.0) ? start.z : -start.z;
+
+ if (x.x < x.y) {
+ if (x.x < x.z) {
+ x.x = 1.0f;
+ x.y = x.z = 0.0f;
+ } else {
+ x.z = 1.0f;
+ x.x = x.y = 0.0f;
+ }
+ } else {
+ if (x.y < x.z) {
+ x.y = 1.0f;
+ x.x = x.z = 0.0f;
+ } else {
+ x.z = 1.0f;
+ x.x = x.y = 0.0f;
+ }
+ }
+
+ u.x = x.x - start.x;
+ u.y = x.y - start.y;
+ u.z = x.z - start.z;
+ v.x = x.x - end.x;
+ v.y = x.y - end.y;
+ v.z = x.z - end.z;
+
+ c1 = 2.0f / u.dot(u);
+ c2 = 2.0f / v.dot(v);
+ c3 = c1 * c2 * u.dot(v);
+
+ for (i = 0; i < 3; i++) {
+ for (j = 0; j < 3; j++) {
+ float val = -c1 * u.get(i) * u.get(j) - c2 * v.get(i)
+ * v.get(j) + c3 * v.get(i) * u.get(j);
+ set(i, j, val);
+ }
+ float val = get(i, i);
+ set(i, i, val + 1.0f);
+ }
+ } else {
+ // the most common case, unless "start"="end", or "start"=-"end"
+ float hvx, hvz, hvxy, hvxz, hvyz;
+ h = 1.0f / (1.0f + e);
+ hvx = h * v.x;
+ hvz = h * v.z;
+ hvxy = hvx * v.y;
+ hvxz = hvx * v.z;
+ hvyz = hvz * v.y;
+ set(0, 0, e + hvx * v.x);
+ set(0, 1, hvxy - v.z);
+ set(0, 2, hvxz + v.y);
+
+ set(1, 0, hvxy + v.z);
+ set(1, 1, e + h * v.y * v.y);
+ set(1, 2, hvyz - v.x);
+
+ set(2, 0, hvxz - v.y);
+ set(2, 1, hvyz + v.x);
+ set(2, 2, e + hvz * v.z);
+ }
+ }
+
+ /**
+ * <code>scale</code> scales the operation performed by this matrix on a
+ * per-component basis.
+ *
+ * @param scale
+ * The scale applied to each of the X, Y and Z output values.
+ */
+ public void scale(Vector3f scale) {
+ m00 *= scale.x;
+ m10 *= scale.x;
+ m20 *= scale.x;
+ m01 *= scale.y;
+ m11 *= scale.y;
+ m21 *= scale.y;
+ m02 *= scale.z;
+ m12 *= scale.z;
+ m22 *= scale.z;
+ }
+
+ static boolean equalIdentity(Matrix3f mat) {
+ if (Math.abs(mat.m00 - 1) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m11 - 1) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m22 - 1) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m01) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m02) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m10) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m12) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m20) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m21) > 1e-4) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Matrix3f clone() {
+ try {
+ return (Matrix3f) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Matrix4f.java b/engine/src/core/com/jme3/math/Matrix4f.java
new file mode 100644
index 0000000..8521eab
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Matrix4f.java
@@ -0,0 +1,2305 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.util.logging.Logger;
+
+/**
+ * <code>Matrix4f</code> defines and maintains a 4x4 matrix in row major order.
+ * This matrix is intended for use in a translation and rotational capacity.
+ * It provides convenience methods for creating the matrix from a multitude
+ * of sources.
+ *
+ * Matrices are stored assuming column vectors on the right, with the translation
+ * in the rightmost column. Element numbering is row,column, so m03 is the zeroth
+ * row, third column, which is the "x" translation part. This means that the implicit
+ * storage order is column major. However, the get() and set() functions on float
+ * arrays default to row major order!
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Matrix4f implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Matrix4f.class.getName());
+ public float m00, m01, m02, m03;
+ public float m10, m11, m12, m13;
+ public float m20, m21, m22, m23;
+ public float m30, m31, m32, m33;
+ public static final Matrix4f ZERO = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+ public static final Matrix4f IDENTITY = new Matrix4f();
+
+ /**
+ * Constructor instantiates a new <code>Matrix</code> that is set to the
+ * identity matrix.
+ *
+ */
+ public Matrix4f() {
+ loadIdentity();
+ }
+
+ /**
+ * constructs a matrix with the given values.
+ */
+ public Matrix4f(float m00, float m01, float m02, float m03,
+ float m10, float m11, float m12, float m13,
+ float m20, float m21, float m22, float m23,
+ float m30, float m31, float m32, float m33) {
+
+ this.m00 = m00;
+ this.m01 = m01;
+ this.m02 = m02;
+ this.m03 = m03;
+ this.m10 = m10;
+ this.m11 = m11;
+ this.m12 = m12;
+ this.m13 = m13;
+ this.m20 = m20;
+ this.m21 = m21;
+ this.m22 = m22;
+ this.m23 = m23;
+ this.m30 = m30;
+ this.m31 = m31;
+ this.m32 = m32;
+ this.m33 = m33;
+ }
+
+ /**
+ * Create a new Matrix4f, given data in column-major format.
+ *
+ * @param array
+ * An array of 16 floats in column-major format (translation in elements 12, 13 and 14).
+ */
+ public Matrix4f(float[] array) {
+ set(array, false);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Matrix</code> that is set to the
+ * provided matrix. This constructor copies a given Matrix. If the provided
+ * matrix is null, the constructor sets the matrix to the identity.
+ *
+ * @param mat
+ * the matrix to copy.
+ */
+ public Matrix4f(Matrix4f mat) {
+ copy(mat);
+ }
+
+ /**
+ * <code>copy</code> transfers the contents of a given matrix to this
+ * matrix. If a null matrix is supplied, this matrix is set to the identity
+ * matrix.
+ *
+ * @param matrix
+ * the matrix to copy.
+ */
+ public void copy(Matrix4f matrix) {
+ if (null == matrix) {
+ loadIdentity();
+ } else {
+ m00 = matrix.m00;
+ m01 = matrix.m01;
+ m02 = matrix.m02;
+ m03 = matrix.m03;
+ m10 = matrix.m10;
+ m11 = matrix.m11;
+ m12 = matrix.m12;
+ m13 = matrix.m13;
+ m20 = matrix.m20;
+ m21 = matrix.m21;
+ m22 = matrix.m22;
+ m23 = matrix.m23;
+ m30 = matrix.m30;
+ m31 = matrix.m31;
+ m32 = matrix.m32;
+ m33 = matrix.m33;
+ }
+ }
+
+ public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector3f left) {
+ loadIdentity();
+
+ TempVars vars = TempVars.get();
+
+ Vector3f f = vars.vect1.set(direction);
+ Vector3f s = vars.vect2.set(f).crossLocal(up);
+ Vector3f u = vars.vect3.set(s).crossLocal(f);
+// s.normalizeLocal();
+// u.normalizeLocal();
+
+ m00 = s.x;
+ m01 = s.y;
+ m02 = s.z;
+
+ m10 = u.x;
+ m11 = u.y;
+ m12 = u.z;
+
+ m20 = -f.x;
+ m21 = -f.y;
+ m22 = -f.z;
+
+// m00 = -left.x;
+// m10 = -left.y;
+// m20 = -left.z;
+//
+// m01 = up.x;
+// m11 = up.y;
+// m21 = up.z;
+//
+// m02 = -direction.x;
+// m12 = -direction.y;
+// m22 = -direction.z;
+//
+
+ Matrix4f transMatrix = vars.tempMat4;
+ transMatrix.loadIdentity();
+ transMatrix.m03 = -location.x;
+ transMatrix.m13 = -location.y;
+ transMatrix.m23 = -location.z;
+ this.multLocal(transMatrix);
+
+ vars.release();
+
+// transMatrix.multLocal(this);
+
+// set(transMatrix);
+ }
+
+ /**
+ * <code>get</code> retrieves the values of this object into
+ * a float array in row-major order.
+ *
+ * @param matrix
+ * the matrix to set the values into.
+ */
+ public void get(float[] matrix) {
+ get(matrix, true);
+ }
+
+ /**
+ * <code>set</code> retrieves the values of this object into
+ * a float array.
+ *
+ * @param matrix
+ * the matrix to set the values into.
+ * @param rowMajor
+ * whether the outgoing data is in row or column major order.
+ */
+ public void get(float[] matrix, boolean rowMajor) {
+ if (matrix.length != 16) {
+ throw new IllegalArgumentException(
+ "Array must be of size 16.");
+ }
+
+ if (rowMajor) {
+ matrix[0] = m00;
+ matrix[1] = m01;
+ matrix[2] = m02;
+ matrix[3] = m03;
+ matrix[4] = m10;
+ matrix[5] = m11;
+ matrix[6] = m12;
+ matrix[7] = m13;
+ matrix[8] = m20;
+ matrix[9] = m21;
+ matrix[10] = m22;
+ matrix[11] = m23;
+ matrix[12] = m30;
+ matrix[13] = m31;
+ matrix[14] = m32;
+ matrix[15] = m33;
+ } else {
+ matrix[0] = m00;
+ matrix[4] = m01;
+ matrix[8] = m02;
+ matrix[12] = m03;
+ matrix[1] = m10;
+ matrix[5] = m11;
+ matrix[9] = m12;
+ matrix[13] = m13;
+ matrix[2] = m20;
+ matrix[6] = m21;
+ matrix[10] = m22;
+ matrix[14] = m23;
+ matrix[3] = m30;
+ matrix[7] = m31;
+ matrix[11] = m32;
+ matrix[15] = m33;
+ }
+ }
+
+ /**
+ * <code>get</code> retrieves a value from the matrix at the given
+ * position. If the position is invalid a <code>JmeException</code> is
+ * thrown.
+ *
+ * @param i
+ * the row index.
+ * @param j
+ * the colum index.
+ * @return the value at (i, j).
+ */
+ @SuppressWarnings("fallthrough")
+ public float get(int i, int j) {
+ switch (i) {
+ case 0:
+ switch (j) {
+ case 0:
+ return m00;
+ case 1:
+ return m01;
+ case 2:
+ return m02;
+ case 3:
+ return m03;
+ }
+ case 1:
+ switch (j) {
+ case 0:
+ return m10;
+ case 1:
+ return m11;
+ case 2:
+ return m12;
+ case 3:
+ return m13;
+ }
+ case 2:
+ switch (j) {
+ case 0:
+ return m20;
+ case 1:
+ return m21;
+ case 2:
+ return m22;
+ case 3:
+ return m23;
+ }
+ case 3:
+ switch (j) {
+ case 0:
+ return m30;
+ case 1:
+ return m31;
+ case 2:
+ return m32;
+ case 3:
+ return m33;
+ }
+ }
+
+ logger.warning("Invalid matrix index.");
+ throw new IllegalArgumentException("Invalid indices into matrix.");
+ }
+
+ /**
+ * <code>getColumn</code> returns one of three columns specified by the
+ * parameter. This column is returned as a float array of length 4.
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 3.
+ * @return the column specified by the index.
+ */
+ public float[] getColumn(int i) {
+ return getColumn(i, null);
+ }
+
+ /**
+ * <code>getColumn</code> returns one of three columns specified by the
+ * parameter. This column is returned as a float[4].
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 3.
+ * @param store
+ * the float array to store the result in. if null, a new one
+ * is created.
+ * @return the column specified by the index.
+ */
+ public float[] getColumn(int i, float[] store) {
+ if (store == null) {
+ store = new float[4];
+ }
+ switch (i) {
+ case 0:
+ store[0] = m00;
+ store[1] = m10;
+ store[2] = m20;
+ store[3] = m30;
+ break;
+ case 1:
+ store[0] = m01;
+ store[1] = m11;
+ store[2] = m21;
+ store[3] = m31;
+ break;
+ case 2:
+ store[0] = m02;
+ store[1] = m12;
+ store[2] = m22;
+ store[3] = m32;
+ break;
+ case 3:
+ store[0] = m03;
+ store[1] = m13;
+ store[2] = m23;
+ store[3] = m33;
+ break;
+ default:
+ logger.warning("Invalid column index.");
+ throw new IllegalArgumentException("Invalid column index. " + i);
+ }
+ return store;
+ }
+
+ /**
+ *
+ * <code>setColumn</code> sets a particular column of this matrix to that
+ * represented by the provided vector.
+ *
+ * @param i
+ * the column to set.
+ * @param column
+ * the data to set.
+ */
+ public void setColumn(int i, float[] column) {
+
+ if (column == null) {
+ logger.warning("Column is null. Ignoring.");
+ return;
+ }
+ switch (i) {
+ case 0:
+ m00 = column[0];
+ m10 = column[1];
+ m20 = column[2];
+ m30 = column[3];
+ break;
+ case 1:
+ m01 = column[0];
+ m11 = column[1];
+ m21 = column[2];
+ m31 = column[3];
+ break;
+ case 2:
+ m02 = column[0];
+ m12 = column[1];
+ m22 = column[2];
+ m32 = column[3];
+ break;
+ case 3:
+ m03 = column[0];
+ m13 = column[1];
+ m23 = column[2];
+ m33 = column[3];
+ break;
+ default:
+ logger.warning("Invalid column index.");
+ throw new IllegalArgumentException("Invalid column index. " + i);
+ }
+ }
+
+ /**
+ * <code>set</code> places a given value into the matrix at the given
+ * position. If the position is invalid a <code>JmeException</code> is
+ * thrown.
+ *
+ * @param i
+ * the row index.
+ * @param j
+ * the colum index.
+ * @param value
+ * the value for (i, j).
+ */
+ @SuppressWarnings("fallthrough")
+ public void set(int i, int j, float value) {
+ switch (i) {
+ case 0:
+ switch (j) {
+ case 0:
+ m00 = value;
+ return;
+ case 1:
+ m01 = value;
+ return;
+ case 2:
+ m02 = value;
+ return;
+ case 3:
+ m03 = value;
+ return;
+ }
+ case 1:
+ switch (j) {
+ case 0:
+ m10 = value;
+ return;
+ case 1:
+ m11 = value;
+ return;
+ case 2:
+ m12 = value;
+ return;
+ case 3:
+ m13 = value;
+ return;
+ }
+ case 2:
+ switch (j) {
+ case 0:
+ m20 = value;
+ return;
+ case 1:
+ m21 = value;
+ return;
+ case 2:
+ m22 = value;
+ return;
+ case 3:
+ m23 = value;
+ return;
+ }
+ case 3:
+ switch (j) {
+ case 0:
+ m30 = value;
+ return;
+ case 1:
+ m31 = value;
+ return;
+ case 2:
+ m32 = value;
+ return;
+ case 3:
+ m33 = value;
+ return;
+ }
+ }
+
+ logger.warning("Invalid matrix index.");
+ throw new IllegalArgumentException("Invalid indices into matrix.");
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from an array of
+ * values.
+ *
+ * @param matrix
+ * the matrix to set the value to.
+ * @throws JmeException
+ * if the array is not of size 16.
+ */
+ public void set(float[][] matrix) {
+ if (matrix.length != 4 || matrix[0].length != 4) {
+ throw new IllegalArgumentException(
+ "Array must be of size 16.");
+ }
+
+ m00 = matrix[0][0];
+ m01 = matrix[0][1];
+ m02 = matrix[0][2];
+ m03 = matrix[0][3];
+ m10 = matrix[1][0];
+ m11 = matrix[1][1];
+ m12 = matrix[1][2];
+ m13 = matrix[1][3];
+ m20 = matrix[2][0];
+ m21 = matrix[2][1];
+ m22 = matrix[2][2];
+ m23 = matrix[2][3];
+ m30 = matrix[3][0];
+ m31 = matrix[3][1];
+ m32 = matrix[3][2];
+ m33 = matrix[3][3];
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from another matrix.
+ *
+ * @param matrix
+ * the matrix to read the value from.
+ */
+ public Matrix4f set(Matrix4f matrix) {
+ m00 = matrix.m00;
+ m01 = matrix.m01;
+ m02 = matrix.m02;
+ m03 = matrix.m03;
+ m10 = matrix.m10;
+ m11 = matrix.m11;
+ m12 = matrix.m12;
+ m13 = matrix.m13;
+ m20 = matrix.m20;
+ m21 = matrix.m21;
+ m22 = matrix.m22;
+ m23 = matrix.m23;
+ m30 = matrix.m30;
+ m31 = matrix.m31;
+ m32 = matrix.m32;
+ m33 = matrix.m33;
+ return this;
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from an array of
+ * values assuming that the data is rowMajor order;
+ *
+ * @param matrix
+ * the matrix to set the value to.
+ */
+ public void set(float[] matrix) {
+ set(matrix, true);
+ }
+
+ /**
+ * <code>set</code> sets the values of this matrix from an array of
+ * values;
+ *
+ * @param matrix
+ * the matrix to set the value to.
+ * @param rowMajor
+ * whether the incoming data is in row or column major order.
+ */
+ public void set(float[] matrix, boolean rowMajor) {
+ if (matrix.length != 16) {
+ throw new IllegalArgumentException(
+ "Array must be of size 16.");
+ }
+
+ if (rowMajor) {
+ m00 = matrix[0];
+ m01 = matrix[1];
+ m02 = matrix[2];
+ m03 = matrix[3];
+ m10 = matrix[4];
+ m11 = matrix[5];
+ m12 = matrix[6];
+ m13 = matrix[7];
+ m20 = matrix[8];
+ m21 = matrix[9];
+ m22 = matrix[10];
+ m23 = matrix[11];
+ m30 = matrix[12];
+ m31 = matrix[13];
+ m32 = matrix[14];
+ m33 = matrix[15];
+ } else {
+ m00 = matrix[0];
+ m01 = matrix[4];
+ m02 = matrix[8];
+ m03 = matrix[12];
+ m10 = matrix[1];
+ m11 = matrix[5];
+ m12 = matrix[9];
+ m13 = matrix[13];
+ m20 = matrix[2];
+ m21 = matrix[6];
+ m22 = matrix[10];
+ m23 = matrix[14];
+ m30 = matrix[3];
+ m31 = matrix[7];
+ m32 = matrix[11];
+ m33 = matrix[15];
+ }
+ }
+
+ public Matrix4f transpose() {
+ float[] tmp = new float[16];
+ get(tmp, true);
+ Matrix4f mat = new Matrix4f(tmp);
+ return mat;
+ }
+
+ /**
+ * <code>transpose</code> locally transposes this Matrix.
+ *
+ * @return this object for chaining.
+ */
+ public Matrix4f transposeLocal() {
+ float tmp = m01;
+ m01 = m10;
+ m10 = tmp;
+
+ tmp = m02;
+ m02 = m20;
+ m20 = tmp;
+
+ tmp = m03;
+ m03 = m30;
+ m30 = tmp;
+
+ tmp = m12;
+ m12 = m21;
+ m21 = tmp;
+
+ tmp = m13;
+ m13 = m31;
+ m31 = tmp;
+
+ tmp = m23;
+ m23 = m32;
+ m32 = tmp;
+
+ return this;
+ }
+
+ /**
+ * <code>toFloatBuffer</code> returns a FloatBuffer object that contains
+ * the matrix data.
+ *
+ * @return matrix data as a FloatBuffer.
+ */
+ public FloatBuffer toFloatBuffer() {
+ return toFloatBuffer(false);
+ }
+
+ /**
+ * <code>toFloatBuffer</code> returns a FloatBuffer object that contains the
+ * matrix data.
+ *
+ * @param columnMajor
+ * if true, this buffer should be filled with column major data,
+ * otherwise it will be filled row major.
+ * @return matrix data as a FloatBuffer. The position is set to 0 for
+ * convenience.
+ */
+ public FloatBuffer toFloatBuffer(boolean columnMajor) {
+ FloatBuffer fb = BufferUtils.createFloatBuffer(16);
+ fillFloatBuffer(fb, columnMajor);
+ fb.rewind();
+ return fb;
+ }
+
+ /**
+ * <code>fillFloatBuffer</code> fills a FloatBuffer object with
+ * the matrix data.
+ * @param fb the buffer to fill, must be correct size
+ * @return matrix data as a FloatBuffer.
+ */
+ public FloatBuffer fillFloatBuffer(FloatBuffer fb) {
+ return fillFloatBuffer(fb, false);
+ }
+
+ /**
+ * <code>fillFloatBuffer</code> fills a FloatBuffer object with the matrix
+ * data.
+ *
+ * @param fb
+ * the buffer to fill, starting at current position. Must have
+ * room for 16 more floats.
+ * @param columnMajor
+ * if true, this buffer should be filled with column major data,
+ * otherwise it will be filled row major.
+ * @return matrix data as a FloatBuffer. (position is advanced by 16 and any
+ * limit set is not changed).
+ */
+ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+// if (columnMajor) {
+// fb.put(m00).put(m10).put(m20).put(m30);
+// fb.put(m01).put(m11).put(m21).put(m31);
+// fb.put(m02).put(m12).put(m22).put(m32);
+// fb.put(m03).put(m13).put(m23).put(m33);
+// } else {
+// fb.put(m00).put(m01).put(m02).put(m03);
+// fb.put(m10).put(m11).put(m12).put(m13);
+// fb.put(m20).put(m21).put(m22).put(m23);
+// fb.put(m30).put(m31).put(m32).put(m33);
+// }
+
+ TempVars vars = TempVars.get();
+
+
+ fillFloatArray(vars.matrixWrite, columnMajor);
+ fb.put(vars.matrixWrite, 0, 16);
+
+ vars.release();
+
+ return fb;
+ }
+
+ public void fillFloatArray(float[] f, boolean columnMajor) {
+ if (columnMajor) {
+ f[ 0] = m00;
+ f[ 1] = m10;
+ f[ 2] = m20;
+ f[ 3] = m30;
+ f[ 4] = m01;
+ f[ 5] = m11;
+ f[ 6] = m21;
+ f[ 7] = m31;
+ f[ 8] = m02;
+ f[ 9] = m12;
+ f[10] = m22;
+ f[11] = m32;
+ f[12] = m03;
+ f[13] = m13;
+ f[14] = m23;
+ f[15] = m33;
+ } else {
+ f[ 0] = m00;
+ f[ 1] = m01;
+ f[ 2] = m02;
+ f[ 3] = m03;
+ f[ 4] = m10;
+ f[ 5] = m11;
+ f[ 6] = m12;
+ f[ 7] = m13;
+ f[ 8] = m20;
+ f[ 9] = m21;
+ f[10] = m22;
+ f[11] = m23;
+ f[12] = m30;
+ f[13] = m31;
+ f[14] = m32;
+ f[15] = m33;
+ }
+ }
+
+ /**
+ * <code>readFloatBuffer</code> reads value for this matrix from a FloatBuffer.
+ * @param fb the buffer to read from, must be correct size
+ * @return this data as a FloatBuffer.
+ */
+ public Matrix4f readFloatBuffer(FloatBuffer fb) {
+ return readFloatBuffer(fb, false);
+ }
+
+ /**
+ * <code>readFloatBuffer</code> reads value for this matrix from a FloatBuffer.
+ * @param fb the buffer to read from, must be correct size
+ * @param columnMajor if true, this buffer should be filled with column
+ * major data, otherwise it will be filled row major.
+ * @return this data as a FloatBuffer.
+ */
+ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) {
+
+ if (columnMajor) {
+ m00 = fb.get();
+ m10 = fb.get();
+ m20 = fb.get();
+ m30 = fb.get();
+ m01 = fb.get();
+ m11 = fb.get();
+ m21 = fb.get();
+ m31 = fb.get();
+ m02 = fb.get();
+ m12 = fb.get();
+ m22 = fb.get();
+ m32 = fb.get();
+ m03 = fb.get();
+ m13 = fb.get();
+ m23 = fb.get();
+ m33 = fb.get();
+ } else {
+ m00 = fb.get();
+ m01 = fb.get();
+ m02 = fb.get();
+ m03 = fb.get();
+ m10 = fb.get();
+ m11 = fb.get();
+ m12 = fb.get();
+ m13 = fb.get();
+ m20 = fb.get();
+ m21 = fb.get();
+ m22 = fb.get();
+ m23 = fb.get();
+ m30 = fb.get();
+ m31 = fb.get();
+ m32 = fb.get();
+ m33 = fb.get();
+ }
+ return this;
+ }
+
+ /**
+ * <code>loadIdentity</code> sets this matrix to the identity matrix,
+ * namely all zeros with ones along the diagonal.
+ *
+ */
+ public void loadIdentity() {
+ m01 = m02 = m03 = 0.0f;
+ m10 = m12 = m13 = 0.0f;
+ m20 = m21 = m23 = 0.0f;
+ m30 = m31 = m32 = 0.0f;
+ m00 = m11 = m22 = m33 = 1.0f;
+ }
+
+ public void fromFrustum(float near, float far, float left, float right, float top, float bottom, boolean parallel) {
+ loadIdentity();
+ if (parallel) {
+ // scale
+ m00 = 2.0f / (right - left);
+ //m11 = 2.0f / (bottom - top);
+ m11 = 2.0f / (top - bottom);
+ m22 = -2.0f / (far - near);
+ m33 = 1f;
+
+ // translation
+ m03 = -(right + left) / (right - left);
+ //m31 = -(bottom + top) / (bottom - top);
+ m13 = -(top + bottom) / (top - bottom);
+ m23 = -(far + near) / (far - near);
+ } else {
+ m00 = (2.0f * near) / (right - left);
+ m11 = (2.0f * near) / (top - bottom);
+ m32 = -1.0f;
+ m33 = -0.0f;
+
+ // A
+ m02 = (right + left) / (right - left);
+
+ // B
+ m12 = (top + bottom) / (top - bottom);
+
+ // C
+ m22 = -(far + near) / (far - near);
+
+ // D
+ m23 = -(2.0f * far * near) / (far - near);
+ }
+ }
+
+ /**
+ * <code>fromAngleAxis</code> sets this matrix4f to the values specified
+ * by an angle and an axis of rotation. This method creates an object, so
+ * use fromAngleNormalAxis if your axis is already normalized.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation.
+ */
+ public void fromAngleAxis(float angle, Vector3f axis) {
+ Vector3f normAxis = axis.normalize();
+ fromAngleNormalAxis(angle, normAxis);
+ }
+
+ /**
+ * <code>fromAngleNormalAxis</code> sets this matrix4f to the values
+ * specified by an angle and a normalized axis of rotation.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation (already normalized).
+ */
+ public void fromAngleNormalAxis(float angle, Vector3f axis) {
+ zero();
+ m33 = 1;
+
+ float fCos = FastMath.cos(angle);
+ float fSin = FastMath.sin(angle);
+ float fOneMinusCos = ((float) 1.0) - fCos;
+ float fX2 = axis.x * axis.x;
+ float fY2 = axis.y * axis.y;
+ float fZ2 = axis.z * axis.z;
+ float fXYM = axis.x * axis.y * fOneMinusCos;
+ float fXZM = axis.x * axis.z * fOneMinusCos;
+ float fYZM = axis.y * axis.z * fOneMinusCos;
+ float fXSin = axis.x * fSin;
+ float fYSin = axis.y * fSin;
+ float fZSin = axis.z * fSin;
+
+ m00 = fX2 * fOneMinusCos + fCos;
+ m01 = fXYM - fZSin;
+ m02 = fXZM + fYSin;
+ m10 = fXYM + fZSin;
+ m11 = fY2 * fOneMinusCos + fCos;
+ m12 = fYZM - fXSin;
+ m20 = fXZM - fYSin;
+ m21 = fYZM + fXSin;
+ m22 = fZ2 * fOneMinusCos + fCos;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix by a scalar.
+ *
+ * @param scalar
+ * the scalar to multiply this matrix by.
+ */
+ public void multLocal(float scalar) {
+ m00 *= scalar;
+ m01 *= scalar;
+ m02 *= scalar;
+ m03 *= scalar;
+ m10 *= scalar;
+ m11 *= scalar;
+ m12 *= scalar;
+ m13 *= scalar;
+ m20 *= scalar;
+ m21 *= scalar;
+ m22 *= scalar;
+ m23 *= scalar;
+ m30 *= scalar;
+ m31 *= scalar;
+ m32 *= scalar;
+ m33 *= scalar;
+ }
+
+ public Matrix4f mult(float scalar) {
+ Matrix4f out = new Matrix4f();
+ out.set(this);
+ out.multLocal(scalar);
+ return out;
+ }
+
+ public Matrix4f mult(float scalar, Matrix4f store) {
+ store.set(this);
+ store.multLocal(scalar);
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix with another matrix. The
+ * result matrix will then be returned. This matrix will be on the left hand
+ * side, while the parameter matrix will be on the right.
+ *
+ * @param in2
+ * the matrix to multiply this matrix by.
+ * @return the resultant matrix
+ */
+ public Matrix4f mult(Matrix4f in2) {
+ return mult(in2, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix with another matrix. The
+ * result matrix will then be returned. This matrix will be on the left hand
+ * side, while the parameter matrix will be on the right.
+ *
+ * @param in2
+ * the matrix to multiply this matrix by.
+ * @param store
+ * where to store the result. It is safe for in2 and store to be
+ * the same object.
+ * @return the resultant matrix
+ */
+ public Matrix4f mult(Matrix4f in2, Matrix4f store) {
+ if (store == null) {
+ store = new Matrix4f();
+ }
+
+ float temp00, temp01, temp02, temp03;
+ float temp10, temp11, temp12, temp13;
+ float temp20, temp21, temp22, temp23;
+ float temp30, temp31, temp32, temp33;
+
+ temp00 = m00 * in2.m00
+ + m01 * in2.m10
+ + m02 * in2.m20
+ + m03 * in2.m30;
+ temp01 = m00 * in2.m01
+ + m01 * in2.m11
+ + m02 * in2.m21
+ + m03 * in2.m31;
+ temp02 = m00 * in2.m02
+ + m01 * in2.m12
+ + m02 * in2.m22
+ + m03 * in2.m32;
+ temp03 = m00 * in2.m03
+ + m01 * in2.m13
+ + m02 * in2.m23
+ + m03 * in2.m33;
+
+ temp10 = m10 * in2.m00
+ + m11 * in2.m10
+ + m12 * in2.m20
+ + m13 * in2.m30;
+ temp11 = m10 * in2.m01
+ + m11 * in2.m11
+ + m12 * in2.m21
+ + m13 * in2.m31;
+ temp12 = m10 * in2.m02
+ + m11 * in2.m12
+ + m12 * in2.m22
+ + m13 * in2.m32;
+ temp13 = m10 * in2.m03
+ + m11 * in2.m13
+ + m12 * in2.m23
+ + m13 * in2.m33;
+
+ temp20 = m20 * in2.m00
+ + m21 * in2.m10
+ + m22 * in2.m20
+ + m23 * in2.m30;
+ temp21 = m20 * in2.m01
+ + m21 * in2.m11
+ + m22 * in2.m21
+ + m23 * in2.m31;
+ temp22 = m20 * in2.m02
+ + m21 * in2.m12
+ + m22 * in2.m22
+ + m23 * in2.m32;
+ temp23 = m20 * in2.m03
+ + m21 * in2.m13
+ + m22 * in2.m23
+ + m23 * in2.m33;
+
+ temp30 = m30 * in2.m00
+ + m31 * in2.m10
+ + m32 * in2.m20
+ + m33 * in2.m30;
+ temp31 = m30 * in2.m01
+ + m31 * in2.m11
+ + m32 * in2.m21
+ + m33 * in2.m31;
+ temp32 = m30 * in2.m02
+ + m31 * in2.m12
+ + m32 * in2.m22
+ + m33 * in2.m32;
+ temp33 = m30 * in2.m03
+ + m31 * in2.m13
+ + m32 * in2.m23
+ + m33 * in2.m33;
+
+ store.m00 = temp00;
+ store.m01 = temp01;
+ store.m02 = temp02;
+ store.m03 = temp03;
+ store.m10 = temp10;
+ store.m11 = temp11;
+ store.m12 = temp12;
+ store.m13 = temp13;
+ store.m20 = temp20;
+ store.m21 = temp21;
+ store.m22 = temp22;
+ store.m23 = temp23;
+ store.m30 = temp30;
+ store.m31 = temp31;
+ store.m32 = temp32;
+ store.m33 = temp33;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies this matrix with another matrix. The
+ * results are stored internally and a handle to this matrix will
+ * then be returned. This matrix will be on the left hand
+ * side, while the parameter matrix will be on the right.
+ *
+ * @param in2
+ * the matrix to multiply this matrix by.
+ * @return the resultant matrix
+ */
+ public Matrix4f multLocal(Matrix4f in2) {
+ return mult(in2, this);
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix. The
+ * resulting vector is returned as a new Vector3f.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @return the rotated vector.
+ */
+ public Vector3f mult(Vector3f vec) {
+ return mult(vec, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix and adds
+ * translation. The resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. Created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector3f mult(Vector3f vec, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+ store.x = m00 * vx + m01 * vy + m02 * vz + m03;
+ store.y = m10 * vx + m11 * vy + m12 * vz + m13;
+ store.z = m20 * vx + m21 * vy + m22 * vz + m23;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies a <code>Vector4f</code> about a rotation
+ * matrix. The resulting vector is returned as a new <code>Vector4f</code>.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @return the rotated vector.
+ */
+ public Vector4f mult(Vector4f vec) {
+ return mult(vec, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies a <code>Vector4f</code> about a rotation
+ * matrix. The resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. Created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector4f mult(Vector4f vec, Vector4f store) {
+ if (null == vec) {
+ logger.info("Source vector is null, null result returned.");
+ return null;
+ }
+ if (store == null) {
+ store = new Vector4f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w;
+ store.x = m00 * vx + m01 * vy + m02 * vz + m03 * vw;
+ store.y = m10 * vx + m11 * vy + m12 * vz + m13 * vw;
+ store.z = m20 * vx + m21 * vy + m22 * vz + m23 * vw;
+ store.w = m30 * vx + m31 * vy + m32 * vz + m33 * vw;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix. The
+ * resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ *
+ * @return the rotated vector.
+ */
+ public Vector4f multAcross(Vector4f vec) {
+ return multAcross(vec, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix. The
+ * resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector4f multAcross(Vector4f vec, Vector4f store) {
+ if (null == vec) {
+ logger.info("Source vector is null, null result returned.");
+ return null;
+ }
+ if (store == null) {
+ store = new Vector4f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z, vw = vec.w;
+ store.x = m00 * vx + m10 * vy + m20 * vz + m30 * vw;
+ store.y = m01 * vx + m11 * vy + m21 * vz + m31 * vw;
+ store.z = m02 * vx + m12 * vy + m22 * vz + m32 * vw;
+ store.z = m03 * vx + m13 * vy + m23 * vz + m33 * vw;
+
+ return store;
+ }
+
+ /**
+ * <code>multNormal</code> multiplies a vector about a rotation matrix, but
+ * does not add translation. The resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. Created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector3f multNormal(Vector3f vec, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+ store.x = m00 * vx + m01 * vy + m02 * vz;
+ store.y = m10 * vx + m11 * vy + m12 * vz;
+ store.z = m20 * vx + m21 * vy + m22 * vz;
+
+ return store;
+ }
+
+ /**
+ * <code>multNormal</code> multiplies a vector about a rotation matrix, but
+ * does not add translation. The resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. Created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector3f multNormalAcross(Vector3f vec, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+ store.x = m00 * vx + m10 * vy + m20 * vz;
+ store.y = m01 * vx + m11 * vy + m21 * vz;
+ store.z = m02 * vx + m12 * vy + m22 * vz;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix and adds
+ * translation. The w value is returned as a result of
+ * multiplying the last column of the matrix by 1.0
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in.
+ * @return the W value
+ */
+ public float multProj(Vector3f vec, Vector3f store) {
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+ store.x = m00 * vx + m01 * vy + m02 * vz + m03;
+ store.y = m10 * vx + m11 * vy + m12 * vz + m13;
+ store.z = m20 * vx + m21 * vy + m22 * vz + m23;
+ return m30 * vx + m31 * vy + m32 * vz + m33;
+ }
+
+ /**
+ * <code>mult</code> multiplies a vector about a rotation matrix. The
+ * resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a vector to store the result in. created if null is passed.
+ * @return the rotated vector.
+ */
+ public Vector3f multAcross(Vector3f vec, Vector3f store) {
+ if (null == vec) {
+ logger.info("Source vector is null, null result returned.");
+ return null;
+ }
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+ store.x = m00 * vx + m10 * vy + m20 * vz + m30 * 1;
+ store.y = m01 * vx + m11 * vy + m21 * vz + m31 * 1;
+ store.z = m02 * vx + m12 * vy + m22 * vz + m32 * 1;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies a quaternion about a matrix. The
+ * resulting vector is returned.
+ *
+ * @param vec
+ * vec to multiply against.
+ * @param store
+ * a quaternion to store the result in. created if null is passed.
+ * @return store = this * vec
+ */
+ public Quaternion mult(Quaternion vec, Quaternion store) {
+
+ if (null == vec) {
+ logger.warning("Source vector is null, null result returned.");
+ return null;
+ }
+ if (store == null) {
+ store = new Quaternion();
+ }
+
+ float x = m00 * vec.x + m10 * vec.y + m20 * vec.z + m30 * vec.w;
+ float y = m01 * vec.x + m11 * vec.y + m21 * vec.z + m31 * vec.w;
+ float z = m02 * vec.x + m12 * vec.y + m22 * vec.z + m32 * vec.w;
+ float w = m03 * vec.x + m13 * vec.y + m23 * vec.z + m33 * vec.w;
+ store.x = x;
+ store.y = y;
+ store.z = z;
+ store.w = w;
+
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies an array of 4 floats against this rotation
+ * matrix. The results are stored directly in the array. (vec4f x mat4f)
+ *
+ * @param vec4f
+ * float array (size 4) to multiply against the matrix.
+ * @return the vec4f for chaining.
+ */
+ public float[] mult(float[] vec4f) {
+ if (null == vec4f || vec4f.length != 4) {
+ logger.warning("invalid array given, must be nonnull and length 4");
+ return null;
+ }
+
+ float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];
+
+ vec4f[0] = m00 * x + m01 * y + m02 * z + m03 * w;
+ vec4f[1] = m10 * x + m11 * y + m12 * z + m13 * w;
+ vec4f[2] = m20 * x + m21 * y + m22 * z + m23 * w;
+ vec4f[3] = m30 * x + m31 * y + m32 * z + m33 * w;
+
+ return vec4f;
+ }
+
+ /**
+ * <code>mult</code> multiplies an array of 4 floats against this rotation
+ * matrix. The results are stored directly in the array. (vec4f x mat4f)
+ *
+ * @param vec4f
+ * float array (size 4) to multiply against the matrix.
+ * @return the vec4f for chaining.
+ */
+ public float[] multAcross(float[] vec4f) {
+ if (null == vec4f || vec4f.length != 4) {
+ logger.warning("invalid array given, must be nonnull and length 4");
+ return null;
+ }
+
+ float x = vec4f[0], y = vec4f[1], z = vec4f[2], w = vec4f[3];
+
+ vec4f[0] = m00 * x + m10 * y + m20 * z + m30 * w;
+ vec4f[1] = m01 * x + m11 * y + m21 * z + m31 * w;
+ vec4f[2] = m02 * x + m12 * y + m22 * z + m32 * w;
+ vec4f[3] = m03 * x + m13 * y + m23 * z + m33 * w;
+
+ return vec4f;
+ }
+
+ /**
+ * Inverts this matrix as a new Matrix4f.
+ *
+ * @return The new inverse matrix
+ */
+ public Matrix4f invert() {
+ return invert(null);
+ }
+
+ /**
+ * Inverts this matrix and stores it in the given store.
+ *
+ * @return The store
+ */
+ public Matrix4f invert(Matrix4f store) {
+ if (store == null) {
+ store = new Matrix4f();
+ }
+
+ float fA0 = m00 * m11 - m01 * m10;
+ float fA1 = m00 * m12 - m02 * m10;
+ float fA2 = m00 * m13 - m03 * m10;
+ float fA3 = m01 * m12 - m02 * m11;
+ float fA4 = m01 * m13 - m03 * m11;
+ float fA5 = m02 * m13 - m03 * m12;
+ float fB0 = m20 * m31 - m21 * m30;
+ float fB1 = m20 * m32 - m22 * m30;
+ float fB2 = m20 * m33 - m23 * m30;
+ float fB3 = m21 * m32 - m22 * m31;
+ float fB4 = m21 * m33 - m23 * m31;
+ float fB5 = m22 * m33 - m23 * m32;
+ float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+
+ if (FastMath.abs(fDet) <= 0f) {
+ throw new ArithmeticException("This matrix cannot be inverted");
+ }
+
+ store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+ store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+ store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+ store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+ store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+ store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+ store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+ store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+ store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+ store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+ store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+ store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+ store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+ store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+ store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+ store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+ float fInvDet = 1.0f / fDet;
+ store.multLocal(fInvDet);
+
+ return store;
+ }
+
+ /**
+ * Inverts this matrix locally.
+ *
+ * @return this
+ */
+ public Matrix4f invertLocal() {
+
+ float fA0 = m00 * m11 - m01 * m10;
+ float fA1 = m00 * m12 - m02 * m10;
+ float fA2 = m00 * m13 - m03 * m10;
+ float fA3 = m01 * m12 - m02 * m11;
+ float fA4 = m01 * m13 - m03 * m11;
+ float fA5 = m02 * m13 - m03 * m12;
+ float fB0 = m20 * m31 - m21 * m30;
+ float fB1 = m20 * m32 - m22 * m30;
+ float fB2 = m20 * m33 - m23 * m30;
+ float fB3 = m21 * m32 - m22 * m31;
+ float fB4 = m21 * m33 - m23 * m31;
+ float fB5 = m22 * m33 - m23 * m32;
+ float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+
+ if (FastMath.abs(fDet) <= 0f) {
+ return zero();
+ }
+
+ float f00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+ float f10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+ float f20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+ float f30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+ float f01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+ float f11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+ float f21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+ float f31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+ float f02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+ float f12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+ float f22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+ float f32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+ float f03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+ float f13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+ float f23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+ float f33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+ m00 = f00;
+ m01 = f01;
+ m02 = f02;
+ m03 = f03;
+ m10 = f10;
+ m11 = f11;
+ m12 = f12;
+ m13 = f13;
+ m20 = f20;
+ m21 = f21;
+ m22 = f22;
+ m23 = f23;
+ m30 = f30;
+ m31 = f31;
+ m32 = f32;
+ m33 = f33;
+
+ float fInvDet = 1.0f / fDet;
+ multLocal(fInvDet);
+
+ return this;
+ }
+
+ /**
+ * Returns a new matrix representing the adjoint of this matrix.
+ *
+ * @return The adjoint matrix
+ */
+ public Matrix4f adjoint() {
+ return adjoint(null);
+ }
+
+ public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) {
+ // Ordering:
+ // 1. Scale
+ // 2. Rotate
+ // 3. Translate
+
+ // Set up final matrix with scale, rotation and translation
+ m00 = scale.x * rotMat.m00;
+ m01 = scale.y * rotMat.m01;
+ m02 = scale.z * rotMat.m02;
+ m03 = position.x;
+ m10 = scale.x * rotMat.m10;
+ m11 = scale.y * rotMat.m11;
+ m12 = scale.z * rotMat.m12;
+ m13 = position.y;
+ m20 = scale.x * rotMat.m20;
+ m21 = scale.y * rotMat.m21;
+ m22 = scale.z * rotMat.m22;
+ m23 = position.z;
+
+ // No projection term
+ m30 = 0;
+ m31 = 0;
+ m32 = 0;
+ m33 = 1;
+ }
+
+ /**
+ * Places the adjoint of this matrix in store (creates store if null.)
+ *
+ * @param store
+ * The matrix to store the result in. If null, a new matrix is created.
+ * @return store
+ */
+ public Matrix4f adjoint(Matrix4f store) {
+ if (store == null) {
+ store = new Matrix4f();
+ }
+
+ float fA0 = m00 * m11 - m01 * m10;
+ float fA1 = m00 * m12 - m02 * m10;
+ float fA2 = m00 * m13 - m03 * m10;
+ float fA3 = m01 * m12 - m02 * m11;
+ float fA4 = m01 * m13 - m03 * m11;
+ float fA5 = m02 * m13 - m03 * m12;
+ float fB0 = m20 * m31 - m21 * m30;
+ float fB1 = m20 * m32 - m22 * m30;
+ float fB2 = m20 * m33 - m23 * m30;
+ float fB3 = m21 * m32 - m22 * m31;
+ float fB4 = m21 * m33 - m23 * m31;
+ float fB5 = m22 * m33 - m23 * m32;
+
+ store.m00 = +m11 * fB5 - m12 * fB4 + m13 * fB3;
+ store.m10 = -m10 * fB5 + m12 * fB2 - m13 * fB1;
+ store.m20 = +m10 * fB4 - m11 * fB2 + m13 * fB0;
+ store.m30 = -m10 * fB3 + m11 * fB1 - m12 * fB0;
+ store.m01 = -m01 * fB5 + m02 * fB4 - m03 * fB3;
+ store.m11 = +m00 * fB5 - m02 * fB2 + m03 * fB1;
+ store.m21 = -m00 * fB4 + m01 * fB2 - m03 * fB0;
+ store.m31 = +m00 * fB3 - m01 * fB1 + m02 * fB0;
+ store.m02 = +m31 * fA5 - m32 * fA4 + m33 * fA3;
+ store.m12 = -m30 * fA5 + m32 * fA2 - m33 * fA1;
+ store.m22 = +m30 * fA4 - m31 * fA2 + m33 * fA0;
+ store.m32 = -m30 * fA3 + m31 * fA1 - m32 * fA0;
+ store.m03 = -m21 * fA5 + m22 * fA4 - m23 * fA3;
+ store.m13 = +m20 * fA5 - m22 * fA2 + m23 * fA1;
+ store.m23 = -m20 * fA4 + m21 * fA2 - m23 * fA0;
+ store.m33 = +m20 * fA3 - m21 * fA1 + m22 * fA0;
+
+ return store;
+ }
+
+ /**
+ * <code>determinant</code> generates the determinate of this matrix.
+ *
+ * @return the determinate
+ */
+ public float determinant() {
+ float fA0 = m00 * m11 - m01 * m10;
+ float fA1 = m00 * m12 - m02 * m10;
+ float fA2 = m00 * m13 - m03 * m10;
+ float fA3 = m01 * m12 - m02 * m11;
+ float fA4 = m01 * m13 - m03 * m11;
+ float fA5 = m02 * m13 - m03 * m12;
+ float fB0 = m20 * m31 - m21 * m30;
+ float fB1 = m20 * m32 - m22 * m30;
+ float fB2 = m20 * m33 - m23 * m30;
+ float fB3 = m21 * m32 - m22 * m31;
+ float fB4 = m21 * m33 - m23 * m31;
+ float fB5 = m22 * m33 - m23 * m32;
+ float fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0;
+ return fDet;
+ }
+
+ /**
+ * Sets all of the values in this matrix to zero.
+ *
+ * @return this matrix
+ */
+ public Matrix4f zero() {
+ m00 = m01 = m02 = m03 = 0.0f;
+ m10 = m11 = m12 = m13 = 0.0f;
+ m20 = m21 = m22 = m23 = 0.0f;
+ m30 = m31 = m32 = m33 = 0.0f;
+ return this;
+ }
+
+ public Matrix4f add(Matrix4f mat) {
+ Matrix4f result = new Matrix4f();
+ result.m00 = this.m00 + mat.m00;
+ result.m01 = this.m01 + mat.m01;
+ result.m02 = this.m02 + mat.m02;
+ result.m03 = this.m03 + mat.m03;
+ result.m10 = this.m10 + mat.m10;
+ result.m11 = this.m11 + mat.m11;
+ result.m12 = this.m12 + mat.m12;
+ result.m13 = this.m13 + mat.m13;
+ result.m20 = this.m20 + mat.m20;
+ result.m21 = this.m21 + mat.m21;
+ result.m22 = this.m22 + mat.m22;
+ result.m23 = this.m23 + mat.m23;
+ result.m30 = this.m30 + mat.m30;
+ result.m31 = this.m31 + mat.m31;
+ result.m32 = this.m32 + mat.m32;
+ result.m33 = this.m33 + mat.m33;
+ return result;
+ }
+
+ /**
+ * <code>add</code> adds the values of a parameter matrix to this matrix.
+ *
+ * @param mat
+ * the matrix to add to this.
+ */
+ public void addLocal(Matrix4f mat) {
+ m00 += mat.m00;
+ m01 += mat.m01;
+ m02 += mat.m02;
+ m03 += mat.m03;
+ m10 += mat.m10;
+ m11 += mat.m11;
+ m12 += mat.m12;
+ m13 += mat.m13;
+ m20 += mat.m20;
+ m21 += mat.m21;
+ m22 += mat.m22;
+ m23 += mat.m23;
+ m30 += mat.m30;
+ m31 += mat.m31;
+ m32 += mat.m32;
+ m33 += mat.m33;
+ }
+
+ public Vector3f toTranslationVector() {
+ return new Vector3f(m03, m13, m23);
+ }
+
+ public void toTranslationVector(Vector3f vector) {
+ vector.set(m03, m13, m23);
+ }
+
+ public Quaternion toRotationQuat() {
+ Quaternion quat = new Quaternion();
+ quat.fromRotationMatrix(toRotationMatrix());
+ return quat;
+ }
+
+ public void toRotationQuat(Quaternion q) {
+ q.fromRotationMatrix(toRotationMatrix());
+ }
+
+ public Matrix3f toRotationMatrix() {
+ return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22);
+
+ }
+
+ public void toRotationMatrix(Matrix3f mat) {
+ mat.m00 = m00;
+ mat.m01 = m01;
+ mat.m02 = m02;
+ mat.m10 = m10;
+ mat.m11 = m11;
+ mat.m12 = m12;
+ mat.m20 = m20;
+ mat.m21 = m21;
+ mat.m22 = m22;
+
+ }
+
+ public void setScale(float x, float y, float z) {
+ m00 *= x;
+ m11 *= y;
+ m22 *= z;
+ }
+
+ public void setScale(Vector3f scale) {
+ m00 *= scale.x;
+ m11 *= scale.y;
+ m22 *= scale.z;
+ }
+
+ /**
+ * <code>setTranslation</code> will set the matrix's translation values.
+ *
+ * @param translation
+ * the new values for the translation.
+ * @throws JmeException
+ * if translation is not size 3.
+ */
+ public void setTranslation(float[] translation) {
+ if (translation.length != 3) {
+ throw new IllegalArgumentException(
+ "Translation size must be 3.");
+ }
+ m03 = translation[0];
+ m13 = translation[1];
+ m23 = translation[2];
+ }
+
+ /**
+ * <code>setTranslation</code> will set the matrix's translation values.
+ *
+ * @param x
+ * value of the translation on the x axis
+ * @param y
+ * value of the translation on the y axis
+ * @param z
+ * value of the translation on the z axis
+ */
+ public void setTranslation(float x, float y, float z) {
+ m03 = x;
+ m13 = y;
+ m23 = z;
+ }
+
+ /**
+ * <code>setTranslation</code> will set the matrix's translation values.
+ *
+ * @param translation
+ * the new values for the translation.
+ */
+ public void setTranslation(Vector3f translation) {
+ m03 = translation.x;
+ m13 = translation.y;
+ m23 = translation.z;
+ }
+
+ /**
+ * <code>setInverseTranslation</code> will set the matrix's inverse
+ * translation values.
+ *
+ * @param translation
+ * the new values for the inverse translation.
+ * @throws JmeException
+ * if translation is not size 3.
+ */
+ public void setInverseTranslation(float[] translation) {
+ if (translation.length != 3) {
+ throw new IllegalArgumentException(
+ "Translation size must be 3.");
+ }
+ m03 = -translation[0];
+ m13 = -translation[1];
+ m23 = -translation[2];
+ }
+
+ /**
+ * <code>angleRotation</code> sets this matrix to that of a rotation about
+ * three axes (x, y, z). Where each axis has a specified rotation in
+ * degrees. These rotations are expressed in a single <code>Vector3f</code>
+ * object.
+ *
+ * @param angles
+ * the angles to rotate.
+ */
+ public void angleRotation(Vector3f angles) {
+ float angle;
+ float sr, sp, sy, cr, cp, cy;
+
+ angle = (angles.z * FastMath.DEG_TO_RAD);
+ sy = FastMath.sin(angle);
+ cy = FastMath.cos(angle);
+ angle = (angles.y * FastMath.DEG_TO_RAD);
+ sp = FastMath.sin(angle);
+ cp = FastMath.cos(angle);
+ angle = (angles.x * FastMath.DEG_TO_RAD);
+ sr = FastMath.sin(angle);
+ cr = FastMath.cos(angle);
+
+ // matrix = (Z * Y) * X
+ m00 = cp * cy;
+ m10 = cp * sy;
+ m20 = -sp;
+ m01 = sr * sp * cy + cr * -sy;
+ m11 = sr * sp * sy + cr * cy;
+ m21 = sr * cp;
+ m02 = (cr * sp * cy + -sr * -sy);
+ m12 = (cr * sp * sy + -sr * cy);
+ m22 = cr * cp;
+ m03 = 0.0f;
+ m13 = 0.0f;
+ m23 = 0.0f;
+ }
+
+ /**
+ * <code>setRotationQuaternion</code> builds a rotation from a
+ * <code>Quaternion</code>.
+ *
+ * @param quat
+ * the quaternion to build the rotation from.
+ * @throws NullPointerException
+ * if quat is null.
+ */
+ public void setRotationQuaternion(Quaternion quat) {
+ quat.toRotationMatrix(this);
+ }
+
+ /**
+ * <code>setInverseRotationRadians</code> builds an inverted rotation from
+ * Euler angles that are in radians.
+ *
+ * @param angles
+ * the Euler angles in radians.
+ * @throws JmeException
+ * if angles is not size 3.
+ */
+ public void setInverseRotationRadians(float[] angles) {
+ if (angles.length != 3) {
+ throw new IllegalArgumentException(
+ "Angles must be of size 3.");
+ }
+ double cr = FastMath.cos(angles[0]);
+ double sr = FastMath.sin(angles[0]);
+ double cp = FastMath.cos(angles[1]);
+ double sp = FastMath.sin(angles[1]);
+ double cy = FastMath.cos(angles[2]);
+ double sy = FastMath.sin(angles[2]);
+
+ m00 = (float) (cp * cy);
+ m10 = (float) (cp * sy);
+ m20 = (float) (-sp);
+
+ double srsp = sr * sp;
+ double crsp = cr * sp;
+
+ m01 = (float) (srsp * cy - cr * sy);
+ m11 = (float) (srsp * sy + cr * cy);
+ m21 = (float) (sr * cp);
+
+ m02 = (float) (crsp * cy + sr * sy);
+ m12 = (float) (crsp * sy - sr * cy);
+ m22 = (float) (cr * cp);
+ }
+
+ /**
+ * <code>setInverseRotationDegrees</code> builds an inverted rotation from
+ * Euler angles that are in degrees.
+ *
+ * @param angles
+ * the Euler angles in degrees.
+ * @throws JmeException
+ * if angles is not size 3.
+ */
+ public void setInverseRotationDegrees(float[] angles) {
+ if (angles.length != 3) {
+ throw new IllegalArgumentException(
+ "Angles must be of size 3.");
+ }
+ float vec[] = new float[3];
+ vec[0] = (angles[0] * FastMath.RAD_TO_DEG);
+ vec[1] = (angles[1] * FastMath.RAD_TO_DEG);
+ vec[2] = (angles[2] * FastMath.RAD_TO_DEG);
+ setInverseRotationRadians(vec);
+ }
+
+ /**
+ *
+ * <code>inverseTranslateVect</code> translates a given Vector3f by the
+ * translation part of this matrix.
+ *
+ * @param vec
+ * the Vector3f data to be translated.
+ * @throws JmeException
+ * if the size of the Vector3f is not 3.
+ */
+ public void inverseTranslateVect(float[] vec) {
+ if (vec.length != 3) {
+ throw new IllegalArgumentException(
+ "vec must be of size 3.");
+ }
+
+ vec[0] = vec[0] - m03;
+ vec[1] = vec[1] - m13;
+ vec[2] = vec[2] - m23;
+ }
+
+ /**
+ *
+ * <code>inverseTranslateVect</code> translates a given Vector3f by the
+ * translation part of this matrix.
+ *
+ * @param data
+ * the Vector3f to be translated.
+ * @throws JmeException
+ * if the size of the Vector3f is not 3.
+ */
+ public void inverseTranslateVect(Vector3f data) {
+ data.x -= m03;
+ data.y -= m13;
+ data.z -= m23;
+ }
+
+ /**
+ *
+ * <code>inverseTranslateVect</code> translates a given Vector3f by the
+ * translation part of this matrix.
+ *
+ * @param data
+ * the Vector3f to be translated.
+ * @throws JmeException
+ * if the size of the Vector3f is not 3.
+ */
+ public void translateVect(Vector3f data) {
+ data.x += m03;
+ data.y += m13;
+ data.z += m23;
+ }
+
+ /**
+ *
+ * <code>inverseRotateVect</code> rotates a given Vector3f by the rotation
+ * part of this matrix.
+ *
+ * @param vec
+ * the Vector3f to be rotated.
+ */
+ public void inverseRotateVect(Vector3f vec) {
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+
+ vec.x = vx * m00 + vy * m10 + vz * m20;
+ vec.y = vx * m01 + vy * m11 + vz * m21;
+ vec.z = vx * m02 + vy * m12 + vz * m22;
+ }
+
+ public void rotateVect(Vector3f vec) {
+ float vx = vec.x, vy = vec.y, vz = vec.z;
+
+ vec.x = vx * m00 + vy * m01 + vz * m02;
+ vec.y = vx * m10 + vy * m11 + vz * m12;
+ vec.z = vx * m20 + vy * m21 + vz * m22;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this object.
+ * It is in a format of a 4x4 matrix. For example, an identity matrix would
+ * be represented by the following string. com.jme.math.Matrix3f <br>[<br>
+ * 1.0 0.0 0.0 0.0 <br>
+ * 0.0 1.0 0.0 0.0 <br>
+ * 0.0 0.0 1.0 0.0 <br>
+ * 0.0 0.0 0.0 1.0 <br>]<br>
+ *
+ * @return the string representation of this object.
+ */
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("Matrix4f\n[\n");
+ result.append(" ");
+ result.append(m00);
+ result.append(" ");
+ result.append(m01);
+ result.append(" ");
+ result.append(m02);
+ result.append(" ");
+ result.append(m03);
+ result.append(" \n");
+ result.append(" ");
+ result.append(m10);
+ result.append(" ");
+ result.append(m11);
+ result.append(" ");
+ result.append(m12);
+ result.append(" ");
+ result.append(m13);
+ result.append(" \n");
+ result.append(" ");
+ result.append(m20);
+ result.append(" ");
+ result.append(m21);
+ result.append(" ");
+ result.append(m22);
+ result.append(" ");
+ result.append(m23);
+ result.append(" \n");
+ result.append(" ");
+ result.append(m30);
+ result.append(" ");
+ result.append(m31);
+ result.append(" ");
+ result.append(m32);
+ result.append(" ");
+ result.append(m33);
+ result.append(" \n]");
+ return result.toString();
+ }
+
+ /**
+ *
+ * <code>hashCode</code> returns the hash code value as an integer and is
+ * supported for the benefit of hashing based collection classes such as
+ * Hashtable, HashMap, HashSet etc.
+ *
+ * @return the hashcode for this instance of Matrix4f.
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hash = 37;
+ hash = 37 * hash + Float.floatToIntBits(m00);
+ hash = 37 * hash + Float.floatToIntBits(m01);
+ hash = 37 * hash + Float.floatToIntBits(m02);
+ hash = 37 * hash + Float.floatToIntBits(m03);
+
+ hash = 37 * hash + Float.floatToIntBits(m10);
+ hash = 37 * hash + Float.floatToIntBits(m11);
+ hash = 37 * hash + Float.floatToIntBits(m12);
+ hash = 37 * hash + Float.floatToIntBits(m13);
+
+ hash = 37 * hash + Float.floatToIntBits(m20);
+ hash = 37 * hash + Float.floatToIntBits(m21);
+ hash = 37 * hash + Float.floatToIntBits(m22);
+ hash = 37 * hash + Float.floatToIntBits(m23);
+
+ hash = 37 * hash + Float.floatToIntBits(m30);
+ hash = 37 * hash + Float.floatToIntBits(m31);
+ hash = 37 * hash + Float.floatToIntBits(m32);
+ hash = 37 * hash + Float.floatToIntBits(m33);
+
+ return hash;
+ }
+
+ /**
+ * are these two matrices the same? they are is they both have the same mXX values.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Matrix4f) || o == null) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ Matrix4f comp = (Matrix4f) o;
+ if (Float.compare(m00, comp.m00) != 0) {
+ return false;
+ }
+ if (Float.compare(m01, comp.m01) != 0) {
+ return false;
+ }
+ if (Float.compare(m02, comp.m02) != 0) {
+ return false;
+ }
+ if (Float.compare(m03, comp.m03) != 0) {
+ return false;
+ }
+
+ if (Float.compare(m10, comp.m10) != 0) {
+ return false;
+ }
+ if (Float.compare(m11, comp.m11) != 0) {
+ return false;
+ }
+ if (Float.compare(m12, comp.m12) != 0) {
+ return false;
+ }
+ if (Float.compare(m13, comp.m13) != 0) {
+ return false;
+ }
+
+ if (Float.compare(m20, comp.m20) != 0) {
+ return false;
+ }
+ if (Float.compare(m21, comp.m21) != 0) {
+ return false;
+ }
+ if (Float.compare(m22, comp.m22) != 0) {
+ return false;
+ }
+ if (Float.compare(m23, comp.m23) != 0) {
+ return false;
+ }
+
+ if (Float.compare(m30, comp.m30) != 0) {
+ return false;
+ }
+ if (Float.compare(m31, comp.m31) != 0) {
+ return false;
+ }
+ if (Float.compare(m32, comp.m32) != 0) {
+ return false;
+ }
+ if (Float.compare(m33, comp.m33) != 0) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule cap = e.getCapsule(this);
+ cap.write(m00, "m00", 1);
+ cap.write(m01, "m01", 0);
+ cap.write(m02, "m02", 0);
+ cap.write(m03, "m03", 0);
+ cap.write(m10, "m10", 0);
+ cap.write(m11, "m11", 1);
+ cap.write(m12, "m12", 0);
+ cap.write(m13, "m13", 0);
+ cap.write(m20, "m20", 0);
+ cap.write(m21, "m21", 0);
+ cap.write(m22, "m22", 1);
+ cap.write(m23, "m23", 0);
+ cap.write(m30, "m30", 0);
+ cap.write(m31, "m31", 0);
+ cap.write(m32, "m32", 0);
+ cap.write(m33, "m33", 1);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule cap = e.getCapsule(this);
+ m00 = cap.readFloat("m00", 1);
+ m01 = cap.readFloat("m01", 0);
+ m02 = cap.readFloat("m02", 0);
+ m03 = cap.readFloat("m03", 0);
+ m10 = cap.readFloat("m10", 0);
+ m11 = cap.readFloat("m11", 1);
+ m12 = cap.readFloat("m12", 0);
+ m13 = cap.readFloat("m13", 0);
+ m20 = cap.readFloat("m20", 0);
+ m21 = cap.readFloat("m21", 0);
+ m22 = cap.readFloat("m22", 1);
+ m23 = cap.readFloat("m23", 0);
+ m30 = cap.readFloat("m30", 0);
+ m31 = cap.readFloat("m31", 0);
+ m32 = cap.readFloat("m32", 0);
+ m33 = cap.readFloat("m33", 1);
+ }
+
+ /**
+ * @return true if this matrix is identity
+ */
+ public boolean isIdentity() {
+ return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0)
+ && (m10 == 0 && m11 == 1 && m12 == 0 && m13 == 0)
+ && (m20 == 0 && m21 == 0 && m22 == 1 && m23 == 0)
+ && (m30 == 0 && m31 == 0 && m32 == 0 && m33 == 1);
+ }
+
+ /**
+ * Apply a scale to this matrix.
+ *
+ * @param scale
+ * the scale to apply
+ */
+ public void scale(Vector3f scale) {
+ m00 *= scale.getX();
+ m10 *= scale.getX();
+ m20 *= scale.getX();
+ m30 *= scale.getX();
+ m01 *= scale.getY();
+ m11 *= scale.getY();
+ m21 *= scale.getY();
+ m31 *= scale.getY();
+ m02 *= scale.getZ();
+ m12 *= scale.getZ();
+ m22 *= scale.getZ();
+ m32 *= scale.getZ();
+ }
+
+ static boolean equalIdentity(Matrix4f mat) {
+ if (Math.abs(mat.m00 - 1) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m11 - 1) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m22 - 1) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m33 - 1) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m01) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m02) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m03) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m10) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m12) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m13) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m20) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m21) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m23) > 1e-4) {
+ return false;
+ }
+
+ if (Math.abs(mat.m30) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m31) > 1e-4) {
+ return false;
+ }
+ if (Math.abs(mat.m32) > 1e-4) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // XXX: This tests more solid than converting the q to a matrix and multiplying... why?
+ public void multLocal(Quaternion rotation) {
+ Vector3f axis = new Vector3f();
+ float angle = rotation.toAngleAxis(axis);
+ Matrix4f matrix4f = new Matrix4f();
+ matrix4f.fromAngleAxis(angle, axis);
+ multLocal(matrix4f);
+ }
+
+ @Override
+ public Matrix4f clone() {
+ try {
+ return (Matrix4f) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Plane.java b/engine/src/core/com/jme3/math/Plane.java
new file mode 100644
index 0000000..e14e645
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Plane.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * <code>Plane</code> defines a plane where Normal dot (x,y,z) = Constant.
+ * This provides methods for calculating a "distance" of a point from this
+ * plane. The distance is pseudo due to the fact that it can be negative if the
+ * point is on the non-normal side of the plane.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class Plane implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger
+ .getLogger(Plane.class.getName());
+
+ public static enum Side {
+ None,
+ Positive,
+ Negative
+ }
+
+ /**
+ * Vector normal to the plane.
+ */
+ protected Vector3f normal = new Vector3f();
+
+ /**
+ * Constant of the plane. See formula in class definition.
+ */
+ protected float constant;
+
+ /**
+ * Constructor instantiates a new <code>Plane</code> object. This is the
+ * default object and contains a normal of (0,0,0) and a constant of 0.
+ */
+ public Plane() {
+ }
+
+ /**
+ * Constructor instantiates a new <code>Plane</code> object. The normal
+ * and constant values are set at creation.
+ *
+ * @param normal
+ * the normal of the plane.
+ * @param constant
+ * the constant of the plane.
+ */
+ public Plane(Vector3f normal, float constant) {
+ if (normal == null) {
+ throw new IllegalArgumentException("normal cannot be null");
+ }
+
+ this.normal.set(normal);
+ this.constant = constant;
+ }
+
+ /**
+ * <code>setNormal</code> sets the normal of the plane.
+ *
+ * @param normal
+ * the new normal of the plane.
+ */
+ public void setNormal(Vector3f normal) {
+ if (normal == null) {
+ throw new IllegalArgumentException("normal cannot be null");
+ }
+ this.normal.set(normal);
+ }
+
+ /**
+ * <code>setNormal</code> sets the normal of the plane.
+ *
+ */
+ public void setNormal(float x, float y, float z) {
+ this.normal.set(x,y,z);
+ }
+
+ /**
+ * <code>getNormal</code> retrieves the normal of the plane.
+ *
+ * @return the normal of the plane.
+ */
+ public Vector3f getNormal() {
+ return normal;
+ }
+
+ /**
+ * <code>setConstant</code> sets the constant value that helps define the
+ * plane.
+ *
+ * @param constant
+ * the new constant value.
+ */
+ public void setConstant(float constant) {
+ this.constant = constant;
+ }
+
+ /**
+ * <code>getConstant</code> returns the constant of the plane.
+ *
+ * @return the constant of the plane.
+ */
+ public float getConstant() {
+ return constant;
+ }
+
+ public Vector3f getClosestPoint(Vector3f point, Vector3f store){
+// float t = constant - normal.dot(point);
+// return store.set(normal).multLocal(t).addLocal(point);
+ float t = (constant - normal.dot(point)) / normal.dot(normal);
+ return store.set(normal).multLocal(t).addLocal(point);
+ }
+
+ public Vector3f getClosestPoint(Vector3f point){
+ return getClosestPoint(point, new Vector3f());
+ }
+
+ public Vector3f reflect(Vector3f point, Vector3f store){
+ if (store == null)
+ store = new Vector3f();
+
+ float d = pseudoDistance(point);
+ store.set(normal).negateLocal().multLocal(d * 2f);
+ store.addLocal(point);
+ return store;
+ }
+
+ /**
+ * <code>pseudoDistance</code> calculates the distance from this plane to
+ * a provided point. If the point is on the negative side of the plane the
+ * distance returned is negative, otherwise it is positive. If the point is
+ * on the plane, it is zero.
+ *
+ * @param point
+ * the point to check.
+ * @return the signed distance from the plane to a point.
+ */
+ public float pseudoDistance(Vector3f point) {
+ return normal.dot(point) - constant;
+ }
+
+ /**
+ * <code>whichSide</code> returns the side at which a point lies on the
+ * plane. The positive values returned are: NEGATIVE_SIDE, POSITIVE_SIDE and
+ * NO_SIDE.
+ *
+ * @param point
+ * the point to check.
+ * @return the side at which the point lies.
+ */
+ public Side whichSide(Vector3f point) {
+ float dis = pseudoDistance(point);
+ if (dis < 0) {
+ return Side.Negative;
+ } else if (dis > 0) {
+ return Side.Positive;
+ } else {
+ return Side.None;
+ }
+ }
+
+ public boolean isOnPlane(Vector3f point){
+ float dist = pseudoDistance(point);
+ if (dist < FastMath.FLT_EPSILON && dist > -FastMath.FLT_EPSILON)
+ return true;
+ else
+ return false;
+ }
+
+ /**
+ * Initialize this plane using the three points of the given triangle.
+ *
+ * @param t
+ * the triangle
+ */
+ public void setPlanePoints(AbstractTriangle t) {
+ setPlanePoints(t.get1(), t.get2(), t.get3());
+ }
+
+ /**
+ * Initialize this plane using a point of origin and a normal.
+ *
+ * @param origin
+ * @param normal
+ */
+ public void setOriginNormal(Vector3f origin, Vector3f normal){
+ this.normal.set(normal);
+ this.constant = normal.x * origin.x + normal.y * origin.y + normal.z * origin.z;
+ }
+
+ /**
+ * Initialize the Plane using the given 3 points as coplanar.
+ *
+ * @param v1
+ * the first point
+ * @param v2
+ * the second point
+ * @param v3
+ * the third point
+ */
+ public void setPlanePoints(Vector3f v1, Vector3f v2, Vector3f v3) {
+ normal.set(v2).subtractLocal(v1);
+ normal.crossLocal(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z)
+ .normalizeLocal();
+ constant = normal.dot(v1);
+ }
+
+ /**
+ * <code>toString</code> returns a string thta represents the string
+ * representation of this plane. It represents the normal as a
+ * <code>Vector3f</code> object, so the format is the following:
+ * com.jme.math.Plane [Normal: org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY,
+ * Z=ZZ.ZZZZ] - Constant: CC.CCCCC]
+ *
+ * @return the string representation of this plane.
+ */
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [Normal: " + normal + " - Constant: "
+ + constant + "]";
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(normal, "normal", Vector3f.ZERO);
+ capsule.write(constant, "constant", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ normal = (Vector3f) capsule.readSavable("normal", Vector3f.ZERO.clone());
+ constant = capsule.readFloat("constant", 0);
+ }
+
+ @Override
+ public Plane clone() {
+ try {
+ Plane p = (Plane) super.clone();
+ p.normal = normal.clone();
+ return p;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Quaternion.java b/engine/src/core/com/jme3/math/Quaternion.java
new file mode 100644
index 0000000..5a5a1c9
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Quaternion.java
@@ -0,0 +1,1345 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.logging.Logger;
+
+/**
+ * <code>Quaternion</code> defines a single example of a more general class of
+ * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a
+ * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth
+ * continuous rotation.
+ *
+ * <code>Quaternion</code> is defined by four floating point numbers: {x y z
+ * w}.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Quaternion implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Quaternion.class.getName());
+ /**
+ * Represents the identity quaternion rotation (0, 0, 0, 1).
+ */
+ public static final Quaternion IDENTITY = new Quaternion();
+ public static final Quaternion DIRECTION_Z = new Quaternion();
+ public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0);
+
+ static {
+ DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z);
+ }
+ protected float x, y, z, w;
+
+ /**
+ * Constructor instantiates a new <code>Quaternion</code> object
+ * initializing all values to zero, except w which is initialized to 1.
+ *
+ */
+ public Quaternion() {
+ x = 0;
+ y = 0;
+ z = 0;
+ w = 1;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Quaternion</code> object from the
+ * given list of parameters.
+ *
+ * @param x
+ * the x value of the quaternion.
+ * @param y
+ * the y value of the quaternion.
+ * @param z
+ * the z value of the quaternion.
+ * @param w
+ * the w value of the quaternion.
+ */
+ public Quaternion(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public float getZ() {
+ return z;
+ }
+
+ public float getW() {
+ return w;
+ }
+
+ /**
+ * sets the data in a <code>Quaternion</code> object from the given list
+ * of parameters.
+ *
+ * @param x
+ * the x value of the quaternion.
+ * @param y
+ * the y value of the quaternion.
+ * @param z
+ * the z value of the quaternion.
+ * @param w
+ * the w value of the quaternion.
+ * @return this
+ */
+ public Quaternion set(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ return this;
+ }
+
+ /**
+ * Sets the data in this <code>Quaternion</code> object to be equal to the
+ * passed <code>Quaternion</code> object. The values are copied producing
+ * a new object.
+ *
+ * @param q
+ * The Quaternion to copy values from.
+ * @return this
+ */
+ public Quaternion set(Quaternion q) {
+ this.x = q.x;
+ this.y = q.y;
+ this.z = q.z;
+ this.w = q.w;
+ return this;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Quaternion</code> object from a
+ * collection of rotation angles.
+ *
+ * @param angles
+ * the angles of rotation (x, y, z) that will define the
+ * <code>Quaternion</code>.
+ */
+ public Quaternion(float[] angles) {
+ fromAngles(angles);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Quaternion</code> object from an
+ * interpolation between two other quaternions.
+ *
+ * @param q1
+ * the first quaternion.
+ * @param q2
+ * the second quaternion.
+ * @param interp
+ * the amount to interpolate between the two quaternions.
+ */
+ public Quaternion(Quaternion q1, Quaternion q2, float interp) {
+ slerp(q1, q2, interp);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Quaternion</code> object from an
+ * existing quaternion, creating a copy.
+ *
+ * @param q
+ * the quaternion to copy.
+ */
+ public Quaternion(Quaternion q) {
+ this.x = q.x;
+ this.y = q.y;
+ this.z = q.z;
+ this.w = q.w;
+ }
+
+ /**
+ * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1).
+ */
+ public void loadIdentity() {
+ x = y = z = 0;
+ w = 1;
+ }
+
+ /**
+ * @return true if this Quaternion is {0,0,0,1}
+ */
+ public boolean isIdentity() {
+ if (x == 0 && y == 0 && z == 0 && w == 1) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * <code>fromAngles</code> builds a quaternion from the Euler rotation
+ * angles (y,r,p).
+ *
+ * @param angles
+ * the Euler angles of rotation (in radians).
+ */
+ public Quaternion fromAngles(float[] angles) {
+ if (angles.length != 3) {
+ throw new IllegalArgumentException(
+ "Angles array must have three elements");
+ }
+
+ return fromAngles(angles[0], angles[1], angles[2]);
+ }
+
+ /**
+ * <code>fromAngles</code> builds a Quaternion from the Euler rotation
+ * angles (y,r,p). Note that we are applying in order: roll, pitch, yaw but
+ * we've ordered them in x, y, and z for convenience.
+ * @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm</a>
+ *
+ * @param yaw
+ * the Euler yaw of rotation (in radians). (aka Bank, often rot
+ * around x)
+ * @param roll
+ * the Euler roll of rotation (in radians). (aka Heading, often
+ * rot around y)
+ * @param pitch
+ * the Euler pitch of rotation (in radians). (aka Attitude, often
+ * rot around z)
+ */
+ public Quaternion fromAngles(float yaw, float roll, float pitch) {
+ float angle;
+ float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw;
+ angle = pitch * 0.5f;
+ sinPitch = FastMath.sin(angle);
+ cosPitch = FastMath.cos(angle);
+ angle = roll * 0.5f;
+ sinRoll = FastMath.sin(angle);
+ cosRoll = FastMath.cos(angle);
+ angle = yaw * 0.5f;
+ sinYaw = FastMath.sin(angle);
+ cosYaw = FastMath.cos(angle);
+
+ // variables used to reduce multiplication calls.
+ float cosRollXcosPitch = cosRoll * cosPitch;
+ float sinRollXsinPitch = sinRoll * sinPitch;
+ float cosRollXsinPitch = cosRoll * sinPitch;
+ float sinRollXcosPitch = sinRoll * cosPitch;
+
+ w = (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw);
+ x = (cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw);
+ y = (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw);
+ z = (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw);
+
+ normalize();
+ return this;
+ }
+
+ /**
+ * <code>toAngles</code> returns this quaternion converted to Euler
+ * rotation angles (yaw,roll,pitch).<br/>
+ * @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm</a>
+ *
+ * @param angles
+ * the float[] in which the angles should be stored, or null if
+ * you want a new float[] to be created
+ * @return the float[] in which the angles are stored.
+ */
+ public float[] toAngles(float[] angles) {
+ if (angles == null) {
+ angles = new float[3];
+ } else if (angles.length != 3) {
+ throw new IllegalArgumentException("Angles array must have three elements");
+ }
+
+ float sqw = w * w;
+ float sqx = x * x;
+ float sqy = y * y;
+ float sqz = z * z;
+ float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise
+ // is correction factor
+ float test = x * y + z * w;
+ if (test > 0.499 * unit) { // singularity at north pole
+ angles[1] = 2 * FastMath.atan2(x, w);
+ angles[2] = FastMath.HALF_PI;
+ angles[0] = 0;
+ } else if (test < -0.499 * unit) { // singularity at south pole
+ angles[1] = -2 * FastMath.atan2(x, w);
+ angles[2] = -FastMath.HALF_PI;
+ angles[0] = 0;
+ } else {
+ angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // roll or heading
+ angles[2] = FastMath.asin(2 * test / unit); // pitch or attitude
+ angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // yaw or bank
+ }
+ return angles;
+ }
+
+ /**
+ *
+ * <code>fromRotationMatrix</code> generates a quaternion from a supplied
+ * matrix. This matrix is assumed to be a rotational matrix.
+ *
+ * @param matrix
+ * the matrix that defines the rotation.
+ */
+ public Quaternion fromRotationMatrix(Matrix3f matrix) {
+ return fromRotationMatrix(matrix.m00, matrix.m01, matrix.m02, matrix.m10,
+ matrix.m11, matrix.m12, matrix.m20, matrix.m21, matrix.m22);
+ }
+
+ public Quaternion fromRotationMatrix(float m00, float m01, float m02,
+ float m10, float m11, float m12,
+ float m20, float m21, float m22) {
+ // Use the Graphics Gems code, from
+ // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z
+ // *NOT* the "Matrix and Quaternions FAQ", which has errors!
+
+ // the trace is the sum of the diagonal elements; see
+ // http://mathworld.wolfram.com/MatrixTrace.html
+ float t = m00 + m11 + m22;
+
+ // we protect the division by s by ensuring that s>=1
+ if (t >= 0) { // |w| >= .5
+ float s = FastMath.sqrt(t + 1); // |s|>=1 ...
+ w = 0.5f * s;
+ s = 0.5f / s; // so this division isn't bad
+ x = (m21 - m12) * s;
+ y = (m02 - m20) * s;
+ z = (m10 - m01) * s;
+ } else if ((m00 > m11) && (m00 > m22)) {
+ float s = FastMath.sqrt(1.0f + m00 - m11 - m22); // |s|>=1
+ x = s * 0.5f; // |x| >= .5
+ s = 0.5f / s;
+ y = (m10 + m01) * s;
+ z = (m02 + m20) * s;
+ w = (m21 - m12) * s;
+ } else if (m11 > m22) {
+ float s = FastMath.sqrt(1.0f + m11 - m00 - m22); // |s|>=1
+ y = s * 0.5f; // |y| >= .5
+ s = 0.5f / s;
+ x = (m10 + m01) * s;
+ z = (m21 + m12) * s;
+ w = (m02 - m20) * s;
+ } else {
+ float s = FastMath.sqrt(1.0f + m22 - m00 - m11); // |s|>=1
+ z = s * 0.5f; // |z| >= .5
+ s = 0.5f / s;
+ x = (m02 + m20) * s;
+ y = (m21 + m12) * s;
+ w = (m10 - m01) * s;
+ }
+
+ return this;
+ }
+
+ /**
+ * <code>toRotationMatrix</code> converts this quaternion to a rotational
+ * matrix. Note: the result is created from a normalized version of this quat.
+ *
+ * @return the rotation matrix representation of this quaternion.
+ */
+ public Matrix3f toRotationMatrix() {
+ Matrix3f matrix = new Matrix3f();
+ return toRotationMatrix(matrix);
+ }
+
+ /**
+ * <code>toRotationMatrix</code> converts this quaternion to a rotational
+ * matrix. The result is stored in result.
+ *
+ * @param result
+ * The Matrix3f to store the result in.
+ * @return the rotation matrix representation of this quaternion.
+ */
+ public Matrix3f toRotationMatrix(Matrix3f result) {
+
+ float norm = norm();
+ // we explicitly test norm against one here, saving a division
+ // at the cost of a test and branch. Is it worth it?
+ float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
+
+ // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+ // will be used 2-4 times each.
+ float xs = x * s;
+ float ys = y * s;
+ float zs = z * s;
+ float xx = x * xs;
+ float xy = x * ys;
+ float xz = x * zs;
+ float xw = w * xs;
+ float yy = y * ys;
+ float yz = y * zs;
+ float yw = w * ys;
+ float zz = z * zs;
+ float zw = w * zs;
+
+ // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+ result.m00 = 1 - (yy + zz);
+ result.m01 = (xy - zw);
+ result.m02 = (xz + yw);
+ result.m10 = (xy + zw);
+ result.m11 = 1 - (xx + zz);
+ result.m12 = (yz - xw);
+ result.m20 = (xz - yw);
+ result.m21 = (yz + xw);
+ result.m22 = 1 - (xx + yy);
+
+ return result;
+ }
+
+ /**
+ * <code>toRotationMatrix</code> converts this quaternion to a rotational
+ * matrix. The result is stored in result. 4th row and 4th column values are
+ * untouched. Note: the result is created from a normalized version of this quat.
+ *
+ * @param result
+ * The Matrix4f to store the result in.
+ * @return the rotation matrix representation of this quaternion.
+ */
+ public Matrix4f toRotationMatrix(Matrix4f result) {
+
+ float norm = norm();
+ // we explicitly test norm against one here, saving a division
+ // at the cost of a test and branch. Is it worth it?
+ float s = (norm == 1f) ? 2f : (norm > 0f) ? 2f / norm : 0;
+
+ // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs
+ // will be used 2-4 times each.
+ float xs = x * s;
+ float ys = y * s;
+ float zs = z * s;
+ float xx = x * xs;
+ float xy = x * ys;
+ float xz = x * zs;
+ float xw = w * xs;
+ float yy = y * ys;
+ float yz = y * zs;
+ float yw = w * ys;
+ float zz = z * zs;
+ float zw = w * zs;
+
+ // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here
+ result.m00 = 1 - (yy + zz);
+ result.m01 = (xy - zw);
+ result.m02 = (xz + yw);
+ result.m10 = (xy + zw);
+ result.m11 = 1 - (xx + zz);
+ result.m12 = (yz - xw);
+ result.m20 = (xz - yw);
+ result.m21 = (yz + xw);
+ result.m22 = 1 - (xx + yy);
+
+ return result;
+ }
+
+ /**
+ * <code>getRotationColumn</code> returns one of three columns specified
+ * by the parameter. This column is returned as a <code>Vector3f</code>
+ * object.
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 2.
+ * @return the column specified by the index.
+ */
+ public Vector3f getRotationColumn(int i) {
+ return getRotationColumn(i, null);
+ }
+
+ /**
+ * <code>getRotationColumn</code> returns one of three columns specified
+ * by the parameter. This column is returned as a <code>Vector3f</code>
+ * object. The value is retrieved as if this quaternion was first normalized.
+ *
+ * @param i
+ * the column to retrieve. Must be between 0 and 2.
+ * @param store
+ * the vector object to store the result in. if null, a new one
+ * is created.
+ * @return the column specified by the index.
+ */
+ public Vector3f getRotationColumn(int i, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ float norm = norm();
+ if (norm != 1.0f) {
+ norm = FastMath.invSqrt(norm);
+ }
+
+ float xx = x * x * norm;
+ float xy = x * y * norm;
+ float xz = x * z * norm;
+ float xw = x * w * norm;
+ float yy = y * y * norm;
+ float yz = y * z * norm;
+ float yw = y * w * norm;
+ float zz = z * z * norm;
+ float zw = z * w * norm;
+
+ switch (i) {
+ case 0:
+ store.x = 1 - 2 * (yy + zz);
+ store.y = 2 * (xy + zw);
+ store.z = 2 * (xz - yw);
+ break;
+ case 1:
+ store.x = 2 * (xy - zw);
+ store.y = 1 - 2 * (xx + zz);
+ store.z = 2 * (yz + xw);
+ break;
+ case 2:
+ store.x = 2 * (xz + yw);
+ store.y = 2 * (yz - xw);
+ store.z = 1 - 2 * (xx + yy);
+ break;
+ default:
+ logger.warning("Invalid column index.");
+ throw new IllegalArgumentException("Invalid column index. " + i);
+ }
+
+ return store;
+ }
+
+ /**
+ * <code>fromAngleAxis</code> sets this quaternion to the values specified
+ * by an angle and an axis of rotation. This method creates an object, so
+ * use fromAngleNormalAxis if your axis is already normalized.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation.
+ * @return this quaternion
+ */
+ public Quaternion fromAngleAxis(float angle, Vector3f axis) {
+ Vector3f normAxis = axis.normalize();
+ fromAngleNormalAxis(angle, normAxis);
+ return this;
+ }
+
+ /**
+ * <code>fromAngleNormalAxis</code> sets this quaternion to the values
+ * specified by an angle and a normalized axis of rotation.
+ *
+ * @param angle
+ * the angle to rotate (in radians).
+ * @param axis
+ * the axis of rotation (already normalized).
+ */
+ public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) {
+ if (axis.x == 0 && axis.y == 0 && axis.z == 0) {
+ loadIdentity();
+ } else {
+ float halfAngle = 0.5f * angle;
+ float sin = FastMath.sin(halfAngle);
+ w = FastMath.cos(halfAngle);
+ x = sin * axis.x;
+ y = sin * axis.y;
+ z = sin * axis.z;
+ }
+ return this;
+ }
+
+ /**
+ * <code>toAngleAxis</code> sets a given angle and axis to that
+ * represented by the current quaternion. The values are stored as
+ * following: The axis is provided as a parameter and built by the method,
+ * the angle is returned as a float.
+ *
+ * @param axisStore
+ * the object we'll store the computed axis in.
+ * @return the angle of rotation in radians.
+ */
+ public float toAngleAxis(Vector3f axisStore) {
+ float sqrLength = x * x + y * y + z * z;
+ float angle;
+ if (sqrLength == 0.0f) {
+ angle = 0.0f;
+ if (axisStore != null) {
+ axisStore.x = 1.0f;
+ axisStore.y = 0.0f;
+ axisStore.z = 0.0f;
+ }
+ } else {
+ angle = (2.0f * FastMath.acos(w));
+ if (axisStore != null) {
+ float invLength = (1.0f / FastMath.sqrt(sqrLength));
+ axisStore.x = x * invLength;
+ axisStore.y = y * invLength;
+ axisStore.z = z * invLength;
+ }
+ }
+
+ return angle;
+ }
+
+ /**
+ * <code>slerp</code> sets this quaternion's value as an interpolation
+ * between two other quaternions.
+ *
+ * @param q1
+ * the first quaternion.
+ * @param q2
+ * the second quaternion.
+ * @param t
+ * the amount to interpolate between the two quaternions.
+ */
+ public Quaternion slerp(Quaternion q1, Quaternion q2, float t) {
+ // Create a local quaternion to store the interpolated quaternion
+ if (q1.x == q2.x && q1.y == q2.y && q1.z == q2.z && q1.w == q2.w) {
+ this.set(q1);
+ return this;
+ }
+
+ float result = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z)
+ + (q1.w * q2.w);
+
+ if (result < 0.0f) {
+ // Negate the second quaternion and the result of the dot product
+ q2.x = -q2.x;
+ q2.y = -q2.y;
+ q2.z = -q2.z;
+ q2.w = -q2.w;
+ result = -result;
+ }
+
+ // Set the first and second scale for the interpolation
+ float scale0 = 1 - t;
+ float scale1 = t;
+
+ // Check if the angle between the 2 quaternions was big enough to
+ // warrant such calculations
+ if ((1 - result) > 0.1f) {// Get the angle between the 2 quaternions,
+ // and then store the sin() of that angle
+ float theta = FastMath.acos(result);
+ float invSinTheta = 1f / FastMath.sin(theta);
+
+ // Calculate the scale for q1 and q2, according to the angle and
+ // it's sine value
+ scale0 = FastMath.sin((1 - t) * theta) * invSinTheta;
+ scale1 = FastMath.sin((t * theta)) * invSinTheta;
+ }
+
+ // Calculate the x, y, z and w values for the quaternion by using a
+ // special
+ // form of linear interpolation for quaternions.
+ this.x = (scale0 * q1.x) + (scale1 * q2.x);
+ this.y = (scale0 * q1.y) + (scale1 * q2.y);
+ this.z = (scale0 * q1.z) + (scale1 * q2.z);
+ this.w = (scale0 * q1.w) + (scale1 * q2.w);
+
+ // Return the interpolated quaternion
+ return this;
+ }
+
+ /**
+ * Sets the values of this quaternion to the slerp from itself to q2 by
+ * changeAmnt
+ *
+ * @param q2
+ * Final interpolation value
+ * @param changeAmnt
+ * The amount diffrence
+ */
+ public void slerp(Quaternion q2, float changeAmnt) {
+ if (this.x == q2.x && this.y == q2.y && this.z == q2.z
+ && this.w == q2.w) {
+ return;
+ }
+
+ float result = (this.x * q2.x) + (this.y * q2.y) + (this.z * q2.z)
+ + (this.w * q2.w);
+
+ if (result < 0.0f) {
+ // Negate the second quaternion and the result of the dot product
+ q2.x = -q2.x;
+ q2.y = -q2.y;
+ q2.z = -q2.z;
+ q2.w = -q2.w;
+ result = -result;
+ }
+
+ // Set the first and second scale for the interpolation
+ float scale0 = 1 - changeAmnt;
+ float scale1 = changeAmnt;
+
+ // Check if the angle between the 2 quaternions was big enough to
+ // warrant such calculations
+ if ((1 - result) > 0.1f) {
+ // Get the angle between the 2 quaternions, and then store the sin()
+ // of that angle
+ float theta = FastMath.acos(result);
+ float invSinTheta = 1f / FastMath.sin(theta);
+
+ // Calculate the scale for q1 and q2, according to the angle and
+ // it's sine value
+ scale0 = FastMath.sin((1 - changeAmnt) * theta) * invSinTheta;
+ scale1 = FastMath.sin((changeAmnt * theta)) * invSinTheta;
+ }
+
+ // Calculate the x, y, z and w values for the quaternion by using a
+ // special
+ // form of linear interpolation for quaternions.
+ this.x = (scale0 * this.x) + (scale1 * q2.x);
+ this.y = (scale0 * this.y) + (scale1 * q2.y);
+ this.z = (scale0 * this.z) + (scale1 * q2.z);
+ this.w = (scale0 * this.w) + (scale1 * q2.w);
+ }
+
+ /**
+ * Sets the values of this quaternion to the nlerp from itself to q2 by blend.
+ * @param q2
+ * @param blend
+ */
+ public void nlerp(Quaternion q2, float blend) {
+ float dot = dot(q2);
+ float blendI = 1.0f - blend;
+ if (dot < 0.0f) {
+ x = blendI * x - blend * q2.x;
+ y = blendI * y - blend * q2.y;
+ z = blendI * z - blend * q2.z;
+ w = blendI * w - blend * q2.w;
+ } else {
+ x = blendI * x + blend * q2.x;
+ y = blendI * y + blend * q2.y;
+ z = blendI * z + blend * q2.z;
+ w = blendI * w + blend * q2.w;
+ }
+ normalizeLocal();
+ }
+
+ /**
+ * <code>add</code> adds the values of this quaternion to those of the
+ * parameter quaternion. The result is returned as a new quaternion.
+ *
+ * @param q
+ * the quaternion to add to this.
+ * @return the new quaternion.
+ */
+ public Quaternion add(Quaternion q) {
+ return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w);
+ }
+
+ /**
+ * <code>add</code> adds the values of this quaternion to those of the
+ * parameter quaternion. The result is stored in this Quaternion.
+ *
+ * @param q
+ * the quaternion to add to this.
+ * @return This Quaternion after addition.
+ */
+ public Quaternion addLocal(Quaternion q) {
+ this.x += q.x;
+ this.y += q.y;
+ this.z += q.z;
+ this.w += q.w;
+ return this;
+ }
+
+ /**
+ * <code>subtract</code> subtracts the values of the parameter quaternion
+ * from those of this quaternion. The result is returned as a new
+ * quaternion.
+ *
+ * @param q
+ * the quaternion to subtract from this.
+ * @return the new quaternion.
+ */
+ public Quaternion subtract(Quaternion q) {
+ return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w);
+ }
+
+ /**
+ * <code>subtract</code> subtracts the values of the parameter quaternion
+ * from those of this quaternion. The result is stored in this Quaternion.
+ *
+ * @param q
+ * the quaternion to subtract from this.
+ * @return This Quaternion after subtraction.
+ */
+ public Quaternion subtractLocal(Quaternion q) {
+ this.x -= q.x;
+ this.y -= q.y;
+ this.z -= q.z;
+ this.w -= q.w;
+ return this;
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter quaternion.
+ * The result is returned as a new quaternion. It should be noted that
+ * quaternion multiplication is not commutative so q * p != p * q.
+ *
+ * @param q
+ * the quaternion to multiply this quaternion by.
+ * @return the new quaternion.
+ */
+ public Quaternion mult(Quaternion q) {
+ return mult(q, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter quaternion.
+ * The result is returned as a new quaternion. It should be noted that
+ * quaternion multiplication is not commutative so q * p != p * q.
+ *
+ * It IS safe for q and res to be the same object.
+ * It IS safe for this and res to be the same object.
+ *
+ * @param q
+ * the quaternion to multiply this quaternion by.
+ * @param res
+ * the quaternion to store the result in.
+ * @return the new quaternion.
+ */
+ public Quaternion mult(Quaternion q, Quaternion res) {
+ if (res == null) {
+ res = new Quaternion();
+ }
+ float qw = q.w, qx = q.x, qy = q.y, qz = q.z;
+ res.x = x * qw + y * qz - z * qy + w * qx;
+ res.y = -x * qz + y * qw + z * qx + w * qy;
+ res.z = x * qy - y * qx + z * qw + w * qz;
+ res.w = -x * qx - y * qy - z * qz + w * qw;
+ return res;
+ }
+
+ /**
+ * <code>apply</code> multiplies this quaternion by a parameter matrix
+ * internally.
+ *
+ * @param matrix
+ * the matrix to apply to this quaternion.
+ */
+ public void apply(Matrix3f matrix) {
+ float oldX = x, oldY = y, oldZ = z, oldW = w;
+ fromRotationMatrix(matrix);
+ float tempX = x, tempY = y, tempZ = z, tempW = w;
+
+ x = oldX * tempW + oldY * tempZ - oldZ * tempY + oldW * tempX;
+ y = -oldX * tempZ + oldY * tempW + oldZ * tempX + oldW * tempY;
+ z = oldX * tempY - oldY * tempX + oldZ * tempW + oldW * tempZ;
+ w = -oldX * tempX - oldY * tempY - oldZ * tempZ + oldW * tempW;
+ }
+
+ /**
+ *
+ * <code>fromAxes</code> creates a <code>Quaternion</code> that
+ * represents the coordinate system defined by three axes. These axes are
+ * assumed to be orthogonal and no error checking is applied. Thus, the user
+ * must insure that the three axes being provided indeed represents a proper
+ * right handed coordinate system.
+ *
+ * @param axis
+ * the array containing the three vectors representing the
+ * coordinate system.
+ */
+ public Quaternion fromAxes(Vector3f[] axis) {
+ if (axis.length != 3) {
+ throw new IllegalArgumentException(
+ "Axis array must have three elements");
+ }
+ return fromAxes(axis[0], axis[1], axis[2]);
+ }
+
+ /**
+ *
+ * <code>fromAxes</code> creates a <code>Quaternion</code> that
+ * represents the coordinate system defined by three axes. These axes are
+ * assumed to be orthogonal and no error checking is applied. Thus, the user
+ * must insure that the three axes being provided indeed represents a proper
+ * right handed coordinate system.
+ *
+ * @param xAxis vector representing the x-axis of the coordinate system.
+ * @param yAxis vector representing the y-axis of the coordinate system.
+ * @param zAxis vector representing the z-axis of the coordinate system.
+ */
+ public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) {
+ return fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y,
+ zAxis.y, xAxis.z, yAxis.z, zAxis.z);
+ }
+
+ /**
+ *
+ * <code>toAxes</code> takes in an array of three vectors. Each vector
+ * corresponds to an axis of the coordinate system defined by the quaternion
+ * rotation.
+ *
+ * @param axis
+ * the array of vectors to be filled.
+ */
+ public void toAxes(Vector3f axis[]) {
+ Matrix3f tempMat = toRotationMatrix();
+ axis[0] = tempMat.getColumn(0, axis[0]);
+ axis[1] = tempMat.getColumn(1, axis[1]);
+ axis[2] = tempMat.getColumn(2, axis[2]);
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter vector. The
+ * result is returned as a new vector.
+ *
+ * @param v
+ * the vector to multiply this quaternion by.
+ * @return the new vector.
+ */
+ public Vector3f mult(Vector3f v) {
+ return mult(v, null);
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter vector. The
+ * result is stored in the supplied vector
+ *
+ * @param v
+ * the vector to multiply this quaternion by.
+ * @return v
+ */
+ public Vector3f multLocal(Vector3f v) {
+ float tempX, tempY;
+ tempX = w * w * v.x + 2 * y * w * v.z - 2 * z * w * v.y + x * x * v.x
+ + 2 * y * x * v.y + 2 * z * x * v.z - z * z * v.x - y * y * v.x;
+ tempY = 2 * x * y * v.x + y * y * v.y + 2 * z * y * v.z + 2 * w * z
+ * v.x - z * z * v.y + w * w * v.y - 2 * x * w * v.z - x * x
+ * v.y;
+ v.z = 2 * x * z * v.x + 2 * y * z * v.y + z * z * v.z - 2 * w * y * v.x
+ - y * y * v.z + 2 * w * x * v.y - x * x * v.z + w * w * v.z;
+ v.x = tempX;
+ v.y = tempY;
+ return v;
+ }
+
+ /**
+ * Multiplies this Quaternion by the supplied quaternion. The result is
+ * stored in this Quaternion, which is also returned for chaining. Similar
+ * to this *= q.
+ *
+ * @param q
+ * The Quaternion to multiply this one by.
+ * @return This Quaternion, after multiplication.
+ */
+ public Quaternion multLocal(Quaternion q) {
+ float x1 = x * q.w + y * q.z - z * q.y + w * q.x;
+ float y1 = -x * q.z + y * q.w + z * q.x + w * q.y;
+ float z1 = x * q.y - y * q.x + z * q.w + w * q.z;
+ w = -x * q.x - y * q.y - z * q.z + w * q.w;
+ x = x1;
+ y = y1;
+ z = z1;
+ return this;
+ }
+
+ /**
+ * Multiplies this Quaternion by the supplied quaternion. The result is
+ * stored in this Quaternion, which is also returned for chaining. Similar
+ * to this *= q.
+ *
+ * @param qx -
+ * quat x value
+ * @param qy -
+ * quat y value
+ * @param qz -
+ * quat z value
+ * @param qw -
+ * quat w value
+ *
+ * @return This Quaternion, after multiplication.
+ */
+ public Quaternion multLocal(float qx, float qy, float qz, float qw) {
+ float x1 = x * qw + y * qz - z * qy + w * qx;
+ float y1 = -x * qz + y * qw + z * qx + w * qy;
+ float z1 = x * qy - y * qx + z * qw + w * qz;
+ w = -x * qx - y * qy - z * qz + w * qw;
+ x = x1;
+ y = y1;
+ z = z1;
+ return this;
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter vector. The
+ * result is returned as a new vector.
+ *
+ * @param v
+ * the vector to multiply this quaternion by.
+ * @param store
+ * the vector to store the result in. It IS safe for v and store
+ * to be the same object.
+ * @return the result vector.
+ */
+ public Vector3f mult(Vector3f v, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ if (v.x == 0 && v.y == 0 && v.z == 0) {
+ store.set(0, 0, 0);
+ } else {
+ float vx = v.x, vy = v.y, vz = v.z;
+ store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x
+ * vx + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y
+ * y * vx;
+ store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w
+ * z * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x
+ * x * vy;
+ store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w
+ * y * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w
+ * w * vz;
+ }
+ return store;
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter scalar. The
+ * result is returned as a new quaternion.
+ *
+ * @param scalar
+ * the quaternion to multiply this quaternion by.
+ * @return the new quaternion.
+ */
+ public Quaternion mult(float scalar) {
+ return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w);
+ }
+
+ /**
+ * <code>mult</code> multiplies this quaternion by a parameter scalar. The
+ * result is stored locally.
+ *
+ * @param scalar
+ * the quaternion to multiply this quaternion by.
+ * @return this.
+ */
+ public Quaternion multLocal(float scalar) {
+ w *= scalar;
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ return this;
+ }
+
+ /**
+ * <code>dot</code> calculates and returns the dot product of this
+ * quaternion with that of the parameter quaternion.
+ *
+ * @param q
+ * the quaternion to calculate the dot product of.
+ * @return the dot product of this and the parameter quaternion.
+ */
+ public float dot(Quaternion q) {
+ return w * q.w + x * q.x + y * q.y + z * q.z;
+ }
+
+ /**
+ * <code>norm</code> returns the norm of this quaternion. This is the dot
+ * product of this quaternion with itself.
+ *
+ * @return the norm of the quaternion.
+ */
+ public float norm() {
+ return w * w + x * x + y * y + z * z;
+ }
+
+ /**
+ * <code>normalize</code> normalizes the current <code>Quaternion</code>
+ * @deprecated The naming of this method doesn't follow convention.
+ * Please use {@link Quaternion#normalizeLocal() } instead.
+ */
+ @Deprecated
+ public void normalize() {
+ float n = FastMath.invSqrt(norm());
+ x *= n;
+ y *= n;
+ z *= n;
+ w *= n;
+ }
+
+ /**
+ * <code>normalize</code> normalizes the current <code>Quaternion</code>
+ */
+ public void normalizeLocal() {
+ float n = FastMath.invSqrt(norm());
+ x *= n;
+ y *= n;
+ z *= n;
+ w *= n;
+ }
+
+ /**
+ * <code>inverse</code> returns the inverse of this quaternion as a new
+ * quaternion. If this quaternion does not have an inverse (if its normal is
+ * 0 or less), then null is returned.
+ *
+ * @return the inverse of this quaternion or null if the inverse does not
+ * exist.
+ */
+ public Quaternion inverse() {
+ float norm = norm();
+ if (norm > 0.0) {
+ float invNorm = 1.0f / norm;
+ return new Quaternion(-x * invNorm, -y * invNorm, -z * invNorm, w
+ * invNorm);
+ }
+ // return an invalid result to flag the error
+ return null;
+ }
+
+ /**
+ * <code>inverse</code> calculates the inverse of this quaternion and
+ * returns this quaternion after it is calculated. If this quaternion does
+ * not have an inverse (if it's norma is 0 or less), nothing happens
+ *
+ * @return the inverse of this quaternion
+ */
+ public Quaternion inverseLocal() {
+ float norm = norm();
+ if (norm > 0.0) {
+ float invNorm = 1.0f / norm;
+ x *= -invNorm;
+ y *= -invNorm;
+ z *= -invNorm;
+ w *= invNorm;
+ }
+ return this;
+ }
+
+ /**
+ * <code>negate</code> inverts the values of the quaternion.
+ *
+ */
+ public void negate() {
+ x *= -1;
+ y *= -1;
+ z *= -1;
+ w *= -1;
+ }
+
+ /**
+ *
+ * <code>toString</code> creates the string representation of this
+ * <code>Quaternion</code>. The values of the quaternion are displace (x,
+ * y, z, w), in the following manner: <br>
+ * (x, y, z, w)
+ *
+ * @return the string representation of this object.
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ", " + z + ", " + w + ")";
+ }
+
+ /**
+ * <code>equals</code> determines if two quaternions are logically equal,
+ * that is, if the values of (x, y, z, w) are the same for both quaternions.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Quaternion)) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ Quaternion comp = (Quaternion) o;
+ if (Float.compare(x, comp.x) != 0) {
+ return false;
+ }
+ if (Float.compare(y, comp.y) != 0) {
+ return false;
+ }
+ if (Float.compare(z, comp.z) != 0) {
+ return false;
+ }
+ if (Float.compare(w, comp.w) != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ *
+ * <code>hashCode</code> returns the hash code value as an integer and is
+ * supported for the benefit of hashing based collection classes such as
+ * Hashtable, HashMap, HashSet etc.
+ *
+ * @return the hashcode for this instance of Quaternion.
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ int hash = 37;
+ hash = 37 * hash + Float.floatToIntBits(x);
+ hash = 37 * hash + Float.floatToIntBits(y);
+ hash = 37 * hash + Float.floatToIntBits(z);
+ hash = 37 * hash + Float.floatToIntBits(w);
+ return hash;
+
+ }
+
+ /**
+ * <code>readExternal</code> builds a quaternion from an
+ * <code>ObjectInput</code> object. <br>
+ * NOTE: Used with serialization. Not to be called manually.
+ *
+ * @param in
+ * the ObjectInput value to read from.
+ * @throws IOException
+ * if the ObjectInput value has problems reading a float.
+ * @see java.io.Externalizable
+ */
+ public void readExternal(ObjectInput in) throws IOException {
+ x = in.readFloat();
+ y = in.readFloat();
+ z = in.readFloat();
+ w = in.readFloat();
+ }
+
+ /**
+ * <code>writeExternal</code> writes this quaternion out to a
+ * <code>ObjectOutput</code> object. NOTE: Used with serialization. Not to
+ * be called manually.
+ *
+ * @param out
+ * the object to write to.
+ * @throws IOException
+ * if writing to the ObjectOutput fails.
+ * @see java.io.Externalizable
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeFloat(x);
+ out.writeFloat(y);
+ out.writeFloat(z);
+ out.writeFloat(w);
+ }
+
+ /**
+ * <code>lookAt</code> is a convienence method for auto-setting the
+ * quaternion based on a direction and an up vector. It computes
+ * the rotation to transform the z-axis to point into 'direction'
+ * and the y-axis to 'up'.
+ *
+ * @param direction
+ * where to look at in terms of local coordinates
+ * @param up
+ * a vector indicating the local up direction.
+ * (typically {0, 1, 0} in jME.)
+ */
+ public void lookAt(Vector3f direction, Vector3f up) {
+ TempVars vars = TempVars.get();
+ vars.vect3.set(direction).normalizeLocal();
+ vars.vect1.set(up).crossLocal(direction).normalizeLocal();
+ vars.vect2.set(direction).crossLocal(vars.vect1).normalizeLocal();
+ fromAxes(vars.vect1, vars.vect2, vars.vect3);
+ vars.release();
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule cap = e.getCapsule(this);
+ cap.write(x, "x", 0);
+ cap.write(y, "y", 0);
+ cap.write(z, "z", 0);
+ cap.write(w, "w", 1);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule cap = e.getCapsule(this);
+ x = cap.readFloat("x", 0);
+ y = cap.readFloat("y", 0);
+ z = cap.readFloat("z", 0);
+ w = cap.readFloat("w", 1);
+ }
+
+ /**
+ * @return A new quaternion that describes a rotation that would point you
+ * in the exact opposite direction of this Quaternion.
+ */
+ public Quaternion opposite() {
+ return opposite(null);
+ }
+
+ /**
+ * FIXME: This seems to have singularity type issues with angle == 0, possibly others such as PI.
+ * @param store
+ * A Quaternion to store our result in. If null, a new one is
+ * created.
+ * @return The store quaternion (or a new Quaterion, if store is null) that
+ * describes a rotation that would point you in the exact opposite
+ * direction of this Quaternion.
+ */
+ public Quaternion opposite(Quaternion store) {
+ if (store == null) {
+ store = new Quaternion();
+ }
+
+ Vector3f axis = new Vector3f();
+ float angle = toAngleAxis(axis);
+
+ store.fromAngleAxis(FastMath.PI + angle, axis);
+ return store;
+ }
+
+ /**
+ * @return This Quaternion, altered to describe a rotation that would point
+ * you in the exact opposite direction of where it is pointing
+ * currently.
+ */
+ public Quaternion oppositeLocal() {
+ return opposite(this);
+ }
+
+ @Override
+ public Quaternion clone() {
+ try {
+ return (Quaternion) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Ray.java b/engine/src/core/com/jme3/math/Ray.java
new file mode 100644
index 0000000..f363613
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Ray.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResult;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <code>Ray</code> defines a line segment which has an origin and a direction.
+ * That is, a point and an infinite ray is cast from this point. The ray is
+ * defined by the following equation: R(t) = origin + t*direction for t >= 0.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Ray implements Savable, Cloneable, Collidable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ /**
+ * The ray's begining point.
+ */
+ public Vector3f origin = new Vector3f();
+
+ /**
+ * The direction of the ray.
+ */
+ public Vector3f direction = new Vector3f(0, 0, 1);
+
+
+ public float limit = Float.POSITIVE_INFINITY;
+
+ /**
+ * Constructor instantiates a new <code>Ray</code> object. As default, the
+ * origin is (0,0,0) and the direction is (0,0,1).
+ *
+ */
+ public Ray() {
+ }
+
+ /**
+ * Constructor instantiates a new <code>Ray</code> object. The origin and
+ * direction are given.
+ * @param origin the origin of the ray.
+ * @param direction the direction the ray travels in.
+ */
+ public Ray(Vector3f origin, Vector3f direction) {
+ setOrigin(origin);
+ setDirection(direction);
+ }
+
+ /**
+ * <code>intersect</code> determines if the Ray intersects a triangle.
+ * @param t the Triangle to test against.
+ * @return true if the ray collides.
+ */
+// public boolean intersect(Triangle t) {
+// return intersect(t.get(0), t.get(1), t.get(2));
+// }
+ /**
+ * <code>intersect</code> determines if the Ray intersects a triangle
+ * defined by the specified points.
+ *
+ * @param v0
+ * first point of the triangle.
+ * @param v1
+ * second point of the triangle.
+ * @param v2
+ * third point of the triangle.
+ * @return true if the ray collides.
+ */
+// public boolean intersect(Vector3f v0,Vector3f v1,Vector3f v2){
+// return intersectWhere(v0, v1, v2, null);
+// }
+ /**
+ * <code>intersectWhere</code> determines if the Ray intersects a triangle. It then
+ * stores the point of intersection in the given loc vector
+ * @param t the Triangle to test against.
+ * @param loc
+ * storage vector to save the collision point in (if the ray
+ * collides)
+ * @return true if the ray collides.
+ */
+ public boolean intersectWhere(Triangle t, Vector3f loc) {
+ return intersectWhere(t.get(0), t.get(1), t.get(2), loc);
+ }
+
+ /**
+ * <code>intersectWhere</code> determines if the Ray intersects a triangle
+ * defined by the specified points and if so it stores the point of
+ * intersection in the given loc vector.
+ *
+ * @param v0
+ * first point of the triangle.
+ * @param v1
+ * second point of the triangle.
+ * @param v2
+ * third point of the triangle.
+ * @param loc
+ * storage vector to save the collision point in (if the ray
+ * collides) if null, only boolean is calculated.
+ * @return true if the ray collides.
+ */
+ public boolean intersectWhere(Vector3f v0, Vector3f v1, Vector3f v2,
+ Vector3f loc) {
+ return intersects(v0, v1, v2, loc, false, false);
+ }
+
+ /**
+ * <code>intersectWherePlanar</code> determines if the Ray intersects a
+ * triangle and if so it stores the point of
+ * intersection in the given loc vector as t, u, v where t is the distance
+ * from the origin to the point of intersection and u,v is the intersection
+ * point in terms of the triangle plane.
+ *
+ * @param t the Triangle to test against.
+ * @param loc
+ * storage vector to save the collision point in (if the ray
+ * collides) as t, u, v
+ * @return true if the ray collides.
+ */
+ public boolean intersectWherePlanar(Triangle t, Vector3f loc) {
+ return intersectWherePlanar(t.get(0), t.get(1), t.get(2), loc);
+ }
+
+ /**
+ * <code>intersectWherePlanar</code> determines if the Ray intersects a
+ * triangle defined by the specified points and if so it stores the point of
+ * intersection in the given loc vector as t, u, v where t is the distance
+ * from the origin to the point of intersection and u,v is the intersection
+ * point in terms of the triangle plane.
+ *
+ * @param v0
+ * first point of the triangle.
+ * @param v1
+ * second point of the triangle.
+ * @param v2
+ * third point of the triangle.
+ * @param loc
+ * storage vector to save the collision point in (if the ray
+ * collides) as t, u, v
+ * @return true if the ray collides.
+ */
+ public boolean intersectWherePlanar(Vector3f v0, Vector3f v1, Vector3f v2,
+ Vector3f loc) {
+ return intersects(v0, v1, v2, loc, true, false);
+ }
+
+ /**
+ * <code>intersects</code> does the actual intersection work.
+ *
+ * @param v0
+ * first point of the triangle.
+ * @param v1
+ * second point of the triangle.
+ * @param v2
+ * third point of the triangle.
+ * @param store
+ * storage vector - if null, no intersection is calc'd
+ * @param doPlanar
+ * true if we are calcing planar results.
+ * @param quad
+ * @return true if ray intersects triangle
+ */
+ private boolean intersects(Vector3f v0, Vector3f v1, Vector3f v2,
+ Vector3f store, boolean doPlanar, boolean quad) {
+ TempVars vars = TempVars.get();
+
+ Vector3f tempVa = vars.vect1,
+ tempVb = vars.vect2,
+ tempVc = vars.vect3,
+ tempVd = vars.vect4;
+
+ Vector3f diff = origin.subtract(v0, tempVa);
+ Vector3f edge1 = v1.subtract(v0, tempVb);
+ Vector3f edge2 = v2.subtract(v0, tempVc);
+ Vector3f norm = edge1.cross(edge2, tempVd);
+
+ float dirDotNorm = direction.dot(norm);
+ float sign;
+ if (dirDotNorm > FastMath.FLT_EPSILON) {
+ sign = 1;
+ } else if (dirDotNorm < -FastMath.FLT_EPSILON) {
+ sign = -1f;
+ dirDotNorm = -dirDotNorm;
+ } else {
+ // ray and triangle/quad are parallel
+ vars.release();
+ return false;
+ }
+
+ float dirDotDiffxEdge2 = sign * direction.dot(diff.cross(edge2, edge2));
+ if (dirDotDiffxEdge2 >= 0.0f) {
+ float dirDotEdge1xDiff = sign
+ * direction.dot(edge1.crossLocal(diff));
+
+ if (dirDotEdge1xDiff >= 0.0f) {
+ if (!quad ? dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm : dirDotEdge1xDiff <= dirDotNorm) {
+ float diffDotNorm = -sign * diff.dot(norm);
+ if (diffDotNorm >= 0.0f) {
+ // this method always returns
+ vars.release();
+
+ // ray intersects triangle
+ // if storage vector is null, just return true,
+ if (store == null) {
+ return true;
+ }
+
+ // else fill in.
+ float inv = 1f / dirDotNorm;
+ float t = diffDotNorm * inv;
+ if (!doPlanar) {
+ store.set(origin).addLocal(direction.x * t,
+ direction.y * t, direction.z * t);
+ } else {
+ // these weights can be used to determine
+ // interpolated values, such as texture coord.
+ // eg. texcoord s,t at intersection point:
+ // s = w0*s0 + w1*s1 + w2*s2;
+ // t = w0*t0 + w1*t1 + w2*t2;
+ float w1 = dirDotDiffxEdge2 * inv;
+ float w2 = dirDotEdge1xDiff * inv;
+ //float w0 = 1.0f - w1 - w2;
+ store.set(t, w1, w2);
+ }
+ return true;
+ }
+ }
+ }
+ }
+ vars.release();
+ return false;
+ }
+
+ public float intersects(Vector3f v0, Vector3f v1, Vector3f v2) {
+ float edge1X = v1.x - v0.x;
+ float edge1Y = v1.y - v0.y;
+ float edge1Z = v1.z - v0.z;
+
+ float edge2X = v2.x - v0.x;
+ float edge2Y = v2.y - v0.y;
+ float edge2Z = v2.z - v0.z;
+
+ float normX = ((edge1Y * edge2Z) - (edge1Z * edge2Y));
+ float normY = ((edge1Z * edge2X) - (edge1X * edge2Z));
+ float normZ = ((edge1X * edge2Y) - (edge1Y * edge2X));
+
+ float dirDotNorm = direction.x * normX + direction.y * normY + direction.z * normZ;
+
+ float diffX = origin.x - v0.x;
+ float diffY = origin.y - v0.y;
+ float diffZ = origin.z - v0.z;
+
+ float sign;
+ if (dirDotNorm > FastMath.FLT_EPSILON) {
+ sign = 1;
+ } else if (dirDotNorm < -FastMath.FLT_EPSILON) {
+ sign = -1f;
+ dirDotNorm = -dirDotNorm;
+ } else {
+ // ray and triangle/quad are parallel
+ return Float.POSITIVE_INFINITY;
+ }
+
+ float diffEdge2X = ((diffY * edge2Z) - (diffZ * edge2Y));
+ float diffEdge2Y = ((diffZ * edge2X) - (diffX * edge2Z));
+ float diffEdge2Z = ((diffX * edge2Y) - (diffY * edge2X));
+
+ float dirDotDiffxEdge2 = sign * (direction.x * diffEdge2X
+ + direction.y * diffEdge2Y
+ + direction.z * diffEdge2Z);
+
+ if (dirDotDiffxEdge2 >= 0.0f) {
+ diffEdge2X = ((edge1Y * diffZ) - (edge1Z * diffY));
+ diffEdge2Y = ((edge1Z * diffX) - (edge1X * diffZ));
+ diffEdge2Z = ((edge1X * diffY) - (edge1Y * diffX));
+
+ float dirDotEdge1xDiff = sign * (direction.x * diffEdge2X
+ + direction.y * diffEdge2Y
+ + direction.z * diffEdge2Z);
+
+ if (dirDotEdge1xDiff >= 0.0f) {
+ if (dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm) {
+ float diffDotNorm = -sign * (diffX * normX + diffY * normY + diffZ * normZ);
+ if (diffDotNorm >= 0.0f) {
+ // ray intersects triangle
+ // fill in.
+ float inv = 1f / dirDotNorm;
+ float t = diffDotNorm * inv;
+ return t;
+ }
+ }
+ }
+ }
+
+ return Float.POSITIVE_INFINITY;
+ }
+
+ /**
+ * <code>intersectWherePlanar</code> determines if the Ray intersects a
+ * quad defined by the specified points and if so it stores the point of
+ * intersection in the given loc vector as t, u, v where t is the distance
+ * from the origin to the point of intersection and u,v is the intersection
+ * point in terms of the quad plane.
+ * One edge of the quad is [v0,v1], another one [v0,v2]. The behaviour thus is like
+ * {@link #intersectWherePlanar(Vector3f, Vector3f, Vector3f, Vector3f)} except for
+ * the extended area, which is equivalent to the union of the triangles [v0,v1,v2]
+ * and [-v0+v1+v2,v1,v2].
+ *
+ * @param v0
+ * top left point of the quad.
+ * @param v1
+ * top right point of the quad.
+ * @param v2
+ * bottom left point of the quad.
+ * @param loc
+ * storage vector to save the collision point in (if the ray
+ * collides) as t, u, v
+ * @return true if the ray collides with the quad.
+ */
+ public boolean intersectWherePlanarQuad(Vector3f v0, Vector3f v1, Vector3f v2,
+ Vector3f loc) {
+ return intersects(v0, v1, v2, loc, true, true);
+ }
+
+ /**
+ *
+ * @param p
+ * @param loc
+ * @return true if the ray collides with the given Plane
+ */
+ public boolean intersectsWherePlane(Plane p, Vector3f loc) {
+ float denominator = p.getNormal().dot(direction);
+
+ if (denominator > -FastMath.FLT_EPSILON && denominator < FastMath.FLT_EPSILON) {
+ return false; // coplanar
+ }
+ float numerator = -(p.getNormal().dot(origin) - p.getConstant());
+ float ratio = numerator / denominator;
+
+ if (ratio < FastMath.FLT_EPSILON) {
+ return false; // intersects behind origin
+ }
+ loc.set(direction).multLocal(ratio).addLocal(origin);
+
+ return true;
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ if (other instanceof BoundingVolume) {
+ BoundingVolume bv = (BoundingVolume) other;
+ return bv.collideWith(this, results);
+ } else if (other instanceof AbstractTriangle) {
+ AbstractTriangle tri = (AbstractTriangle) other;
+ float d = intersects(tri.get1(), tri.get2(), tri.get3());
+ if (Float.isInfinite(d) || Float.isNaN(d)) {
+ return 0;
+ }
+
+ Vector3f point = new Vector3f(direction).multLocal(d).addLocal(origin);
+ results.addCollision(new CollisionResult(point, d));
+ return 1;
+ } else {
+ throw new UnsupportedCollisionException();
+ }
+ }
+
+ public float distanceSquared(Vector3f point) {
+ TempVars vars = TempVars.get();
+
+ Vector3f tempVa = vars.vect1,
+ tempVb = vars.vect2;
+
+ point.subtract(origin, tempVa);
+ float rayParam = direction.dot(tempVa);
+ if (rayParam > 0) {
+ origin.add(direction.mult(rayParam, tempVb), tempVb);
+ } else {
+ tempVb.set(origin);
+ rayParam = 0.0f;
+ }
+
+ tempVb.subtract(point, tempVa);
+ float len = tempVa.lengthSquared();
+ vars.release();
+ return len;
+ }
+
+ /**
+ *
+ * <code>getOrigin</code> retrieves the origin point of the ray.
+ *
+ * @return the origin of the ray.
+ */
+ public Vector3f getOrigin() {
+ return origin;
+ }
+
+ /**
+ *
+ * <code>setOrigin</code> sets the origin of the ray.
+ * @param origin the origin of the ray.
+ */
+ public void setOrigin(Vector3f origin) {
+ this.origin.set(origin);
+ }
+
+ /**
+ * <code>getLimit</code> returns the limit of the ray, aka the length.
+ * If the limit is not infinity, then this ray is a line with length <code>
+ * limit</code>.
+ *
+ * @return the limit of the ray, aka the length.
+ */
+ public float getLimit() {
+ return limit;
+ }
+
+ /**
+ * <code>setLimit</code> sets the limit of the ray.
+ * @param limit the limit of the ray.
+ * @see Ray#getLimit()
+ */
+ public void setLimit(float limit) {
+ this.limit = limit;
+ }
+
+ /**
+ *
+ * <code>getDirection</code> retrieves the direction vector of the ray.
+ * @return the direction of the ray.
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ *
+ * <code>setDirection</code> sets the direction vector of the ray.
+ * @param direction the direction of the ray.
+ */
+ public void setDirection(Vector3f direction) {
+ assert direction.isUnitVector();
+ this.direction.set(direction);
+ }
+
+ /**
+ * Copies information from a source ray into this ray.
+ *
+ * @param source
+ * the ray to copy information from
+ */
+ public void set(Ray source) {
+ origin.set(source.getOrigin());
+ direction.set(source.getDirection());
+ }
+
+ public String toString() {
+ return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]";
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(origin, "origin", Vector3f.ZERO);
+ capsule.write(direction, "direction", Vector3f.ZERO);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone());
+ direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public Ray clone() {
+ try {
+ Ray r = (Ray) super.clone();
+ r.direction = direction.clone();
+ r.origin = origin.clone();
+ return r;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Rectangle.java b/engine/src/core/com/jme3/math/Rectangle.java
new file mode 100644
index 0000000..310270c
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Rectangle.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+
+/**
+ *
+ * <code>Rectangle</code> defines a finite plane within three dimensional space
+ * that is specified via three points (A, B, C). These three points define a
+ * triangle with the forth point defining the rectangle ((B + C) - A.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+
+public final class Rectangle implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private Vector3f a, b, c;
+
+ /**
+ * Constructor creates a new <code>Rectangle</code> with no defined corners.
+ * A, B, and C must be set to define a valid rectangle.
+ *
+ */
+ public Rectangle() {
+ a = new Vector3f();
+ b = new Vector3f();
+ c = new Vector3f();
+ }
+
+ /**
+ * Constructor creates a new <code>Rectangle</code> with defined A, B, and C
+ * points that define the area of the rectangle.
+ *
+ * @param a
+ * the first corner of the rectangle.
+ * @param b
+ * the second corner of the rectangle.
+ * @param c
+ * the third corner of the rectangle.
+ */
+ public Rectangle(Vector3f a, Vector3f b, Vector3f c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+
+ /**
+ * <code>getA</code> returns the first point of the rectangle.
+ *
+ * @return the first point of the rectangle.
+ */
+ public Vector3f getA() {
+ return a;
+ }
+
+ /**
+ * <code>setA</code> sets the first point of the rectangle.
+ *
+ * @param a
+ * the first point of the rectangle.
+ */
+ public void setA(Vector3f a) {
+ this.a = a;
+ }
+
+ /**
+ * <code>getB</code> returns the second point of the rectangle.
+ *
+ * @return the second point of the rectangle.
+ */
+ public Vector3f getB() {
+ return b;
+ }
+
+ /**
+ * <code>setB</code> sets the second point of the rectangle.
+ *
+ * @param b
+ * the second point of the rectangle.
+ */
+ public void setB(Vector3f b) {
+ this.b = b;
+ }
+
+ /**
+ * <code>getC</code> returns the third point of the rectangle.
+ *
+ * @return the third point of the rectangle.
+ */
+ public Vector3f getC() {
+ return c;
+ }
+
+ /**
+ * <code>setC</code> sets the third point of the rectangle.
+ *
+ * @param c
+ * the third point of the rectangle.
+ */
+ public void setC(Vector3f c) {
+ this.c = c;
+ }
+
+ /**
+ * <code>random</code> returns a random point within the plane defined by:
+ * A, B, C, and (B + C) - A.
+ *
+ * @return a random point within the rectangle.
+ */
+ public Vector3f random() {
+ return random(null);
+ }
+
+ /**
+ * <code>random</code> returns a random point within the plane defined by:
+ * A, B, C, and (B + C) - A.
+ *
+ * @param result
+ * Vector to store result in
+ * @return a random point within the rectangle.
+ */
+ public Vector3f random(Vector3f result) {
+ if (result == null) {
+ result = new Vector3f();
+ }
+
+ float s = FastMath.nextRandomFloat();
+ float t = FastMath.nextRandomFloat();
+
+ float aMod = 1.0f - s - t;
+ result.set(a.mult(aMod).addLocal(b.mult(s).addLocal(c.mult(t))));
+ return result;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(a, "a", Vector3f.ZERO);
+ capsule.write(b, "b", Vector3f.ZERO);
+ capsule.write(c, "c", Vector3f.ZERO);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ a = (Vector3f) capsule.readSavable("a", Vector3f.ZERO.clone());
+ b = (Vector3f) capsule.readSavable("b", Vector3f.ZERO.clone());
+ c = (Vector3f) capsule.readSavable("c", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public Rectangle clone() {
+ try {
+ Rectangle r = (Rectangle) super.clone();
+ r.a = a.clone();
+ r.b = b.clone();
+ r.c = c.clone();
+ return r;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Ring.java b/engine/src/core/com/jme3/math/Ring.java
new file mode 100644
index 0000000..20f1ea4
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Ring.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+
+/**
+ * <code>Ring</code> defines a flat ring or disk within three dimensional
+ * space that is specified via the ring's center point, an up vector, an inner
+ * radius, and an outer radius.
+ *
+ * @author Andrzej Kapolka
+ * @author Joshua Slack
+ */
+
+public final class Ring implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private Vector3f center, up;
+ private float innerRadius, outerRadius;
+ private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f();
+
+ /**
+ * Constructor creates a new <code>Ring</code> lying on the XZ plane,
+ * centered at the origin, with an inner radius of zero and an outer radius
+ * of one (a unit disk).
+ */
+ public Ring() {
+ center = new Vector3f();
+ up = Vector3f.UNIT_Y.clone();
+ innerRadius = 0f;
+ outerRadius = 1f;
+ }
+
+ /**
+ * Constructor creates a new <code>Ring</code> with defined center point,
+ * up vector, and inner and outer radii.
+ *
+ * @param center
+ * the center of the ring.
+ * @param up
+ * the unit up vector defining the ring's orientation.
+ * @param innerRadius
+ * the ring's inner radius.
+ * @param outerRadius
+ * the ring's outer radius.
+ */
+ public Ring(Vector3f center, Vector3f up, float innerRadius,
+ float outerRadius) {
+ this.center = center;
+ this.up = up;
+ this.innerRadius = innerRadius;
+ this.outerRadius = outerRadius;
+ }
+
+ /**
+ * <code>getCenter</code> returns the center of the ring.
+ *
+ * @return the center of the ring.
+ */
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ /**
+ * <code>setCenter</code> sets the center of the ring.
+ *
+ * @param center
+ * the center of the ring.
+ */
+ public void setCenter(Vector3f center) {
+ this.center = center;
+ }
+
+ /**
+ * <code>getUp</code> returns the ring's up vector.
+ *
+ * @return the ring's up vector.
+ */
+ public Vector3f getUp() {
+ return up;
+ }
+
+ /**
+ * <code>setUp</code> sets the ring's up vector.
+ *
+ * @param up
+ * the ring's up vector.
+ */
+ public void setUp(Vector3f up) {
+ this.up = up;
+ }
+
+ /**
+ * <code>getInnerRadius</code> returns the ring's inner radius.
+ *
+ * @return the ring's inner radius.
+ */
+ public float getInnerRadius() {
+ return innerRadius;
+ }
+
+ /**
+ * <code>setInnerRadius</code> sets the ring's inner radius.
+ *
+ * @param innerRadius
+ * the ring's inner radius.
+ */
+ public void setInnerRadius(float innerRadius) {
+ this.innerRadius = innerRadius;
+ }
+
+ /**
+ * <code>getOuterRadius</code> returns the ring's outer radius.
+ *
+ * @return the ring's outer radius.
+ */
+ public float getOuterRadius() {
+ return outerRadius;
+ }
+
+ /**
+ * <code>setOuterRadius</code> sets the ring's outer radius.
+ *
+ * @param outerRadius
+ * the ring's outer radius.
+ */
+ public void setOuterRadius(float outerRadius) {
+ this.outerRadius = outerRadius;
+ }
+
+ /**
+ *
+ * <code>random</code> returns a random point within the ring.
+ *
+ * @return a random point within the ring.
+ */
+ public Vector3f random() {
+ return random(null);
+ }
+
+ /**
+ *
+ * <code>random</code> returns a random point within the ring.
+ *
+ * @param result Vector to store result in
+ * @return a random point within the ring.
+ */
+ public Vector3f random(Vector3f result) {
+ if (result == null) {
+ result = new Vector3f();
+ }
+
+ // compute a random radius according to the ring area distribution
+ float inner2 = innerRadius * innerRadius, outer2 = outerRadius
+ * outerRadius, r = FastMath.sqrt(inner2
+ + FastMath.nextRandomFloat() * (outer2 - inner2)), theta = FastMath
+ .nextRandomFloat()
+ * FastMath.TWO_PI;
+ up.cross(Vector3f.UNIT_X, b1);
+ if (b1.lengthSquared() < FastMath.FLT_EPSILON) {
+ up.cross(Vector3f.UNIT_Y, b1);
+ }
+ b1.normalizeLocal();
+ up.cross(b1, b2);
+ result.set(b1).multLocal(r * FastMath.cos(theta)).addLocal(center);
+ result.scaleAdd(r * FastMath.sin(theta), b2, result);
+ return result;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(center, "center", Vector3f.ZERO);
+ capsule.write(up, "up", Vector3f.UNIT_Z);
+ capsule.write(innerRadius, "innerRadius", 0f);
+ capsule.write(outerRadius, "outerRadius", 1f);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ center = (Vector3f) capsule.readSavable("center",
+ Vector3f.ZERO.clone());
+ up = (Vector3f) capsule
+ .readSavable("up", Vector3f.UNIT_Z.clone());
+ innerRadius = capsule.readFloat("innerRadius", 0f);
+ outerRadius = capsule.readFloat("outerRadius", 1f);
+ }
+
+ @Override
+ public Ring clone() {
+ try {
+ Ring r = (Ring) super.clone();
+ r.center = center.clone();
+ r.up = up.clone();
+ return r;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/math/Spline.java b/engine/src/core/com/jme3/math/Spline.java
new file mode 100644
index 0000000..b28a797
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Spline.java
@@ -0,0 +1,447 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ *
+ * @author Nehon
+ */
+public class Spline implements Savable {
+
+ public enum SplineType {
+ Linear,
+ CatmullRom,
+ Bezier,
+ Nurb
+ }
+
+ private List<Vector3f> controlPoints = new ArrayList<Vector3f>();
+ private List<Float> knots; //knots of NURBS spline
+ private float[] weights; //weights of NURBS spline
+ private int basisFunctionDegree; //degree of NURBS spline basis function (computed automatically)
+ private boolean cycle;
+ private List<Float> segmentsLength;
+ private float totalLength;
+ private List<Vector3f> CRcontrolPoints;
+ private float curveTension = 0.5f;
+ private SplineType type = SplineType.CatmullRom;
+
+ public Spline() {
+ }
+
+ /**
+ * Create a spline
+ * @param splineType the type of the spline @see {SplineType}
+ * @param controlPoints an array of vector to use as control points of the spline
+ * If the type of the curve is Bezier curve the control points should be provided
+ * in the appropriate way. Each point 'p' describing control position in the scene
+ * should be surrounded by two handler points. This applies to every point except
+ * for the border points of the curve, who should have only one handle point.
+ * The pattern should be as follows:
+ * P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn
+ *
+ * n is the amount of 'P' - points.
+ * @param curveTension the tension of the spline
+ * @param cycle true if the spline cycle.
+ */
+ public Spline(SplineType splineType, Vector3f[] controlPoints, float curveTension, boolean cycle) {
+ if(splineType==SplineType.Nurb) {
+ throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
+ }
+ for (int i = 0; i < controlPoints.length; i++) {
+ Vector3f vector3f = controlPoints[i];
+ this.controlPoints.add(vector3f);
+ }
+ type = splineType;
+ this.curveTension = curveTension;
+ this.cycle = cycle;
+ this.computeTotalLentgh();
+ }
+
+ /**
+ * Create a spline
+ * @param splineType the type of the spline @see {SplineType}
+ * @param controlPoints a list of vector to use as control points of the spline
+ * If the type of the curve is Bezier curve the control points should be provided
+ * in the appropriate way. Each point 'p' describing control position in the scene
+ * should be surrounded by two handler points. This applies to every point except
+ * for the border points of the curve, who should have only one handle point.
+ * The pattern should be as follows:
+ * P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn
+ *
+ * n is the amount of 'P' - points.
+ * @param curveTension the tension of the spline
+ * @param cycle true if the spline cycle.
+ */
+ public Spline(SplineType splineType, List<Vector3f> controlPoints, float curveTension, boolean cycle) {
+ if(splineType==SplineType.Nurb) {
+ throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!");
+ }
+ type = splineType;
+ this.controlPoints.addAll(controlPoints);
+ this.curveTension = curveTension;
+ this.cycle = cycle;
+ this.computeTotalLentgh();
+ }
+
+ /**
+ * Create a NURBS spline. A spline type is automatically set to SplineType.Nurb.
+ * The cycle is set to <b>false</b> by default.
+ * @param controlPoints a list of vector to use as control points of the spline
+ * @param nurbKnots the nurb's spline knots
+ */
+ public Spline(List<Vector4f> controlPoints, List<Float> nurbKnots) {
+ //input data control
+ for(int i=0;i<nurbKnots.size()-1;++i) {
+ if(nurbKnots.get(i)>nurbKnots.get(i+1)) {
+ throw new IllegalArgumentException("The knots values cannot decrease!");
+ }
+ }
+
+ //storing the data
+ type = SplineType.Nurb;
+ this.weights = new float[controlPoints.size()];
+ this.knots = nurbKnots;
+ this.basisFunctionDegree = nurbKnots.size() - weights.length;
+ for(int i=0;i<controlPoints.size();++i) {
+ Vector4f controlPoint = controlPoints.get(i);
+ this.controlPoints.add(new Vector3f(controlPoint.x, controlPoint.y, controlPoint.z));
+ this.weights[i] = controlPoint.w;
+ }
+ CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree);
+ this.computeTotalLentgh();
+ }
+
+ private void initCatmullRomWayPoints(List<Vector3f> list) {
+ if (CRcontrolPoints == null) {
+ CRcontrolPoints = new ArrayList<Vector3f>();
+ } else {
+ CRcontrolPoints.clear();
+ }
+ int nb = list.size() - 1;
+
+ if (cycle) {
+ CRcontrolPoints.add(list.get(list.size() - 2));
+ } else {
+ CRcontrolPoints.add(list.get(0).subtract(list.get(1).subtract(list.get(0))));
+ }
+
+ for (Iterator<Vector3f> it = list.iterator(); it.hasNext();) {
+ Vector3f vector3f = it.next();
+ CRcontrolPoints.add(vector3f);
+ }
+ if (cycle) {
+ CRcontrolPoints.add(list.get(1));
+ } else {
+ CRcontrolPoints.add(list.get(nb).add(list.get(nb).subtract(list.get(nb - 1))));
+ }
+
+ }
+
+ /**
+ * Adds a controlPoint to the spline
+ * @param controlPoint a position in world space
+ */
+ public void addControlPoint(Vector3f controlPoint) {
+ if (controlPoints.size() > 2 && this.cycle) {
+ controlPoints.remove(controlPoints.size() - 1);
+ }
+ controlPoints.add(controlPoint);
+ if (controlPoints.size() >= 2 && this.cycle) {
+ controlPoints.add(controlPoints.get(0));
+ }
+ if (controlPoints.size() > 1) {
+ this.computeTotalLentgh();
+ }
+ }
+
+ /**
+ * remove the controlPoint from the spline
+ * @param controlPoint the controlPoint to remove
+ */
+ public void removeControlPoint(Vector3f controlPoint) {
+ controlPoints.remove(controlPoint);
+ if (controlPoints.size() > 1) {
+ this.computeTotalLentgh();
+ }
+ }
+
+ public void clearControlPoints(){
+ controlPoints.clear();
+ totalLength = 0;
+ }
+
+ /**
+ * This method computes the total length of the curve.
+ */
+ private void computeTotalLentgh() {
+ totalLength = 0;
+ float l = 0;
+ if (segmentsLength == null) {
+ segmentsLength = new ArrayList<Float>();
+ } else {
+ segmentsLength.clear();
+ }
+ if (type == SplineType.Linear) {
+ if (controlPoints.size() > 1) {
+ for (int i = 0; i < controlPoints.size() - 1; i++) {
+ l = controlPoints.get(i + 1).subtract(controlPoints.get(i)).length();
+ segmentsLength.add(l);
+ totalLength += l;
+ }
+ }
+ } else if(type == SplineType.Bezier) {
+ this.computeBezierLength();
+ } else if(type == SplineType.Nurb) {
+ this.computeNurbLength();
+ } else {
+ this.initCatmullRomWayPoints(controlPoints);
+ this.computeCatmulLength();
+ }
+ }
+
+ /**
+ * This method computes the Catmull Rom curve length.
+ */
+ private void computeCatmulLength() {
+ float l = 0;
+ if (controlPoints.size() > 1) {
+ for (int i = 0; i < controlPoints.size() - 1; i++) {
+ l = FastMath.getCatmullRomP1toP2Length(CRcontrolPoints.get(i),
+ CRcontrolPoints.get(i + 1), CRcontrolPoints.get(i + 2), CRcontrolPoints.get(i + 3), 0, 1, curveTension);
+ segmentsLength.add(l);
+ totalLength += l;
+ }
+ }
+ }
+
+ /**
+ * This method calculates the Bezier curve length.
+ */
+ private void computeBezierLength() {
+ float l = 0;
+ if (controlPoints.size() > 1) {
+ for (int i = 0; i < controlPoints.size() - 1; i+=3) {
+ l = FastMath.getBezierP1toP2Length(controlPoints.get(i),
+ controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3));
+ segmentsLength.add(l);
+ totalLength += l;
+ }
+ }
+ }
+
+ /**
+ * This method calculates the NURB curve length.
+ */
+ private void computeNurbLength() {
+ //TODO: implement
+ }
+
+ /**
+ * Iterpolate a position on the spline
+ * @param value a value from 0 to 1 that represent the postion between the curent control point and the next one
+ * @param currentControlPoint the current control point
+ * @param store a vector to store the result (use null to create a new one that will be returned by the method)
+ * @return the position
+ */
+ public Vector3f interpolate(float value, int currentControlPoint, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+ switch (type) {
+ case CatmullRom:
+ FastMath.interpolateCatmullRom(value, curveTension, CRcontrolPoints.get(currentControlPoint), CRcontrolPoints.get(currentControlPoint + 1), CRcontrolPoints.get(currentControlPoint + 2), CRcontrolPoints.get(currentControlPoint + 3), store);
+ break;
+ case Linear:
+ FastMath.interpolateLinear(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), store);
+ break;
+ case Bezier:
+ FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store);
+ break;
+ case Nurb:
+ CurveAndSurfaceMath.interpolateNurbs(value, this, store);
+ break;
+ default:
+ break;
+ }
+ return store;
+ }
+
+ /**
+ * returns the curve tension
+ */
+ public float getCurveTension() {
+ return curveTension;
+ }
+
+ /**
+ * sets the curve tension
+ *
+ * @param curveTension the tension
+ */
+ public void setCurveTension(float curveTension) {
+ this.curveTension = curveTension;
+ if(type==SplineType.CatmullRom) {
+ this.computeTotalLentgh();
+ }
+ }
+
+ /**
+ * returns true if the spline cycle
+ */
+ public boolean isCycle() {
+ return cycle;
+ }
+
+ /**
+ * set to true to make the spline cycle
+ * @param cycle
+ */
+ public void setCycle(boolean cycle) {
+ if(type!=SplineType.Nurb) {
+ if (controlPoints.size() >= 2) {
+ if (this.cycle && !cycle) {
+ controlPoints.remove(controlPoints.size() - 1);
+ }
+ if (!this.cycle && cycle) {
+ controlPoints.add(controlPoints.get(0));
+ }
+ this.cycle = cycle;
+ this.computeTotalLentgh();
+ } else {
+ this.cycle = cycle;
+ }
+ }
+ }
+
+ /**
+ * return the total lenght of the spline
+ */
+ public float getTotalLength() {
+ return totalLength;
+ }
+
+ /**
+ * return the type of the spline
+ */
+ public SplineType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type of the spline
+ * @param type
+ */
+ public void setType(SplineType type) {
+ this.type = type;
+ this.computeTotalLentgh();
+ }
+
+ /**
+ * returns this spline control points
+ */
+ public List<Vector3f> getControlPoints() {
+ return controlPoints;
+ }
+
+ /**
+ * returns a list of float representing the segments lenght
+ */
+ public List<Float> getSegmentsLength() {
+ return segmentsLength;
+ }
+
+ //////////// NURBS getters /////////////////////
+
+ /**
+ * This method returns the minimum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
+ * type - NPE will be thrown.
+ * @return the minimum nurb curve knot value
+ */
+ public float getMinNurbKnot() {
+ return knots.get(basisFunctionDegree - 1);
+ }
+
+ /**
+ * This method returns the maximum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb
+ * type - NPE will be thrown.
+ * @return the maximum nurb curve knot value
+ */
+ public float getMaxNurbKnot() {
+ return knots.get(weights.length);
+ }
+
+ /**
+ * This method returns NURBS' spline knots.
+ * @return NURBS' spline knots
+ */
+ public List<Float> getKnots() {
+ return knots;
+ }
+
+ /**
+ * This method returns NURBS' spline weights.
+ * @return NURBS' spline weights
+ */
+ public float[] getWeights() {
+ return weights;
+ }
+
+ /**
+ * This method returns NURBS' spline basis function degree.
+ * @return NURBS' spline basis function degree
+ */
+ public int getBasisFunctionDegree() {
+ return basisFunctionDegree;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null);
+ oc.write(type, "type", SplineType.CatmullRom);
+ float list[] = new float[segmentsLength.size()];
+ for (int i = 0; i < segmentsLength.size(); i++) {
+ list[i] = segmentsLength.get(i);
+ }
+ oc.write(list, "segmentsLength", null);
+
+ oc.write(totalLength, "totalLength", 0);
+ oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null);
+ oc.write(curveTension, "curveTension", 0.5f);
+ oc.write(cycle, "cycle", false);
+ oc.writeSavableArrayList((ArrayList<Float>)knots, "knots", null);
+ oc.write(weights, "weights", null);
+ oc.write(basisFunctionDegree, "basisFunctionDegree", 0);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+
+ controlPoints = (ArrayList<Vector3f>) in.readSavableArrayList("wayPoints", null);
+ float list[] = in.readFloatArray("segmentsLength", null);
+ if (list != null) {
+ segmentsLength = new ArrayList<Float>();
+ for (int i = 0; i < list.length; i++) {
+ segmentsLength.add(new Float(list[i]));
+ }
+ }
+ type = in.readEnum("pathSplineType", SplineType.class, SplineType.CatmullRom);
+ totalLength = in.readFloat("totalLength", 0);
+ CRcontrolPoints = (ArrayList<Vector3f>) in.readSavableArrayList("CRControlPoints", null);
+ curveTension = in.readFloat("curveTension", 0.5f);
+ cycle = in.readBoolean("cycle", false);
+ knots = in.readSavableArrayList("knots", null);
+ weights = in.readFloatArray("weights", null);
+ basisFunctionDegree = in.readInt("basisFunctionDegree", 0);
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Transform.java b/engine/src/core/com/jme3/math/Transform.java
new file mode 100644
index 0000000..7ccd847
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Transform.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+/**
+ * Started Date: Jul 16, 2004<br><br>
+ * Represents a translation, rotation and scale in one object.
+ *
+ * @author Jack Lindamood
+ * @author Joshua Slack
+ */
+public final class Transform implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ public static final Transform IDENTITY = new Transform();
+
+ private Quaternion rot = new Quaternion();
+ private Vector3f translation = new Vector3f();
+ private Vector3f scale = new Vector3f(1,1,1);
+
+ public Transform(Vector3f translation, Quaternion rot){
+ this.translation.set(translation);
+ this.rot.set(rot);
+ }
+
+ public Transform(Vector3f translation, Quaternion rot, Vector3f scale){
+ this(translation, rot);
+ this.scale.set(scale);
+ }
+
+ public Transform(Vector3f translation){
+ this(translation, Quaternion.IDENTITY);
+ }
+
+ public Transform(Quaternion rot){
+ this(Vector3f.ZERO, rot);
+ }
+
+ public Transform(){
+ this(Vector3f.ZERO, Quaternion.IDENTITY);
+ }
+
+ /**
+ * Sets this rotation to the given Quaternion value.
+ * @param rot The new rotation for this matrix.
+ * @return this
+ */
+ public Transform setRotation(Quaternion rot) {
+ this.rot.set(rot);
+ return this;
+ }
+
+ /**
+ * Sets this translation to the given value.
+ * @param trans The new translation for this matrix.
+ * @return this
+ */
+ public Transform setTranslation(Vector3f trans) {
+ this.translation.set(trans);
+ return this;
+ }
+
+ /**
+ * Return the translation vector in this matrix.
+ * @return translation vector.
+ */
+ public Vector3f getTranslation() {
+ return translation;
+ }
+
+ /**
+ * Sets this scale to the given value.
+ * @param scale The new scale for this matrix.
+ * @return this
+ */
+ public Transform setScale(Vector3f scale) {
+ this.scale.set(scale);
+ return this;
+ }
+
+ /**
+ * Sets this scale to the given value.
+ * @param scale The new scale for this matrix.
+ * @return this
+ */
+ public Transform setScale(float scale) {
+ this.scale.set(scale, scale, scale);
+ return this;
+ }
+
+ /**
+ * Return the scale vector in this matrix.
+ * @return scale vector.
+ */
+ public Vector3f getScale() {
+ return scale;
+ }
+
+ /**
+ * Stores this translation value into the given vector3f. If trans is null, a new vector3f is created to
+ * hold the value. The value, once stored, is returned.
+ * @param trans The store location for this matrix's translation.
+ * @return The value of this matrix's translation.
+ */
+ public Vector3f getTranslation(Vector3f trans) {
+ if (trans==null) trans=new Vector3f();
+ trans.set(this.translation);
+ return trans;
+ }
+
+ /**
+ * Stores this rotation value into the given Quaternion. If quat is null, a new Quaternion is created to
+ * hold the value. The value, once stored, is returned.
+ * @param quat The store location for this matrix's rotation.
+ * @return The value of this matrix's rotation.
+ */
+ public Quaternion getRotation(Quaternion quat) {
+ if (quat==null) quat=new Quaternion();
+ quat.set(rot);
+ return quat;
+ }
+
+ /**
+ * Return the rotation quaternion in this matrix.
+ * @return rotation quaternion.
+ */
+ public Quaternion getRotation() {
+ return rot;
+ }
+
+ /**
+ * Stores this scale value into the given vector3f. If scale is null, a new vector3f is created to
+ * hold the value. The value, once stored, is returned.
+ * @param scale The store location for this matrix's scale.
+ * @return The value of this matrix's scale.
+ */
+ public Vector3f getScale(Vector3f scale) {
+ if (scale==null) scale=new Vector3f();
+ scale.set(this.scale);
+ return scale;
+ }
+
+ /**
+ * Sets this matrix to the interpolation between the first matrix and the second by delta amount.
+ * @param t1 The begining transform.
+ * @param t2 The ending transform.
+ * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2.
+ */
+ public void interpolateTransforms(Transform t1, Transform t2, float delta) {
+ this.rot.slerp(t1.rot,t2.rot,delta);
+ this.translation.interpolate(t1.translation,t2.translation,delta);
+ this.scale.interpolate(t1.scale,t2.scale,delta);
+ }
+
+ /**
+ * Changes the values of this matrix acording to it's parent. Very similar to the concept of Node/Spatial transforms.
+ * @param parent The parent matrix.
+ * @return This matrix, after combining.
+ */
+ public Transform combineWithParent(Transform parent) {
+ scale.multLocal(parent.scale);
+// rot.multLocal(parent.rot);
+ parent.rot.mult(rot, rot);
+
+ // This here, is evil code
+// parent
+// .rot
+// .multLocal(translation)
+// .multLocal(parent.scale)
+// .addLocal(parent.translation);
+
+ translation.multLocal(parent.scale);
+ parent
+ .rot
+ .multLocal(translation)
+ .addLocal(parent.translation);
+ return this;
+ }
+
+ /**
+ * Sets this matrix's translation to the given x,y,z values.
+ * @param x This matrix's new x translation.
+ * @param y This matrix's new y translation.
+ * @param z This matrix's new z translation.
+ * @return this
+ */
+ public Transform setTranslation(float x,float y, float z) {
+ translation.set(x,y,z);
+ return this;
+ }
+
+ /**
+ * Sets this matrix's scale to the given x,y,z values.
+ * @param x This matrix's new x scale.
+ * @param y This matrix's new y scale.
+ * @param z This matrix's new z scale.
+ * @return this
+ */
+ public Transform setScale(float x, float y, float z) {
+ scale.set(x,y,z);
+ return this;
+ }
+
+ public Vector3f transformVector(final Vector3f in, Vector3f store){
+ if (store == null)
+ store = new Vector3f();
+
+ // multiply with scale first, then rotate, finally translate (cf.
+ // Eberly)
+ return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation);
+ }
+
+ public Vector3f transformInverseVector(final Vector3f in, Vector3f store){
+ if (store == null)
+ store = new Vector3f();
+
+ // The author of this code should look above and take the inverse of that
+ // But for some reason, they didnt ..
+// in.subtract(translation, store).divideLocal(scale);
+// rot.inverse().mult(store, store);
+
+ in.subtract(translation, store);
+ rot.inverse().mult(store, store);
+ store.divideLocal(scale);
+
+ return store;
+ }
+
+ /**
+ * Loads the identity. Equal to translation=1,1,1 scale=0,0,0 rot=0,0,0,1.
+ */
+ public void loadIdentity() {
+ translation.set(0,0,0);
+ scale.set(1,1,1);
+ rot.set(0,0,0,1);
+ }
+
+ @Override
+ public String toString(){
+ return getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n"
+ + "[ " + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "]\n"
+ + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]";
+ }
+
+ /**
+ * Sets this matrix to be equal to the given matrix.
+ * @param matrixQuat The matrix to be equal to.
+ * @return this
+ */
+ public Transform set(Transform matrixQuat) {
+ this.translation.set(matrixQuat.translation);
+ this.rot.set(matrixQuat.rot);
+ this.scale.set(matrixQuat.scale);
+ return this;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(rot, "rot", new Quaternion());
+ capsule.write(translation, "translation", Vector3f.ZERO);
+ capsule.write(scale, "scale", Vector3f.UNIT_XYZ);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+
+ rot = (Quaternion)capsule.readSavable("rot", new Quaternion());
+ translation = (Vector3f)capsule.readSavable("translation", Vector3f.ZERO);
+ scale = (Vector3f)capsule.readSavable("scale", Vector3f.UNIT_XYZ);
+ }
+
+ @Override
+ public Transform clone() {
+ try {
+ Transform tq = (Transform) super.clone();
+ tq.rot = rot.clone();
+ tq.scale = scale.clone();
+ tq.translation = translation.clone();
+ return tq;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Triangle.java b/engine/src/core/com/jme3/math/Triangle.java
new file mode 100644
index 0000000..6faa53e
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Triangle.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.math;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import java.io.IOException;
+
+/**
+ * <code>Triangle</code> defines an object for containing triangle information.
+ * The triangle is defined by a collection of three {@link Vector3f}
+ * objects.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class Triangle extends AbstractTriangle implements Savable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private Vector3f pointa = new Vector3f();
+ private Vector3f pointb = new Vector3f();
+ private Vector3f pointc = new Vector3f();
+ private transient Vector3f center;
+ private transient Vector3f normal;
+ private float projection;
+ private int index;
+
+ public Triangle() {
+ }
+
+ /**
+ * Constructor instantiates a new <Code>Triangle</code> object with the
+ * supplied vectors as the points. It is recommended that the vertices
+ * be supplied in a counter clockwise winding to support normals for a
+ * right handed coordinate system.
+ * @param p1 the first point of the triangle.
+ * @param p2 the second point of the triangle.
+ * @param p3 the third point of the triangle.
+ */
+ public Triangle(Vector3f p1, Vector3f p2, Vector3f p3) {
+ pointa.set(p1);
+ pointb.set(p2);
+ pointc.set(p3);
+ }
+
+ /**
+ *
+ * <code>get</code> retrieves a point on the triangle denoted by the index
+ * supplied.
+ * @param i the index of the point.
+ * @return the point.
+ */
+ public Vector3f get(int i) {
+ switch (i) {
+ case 0:
+ return pointa;
+ case 1:
+ return pointb;
+ case 2:
+ return pointc;
+ default:
+ return null;
+ }
+ }
+
+ public Vector3f get1() {
+ return pointa;
+ }
+
+ public Vector3f get2() {
+ return pointb;
+ }
+
+ public Vector3f get3() {
+ return pointc;
+ }
+
+ /**
+ *
+ * <code>set</code> sets one of the triangle's points to that specified as
+ * a parameter.
+ * @param i the index to place the point.
+ * @param point the point to set.
+ */
+ public void set(int i, Vector3f point) {
+ switch (i) {
+ case 0:
+ pointa.set(point);
+ break;
+ case 1:
+ pointb.set(point);
+ break;
+ case 2:
+ pointc.set(point);
+ break;
+ }
+ }
+
+ /**
+ *
+ * <code>set</code> sets one of the triangle's points to that specified as
+ * a parameter.
+ * @param i the index to place the point.
+ */
+ public void set(int i, float x, float y, float z) {
+ switch (i) {
+ case 0:
+ pointa.set(x, y, z);
+ break;
+ case 1:
+ pointb.set(x, y, z);
+ break;
+ case 2:
+ pointc.set(x, y, z);
+ break;
+ }
+ }
+
+ public void set1(Vector3f v) {
+ pointa.set(v);
+ }
+
+ public void set2(Vector3f v) {
+ pointb.set(v);
+ }
+
+ public void set3(Vector3f v) {
+ pointc.set(v);
+ }
+
+ public void set(Vector3f v1, Vector3f v2, Vector3f v3) {
+ pointa.set(v1);
+ pointb.set(v2);
+ pointc.set(v3);
+ }
+
+ /**
+ * calculateCenter finds the average point of the triangle.
+ *
+ */
+ public void calculateCenter() {
+ if (center == null) {
+ center = new Vector3f(pointa);
+ } else {
+ center.set(pointa);
+ }
+ center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD);
+ }
+
+ /**
+ * calculateNormal generates the normal for this triangle
+ *
+ */
+ public void calculateNormal() {
+ if (normal == null) {
+ normal = new Vector3f(pointb);
+ } else {
+ normal.set(pointb);
+ }
+ normal.subtractLocal(pointa).crossLocal(pointc.x - pointa.x, pointc.y - pointa.y, pointc.z - pointa.z);
+ normal.normalizeLocal();
+ }
+
+ /**
+ * obtains the center point of this triangle (average of the three triangles)
+ * @return the center point.
+ */
+ public Vector3f getCenter() {
+ if (center == null) {
+ calculateCenter();
+ }
+ return center;
+ }
+
+ /**
+ * sets the center point of this triangle (average of the three triangles)
+ * @param center the center point.
+ */
+ public void setCenter(Vector3f center) {
+ this.center = center;
+ }
+
+ /**
+ * obtains the unit length normal vector of this triangle, if set or
+ * calculated
+ *
+ * @return the normal vector
+ */
+ public Vector3f getNormal() {
+ if (normal == null) {
+ calculateNormal();
+ }
+ return normal;
+ }
+
+ /**
+ * sets the normal vector of this triangle (to conform, must be unit length)
+ * @param normal the normal vector.
+ */
+ public void setNormal(Vector3f normal) {
+ this.normal = normal;
+ }
+
+ /**
+ * obtains the projection of the vertices relative to the line origin.
+ * @return the projection of the triangle.
+ */
+ public float getProjection() {
+ return this.projection;
+ }
+
+ /**
+ * sets the projection of the vertices relative to the line origin.
+ * @param projection the projection of the triangle.
+ */
+ public void setProjection(float projection) {
+ this.projection = projection;
+ }
+
+ /**
+ * obtains an index that this triangle represents if it is contained in a OBBTree.
+ * @return the index in an OBBtree
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * sets an index that this triangle represents if it is contained in a OBBTree.
+ * @param index the index in an OBBtree
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ public static Vector3f computeTriangleNormal(Vector3f v1, Vector3f v2, Vector3f v3, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f(v2);
+ } else {
+ store.set(v2);
+ }
+
+ store.subtractLocal(v1).crossLocal(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z);
+ return store.normalizeLocal();
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ e.getCapsule(this).write(pointa, "pointa", Vector3f.ZERO);
+ e.getCapsule(this).write(pointb, "pointb", Vector3f.ZERO);
+ e.getCapsule(this).write(pointc, "pointc", Vector3f.ZERO);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ pointa = (Vector3f) e.getCapsule(this).readSavable("pointa", Vector3f.ZERO.clone());
+ pointb = (Vector3f) e.getCapsule(this).readSavable("pointb", Vector3f.ZERO.clone());
+ pointc = (Vector3f) e.getCapsule(this).readSavable("pointc", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public Triangle clone() {
+ try {
+ Triangle t = (Triangle) super.clone();
+ t.pointa = pointa.clone();
+ t.pointb = pointb.clone();
+ t.pointc = pointc.clone();
+ return t;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Vector2f.java b/engine/src/core/com/jme3/math/Vector2f.java
new file mode 100644
index 0000000..c2d8c6f
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector2f.java
@@ -0,0 +1,757 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.logging.Logger;
+
+/**
+ * <code>Vector2f</code> defines a Vector for a two float value vector.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Vector2f implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+ private static final Logger logger = Logger.getLogger(Vector2f.class.getName());
+
+ public static final Vector2f ZERO = new Vector2f(0f, 0f);
+ public static final Vector2f UNIT_XY = new Vector2f(1f, 1f);
+
+ /**
+ * the x value of the vector.
+ */
+ public float x;
+ /**
+ * the y value of the vector.
+ */
+ public float y;
+
+ /**
+ * Creates a Vector2f with the given initial x and y values.
+ *
+ * @param x
+ * The x value of this Vector2f.
+ * @param y
+ * The y value of this Vector2f.
+ */
+ public Vector2f(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0).
+ */
+ public Vector2f() {
+ x = y = 0;
+ }
+
+ /**
+ * Creates a new Vector2f that contains the passed vector's information
+ *
+ * @param vector2f
+ * The vector to copy
+ */
+ public Vector2f(Vector2f vector2f) {
+ this.x = vector2f.x;
+ this.y = vector2f.y;
+ }
+
+ /**
+ * set the x and y values of the vector
+ *
+ * @param x
+ * the x value of the vector.
+ * @param y
+ * the y value of the vector.
+ * @return this vector
+ */
+ public Vector2f set(float x, float y) {
+ this.x = x;
+ this.y = y;
+ return this;
+ }
+
+ /**
+ * set the x and y values of the vector from another vector
+ *
+ * @param vec
+ * the vector to copy from
+ * @return this vector
+ */
+ public Vector2f set(Vector2f vec) {
+ this.x = vec.x;
+ this.y = vec.y;
+ return this;
+ }
+
+ /**
+ * <code>add</code> adds a provided vector to this vector creating a
+ * resultant vector which is returned. If the provided vector is null, null
+ * is returned.
+ *
+ * @param vec
+ * the vector to add to this.
+ * @return the resultant vector.
+ */
+ public Vector2f add(Vector2f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ return new Vector2f(x + vec.x, y + vec.y);
+ }
+
+ /**
+ * <code>addLocal</code> adds a provided vector to this vector internally,
+ * and returns a handle to this vector for easy chaining of calls. If the
+ * provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to add to this vector.
+ * @return this
+ */
+ public Vector2f addLocal(Vector2f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x += vec.x;
+ y += vec.y;
+ return this;
+ }
+
+ /**
+ * <code>addLocal</code> adds the provided values to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param addX
+ * value to add to x
+ * @param addY
+ * value to add to y
+ * @return this
+ */
+ public Vector2f addLocal(float addX, float addY) {
+ x += addX;
+ y += addY;
+ return this;
+ }
+
+ /**
+ * <code>add</code> adds this vector by <code>vec</code> and stores the
+ * result in <code>result</code>.
+ *
+ * @param vec
+ * The vector to add.
+ * @param result
+ * The vector to store the result in.
+ * @return The result vector, after adding.
+ */
+ public Vector2f add(Vector2f vec, Vector2f result) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ if (result == null)
+ result = new Vector2f();
+ result.x = x + vec.x;
+ result.y = y + vec.y;
+ return result;
+ }
+
+ /**
+ * <code>dot</code> calculates the dot product of this vector with a
+ * provided vector. If the provided vector is null, 0 is returned.
+ *
+ * @param vec
+ * the vector to dot with this vector.
+ * @return the resultant dot product of this vector and a given vector.
+ */
+ public float dot(Vector2f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, 0 returned.");
+ return 0;
+ }
+ return x * vec.x + y * vec.y;
+ }
+
+ /**
+ * <code>cross</code> calculates the cross product of this vector with a
+ * parameter vector v.
+ *
+ * @param v
+ * the vector to take the cross product of with this.
+ * @return the cross product vector.
+ */
+ public Vector3f cross(Vector2f v) {
+ return new Vector3f(0, 0, determinant(v));
+ }
+
+ public float determinant(Vector2f v) {
+ return (x * v.y) - (y * v.x);
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from this to the
+ * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec
+ *
+ * @param finalVec
+ * The final vector to interpolate towards
+ * @param changeAmnt
+ * An amount between 0.0 - 1.0 representing a percentage change
+ * from this towards finalVec
+ */
+ public Vector2f interpolate(Vector2f finalVec, float changeAmnt) {
+ this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x;
+ this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y;
+ return this;
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from beginVec to
+ * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+ *
+ * @param beginVec
+ * The begining vector (delta=0)
+ * @param finalVec
+ * The final vector to interpolate towards (delta=1)
+ * @param changeAmnt
+ * An amount between 0.0 - 1.0 representing a precentage change
+ * from beginVec towards finalVec
+ */
+ public Vector2f interpolate(Vector2f beginVec, Vector2f finalVec,
+ float changeAmnt) {
+ this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
+ this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
+ return this;
+ }
+
+ /**
+ * Check a vector... if it is null or its floats are NaN or infinite, return
+ * false. Else return true.
+ *
+ * @param vector
+ * the vector to check
+ * @return true or false as stated above.
+ */
+ public static boolean isValidVector(Vector2f vector) {
+ if (vector == null) return false;
+ if (Float.isNaN(vector.x) ||
+ Float.isNaN(vector.y)) return false;
+ if (Float.isInfinite(vector.x) ||
+ Float.isInfinite(vector.y)) return false;
+ return true;
+ }
+
+ /**
+ * <code>length</code> calculates the magnitude of this vector.
+ *
+ * @return the length or magnitude of the vector.
+ */
+ public float length() {
+ return FastMath.sqrt(lengthSquared());
+ }
+
+ /**
+ * <code>lengthSquared</code> calculates the squared value of the
+ * magnitude of the vector.
+ *
+ * @return the magnitude squared of the vector.
+ */
+ public float lengthSquared() {
+ return x * x + y * y;
+ }
+
+ /**
+ * <code>distanceSquared</code> calculates the distance squared between
+ * this vector and vector v.
+ *
+ * @param v the second vector to determine the distance squared.
+ * @return the distance squared between the two vectors.
+ */
+ public float distanceSquared(Vector2f v) {
+ double dx = x - v.x;
+ double dy = y - v.y;
+ return (float) (dx * dx + dy * dy);
+ }
+
+ /**
+ * <code>distanceSquared</code> calculates the distance squared between
+ * this vector and vector v.
+ *
+ * @param otherX The X coordinate of the v vector
+ * @param otherY The Y coordinate of the v vector
+ * @return the distance squared between the two vectors.
+ */
+ public float distanceSquared(float otherX, float otherY) {
+ double dx = x - otherX;
+ double dy = y - otherY;
+ return (float) (dx * dx + dy * dy);
+ }
+
+ /**
+ * <code>distance</code> calculates the distance between this vector and
+ * vector v.
+ *
+ * @param v the second vector to determine the distance.
+ * @return the distance between the two vectors.
+ */
+ public float distance(Vector2f v) {
+ return FastMath.sqrt(distanceSquared(v));
+ }
+
+ /**
+ * <code>mult</code> multiplies this vector by a scalar. The resultant
+ * vector is returned.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return the new vector.
+ */
+ public Vector2f mult(float scalar) {
+ return new Vector2f(x * scalar, y * scalar);
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return this
+ */
+ public Vector2f multLocal(float scalar) {
+ x *= scalar;
+ y *= scalar;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @return this
+ */
+ public Vector2f multLocal(Vector2f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x *= vec.x;
+ y *= vec.y;
+ return this;
+ }
+
+ /**
+ * Multiplies this Vector2f's x and y by the scalar and stores the result in
+ * product. The result is returned for chaining. Similar to
+ * product=this*scalar;
+ *
+ * @param scalar
+ * The scalar to multiply by.
+ * @param product
+ * The vector2f to store the result in.
+ * @return product, after multiplication.
+ */
+ public Vector2f mult(float scalar, Vector2f product) {
+ if (null == product) {
+ product = new Vector2f();
+ }
+
+ product.x = x * scalar;
+ product.y = y * scalar;
+ return product;
+ }
+
+ /**
+ * <code>divide</code> divides the values of this vector by a scalar and
+ * returns the result. The values of this vector remain untouched.
+ *
+ * @param scalar
+ * the value to divide this vectors attributes by.
+ * @return the result <code>Vector</code>.
+ */
+ public Vector2f divide(float scalar) {
+ return new Vector2f(x / scalar, y / scalar);
+ }
+
+ /**
+ * <code>divideLocal</code> divides this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls. Dividing
+ * by zero will result in an exception.
+ *
+ * @param scalar
+ * the value to divides this vector by.
+ * @return this
+ */
+ public Vector2f divideLocal(float scalar) {
+ x /= scalar;
+ y /= scalar;
+ return this;
+ }
+
+ /**
+ * <code>negate</code> returns the negative of this vector. All values are
+ * negated and set to a new vector.
+ *
+ * @return the negated vector.
+ */
+ public Vector2f negate() {
+ return new Vector2f(-x, -y);
+ }
+
+ /**
+ * <code>negateLocal</code> negates the internal values of this vector.
+ *
+ * @return this.
+ */
+ public Vector2f negateLocal() {
+ x = -x;
+ y = -y;
+ return this;
+ }
+
+ /**
+ * <code>subtract</code> subtracts the values of a given vector from those
+ * of this vector creating a new vector object. If the provided vector is
+ * null, an exception is thrown.
+ *
+ * @param vec
+ * the vector to subtract from this vector.
+ * @return the result vector.
+ */
+ public Vector2f subtract(Vector2f vec) {
+ return subtract(vec, null);
+ }
+
+ /**
+ * <code>subtract</code> subtracts the values of a given vector from those
+ * of this vector storing the result in the given vector object. If the
+ * provided vector is null, an exception is thrown.
+ *
+ * @param vec
+ * the vector to subtract from this vector.
+ * @param store
+ * the vector to store the result in. It is safe for this to be
+ * the same as vec. If null, a new vector is created.
+ * @return the result vector.
+ */
+ public Vector2f subtract(Vector2f vec, Vector2f store) {
+ if (store == null)
+ store = new Vector2f();
+ store.x = x - vec.x;
+ store.y = y - vec.y;
+ return store;
+ }
+
+ /**
+ * <code>subtract</code> subtracts the given x,y values from those of this
+ * vector creating a new vector object.
+ *
+ * @param valX
+ * value to subtract from x
+ * @param valY
+ * value to subtract from y
+ * @return this
+ */
+ public Vector2f subtract(float valX, float valY) {
+ return new Vector2f(x - valX, y - valY);
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to subtract
+ * @return this
+ */
+ public Vector2f subtractLocal(Vector2f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x -= vec.x;
+ y -= vec.y;
+ return this;
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts the provided values from this
+ * vector internally, and returns a handle to this vector for easy chaining
+ * of calls.
+ *
+ * @param valX
+ * value to subtract from x
+ * @param valY
+ * value to subtract from y
+ * @return this
+ */
+ public Vector2f subtractLocal(float valX, float valY) {
+ x -= valX;
+ y -= valY;
+ return this;
+ }
+
+ /**
+ * <code>normalize</code> returns the unit vector of this vector.
+ *
+ * @return unit vector of this vector.
+ */
+ public Vector2f normalize() {
+ float length = length();
+ if (length != 0) {
+ return divide(length);
+ }
+
+ return divide(1);
+ }
+
+ /**
+ * <code>normalizeLocal</code> makes this vector into a unit vector of
+ * itself.
+ *
+ * @return this.
+ */
+ public Vector2f normalizeLocal() {
+ float length = length();
+ if (length != 0) {
+ return divideLocal(length);
+ }
+
+ return divideLocal(1);
+ }
+
+ /**
+ * <code>smallestAngleBetween</code> returns (in radians) the minimum
+ * angle between two vectors. It is assumed that both this vector and the
+ * given vector are unit vectors (iow, normalized).
+ *
+ * @param otherVector
+ * a unit vector to find the angle against
+ * @return the angle in radians.
+ */
+ public float smallestAngleBetween(Vector2f otherVector) {
+ float dotProduct = dot(otherVector);
+ float angle = FastMath.acos(dotProduct);
+ return angle;
+ }
+
+ /**
+ * <code>angleBetween</code> returns (in radians) the angle required to
+ * rotate a ray represented by this vector to lie colinear to a ray
+ * described by the given vector. It is assumed that both this vector and
+ * the given vector are unit vectors (iow, normalized).
+ *
+ * @param otherVector
+ * the "destination" unit vector
+ * @return the angle in radians.
+ */
+ public float angleBetween(Vector2f otherVector) {
+ float angle = FastMath.atan2(otherVector.y, otherVector.x)
+ - FastMath.atan2(y, x);
+ return angle;
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public Vector2f setX(float x) {
+ this.x = x;
+ return this;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public Vector2f setY(float y) {
+ this.y = y;
+ return this;
+ }
+ /**
+ * <code>getAngle</code> returns (in radians) the angle represented by
+ * this Vector2f as expressed by a conversion from rectangular coordinates (<code>x</code>,&nbsp;<code>y</code>)
+ * to polar coordinates (r,&nbsp;<i>theta</i>).
+ *
+ * @return the angle in radians. [-pi, pi)
+ */
+ public float getAngle() {
+ return FastMath.atan2(y, x);
+ }
+
+ /**
+ * <code>zero</code> resets this vector's data to zero internally.
+ */
+ public Vector2f zero() {
+ x = y = 0;
+ return this;
+ }
+
+ /**
+ * <code>hashCode</code> returns a unique code for this vector object
+ * based on it's values. If two vectors are logically equivalent, they will
+ * return the same hash code value.
+ *
+ * @return the hash code value of this vector.
+ */
+ public int hashCode() {
+ int hash = 37;
+ hash += 37 * hash + Float.floatToIntBits(x);
+ hash += 37 * hash + Float.floatToIntBits(y);
+ return hash;
+ }
+
+ @Override
+ public Vector2f clone() {
+ try {
+ return (Vector2f) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+
+ /**
+ * Saves this Vector2f into the given float[] object.
+ *
+ * @param floats
+ * The float[] to take this Vector2f. If null, a new float[2] is
+ * created.
+ * @return The array, with X, Y float values in that order
+ */
+ public float[] toArray(float[] floats) {
+ if (floats == null) {
+ floats = new float[2];
+ }
+ floats[0] = x;
+ floats[1] = y;
+ return floats;
+ }
+
+ /**
+ * are these two vectors the same? they are is they both have the same x and
+ * y values.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof Vector2f)) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ Vector2f comp = (Vector2f) o;
+ if (Float.compare(x, comp.x) != 0)
+ return false;
+ if (Float.compare(y, comp.y) != 0)
+ return false;
+ return true;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this vector
+ * object. The format of the string is such: com.jme.math.Vector2f
+ * [X=XX.XXXX, Y=YY.YYYY]
+ *
+ * @return the string representation of this vector.
+ */
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param in
+ * ObjectInput
+ * @throws IOException
+ * @throws ClassNotFoundException
+ * @see java.io.Externalizable
+ */
+ public void readExternal(ObjectInput in) throws IOException,
+ ClassNotFoundException {
+ x = in.readFloat();
+ y = in.readFloat();
+ }
+
+ /**
+ * Used with serialization. Not to be called manually.
+ *
+ * @param out
+ * ObjectOutput
+ * @throws IOException
+ * @see java.io.Externalizable
+ */
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeFloat(x);
+ out.writeFloat(y);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(x, "x", 0);
+ capsule.write(y, "y", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ x = capsule.readFloat("x", 0);
+ y = capsule.readFloat("y", 0);
+ }
+
+ public void rotateAroundOrigin(float angle, boolean cw) {
+ if (cw)
+ angle = -angle;
+ float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y;
+ float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y;
+ x = newX;
+ y = newY;
+ }
+}
diff --git a/engine/src/core/com/jme3/math/Vector3f.java b/engine/src/core/com/jme3/math/Vector3f.java
new file mode 100644
index 0000000..9f6f851
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector3f.java
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/*
+ * -- Added *Local methods to cut down on object creation - JS
+ */
+
+/**
+ * <code>Vector3f</code> defines a Vector for a three float value tuple.
+ * <code>Vector3f</code> can represent any three dimensional value, such as a
+ * vertex, a normal, etc. Utility methods are also included to aid in
+ * mathematical calculations.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public final class Vector3f implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Vector3f.class.getName());
+
+ public final static Vector3f ZERO = new Vector3f(0, 0, 0);
+ public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN);
+ public final static Vector3f UNIT_X = new Vector3f(1, 0, 0);
+ public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0);
+ public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1);
+ public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1);
+ public final static Vector3f POSITIVE_INFINITY = new Vector3f(
+ Float.POSITIVE_INFINITY,
+ Float.POSITIVE_INFINITY,
+ Float.POSITIVE_INFINITY);
+ public final static Vector3f NEGATIVE_INFINITY = new Vector3f(
+ Float.NEGATIVE_INFINITY,
+ Float.NEGATIVE_INFINITY,
+ Float.NEGATIVE_INFINITY);
+
+
+ /**
+ * the x value of the vector.
+ */
+ public float x;
+
+ /**
+ * the y value of the vector.
+ */
+ public float y;
+
+ /**
+ * the z value of the vector.
+ */
+ public float z;
+
+ /**
+ * Constructor instantiates a new <code>Vector3f</code> with default
+ * values of (0,0,0).
+ *
+ */
+ public Vector3f() {
+ x = y = z = 0;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Vector3f</code> with provides
+ * values.
+ *
+ * @param x
+ * the x value of the vector.
+ * @param y
+ * the y value of the vector.
+ * @param z
+ * the z value of the vector.
+ */
+ public Vector3f(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Vector3f</code> that is a copy
+ * of the provided vector
+ * @param copy The Vector3f to copy
+ */
+ public Vector3f(Vector3f copy) {
+ this.set(copy);
+ }
+
+ /**
+ * <code>set</code> sets the x,y,z values of the vector based on passed
+ * parameters.
+ *
+ * @param x
+ * the x value of the vector.
+ * @param y
+ * the y value of the vector.
+ * @param z
+ * the z value of the vector.
+ * @return this vector
+ */
+ public Vector3f set(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ return this;
+ }
+
+ /**
+ * <code>set</code> sets the x,y,z values of the vector by copying the
+ * supplied vector.
+ *
+ * @param vect
+ * the vector to copy.
+ * @return this vector
+ */
+ public Vector3f set(Vector3f vect) {
+ this.x = vect.x;
+ this.y = vect.y;
+ this.z = vect.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>add</code> adds a provided vector to this vector creating a
+ * resultant vector which is returned. If the provided vector is null, null
+ * is returned.
+ *
+ * @param vec
+ * the vector to add to this.
+ * @return the resultant vector.
+ */
+ public Vector3f add(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ return new Vector3f(x + vec.x, y + vec.y, z + vec.z);
+ }
+
+ /**
+ *
+ * <code>add</code> adds the values of a provided vector storing the
+ * values in the supplied vector.
+ *
+ * @param vec
+ * the vector to add to this
+ * @param result
+ * the vector to store the result in
+ * @return result returns the supplied result vector.
+ */
+ public Vector3f add(Vector3f vec, Vector3f result) {
+ result.x = x + vec.x;
+ result.y = y + vec.y;
+ result.z = z + vec.z;
+ return result;
+ }
+
+ /**
+ * <code>addLocal</code> adds a provided vector to this vector internally,
+ * and returns a handle to this vector for easy chaining of calls. If the
+ * provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to add to this vector.
+ * @return this
+ */
+ public Vector3f addLocal(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x += vec.x;
+ y += vec.y;
+ z += vec.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>add</code> adds the provided values to this vector, creating a
+ * new vector that is then returned.
+ *
+ * @param addX
+ * the x value to add.
+ * @param addY
+ * the y value to add.
+ * @param addZ
+ * the z value to add.
+ * @return the result vector.
+ */
+ public Vector3f add(float addX, float addY, float addZ) {
+ return new Vector3f(x + addX, y + addY, z + addZ);
+ }
+
+ /**
+ * <code>addLocal</code> adds the provided values to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param addX
+ * value to add to x
+ * @param addY
+ * value to add to y
+ * @param addZ
+ * value to add to z
+ * @return this
+ */
+ public Vector3f addLocal(float addX, float addY, float addZ) {
+ x += addX;
+ y += addY;
+ z += addZ;
+ return this;
+ }
+
+ /**
+ *
+ * <code>scaleAdd</code> multiplies this vector by a scalar then adds the
+ * given Vector3f.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @param add
+ * the value to add
+ */
+ public Vector3f scaleAdd(float scalar, Vector3f add) {
+ x = x * scalar + add.x;
+ y = y * scalar + add.y;
+ z = z * scalar + add.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>scaleAdd</code> multiplies the given vector by a scalar then adds
+ * the given vector.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @param mult
+ * the value to multiply the scalar by
+ * @param add
+ * the value to add
+ */
+ public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) {
+ this.x = mult.x * scalar + add.x;
+ this.y = mult.y * scalar + add.y;
+ this.z = mult.z * scalar + add.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>dot</code> calculates the dot product of this vector with a
+ * provided vector. If the provided vector is null, 0 is returned.
+ *
+ * @param vec
+ * the vector to dot with this vector.
+ * @return the resultant dot product of this vector and a given vector.
+ */
+ public float dot(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, 0 returned.");
+ return 0;
+ }
+ return x * vec.x + y * vec.y + z * vec.z;
+ }
+
+ /**
+ * <code>cross</code> calculates the cross product of this vector with a
+ * parameter vector v.
+ *
+ * @param v
+ * the vector to take the cross product of with this.
+ * @return the cross product vector.
+ */
+ public Vector3f cross(Vector3f v) {
+ return cross(v, null);
+ }
+
+ /**
+ * <code>cross</code> calculates the cross product of this vector with a
+ * parameter vector v. The result is stored in <code>result</code>
+ *
+ * @param v
+ * the vector to take the cross product of with this.
+ * @param result
+ * the vector to store the cross product result.
+ * @return result, after recieving the cross product vector.
+ */
+ public Vector3f cross(Vector3f v,Vector3f result) {
+ return cross(v.x, v.y, v.z, result);
+ }
+
+ /**
+ * <code>cross</code> calculates the cross product of this vector with a
+ * parameter vector v. The result is stored in <code>result</code>
+ *
+ * @param otherX
+ * x component of the vector to take the cross product of with this.
+ * @param otherY
+ * y component of the vector to take the cross product of with this.
+ * @param otherZ
+ * z component of the vector to take the cross product of with this.
+ * @param result
+ * the vector to store the cross product result.
+ * @return result, after recieving the cross product vector.
+ */
+ public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) {
+ if (result == null) result = new Vector3f();
+ float resX = ((y * otherZ) - (z * otherY));
+ float resY = ((z * otherX) - (x * otherZ));
+ float resZ = ((x * otherY) - (y * otherX));
+ result.set(resX, resY, resZ);
+ return result;
+ }
+
+ /**
+ * <code>crossLocal</code> calculates the cross product of this vector
+ * with a parameter vector v.
+ *
+ * @param v
+ * the vector to take the cross product of with this.
+ * @return this.
+ */
+ public Vector3f crossLocal(Vector3f v) {
+ return crossLocal(v.x, v.y, v.z);
+ }
+
+ /**
+ * <code>crossLocal</code> calculates the cross product of this vector
+ * with a parameter vector v.
+ *
+ * @param otherX
+ * x component of the vector to take the cross product of with this.
+ * @param otherY
+ * y component of the vector to take the cross product of with this.
+ * @param otherZ
+ * z component of the vector to take the cross product of with this.
+ * @return this.
+ */
+ public Vector3f crossLocal(float otherX, float otherY, float otherZ) {
+ float tempx = ( y * otherZ ) - ( z * otherY );
+ float tempy = ( z * otherX ) - ( x * otherZ );
+ z = (x * otherY) - (y * otherX);
+ x = tempx;
+ y = tempy;
+ return this;
+ }
+
+ public Vector3f project(Vector3f other){
+ float n = this.dot(other); // A . B
+ float d = other.lengthSquared(); // |B|^2
+ return new Vector3f(other).normalizeLocal().multLocal(n/d);
+ }
+
+ /**
+ * Returns true if this vector is a unit vector (length() ~= 1),
+ * returns false otherwise.
+ *
+ * @return true if this vector is a unit vector (length() ~= 1),
+ * or false otherwise.
+ */
+ public boolean isUnitVector(){
+ float len = length();
+ return 0.99f < len && len < 1.01f;
+ }
+
+ /**
+ * <code>length</code> calculates the magnitude of this vector.
+ *
+ * @return the length or magnitude of the vector.
+ */
+ public float length() {
+ return FastMath.sqrt(lengthSquared());
+ }
+
+ /**
+ * <code>lengthSquared</code> calculates the squared value of the
+ * magnitude of the vector.
+ *
+ * @return the magnitude squared of the vector.
+ */
+ public float lengthSquared() {
+ return x * x + y * y + z * z;
+ }
+
+ /**
+ * <code>distanceSquared</code> calculates the distance squared between
+ * this vector and vector v.
+ *
+ * @param v the second vector to determine the distance squared.
+ * @return the distance squared between the two vectors.
+ */
+ public float distanceSquared(Vector3f v) {
+ double dx = x - v.x;
+ double dy = y - v.y;
+ double dz = z - v.z;
+ return (float) (dx * dx + dy * dy + dz * dz);
+ }
+
+ /**
+ * <code>distance</code> calculates the distance between this vector and
+ * vector v.
+ *
+ * @param v the second vector to determine the distance.
+ * @return the distance between the two vectors.
+ */
+ public float distance(Vector3f v) {
+ return FastMath.sqrt(distanceSquared(v));
+ }
+
+ /**
+ *
+ * <code>mult</code> multiplies this vector by a scalar. The resultant
+ * vector is returned.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return the new vector.
+ */
+ public Vector3f mult(float scalar) {
+ return new Vector3f(x * scalar, y * scalar, z * scalar);
+ }
+
+ /**
+ *
+ * <code>mult</code> multiplies this vector by a scalar. The resultant
+ * vector is supplied as the second parameter and returned.
+ *
+ * @param scalar the scalar to multiply this vector by.
+ * @param product the product to store the result in.
+ * @return product
+ */
+ public Vector3f mult(float scalar, Vector3f product) {
+ if (null == product) {
+ product = new Vector3f();
+ }
+
+ product.x = x * scalar;
+ product.y = y * scalar;
+ product.z = z * scalar;
+ return product;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return this
+ */
+ public Vector3f multLocal(float scalar) {
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @return this
+ */
+ public Vector3f multLocal(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x *= vec.x;
+ y *= vec.y;
+ z *= vec.z;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this vector by 3 scalars
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return this
+ */
+ public Vector3f multLocal(float x, float y, float z) {
+ this.x *= x;
+ this.y *= y;
+ this.z *= z;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @return this
+ */
+ public Vector3f mult(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ return mult(vec, null);
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @param store result vector (null to create a new vector)
+ * @return this
+ */
+ public Vector3f mult(Vector3f vec, Vector3f store) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ if (store == null) store = new Vector3f();
+ return store.set(x * vec.x, y * vec.y, z * vec.z);
+ }
+
+
+ /**
+ * <code>divide</code> divides the values of this vector by a scalar and
+ * returns the result. The values of this vector remain untouched.
+ *
+ * @param scalar
+ * the value to divide this vectors attributes by.
+ * @return the result <code>Vector</code>.
+ */
+ public Vector3f divide(float scalar) {
+ scalar = 1f/scalar;
+ return new Vector3f(x * scalar, y * scalar, z * scalar);
+ }
+
+ /**
+ * <code>divideLocal</code> divides this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls. Dividing
+ * by zero will result in an exception.
+ *
+ * @param scalar
+ * the value to divides this vector by.
+ * @return this
+ */
+ public Vector3f divideLocal(float scalar) {
+ scalar = 1f/scalar;
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ return this;
+ }
+
+
+ /**
+ * <code>divide</code> divides the values of this vector by a scalar and
+ * returns the result. The values of this vector remain untouched.
+ *
+ * @param scalar
+ * the value to divide this vectors attributes by.
+ * @return the result <code>Vector</code>.
+ */
+ public Vector3f divide(Vector3f scalar) {
+ return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z);
+ }
+
+ /**
+ * <code>divideLocal</code> divides this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls. Dividing
+ * by zero will result in an exception.
+ *
+ * @param scalar
+ * the value to divides this vector by.
+ * @return this
+ */
+ public Vector3f divideLocal(Vector3f scalar) {
+ x /= scalar.x;
+ y /= scalar.y;
+ z /= scalar.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>negate</code> returns the negative of this vector. All values are
+ * negated and set to a new vector.
+ *
+ * @return the negated vector.
+ */
+ public Vector3f negate() {
+ return new Vector3f(-x, -y, -z);
+ }
+
+ /**
+ *
+ * <code>negateLocal</code> negates the internal values of this vector.
+ *
+ * @return this.
+ */
+ public Vector3f negateLocal() {
+ x = -x;
+ y = -y;
+ z = -z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>subtract</code> subtracts the values of a given vector from those
+ * of this vector creating a new vector object. If the provided vector is
+ * null, null is returned.
+ *
+ * @param vec
+ * the vector to subtract from this vector.
+ * @return the result vector.
+ */
+ public Vector3f subtract(Vector3f vec) {
+ return new Vector3f(x - vec.x, y - vec.y, z - vec.z);
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to subtract
+ * @return this
+ */
+ public Vector3f subtractLocal(Vector3f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x -= vec.x;
+ y -= vec.y;
+ z -= vec.z;
+ return this;
+ }
+
+ /**
+ *
+ * <code>subtract</code>
+ *
+ * @param vec
+ * the vector to subtract from this
+ * @param result
+ * the vector to store the result in
+ * @return result
+ */
+ public Vector3f subtract(Vector3f vec, Vector3f result) {
+ if(result == null) {
+ result = new Vector3f();
+ }
+ result.x = x - vec.x;
+ result.y = y - vec.y;
+ result.z = z - vec.z;
+ return result;
+ }
+
+ /**
+ *
+ * <code>subtract</code> subtracts the provided values from this vector,
+ * creating a new vector that is then returned.
+ *
+ * @param subtractX
+ * the x value to subtract.
+ * @param subtractY
+ * the y value to subtract.
+ * @param subtractZ
+ * the z value to subtract.
+ * @return the result vector.
+ */
+ public Vector3f subtract(float subtractX, float subtractY, float subtractZ) {
+ return new Vector3f(x - subtractX, y - subtractY, z - subtractZ);
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts the provided values from this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param subtractX
+ * the x value to subtract.
+ * @param subtractY
+ * the y value to subtract.
+ * @param subtractZ
+ * the z value to subtract.
+ * @return this
+ */
+ public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) {
+ x -= subtractX;
+ y -= subtractY;
+ z -= subtractZ;
+ return this;
+ }
+
+ /**
+ * <code>normalize</code> returns the unit vector of this vector.
+ *
+ * @return unit vector of this vector.
+ */
+ public Vector3f normalize() {
+// float length = length();
+// if (length != 0) {
+// return divide(length);
+// }
+//
+// return divide(1);
+ float length = x * x + y * y + z * z;
+ if (length != 1f && length != 0f){
+ length = 1.0f / FastMath.sqrt(length);
+ return new Vector3f(x * length, y * length, z * length);
+ }
+ return clone();
+ }
+
+ /**
+ * <code>normalizeLocal</code> makes this vector into a unit vector of
+ * itself.
+ *
+ * @return this.
+ */
+ public Vector3f normalizeLocal() {
+ // NOTE: this implementation is more optimized
+ // than the old jme normalize as this method
+ // is commonly used.
+ float length = x * x + y * y + z * z;
+ if (length != 1f && length != 0f){
+ length = 1.0f / FastMath.sqrt(length);
+ x *= length;
+ y *= length;
+ z *= length;
+ }
+ return this;
+ }
+
+ /**
+ * <code>maxLocal</code> computes the maximum value for each
+ * component in this and <code>other</code> vector. The result is stored
+ * in this vector.
+ * @param other
+ */
+ public void maxLocal(Vector3f other){
+ x = other.x > x ? other.x : x;
+ y = other.y > y ? other.y : y;
+ z = other.z > z ? other.z : z;
+ }
+
+ /**
+ * <code>minLocal</code> computes the minimum value for each
+ * component in this and <code>other</code> vector. The result is stored
+ * in this vector.
+ * @param other
+ */
+ public void minLocal(Vector3f other){
+ x = other.x < x ? other.x : x;
+ y = other.y < y ? other.y : y;
+ z = other.z < z ? other.z : z;
+ }
+
+ /**
+ * <code>zero</code> resets this vector's data to zero internally.
+ */
+ public Vector3f zero() {
+ x = y = z = 0;
+ return this;
+ }
+
+ /**
+ * <code>angleBetween</code> returns (in radians) the angle between two vectors.
+ * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
+ *
+ * @param otherVector a unit vector to find the angle against
+ * @return the angle in radians.
+ */
+ public float angleBetween(Vector3f otherVector) {
+ float dotProduct = dot(otherVector);
+ float angle = FastMath.acos(dotProduct);
+ return angle;
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from this to the finalVec
+ * this=(1-changeAmnt)*this + changeAmnt * finalVec
+ * @param finalVec The final vector to interpolate towards
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from this towards finalVec
+ */
+ public Vector3f interpolate(Vector3f finalVec, float changeAmnt) {
+ this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x;
+ this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y;
+ this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z;
+ return this;
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec
+ * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+ * @param beginVec the beging vector (changeAmnt=0)
+ * @param finalVec The final vector to interpolate towards
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from beginVec towards finalVec
+ */
+ public Vector3f interpolate(Vector3f beginVec,Vector3f finalVec, float changeAmnt) {
+ this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x;
+ this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y;
+ this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z;
+ return this;
+ }
+
+ /**
+ * Check a vector... if it is null or its floats are NaN or infinite,
+ * return false. Else return true.
+ * @param vector the vector to check
+ * @return true or false as stated above.
+ */
+ public static boolean isValidVector(Vector3f vector) {
+ if (vector == null) return false;
+ if (Float.isNaN(vector.x) ||
+ Float.isNaN(vector.y) ||
+ Float.isNaN(vector.z)) return false;
+ if (Float.isInfinite(vector.x) ||
+ Float.isInfinite(vector.y) ||
+ Float.isInfinite(vector.z)) return false;
+ return true;
+ }
+
+ public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) {
+ w.normalizeLocal();
+ generateComplementBasis(u, v, w);
+ }
+
+ public static void generateComplementBasis(Vector3f u, Vector3f v,
+ Vector3f w) {
+ float fInvLength;
+
+ if (FastMath.abs(w.x) >= FastMath.abs(w.y)) {
+ // w.x or w.z is the largest magnitude component, swap them
+ fInvLength = FastMath.invSqrt(w.x * w.x + w.z * w.z);
+ u.x = -w.z * fInvLength;
+ u.y = 0.0f;
+ u.z = +w.x * fInvLength;
+ v.x = w.y * u.z;
+ v.y = w.z * u.x - w.x * u.z;
+ v.z = -w.y * u.x;
+ } else {
+ // w.y or w.z is the largest magnitude component, swap them
+ fInvLength = FastMath.invSqrt(w.y * w.y + w.z * w.z);
+ u.x = 0.0f;
+ u.y = +w.z * fInvLength;
+ u.z = -w.y * fInvLength;
+ v.x = w.y * u.z - w.z * u.y;
+ v.y = -w.x * u.z;
+ v.z = w.x * u.y;
+ }
+ }
+
+ @Override
+ public Vector3f clone() {
+ try {
+ return (Vector3f) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+
+ /**
+ * Saves this Vector3f into the given float[] object.
+ *
+ * @param floats
+ * The float[] to take this Vector3f. If null, a new float[3] is
+ * created.
+ * @return The array, with X, Y, Z float values in that order
+ */
+ public float[] toArray(float[] floats) {
+ if (floats == null) {
+ floats = new float[3];
+ }
+ floats[0] = x;
+ floats[1] = y;
+ floats[2] = z;
+ return floats;
+ }
+
+ /**
+ * are these two vectors the same? they are is they both have the same x,y,
+ * and z values.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof Vector3f)) { return false; }
+
+ if (this == o) { return true; }
+
+ Vector3f comp = (Vector3f) o;
+ if (Float.compare(x,comp.x) != 0) return false;
+ if (Float.compare(y,comp.y) != 0) return false;
+ if (Float.compare(z,comp.z) != 0) return false;
+ return true;
+ }
+
+ /**
+ * <code>hashCode</code> returns a unique code for this vector object based
+ * on it's values. If two vectors are logically equivalent, they will return
+ * the same hash code value.
+ * @return the hash code value of this vector.
+ */
+ public int hashCode() {
+ int hash = 37;
+ hash += 37 * hash + Float.floatToIntBits(x);
+ hash += 37 * hash + Float.floatToIntBits(y);
+ hash += 37 * hash + Float.floatToIntBits(z);
+ return hash;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this vector.
+ * The format is:
+ *
+ * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ]
+ *
+ * @return the string representation of this vector.
+ */
+ public String toString() {
+ return "(" + x + ", " + y + ", " + z + ")";
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(x, "x", 0);
+ capsule.write(y, "y", 0);
+ capsule.write(z, "z", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ x = capsule.readFloat("x", 0);
+ y = capsule.readFloat("y", 0);
+ z = capsule.readFloat("z", 0);
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public Vector3f setX(float x) {
+ this.x = x;
+ return this;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public Vector3f setY(float y) {
+ this.y = y;
+ return this;
+ }
+
+ public float getZ() {
+ return z;
+ }
+
+ public Vector3f setZ(float z) {
+ this.z = z;
+ return this;
+ }
+
+ /**
+ * @param index
+ * @return x value if index == 0, y value if index == 1 or z value if index ==
+ * 2
+ * @throws IllegalArgumentException
+ * if index is not one of 0, 1, 2.
+ */
+ public float get(int index) {
+ switch (index) {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ }
+ throw new IllegalArgumentException("index must be either 0, 1 or 2");
+ }
+
+ /**
+ * @param index
+ * which field index in this vector to set.
+ * @param value
+ * to set to one of x, y or z.
+ * @throws IllegalArgumentException
+ * if index is not one of 0, 1, 2.
+ */
+ public void set(int index, float value) {
+ switch (index) {
+ case 0:
+ x = value;
+ return;
+ case 1:
+ y = value;
+ return;
+ case 2:
+ z = value;
+ return;
+ }
+ throw new IllegalArgumentException("index must be either 0, 1 or 2");
+ }
+
+}
diff --git a/engine/src/core/com/jme3/math/Vector4f.java b/engine/src/core/com/jme3/math/Vector4f.java
new file mode 100644
index 0000000..d6e7ad7
--- /dev/null
+++ b/engine/src/core/com/jme3/math/Vector4f.java
@@ -0,0 +1,1003 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.math;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * <code>Vector4f</code> defines a Vector for a four float value tuple.
+ * <code>Vector4f</code> can represent any four dimensional value, such as a
+ * vertex, a normal, etc. Utility methods are also included to aid in
+ * mathematical calculations.
+ *
+ * @author Maarten Steur
+ */
+public final class Vector4f implements Savable, Cloneable, java.io.Serializable {
+
+ static final long serialVersionUID = 1;
+
+ private static final Logger logger = Logger.getLogger(Vector4f.class.getName());
+
+ public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0);
+ public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN);
+ public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0);
+ public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0);
+ public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0);
+ public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1);
+ public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1);
+ public final static Vector4f POSITIVE_INFINITY = new Vector4f(
+ Float.POSITIVE_INFINITY,
+ Float.POSITIVE_INFINITY,
+ Float.POSITIVE_INFINITY,
+ Float.POSITIVE_INFINITY);
+ public final static Vector4f NEGATIVE_INFINITY = new Vector4f(
+ Float.NEGATIVE_INFINITY,
+ Float.NEGATIVE_INFINITY,
+ Float.NEGATIVE_INFINITY,
+ Float.NEGATIVE_INFINITY);
+
+ /**
+ * the x value of the vector.
+ */
+ public float x;
+
+ /**
+ * the y value of the vector.
+ */
+ public float y;
+
+ /**
+ * the z value of the vector.
+ */
+ public float z;
+
+ /**
+ * the w value of the vector.
+ */
+ public float w;
+
+ /**
+ * Constructor instantiates a new <code>Vector3f</code> with default
+ * values of (0,0,0).
+ *
+ */
+ public Vector4f() {
+ x = y = z = w = 0;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Vector4f</code> with provides
+ * values.
+ *
+ * @param x
+ * the x value of the vector.
+ * @param y
+ * the y value of the vector.
+ * @param z
+ * the z value of the vector.
+ * @param w
+ * the w value of the vector.
+ */
+ public Vector4f(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Vector3f</code> that is a copy
+ * of the provided vector
+ * @param copy The Vector3f to copy
+ */
+ public Vector4f(Vector4f copy) {
+ this.set(copy);
+ }
+
+ /**
+ * <code>set</code> sets the x,y,z,w values of the vector based on passed
+ * parameters.
+ *
+ * @param x
+ * the x value of the vector.
+ * @param y
+ * the y value of the vector.
+ * @param z
+ * the z value of the vector.
+ * @param w
+ * the w value of the vector.
+ * @return this vector
+ */
+ public Vector4f set(float x, float y, float z, float w) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+ return this;
+ }
+
+ /**
+ * <code>set</code> sets the x,y,z values of the vector by copying the
+ * supplied vector.
+ *
+ * @param vect
+ * the vector to copy.
+ * @return this vector
+ */
+ public Vector4f set(Vector4f vect) {
+ this.x = vect.x;
+ this.y = vect.y;
+ this.z = vect.z;
+ this.w = vect.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>add</code> adds a provided vector to this vector creating a
+ * resultant vector which is returned. If the provided vector is null, null
+ * is returned.
+ *
+ * @param vec
+ * the vector to add to this.
+ * @return the resultant vector.
+ */
+ public Vector4f add(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ return new Vector4f(x + vec.x, y + vec.y, z + vec.z, w + vec.w);
+ }
+
+ /**
+ *
+ * <code>add</code> adds the values of a provided vector storing the
+ * values in the supplied vector.
+ *
+ * @param vec
+ * the vector to add to this
+ * @param result
+ * the vector to store the result in
+ * @return result returns the supplied result vector.
+ */
+ public Vector4f add(Vector4f vec, Vector4f result) {
+ result.x = x + vec.x;
+ result.y = y + vec.y;
+ result.z = z + vec.z;
+ result.w = w + vec.w;
+ return result;
+ }
+
+ /**
+ * <code>addLocal</code> adds a provided vector to this vector internally,
+ * and returns a handle to this vector for easy chaining of calls. If the
+ * provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to add to this vector.
+ * @return this
+ */
+ public Vector4f addLocal(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x += vec.x;
+ y += vec.y;
+ z += vec.z;
+ w += vec.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>add</code> adds the provided values to this vector, creating a
+ * new vector that is then returned.
+ *
+ * @param addX
+ * the x value to add.
+ * @param addY
+ * the y value to add.
+ * @param addZ
+ * the z value to add.
+ * @return the result vector.
+ */
+ public Vector4f add(float addX, float addY, float addZ, float addW) {
+ return new Vector4f(x + addX, y + addY, z + addZ, w + addW);
+ }
+
+ /**
+ * <code>addLocal</code> adds the provided values to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param addX
+ * value to add to x
+ * @param addY
+ * value to add to y
+ * @param addZ
+ * value to add to z
+ * @return this
+ */
+ public Vector4f addLocal(float addX, float addY, float addZ, float addW) {
+ x += addX;
+ y += addY;
+ z += addZ;
+ w += addW;
+ return this;
+ }
+
+ /**
+ *
+ * <code>scaleAdd</code> multiplies this vector by a scalar then adds the
+ * given Vector3f.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @param add
+ * the value to add
+ */
+ public Vector4f scaleAdd(float scalar, Vector4f add) {
+ x = x * scalar + add.x;
+ y = y * scalar + add.y;
+ z = z * scalar + add.z;
+ w = w * scalar + add.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>scaleAdd</code> multiplies the given vector by a scalar then adds
+ * the given vector.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @param mult
+ * the value to multiply the scalar by
+ * @param add
+ * the value to add
+ */
+ public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) {
+ this.x = mult.x * scalar + add.x;
+ this.y = mult.y * scalar + add.y;
+ this.z = mult.z * scalar + add.z;
+ this.w = mult.w * scalar + add.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>dot</code> calculates the dot product of this vector with a
+ * provided vector. If the provided vector is null, 0 is returned.
+ *
+ * @param vec
+ * the vector to dot with this vector.
+ * @return the resultant dot product of this vector and a given vector.
+ */
+ public float dot(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, 0 returned.");
+ return 0;
+ }
+ return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
+ }
+
+ public Vector4f project(Vector4f other){
+ float n = this.dot(other); // A . B
+ float d = other.lengthSquared(); // |B|^2
+ return new Vector4f(other).normalizeLocal().multLocal(n/d);
+ }
+
+ /**
+ * Returns true if this vector is a unit vector (length() ~= 1),
+ * returns false otherwise.
+ *
+ * @return true if this vector is a unit vector (length() ~= 1),
+ * or false otherwise.
+ */
+ public boolean isUnitVector(){
+ float len = length();
+ return 0.99f < len && len < 1.01f;
+ }
+
+ /**
+ * <code>length</code> calculates the magnitude of this vector.
+ *
+ * @return the length or magnitude of the vector.
+ */
+ public float length() {
+ return FastMath.sqrt(lengthSquared());
+ }
+
+ /**
+ * <code>lengthSquared</code> calculates the squared value of the
+ * magnitude of the vector.
+ *
+ * @return the magnitude squared of the vector.
+ */
+ public float lengthSquared() {
+ return x * x + y * y + z * z + w * w;
+ }
+
+ /**
+ * <code>distanceSquared</code> calculates the distance squared between
+ * this vector and vector v.
+ *
+ * @param v the second vector to determine the distance squared.
+ * @return the distance squared between the two vectors.
+ */
+ public float distanceSquared(Vector4f v) {
+ double dx = x - v.x;
+ double dy = y - v.y;
+ double dz = z - v.z;
+ double dw = w - v.w;
+ return (float) (dx * dx + dy * dy + dz * dz + dw * dw);
+ }
+
+ /**
+ * <code>distance</code> calculates the distance between this vector and
+ * vector v.
+ *
+ * @param v the second vector to determine the distance.
+ * @return the distance between the two vectors.
+ */
+ public float distance(Vector4f v) {
+ return FastMath.sqrt(distanceSquared(v));
+ }
+
+ /**
+ *
+ * <code>mult</code> multiplies this vector by a scalar. The resultant
+ * vector is returned.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return the new vector.
+ */
+ public Vector4f mult(float scalar) {
+ return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar);
+ }
+
+ /**
+ *
+ * <code>mult</code> multiplies this vector by a scalar. The resultant
+ * vector is supplied as the second parameter and returned.
+ *
+ * @param scalar the scalar to multiply this vector by.
+ * @param product the product to store the result in.
+ * @return product
+ */
+ public Vector4f mult(float scalar, Vector4f product) {
+ if (null == product) {
+ product = new Vector4f();
+ }
+
+ product.x = x * scalar;
+ product.y = y * scalar;
+ product.z = z * scalar;
+ product.w = w * scalar;
+ return product;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls.
+ *
+ * @param scalar
+ * the value to multiply this vector by.
+ * @return this
+ */
+ public Vector4f multLocal(float scalar) {
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ w *= scalar;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @return this
+ */
+ public Vector4f multLocal(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x *= vec.x;
+ y *= vec.y;
+ z *= vec.z;
+ w *= vec.w;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies this vector by 3 scalars
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @param w
+ * @return this
+ */
+ public Vector4f multLocal(float x, float y, float z, float w) {
+ this.x *= x;
+ this.y *= y;
+ this.z *= z;
+ this.w *= w;
+ return this;
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @return this
+ */
+ public Vector4f mult(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ return mult(vec, null);
+ }
+
+ /**
+ * <code>multLocal</code> multiplies a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to mult to this vector.
+ * @param store result vector (null to create a new vector)
+ * @return this
+ */
+ public Vector4f mult(Vector4f vec, Vector4f store) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ if (store == null) store = new Vector4f();
+ return store.set(x * vec.x, y * vec.y, z * vec.z, w * vec.w);
+ }
+
+ /**
+ * <code>divide</code> divides the values of this vector by a scalar and
+ * returns the result. The values of this vector remain untouched.
+ *
+ * @param scalar
+ * the value to divide this vectors attributes by.
+ * @return the result <code>Vector</code>.
+ */
+ public Vector4f divide(float scalar) {
+ scalar = 1f/scalar;
+ return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar);
+ }
+
+ /**
+ * <code>divideLocal</code> divides this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls. Dividing
+ * by zero will result in an exception.
+ *
+ * @param scalar
+ * the value to divides this vector by.
+ * @return this
+ */
+ public Vector4f divideLocal(float scalar) {
+ scalar = 1f/scalar;
+ x *= scalar;
+ y *= scalar;
+ z *= scalar;
+ w *= scalar;
+ return this;
+ }
+
+ /**
+ * <code>divide</code> divides the values of this vector by a scalar and
+ * returns the result. The values of this vector remain untouched.
+ *
+ * @param scalar
+ * the value to divide this vectors attributes by.
+ * @return the result <code>Vector</code>.
+ */
+ public Vector4f divide(Vector4f scalar) {
+ return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w);
+ }
+
+ /**
+ * <code>divideLocal</code> divides this vector by a scalar internally,
+ * and returns a handle to this vector for easy chaining of calls. Dividing
+ * by zero will result in an exception.
+ *
+ * @param scalar
+ * the value to divides this vector by.
+ * @return this
+ */
+ public Vector4f divideLocal(Vector4f scalar) {
+ x /= scalar.x;
+ y /= scalar.y;
+ z /= scalar.z;
+ w /= scalar.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>negate</code> returns the negative of this vector. All values are
+ * negated and set to a new vector.
+ *
+ * @return the negated vector.
+ */
+ public Vector4f negate() {
+ return new Vector4f(-x, -y, -z, -w);
+ }
+
+ /**
+ *
+ * <code>negateLocal</code> negates the internal values of this vector.
+ *
+ * @return this.
+ */
+ public Vector4f negateLocal() {
+ x = -x;
+ y = -y;
+ z = -z;
+ w = -w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>subtract</code> subtracts the values of a given vector from those
+ * of this vector creating a new vector object. If the provided vector is
+ * null, null is returned.
+ *
+ * @param vec
+ * the vector to subtract from this vector.
+ * @return the result vector.
+ */
+ public Vector4f subtract(Vector4f vec) {
+ return new Vector4f(x - vec.x, y - vec.y, z - vec.z, w - vec.w);
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts a provided vector to this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls. If the provided vector is null, null is returned.
+ *
+ * @param vec
+ * the vector to subtract
+ * @return this
+ */
+ public Vector4f subtractLocal(Vector4f vec) {
+ if (null == vec) {
+ logger.warning("Provided vector is null, null returned.");
+ return null;
+ }
+ x -= vec.x;
+ y -= vec.y;
+ z -= vec.z;
+ w -= vec.w;
+ return this;
+ }
+
+ /**
+ *
+ * <code>subtract</code>
+ *
+ * @param vec
+ * the vector to subtract from this
+ * @param result
+ * the vector to store the result in
+ * @return result
+ */
+ public Vector4f subtract(Vector4f vec, Vector4f result) {
+ if(result == null) {
+ result = new Vector4f();
+ }
+ result.x = x - vec.x;
+ result.y = y - vec.y;
+ result.z = z - vec.z;
+ result.w = w - vec.w;
+ return result;
+ }
+
+ /**
+ *
+ * <code>subtract</code> subtracts the provided values from this vector,
+ * creating a new vector that is then returned.
+ *
+ * @param subtractX
+ * the x value to subtract.
+ * @param subtractY
+ * the y value to subtract.
+ * @param subtractZ
+ * the z value to subtract.
+ * @param subtractW
+ * the w value to subtract.
+ * @return the result vector.
+ */
+ public Vector4f subtract(float subtractX, float subtractY, float subtractZ, float subtractW) {
+ return new Vector4f(x - subtractX, y - subtractY, z - subtractZ, w - subtractW);
+ }
+
+ /**
+ * <code>subtractLocal</code> subtracts the provided values from this vector
+ * internally, and returns a handle to this vector for easy chaining of
+ * calls.
+ *
+ * @param subtractX
+ * the x value to subtract.
+ * @param subtractY
+ * the y value to subtract.
+ * @param subtractZ
+ * the z value to subtract.
+ * @param subtractW
+ * the w value to subtract.
+ * @return this
+ */
+ public Vector4f subtractLocal(float subtractX, float subtractY, float subtractZ, float subtractW) {
+ x -= subtractX;
+ y -= subtractY;
+ z -= subtractZ;
+ w -= subtractW;
+ return this;
+ }
+
+ /**
+ * <code>normalize</code> returns the unit vector of this vector.
+ *
+ * @return unit vector of this vector.
+ */
+ public Vector4f normalize() {
+// float length = length();
+// if (length != 0) {
+// return divide(length);
+// }
+//
+// return divide(1);
+ float length = x * x + y * y + z * z + w * w;
+ if (length != 1f && length != 0f){
+ length = 1.0f / FastMath.sqrt(length);
+ return new Vector4f(x * length, y * length, z * length, w * length);
+ }
+ return clone();
+ }
+
+ /**
+ * <code>normalizeLocal</code> makes this vector into a unit vector of
+ * itself.
+ *
+ * @return this.
+ */
+ public Vector4f normalizeLocal() {
+ // NOTE: this implementation is more optimized
+ // than the old jme normalize as this method
+ // is commonly used.
+ float length = x * x + y * y + z * z + w * w;
+ if (length != 1f && length != 0f){
+ length = 1.0f / FastMath.sqrt(length);
+ x *= length;
+ y *= length;
+ z *= length;
+ w *= length;
+ }
+ return this;
+ }
+
+ /**
+ * <code>maxLocal</code> computes the maximum value for each
+ * component in this and <code>other</code> vector. The result is stored
+ * in this vector.
+ * @param other
+ */
+ public void maxLocal(Vector4f other){
+ x = other.x > x ? other.x : x;
+ y = other.y > y ? other.y : y;
+ z = other.z > z ? other.z : z;
+ w = other.w > w ? other.w : w;
+ }
+
+ /**
+ * <code>minLocal</code> computes the minimum value for each
+ * component in this and <code>other</code> vector. The result is stored
+ * in this vector.
+ * @param other
+ */
+ public void minLocal(Vector4f other){
+ x = other.x < x ? other.x : x;
+ y = other.y < y ? other.y : y;
+ z = other.z < z ? other.z : z;
+ w = other.w < w ? other.w : w;
+ }
+
+ /**
+ * <code>zero</code> resets this vector's data to zero internally.
+ */
+ public Vector4f zero() {
+ x = y = z = w = 0;
+ return this;
+ }
+
+ /**
+ * <code>angleBetween</code> returns (in radians) the angle between two vectors.
+ * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
+ *
+ * @param otherVector a unit vector to find the angle against
+ * @return the angle in radians.
+ */
+ public float angleBetween(Vector4f otherVector) {
+ float dotProduct = dot(otherVector);
+ float angle = FastMath.acos(dotProduct);
+ return angle;
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from this to the finalVec
+ * this=(1-changeAmnt)*this + changeAmnt * finalVec
+ * @param finalVec The final vector to interpolate towards
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from this towards finalVec
+ */
+ public Vector4f interpolate(Vector4f finalVec, float changeAmnt) {
+ this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x;
+ this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y;
+ this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z;
+ this.w=(1-changeAmnt)*this.w + changeAmnt*finalVec.w;
+ return this;
+ }
+
+ /**
+ * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec
+ * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec
+ * @param beginVec the beging vector (changeAmnt=0)
+ * @param finalVec The final vector to interpolate towards
+ * @param changeAmnt An amount between 0.0 - 1.0 representing a precentage
+ * change from beginVec towards finalVec
+ */
+ public Vector4f interpolate(Vector4f beginVec,Vector4f finalVec, float changeAmnt) {
+ this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x;
+ this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y;
+ this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z;
+ this.w=(1-changeAmnt)*beginVec.w + changeAmnt*finalVec.w;
+ return this;
+ }
+
+ /**
+ * Check a vector... if it is null or its floats are NaN or infinite,
+ * return false. Else return true.
+ * @param vector the vector to check
+ * @return true or false as stated above.
+ */
+ public static boolean isValidVector(Vector4f vector) {
+ if (vector == null) return false;
+ if (Float.isNaN(vector.x) ||
+ Float.isNaN(vector.y) ||
+ Float.isNaN(vector.z)||
+ Float.isNaN(vector.w)) return false;
+ if (Float.isInfinite(vector.x) ||
+ Float.isInfinite(vector.y) ||
+ Float.isInfinite(vector.z) ||
+ Float.isInfinite(vector.w)) return false;
+ return true;
+ }
+
+ @Override
+ public Vector4f clone() {
+ try {
+ return (Vector4f) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(); // can not happen
+ }
+ }
+
+ /**
+ * Saves this Vector3f into the given float[] object.
+ *
+ * @param floats
+ * The float[] to take this Vector3f. If null, a new float[3] is
+ * created.
+ * @return The array, with X, Y, Z float values in that order
+ */
+ public float[] toArray(float[] floats) {
+ if (floats == null) {
+ floats = new float[4];
+ }
+ floats[0] = x;
+ floats[1] = y;
+ floats[2] = z;
+ floats[3] = w;
+ return floats;
+ }
+
+ /**
+ * are these two vectors the same? they are is they both have the same x,y,
+ * and z values.
+ *
+ * @param o
+ * the object to compare for equality
+ * @return true if they are equal
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof Vector4f)) { return false; }
+
+ if (this == o) { return true; }
+
+ Vector4f comp = (Vector4f) o;
+ if (Float.compare(x,comp.x) != 0) return false;
+ if (Float.compare(y,comp.y) != 0) return false;
+ if (Float.compare(z,comp.z) != 0) return false;
+ if (Float.compare(w,comp.w) != 0) return false;
+ return true;
+ }
+
+ /**
+ * <code>hashCode</code> returns a unique code for this vector object based
+ * on it's values. If two vectors are logically equivalent, they will return
+ * the same hash code value.
+ * @return the hash code value of this vector.
+ */
+ public int hashCode() {
+ int hash = 37;
+ hash += 37 * hash + Float.floatToIntBits(x);
+ hash += 37 * hash + Float.floatToIntBits(y);
+ hash += 37 * hash + Float.floatToIntBits(z);
+ hash += 37 * hash + Float.floatToIntBits(w);
+ return hash;
+ }
+
+ /**
+ * <code>toString</code> returns the string representation of this vector.
+ * The format is:
+ *
+ * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ, W=WW.WWWW]
+ *
+ * @return the string representation of this vector.
+ */
+ public String toString() {
+ return "(" + x + ", " + y + ", " + z + ", " + w + ")";
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(x, "x", 0);
+ capsule.write(y, "y", 0);
+ capsule.write(z, "z", 0);
+ capsule.write(w, "w", 0);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ x = capsule.readFloat("x", 0);
+ y = capsule.readFloat("y", 0);
+ z = capsule.readFloat("z", 0);
+ w = capsule.readFloat("w", 0);
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public Vector4f setX(float x) {
+ this.x = x;
+ return this;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ public Vector4f setY(float y) {
+ this.y = y;
+ return this;
+ }
+
+ public float getZ() {
+ return z;
+ }
+
+ public Vector4f setZ(float z) {
+ this.z = z;
+ return this;
+ }
+
+ public float getW() {
+ return w;
+ }
+
+ public Vector4f setW(float w) {
+ this.w = w;
+ return this;
+ }
+
+ /**
+ * @param index
+ * @return x value if index == 0, y value if index == 1 or z value if index ==
+ * 2
+ * @throws IllegalArgumentException
+ * if index is not one of 0, 1, 2.
+ */
+ public float get(int index) {
+ switch (index) {
+ case 0:
+ return x;
+ case 1:
+ return y;
+ case 2:
+ return z;
+ case 3:
+ return w;
+ }
+ throw new IllegalArgumentException("index must be either 0, 1, 2 or 3");
+ }
+
+ /**
+ * @param index
+ * which field index in this vector to set.
+ * @param value
+ * to set to one of x, y, z or w.
+ * @throws IllegalArgumentException
+ * if index is not one of 0, 1, 2, 3.
+ */
+ public void set(int index, float value) {
+ switch (index) {
+ case 0:
+ x = value;
+ return;
+ case 1:
+ y = value;
+ return;
+ case 2:
+ z = value;
+ return;
+ case 3:
+ w = value;
+ return;
+ }
+ throw new IllegalArgumentException("index must be either 0, 1, 2 or 3");
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/math/package.html b/engine/src/core/com/jme3/math/package.html
new file mode 100644
index 0000000..64da8aa
--- /dev/null
+++ b/engine/src/core/com/jme3/math/package.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.math</code> package provides mathematic data structures
+and utilities which are used by the rest of the engine.
+The math package provides the following classes:<br>
+<h3>General purpose vectors</h3>
+<ul>
+ <li>{@link com.jme3.math.Vector2f} - 2D general purpose vector</li>
+ <li>{@link com.jme3.math.Vector3f} - 3D general purpose vector</li>
+ <li>{@link com.jme3.math.Vector4f} - 4D general purpose vector</li>
+</ul>
+<h3>Special purpose vectors</h3>
+<ul>
+ <li>{@link com.jme3.math.ColorRGBA} - Floating-point RGB color with alpha</li>
+ <li>{@link com.jme3.math.Quaternion} - Specialized 4D data structure to represent rotation</li>
+</ul>
+<h3>Matrices</h3>
+<ul>
+ <li>{@link com.jme3.math.Matrix3f} - 3x3 matrix, usually used to represent rotation</li>
+ <li>{@link com.jme3.math.Matrix4f} - 4x4 matrix, used as an efficient transform representation</li>
+</ul>
+<h3>Shapes</h3>
+<ul>
+ <li>{@link com.jme3.math.AbstractTriangle} - Abstract triangle. Data to be provided by implementation</li>
+ <li>{@link com.jme3.math.Triangle} - Concrete implementation of AbstractTriangle with center and normal vectors</li>
+ <li>{@link com.jme3.math.Line} - Infinite 3D line</li>
+ <li>{@link com.jme3.math.LineSegment} - 3D line with start and end point</li>
+ <li>{@link com.jme3.math.Plane} - 3D plane</li>
+ <li>{@link com.jme3.math.Ray} - 3D ray</li>
+ <li>{@link com.jme3.math.Rectangle} - 3D rectangle</li>
+ <li>{@link com.jme3.math.Ring} - 3D ring</li>
+</ul>
+<h3>Curves</h3>
+<ul>
+ <li>{@link com.jme3.math.Spline} - 3D curve defined by control points and a function</li>
+</ul>
+<h3>Utility classes</h3>
+<ul>
+ <li>{@link com.jme3.math.Transform} - Representation of a transform with translation, rotation, and scale</li>
+ <li>{@link com.jme3.math.FastMath} - Contains static methods for floating-point math</li>
+ <li>{@link com.jme3.math.CurveAndSurfaceMath} - Contains static methods specific to curve and surface math</li>
+ <li>{@link com.jme3.math.Eigen3f} - Provides computation of eigenvectors given a matrix</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/post/Filter.java b/engine/src/core/com/jme3/post/Filter.java
new file mode 100644
index 0000000..ef9a6ff
--- /dev/null
+++ b/engine/src/core/com/jme3/post/Filter.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Filters are 2D effects applied to the rendered scene.<br>
+ * The filter is fed with the rendered scene image rendered in an offscreen frame buffer.<br>
+ * This texture is applied on a fullscreen quad, with a special material.<br>
+ * This material uses a shader that aplly the desired effect to the scene texture.<br>
+ * <br>
+ * This class is abstract, any Filter must extend it.<br>
+ * Any filter holds a frameBuffer and a texture<br>
+ * The getMaterial must return a Material that use a GLSL shader immplementing the desired effect<br>
+ *
+ * @author Rémy Bouquet aka Nehon
+ */
+public abstract class Filter implements Savable {
+
+
+ private String name;
+ protected Pass defaultPass;
+ protected List<Pass> postRenderPasses;
+ protected Material material;
+ protected boolean enabled = true;
+ protected FilterPostProcessor processor;
+
+ public Filter(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Inner class Pass
+ * Pass are like filters in filters.
+ * Some filters will need multiple passes before the final render
+ */
+ public class Pass {
+
+ protected FrameBuffer renderFrameBuffer;
+ protected Texture2D renderedTexture;
+ protected Texture2D depthTexture;
+ protected Material passMaterial;
+
+ /**
+ * init the pass called internally
+ * @param renderer
+ * @param width
+ * @param height
+ * @param textureFormat
+ * @param depthBufferFormat
+ * @param numSamples
+ */
+ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples, boolean renderDepth) {
+ Collection<Caps> caps = renderer.getCaps();
+ if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample) && caps.contains(Caps.OpenGL31)) {
+ renderFrameBuffer = new FrameBuffer(width, height, numSamples);
+ renderedTexture = new Texture2D(width, height, numSamples, textureFormat);
+ renderFrameBuffer.setDepthBuffer(depthBufferFormat);
+ if (renderDepth) {
+ depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat);
+ renderFrameBuffer.setDepthTexture(depthTexture);
+ }
+ } else {
+ renderFrameBuffer = new FrameBuffer(width, height, 1);
+ renderedTexture = new Texture2D(width, height, textureFormat);
+ renderFrameBuffer.setDepthBuffer(depthBufferFormat);
+ if (renderDepth) {
+ depthTexture = new Texture2D(width, height, depthBufferFormat);
+ renderFrameBuffer.setDepthTexture(depthTexture);
+ }
+ }
+
+ renderFrameBuffer.setColorTexture(renderedTexture);
+
+
+ }
+
+ /**
+ * init the pass called internally
+ * @param renderer
+ * @param width
+ * @param height
+ * @param textureFormat
+ * @param depthBufferFormat
+ */
+ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat) {
+ init(renderer, width, height, textureFormat, depthBufferFormat, 1);
+ }
+
+ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples) {
+ init(renderer, width, height, textureFormat, depthBufferFormat, numSamples, false);
+ }
+
+ /**
+ * init the pass called internally
+ * @param renderer
+ * @param width
+ * @param height
+ * @param textureFormat
+ * @param depthBufferFormat
+ * @param numSample
+ * @param material
+ */
+ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSample, Material material) {
+ init(renderer, width, height, textureFormat, depthBufferFormat, numSample);
+ passMaterial = material;
+ }
+
+ public boolean requiresSceneAsTexture() {
+ return false;
+ }
+
+ public boolean requiresDepthAsTexture() {
+ return false;
+ }
+
+ public void beforeRender() {
+ }
+
+ public FrameBuffer getRenderFrameBuffer() {
+ return renderFrameBuffer;
+ }
+
+ public void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {
+ this.renderFrameBuffer = renderFrameBuffer;
+ }
+
+ public Texture2D getDepthTexture() {
+ return depthTexture;
+ }
+
+ public Texture2D getRenderedTexture() {
+ return renderedTexture;
+ }
+
+ public void setRenderedTexture(Texture2D renderedTexture) {
+ this.renderedTexture = renderedTexture;
+ }
+
+ public Material getPassMaterial() {
+ return passMaterial;
+ }
+
+ public void setPassMaterial(Material passMaterial) {
+ this.passMaterial = passMaterial;
+ }
+
+ public void cleanup(Renderer r) {
+ }
+ }
+
+ /**
+ * returns the default pass texture format
+ * @return
+ */
+ protected Format getDefaultPassTextureFormat() {
+ return Format.RGBA8;
+ }
+
+ /**
+ * returns the default pass depth format
+ * @return
+ */
+ protected Format getDefaultPassDepthFormat() {
+ return Format.Depth;
+ }
+
+ /**
+ * contruct a Filter
+ */
+ protected Filter() {
+ this("filter");
+ }
+
+ /**
+ *
+ * initialize this filter
+ * use InitFilter for overriding filter initialization
+ * @param manager the assetManager
+ * @param renderManager the renderManager
+ * @param vp the viewport
+ * @param w the width
+ * @param h the height
+ */
+ protected final void init(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) {
+ // cleanup(renderManager.getRenderer());
+ defaultPass = new Pass();
+ defaultPass.init(renderManager.getRenderer(), w, h, getDefaultPassTextureFormat(), getDefaultPassDepthFormat());
+ initFilter(manager, renderManager, vp, w, h);
+ }
+
+ /**
+ * cleanup this filter
+ * @param r
+ */
+ protected final void cleanup(Renderer r) {
+ processor = null;
+ if (defaultPass != null) {
+ defaultPass.cleanup(r);
+ }
+ if (postRenderPasses != null) {
+ for (Iterator<Pass> it = postRenderPasses.iterator(); it.hasNext();) {
+ Pass pass = it.next();
+ pass.cleanup(r);
+ }
+ }
+ cleanUpFilter(r);
+ }
+
+ /**
+ * Initialization of sub classes filters
+ * This method is called once when the filter is added to the FilterPostProcessor
+ * It should contain Material initializations and extra passes initialization
+ * @param manager the assetManager
+ * @param renderManager the renderManager
+ * @param vp the viewPort where this filter is rendered
+ * @param w the width of the filter
+ * @param h the height of the filter
+ */
+ protected abstract void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h);
+
+ /**
+ * override this method if you have some cleanup to do
+ * @param r the renderer
+ */
+ protected void cleanUpFilter(Renderer r) {
+ }
+
+ ;
+
+ /**
+ * Must return the material used for this filter.
+ * this method is called every frame.
+ *
+ * @return the material used for this filter.
+ */
+ protected abstract Material getMaterial();
+
+ /**
+ * Override this method if you want to make a pre pass, before the actual rendering of the frame
+ * @param renderManager
+ * @param viewPort
+ */
+ protected void postQueue(RenderManager renderManager, ViewPort viewPort) {
+ }
+
+ /**
+ * Override this method if you want to modify parameters according to tpf before the rendering of the frame.
+ * This is usefull for animated filters
+ * Also it can be the place to render pre passes
+ * @param tpf the time used to render the previous frame
+ */
+ protected void preFrame(float tpf) {
+ }
+
+ /**
+ * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering
+ * @param renderManager
+ * @param viewPort
+ * @param prevFilterBuffer
+ * @param sceneBuffer
+ */
+ protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) {
+ }
+
+ /**
+ * Override this method if you want to save extra properties when the filter is saved else only basic properties of the filter will be saved
+ * This method should always begin by super.write(ex);
+ * @param ex
+ * @throws IOException
+ */
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(name, "name", "");
+ oc.write(enabled, "enabled", true);
+ }
+
+ /**
+ * Override this method if you want to load extra properties when the filter
+ * is loaded else only basic properties of the filter will be loaded
+ * This method should always begin by super.read(im);
+ */
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ name = ic.readString("name", "");
+ enabled = ic.readBoolean("enabled", true);
+ }
+
+ /**
+ * returns the name of the filter
+ * @return
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the filter
+ * @param name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * returns the default pass frame buffer
+ * @return
+ */
+ protected FrameBuffer getRenderFrameBuffer() {
+ return defaultPass.renderFrameBuffer;
+ }
+
+ /**
+ * sets the default pas frame buffer
+ * @param renderFrameBuffer
+ */
+ protected void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) {
+ this.defaultPass.renderFrameBuffer = renderFrameBuffer;
+ }
+
+ /**
+ * returns the rendered texture of this filter
+ * @return
+ */
+ protected Texture2D getRenderedTexture() {
+ return defaultPass.renderedTexture;
+ }
+
+ /**
+ * sets the rendered texture of this filter
+ * @param renderedTexture
+ */
+ protected void setRenderedTexture(Texture2D renderedTexture) {
+ this.defaultPass.renderedTexture = renderedTexture;
+ }
+
+ /**
+ * Override this method and return true if your Filter needs the depth texture
+ *
+ * @return true if your Filter need the depth texture
+ */
+ protected boolean isRequiresDepthTexture() {
+ return false;
+ }
+
+ /**
+ * Override this method and return false if your Filter does not need the scene texture
+ *
+ * @return false if your Filter does not need the scene texture
+ */
+ protected boolean isRequiresSceneTexture() {
+ return true;
+ }
+
+ /**
+ * returns the list of the postRender passes
+ * @return
+ */
+ protected List<Pass> getPostRenderPasses() {
+ return postRenderPasses;
+ }
+
+ /**
+ * Enable or disable this filter
+ * @param enabled true to enable
+ */
+ public void setEnabled(boolean enabled) {
+ if (processor != null) {
+ processor.setFilterState(this, enabled);
+ } else {
+ this.enabled = enabled;
+ }
+ }
+
+ /**
+ * returns ttrue if the filter is enabled
+ * @return enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * sets a reference to the FilterPostProcessor ti which this filter is attached
+ * @param proc
+ */
+ protected void setProcessor(FilterPostProcessor proc) {
+ processor = proc;
+ }
+}
diff --git a/engine/src/core/com/jme3/post/FilterPostProcessor.java b/engine/src/core/com/jme3/post/FilterPostProcessor.java
new file mode 100644
index 0000000..2e48f0f
--- /dev/null
+++ b/engine/src/core/com/jme3/post/FilterPostProcessor.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.renderer.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene<br>
+ * It manages a list of filters that will be applied in the order in which they've been added to the list
+ * @author Rémy Bouquet aka Nehon
+ */
+public class FilterPostProcessor implements SceneProcessor, Savable {
+
+ private RenderManager renderManager;
+ private Renderer renderer;
+ private ViewPort viewPort;
+ private FrameBuffer renderFrameBufferMS;
+ private int numSamples = 1;
+ private FrameBuffer renderFrameBuffer;
+ private Texture2D filterTexture;
+ private Texture2D depthTexture;
+ private List<Filter> filters = new ArrayList<Filter>();
+ private AssetManager assetManager;
+ private Camera filterCam = new Camera(1, 1);
+ private Picture fsQuad;
+ private boolean computeDepth = false;
+ private FrameBuffer outputBuffer;
+ private int width;
+ private int height;
+ private float bottom;
+ private float left;
+ private float right;
+ private float top;
+ private int originalWidth;
+ private int originalHeight;
+ private int lastFilterIndex = -1;
+ private boolean cameraInit = false;
+
+ /**
+ * Create a FilterProcessor
+ * @param assetManager the assetManager
+ */
+ public FilterPostProcessor(AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ /**
+ * Don't use this constructor use {@link FilterPostProcessor(AssetManager assetManager)}<br>
+ * This constructor is used for serialization only
+ */
+ public FilterPostProcessor() {
+ }
+
+ /**
+ * Adds a filter to the filters list<br>
+ * @param filter the filter to add
+ */
+ public void addFilter(Filter filter) {
+ filters.add(filter);
+ filter.setProcessor(this);
+
+ if (isInitialized()) {
+ initFilter(filter, viewPort);
+ }
+
+ setFilterState(filter, filter.isEnabled());
+
+ }
+
+ /**
+ * removes this filters from the filters list
+ * @param filter
+ */
+ public void removeFilter(Filter filter) {
+ filters.remove(filter);
+ filter.cleanup(renderer);
+ updateLastFilterIndex();
+ }
+
+ public Iterator<Filter> getFilterIterator() {
+ return filters.iterator();
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ renderManager = rm;
+ renderer = rm.getRenderer();
+ viewPort = vp;
+ fsQuad = new Picture("filter full screen quad");
+
+ Camera cam = vp.getCamera();
+
+ //save view port diensions
+ left = cam.getViewPortLeft();
+ right = cam.getViewPortRight();
+ top = cam.getViewPortTop();
+ bottom = cam.getViewPortBottom();
+ originalWidth = cam.getWidth();
+ originalHeight = cam.getHeight();
+ //first call to reshape
+ reshape(vp, cam.getWidth(), cam.getHeight());
+ }
+
+ /**
+ * init the given filter
+ * @param filter
+ * @param vp
+ */
+ private void initFilter(Filter filter, ViewPort vp) {
+ filter.init(assetManager, renderManager, vp, width, height);
+ if (filter.isRequiresDepthTexture()) {
+ if (!computeDepth && renderFrameBuffer != null) {
+ depthTexture = new Texture2D(width, height, Format.Depth24);
+ renderFrameBuffer.setDepthTexture(depthTexture);
+ }
+ computeDepth = true;
+ filter.getMaterial().setTexture("DepthTexture", depthTexture);
+ }
+ }
+
+ /**
+ * renders a filter on a fullscreen quad
+ * @param r
+ * @param buff
+ * @param mat
+ */
+ private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) {
+ if (buff == outputBuffer) {
+ fsQuad.setWidth(width);
+ fsQuad.setHeight(height);
+ filterCam.resize(originalWidth, originalHeight, true);
+ fsQuad.setPosition(left * originalWidth, bottom * originalHeight);
+ } else {
+ fsQuad.setWidth(buff.getWidth());
+ fsQuad.setHeight(buff.getHeight());
+ filterCam.resize(buff.getWidth(), buff.getHeight(), true);
+ fsQuad.setPosition(0, 0);
+ }
+
+ if (mat.getAdditionalRenderState().isDepthWrite()) {
+ mat.getAdditionalRenderState().setDepthTest(false);
+ mat.getAdditionalRenderState().setDepthWrite(false);
+ }
+
+ fsQuad.setMaterial(mat);
+ fsQuad.updateGeometricState();
+
+ renderManager.setCamera(filterCam, true);
+ r.setFrameBuffer(buff);
+ r.clearBuffers(false, true, true);
+ renderManager.renderGeometry(fsQuad);
+
+ }
+
+ public boolean isInitialized() {
+ return viewPort != null;
+ }
+
+ public void postQueue(RenderQueue rq) {
+
+ for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
+ Filter filter = it.next();
+ if (filter.isEnabled()) {
+ filter.postQueue(renderManager, viewPort);
+ }
+ }
+
+ }
+ Picture pic = new Picture("debug");
+
+ /**
+ * iterate through the filter list and renders filters
+ * @param r
+ * @param sceneFb
+ */
+ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) {
+ Texture2D tex = filterTexture;
+ FrameBuffer buff = sceneFb;
+ boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1;
+ for (int i = 0; i < filters.size(); i++) {
+ Filter filter = filters.get(i);
+ if (filter.isEnabled()) {
+ if (filter.getPostRenderPasses() != null) {
+ for (Iterator<Filter.Pass> it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) {
+ Filter.Pass pass = it1.next();
+ pass.beforeRender();
+ if (pass.requiresSceneAsTexture()) {
+ pass.getPassMaterial().setTexture("Texture", tex);
+ if (tex.getImage().getMultiSamples() > 1) {
+ pass.getPassMaterial().setInt("NumSamples", tex.getImage().getMultiSamples());
+ } else {
+ pass.getPassMaterial().clearParam("NumSamples");
+
+ }
+ }
+ if (pass.requiresDepthAsTexture()) {
+ pass.getPassMaterial().setTexture("DepthTexture", depthTexture);
+ if (msDepth) {
+ pass.getPassMaterial().setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());
+ } else {
+ pass.getPassMaterial().clearParam("NumSamplesDepth");
+ }
+ }
+ renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial());
+ }
+ }
+
+ filter.postFrame(renderManager, viewPort, buff, sceneFb);
+
+ Material mat = filter.getMaterial();
+ if (msDepth && filter.isRequiresDepthTexture()) {
+ mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples());
+ }
+
+ if (filter.isRequiresSceneTexture()) {
+ mat.setTexture("Texture", tex);
+ if (tex.getImage().getMultiSamples() > 1) {
+ mat.setInt("NumSamples", tex.getImage().getMultiSamples());
+ } else {
+ mat.clearParam("NumSamples");
+ }
+ }
+
+ buff = outputBuffer;
+ if (i != lastFilterIndex) {
+ buff = filter.getRenderFrameBuffer();
+ tex = filter.getRenderedTexture();
+
+ }
+ renderProcessing(r, buff, mat);
+ }
+ }
+ }
+
+ public void postFrame(FrameBuffer out) {
+
+ FrameBuffer sceneBuffer = renderFrameBuffer;
+ if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL31)) {
+ renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer);
+ } else if (renderFrameBufferMS != null) {
+ sceneBuffer = renderFrameBufferMS;
+ }
+ renderFilterChain(renderer, sceneBuffer);
+ renderer.setFrameBuffer(outputBuffer);
+
+ //viewport can be null if no filters are enabled
+ if (viewPort != null) {
+ renderManager.setCamera(viewPort.getCamera(), false);
+ }
+
+ }
+
+ public void preFrame(float tpf) {
+ if (filters.isEmpty() || lastFilterIndex == -1) {
+ //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was
+ if (cameraInit) {
+ viewPort.getCamera().resize(originalWidth, originalHeight, true);
+ viewPort.getCamera().setViewPort(left, right, bottom, top);
+ viewPort.setOutputFrameBuffer(outputBuffer);
+ cameraInit = false;
+ }
+
+ } else {
+ if (renderFrameBufferMS != null) {
+ viewPort.setOutputFrameBuffer(renderFrameBufferMS);
+ } else {
+ viewPort.setOutputFrameBuffer(renderFrameBuffer);
+ }
+ //init of the camera if it wasn't already
+ if (!cameraInit) {
+ viewPort.getCamera().resize(width, height, true);
+ viewPort.getCamera().setViewPort(0, 1, 0, 1);
+ }
+ }
+
+ for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
+ Filter filter = it.next();
+ if (filter.isEnabled()) {
+ filter.preFrame(tpf);
+ }
+ }
+
+ }
+
+ /**
+ * sets the filter to enabled or disabled
+ * @param filter
+ * @param enabled
+ */
+ protected void setFilterState(Filter filter, boolean enabled) {
+ if (filters.contains(filter)) {
+ filter.enabled = enabled;
+ updateLastFilterIndex();
+ }
+ }
+
+ /**
+ * compute the index of the last filter to render
+ */
+ private void updateLastFilterIndex() {
+ lastFilterIndex = -1;
+ for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) {
+ if (filters.get(i).isEnabled()) {
+ lastFilterIndex = i;
+ return;
+ }
+ }
+ if (lastFilterIndex == -1) {
+ cleanup();
+ }
+ }
+
+ public void cleanup() {
+ if (viewPort != null) {
+ //reseting the viewport camera viewport to its initial value
+ viewPort.getCamera().resize(originalWidth, originalHeight, true);
+ viewPort.getCamera().setViewPort(left, right, bottom, top);
+ viewPort.setOutputFrameBuffer(outputBuffer);
+ viewPort = null;
+ }
+
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ //this has no effect at first init but is useful when resizing the canvas with multi views
+ Camera cam = vp.getCamera();
+ cam.setViewPort(left, right, bottom, top);
+ //resizing the camera to fit the new viewport and saving original dimensions
+ cam.resize(w, h, false);
+ left = cam.getViewPortLeft();
+ right = cam.getViewPortRight();
+ top = cam.getViewPortTop();
+ bottom = cam.getViewPortBottom();
+ originalWidth = w;
+ originalHeight = h;
+ cam.setViewPort(0, 1, 0, 1);
+
+ //computing real dimension of the viewport and resizing he camera
+ width = (int) (w * (Math.abs(right - left)));
+ height = (int) (h * (Math.abs(bottom - top)));
+ width = Math.max(1, width);
+ height = Math.max(1, height);
+ cam.resize(width, height, false);
+ cameraInit = true;
+ computeDepth = false;
+
+ if (renderFrameBuffer == null) {
+ outputBuffer = viewPort.getOutputFrameBuffer();
+ }
+
+ Collection<Caps> caps = renderer.getCaps();
+
+ //antialiasing on filters only supported in opengl 3 due to depth read problem
+ if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) {
+ renderFrameBufferMS = new FrameBuffer(width, height, numSamples);
+ if (caps.contains(Caps.OpenGL31)) {
+ Texture2D msColor = new Texture2D(width, height, numSamples, Format.RGBA8);
+ Texture2D msDepth = new Texture2D(width, height, numSamples, Format.Depth);
+ renderFrameBufferMS.setDepthTexture(msDepth);
+ renderFrameBufferMS.setColorTexture(msColor);
+ filterTexture = msColor;
+ depthTexture = msDepth;
+ } else {
+ renderFrameBufferMS.setDepthBuffer(Format.Depth);
+ renderFrameBufferMS.setColorBuffer(Format.RGBA8);
+ }
+ }
+
+ if (numSamples <= 1 || !caps.contains(Caps.OpenGL31)) {
+ renderFrameBuffer = new FrameBuffer(width, height, 1);
+ renderFrameBuffer.setDepthBuffer(Format.Depth);
+ filterTexture = new Texture2D(width, height, Format.RGBA8);
+ renderFrameBuffer.setColorTexture(filterTexture);
+ }
+
+ for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
+ Filter filter = it.next();
+ initFilter(filter, vp);
+ }
+
+ if (renderFrameBufferMS != null) {
+ viewPort.setOutputFrameBuffer(renderFrameBufferMS);
+ } else {
+ viewPort.setOutputFrameBuffer(renderFrameBuffer);
+ }
+ }
+
+ /**
+ * return the number of samples for antialiasing
+ * @return numSamples
+ */
+ public int getNumSamples() {
+ return numSamples;
+ }
+
+ /**
+ *
+ * Removes all the filters from this processor
+ */
+ public void removeAllFilters() {
+ filters.clear();
+ updateLastFilterIndex();
+ }
+
+ /**
+ * Sets the number of samples for antialiasing
+ * @param numSamples the number of Samples
+ */
+ public void setNumSamples(int numSamples) {
+ if (numSamples <= 0) {
+ throw new IllegalArgumentException("numSamples must be > 0");
+ }
+
+ this.numSamples = numSamples;
+ }
+
+ /**
+ * Sets the asset manager for this processor
+ * @param assetManager
+ */
+ public void setAssetManager(AssetManager assetManager) {
+ this.assetManager = assetManager;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(numSamples, "numSamples", 0);
+ oc.writeSavableArrayList((ArrayList) filters, "filters", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ numSamples = ic.readInt("numSamples", 0);
+ filters = ic.readSavableArrayList("filters", null);
+ for (Filter filter : filters) {
+ filter.setProcessor(this);
+ setFilterState(filter, filter.isEnabled());
+ }
+ assetManager = im.getAssetManager();
+ }
+
+ /**
+ * For internal use only<br>
+ * returns the depth texture of the scene
+ * @return
+ */
+ public Texture2D getDepthTexture() {
+ return depthTexture;
+ }
+
+ /**
+ * For internal use only<br>
+ * returns the rendered texture of the scene
+ * @return
+ */
+ public Texture2D getFilterTexture() {
+ return filterTexture;
+ }
+}
diff --git a/engine/src/core/com/jme3/post/HDRRenderer.java b/engine/src/core/com/jme3/post/HDRRenderer.java
new file mode 100644
index 0000000..bac8334
--- /dev/null
+++ b/engine/src/core/com/jme3/post/HDRRenderer.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.Vector2f;
+import com.jme3.renderer.*;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+import java.util.Collection;
+import java.util.logging.Logger;
+
+public class HDRRenderer implements SceneProcessor {
+
+ private static final int LUMMODE_NONE = 0x1,
+ LUMMODE_ENCODE_LUM = 0x2,
+ LUMMODE_DECODE_LUM = 0x3;
+
+ private Renderer renderer;
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName());
+
+ private Camera fbCam = new Camera(1, 1);
+
+ private FrameBuffer msFB;
+
+ private FrameBuffer mainSceneFB;
+ private Texture2D mainScene;
+ private FrameBuffer scene64FB;
+ private Texture2D scene64;
+ private FrameBuffer scene8FB;
+ private Texture2D scene8;
+ private FrameBuffer scene1FB[] = new FrameBuffer[2];
+ private Texture2D scene1[] = new Texture2D[2];
+
+ private Material hdr64;
+ private Material hdr8;
+ private Material hdr1;
+ private Material tone;
+
+ private Picture fsQuad;
+ private float time = 0;
+ private int curSrc = -1;
+ private int oppSrc = -1;
+ private float blendFactor = 0;
+
+ private int numSamples = 0;
+ private float exposure = 0.18f;
+ private float whiteLevel = 100f;
+ private float throttle = -1;
+ private int maxIterations = -1;
+ private Image.Format bufFormat = Format.RGB16F;
+
+ private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps;
+ private MagFilter fbMagFilter = MagFilter.Bilinear;
+ private AssetManager manager;
+
+ private boolean enabled = true;
+
+ public HDRRenderer(AssetManager manager, Renderer renderer){
+ this.manager = manager;
+ this.renderer = renderer;
+
+ Collection<Caps> caps = renderer.getCaps();
+ if (caps.contains(Caps.PackedFloatColorBuffer))
+ bufFormat = Format.RGB111110F;
+ else if (caps.contains(Caps.FloatColorBuffer))
+ bufFormat = Format.RGB16F;
+ else{
+ enabled = false;
+ return;
+ }
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setSamples(int samples){
+ this.numSamples = samples;
+ }
+
+ public void setExposure(float exp){
+ this.exposure = exp;
+ }
+
+ public void setWhiteLevel(float whiteLevel){
+ this.whiteLevel = whiteLevel;
+ }
+
+ public void setMaxIterations(int maxIterations){
+ this.maxIterations = maxIterations;
+
+ // regenerate shaders if needed
+ if (hdr64 != null)
+ createLumShaders();
+ }
+
+ public void setThrottle(float throttle){
+ this.throttle = throttle;
+ }
+
+ public void setUseFastFilter(boolean fastFilter){
+ if (fastFilter){
+ fbMagFilter = MagFilter.Nearest;
+ fbMinFilter = MinFilter.NearestNoMipMaps;
+ }else{
+ fbMagFilter = MagFilter.Bilinear;
+ fbMinFilter = MinFilter.BilinearNoMipMaps;
+ }
+ }
+
+ public Picture createDisplayQuad(/*int mode, Texture tex*/){
+ if (scene64 == null)
+ return null;
+
+ Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");
+// if (mode == LUMMODE_ENCODE_LUM)
+// mat.setBoolean("EncodeLum", true);
+// else if (mode == LUMMODE_DECODE_LUM)
+ mat.setBoolean("DecodeLum", true);
+ mat.setTexture("Texture", scene64);
+// mat.setTexture("Texture", tex);
+
+ Picture dispQuad = new Picture("Luminance Display");
+ dispQuad.setMaterial(mat);
+ return dispQuad;
+ }
+
+ private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mode,
+ int iters, Texture tex){
+ Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md");
+
+ Vector2f blockSize = new Vector2f(1f / bufW, 1f / bufH);
+ Vector2f pixelSize = new Vector2f(1f / srcW, 1f / srcH);
+ Vector2f blocks = new Vector2f();
+ float numPixels = Float.POSITIVE_INFINITY;
+ if (iters != -1){
+ do {
+ pixelSize.multLocal(2);
+ blocks.set(blockSize.x / pixelSize.x,
+ blockSize.y / pixelSize.y);
+ numPixels = blocks.x * blocks.y;
+ } while (numPixels > iters);
+ }else{
+ blocks.set(blockSize.x / pixelSize.x,
+ blockSize.y / pixelSize.y);
+ numPixels = blocks.x * blocks.y;
+ }
+ System.out.println(numPixels);
+
+ mat.setBoolean("Blocks", true);
+ if (mode == LUMMODE_ENCODE_LUM)
+ mat.setBoolean("EncodeLum", true);
+ else if (mode == LUMMODE_DECODE_LUM)
+ mat.setBoolean("DecodeLum", true);
+
+ mat.setTexture("Texture", tex);
+ mat.setVector2("BlockSize", blockSize);
+ mat.setVector2("PixelSize", pixelSize);
+ mat.setFloat("NumPixels", numPixels);
+
+ return mat;
+ }
+
+ private void createLumShaders(){
+ int w = mainSceneFB.getWidth();
+ int h = mainSceneFB.getHeight();
+ hdr64 = createLumShader(w, h, 64, 64, LUMMODE_ENCODE_LUM, maxIterations, mainScene);
+ hdr8 = createLumShader(64, 64, 8, 8, LUMMODE_NONE, maxIterations, scene64);
+ hdr1 = createLumShader(8, 8, 1, 1, LUMMODE_NONE, maxIterations, scene8);
+ }
+
+ private int opposite(int i){
+ return i == 1 ? 0 : 1;
+ }
+
+ private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){
+ if (dst == null){
+ fsQuad.setWidth(mainSceneFB.getWidth());
+ fsQuad.setHeight(mainSceneFB.getHeight());
+ fbCam.resize(mainSceneFB.getWidth(), mainSceneFB.getHeight(), true);
+ }else{
+ fsQuad.setWidth(dst.getWidth());
+ fsQuad.setHeight(dst.getHeight());
+ fbCam.resize(dst.getWidth(), dst.getHeight(), true);
+ }
+ fsQuad.setMaterial(mat);
+ fsQuad.updateGeometricState();
+ renderManager.setCamera(fbCam, true);
+
+ r.setFrameBuffer(dst);
+ r.clearBuffers(true, true, true);
+ renderManager.renderGeometry(fsQuad);
+ }
+
+ private void renderToneMap(Renderer r, FrameBuffer out){
+ tone.setFloat("A", exposure);
+ tone.setFloat("White", whiteLevel);
+ tone.setTexture("Lum", scene1[oppSrc]);
+ tone.setTexture("Lum2", scene1[curSrc]);
+ tone.setFloat("BlendFactor", blendFactor);
+ renderProcessing(r, out, tone);
+ }
+
+ private void updateAverageLuminance(Renderer r){
+ renderProcessing(r, scene64FB, hdr64);
+ renderProcessing(r, scene8FB, hdr8);
+ renderProcessing(r, scene1FB[curSrc], hdr1);
+ }
+
+ public boolean isInitialized(){
+ return viewPort != null;
+ }
+
+ public void reshape(ViewPort vp, int w, int h){
+ if (mainSceneFB != null){
+ renderer.deleteFrameBuffer(mainSceneFB);
+ }
+
+ mainSceneFB = new FrameBuffer(w, h, 1);
+ mainScene = new Texture2D(w, h, bufFormat);
+ mainSceneFB.setDepthBuffer(Format.Depth);
+ mainSceneFB.setColorTexture(mainScene);
+ mainScene.setMagFilter(fbMagFilter);
+ mainScene.setMinFilter(fbMinFilter);
+
+ if (msFB != null){
+ renderer.deleteFrameBuffer(msFB);
+ }
+
+ tone.setTexture("Texture", mainScene);
+
+ Collection<Caps> caps = renderer.getCaps();
+ if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)){
+ msFB = new FrameBuffer(w, h, numSamples);
+ msFB.setDepthBuffer(Format.Depth);
+ msFB.setColorBuffer(bufFormat);
+ vp.setOutputFrameBuffer(msFB);
+ }else{
+ if (numSamples > 1)
+ logger.warning("FBO multisampling not supported on this GPU, request ignored.");
+
+ vp.setOutputFrameBuffer(mainSceneFB);
+ }
+
+ createLumShaders();
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp){
+ if (!enabled)
+ return;
+
+ renderer = rm.getRenderer();
+ renderManager = rm;
+ viewPort = vp;
+
+ // loadInitial()
+ fsQuad = new Picture("HDR Fullscreen Quad");
+
+ Format lumFmt = Format.RGB8;
+ scene64FB = new FrameBuffer(64, 64, 1);
+ scene64 = new Texture2D(64, 64, lumFmt);
+ scene64FB.setColorTexture(scene64);
+ scene64.setMagFilter(fbMagFilter);
+ scene64.setMinFilter(fbMinFilter);
+
+ scene8FB = new FrameBuffer(8, 8, 1);
+ scene8 = new Texture2D(8, 8, lumFmt);
+ scene8FB.setColorTexture(scene8);
+ scene8.setMagFilter(fbMagFilter);
+ scene8.setMinFilter(fbMinFilter);
+
+ scene1FB[0] = new FrameBuffer(1, 1, 1);
+ scene1[0] = new Texture2D(1, 1, lumFmt);
+ scene1FB[0].setColorTexture(scene1[0]);
+
+ scene1FB[1] = new FrameBuffer(1, 1, 1);
+ scene1[1] = new Texture2D(1, 1, lumFmt);
+ scene1FB[1].setColorTexture(scene1[1]);
+
+ // prepare tonemap shader
+ tone = new Material(manager, "Common/MatDefs/Hdr/ToneMap.j3md");
+ tone.setFloat("A", 0.18f);
+ tone.setFloat("White", 100);
+
+ // load();
+ int w = vp.getCamera().getWidth();
+ int h = vp.getCamera().getHeight();
+ reshape(vp, w, h);
+
+
+ }
+
+ public void preFrame(float tpf) {
+ if (!enabled)
+ return;
+
+ time += tpf;
+ blendFactor = (time / throttle);
+ }
+
+ public void postQueue(RenderQueue rq) {
+ }
+
+ public void postFrame(FrameBuffer out) {
+ if (!enabled)
+ return;
+
+ if (msFB != null){
+ // first render to multisampled FB
+// renderer.setFrameBuffer(msFB);
+// renderer.clearBuffers(true,true,true);
+//
+// renderManager.renderViewPortRaw(viewPort);
+
+ // render back to non-multisampled FB
+ renderer.copyFrameBuffer(msFB, mainSceneFB);
+ }else{
+// renderer.setFrameBuffer(mainSceneFB);
+// renderer.clearBuffers(true,true,false);
+//
+// renderManager.renderViewPortRaw(viewPort);
+ }
+
+ // should we update avg lum?
+ if (throttle == -1){
+ // update every frame
+ curSrc = 0;
+ oppSrc = 0;
+ blendFactor = 0;
+ time = 0;
+ updateAverageLuminance(renderer);
+ }else{
+ if (curSrc == -1){
+ curSrc = 0;
+ oppSrc = 0;
+
+ // initial update
+ updateAverageLuminance(renderer);
+
+ blendFactor = 0;
+ time = 0;
+ }else if (time > throttle){
+
+ // time to switch
+ oppSrc = curSrc;
+ curSrc = opposite(curSrc);
+
+ updateAverageLuminance(renderer);
+
+ blendFactor = 0;
+ time = 0;
+ }
+ }
+
+ // since out == mainSceneFB, tonemap into the main screen instead
+ //renderToneMap(renderer, out);
+ renderToneMap(renderer, null);
+
+ renderManager.setCamera(viewPort.getCamera(), false);
+ }
+
+ public void cleanup() {
+ if (!enabled)
+ return;
+
+ if (msFB != null)
+ renderer.deleteFrameBuffer(msFB);
+ if (mainSceneFB != null)
+ renderer.deleteFrameBuffer(mainSceneFB);
+ if (scene64FB != null){
+ renderer.deleteFrameBuffer(scene64FB);
+ renderer.deleteFrameBuffer(scene8FB);
+ renderer.deleteFrameBuffer(scene1FB[0]);
+ renderer.deleteFrameBuffer(scene1FB[1]);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/post/PreDepthProcessor.java b/engine/src/core/com/jme3/post/PreDepthProcessor.java
new file mode 100644
index 0000000..2544aab
--- /dev/null
+++ b/engine/src/core/com/jme3/post/PreDepthProcessor.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.post;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.FaceCullMode;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Processor that lays depth first, this can improve performance in complex
+ * scenes.
+ */
+public class PreDepthProcessor implements SceneProcessor {
+
+ private RenderManager rm;
+ private ViewPort vp;
+ private AssetManager assetManager;
+ private Material preDepth;
+ private RenderState forcedRS;
+
+ public PreDepthProcessor(AssetManager assetManager){
+ this.assetManager = assetManager;
+ preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md");
+ preDepth.getAdditionalRenderState().setPolyOffset(0, 0);
+ preDepth.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Back);
+
+ forcedRS = new RenderState();
+ forcedRS.setDepthTest(true);
+ forcedRS.setDepthWrite(false);
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ this.rm = rm;
+ this.vp = vp;
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ this.vp = vp;
+ }
+
+ public boolean isInitialized() {
+ return vp != null;
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void postQueue(RenderQueue rq) {
+ // lay depth first
+ rm.setForcedMaterial(preDepth);
+ rq.renderQueue(RenderQueue.Bucket.Opaque, rm, vp.getCamera(), false);
+ rm.setForcedMaterial(null);
+
+ rm.setForcedRenderState(forcedRS);
+ }
+
+ public void postFrame(FrameBuffer out) {
+ rm.setForcedRenderState(null);
+ }
+
+ public void cleanup() {
+ vp = null;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/post/SceneProcessor.java b/engine/src/core/com/jme3/post/SceneProcessor.java
new file mode 100644
index 0000000..7b73706
--- /dev/null
+++ b/engine/src/core/com/jme3/post/SceneProcessor.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.post;
+
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.texture.FrameBuffer;
+
+/**
+ * Scene processors are used to compute/render things before and after the classic render of the scene.
+ * They have to be added to a viewport and are rendered in the order they've been added
+ *
+ * @author Kirill Vainer
+ */
+public interface SceneProcessor {
+
+ /**
+ * Called in the render thread to initialize the scene processor.
+ *
+ * @param rm The render manager to which the SP was added to
+ * @param vp The viewport to which the SP is assigned
+ */
+ public void initialize(RenderManager rm, ViewPort vp);
+
+ /**
+ * Called when the resolution of the viewport has been changed.
+ * @param vp
+ */
+ public void reshape(ViewPort vp, int w, int h);
+
+ /**
+ * @return True if initialize() has been called on this SceneProcessor,
+ * false if otherwise.
+ */
+ public boolean isInitialized();
+
+ /**
+ * Called before a frame
+ *
+ * @param tpf Time per frame
+ */
+ public void preFrame(float tpf);
+
+ /**
+ * Called after the scene graph has been queued, but before it is flushed.
+ *
+ * @param rq The render queue
+ */
+ public void postQueue(RenderQueue rq);
+
+ /**
+ * Called after a frame has been rendered and the queue flushed.
+ *
+ * @param out The FB to which the scene was rendered.
+ */
+ public void postFrame(FrameBuffer out);
+
+ /**
+ * Called when the SP is removed from the RM.
+ */
+ public void cleanup();
+
+}
diff --git a/engine/src/core/com/jme3/post/package.html b/engine/src/core/com/jme3/post/package.html
new file mode 100644
index 0000000..632e715
--- /dev/null
+++ b/engine/src/core/com/jme3/post/package.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.post</code> package provides utilities for
+render processing.
+<p>
+The {@link com.jme3.post.SceneProcessor} interface is used as the base interface
+for all render processing. The SceneProcessor contains hooks for various rendering
+events.
+<p>
+One use of render processing is post-processing, which is applying effects
+on an already-rendered scene. The engine's post-processing system is implemented
+in the {@link com.jme3.post.FilterPostProcessor} class, which contains a list
+of {@link com.jme3.post.Filter filters}. Each are invoked in order to apply
+various effects on the rendered scene.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/renderer/Camera.java b/engine/src/core/com/jme3/renderer/Camera.java
new file mode 100644
index 0000000..74ba6cf
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Camera.java
@@ -0,0 +1,1436 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.*;
+import com.jme3.math.*;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Camera</code> is a standalone, purely mathematical class for doing
+ * camera-related computations.
+ *
+ * <p>
+ * Given input data such as location, orientation (direction, left, up),
+ * and viewport settings, it can compute data necessary to render objects
+ * with the graphics library. Two matrices are generated, the view matrix
+ * transforms objects from world space into eye space, while the projection
+ * matrix transforms objects from eye space into clip space.
+ * </p>
+ * <p>Another purpose of the camera class is to do frustum culling operations,
+ * defined by six planes which define a 3D frustum shape, it is possible to
+ * test if an object bounded by a mathematically defined volume is inside
+ * the camera frustum, and thus to avoid rendering objects that are outside
+ * the frustum
+ * </p>
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ */
+public class Camera implements Savable, Cloneable {
+
+ private static final Logger logger = Logger.getLogger(Camera.class.getName());
+
+ /**
+ * The <code>FrustumIntersect</code> enum is returned as a result
+ * of a culling check operation,
+ * see {@link #contains(com.jme3.bounding.BoundingVolume) }
+ */
+ public enum FrustumIntersect {
+
+ /**
+ * defines a constant assigned to spatials that are completely outside
+ * of this camera's view frustum.
+ */
+ Outside,
+ /**
+ * defines a constant assigned to spatials that are completely inside
+ * the camera's view frustum.
+ */
+ Inside,
+ /**
+ * defines a constant assigned to spatials that are intersecting one of
+ * the six planes that define the view frustum.
+ */
+ Intersects;
+ }
+ /**
+ * LEFT_PLANE represents the left plane of the camera frustum.
+ */
+ private static final int LEFT_PLANE = 0;
+ /**
+ * RIGHT_PLANE represents the right plane of the camera frustum.
+ */
+ private static final int RIGHT_PLANE = 1;
+ /**
+ * BOTTOM_PLANE represents the bottom plane of the camera frustum.
+ */
+ private static final int BOTTOM_PLANE = 2;
+ /**
+ * TOP_PLANE represents the top plane of the camera frustum.
+ */
+ private static final int TOP_PLANE = 3;
+ /**
+ * FAR_PLANE represents the far plane of the camera frustum.
+ */
+ private static final int FAR_PLANE = 4;
+ /**
+ * NEAR_PLANE represents the near plane of the camera frustum.
+ */
+ private static final int NEAR_PLANE = 5;
+ /**
+ * FRUSTUM_PLANES represents the number of planes of the camera frustum.
+ */
+ private static final int FRUSTUM_PLANES = 6;
+ /**
+ * MAX_WORLD_PLANES holds the maximum planes allowed by the system.
+ */
+ private static final int MAX_WORLD_PLANES = 6;
+ /**
+ * Camera's location
+ */
+ protected Vector3f location;
+ /**
+ * The orientation of the camera.
+ */
+ protected Quaternion rotation;
+ /**
+ * Distance from camera to near frustum plane.
+ */
+ protected float frustumNear;
+ /**
+ * Distance from camera to far frustum plane.
+ */
+ protected float frustumFar;
+ /**
+ * Distance from camera to left frustum plane.
+ */
+ protected float frustumLeft;
+ /**
+ * Distance from camera to right frustum plane.
+ */
+ protected float frustumRight;
+ /**
+ * Distance from camera to top frustum plane.
+ */
+ protected float frustumTop;
+ /**
+ * Distance from camera to bottom frustum plane.
+ */
+ protected float frustumBottom;
+ //Temporary values computed in onFrustumChange that are needed if a
+ //call is made to onFrameChange.
+ protected float[] coeffLeft;
+ protected float[] coeffRight;
+ protected float[] coeffBottom;
+ protected float[] coeffTop;
+ //view port coordinates
+ /**
+ * Percent value on display where horizontal viewing starts for this camera.
+ * Default is 0.
+ */
+ protected float viewPortLeft;
+ /**
+ * Percent value on display where horizontal viewing ends for this camera.
+ * Default is 1.
+ */
+ protected float viewPortRight;
+ /**
+ * Percent value on display where vertical viewing ends for this camera.
+ * Default is 1.
+ */
+ protected float viewPortTop;
+ /**
+ * Percent value on display where vertical viewing begins for this camera.
+ * Default is 0.
+ */
+ protected float viewPortBottom;
+ /**
+ * Array holding the planes that this camera will check for culling.
+ */
+ protected Plane[] worldPlane;
+ /**
+ * A mask value set during contains() that allows fast culling of a Node's
+ * children.
+ */
+ private int planeState;
+ protected int width;
+ protected int height;
+ protected boolean viewportChanged = true;
+ /**
+ * store the value for field parallelProjection
+ */
+ private boolean parallelProjection;
+ protected Matrix4f projectionMatrixOverride;
+ protected Matrix4f viewMatrix = new Matrix4f();
+ protected Matrix4f projectionMatrix = new Matrix4f();
+ protected Matrix4f viewProjectionMatrix = new Matrix4f();
+ private BoundingBox guiBounding = new BoundingBox();
+ /** The camera's name. */
+ protected String name;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Camera() {
+ worldPlane = new Plane[MAX_WORLD_PLANES];
+ for (int i = 0; i < MAX_WORLD_PLANES; i++) {
+ worldPlane[i] = new Plane();
+ }
+ }
+
+ /**
+ * Constructor instantiates a new <code>Camera</code> object. All
+ * values of the camera are set to default.
+ */
+ public Camera(int width, int height) {
+ this();
+ location = new Vector3f();
+ rotation = new Quaternion();
+
+ frustumNear = 1.0f;
+ frustumFar = 2.0f;
+ frustumLeft = -0.5f;
+ frustumRight = 0.5f;
+ frustumTop = 0.5f;
+ frustumBottom = -0.5f;
+
+ coeffLeft = new float[2];
+ coeffRight = new float[2];
+ coeffBottom = new float[2];
+ coeffTop = new float[2];
+
+ viewPortLeft = 0.0f;
+ viewPortRight = 1.0f;
+ viewPortTop = 1.0f;
+ viewPortBottom = 0.0f;
+
+ this.width = width;
+ this.height = height;
+
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+
+ logger.log(Level.INFO, "Camera created (W: {0}, H: {1})", new Object[]{width, height});
+ }
+
+ @Override
+ public Camera clone() {
+ try {
+ Camera cam = (Camera) super.clone();
+ cam.viewportChanged = true;
+ cam.planeState = 0;
+
+ cam.worldPlane = new Plane[MAX_WORLD_PLANES];
+ for (int i = 0; i < worldPlane.length; i++) {
+ cam.worldPlane[i] = worldPlane[i].clone();
+ }
+
+ cam.coeffLeft = new float[2];
+ cam.coeffRight = new float[2];
+ cam.coeffBottom = new float[2];
+ cam.coeffTop = new float[2];
+
+ cam.location = location.clone();
+ cam.rotation = rotation.clone();
+
+ if (projectionMatrixOverride != null) {
+ cam.projectionMatrixOverride = projectionMatrixOverride.clone();
+ }
+
+ cam.viewMatrix = viewMatrix.clone();
+ cam.projectionMatrix = projectionMatrix.clone();
+ cam.viewProjectionMatrix = viewProjectionMatrix.clone();
+ cam.guiBounding = (BoundingBox) guiBounding.clone();
+
+ cam.update();
+
+ return cam;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * This method copise the settings of the given camera.
+ *
+ * @param cam
+ * the camera we copy the settings from
+ */
+ public void copyFrom(Camera cam) {
+ location.set(cam.location);
+ rotation.set(cam.rotation);
+
+ frustumNear = cam.frustumNear;
+ frustumFar = cam.frustumFar;
+ frustumLeft = cam.frustumLeft;
+ frustumRight = cam.frustumRight;
+ frustumTop = cam.frustumTop;
+ frustumBottom = cam.frustumBottom;
+
+ coeffLeft[0] = cam.coeffLeft[0];
+ coeffLeft[1] = cam.coeffLeft[1];
+ coeffRight[0] = cam.coeffRight[0];
+ coeffRight[1] = cam.coeffRight[1];
+ coeffBottom[0] = cam.coeffBottom[0];
+ coeffBottom[1] = cam.coeffBottom[1];
+ coeffTop[0] = cam.coeffTop[0];
+ coeffTop[1] = cam.coeffTop[1];
+
+ viewPortLeft = cam.viewPortLeft;
+ viewPortRight = cam.viewPortRight;
+ viewPortTop = cam.viewPortTop;
+ viewPortBottom = cam.viewPortBottom;
+
+ this.width = cam.width;
+ this.height = cam.height;
+
+ this.planeState = cam.planeState;
+ this.viewportChanged = cam.viewportChanged;
+ for (int i = 0; i < MAX_WORLD_PLANES; ++i) {
+ worldPlane[i].setNormal(cam.worldPlane[i].getNormal());
+ worldPlane[i].setConstant(cam.worldPlane[i].getConstant());
+ }
+
+ this.parallelProjection = cam.parallelProjection;
+ if(cam.projectionMatrixOverride != null) {
+ if(this.projectionMatrixOverride == null) {
+ this.projectionMatrixOverride = cam.projectionMatrixOverride.clone();
+ } else {
+ this.projectionMatrixOverride.set(cam.projectionMatrixOverride);
+ }
+ } else {
+ this.projectionMatrixOverride = null;
+ }
+ this.viewMatrix.set(cam.viewMatrix);
+ this.projectionMatrix.set(cam.projectionMatrix);
+ this.viewProjectionMatrix.set(cam.viewProjectionMatrix);
+
+ this.guiBounding.setXExtent(cam.guiBounding.getXExtent());
+ this.guiBounding.setYExtent(cam.guiBounding.getYExtent());
+ this.guiBounding.setZExtent(cam.guiBounding.getZExtent());
+ this.guiBounding.setCenter(cam.guiBounding.getCenter());
+ this.guiBounding.setCheckPlane(cam.guiBounding.getCheckPlane());
+
+ this.name = cam.name;
+ }
+
+ /**
+ * This method sets the cameras name.
+ * @param name the cameras name
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * This method returns the cameras name.
+ * @return the cameras name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets a clipPlane for this camera.
+ * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
+ * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
+ * more info here
+ * <ul>
+ * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a>
+ * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a>
+ * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a>
+ * </ul>
+ *
+ * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
+ * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
+ * @param clipPlane the plane
+ * @param side the side the camera stands from the plane
+ */
+ public void setClipPlane(Plane clipPlane, Plane.Side side) {
+ float sideFactor = 1;
+ if (side == Plane.Side.Negative) {
+ sideFactor = -1;
+ }
+ //we are on the other side of the plane no need to clip anymore.
+ if (clipPlane.whichSide(location) == side) {
+ return;
+ }
+ Matrix4f p = projectionMatrix.clone();
+
+ Matrix4f ivm = viewMatrix.clone();
+
+ Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant());
+ Vector3f pp = ivm.mult(point);
+ Vector3f pn = ivm.multNormal(clipPlane.getNormal(), null);
+ Vector4f clipPlaneV = new Vector4f(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, -(pp.dot(pn)) * sideFactor);
+
+ Vector4f v = new Vector4f(0, 0, 0, 0);
+
+ v.x = (Math.signum(clipPlaneV.x) + p.m02) / p.m00;
+ v.y = (Math.signum(clipPlaneV.y) + p.m12) / p.m11;
+ v.z = -1.0f;
+ v.w = (1.0f + p.m22) / p.m23;
+
+ float dot = clipPlaneV.dot(v);//clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w;
+ Vector4f c = clipPlaneV.mult(2.0f / dot);
+
+ p.m20 = c.x - p.m30;
+ p.m21 = c.y - p.m31;
+ p.m22 = c.z - p.m32;
+ p.m23 = c.w - p.m33;
+ setProjectionMatrix(p);
+ }
+
+ /**
+ * Sets a clipPlane for this camera.
+ * The cliPlane is used to recompute the projectionMatrix using the plane as the near plane
+ * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel
+ * more info here
+ * <ul>
+ * <li><a href="http://www.terathon.com/code/oblique.html">http://www.terathon.com/code/oblique.html</a></li>
+ * <li><a href="http://aras-p.info/texts/obliqueortho.html">http://aras-p.info/texts/obliqueortho.html</a></li>
+ * <li><a href="http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html">
+ * http://hacksoflife.blogspot.com/2008/12/every-now-and-then-i-come-across.html</a></li>
+ * </ul>
+ *
+ * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket.
+ * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java
+ * @param clipPlane the plane
+ */
+ public void setClipPlane(Plane clipPlane) {
+ setClipPlane(clipPlane, clipPlane.whichSide(location));
+ }
+
+ /**
+ * Resizes this camera's view with the given width and height. This is
+ * similar to constructing a new camera, but reusing the same Object. This
+ * method is called by an associated {@link RenderManager} to notify the camera of
+ * changes in the display dimensions.
+ *
+ * @param width the view width
+ * @param height the view height
+ * @param fixAspect If true, the camera's aspect ratio will be recomputed.
+ * Recomputing the aspect ratio requires changing the frustum values.
+ */
+ public void resize(int width, int height, boolean fixAspect) {
+ this.width = width;
+ this.height = height;
+ onViewPortChange();
+
+ if (fixAspect /*&& !parallelProjection*/) {
+ frustumRight = frustumTop * ((float) width / height);
+ frustumLeft = -frustumRight;
+ onFrustumChange();
+ }
+ }
+
+ /**
+ * <code>getFrustumBottom</code> returns the value of the bottom frustum
+ * plane.
+ *
+ * @return the value of the bottom frustum plane.
+ */
+ public float getFrustumBottom() {
+ return frustumBottom;
+ }
+
+ /**
+ * <code>setFrustumBottom</code> sets the value of the bottom frustum
+ * plane.
+ *
+ * @param frustumBottom the value of the bottom frustum plane.
+ */
+ public void setFrustumBottom(float frustumBottom) {
+ this.frustumBottom = frustumBottom;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getFrustumFar</code> gets the value of the far frustum plane.
+ *
+ * @return the value of the far frustum plane.
+ */
+ public float getFrustumFar() {
+ return frustumFar;
+ }
+
+ /**
+ * <code>setFrustumFar</code> sets the value of the far frustum plane.
+ *
+ * @param frustumFar the value of the far frustum plane.
+ */
+ public void setFrustumFar(float frustumFar) {
+ this.frustumFar = frustumFar;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getFrustumLeft</code> gets the value of the left frustum plane.
+ *
+ * @return the value of the left frustum plane.
+ */
+ public float getFrustumLeft() {
+ return frustumLeft;
+ }
+
+ /**
+ * <code>setFrustumLeft</code> sets the value of the left frustum plane.
+ *
+ * @param frustumLeft the value of the left frustum plane.
+ */
+ public void setFrustumLeft(float frustumLeft) {
+ this.frustumLeft = frustumLeft;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getFrustumNear</code> gets the value of the near frustum plane.
+ *
+ * @return the value of the near frustum plane.
+ */
+ public float getFrustumNear() {
+ return frustumNear;
+ }
+
+ /**
+ * <code>setFrustumNear</code> sets the value of the near frustum plane.
+ *
+ * @param frustumNear the value of the near frustum plane.
+ */
+ public void setFrustumNear(float frustumNear) {
+ this.frustumNear = frustumNear;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getFrustumRight</code> gets the value of the right frustum plane.
+ *
+ * @return frustumRight the value of the right frustum plane.
+ */
+ public float getFrustumRight() {
+ return frustumRight;
+ }
+
+ /**
+ * <code>setFrustumRight</code> sets the value of the right frustum plane.
+ *
+ * @param frustumRight the value of the right frustum plane.
+ */
+ public void setFrustumRight(float frustumRight) {
+ this.frustumRight = frustumRight;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getFrustumTop</code> gets the value of the top frustum plane.
+ *
+ * @return the value of the top frustum plane.
+ */
+ public float getFrustumTop() {
+ return frustumTop;
+ }
+
+ /**
+ * <code>setFrustumTop</code> sets the value of the top frustum plane.
+ *
+ * @param frustumTop the value of the top frustum plane.
+ */
+ public void setFrustumTop(float frustumTop) {
+ this.frustumTop = frustumTop;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>getLocation</code> retrieves the location vector of the camera.
+ *
+ * @return the position of the camera.
+ * @see Camera#getLocation()
+ */
+ public Vector3f getLocation() {
+ return location;
+ }
+
+ /**
+ * <code>getRotation</code> retrieves the rotation quaternion of the camera.
+ *
+ * @return the rotation of the camera.
+ */
+ public Quaternion getRotation() {
+ return rotation;
+ }
+
+ /**
+ * <code>getDirection</code> retrieves the direction vector the camera is
+ * facing.
+ *
+ * @return the direction the camera is facing.
+ * @see Camera#getDirection()
+ */
+ public Vector3f getDirection() {
+ return rotation.getRotationColumn(2);
+ }
+
+ /**
+ * <code>getLeft</code> retrieves the left axis of the camera.
+ *
+ * @return the left axis of the camera.
+ * @see Camera#getLeft()
+ */
+ public Vector3f getLeft() {
+ return rotation.getRotationColumn(0);
+ }
+
+ /**
+ * <code>getUp</code> retrieves the up axis of the camera.
+ *
+ * @return the up axis of the camera.
+ * @see Camera#getUp()
+ */
+ public Vector3f getUp() {
+ return rotation.getRotationColumn(1);
+ }
+
+ /**
+ * <code>getDirection</code> retrieves the direction vector the camera is
+ * facing.
+ *
+ * @return the direction the camera is facing.
+ * @see Camera#getDirection()
+ */
+ public Vector3f getDirection(Vector3f store) {
+ return rotation.getRotationColumn(2, store);
+ }
+
+ /**
+ * <code>getLeft</code> retrieves the left axis of the camera.
+ *
+ * @return the left axis of the camera.
+ * @see Camera#getLeft()
+ */
+ public Vector3f getLeft(Vector3f store) {
+ return rotation.getRotationColumn(0, store);
+ }
+
+ /**
+ * <code>getUp</code> retrieves the up axis of the camera.
+ *
+ * @return the up axis of the camera.
+ * @see Camera#getUp()
+ */
+ public Vector3f getUp(Vector3f store) {
+ return rotation.getRotationColumn(1, store);
+ }
+
+ /**
+ * <code>setLocation</code> sets the position of the camera.
+ *
+ * @param location the position of the camera.
+ */
+ public void setLocation(Vector3f location) {
+ this.location.set(location);
+ onFrameChange();
+ }
+
+ /**
+ * <code>setRotation</code> sets the orientation of this camera.
+ * This will be equivelant to setting each of the axes:
+ * <code><br>
+ * cam.setLeft(rotation.getRotationColumn(0));<br>
+ * cam.setUp(rotation.getRotationColumn(1));<br>
+ * cam.setDirection(rotation.getRotationColumn(2));<br>
+ * </code>
+ *
+ * @param rotation the rotation of this camera
+ */
+ public void setRotation(Quaternion rotation) {
+ this.rotation.set(rotation);
+ onFrameChange();
+ }
+
+ /**
+ * <code>lookAtDirection</code> sets the direction the camera is facing
+ * given a direction and an up vector.
+ *
+ * @param direction the direction this camera is facing.
+ */
+ public void lookAtDirection(Vector3f direction, Vector3f up) {
+ this.rotation.lookAt(direction, up);
+ onFrameChange();
+ }
+
+ /**
+ * <code>setAxes</code> sets the axes (left, up and direction) for this
+ * camera.
+ *
+ * @param left the left axis of the camera.
+ * @param up the up axis of the camera.
+ * @param direction the direction the camera is facing.
+ *
+ * @see Camera#setAxes(com.jme3.math.Quaternion)
+ */
+ public void setAxes(Vector3f left, Vector3f up, Vector3f direction) {
+ this.rotation.fromAxes(left, up, direction);
+ onFrameChange();
+ }
+
+ /**
+ * <code>setAxes</code> uses a rotational matrix to set the axes of the
+ * camera.
+ *
+ * @param axes the matrix that defines the orientation of the camera.
+ */
+ public void setAxes(Quaternion axes) {
+ this.rotation.set(axes);
+ onFrameChange();
+ }
+
+ /**
+ * normalize normalizes the camera vectors.
+ */
+ public void normalize() {
+ this.rotation.normalizeLocal();
+ onFrameChange();
+ }
+
+ /**
+ * <code>setFrustum</code> sets the frustum of this camera object.
+ *
+ * @param near the near plane.
+ * @param far the far plane.
+ * @param left the left plane.
+ * @param right the right plane.
+ * @param top the top plane.
+ * @param bottom the bottom plane.
+ * @see Camera#setFrustum(float, float, float, float,
+ * float, float)
+ */
+ public void setFrustum(float near, float far, float left, float right,
+ float top, float bottom) {
+
+ frustumNear = near;
+ frustumFar = far;
+ frustumLeft = left;
+ frustumRight = right;
+ frustumTop = top;
+ frustumBottom = bottom;
+ onFrustumChange();
+ }
+
+ /**
+ * <code>setFrustumPerspective</code> defines the frustum for the camera. This
+ * frustum is defined by a viewing angle, aspect ratio, and near/far planes
+ *
+ * @param fovY Frame of view angle along the Y in degrees.
+ * @param aspect Width:Height ratio
+ * @param near Near view plane distance
+ * @param far Far view plane distance
+ */
+ public void setFrustumPerspective(float fovY, float aspect, float near,
+ float far) {
+ if (Float.isNaN(aspect) || Float.isInfinite(aspect)) {
+ // ignore.
+ logger.log(Level.WARNING, "Invalid aspect given to setFrustumPerspective: {0}", aspect);
+ return;
+ }
+
+ float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * near;
+ float w = h * aspect;
+ frustumLeft = -w;
+ frustumRight = w;
+ frustumBottom = -h;
+ frustumTop = h;
+ frustumNear = near;
+ frustumFar = far;
+
+ onFrustumChange();
+ }
+
+ /**
+ * <code>setFrame</code> sets the orientation and location of the camera.
+ *
+ * @param location the point position of the camera.
+ * @param left the left axis of the camera.
+ * @param up the up axis of the camera.
+ * @param direction the facing of the camera.
+ * @see Camera#setFrame(com.jme3.math.Vector3f,
+ * com.jme3.math.Vector3f, com.jme3.math.Vector3f, com.jme3.math.Vector3f)
+ */
+ public void setFrame(Vector3f location, Vector3f left, Vector3f up,
+ Vector3f direction) {
+
+ this.location = location;
+ this.rotation.fromAxes(left, up, direction);
+ onFrameChange();
+ }
+
+ /**
+ * <code>lookAt</code> is a convienence method for auto-setting the frame
+ * based on a world position the user desires the camera to look at. It
+ * repoints the camera towards the given position using the difference
+ * between the position and the current camera location as a direction
+ * vector and the 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.
+ * (typically {0, 1, 0} in jME.)
+ */
+ public void lookAt(Vector3f pos, Vector3f worldUpVector) {
+ TempVars vars = TempVars.get();
+ Vector3f newDirection = vars.vect1;
+ Vector3f newUp = vars.vect2;
+ Vector3f newLeft = vars.vect3;
+
+ newDirection.set(pos).subtractLocal(location).normalizeLocal();
+
+ newUp.set(worldUpVector).normalizeLocal();
+ if (newUp.equals(Vector3f.ZERO)) {
+ newUp.set(Vector3f.UNIT_Y);
+ }
+
+ newLeft.set(newUp).crossLocal(newDirection).normalizeLocal();
+ if (newLeft.equals(Vector3f.ZERO)) {
+ if (newDirection.x != 0) {
+ newLeft.set(newDirection.y, -newDirection.x, 0f);
+ } else {
+ newLeft.set(0f, newDirection.z, -newDirection.y);
+ }
+ }
+
+ newUp.set(newDirection).crossLocal(newLeft).normalizeLocal();
+
+ this.rotation.fromAxes(newLeft, newUp, newDirection);
+ this.rotation.normalizeLocal();
+ vars.release();
+
+ onFrameChange();
+ }
+
+ /**
+ * <code>setFrame</code> sets the orientation and location of the camera.
+ *
+ * @param location
+ * the point position of the camera.
+ * @param axes
+ * the orientation of the camera.
+ */
+ public void setFrame(Vector3f location, Quaternion axes) {
+ this.location = location;
+ this.rotation.set(axes);
+ onFrameChange();
+ }
+
+ /**
+ * <code>update</code> updates the camera parameters by calling
+ * <code>onFrustumChange</code>,<code>onViewPortChange</code> and
+ * <code>onFrameChange</code>.
+ *
+ * @see Camera#update()
+ */
+ public void update() {
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+ }
+
+ /**
+ * <code>getPlaneState</code> returns the state of the frustum planes. So
+ * checks can be made as to which frustum plane has been examined for
+ * culling thus far.
+ *
+ * @return the current plane state int.
+ */
+ public int getPlaneState() {
+ return planeState;
+ }
+
+ /**
+ * <code>setPlaneState</code> sets the state to keep track of tested
+ * planes for culling.
+ *
+ * @param planeState the updated state.
+ */
+ public void setPlaneState(int planeState) {
+ this.planeState = planeState;
+ }
+
+ /**
+ * <code>getViewPortLeft</code> gets the left boundary of the viewport
+ *
+ * @return the left boundary of the viewport
+ */
+ public float getViewPortLeft() {
+ return viewPortLeft;
+ }
+
+ /**
+ * <code>setViewPortLeft</code> sets the left boundary of the viewport
+ *
+ * @param left the left boundary of the viewport
+ */
+ public void setViewPortLeft(float left) {
+ viewPortLeft = left;
+ onViewPortChange();
+ }
+
+ /**
+ * <code>getViewPortRight</code> gets the right boundary of the viewport
+ *
+ * @return the right boundary of the viewport
+ */
+ public float getViewPortRight() {
+ return viewPortRight;
+ }
+
+ /**
+ * <code>setViewPortRight</code> sets the right boundary of the viewport
+ *
+ * @param right the right boundary of the viewport
+ */
+ public void setViewPortRight(float right) {
+ viewPortRight = right;
+ onViewPortChange();
+ }
+
+ /**
+ * <code>getViewPortTop</code> gets the top boundary of the viewport
+ *
+ * @return the top boundary of the viewport
+ */
+ public float getViewPortTop() {
+ return viewPortTop;
+ }
+
+ /**
+ * <code>setViewPortTop</code> sets the top boundary of the viewport
+ *
+ * @param top the top boundary of the viewport
+ */
+ public void setViewPortTop(float top) {
+ viewPortTop = top;
+ onViewPortChange();
+ }
+
+ /**
+ * <code>getViewPortBottom</code> gets the bottom boundary of the viewport
+ *
+ * @return the bottom boundary of the viewport
+ */
+ public float getViewPortBottom() {
+ return viewPortBottom;
+ }
+
+ /**
+ * <code>setViewPortBottom</code> sets the bottom boundary of the viewport
+ *
+ * @param bottom the bottom boundary of the viewport
+ */
+ public void setViewPortBottom(float bottom) {
+ viewPortBottom = bottom;
+ onViewPortChange();
+ }
+
+ /**
+ * <code>setViewPort</code> sets the boundaries of the viewport
+ *
+ * @param left the left boundary of the viewport (default: 0)
+ * @param right the right boundary of the viewport (default: 1)
+ * @param bottom the bottom boundary of the viewport (default: 0)
+ * @param top the top boundary of the viewport (default: 1)
+ */
+ public void setViewPort(float left, float right, float bottom, float top) {
+ this.viewPortLeft = left;
+ this.viewPortRight = right;
+ this.viewPortBottom = bottom;
+ this.viewPortTop = top;
+ onViewPortChange();
+ }
+
+ /**
+ * Returns the pseudo distance from the given position to the near
+ * plane of the camera. This is used for render queue sorting.
+ * @param pos The position to compute a distance to.
+ * @return Distance from the far plane to the point.
+ */
+ public float distanceToNearPlane(Vector3f pos) {
+ return worldPlane[NEAR_PLANE].pseudoDistance(pos);
+ }
+
+ /**
+ * <code>contains</code> tests a bounding volume against the planes of the
+ * camera's frustum. The frustums planes are set such that the normals all
+ * face in towards the viewable scene. Therefore, if the bounding volume is
+ * on the negative side of the plane is can be culled out.
+ *
+ * NOTE: This method is used internally for culling, for public usage,
+ * the plane state of the bounding volume must be saved and restored, e.g:
+ * <code>BoundingVolume bv;<br/>
+ * Camera c;<br/>
+ * int planeState = bv.getPlaneState();<br/>
+ * bv.setPlaneState(0);<br/>
+ * c.contains(bv);<br/>
+ * bv.setPlaneState(plateState);<br/>
+ * </code>
+ *
+ * @param bound the bound to check for culling
+ * @return See enums in <code>FrustumIntersect</code>
+ */
+ public FrustumIntersect contains(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
+ }
+ int planeId = (planeCounter == FRUSTUM_PLANES) ? bound.getCheckPlane() : planeCounter;
+// int planeId = planeCounter;
+
+ mask = 1 << (planeId);
+ if ((planeState & mask) == 0) {
+ Plane.Side side = bound.whichSide(worldPlane[planeId]);
+
+ if (side == Plane.Side.Negative) {
+ //object is outside of frustum
+ bound.setCheckPlane(planeId);
+ return FrustumIntersect.Outside;
+ } else if (side == Plane.Side.Positive) {
+ //object is visible on *this* plane, so mark this plane
+ //so that we don't check it for sub nodes.
+ planeState |= mask;
+ } else {
+ rVal = FrustumIntersect.Intersects;
+ }
+ }
+ }
+
+ return rVal;
+ }
+
+ /**
+ * <code>containsGui</code> tests a bounding volume against the ortho
+ * bounding box of the camera. A bounding box spanning from
+ * 0, 0 to Width, Height. Constrained by the viewport settings on the
+ * camera.
+ *
+ * @param bound the bound to check for culling
+ * @return True if the camera contains the gui element bounding volume.
+ */
+ public boolean containsGui(BoundingVolume bound) {
+ return guiBounding.intersects(bound);
+ }
+
+ /**
+ * @return the view matrix of the camera.
+ * The view matrix transforms world space into eye space.
+ * This matrix is usually defined by the position and
+ * orientation of the camera.
+ */
+ public Matrix4f getViewMatrix() {
+ return viewMatrix;
+ }
+
+ /**
+ * Overrides the projection matrix used by the camera. Will
+ * use the matrix for computing the view projection matrix as well.
+ * Use null argument to return to normal functionality.
+ *
+ * @param projMatrix
+ */
+ public void setProjectionMatrix(Matrix4f projMatrix) {
+ projectionMatrixOverride = projMatrix;
+ updateViewProjection();
+ }
+
+ /**
+ * @return the projection matrix of the camera.
+ * The view projection matrix transforms eye space into clip space.
+ * This matrix is usually defined by the viewport and perspective settings
+ * of the camera.
+ */
+ public Matrix4f getProjectionMatrix() {
+ if (projectionMatrixOverride != null) {
+ return projectionMatrixOverride;
+ }
+
+ return projectionMatrix;
+ }
+
+ /**
+ * Updates the view projection matrix.
+ */
+ public void updateViewProjection() {
+ if (projectionMatrixOverride != null) {
+ viewProjectionMatrix.set(projectionMatrixOverride).multLocal(viewMatrix);
+ } else {
+ //viewProjectionMatrix.set(viewMatrix).multLocal(projectionMatrix);
+ viewProjectionMatrix.set(projectionMatrix).multLocal(viewMatrix);
+ }
+ }
+
+ /**
+ * @return The result of multiplying the projection matrix by the view
+ * matrix. This matrix is required for rendering an object. It is
+ * precomputed so as to not compute it every time an object is rendered.
+ */
+ public Matrix4f getViewProjectionMatrix() {
+ return viewProjectionMatrix;
+ }
+
+ /**
+ * @return True if the viewport (width, height, left, right, bottom, up)
+ * has been changed. This is needed in the renderer so that the proper
+ * viewport can be set-up.
+ */
+ public boolean isViewportChanged() {
+ return viewportChanged;
+ }
+
+ /**
+ * Clears the viewport changed flag once it has been updated inside
+ * the renderer.
+ */
+ public void clearViewportChanged() {
+ viewportChanged = false;
+ }
+
+ /**
+ * Called when the viewport has been changed.
+ */
+ public void onViewPortChange() {
+ viewportChanged = true;
+ setGuiBounding();
+ }
+
+ private void setGuiBounding() {
+ float sx = width * viewPortLeft;
+ float ex = width * viewPortRight;
+ float sy = height * viewPortBottom;
+ float ey = height * viewPortTop;
+ float xExtent = Math.max(0f, (ex - sx) / 2f);
+ float yExtent = Math.max(0f, (ey - sy) / 2f);
+ guiBounding.setCenter(new Vector3f(sx + xExtent, sy + yExtent, 0));
+ guiBounding.setXExtent(xExtent);
+ guiBounding.setYExtent(yExtent);
+ guiBounding.setZExtent(Float.MAX_VALUE);
+ }
+
+ /**
+ * <code>onFrustumChange</code> updates the frustum to reflect any changes
+ * made to the planes. The new frustum values are kept in a temporary
+ * location for use when calculating the new frame. The projection
+ * matrix is updated to reflect the current values of the frustum.
+ */
+ public void onFrustumChange() {
+ if (!isParallelProjection()) {
+ float nearSquared = frustumNear * frustumNear;
+ float leftSquared = frustumLeft * frustumLeft;
+ float rightSquared = frustumRight * frustumRight;
+ float bottomSquared = frustumBottom * frustumBottom;
+ float topSquared = frustumTop * frustumTop;
+
+ float inverseLength = FastMath.invSqrt(nearSquared + leftSquared);
+ coeffLeft[0] = frustumNear * inverseLength;
+ coeffLeft[1] = -frustumLeft * inverseLength;
+
+ inverseLength = FastMath.invSqrt(nearSquared + rightSquared);
+ coeffRight[0] = -frustumNear * inverseLength;
+ coeffRight[1] = frustumRight * inverseLength;
+
+ inverseLength = FastMath.invSqrt(nearSquared + bottomSquared);
+ coeffBottom[0] = frustumNear * inverseLength;
+ coeffBottom[1] = -frustumBottom * inverseLength;
+
+ inverseLength = FastMath.invSqrt(nearSquared + topSquared);
+ coeffTop[0] = -frustumNear * inverseLength;
+ coeffTop[1] = frustumTop * inverseLength;
+ } else {
+ coeffLeft[0] = 1;
+ coeffLeft[1] = 0;
+
+ coeffRight[0] = -1;
+ coeffRight[1] = 0;
+
+ coeffBottom[0] = 1;
+ coeffBottom[1] = 0;
+
+ coeffTop[0] = -1;
+ coeffTop[1] = 0;
+ }
+
+ projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight, frustumTop, frustumBottom, parallelProjection);
+// projectionMatrix.transposeLocal();
+
+ // The frame is effected by the frustum values
+ // update it as well
+ onFrameChange();
+ }
+
+ /**
+ * <code>onFrameChange</code> updates the view frame of the camera.
+ */
+ public void onFrameChange() {
+ TempVars vars = TempVars.get();
+
+ Vector3f left = getLeft(vars.vect1);
+ Vector3f direction = getDirection(vars.vect2);
+ Vector3f up = getUp(vars.vect3);
+
+ float dirDotLocation = direction.dot(location);
+
+ // left plane
+ Vector3f leftPlaneNormal = worldPlane[LEFT_PLANE].getNormal();
+ leftPlaneNormal.x = left.x * coeffLeft[0];
+ leftPlaneNormal.y = left.y * coeffLeft[0];
+ leftPlaneNormal.z = left.z * coeffLeft[0];
+ leftPlaneNormal.addLocal(direction.x * coeffLeft[1], direction.y
+ * coeffLeft[1], direction.z * coeffLeft[1]);
+ worldPlane[LEFT_PLANE].setConstant(location.dot(leftPlaneNormal));
+
+ // right plane
+ Vector3f rightPlaneNormal = worldPlane[RIGHT_PLANE].getNormal();
+ rightPlaneNormal.x = left.x * coeffRight[0];
+ rightPlaneNormal.y = left.y * coeffRight[0];
+ rightPlaneNormal.z = left.z * coeffRight[0];
+ rightPlaneNormal.addLocal(direction.x * coeffRight[1], direction.y
+ * coeffRight[1], direction.z * coeffRight[1]);
+ worldPlane[RIGHT_PLANE].setConstant(location.dot(rightPlaneNormal));
+
+ // bottom plane
+ Vector3f bottomPlaneNormal = worldPlane[BOTTOM_PLANE].getNormal();
+ bottomPlaneNormal.x = up.x * coeffBottom[0];
+ bottomPlaneNormal.y = up.y * coeffBottom[0];
+ bottomPlaneNormal.z = up.z * coeffBottom[0];
+ bottomPlaneNormal.addLocal(direction.x * coeffBottom[1], direction.y
+ * coeffBottom[1], direction.z * coeffBottom[1]);
+ worldPlane[BOTTOM_PLANE].setConstant(location.dot(bottomPlaneNormal));
+
+ // top plane
+ Vector3f topPlaneNormal = worldPlane[TOP_PLANE].getNormal();
+ topPlaneNormal.x = up.x * coeffTop[0];
+ topPlaneNormal.y = up.y * coeffTop[0];
+ topPlaneNormal.z = up.z * coeffTop[0];
+ topPlaneNormal.addLocal(direction.x * coeffTop[1], direction.y
+ * coeffTop[1], direction.z * coeffTop[1]);
+ worldPlane[TOP_PLANE].setConstant(location.dot(topPlaneNormal));
+
+ if (isParallelProjection()) {
+ worldPlane[LEFT_PLANE].setConstant(worldPlane[LEFT_PLANE].getConstant() + frustumLeft);
+ worldPlane[RIGHT_PLANE].setConstant(worldPlane[RIGHT_PLANE].getConstant() - frustumRight);
+ worldPlane[TOP_PLANE].setConstant(worldPlane[TOP_PLANE].getConstant() - frustumTop);
+ worldPlane[BOTTOM_PLANE].setConstant(worldPlane[BOTTOM_PLANE].getConstant() + frustumBottom);
+ }
+
+ // far plane
+ worldPlane[FAR_PLANE].setNormal(left);
+ worldPlane[FAR_PLANE].setNormal(-direction.x, -direction.y, -direction.z);
+ worldPlane[FAR_PLANE].setConstant(-(dirDotLocation + frustumFar));
+
+ // near plane
+ worldPlane[NEAR_PLANE].setNormal(direction.x, direction.y, direction.z);
+ worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear);
+
+ viewMatrix.fromFrame(location, direction, up, left);
+
+ vars.release();
+
+// viewMatrix.transposeLocal();
+ updateViewProjection();
+ }
+
+ /**
+ * @return true if parallel projection is enable, false if in normal perspective mode
+ * @see #setParallelProjection(boolean)
+ */
+ public boolean isParallelProjection() {
+ return this.parallelProjection;
+ }
+
+ /**
+ * Enable/disable parallel projection.
+ *
+ * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode
+ */
+ public void setParallelProjection(final boolean value) {
+ this.parallelProjection = value;
+ onFrustumChange();
+ }
+
+ /**
+ * @see Camera#getWorldCoordinates
+ */
+ public Vector3f getWorldCoordinates(Vector2f screenPos, float zPos) {
+ return getWorldCoordinates(screenPos, zPos, null);
+ }
+
+ /**
+ * @see Camera#getWorldCoordinates
+ */
+ public Vector3f getWorldCoordinates(Vector2f screenPosition,
+ float zPos, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+ Matrix4f inverseMat = new Matrix4f(viewProjectionMatrix);
+ inverseMat.invertLocal();
+
+ store.set(
+ (screenPosition.x / getWidth() - viewPortLeft) / (viewPortRight - viewPortLeft) * 2 - 1,
+ (screenPosition.y / getHeight() - viewPortBottom) / (viewPortTop - viewPortBottom) * 2 - 1,
+ zPos * 2 - 1);
+
+ float w = inverseMat.multProj(store, store);
+ store.multLocal(1f / w);
+
+ return store;
+ }
+
+ /**
+ * Converts the given position from world space to screen space.
+ *
+ * @see Camera#getScreenCoordinates
+ */
+ public Vector3f getScreenCoordinates(Vector3f worldPos) {
+ return getScreenCoordinates(worldPos, null);
+ }
+
+ /**
+ * Converts the given position from world space to screen space.
+ *
+ * @see Camera#getScreenCoordinates(Vector3f, Vector3f)
+ */
+ public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) {
+ if (store == null) {
+ store = new Vector3f();
+ }
+
+// TempVars vars = vars.lock();
+// Quaternion tmp_quat = vars.quat1;
+// tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 );
+// viewProjectionMatrix.mult(tmp_quat, tmp_quat);
+// tmp_quat.multLocal( 1.0f / tmp_quat.getW() );
+// store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) * getWidth();
+// store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) * getHeight();
+// store.z = ( tmp_quat.getZ() + 1 ) / 2;
+// vars.release();
+
+ float w = viewProjectionMatrix.multProj(worldPosition, store);
+ store.divideLocal(w);
+
+ store.x = ((store.x + 1f) * (viewPortRight - viewPortLeft) / 2f + viewPortLeft) * getWidth();
+ store.y = ((store.y + 1f) * (viewPortTop - viewPortBottom) / 2f + viewPortBottom) * getHeight();
+ store.z = (store.z + 1f) / 2f;
+
+ return store;
+ }
+
+ /**
+ * @return the width/resolution of the display.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return the height/resolution of the display.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public String toString() {
+ return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n"
+ + "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n"
+ + "near=" + frustumNear + ", far=" + frustumFar + "]";
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(location, "location", Vector3f.ZERO);
+ capsule.write(rotation, "rotation", Quaternion.DIRECTION_Z);
+ capsule.write(frustumNear, "frustumNear", 1);
+ capsule.write(frustumFar, "frustumFar", 2);
+ capsule.write(frustumLeft, "frustumLeft", -0.5f);
+ capsule.write(frustumRight, "frustumRight", 0.5f);
+ capsule.write(frustumTop, "frustumTop", 0.5f);
+ capsule.write(frustumBottom, "frustumBottom", -0.5f);
+ capsule.write(coeffLeft, "coeffLeft", new float[2]);
+ capsule.write(coeffRight, "coeffRight", new float[2]);
+ capsule.write(coeffBottom, "coeffBottom", new float[2]);
+ capsule.write(coeffTop, "coeffTop", new float[2]);
+ 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(name, "name", null);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone());
+ rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone());
+ frustumNear = capsule.readFloat("frustumNear", 1);
+ frustumFar = capsule.readFloat("frustumFar", 2);
+ frustumLeft = capsule.readFloat("frustumLeft", -0.5f);
+ frustumRight = capsule.readFloat("frustumRight", 0.5f);
+ frustumTop = capsule.readFloat("frustumTop", 0.5f);
+ frustumBottom = capsule.readFloat("frustumBottom", -0.5f);
+ coeffLeft = capsule.readFloatArray("coeffLeft", new float[2]);
+ coeffRight = capsule.readFloatArray("coeffRight", new float[2]);
+ coeffBottom = capsule.readFloatArray("coeffBottom", new float[2]);
+ coeffTop = capsule.readFloatArray("coeffTop", new float[2]);
+ viewPortLeft = capsule.readFloat("viewPortLeft", 0);
+ viewPortRight = capsule.readFloat("viewPortRight", 1);
+ viewPortTop = capsule.readFloat("viewPortTop", 1);
+ viewPortBottom = capsule.readFloat("viewPortBottom", 0);
+ width = capsule.readInt("width", 1);
+ height = capsule.readInt("height", 1);
+ name = capsule.readString("name", null);
+ onFrustumChange();
+ onViewPortChange();
+ onFrameChange();
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/Caps.java b/engine/src/core/com/jme3/renderer/Caps.java
new file mode 100644
index 0000000..4dd1ed8
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Caps.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.shader.Shader;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.FrameBuffer.RenderBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import java.util.Collection;
+
+/**
+ * <code>Caps</code> is an enum specifying a capability that the {@link Renderer}
+ * supports.
+ *
+ * @author Kirill Vainer
+ */
+public enum Caps {
+
+ /**
+ * Supports {@link FrameBuffer FrameBuffers}.
+ * <p>
+ * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.<br>
+ * OpenGL ES: Renderer supports OpenGL ES 2.0.
+ */
+ FrameBuffer,
+
+ /**
+ * Supports framebuffer Multiple Render Targets (MRT)
+ * <p>
+ * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension
+ */
+ FrameBufferMRT,
+
+ /**
+ * Supports framebuffer multi-sampling
+ * <p>
+ * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension<br>
+ * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or
+ * GL_ANGLE_framebuffer_multisample.
+ */
+ FrameBufferMultisample,
+
+ /**
+ * Supports texture multi-sampling
+ * <p>
+ * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension<br>
+ * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture
+ * extension.
+ */
+ TextureMultisample,
+
+ /**
+ * Supports OpenGL 2.0 or OpenGL ES 2.0.
+ */
+ OpenGL20,
+
+ /**
+ * Supports OpenGL 2.1
+ */
+ OpenGL21,
+
+ /**
+ * Supports OpenGL 3.0
+ */
+ OpenGL30,
+
+ /**
+ * Supports OpenGL 3.1
+ */
+ OpenGL31,
+
+ /**
+ * Supports OpenGL 3.2
+ */
+ OpenGL32,
+
+ /**
+ * Supports OpenGL ARB program.
+ * <p>
+ * OpenGL: Renderer exposes ARB_vertex_program and ARB_fragment_program
+ * extensions.
+ */
+ ARBprogram,
+
+ /**
+ * Supports GLSL 1.0
+ */
+ GLSL100,
+
+ /**
+ * Supports GLSL 1.1
+ */
+ GLSL110,
+
+ /**
+ * Supports GLSL 1.2
+ */
+ GLSL120,
+
+ /**
+ * Supports GLSL 1.3
+ */
+ GLSL130,
+
+ /**
+ * Supports GLSL 1.4
+ */
+ GLSL140,
+
+ /**
+ * Supports GLSL 1.5
+ */
+ GLSL150,
+
+ /**
+ * Supports GLSL 3.3
+ */
+ GLSL330,
+
+ /**
+ * Supports reading from textures inside the vertex shader.
+ */
+ VertexTextureFetch,
+
+ /**
+ * Supports geometry shader.
+ */
+ GeometryShader,
+
+ /**
+ * Supports texture arrays
+ */
+ TextureArray,
+
+ /**
+ * Supports texture buffers
+ */
+ TextureBuffer,
+
+ /**
+ * Supports floating point textures (Format.RGB16F)
+ */
+ FloatTexture,
+
+ /**
+ * Supports floating point FBO color buffers (Format.RGB16F)
+ */
+ FloatColorBuffer,
+
+ /**
+ * Supports floating point depth buffer
+ */
+ FloatDepthBuffer,
+
+ /**
+ * Supports Format.RGB111110F for textures
+ */
+ PackedFloatTexture,
+
+ /**
+ * Supports Format.RGB9E5 for textures
+ */
+ SharedExponentTexture,
+
+ /**
+ * Supports Format.RGB111110F for FBO color buffers
+ */
+ PackedFloatColorBuffer,
+
+ /**
+ * Supports Format.RGB9E5 for FBO color buffers
+ */
+ SharedExponentColorBuffer,
+
+ /**
+ * Supports Format.LATC for textures, this includes
+ * support for ATI's 3Dc texture compression.
+ */
+ TextureCompressionLATC,
+
+ /**
+ * Supports Non-Power-Of-Two (NPOT) textures and framebuffers
+ */
+ NonPowerOfTwoTextures,
+
+ /// Vertex Buffer features
+ MeshInstancing,
+
+ /**
+ * Supports VAO, or vertex buffer arrays
+ */
+ VertexBufferArray,
+
+ /**
+ * Supports multisampling on the screen
+ */
+ Multisample;
+
+ /**
+ * Returns true if given the renderer capabilities, the texture
+ * can be supported by the renderer.
+ * <p>
+ * This only checks the format of the texture, non-power-of-2
+ * textures are scaled automatically inside the renderer
+ * if are not supported natively.
+ *
+ * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+ * @param tex The texture to check
+ * @return True if it is supported, false otherwise.
+ */
+ public static boolean supports(Collection<Caps> caps, Texture tex){
+ if (tex.getType() == Texture.Type.TwoDimensionalArray
+ && !caps.contains(Caps.TextureArray))
+ return false;
+
+ Image img = tex.getImage();
+ if (img == null)
+ return true;
+
+ Format fmt = img.getFormat();
+ switch (fmt){
+ case Depth32F:
+ return caps.contains(Caps.FloatDepthBuffer);
+ case LATC:
+ return caps.contains(Caps.TextureCompressionLATC);
+ case RGB16F_to_RGB111110F:
+ case RGB111110F:
+ return caps.contains(Caps.PackedFloatTexture);
+ case RGB16F_to_RGB9E5:
+ case RGB9E5:
+ return caps.contains(Caps.SharedExponentTexture);
+ default:
+ if (fmt.isFloatingPont())
+ return caps.contains(Caps.FloatTexture);
+
+ return true;
+ }
+ }
+
+ /**
+ * Returns true if given the renderer capabilities, the framebuffer
+ * can be supported by the renderer.
+ *
+ * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+ * @param fb The framebuffer to check
+ * @return True if it is supported, false otherwise.
+ */
+ public static boolean supports(Collection<Caps> caps, FrameBuffer fb){
+ if (!caps.contains(Caps.FrameBuffer))
+ return false;
+
+ if (fb.getSamples() > 1
+ && !caps.contains(Caps.FrameBufferMultisample))
+ return false;
+
+ RenderBuffer colorBuf = fb.getColorBuffer();
+ RenderBuffer depthBuf = fb.getDepthBuffer();
+
+ if (depthBuf != null){
+ Format depthFmt = depthBuf.getFormat();
+ if (!depthFmt.isDepthFormat()){
+ return false;
+ }else{
+ if (depthFmt == Format.Depth32F
+ && !caps.contains(Caps.FloatDepthBuffer))
+ return false;
+ }
+ }
+ if (colorBuf != null){
+ Format colorFmt = colorBuf.getFormat();
+ if (colorFmt.isDepthFormat())
+ return false;
+
+ if (colorFmt.isCompressed())
+ return false;
+
+ switch (colorFmt){
+ case RGB111110F:
+ return caps.contains(Caps.PackedFloatColorBuffer);
+ case RGB16F_to_RGB111110F:
+ case RGB16F_to_RGB9E5:
+ case RGB9E5:
+ return false;
+ default:
+ if (colorFmt.isFloatingPont())
+ return caps.contains(Caps.FloatColorBuffer);
+
+ return true;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if given the renderer capabilities, the shader
+ * can be supported by the renderer.
+ *
+ * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }.
+ * @param shader The shader to check
+ * @return True if it is supported, false otherwise.
+ */
+ public static boolean supports(Collection<Caps> caps, Shader shader){
+ String lang = shader.getLanguage();
+ if (lang.startsWith("GLSL")){
+ int ver = Integer.parseInt(lang.substring(4));
+ switch (ver){
+ case 100:
+ return caps.contains(Caps.GLSL100);
+ case 110:
+ return caps.contains(Caps.GLSL110);
+ case 120:
+ return caps.contains(Caps.GLSL120);
+ case 130:
+ return caps.contains(Caps.GLSL130);
+ case 140:
+ return caps.contains(Caps.GLSL140);
+ case 150:
+ return caps.contains(Caps.GLSL150);
+ case 330:
+ return caps.contains(Caps.GLSL330);
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/GL1Renderer.java b/engine/src/core/com/jme3/renderer/GL1Renderer.java
new file mode 100644
index 0000000..78df0c6
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/GL1Renderer.java
@@ -0,0 +1,26 @@
+package com.jme3.renderer;
+
+import com.jme3.material.FixedFuncBinding;
+
+/**
+ * Renderer sub-interface that is used for non-shader based renderers.
+ * <p>
+ * The <code>GL1Renderer</code> provides a single call,
+ * {@link #setFixedFuncBinding(com.jme3.material.FixedFuncBinding, java.lang.Object) }
+ * which allows to set fixed functionality state.
+ *
+ * @author Kirill Vainer
+ */
+public interface GL1Renderer extends Renderer {
+
+ /**
+ * Set the fixed functionality state.
+ * <p>
+ * See {@link FixedFuncBinding} for various values that
+ * can be set.
+ *
+ * @param ffBinding The binding to set
+ * @param val The value
+ */
+ public void setFixedFuncBinding(FixedFuncBinding ffBinding, Object val);
+}
diff --git a/engine/src/core/com/jme3/renderer/IDList.java b/engine/src/core/com/jme3/renderer/IDList.java
new file mode 100644
index 0000000..2db7294
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/IDList.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import java.util.Arrays;
+
+/**
+ * A specialized data-structure used to optimize state changes of "slot"
+ * based state.
+ */
+public class IDList {
+
+ public int[] newList = new int[16];
+ public int[] oldList = new int[16];
+ public int newLen = 0;
+ public int oldLen = 0;
+
+ /**
+ * Reset all states to zero
+ */
+ public void reset(){
+ newLen = 0;
+ oldLen = 0;
+ Arrays.fill(newList, 0);
+ Arrays.fill(oldList, 0);
+ }
+
+ /**
+ * Adds an index to the new list.
+ * If the index was not in the old list, false is returned,
+ * if the index was in the old list, it is removed from the old
+ * list and true is returned.
+ *
+ * @param idx The index to move
+ * @return True if it existed in old list and was removed
+ * from there, false otherwise.
+ */
+ public boolean moveToNew(int idx){
+ if (newLen == 0 || newList[newLen-1] != idx)
+ // add item to newList first
+ newList[newLen++] = idx;
+
+ // find idx in oldList, if removed successfuly, return true.
+ for (int i = 0; i < oldLen; i++){
+ if (oldList[i] == idx){
+ // found index in slot i
+ // delete index from old list
+ oldLen --;
+ for (int j = i; j < oldLen; j++){
+ oldList[j] = oldList[j+1];
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Copies the new list to the old list, and clears the new list.
+ */
+ public void copyNewToOld(){
+ System.arraycopy(newList, 0, oldList, 0, newLen);
+ oldLen = newLen;
+ newLen = 0;
+ }
+
+ /**
+ * Prints the contents of the lists
+ */
+ public void print(){
+ if (newLen > 0){
+ System.out.print("New List: ");
+ for (int i = 0; i < newLen; i++){
+ if (i == newLen -1)
+ System.out.println(newList[i]);
+ else
+ System.out.print(newList[i]+", ");
+ }
+ }
+ if (oldLen > 0){
+ System.out.print("Old List: ");
+ for (int i = 0; i < oldLen; i++){
+ if (i == oldLen -1)
+ System.out.println(oldList[i]);
+ else
+ System.out.print(oldList[i]+", ");
+ }
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/RenderContext.java b/engine/src/core/com/jme3/renderer/RenderContext.java
new file mode 100644
index 0000000..ee9ff9c
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RenderContext.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+
+/**
+ * Represents the current state of the graphics library. This class is used
+ * internally to reduce state changes. NOTE: This class is specific to OpenGL.
+ */
+public class RenderContext {
+
+ /**
+ * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode)
+ */
+ public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off;
+
+ /**
+ * @see RenderState#setDepthTest(boolean)
+ */
+ public boolean depthTestEnabled = false;
+
+ /**
+ * @see RenderState#setAlphaTest(boolean)
+ */
+ public boolean alphaTestEnabled = false;
+
+ /**
+ * @see RenderState#setDepthWrite(boolean)
+ */
+ public boolean depthWriteEnabled = true;
+
+ /**
+ * @see RenderState#setColorWrite(boolean)
+ */
+ public boolean colorWriteEnabled = true;
+
+ /**
+ * @see Renderer#setClipRect(int, int, int, int)
+ */
+ public boolean clipRectEnabled = false;
+
+ /**
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public boolean polyOffsetEnabled = false;
+
+ /**
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public float polyOffsetFactor = 0;
+
+ /**
+ * @see RenderState#setPolyOffset(float, float)
+ */
+ public float polyOffsetUnits = 0;
+
+ /**
+ * For normals only. Uses GL_NORMALIZE.
+ *
+ * @see VertexBuffer#setNormalized(boolean)
+ */
+ public boolean normalizeEnabled = false;
+
+ /**
+ * For glMatrixMode.
+ *
+ * @see Renderer#setWorldMatrix(com.jme3.math.Matrix4f)
+ * @see Renderer#setViewProjectionMatrices(com.jme3.math.Matrix4f, com.jme3.math.Matrix4f)
+ */
+ public int matrixMode = -1;
+
+ /**
+ * @see Mesh#setPointSize(float)
+ */
+ public float pointSize = 1;
+
+ /**
+ * @see Mesh#setLineWidth(float)
+ */
+ public float lineWidth = 1;
+
+ /**
+ * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode)
+ */
+ public RenderState.BlendMode blendMode = RenderState.BlendMode.Off;
+
+ /**
+ * @see RenderState#setWireframe(boolean)
+ */
+ public boolean wireframe = false;
+
+ /**
+ * @see RenderState#setPointSprite(boolean)
+ */
+ public boolean pointSprite = false;
+
+ /**
+ * @see Renderer#setShader(com.jme3.shader.Shader)
+ */
+ public int boundShaderProgram;
+
+ /**
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
+ */
+ public int boundFBO = 0;
+
+ /**
+ * Currently bound Renderbuffer
+ *
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
+ */
+ public int boundRB = 0;
+
+ /**
+ * Currently bound draw buffer
+ * -2 = GL_NONE
+ * -1 = GL_BACK
+ * 0 = GL_COLOR_ATTACHMENT0
+ * n = GL_COLOR_ATTACHMENTn
+ * where n is an integer greater than 1
+ *
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
+ * @see FrameBuffer#setTargetIndex(int)
+ */
+ public int boundDrawBuf = -1;
+
+ /**
+ * Currently bound read buffer
+ *
+ * @see RenderContext#boundDrawBuf
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
+ * @see FrameBuffer#setTargetIndex(int)
+ */
+ public int boundReadBuf = -1;
+
+ /**
+ * Currently bound element array vertex buffer.
+ *
+ * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int)
+ */
+ public int boundElementArrayVBO;
+
+ /**
+ * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int)
+ */
+ public int boundVertexArray;
+
+ /**
+ * Currently bound array vertex buffer.
+ *
+ * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int)
+ */
+ public int boundArrayVBO;
+
+ public int numTexturesSet = 0;
+
+ /**
+ * Current bound texture IDs for each texture unit.
+ *
+ * @see Renderer#setTexture(int, com.jme3.texture.Texture)
+ */
+ public Image[] boundTextures = new Image[16];
+
+ /**
+ * IDList for texture units
+ *
+ * @see Renderer#setTexture(int, com.jme3.texture.Texture)
+ */
+ public IDList textureIndexList = new IDList();
+
+ /**
+ * Currently bound texture unit
+ *
+ * @see Renderer#setTexture(int, com.jme3.texture.Texture)
+ */
+ public int boundTextureUnit = 0;
+
+ /**
+ * Stencil Buffer state
+ */
+ public boolean stencilTest = false;
+ public RenderState.StencilOperation frontStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+ public RenderState.StencilOperation frontStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+ public RenderState.StencilOperation frontStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+ public RenderState.StencilOperation backStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+ public RenderState.StencilOperation backStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+ public RenderState.StencilOperation backStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+ public RenderState.TestFunction frontStencilFunction = RenderState.TestFunction.Always;
+ public RenderState.TestFunction backStencilFunction = RenderState.TestFunction.Always;
+
+ /**
+ * Vertex attribs currently bound and enabled. If a slot is null, then
+ * it is disabled.
+ */
+ public VertexBuffer[] boundAttribs = new VertexBuffer[16];
+
+ /**
+ * IDList for vertex attributes
+ */
+ public IDList attribIndexList = new IDList();
+
+ /**
+ * Ambient color (GL1 only)
+ */
+ public ColorRGBA ambient;
+
+ /**
+ * Diffuse color (GL1 only)
+ */
+ public ColorRGBA diffuse;
+
+ /**
+ * Specular color (GL1 only)
+ */
+ public ColorRGBA specular;
+
+ /**
+ * Material color (GL1 only)
+ */
+ public ColorRGBA color;
+
+ /**
+ * Shininess (GL1 only)
+ */
+ public float shininess;
+
+ /**
+ * Use vertex color (GL1 only)
+ */
+ public boolean useVertexColor;
+
+ /**
+ * Reset the RenderContext to default GL state
+ */
+ public void reset(){
+ cullMode = RenderState.FaceCullMode.Off;
+ depthTestEnabled = false;
+ alphaTestEnabled = false;
+ depthWriteEnabled = false;
+ colorWriteEnabled = false;
+ clipRectEnabled = false;
+ polyOffsetEnabled = false;
+ polyOffsetFactor = 0;
+ polyOffsetUnits = 0;
+ normalizeEnabled = false;
+ matrixMode = -1;
+ pointSize = 1;
+ blendMode = RenderState.BlendMode.Off;
+ wireframe = false;
+ boundShaderProgram = 0;
+ boundFBO = 0;
+ boundRB = 0;
+ boundDrawBuf = -1;
+ boundReadBuf = -1;
+ boundElementArrayVBO = 0;
+ boundVertexArray = 0;
+ boundArrayVBO = 0;
+ numTexturesSet = 0;
+ for (int i = 0; i < boundTextures.length; i++)
+ boundTextures[i] = null;
+
+ textureIndexList.reset();
+ boundTextureUnit = 0;
+ for (int i = 0; i < boundAttribs.length; i++)
+ boundAttribs[i] = null;
+
+ attribIndexList.reset();
+
+ stencilTest = false;
+ frontStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+ frontStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+ frontStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+ backStencilStencilFailOperation = RenderState.StencilOperation.Keep;
+ backStencilDepthFailOperation = RenderState.StencilOperation.Keep;
+ backStencilDepthPassOperation = RenderState.StencilOperation.Keep;
+ frontStencilFunction = RenderState.TestFunction.Always;
+ backStencilFunction = RenderState.TestFunction.Always;
+
+ ambient = diffuse = specular = color = null;
+ shininess = 0;
+ useVertexColor = false;
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/RenderManager.java b/engine/src/core/com/jme3/renderer/RenderManager.java
new file mode 100644
index 0000000..1d58d22
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RenderManager.java
@@ -0,0 +1,1170 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.material.Material;
+import com.jme3.material.MaterialDef;
+import com.jme3.material.RenderState;
+import com.jme3.material.Technique;
+import com.jme3.math.*;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.*;
+import com.jme3.shader.Uniform;
+import com.jme3.shader.UniformBinding;
+import com.jme3.shader.VarType;
+import com.jme3.system.NullRenderer;
+import com.jme3.system.Timer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.TempVars;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * <code>RenderManager</code> is a high-level rendering interface that is
+ * above the Renderer implementation. RenderManager takes care
+ * of rendering the scene graphs attached to each viewport and
+ * handling SceneProcessors.
+ *
+ * @see SceneProcessor
+ * @see ViewPort
+ * @see Spatial
+ */
+public class RenderManager {
+
+ private static final Logger logger = Logger.getLogger(RenderManager.class.getName());
+
+ private Renderer renderer;
+ private Timer timer;
+ private ArrayList<ViewPort> preViewPorts = new ArrayList<ViewPort>();
+ private ArrayList<ViewPort> viewPorts = new ArrayList<ViewPort>();
+ private ArrayList<ViewPort> postViewPorts = new ArrayList<ViewPort>();
+ private Camera prevCam = null;
+ private Material forcedMaterial = null;
+ private String forcedTechnique = null;
+ private RenderState forcedRenderState = null;
+ private boolean shader;
+ private int viewX, viewY, viewWidth, viewHeight;
+ private float near, far;
+ private Matrix4f orthoMatrix = new Matrix4f();
+ private Matrix4f viewMatrix = new Matrix4f();
+ private Matrix4f projMatrix = new Matrix4f();
+ private Matrix4f viewProjMatrix = new Matrix4f();
+ private Matrix4f worldMatrix = new Matrix4f();
+ private Vector3f camUp = new Vector3f(),
+ camLeft = new Vector3f(),
+ camDir = new Vector3f(),
+ camLoc = new Vector3f();
+ //temp technique
+ private String tmpTech;
+ private boolean handleTranlucentBucket = true;
+
+ /**
+ * Create a high-level rendering interface over the
+ * low-level rendering interface.
+ * @param renderer
+ */
+ public RenderManager(Renderer renderer) {
+ this.renderer = renderer;
+ //this.shader = renderer.getCaps().contains(Caps.GLSL100);
+ }
+
+ /**
+ * Returns the pre ViewPort with the given name.
+ *
+ * @param viewName The name of the pre ViewPort to look up
+ * @return The ViewPort, or null if not found.
+ *
+ * @see #createPreView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public ViewPort getPreView(String viewName) {
+ for (int i = 0; i < preViewPorts.size(); i++) {
+ if (preViewPorts.get(i).getName().equals(viewName)) {
+ return preViewPorts.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes the specified pre ViewPort.
+ *
+ * @param view The pre ViewPort to remove
+ * @return True if the ViewPort was removed successfully.
+ *
+ * @see #createPreView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public boolean removePreView(ViewPort view) {
+ return preViewPorts.remove(view);
+ }
+
+ /**
+ * Returns the main ViewPort with the given name.
+ *
+ * @param viewName The name of the main ViewPort to look up
+ * @return The ViewPort, or null if not found.
+ *
+ * @see #createMainView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public ViewPort getMainView(String viewName) {
+ for (int i = 0; i < viewPorts.size(); i++) {
+ if (viewPorts.get(i).getName().equals(viewName)) {
+ return viewPorts.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes the main ViewPort with the specified name.
+ *
+ * @param viewName The main ViewPort name to remove
+ * @return True if the ViewPort was removed successfully.
+ *
+ * @see #createMainView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public boolean removeMainView(String viewName) {
+ for (int i = 0; i < viewPorts.size(); i++) {
+ if (viewPorts.get(i).getName().equals(viewName)) {
+ viewPorts.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes the specified main ViewPort.
+ *
+ * @param view The main ViewPort to remove
+ * @return True if the ViewPort was removed successfully.
+ *
+ * @see #createMainView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public boolean removeMainView(ViewPort view) {
+ return viewPorts.remove(view);
+ }
+
+ /**
+ * Returns the post ViewPort with the given name.
+ *
+ * @param viewName The name of the post ViewPort to look up
+ * @return The ViewPort, or null if not found.
+ *
+ * @see #createPostView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public ViewPort getPostView(String viewName) {
+ for (int i = 0; i < postViewPorts.size(); i++) {
+ if (postViewPorts.get(i).getName().equals(viewName)) {
+ return postViewPorts.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes the post ViewPort with the specified name.
+ *
+ * @param viewName The post ViewPort name to remove
+ * @return True if the ViewPort was removed successfully.
+ *
+ * @see #createPostView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public boolean removePostView(String viewName) {
+ for (int i = 0; i < postViewPorts.size(); i++) {
+ if (postViewPorts.get(i).getName().equals(viewName)) {
+ postViewPorts.remove(i);
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes the specified post ViewPort.
+ *
+ * @param view The post ViewPort to remove
+ * @return True if the ViewPort was removed successfully.
+ *
+ * @see #createPostView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public boolean removePostView(ViewPort view) {
+ return postViewPorts.remove(view);
+ }
+
+ /**
+ * Returns a read-only list of all pre ViewPorts
+ * @return a read-only list of all pre ViewPorts
+ * @see #createPreView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public List<ViewPort> getPreViews() {
+ return Collections.unmodifiableList(preViewPorts);
+ }
+
+ /**
+ * Returns a read-only list of all main ViewPorts
+ * @return a read-only list of all main ViewPorts
+ * @see #createMainView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public List<ViewPort> getMainViews() {
+ return Collections.unmodifiableList(viewPorts);
+ }
+
+ /**
+ * Returns a read-only list of all post ViewPorts
+ * @return a read-only list of all post ViewPorts
+ * @see #createPostView(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public List<ViewPort> getPostViews() {
+ return Collections.unmodifiableList(postViewPorts);
+ }
+
+ /**
+ * Creates a new pre ViewPort, to display the given camera's content.
+ * <p>
+ * The view will be processed before the main and post viewports.
+ */
+ public ViewPort createPreView(String viewName, Camera cam) {
+ ViewPort vp = new ViewPort(viewName, cam);
+ preViewPorts.add(vp);
+ return vp;
+ }
+
+ /**
+ * Creates a new main ViewPort, to display the given camera's content.
+ * <p>
+ * The view will be processed before the post viewports but after
+ * the pre viewports.
+ */
+ public ViewPort createMainView(String viewName, Camera cam) {
+ ViewPort vp = new ViewPort(viewName, cam);
+ viewPorts.add(vp);
+ return vp;
+ }
+
+ /**
+ * Creates a new post ViewPort, to display the given camera's content.
+ * <p>
+ * The view will be processed after the pre and main viewports.
+ */
+ public ViewPort createPostView(String viewName, Camera cam) {
+ ViewPort vp = new ViewPort(viewName, cam);
+ postViewPorts.add(vp);
+ return vp;
+ }
+
+ private void notifyReshape(ViewPort vp, int w, int h) {
+ List<SceneProcessor> processors = vp.getProcessors();
+ for (SceneProcessor proc : processors) {
+ if (!proc.isInitialized()) {
+ proc.initialize(this, vp);
+ } else {
+ proc.reshape(vp, w, h);
+ }
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Updates the resolution of all on-screen cameras to match
+ * the given width and height.
+ */
+ public void notifyReshape(int w, int h) {
+ for (ViewPort vp : preViewPorts) {
+ if (vp.getOutputFrameBuffer() == null) {
+ Camera cam = vp.getCamera();
+ cam.resize(w, h, true);
+ }
+ notifyReshape(vp, w, h);
+ }
+ for (ViewPort vp : viewPorts) {
+ if (vp.getOutputFrameBuffer() == null) {
+ Camera cam = vp.getCamera();
+ cam.resize(w, h, true);
+ }
+ notifyReshape(vp, w, h);
+ }
+ for (ViewPort vp : postViewPorts) {
+ if (vp.getOutputFrameBuffer() == null) {
+ Camera cam = vp.getCamera();
+ cam.resize(w, h, true);
+ }
+ notifyReshape(vp, w, h);
+ }
+ }
+
+ /**
+ * Internal use only.
+ * Updates the given list of uniforms with {@link UniformBinding uniform bindings}
+ * based on the current world state.
+ */
+ public void updateUniformBindings(List<Uniform> params) {
+ // assums worldMatrix is properly set.
+ TempVars vars = TempVars.get();
+
+ Matrix4f tempMat4 = vars.tempMat4;
+ Matrix3f tempMat3 = vars.tempMat3;
+ Vector2f tempVec2 = vars.vect2d;
+ Quaternion tempVec4 = vars.quat1;
+
+ for (int i = 0; i < params.size(); i++) {
+ Uniform u = params.get(i);
+ switch (u.getBinding()) {
+ case WorldMatrix:
+ u.setValue(VarType.Matrix4, worldMatrix);
+ break;
+ case ViewMatrix:
+ u.setValue(VarType.Matrix4, viewMatrix);
+ break;
+ case ProjectionMatrix:
+ u.setValue(VarType.Matrix4, projMatrix);
+ break;
+ case ViewProjectionMatrix:
+ u.setValue(VarType.Matrix4, viewProjMatrix);
+ break;
+ case WorldViewMatrix:
+ tempMat4.set(viewMatrix);
+ tempMat4.multLocal(worldMatrix);
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case NormalMatrix:
+ tempMat4.set(viewMatrix);
+ tempMat4.multLocal(worldMatrix);
+ tempMat4.toRotationMatrix(tempMat3);
+ tempMat3.invertLocal();
+ tempMat3.transposeLocal();
+ u.setValue(VarType.Matrix3, tempMat3);
+ break;
+ case WorldViewProjectionMatrix:
+ tempMat4.set(viewProjMatrix);
+ tempMat4.multLocal(worldMatrix);
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case WorldMatrixInverse:
+ tempMat4.multLocal(worldMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case ViewMatrixInverse:
+ tempMat4.set(viewMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case ProjectionMatrixInverse:
+ tempMat4.set(projMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case ViewProjectionMatrixInverse:
+ tempMat4.set(viewProjMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case WorldViewMatrixInverse:
+ tempMat4.set(viewMatrix);
+ tempMat4.multLocal(worldMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case NormalMatrixInverse:
+ tempMat4.set(viewMatrix);
+ tempMat4.multLocal(worldMatrix);
+ tempMat4.toRotationMatrix(tempMat3);
+ tempMat3.invertLocal();
+ tempMat3.transposeLocal();
+ tempMat3.invertLocal();
+ u.setValue(VarType.Matrix3, tempMat3);
+ break;
+ case WorldViewProjectionMatrixInverse:
+ tempMat4.set(viewProjMatrix);
+ tempMat4.multLocal(worldMatrix);
+ tempMat4.invertLocal();
+ u.setValue(VarType.Matrix4, tempMat4);
+ break;
+ case ViewPort:
+ tempVec4.set(viewX, viewY, viewWidth, viewHeight);
+ u.setValue(VarType.Vector4, tempVec4);
+ break;
+ case Resolution:
+ tempVec2.set(viewWidth, viewHeight);
+ u.setValue(VarType.Vector2, tempVec2);
+ break;
+ case Aspect:
+ float aspect = ((float) viewWidth) / viewHeight;
+ u.setValue(VarType.Float, aspect);
+ break;
+ case FrustumNearFar:
+ tempVec2.set(near, far);
+ u.setValue(VarType.Vector2, tempVec2);
+ break;
+ case CameraPosition:
+ u.setValue(VarType.Vector3, camLoc);
+ break;
+ case CameraDirection:
+ u.setValue(VarType.Vector3, camDir);
+ break;
+ case CameraLeft:
+ u.setValue(VarType.Vector3, camLeft);
+ break;
+ case CameraUp:
+ u.setValue(VarType.Vector3, camUp);
+ break;
+ case Time:
+ u.setValue(VarType.Float, timer.getTimeInSeconds());
+ break;
+ case Tpf:
+ u.setValue(VarType.Float, timer.getTimePerFrame());
+ break;
+ case FrameRate:
+ u.setValue(VarType.Float, timer.getFrameRate());
+ break;
+ }
+ }
+
+ vars.release();
+ }
+
+ /**
+ * Set the material to use to render all future objects.
+ * This overrides the material set on the geometry and renders
+ * with the provided material instead.
+ * Use null to clear the material and return renderer to normal
+ * functionality.
+ * @param mat The forced material to set, or null to return to normal
+ */
+ public void setForcedMaterial(Material mat) {
+ forcedMaterial = mat;
+ }
+
+ /**
+ * Returns the forced render state previously set with
+ * {@link #setForcedRenderState(com.jme3.material.RenderState) }.
+ * @return the forced render state
+ */
+ public RenderState getForcedRenderState() {
+ return forcedRenderState;
+ }
+
+ /**
+ * Set the render state to use for all future objects.
+ * This overrides the render state set on the material and instead
+ * forces this render state to be applied for all future materials
+ * rendered. Set to null to return to normal functionality.
+ *
+ * @param forcedRenderState The forced render state to set, or null
+ * to return to normal
+ */
+ public void setForcedRenderState(RenderState forcedRenderState) {
+ this.forcedRenderState = forcedRenderState;
+ }
+
+ /**
+ * Set the timer that should be used to query the time based
+ * {@link UniformBinding}s for material world parameters.
+ *
+ * @param timer The timer to query time world parameters
+ */
+ public void setTimer(Timer timer) {
+ this.timer = timer;
+ }
+
+ /**
+ * Returns the forced technique name set.
+ *
+ * @return the forced technique name set.
+ *
+ * @see #setForcedTechnique(java.lang.String)
+ */
+ public String getForcedTechnique() {
+ return forcedTechnique;
+ }
+
+ /**
+ * Sets the forced technique to use when rendering geometries.
+ * <p>
+ * If the specified technique name is available on the geometry's
+ * material, then it is used, otherwise, the
+ * {@link #setForcedMaterial(com.jme3.material.Material) forced material} is used.
+ * If a forced material is not set and the forced technique name cannot
+ * be found on the material, the geometry will <em>not</em> be rendered.
+ *
+ * @param forcedTechnique The forced technique name to use, set to null
+ * to return to normal functionality.
+ *
+ * @see #renderGeometry(com.jme3.scene.Geometry)
+ */
+ public void setForcedTechnique(String forcedTechnique) {
+ this.forcedTechnique = forcedTechnique;
+ }
+
+ /**
+ * Enable or disable alpha-to-coverage.
+ * <p>
+ * When alpha to coverage is enabled and the renderer implementation
+ * supports it, then alpha blending will be replaced with alpha dissolve
+ * if multi-sampling is also set on the renderer.
+ * This feature allows avoiding of alpha blending artifacts due to
+ * lack of triangle-level back-to-front sorting.
+ *
+ * @param value True to enable alpha-to-coverage, false otherwise.
+ */
+ public void setAlphaToCoverage(boolean value) {
+ renderer.setAlphaToCoverage(value);
+ }
+
+ /**
+ * True if the translucent bucket should automatically be rendered
+ * by the RenderManager.
+ *
+ * @return Whether or not the translucent bucket is rendered.
+ *
+ * @see #setHandleTranslucentBucket(boolean)
+ */
+ public boolean isHandleTranslucentBucket() {
+ return handleTranlucentBucket;
+ }
+
+ /**
+ * Enable or disable rendering of the
+ * {@link Bucket#Translucent translucent bucket}
+ * by the RenderManager. The default is enabled.
+ *
+ * @param handleTranslucentBucket Whether or not the translucent bucket should
+ * be rendered.
+ */
+ public void setHandleTranslucentBucket(boolean handleTranslucentBucket) {
+ this.handleTranlucentBucket = handleTranslucentBucket;
+ }
+
+ /**
+ * Internal use only. Sets the world matrix to use for future
+ * rendering. This has no effect unless objects are rendered manually
+ * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }.
+ * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will
+ * override this value.
+ *
+ * @param mat The world matrix to set
+ */
+ public void setWorldMatrix(Matrix4f mat) {
+ if (shader) {
+ worldMatrix.set(mat);
+ } else {
+ renderer.setWorldMatrix(mat);
+ }
+ }
+
+ /**
+ * Renders the given geometry.
+ * <p>
+ * First the proper world matrix is set, if
+ * the geometry's {@link Geometry#setIgnoreTransform(boolean) ignore transform}
+ * feature is enabled, the identity world matrix is used, otherwise, the
+ * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used.
+ * <p>
+ * Once the world matrix is applied, the proper material is chosen for rendering.
+ * If a {@link #setForcedMaterial(com.jme3.material.Material) forced material} is
+ * set on this RenderManager, then it is used for rendering the geometry,
+ * otherwise, the {@link Geometry#getMaterial() geometry's material} is used.
+ * <p>
+ * If a {@link #setForcedTechnique(java.lang.String) forced technique} is
+ * set on this RenderManager, then it is selected automatically
+ * on the geometry's material and is used for rendering. Otherwise, one
+ * of the {@link MaterialDef#getDefaultTechniques() default techniques} is
+ * used.
+ * <p>
+ * If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced
+ * render state} is set on this RenderManager, then it is used
+ * for rendering the material, and the material's own render state is ignored.
+ * Otherwise, the material's render state is used as intended.
+ *
+ * @param g The geometry to render
+ *
+ * @see Technique
+ * @see RenderState
+ * @see Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
+ * @see Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager)
+ */
+ public void renderGeometry(Geometry g) {
+ if (g.isIgnoreTransform()) {
+ setWorldMatrix(Matrix4f.IDENTITY);
+ } else {
+ setWorldMatrix(g.getWorldMatrix());
+ }
+
+ //if forcedTechnique we try to force it for render,
+ //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null
+ //else the geom is not rendered
+ if (forcedTechnique != null) {
+ if (g.getMaterial().getMaterialDef().getTechniqueDef(forcedTechnique) != null) {
+ tmpTech = g.getMaterial().getActiveTechnique() != null ? g.getMaterial().getActiveTechnique().getDef().getName() : "Default";
+ g.getMaterial().selectTechnique(forcedTechnique, this);
+ // use geometry's material
+ g.getMaterial().render(g, this);
+ g.getMaterial().selectTechnique(tmpTech, this);
+ //Reverted this part from revision 6197
+ //If forcedTechnique does not exists, and frocedMaterial is not set, the geom MUST NOT be rendered
+ } else if (forcedMaterial != null) {
+ // use forced material
+ forcedMaterial.render(g, this);
+ }
+ } else if (forcedMaterial != null) {
+ // use forced material
+ forcedMaterial.render(g, this);
+ } else {
+ g.getMaterial().render(g, this);
+ }
+ }
+
+ /**
+ * Renders the given GeometryList.
+ * <p>
+ * For every geometry in the list, the
+ * {@link #renderGeometry(com.jme3.scene.Geometry) } method is called.
+ *
+ * @param gl The geometry list to render.
+ *
+ * @see GeometryList
+ * @see #renderGeometry(com.jme3.scene.Geometry)
+ */
+ public void renderGeometryList(GeometryList gl) {
+ for (int i = 0; i < gl.size(); i++) {
+ renderGeometry(gl.get(i));
+ }
+ }
+
+ /**
+ * If a spatial is not inside the eye frustum, it
+ * is still rendered in the shadow frustum (shadow casting queue)
+ * through this recursive method.
+ */
+ private void renderShadow(Spatial s, RenderQueue rq) {
+ if (s instanceof Node) {
+ Node n = (Node) s;
+ List<Spatial> children = n.getChildren();
+ for (int i = 0; i < children.size(); i++) {
+ renderShadow(children.get(i), rq);
+ }
+ } else if (s instanceof Geometry) {
+ Geometry gm = (Geometry) s;
+
+ RenderQueue.ShadowMode shadowMode = s.getShadowMode();
+ if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive) {
+ //forcing adding to shadow cast mode, culled objects doesn't have to be in the receiver queue
+ rq.addToShadowQueue(gm, RenderQueue.ShadowMode.Cast);
+ }
+ }
+ }
+
+ /**
+ * Preloads a scene for rendering.
+ * <p>
+ * After invocation of this method, the underlying
+ * renderer would have uploaded any textures, shaders and meshes
+ * used by the given scene to the video driver.
+ * Using this method is useful when wishing to avoid the initial pause
+ * when rendering a scene for the first time. Note that it is not
+ * guaranteed that the underlying renderer will actually choose to upload
+ * the data to the GPU so some pause is still to be expected.
+ *
+ * @param scene The scene to preload
+ */
+ public void preloadScene(Spatial scene) {
+ if (scene instanceof Node) {
+ // recurse for all children
+ Node n = (Node) scene;
+ List<Spatial> children = n.getChildren();
+ for (int i = 0; i < children.size(); i++) {
+ preloadScene(children.get(i));
+ }
+ } else if (scene instanceof Geometry) {
+ // add to the render queue
+ Geometry gm = (Geometry) scene;
+ if (gm.getMaterial() == null) {
+ throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
+ }
+
+ gm.getMaterial().preload(this);
+ Mesh mesh = gm.getMesh();
+ if (mesh != null) {
+ for (Entry<VertexBuffer> entry : mesh.getBuffers()) {
+ VertexBuffer buf = entry.getValue();
+ if (buf.getData() != null) {
+ renderer.updateBufferData(buf);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Flattens the given scene graph into the ViewPort's RenderQueue,
+ * checking for culling as the call goes down the graph recursively.
+ * <p>
+ * First, the scene is checked for culling based on the <code>Spatial</code>s
+ * {@link Spatial#setCullHint(com.jme3.scene.Spatial.CullHint) cull hint},
+ * if the camera frustum contains the scene, then this method is recursively
+ * called on its children.
+ * <p>
+ * When the scene's leaves or {@link Geometry geometries} are reached,
+ * they are each enqueued into the
+ * {@link ViewPort#getQueue() ViewPort's render queue}.
+ * <p>
+ * In addition to enqueuing the visible geometries, this method
+ * also scenes which cast or receive shadows, by putting them into the
+ * RenderQueue's
+ * {@link RenderQueue#addToShadowQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.ShadowMode)
+ * shadow queue}. Each Spatial which has its
+ * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode}
+ * set to not off, will be put into the appropriate shadow queue, note that
+ * this process does not check for frustum culling on any
+ * {@link ShadowMode#Cast shadow casters}, as they don't have to be
+ * in the eye camera frustum to cast shadows on objects that are inside it.
+ *
+ * @param scene The scene to flatten into the queue
+ * @param vp The ViewPort provides the {@link ViewPort#getCamera() camera}
+ * used for culling and the {@link ViewPort#getQueue() queue} used to
+ * contain the flattened scene graph.
+ */
+ public void renderScene(Spatial scene, ViewPort vp) {
+ if (scene.getParent() == null) {
+ vp.getCamera().setPlaneState(0);
+ }
+ // check culling first.
+ if (!scene.checkCulling(vp.getCamera())) {
+ // move on to shadow-only render
+ if ((scene.getShadowMode() != RenderQueue.ShadowMode.Off || scene instanceof Node) && scene.getCullHint()!=Spatial.CullHint.Always) {
+ renderShadow(scene, vp.getQueue());
+ }
+ return;
+ }
+
+ scene.runControlRender(this, vp);
+ if (scene instanceof Node) {
+ // recurse for all children
+ Node n = (Node) scene;
+ List<Spatial> children = n.getChildren();
+ //saving cam state for culling
+ int camState = vp.getCamera().getPlaneState();
+ for (int i = 0; i < children.size(); i++) {
+ //restoring cam state before proceeding children recusively
+ vp.getCamera().setPlaneState(camState);
+ renderScene(children.get(i), vp);
+
+ }
+ } else if (scene instanceof Geometry) {
+
+ // add to the render queue
+ Geometry gm = (Geometry) scene;
+ if (gm.getMaterial() == null) {
+ throw new IllegalStateException("No material is set for Geometry: " + gm.getName());
+ }
+
+ vp.getQueue().addToQueue(gm, scene.getQueueBucket());
+
+ // add to shadow queue if needed
+ RenderQueue.ShadowMode shadowMode = scene.getShadowMode();
+ if (shadowMode != RenderQueue.ShadowMode.Off) {
+ vp.getQueue().addToShadowQueue(gm, shadowMode);
+ }
+ }
+ }
+
+ /**
+ * Returns the camera currently used for rendering.
+ * <p>
+ * The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }.
+ *
+ * @return the camera currently used for rendering.
+ */
+ public Camera getCurrentCamera() {
+ return prevCam;
+ }
+
+ /**
+ * The renderer implementation used for rendering operations.
+ *
+ * @return The renderer implementation
+ *
+ * @see #RenderManager(com.jme3.renderer.Renderer)
+ * @see Renderer
+ */
+ public Renderer getRenderer() {
+ return renderer;
+ }
+
+ /**
+ * Flushes the ViewPort's {@link ViewPort#getQueue() render queue}
+ * by rendering each of its visible buckets.
+ * By default the queues will automatically be cleared after rendering,
+ * so there's no need to clear them manually.
+ *
+ * @param vp The ViewPort of which the queue will be flushed
+ *
+ * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera)
+ * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList)
+ */
+ public void flushQueue(ViewPort vp) {
+ renderViewPortQueues(vp, true);
+ }
+
+ /**
+ * Clears the queue of the given ViewPort.
+ * Simply calls {@link RenderQueue#clear() } on the ViewPort's
+ * {@link ViewPort#getQueue() render queue}.
+ *
+ * @param vp The ViewPort of which the queue will be cleared.
+ *
+ * @see RenderQueue#clear()
+ * @see ViewPort#getQueue()
+ */
+ public void clearQueue(ViewPort vp) {
+ vp.getQueue().clear();
+ }
+
+ /**
+ * Render the given viewport queues.
+ * <p>
+ * Changes the {@link Renderer#setDepthRange(float, float) depth range}
+ * appropriately as expected by each queue and then calls
+ * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) }
+ * on the queue. Makes sure to restore the depth range to [0, 1]
+ * at the end of the call.
+ * Note that the {@link Bucket#Translucent translucent bucket} is NOT
+ * rendered by this method. Instead the user should call
+ * {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) }
+ * after this call.
+ *
+ * @param vp the viewport of which queue should be rendered
+ * @param flush If true, the queues will be cleared after
+ * rendering.
+ *
+ * @see RenderQueue
+ * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort)
+ */
+ public void renderViewPortQueues(ViewPort vp, boolean flush) {
+ RenderQueue rq = vp.getQueue();
+ Camera cam = vp.getCamera();
+ boolean depthRangeChanged = false;
+
+ // render opaque objects with default depth range
+ // opaque objects are sorted front-to-back, reducing overdraw
+ rq.renderQueue(Bucket.Opaque, this, cam, flush);
+
+ // render the sky, with depth range set to the farthest
+ if (!rq.isQueueEmpty(Bucket.Sky)) {
+ renderer.setDepthRange(1, 1);
+ rq.renderQueue(Bucket.Sky, this, cam, flush);
+ depthRangeChanged = true;
+ }
+
+
+ // transparent objects are last because they require blending with the
+ // rest of the scene's objects. Consequently, they are sorted
+ // back-to-front.
+ if (!rq.isQueueEmpty(Bucket.Transparent)) {
+ if (depthRangeChanged) {
+ renderer.setDepthRange(0, 1);
+ depthRangeChanged = false;
+ }
+
+ rq.renderQueue(Bucket.Transparent, this, cam, flush);
+ }
+
+ if (!rq.isQueueEmpty(Bucket.Gui)) {
+ renderer.setDepthRange(0, 0);
+ setCamera(cam, true);
+ rq.renderQueue(Bucket.Gui, this, cam, flush);
+ setCamera(cam, false);
+ depthRangeChanged = true;
+ }
+
+ // restore range to default
+ if (depthRangeChanged) {
+ renderer.setDepthRange(0, 1);
+ }
+ }
+
+ /**
+ * Renders the {@link Bucket#Translucent translucent queue} on the viewPort.
+ * <p>
+ * This call does nothing unless {@link #setHandleTranslucentBucket(boolean) }
+ * is set to true. This method clears the translucent queue after rendering
+ * it.
+ *
+ * @param vp The viewport of which the translucent queue should be rendered.
+ *
+ * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean)
+ * @see #setHandleTranslucentBucket(boolean)
+ */
+ public void renderTranslucentQueue(ViewPort vp) {
+ RenderQueue rq = vp.getQueue();
+ if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranlucentBucket) {
+ rq.renderQueue(Bucket.Translucent, this, vp.getCamera(), true);
+ }
+ }
+
+ private void setViewPort(Camera cam) {
+ // this will make sure to update viewport only if needed
+ if (cam != prevCam || cam.isViewportChanged()) {
+ viewX = (int) (cam.getViewPortLeft() * cam.getWidth());
+ viewY = (int) (cam.getViewPortBottom() * cam.getHeight());
+ viewWidth = (int) ((cam.getViewPortRight() - cam.getViewPortLeft()) * cam.getWidth());
+ viewHeight = (int) ((cam.getViewPortTop() - cam.getViewPortBottom()) * cam.getHeight());
+ renderer.setViewPort(viewX, viewY, viewWidth, viewHeight);
+ renderer.setClipRect(viewX, viewY, viewWidth, viewHeight);
+ cam.clearViewportChanged();
+ prevCam = cam;
+
+// float translateX = viewWidth == viewX ? 0 : -(viewWidth + viewX) / (viewWidth - viewX);
+// float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY);
+// float scaleX = viewWidth == viewX ? 1f : 2f / (viewWidth - viewX);
+// float scaleY = viewHeight == viewY ? 1f : 2f / (viewHeight - viewY);
+//
+// orthoMatrix.loadIdentity();
+// orthoMatrix.setTranslation(translateX, translateY, 0);
+// orthoMatrix.setScale(scaleX, scaleY, 0);
+
+ orthoMatrix.loadIdentity();
+ orthoMatrix.setTranslation(-1f, -1f, 0f);
+ orthoMatrix.setScale(2f / cam.getWidth(), 2f / cam.getHeight(), 0f);
+ }
+ }
+
+ private void setViewProjection(Camera cam, boolean ortho) {
+ if (shader) {
+ if (ortho) {
+ viewMatrix.set(Matrix4f.IDENTITY);
+ projMatrix.set(orthoMatrix);
+ viewProjMatrix.set(orthoMatrix);
+ } else {
+ viewMatrix.set(cam.getViewMatrix());
+ projMatrix.set(cam.getProjectionMatrix());
+ viewProjMatrix.set(cam.getViewProjectionMatrix());
+ }
+
+ camLoc.set(cam.getLocation());
+ cam.getLeft(camLeft);
+ cam.getUp(camUp);
+ cam.getDirection(camDir);
+
+ near = cam.getFrustumNear();
+ far = cam.getFrustumFar();
+ } else {
+ if (ortho) {
+ renderer.setViewProjectionMatrices(Matrix4f.IDENTITY, orthoMatrix);
+ } else {
+ renderer.setViewProjectionMatrices(cam.getViewMatrix(),
+ cam.getProjectionMatrix());
+ }
+
+ }
+ }
+
+ /**
+ * Set the camera to use for rendering.
+ * <p>
+ * First, the camera's
+ * {@link Camera#setViewPort(float, float, float, float) view port parameters}
+ * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and
+ * {@link Camera#getProjectionMatrix() projection} matrices are set
+ * on the renderer. If <code>ortho</code> is <code>true</code>, then
+ * instead of using the camera's view and projection matrices, an ortho
+ * matrix is computed and used instead of the view projection matrix.
+ * The ortho matrix converts from the range (0 ~ Width, 0 ~ Height, -1 ~ +1)
+ * to the clip range (-1 ~ +1, -1 ~ +1, -1 ~ +1).
+ *
+ * @param cam The camera to set
+ * @param ortho True if to use orthographic projection (for GUI rendering),
+ * false if to use the camera's view and projection matrices.
+ */
+ public void setCamera(Camera cam, boolean ortho) {
+ setViewPort(cam);
+ setViewProjection(cam, ortho);
+ }
+
+ /**
+ * Draws the viewport but without notifying {@link SceneProcessor scene
+ * processors} of any rendering events.
+ *
+ * @param vp The ViewPort to render
+ *
+ * @see #renderViewPort(com.jme3.renderer.ViewPort, float)
+ */
+ public void renderViewPortRaw(ViewPort vp) {
+ setCamera(vp.getCamera(), false);
+ List<Spatial> scenes = vp.getScenes();
+ for (int i = scenes.size() - 1; i >= 0; i--) {
+ renderScene(scenes.get(i), vp);
+ }
+ flushQueue(vp);
+ }
+
+ /**
+ * Renders the {@link ViewPort}.
+ * <p>
+ * If the ViewPort is {@link ViewPort#isEnabled() disabled}, this method
+ * returns immediately. Otherwise, the ViewPort is rendered by
+ * the following process:<br>
+ * <ul>
+ * <li>All {@link SceneProcessor scene processors} that are attached
+ * to the ViewPort are {@link SceneProcessor#initialize(com.jme3.renderer.RenderManager, com.jme3.renderer.ViewPort) initialized}.
+ * </li>
+ * <li>The SceneProcessors' {@link SceneProcessor#preFrame(float) } method
+ * is called.</li>
+ * <li>The ViewPort's {@link ViewPort#getOutputFrameBuffer() output framebuffer}
+ * is set on the Renderer</li>
+ * <li>The camera is set on the renderer, including its view port parameters.
+ * (see {@link #setCamera(com.jme3.renderer.Camera, boolean) })</li>
+ * <li>Any buffers that the ViewPort requests to be cleared are cleared
+ * and the {@link ViewPort#getBackgroundColor() background color} is set</li>
+ * <li>Every scene that is attached to the ViewPort is flattened into
+ * the ViewPort's render queue
+ * (see {@link #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) })
+ * </li>
+ * <li>The SceneProcessors' {@link SceneProcessor#postQueue(com.jme3.renderer.queue.RenderQueue) }
+ * method is called.</li>
+ * <li>The render queue is sorted and then flushed, sending
+ * rendering commands to the underlying Renderer implementation.
+ * (see {@link #flushQueue(com.jme3.renderer.ViewPort) })</li>
+ * <li>The SceneProcessors' {@link SceneProcessor#postFrame(com.jme3.texture.FrameBuffer) }
+ * method is called.</li>
+ * <li>The translucent queue of the ViewPort is sorted and then flushed
+ * (see {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) })</li>
+ * <li>If any objects remained in the render queue, they are removed
+ * from the queue. This is generally objects added to the
+ * {@link RenderQueue#renderShadowQueue(com.jme3.renderer.queue.RenderQueue.ShadowMode, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean)
+ * shadow queue}
+ * which were not rendered because of a missing shadow renderer.</li>
+ * </ul>
+ *
+ * @param vp
+ * @param tpf
+ */
+ public void renderViewPort(ViewPort vp, float tpf) {
+ if (!vp.isEnabled()) {
+ return;
+ }
+ List<SceneProcessor> processors = vp.getProcessors();
+ if (processors.isEmpty()) {
+ processors = null;
+ }
+
+ if (processors != null) {
+ for (SceneProcessor proc : processors) {
+ if (!proc.isInitialized()) {
+ proc.initialize(this, vp);
+ }
+ proc.preFrame(tpf);
+ }
+ }
+
+ renderer.setFrameBuffer(vp.getOutputFrameBuffer());
+ setCamera(vp.getCamera(), false);
+ if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) {
+ if (vp.isClearColor()) {
+ renderer.setBackgroundColor(vp.getBackgroundColor());
+ }
+ renderer.clearBuffers(vp.isClearColor(),
+ vp.isClearDepth(),
+ vp.isClearStencil());
+ }
+
+ List<Spatial> scenes = vp.getScenes();
+ for (int i = scenes.size() - 1; i >= 0; i--) {
+ renderScene(scenes.get(i), vp);
+ }
+
+ if (processors != null) {
+ for (SceneProcessor proc : processors) {
+ proc.postQueue(vp.getQueue());
+ }
+ }
+
+ flushQueue(vp);
+
+ if (processors != null) {
+ for (SceneProcessor proc : processors) {
+ proc.postFrame(vp.getOutputFrameBuffer());
+ }
+ }
+ //renders the translucent objects queue after processors have been rendered
+ renderTranslucentQueue(vp);
+ // clear any remaining spatials that were not rendered.
+ clearQueue(vp);
+ }
+
+ /**
+ * Called by the application to render any ViewPorts
+ * added to this RenderManager.
+ * <p>
+ * Renders any viewports that were added using the following methods:
+ * <ul>
+ * <li>{@link #createPreView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * <li>{@link #createMainView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * <li>{@link #createPostView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * </ul>
+ *
+ * @param tpf Time per frame value
+ */
+ public void render(float tpf, boolean mainFrameBufferActive) {
+ if (renderer instanceof NullRenderer) {
+ return;
+ }
+
+ this.shader = renderer.getCaps().contains(Caps.GLSL100);
+
+ for (int i = 0; i < preViewPorts.size(); i++) {
+ ViewPort vp = preViewPorts.get(i);
+ if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+ renderViewPort(vp, tpf);
+ }
+ }
+ for (int i = 0; i < viewPorts.size(); i++) {
+ ViewPort vp = viewPorts.get(i);
+ if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+ renderViewPort(vp, tpf);
+ }
+ }
+ for (int i = 0; i < postViewPorts.size(); i++) {
+ ViewPort vp = postViewPorts.get(i);
+ if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive){
+ renderViewPort(vp, tpf);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/Renderer.java b/engine/src/core/com/jme3/renderer/Renderer.java
new file mode 100644
index 0000000..6e366ab
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Renderer.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer;
+
+import com.jme3.light.LightList;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+/**
+ * The <code>Renderer</code> is responsible for taking rendering commands and
+ * executing them on the underlying video hardware.
+ *
+ * @author Kirill Vainer
+ */
+public interface Renderer {
+
+ /**
+ * Get the capabilities of the renderer.
+ * @return The capabilities of the renderer.
+ */
+ public EnumSet<Caps> getCaps();
+
+ /**
+ * The statistics allow tracking of how data
+ * per frame, such as number of objects rendered, number of triangles, etc.
+ * These are updated when the Renderer's methods are used, make sure
+ * to call {@link Statistics#clearFrame() } at the appropriate time
+ * to get accurate info per frame.
+ */
+ public Statistics getStatistics();
+
+ /**
+ * Invalidates the current rendering state. Should be called after
+ * the GL state was changed manually or through an external library.
+ */
+ public void invalidateState();
+
+ /**
+ * Clears certain channels of the currently bound framebuffer.
+ *
+ * @param color True if to clear colors (RGBA)
+ * @param depth True if to clear depth/z
+ * @param stencil True if to clear stencil buffer (if available, otherwise
+ * ignored)
+ */
+ public void clearBuffers(boolean color, boolean depth, boolean stencil);
+
+ /**
+ * Sets the background (aka clear) color.
+ *
+ * @param color The background color to set
+ */
+ public void setBackgroundColor(ColorRGBA color);
+
+ /**
+ * Applies the given {@link RenderState}, making the necessary
+ * GL calls so that the state is applied.
+ */
+ public void applyRenderState(RenderState state);
+
+ /**
+ * Set the range of the depth values for objects. All rendered
+ * objects will have their depth clamped to this range.
+ *
+ * @param start The range start
+ * @param end The range end
+ */
+ public void setDepthRange(float start, float end);
+
+ /**
+ * Called when a new frame has been rendered.
+ */
+ public void onFrame();
+
+ /**
+ * Set the world matrix to use. Does nothing if the Renderer is
+ * shader based.
+ *
+ * @param worldMatrix World matrix to use.
+ */
+ public void setWorldMatrix(Matrix4f worldMatrix);
+
+ /**
+ * Sets the view and projection matrices to use. Does nothing if the Renderer
+ * is shader based.
+ *
+ * @param viewMatrix The view matrix to use.
+ * @param projMatrix The projection matrix to use.
+ */
+ public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix);
+
+ /**
+ * Set the viewport location and resolution on the screen.
+ *
+ * @param x The x coordinate of the viewport
+ * @param y The y coordinate of the viewport
+ * @param width Width of the viewport
+ * @param height Height of the viewport
+ */
+ public void setViewPort(int x, int y, int width, int height);
+
+ /**
+ * Specifies a clipping rectangle.
+ * For all future rendering commands, no pixels will be allowed
+ * to be rendered outside of the clip rectangle.
+ *
+ * @param x The x coordinate of the clip rect
+ * @param y The y coordinate of the clip rect
+ * @param width Width of the clip rect
+ * @param height Height of the clip rect
+ */
+ public void setClipRect(int x, int y, int width, int height);
+
+ /**
+ * Clears the clipping rectangle set with
+ * {@link #setClipRect(int, int, int, int) }.
+ */
+ public void clearClipRect();
+
+ /**
+ * Set lighting state.
+ * Does nothing if the renderer is shader based.
+ * The lights should be provided in world space.
+ * Specify <code>null</code> to disable lighting.
+ *
+ * @param lights The light list to set.
+ */
+ public void setLighting(LightList lights);
+
+ /**
+ * Sets the shader to use for rendering.
+ * If the shader has not been uploaded yet, it is compiled
+ * and linked. If it has been uploaded, then the
+ * uniform data is updated and the shader is set.
+ *
+ * @param shader The shader to use for rendering.
+ */
+ public void setShader(Shader shader);
+
+ /**
+ * Deletes a shader. This method also deletes
+ * the attached shader sources.
+ *
+ * @param shader Shader to delete.
+ */
+ public void deleteShader(Shader shader);
+
+ /**
+ * Deletes the provided shader source.
+ *
+ * @param source The ShaderSource to delete.
+ */
+ public void deleteShaderSource(ShaderSource source);
+
+ /**
+ * Copies contents from src to dst, scaling if necessary.
+ */
+ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst);
+
+ /**
+ * Copies contents from src to dst, scaling if necessary.
+ * set copyDepth to false to only copy the color buffers.
+ */
+ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth);
+
+ /**
+ * Sets the framebuffer that will be drawn to.
+ */
+ public void setFrameBuffer(FrameBuffer fb);
+
+ /**
+ * Set the framebuffer that will be set instead of the main framebuffer
+ * when a call to setFrameBuffer(null) is made.
+ *
+ * @param fb
+ */
+ public void setMainFrameBufferOverride(FrameBuffer fb);
+
+ /**
+ * Reads the pixels currently stored in the specified framebuffer
+ * into the given ByteBuffer object.
+ * Only color pixels are transferred, the format is BGRA with 8 bits
+ * per component. The given byte buffer should have at least
+ * fb.getWidth() * fb.getHeight() * 4 bytes remaining.
+ *
+ * @param fb The framebuffer to read from
+ * @param byteBuf The bytebuffer to transfer color data to
+ */
+ public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf);
+
+ /**
+ * Deletes a framebuffer and all attached renderbuffers
+ */
+ public void deleteFrameBuffer(FrameBuffer fb);
+
+ /**
+ * Sets the texture to use for the given texture unit.
+ */
+ public void setTexture(int unit, Texture tex);
+
+ /**
+ * Deletes a texture from the GPU.
+ */
+ public void deleteImage(Image image);
+
+ /**
+ * Uploads a vertex buffer to the GPU.
+ *
+ * @param vb The vertex buffer to upload
+ */
+ public void updateBufferData(VertexBuffer vb);
+
+ /**
+ * Deletes a vertex buffer from the GPU.
+ * @param vb The vertex buffer to delete
+ */
+ public void deleteBuffer(VertexBuffer vb);
+
+ /**
+ * Renders <code>count</code> meshes, with the geometry data supplied.
+ * The shader which is currently set with <code>setShader</code> is
+ * responsible for transforming the input verticies into clip space
+ * and shading it based on the given vertex attributes.
+ * The int variable gl_InstanceID can be used to access the current
+ * instance of the mesh being rendered inside the vertex shader.
+ *
+ * @param mesh The mesh to render
+ * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
+ * @param count Number of mesh instances to render
+ */
+ public void renderMesh(Mesh mesh, int lod, int count);
+
+ /**
+ * Resets all previously used {@link GLObject}s on this Renderer.
+ * The state of the GLObjects is reset in such way, that using
+ * them again will cause the renderer to reupload them.
+ * Call this method when you know the GL context is going to shutdown.
+ *
+ * @see GLObject#resetObject()
+ */
+ public void resetGLObjects();
+
+ /**
+ * Deletes all previously used {@link GLObject}s on this Renderer, and
+ * then resets the GLObjects.
+ *
+ * @see #resetGLObjects()
+ * @see GLObject#deleteObject(com.jme3.renderer.Renderer)
+ */
+ public void cleanup();
+
+ /**
+ * Sets the alpha to coverage state.
+ * <p>
+ * When alpha coverage and multi-sampling is enabled,
+ * each pixel will contain alpha coverage in all
+ * of its subsamples, which is then combined when
+ * other future alpha-blended objects are rendered.
+ * </p>
+ * <p>
+ * Alpha-to-coverage is useful for rendering transparent objects
+ * without having to worry about sorting them.
+ * </p>
+ */
+ public void setAlphaToCoverage(boolean value);
+}
diff --git a/engine/src/core/com/jme3/renderer/RendererException.java b/engine/src/core/com/jme3/renderer/RendererException.java
new file mode 100644
index 0000000..41964a2
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/RendererException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+/**
+ * <code>RendererException</code> is raised when a renderer encounters
+ * a fatal rendering error.
+ *
+ * @author Kirill Vainer
+ */
+public class RendererException extends RuntimeException {
+
+ /**
+ * Creates a new instance of <code>RendererException</code>
+ */
+ public RendererException(String message){
+ super(message);
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/Statistics.java b/engine/src/core/com/jme3/renderer/Statistics.java
new file mode 100644
index 0000000..fb8f2a0
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/Statistics.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.scene.Mesh;
+import com.jme3.shader.Shader;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import java.util.HashSet;
+
+/**
+ * The statistics class allows tracking of real-time rendering statistics.
+ * <p>
+ * The <code>Statistics</code> can be retrieved by using {@link Renderer#getStatistics() }.
+ *
+ * @author Kirill Vainer
+ */
+public class Statistics {
+
+ protected int numObjects;
+ protected int numTriangles;
+ protected int numVertices;
+ protected int numShaderSwitches;
+ protected int numTextureBinds;
+ protected int numFboSwitches;
+ protected int numUniformsSet;
+
+ protected int memoryShaders;
+ protected int memoryFrameBuffers;
+ protected int memoryTextures;
+
+ protected HashSet<Integer> shadersUsed = new HashSet<Integer>();
+ protected HashSet<Integer> texturesUsed = new HashSet<Integer>();
+ protected HashSet<Integer> fbosUsed = new HashSet<Integer>();
+
+ /**
+ * Returns a list of labels corresponding to each statistic.
+ *
+ * @return a list of labels corresponding to each statistic.
+ *
+ * @see #getData(int[])
+ */
+ public String[] getLabels(){
+ return new String[]{ "Vertices",
+ "Triangles",
+ "Uniforms",
+
+ "Objects",
+
+ "Shaders (S)",
+ "Shaders (F)",
+ "Shaders (M)",
+
+ "Textures (S)",
+ "Textures (F)",
+ "Textures (M)",
+
+ "FrameBuffers (S)",
+ "FrameBuffers (F)",
+ "FrameBuffers (M)" };
+
+ }
+
+ /**
+ * Retrieves the statistics data into the given array.
+ * The array should be as large as the array given in
+ * {@link #getLabels() }.
+ *
+ * @param data The data array to write to
+ */
+ public void getData(int[] data){
+ data[0] = numVertices;
+ data[1] = numTriangles;
+ data[2] = numUniformsSet;
+ data[3] = numObjects;
+
+ data[4] = numShaderSwitches;
+ data[5] = shadersUsed.size();
+ data[6] = memoryShaders;
+
+ data[7] = numTextureBinds;
+ data[8] = texturesUsed.size();
+ data[9] = memoryTextures;
+
+ data[10] = numFboSwitches;
+ data[11] = fbosUsed.size();
+ data[12] = memoryFrameBuffers;
+ }
+
+ /**
+ * Called by the Renderer when a mesh has been drawn.
+ *
+ */
+ public void onMeshDrawn(Mesh mesh, int lod){
+ numObjects ++;
+ numTriangles += mesh.getTriangleCount(lod);
+ numVertices += mesh.getVertexCount();
+ }
+
+ /**
+ * Called by the Renderer when a shader has been utilized.
+ *
+ * @param shader The shader that was used
+ * @param wasSwitched If true, the shader has required a state switch
+ */
+ public void onShaderUse(Shader shader, boolean wasSwitched){
+ assert shader.getId() >= 1;
+
+ if (!shadersUsed.contains(shader.getId()))
+ shadersUsed.add(shader.getId());
+
+ if (wasSwitched)
+ numShaderSwitches++;
+ }
+
+ /**
+ * Called by the Renderer when a uniform was set.
+ */
+ public void onUniformSet(){
+ numUniformsSet ++;
+ }
+
+ /**
+ * Called by the Renderer when a texture has been set.
+ *
+ * @param image The image that was set
+ * @param wasSwitched If true, the texture has required a state switch
+ */
+ public void onTextureUse(Image image, boolean wasSwitched){
+ assert image.getId() >= 1;
+
+ if (!texturesUsed.contains(image.getId()))
+ texturesUsed.add(image.getId());
+
+ if (wasSwitched)
+ numTextureBinds ++;
+ }
+
+ /**
+ * Called by the Renderer when a framebuffer has been set.
+ *
+ * @param fb The framebuffer that was set
+ * @param wasSwitched If true, the framebuffer required a state switch
+ */
+ public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){
+ if (fb != null){
+ assert fb.getId() >= 1;
+
+ if (!fbosUsed.contains(fb.getId()))
+ fbosUsed.add(fb.getId());
+ }
+
+ if (wasSwitched)
+ numFboSwitches ++;
+ }
+
+ /**
+ * Clears all frame-specific statistics such as objects used per frame.
+ */
+ public void clearFrame(){
+ shadersUsed.clear();
+ texturesUsed.clear();
+ fbosUsed.clear();
+
+ numObjects = 0;
+ numTriangles = 0;
+ numVertices = 0;
+ numShaderSwitches = 0;
+ numTextureBinds = 0;
+ numFboSwitches = 0;
+ numUniformsSet = 0;
+ }
+
+ /**
+ * Called by the Renderer when it creates a new shader
+ */
+ public void onNewShader(){
+ memoryShaders ++;
+ }
+
+ /**
+ * Called by the Renderer when it creates a new texture
+ */
+ public void onNewTexture(){
+ memoryTextures ++;
+ }
+
+ /**
+ * Called by the Renderer when it creates a new framebuffer
+ */
+ public void onNewFrameBuffer(){
+ memoryFrameBuffers ++;
+ }
+
+ /**
+ * Called by the Renderer when it deletes a shader
+ */
+ public void onDeleteShader(){
+ memoryShaders --;
+ }
+
+ /**
+ * Called by the Renderer when it deletes a texture
+ */
+ public void onDeleteTexture(){
+ memoryTextures --;
+ }
+
+ /**
+ * Called by the Renderer when it deletes a framebuffer
+ */
+ public void onDeleteFrameBuffer(){
+ memoryFrameBuffers --;
+ }
+
+ /**
+ * Called when video memory is cleared.
+ */
+ public void clearMemory(){
+ memoryFrameBuffers = 0;
+ memoryShaders = 0;
+ memoryTextures = 0;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/ViewPort.java b/engine/src/core/com/jme3/renderer/ViewPort.java
new file mode 100644
index 0000000..ad9ab2f
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/ViewPort.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.scene.Spatial;
+import com.jme3.texture.FrameBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A <code>ViewPort</code> represents a view inside the display
+ * window or a {@link FrameBuffer} to which scenes will be rendered.
+ * <p>
+ * A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera}
+ * which is used to render a set of {@link #attachScene(com.jme3.scene.Spatial) scenes}.
+ * A view port has a location on the screen as set by the
+ * {@link Camera#setViewPort(float, float, float, float) } method.
+ * By default, a view port does not clear the framebuffer, but it can be
+ * set to {@link #setClearFlags(boolean, boolean, boolean) clear the framebuffer}.
+ * The background color which the color buffer is cleared to can be specified
+ * via the {@link #setBackgroundColor(com.jme3.math.ColorRGBA)} method.
+ * <p>
+ * A ViewPort has a list of {@link SceneProcessor}s which can
+ * control how the ViewPort is rendered by the {@link RenderManager}.
+ *
+ * @author Kirill Vainer
+ *
+ * @see RenderManager
+ * @see SceneProcessor
+ * @see Spatial
+ * @see Camera
+ */
+public class ViewPort {
+
+ protected final String name;
+ protected final Camera cam;
+ protected final RenderQueue queue = new RenderQueue();
+ protected final ArrayList<Spatial> sceneList = new ArrayList<Spatial>();
+ protected final ArrayList<SceneProcessor> processors = new ArrayList<SceneProcessor>();
+ protected FrameBuffer out = null;
+
+ protected final ColorRGBA backColor = new ColorRGBA(0,0,0,0);
+ protected boolean clearDepth = false, clearColor = false, clearStencil = false;
+ private boolean enabled = true;
+
+ /**
+ * Create a new viewport. User code should generally use these methods instead:<br>
+ * <ul>
+ * <li>{@link RenderManager#createPreView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * <li>{@link RenderManager#createMainView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * <li>{@link RenderManager#createPostView(java.lang.String, com.jme3.renderer.Camera) }</li>
+ * </ul>
+ *
+ * @param name The name of the viewport. Used for debugging only.
+ * @param cam The camera through which the viewport is rendered. The camera
+ * cannot be swapped to a different one after creating the viewport.
+ */
+ public ViewPort(String name, Camera cam) {
+ this.name = name;
+ this.cam = cam;
+ }
+
+ /**
+ * Returns the name of the viewport as set in the constructor.
+ *
+ * @return the name of the viewport
+ *
+ * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera)
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Get the list of {@link SceneProcessor scene processors} that were
+ * added to this <code>ViewPort</code>
+ *
+ * @return the list of processors attached to this ViewPort
+ *
+ * @see #addProcessor(com.jme3.post.SceneProcessor)
+ */
+ public List<SceneProcessor> getProcessors(){
+ return processors;
+ }
+
+ /**
+ * Adds a {@link SceneProcessor} to this ViewPort.
+ * <p>
+ * SceneProcessors that are added to the ViewPort will be notified
+ * of events as the ViewPort is being rendered by the {@link RenderManager}.
+ *
+ * @param processor The processor to add
+ *
+ * @see SceneProcessor
+ */
+ public void addProcessor(SceneProcessor processor){
+ processors.add(processor);
+ }
+
+ /**
+ * Removes a {@link SceneProcessor} from this ViewPort.
+ * <p>
+ * The processor will no longer receive events occurring to this ViewPort.
+ *
+ * @param processor The processor to remove
+ *
+ * @see SceneProcessor
+ */
+ public void removeProcessor(SceneProcessor processor){
+ processors.remove(processor);
+ processor.cleanup();
+ }
+
+ /**
+ * Check if depth buffer clearing is enabled.
+ *
+ * @return true if depth buffer clearing is enabled.
+ *
+ * @see #setClearDepth(boolean)
+ */
+ public boolean isClearDepth() {
+ return clearDepth;
+ }
+
+ /**
+ * Enable or disable clearing of the depth buffer for this ViewPort.
+ * <p>
+ * By default depth clearing is disabled.
+ *
+ * @param clearDepth Enable/disable depth buffer clearing.
+ */
+ public void setClearDepth(boolean clearDepth) {
+ this.clearDepth = clearDepth;
+ }
+
+ /**
+ * Check if color buffer clearing is enabled.
+ *
+ * @return true if color buffer clearing is enabled.
+ *
+ * @see #setClearColor(boolean)
+ */
+ public boolean isClearColor() {
+ return clearColor;
+ }
+
+ /**
+ * Enable or disable clearing of the color buffer for this ViewPort.
+ * <p>
+ * By default color clearing is disabled.
+ *
+ * @param clearColor Enable/disable color buffer clearing.
+ */
+ public void setClearColor(boolean clearColor) {
+ this.clearColor = clearColor;
+ }
+
+ /**
+ * Check if stencil buffer clearing is enabled.
+ *
+ * @return true if stencil buffer clearing is enabled.
+ *
+ * @see #setClearStencil(boolean)
+ */
+ public boolean isClearStencil() {
+ return clearStencil;
+ }
+
+ /**
+ * Enable or disable clearing of the stencil buffer for this ViewPort.
+ * <p>
+ * By default stencil clearing is disabled.
+ *
+ * @param clearStencil Enable/disable stencil buffer clearing.
+ */
+ public void setClearStencil(boolean clearStencil) {
+ this.clearStencil = clearStencil;
+ }
+
+ /**
+ * Set the clear flags (color, depth, stencil) in one call.
+ *
+ * @param color If color buffer clearing should be enabled.
+ * @param depth If depth buffer clearing should be enabled.
+ * @param stencil If stencil buffer clearing should be enabled.
+ *
+ * @see #setClearColor(boolean)
+ * @see #setClearDepth(boolean)
+ * @see #setClearStencil(boolean)
+ */
+ public void setClearFlags(boolean color, boolean depth, boolean stencil){
+ this.clearColor = color;
+ this.clearDepth = depth;
+ this.clearStencil = stencil;
+ }
+
+ /**
+ * Returns the framebuffer where this ViewPort's scenes are
+ * rendered to.
+ *
+ * @return the framebuffer where this ViewPort's scenes are
+ * rendered to.
+ *
+ * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer)
+ */
+ public FrameBuffer getOutputFrameBuffer() {
+ return out;
+ }
+
+ /**
+ * Sets the output framebuffer for the ViewPort.
+ * <p>
+ * The output framebuffer specifies where the scenes attached
+ * to this ViewPort are rendered to. By default this is <code>null</code>
+ * which indicates the scenes are rendered to the display window.
+ *
+ * @param out The framebuffer to render scenes to, or null if to render
+ * to the screen.
+ */
+ public void setOutputFrameBuffer(FrameBuffer out) {
+ this.out = out;
+ }
+
+ /**
+ * Returns the camera which renders the attached scenes.
+ *
+ * @return the camera which renders the attached scenes.
+ *
+ * @see Camera
+ */
+ public Camera getCamera() {
+ return cam;
+ }
+
+ /**
+ * Internal use only.
+ */
+ public RenderQueue getQueue() {
+ return queue;
+ }
+
+ /**
+ * Attaches a new scene to render in this ViewPort.
+ *
+ * @param scene The scene to attach
+ *
+ * @see Spatial
+ */
+ public void attachScene(Spatial scene){
+ sceneList.add(scene);
+ }
+
+ /**
+ * Detaches a scene from rendering.
+ *
+ * @param scene The scene to detach
+ *
+ * @see #attachScene(com.jme3.scene.Spatial)
+ */
+ public void detachScene(Spatial scene){
+ sceneList.remove(scene);
+ }
+
+ /**
+ * Removes all attached scenes.
+ *
+ * @see #attachScene(com.jme3.scene.Spatial)
+ */
+ public void clearScenes() {
+ sceneList.clear();
+ }
+
+ /**
+ * Returns a list of all attached scenes.
+ *
+ * @return a list of all attached scenes.
+ *
+ * @see #attachScene(com.jme3.scene.Spatial)
+ */
+ public List<Spatial> getScenes(){
+ return sceneList;
+ }
+
+ /**
+ * Sets the background color.
+ * <p>
+ * When the ViewPort's color buffer is cleared
+ * (if {@link #setClearColor(boolean) color clearing} is enabled),
+ * this specifies the color to which the color buffer is set to.
+ * By default the background color is black without alpha.
+ *
+ * @param background the background color.
+ */
+ public void setBackgroundColor(ColorRGBA background){
+ backColor.set(background);
+ }
+
+ /**
+ * Returns the background color of this ViewPort
+ *
+ * @return the background color of this ViewPort
+ *
+ * @see #setBackgroundColor(com.jme3.math.ColorRGBA)
+ */
+ public ColorRGBA getBackgroundColor(){
+ return backColor;
+ }
+
+ /**
+ * Enable or disable this ViewPort.
+ * <p>
+ * Disabled ViewPorts are skipped by the {@link RenderManager} when
+ * rendering. By default all ViewPorts are enabled.
+ *
+ * @param enable If the viewport should be disabled or enabled.
+ */
+ public void setEnabled(boolean enable) {
+ this.enabled = enable;
+ }
+
+ /**
+ * Returns true if the viewport is enabled, false otherwise.
+ * @return true if the viewport is enabled, false otherwise.
+ * @see #setEnabled(boolean)
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/package.html b/engine/src/core/com/jme3/renderer/package.html
new file mode 100644
index 0000000..9d80ec8
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/package.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.renderer</code> package provides classes responsible for
+rendering.
+<p>
+The most critical classes are the {@link com.jme3.renderer.Renderer},
+which is the low-level rendering implementation and is abstract, and the
+{@link com.jme3.renderer.RenderManager} class, which provides the high-level
+rendering logic on top of the <code>Renderer</code>.
+<p>
+To accompany rendering, several helper classes are available.
+<ul>
+ <li>The {@link com.jme3.renderer.Camera} is used to specify the point-of-view
+ from which scenes are rendered.</li>
+ <li>The {@link com.jme3.renderer.ViewPort} is the
+aggregation of a Camera and a set of {@link com.jme3.scene.Spatial scenes}
+which are to be rendered, as well as additional info.</li>
+ <li>The {@link com.jme3.renderer.Caps} class contains renderer capabilities
+which the user can query to find out what features are available in the
+rendering implementation. </li>
+ <li>The {@link com.jme3.renderer.Statistics} class is updated in real time
+ by the Renderer, and is used to find out various statistics about
+ the rendering</li>
+ <li>The {@link com.jme3.renderer.GLObjectManager} and {@link com.jme3.renderer.GLObject} classes
+ provide a link between the renderer's native objects and Java's garbage collected objects,
+ allowing the engine to track when the Java object counterpart is garbage collected
+ and then delete the native object counterpart from the renderer.</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java
new file mode 100644
index 0000000..8f42df7
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GeometryComparator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import java.util.Comparator;
+
+/**
+ * <code>GeometryComparator</code> is a special version of {@link Comparator}
+ * that is used to sort geometries for rendering in the {@link RenderQueue}.
+ *
+ * @author Kirill Vainer
+ */
+public interface GeometryComparator extends Comparator<Geometry> {
+
+ /**
+ * Set the camera to use for sorting.
+ *
+ * @param cam The camera to use for sorting
+ */
+ public void setCamera(Camera cam);
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/GeometryList.java b/engine/src/core/com/jme3/renderer/queue/GeometryList.java
new file mode 100644
index 0000000..9f36ed9
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GeometryList.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+import com.jme3.util.SortUtil;
+
+/**
+ * This class is a special purpose list of {@link Geometry} objects for render
+ * queuing.
+ *
+ * @author Jack Lindamood
+ * @author Three Rings - better sorting alg.
+ * @author Kirill Vainer
+ */
+public class GeometryList {
+
+ private static final int DEFAULT_SIZE = 32;
+
+ private Geometry[] geometries;
+ private Geometry[] geometries2;
+ private int size;
+ private GeometryComparator comparator;
+
+ /**
+ * Initializes the GeometryList to use the given {@link GeometryComparator}
+ * to use for comparing geometries.
+ *
+ * @param comparator The comparator to use.
+ */
+ public GeometryList(GeometryComparator comparator) {
+ size = 0;
+ geometries = new Geometry[DEFAULT_SIZE];
+ geometries2 = new Geometry[DEFAULT_SIZE];
+ this.comparator = comparator;
+ }
+
+ /**
+ * Set the camera that will be set on the geometry comparators
+ * via {@link GeometryComparator#setCamera(com.jme3.renderer.Camera)}.
+ *
+ * @param cam Camera to use for sorting.
+ */
+ public void setCamera(Camera cam){
+ this.comparator.setCamera(cam);
+ }
+
+ /**
+ * Returns the number of elements in this GeometryList.
+ *
+ * @return Number of elements in the list
+ */
+ public int size(){
+ return size;
+ }
+
+ /**
+ * Returns the element at the given index.
+ *
+ * @param index The index to lookup
+ * @return Geometry at the index
+ */
+ public Geometry get(int index){
+ return geometries[index];
+ }
+
+ /**
+ * Adds a geometry to the list.
+ * List size is doubled if there is no room.
+ *
+ * @param g
+ * The geometry to add.
+ */
+ public void add(Geometry g) {
+ if (size == geometries.length) {
+ Geometry[] temp = new Geometry[size * 2];
+ System.arraycopy(geometries, 0, temp, 0, size);
+ geometries = temp; // original list replaced by double-size list
+
+ geometries2 = new Geometry[size * 2];
+ }
+ geometries[size++] = g;
+ }
+
+ /**
+ * Resets list size to 0.
+ */
+ public void clear() {
+ for (int i = 0; i < size; i++){
+ geometries[i] = null;
+ }
+
+ size = 0;
+ }
+
+ /**
+ * Sorts the elements in the list according to their Comparator.
+ */
+ public void sort() {
+ if (size > 1) {
+ // sort the spatial list using the comparator
+
+// SortUtil.qsort(geometries, 0, size, comparator);
+// Arrays.sort(geometries, 0, size, comparator);
+
+ System.arraycopy(geometries, 0, geometries2, 0, size);
+ SortUtil.msort(geometries2, geometries, 0, size-1, comparator);
+
+
+ }
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/renderer/queue/GuiComparator.java b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java
new file mode 100644
index 0000000..b35a03c
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/GuiComparator.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+/**
+ * <code>GuiComparator</code> sorts geometries back-to-front based
+ * on their Z position.
+ *
+ * @author Kirill Vainer
+ */
+public class GuiComparator implements GeometryComparator {
+
+ public int compare(Geometry o1, Geometry o2) {
+ float z1 = o1.getWorldTranslation().getZ();
+ float z2 = o2.getWorldTranslation().getZ();
+ if (z1 > z2)
+ return 1;
+ else if (z1 < z2)
+ return -1;
+ else
+ return 0;
+ }
+
+ public void setCamera(Camera cam) {
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/NullComparator.java b/engine/src/core/com/jme3/renderer/queue/NullComparator.java
new file mode 100644
index 0000000..6463558
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/NullComparator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+/**
+ * <code>NullComparator</code> does not sort geometries. They will be in
+ * arbitrary order.
+ *
+ * @author Kirill Vainer
+ */
+public class NullComparator implements GeometryComparator {
+ public int compare(Geometry o1, Geometry o2) {
+ return 0;
+ }
+
+ public void setCamera(Camera cam) {
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java
new file mode 100644
index 0000000..85f4093
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/OpaqueComparator.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+public class OpaqueComparator implements GeometryComparator {
+
+ private Camera cam;
+ private final Vector3f tempVec = new Vector3f();
+ private final Vector3f tempVec2 = new Vector3f();
+
+ public void setCamera(Camera cam){
+ this.cam = cam;
+ }
+
+ public float distanceToCam(Geometry spat){
+ if (spat == null)
+ return Float.NEGATIVE_INFINITY;
+
+ if (spat.queueDistance != Float.NEGATIVE_INFINITY)
+ return spat.queueDistance;
+
+ Vector3f camPosition = cam.getLocation();
+ Vector3f viewVector = cam.getDirection(tempVec2);
+ Vector3f spatPosition = null;
+
+ if (spat.getWorldBound() != null){
+ spatPosition = spat.getWorldBound().getCenter();
+ }else{
+ spatPosition = spat.getWorldTranslation();
+ }
+
+ spatPosition.subtract(camPosition, tempVec);
+ spat.queueDistance = tempVec.dot(viewVector);
+
+ return spat.queueDistance;
+ }
+
+ public int compare(Geometry o1, Geometry o2) {
+ Material m1 = o1.getMaterial();
+ Material m2 = o2.getMaterial();
+
+ int sortId = m1.compareTo(m2);
+
+ if (sortId == 0){
+ // use the same shader.
+ // sort front-to-back then.
+ float d1 = distanceToCam(o1);
+ float d2 = distanceToCam(o2);
+
+ if (d1 == d2)
+ return 0;
+ else if (d1 < d2)
+ return -1;
+ else
+ return 1;
+ }else{
+ return sortId;
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/RenderQueue.java b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java
new file mode 100644
index 0000000..dba604e
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/RenderQueue.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.renderer.queue;
+
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+
+/**
+ * <code>RenderQueue</code> is used to queue up and sort
+ * {@link Geometry geometries} for rendering.
+ *
+ * @author Kirill Vainer
+ */
+public class RenderQueue {
+
+ private GeometryList opaqueList;
+ private GeometryList guiList;
+ private GeometryList transparentList;
+ private GeometryList translucentList;
+ private GeometryList skyList;
+ private GeometryList shadowRecv;
+ private GeometryList shadowCast;
+
+ /**
+ * Creates a new RenderQueue, the default {@link GeometryComparator comparators}
+ * are used for all {@link GeometryList geometry lists}.
+ */
+ public RenderQueue() {
+ this.opaqueList = new GeometryList(new OpaqueComparator());
+ this.guiList = new GeometryList(new GuiComparator());
+ this.transparentList = new GeometryList(new TransparentComparator());
+ this.translucentList = new GeometryList(new TransparentComparator());
+ this.skyList = new GeometryList(new NullComparator());
+ this.shadowRecv = new GeometryList(new OpaqueComparator());
+ this.shadowCast = new GeometryList(new OpaqueComparator());
+ }
+
+ /**
+ * The render queue <code>Bucket</code> specifies the bucket
+ * to which the spatial will be placed when rendered.
+ * <p>
+ * The behavior of the rendering will differ depending on which
+ * bucket the spatial is placed. A spatial's queue bucket can be set
+ * via {@link Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) }.
+ */
+ public enum Bucket {
+ /**
+ * The renderer will try to find the optimal order for rendering all
+ * objects using this mode.
+ * You should use this mode for most normal objects, except transparent
+ * ones, as it could give a nice performance boost to your application.
+ */
+ Opaque,
+
+ /**
+ * This is the mode you should use for object with
+ * transparency in them. It will ensure the objects furthest away are
+ * rendered first. That ensures when another transparent object is drawn on
+ * top of previously drawn objects, you can see those (and the object drawn
+ * using Opaque) through the transparent parts of the newly drawn
+ * object.
+ */
+ Transparent,
+
+ /**
+ * A special mode used for rendering really far away, flat objects -
+ * e.g. skies. In this mode, the depth is set to infinity so
+ * spatials in this bucket will appear behind everything, the downside
+ * to this bucket is that 3D objects will not be rendered correctly
+ * due to lack of depth testing.
+ */
+ Sky,
+
+ /**
+ * A special mode used for rendering transparent objects that
+ * should not be effected by {@link SceneProcessor}.
+ * Generally this would contain translucent objects, and
+ * also objects that do not write to the depth buffer such as
+ * particle emitters.
+ */
+ Translucent,
+
+ /**
+ * This is a special mode, for drawing 2D object
+ * without perspective (such as GUI or HUD parts).
+ * The spatial's world coordinate system has the range
+ * of [0, 0, -1] to [Width, Height, 1] where Width/Height is
+ * the resolution of the screen rendered to. Any spatials
+ * outside of that range are culled.
+ */
+ Gui,
+
+ /**
+ * A special mode, that will ensure that this spatial uses the same
+ * mode as the parent Node does.
+ */
+ Inherit,
+ }
+
+ /**
+ * <code>ShadowMode</code> is a marker used to specify how shadow
+ * effects should treat the spatial.
+ */
+ public enum ShadowMode {
+ /**
+ * Disable both shadow casting and shadow receiving for this spatial.
+ * Generally used for special effects like particle emitters.
+ */
+ Off,
+
+ /**
+ * Enable casting of shadows but not receiving them.
+ */
+ Cast,
+
+ /**
+ * Enable receiving of shadows but not casting them.
+ */
+ Receive,
+
+ /**
+ * Enable both receiving and casting of shadows.
+ */
+ CastAndReceive,
+
+ /**
+ * Inherit the <code>ShadowMode</code> from the parent node.
+ */
+ Inherit
+ }
+
+ /**
+ * Sets a different geometry comparator for the specified bucket, one
+ * of Gui, Opaque, Sky, or Transparent. The GeometryComparators are
+ * used to sort the accumulated list of geometries before actual rendering
+ * occurs.
+ *
+ * <p>The most significant comparator is the one for the transparent
+ * bucket since there is no correct way to sort the transparent bucket
+ * that will handle all geometry all the time. In certain cases, the
+ * application may know the best way to sort and now has the option of
+ * configuring a specific implementation.</p>
+ *
+ * <p>The default comparators are:</p>
+ * <ul>
+ * <li>Bucket.Opaque: {@link com.jme3.renderer.queue.OpaqueComparator} which sorts
+ * by material first and front to back within the same material.
+ * <li>Bucket.Transparent: {@link com.jme3.renderer.queue.TransparentComparator} which
+ * sorts purely back to front by leading bounding edge with no material sort.
+ * <li>Bucket.Translucent: {@link com.jme3.renderer.queue.TransparentComparator} which
+ * sorts purely back to front by leading bounding edge with no material sort. this bucket is rendered after post processors.
+ * <li>Bucket.Sky: {@link com.jme3.renderer.queue.NullComparator} which does no sorting
+ * at all.
+ * <li>Bucket.Gui: {@link com.jme3.renderer.queue.GuiComparator} sorts geometries back to
+ * front based on their Z values.
+ */
+ public void setGeometryComparator(Bucket bucket, GeometryComparator c) {
+ switch (bucket) {
+ case Gui:
+ guiList = new GeometryList(c);
+ break;
+ case Opaque:
+ opaqueList = new GeometryList(c);
+ break;
+ case Sky:
+ skyList = new GeometryList(c);
+ break;
+ case Transparent:
+ transparentList = new GeometryList(c);
+ break;
+ case Translucent:
+ translucentList = new GeometryList(c);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown bucket type: " + bucket);
+ }
+ }
+
+ /**
+ * Adds a geometry to a shadow bucket.
+ * Note that this operation is done automatically by the
+ * {@link RenderManager}. {@link SceneProcessor}s that handle
+ * shadow rendering should fetch the queue by using
+ * {@link #getShadowQueueContent(com.jme3.renderer.queue.RenderQueue.ShadowMode) },
+ * by default no action is taken on the shadow queues.
+ *
+ * @param g The geometry to add
+ * @param shadBucket The shadow bucket type, if it is
+ * {@link ShadowMode#CastAndReceive}, it is added to both the cast
+ * and the receive buckets.
+ */
+ public void addToShadowQueue(Geometry g, ShadowMode shadBucket) {
+ switch (shadBucket) {
+ case Inherit:
+ break;
+ case Off:
+ break;
+ case Cast:
+ shadowCast.add(g);
+ break;
+ case Receive:
+ shadowRecv.add(g);
+ break;
+ case CastAndReceive:
+ shadowCast.add(g);
+ shadowRecv.add(g);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized shadow bucket type: " + shadBucket);
+ }
+ }
+
+ /**
+ * Adds a geometry to the given bucket.
+ * The {@link RenderManager} automatically handles this task
+ * when flattening the scene graph. The bucket to add
+ * the geometry is determined by {@link Geometry#getQueueBucket() }.
+ *
+ * @param g The geometry to add
+ * @param bucket The bucket to add to, usually
+ * {@link Geometry#getQueueBucket() }.
+ */
+ public void addToQueue(Geometry g, Bucket bucket) {
+ switch (bucket) {
+ case Gui:
+ guiList.add(g);
+ break;
+ case Opaque:
+ opaqueList.add(g);
+ break;
+ case Sky:
+ skyList.add(g);
+ break;
+ case Transparent:
+ transparentList.add(g);
+ break;
+ case Translucent:
+ translucentList.add(g);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unknown bucket type: " + bucket);
+ }
+ }
+
+ /**
+ *
+ * @param shadBucket
+ * @return
+ */
+ public GeometryList getShadowQueueContent(ShadowMode shadBucket) {
+ switch (shadBucket) {
+ case Cast:
+ return shadowCast;
+ case Receive:
+ return shadowRecv;
+ default:
+ throw new IllegalArgumentException("Only Cast or Receive are allowed");
+ }
+ }
+
+ private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) {
+ list.setCamera(cam); // select camera for sorting
+ list.sort();
+ for (int i = 0; i < list.size(); i++) {
+ Geometry obj = list.get(i);
+ assert obj != null;
+ rm.renderGeometry(obj);
+ obj.queueDistance = Float.NEGATIVE_INFINITY;
+ }
+ if (clear) {
+ list.clear();
+ }
+ }
+
+ public void renderShadowQueue(GeometryList list, RenderManager rm, Camera cam, boolean clear) {
+ renderGeometryList(list, rm, cam, clear);
+ }
+
+ public void renderShadowQueue(ShadowMode shadBucket, RenderManager rm, Camera cam, boolean clear) {
+ switch (shadBucket) {
+ case Cast:
+ renderGeometryList(shadowCast, rm, cam, clear);
+ break;
+ case Receive:
+ renderGeometryList(shadowRecv, rm, cam, clear);
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected shadow bucket: " + shadBucket);
+ }
+ }
+
+ public boolean isQueueEmpty(Bucket bucket) {
+ switch (bucket) {
+ case Gui:
+ return guiList.size() == 0;
+ case Opaque:
+ return opaqueList.size() == 0;
+ case Sky:
+ return skyList.size() == 0;
+ case Transparent:
+ return transparentList.size() == 0;
+ case Translucent:
+ return translucentList.size() == 0;
+ default:
+ throw new UnsupportedOperationException("Unsupported bucket type: " + bucket);
+ }
+ }
+
+ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam) {
+ renderQueue(bucket, rm, cam, true);
+ }
+
+ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean clear) {
+ switch (bucket) {
+ case Gui:
+ renderGeometryList(guiList, rm, cam, clear);
+ break;
+ case Opaque:
+ renderGeometryList(opaqueList, rm, cam, clear);
+ break;
+ case Sky:
+ renderGeometryList(skyList, rm, cam, clear);
+ break;
+ case Transparent:
+ renderGeometryList(transparentList, rm, cam, clear);
+ break;
+ case Translucent:
+ renderGeometryList(translucentList, rm, cam, clear);
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Unsupported bucket type: " + bucket);
+ }
+ }
+
+ public void clear() {
+ opaqueList.clear();
+ guiList.clear();
+ transparentList.clear();
+ translucentList.clear();
+ skyList.clear();
+ shadowCast.clear();
+ shadowRecv.clear();
+ }
+}
diff --git a/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java
new file mode 100644
index 0000000..0821eba
--- /dev/null
+++ b/engine/src/core/com/jme3/renderer/queue/TransparentComparator.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.renderer.queue;
+
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.scene.Geometry;
+
+public class TransparentComparator implements GeometryComparator {
+
+ private Camera cam;
+ private final Vector3f tempVec = new Vector3f();
+
+ public void setCamera(Camera cam){
+ this.cam = cam;
+ }
+
+ /**
+ * Calculates the distance from a spatial to the camera. Distance is a
+ * squared distance.
+ *
+ * @param spat
+ * Spatial to distancize.
+ * @return Distance from Spatial to camera.
+ */
+ private float distanceToCam2(Geometry spat){
+ if (spat == null)
+ return Float.NEGATIVE_INFINITY;
+
+ if (spat.queueDistance != Float.NEGATIVE_INFINITY)
+ return spat.queueDistance;
+
+ Vector3f camPosition = cam.getLocation();
+ Vector3f viewVector = cam.getDirection();
+ Vector3f spatPosition = null;
+
+ if (spat.getWorldBound() != null){
+ spatPosition = spat.getWorldBound().getCenter();
+ }else{
+ spatPosition = spat.getWorldTranslation();
+ }
+
+ spatPosition.subtract(camPosition, tempVec);
+ spat.queueDistance = tempVec.dot(tempVec);
+
+ float retval = Math.abs(tempVec.dot(viewVector)
+ / viewVector.dot(viewVector));
+ viewVector.mult(retval, tempVec);
+
+ spat.queueDistance = tempVec.length();
+
+ return spat.queueDistance;
+ }
+
+ private float distanceToCam(Geometry spat){
+ // NOTE: It is best to check the distance
+ // to the bound's closest edge vs. the bound's center here.
+ return spat.getWorldBound().distanceToEdge(cam.getLocation());
+ }
+
+ public int compare(Geometry o1, Geometry o2) {
+ float d1 = distanceToCam(o1);
+ float d2 = distanceToCam(o2);
+
+ if (d1 == d2)
+ return 0;
+ else if (d1 < d2)
+ return 1;
+ else
+ return -1;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/AssetLinkNode.java b/engine/src/core/com/jme3/scene/AssetLinkNode.java
new file mode 100644
index 0000000..e50680c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/AssetLinkNode.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.asset.AssetInfo;
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.ModelKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.binary.BinaryImporter;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.util.Map.Entry;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The AssetLinkNode does not store its children when exported to file.
+ * Instead, you can add a list of AssetKeys that will be loaded and attached
+ * when the AssetLinkNode is restored.
+ *
+ * @author normenhansen
+ */
+public class AssetLinkNode extends Node {
+
+ protected ArrayList<ModelKey> assetLoaderKeys = new ArrayList<ModelKey>();
+ protected Map<ModelKey, Spatial> assetChildren = new HashMap<ModelKey, Spatial>();
+
+ public AssetLinkNode() {
+ }
+
+ public AssetLinkNode(ModelKey key) {
+ this(key.getName(), key);
+ }
+
+ public AssetLinkNode(String name, ModelKey key) {
+ super(name);
+ assetLoaderKeys.add(key);
+ }
+
+ /**
+ * Add a "linked" child. These are loaded from the assetManager when the
+ * AssetLinkNode is loaded from a binary file.
+ * @param key
+ */
+ public void addLinkedChild(ModelKey key) {
+ if (assetLoaderKeys.contains(key)) {
+ return;
+ }
+ assetLoaderKeys.add(key);
+ }
+
+ public void removeLinkedChild(ModelKey key) {
+ assetLoaderKeys.remove(key);
+ }
+
+ public ArrayList<ModelKey> getAssetLoaderKeys() {
+ return assetLoaderKeys;
+ }
+
+ public void attachLinkedChild(AssetManager manager, ModelKey key) {
+ addLinkedChild(key);
+ Spatial child = manager.loadAsset(key);
+ assetChildren.put(key, child);
+ attachChild(child);
+ }
+
+ public void attachLinkedChild(Spatial spat, ModelKey key) {
+ addLinkedChild(key);
+ assetChildren.put(key, spat);
+ attachChild(spat);
+ }
+
+ public void detachLinkedChild(ModelKey key) {
+ Spatial spatial = assetChildren.get(key);
+ if (spatial != null) {
+ detachChild(spatial);
+ }
+ removeLinkedChild(key);
+ assetChildren.remove(key);
+ }
+
+ public void detachLinkedChild(Spatial child, ModelKey key) {
+ removeLinkedChild(key);
+ assetChildren.remove(key);
+ detachChild(child);
+ }
+
+ /**
+ * Loads the linked children AssetKeys from the AssetManager and attaches them to the Node<br>
+ * If they are already attached, they will be reloaded.
+ * @param manager
+ */
+ public void attachLinkedChildren(AssetManager manager) {
+ detachLinkedChildren();
+ for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+ ModelKey assetKey = it.next();
+ Spatial curChild = assetChildren.get(assetKey);
+ if (curChild != null) {
+ curChild.removeFromParent();
+ }
+ Spatial child = manager.loadAsset(assetKey);
+ attachChild(child);
+ assetChildren.put(assetKey, child);
+ }
+ }
+
+ public void detachLinkedChildren() {
+ Set<Entry<ModelKey, Spatial>> set = assetChildren.entrySet();
+ for (Iterator<Entry<ModelKey, Spatial>> it = set.iterator(); it.hasNext();) {
+ Entry<ModelKey, Spatial> entry = it.next();
+ entry.getValue().removeFromParent();
+ it.remove();
+ }
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ BinaryImporter importer = BinaryImporter.getInstance();
+ AssetManager loaderManager = e.getAssetManager();
+
+ assetLoaderKeys = (ArrayList<ModelKey>) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<ModelKey>());
+ for (Iterator<ModelKey> it = assetLoaderKeys.iterator(); it.hasNext();) {
+ ModelKey modelKey = it.next();
+ AssetInfo info = loaderManager.locateAsset(modelKey);
+ Spatial child = null;
+ if (info != null) {
+ child = (Spatial) importer.load(info);
+ }
+ if (child != null) {
+ child.parent = this;
+ children.add(child);
+ assetChildren.put(modelKey, child);
+ } else {
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}",
+ new Object[]{ modelKey, key });
+ }
+ }
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ SafeArrayList<Spatial> childs = children;
+ children = new SafeArrayList<Spatial>(Spatial.class);
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null);
+ children = childs;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/BatchNode.java b/engine/src/core/com/jme3/scene/BatchNode.java
new file mode 100644
index 0000000..bc1b2cb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/BatchNode.java
@@ -0,0 +1,653 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.FloatBuffer;
+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;
+
+/**
+ * BatchNode holds geometrie that are batched version of all geometries that are in its sub scenegraph.
+ * There is one geometry per different material in the sub tree.
+ * this geometries are directly attached to the node in the scene graph.
+ * usage is like any other node except you have to call the {@link #batch()} method once all geoms have been attached to the sub scene graph and theire material set
+ * (see todo more automagic for further enhancements)
+ * all the geometry that have been batched are set to {@link CullHint#Always} to not render them.
+ * the sub geometries can be transformed as usual their transforms are used to update the mesh of the geometryBatch.
+ * sub geoms can be removed but it may be slower than the normal spatial removing
+ * Sub geoms can be added after the batch() method has been called but won't be batched and will be rendered as normal geometries.
+ * To integrate them in the batch you have to call the batch() method again on the batchNode.
+ *
+ * TODO normal or tangents or both looks a bit weird
+ * TODO more automagic (batch when needed in the updateLigicalState)
+ * @author Nehon
+ */
+public class BatchNode extends Node implements Savable {
+
+ private static final Logger logger = Logger.getLogger(BatchNode.class.getName());
+ /**
+ * the map of geometry holding the batched meshes
+ */
+ protected Map<Material, Batch> batches = new HashMap<Material, Batch>();
+ /**
+ * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer
+ */
+ private float[] tmpFloat;
+ private float[] tmpFloatN;
+ private float[] tmpFloatT;
+ int maxVertCount = 0;
+ boolean useTangents = false;
+ boolean needsFullRebatch = true;
+
+ /**
+ * Construct a batchNode
+ */
+ public BatchNode() {
+ super();
+ }
+
+ public BatchNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public void updateGeometricState() {
+ if ((refreshFlags & RF_LIGHTLIST) != 0) {
+ updateWorldLightList();
+ }
+
+ if ((refreshFlags & RF_TRANSFORM) != 0) {
+ // combine with parent transforms- same for all spatial
+ // subclasses.
+ updateWorldTransforms();
+ }
+
+ if (!children.isEmpty()) {
+ // the important part- make sure child geometric state is refreshed
+ // first before updating own world bound. This saves
+ // a round-trip later on.
+ // NOTE 9/19/09
+ // Although it does save a round trip,
+
+ for (Spatial child : children.getArray()) {
+ child.updateGeometricState();
+ }
+
+ for (Batch batch : batches.values()) {
+ if (batch.needMeshUpdate) {
+ batch.geometry.getMesh().updateBound();
+ batch.geometry.updateWorldBound();
+ batch.needMeshUpdate = false;
+
+ }
+ }
+
+
+ }
+
+ if ((refreshFlags & RF_BOUND) != 0) {
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ protected Transform getTransforms(Geometry geom) {
+ return geom.getWorldTransform();
+ }
+
+ protected void updateSubBatch(Geometry bg) {
+ Batch batch = batches.get(bg.getMaterial());
+ if (batch != null) {
+ Mesh mesh = batch.geometry.getMesh();
+
+ VertexBuffer pvb = mesh.getBuffer(VertexBuffer.Type.Position);
+ FloatBuffer posBuf = (FloatBuffer) pvb.getData();
+ VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal);
+ FloatBuffer normBuf = (FloatBuffer) nvb.getData();
+
+ if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) {
+
+ VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent);
+ FloatBuffer tanBuf = (FloatBuffer) tvb.getData();
+ doTransformsTangents(posBuf, normBuf, tanBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+ tvb.updateData(tanBuf);
+ } else {
+ doTransforms(posBuf, normBuf, bg.startIndex, bg.startIndex + bg.getVertexCount(), bg.cachedOffsetMat);
+ }
+ pvb.updateData(posBuf);
+ nvb.updateData(normBuf);
+
+
+ batch.needMeshUpdate = true;
+ }
+ }
+
+ /**
+ * Batch this batchNode
+ * every geometry of the sub scene graph of this node will be batched into a single mesh that will be rendered in one call
+ */
+ public void batch() {
+ doBatch();
+ //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice
+ for (Batch batch : batches.values()) {
+ batch.geometry.setIgnoreTransform(true);
+ }
+ }
+
+ protected void doBatch() {
+ Map<Material, List<Geometry>> matMap = new HashMap<Material, List<Geometry>>();
+ maxVertCount = 0;
+ int nbGeoms = 0;
+
+ gatherGeomerties(matMap, this, needsFullRebatch);
+ if (needsFullRebatch) {
+ for (Batch batch : batches.values()) {
+ batch.geometry.removeFromParent();
+ }
+ batches.clear();
+ }
+
+ for (Material material : matMap.keySet()) {
+ Mesh m = new Mesh();
+ List<Geometry> list = matMap.get(material);
+ nbGeoms += list.size();
+ if (!needsFullRebatch) {
+ list.add(batches.get(material).geometry);
+ }
+ mergeGeometries(m, list);
+ m.setDynamic();
+ Batch batch = new Batch();
+
+ batch.geometry = new Geometry(name + "-batch" + batches.size());
+ batch.geometry.setMaterial(material);
+ this.attachChild(batch.geometry);
+
+
+ batch.geometry.setMesh(m);
+ batch.geometry.getMesh().updateCounts();
+ batch.geometry.getMesh().updateBound();
+ batches.put(material, batch);
+ }
+
+ logger.log(Level.INFO, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()});
+
+
+ //init temp float arrays
+ tmpFloat = new float[maxVertCount * 3];
+ tmpFloatN = new float[maxVertCount * 3];
+ if (useTangents) {
+ tmpFloatT = new float[maxVertCount * 4];
+ }
+ }
+
+ private void gatherGeomerties(Map<Material, List<Geometry>> map, Spatial n, boolean rebatch) {
+
+ if (n.getClass() == Geometry.class) {
+
+ if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) {
+ Geometry g = (Geometry) n;
+ if (!g.isBatched() || rebatch) {
+ if (g.getMaterial() == null) {
+ throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching");
+ }
+ List<Geometry> list = map.get(g.getMaterial());
+ if (list == null) {
+ list = new ArrayList<Geometry>();
+ map.put(g.getMaterial(), list);
+ }
+ g.setTransformRefresh();
+ list.add(g);
+ }
+ }
+
+ } else if (n instanceof Node) {
+ for (Spatial child : ((Node) n).getChildren()) {
+ if (child instanceof BatchNode) {
+ continue;
+ }
+ gatherGeomerties(map, child, rebatch);
+ }
+ }
+
+ }
+
+ private boolean isBatch(Spatial s) {
+ for (Batch batch : batches.values()) {
+ if (batch.geometry == s) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets the material to the all the batches of this BatchNode
+ * use setMaterial(Material material,int batchIndex) to set a material to a specific batch
+ *
+ * @param material the material to use for this geometry
+ */
+ @Override
+ public void setMaterial(Material material) {
+// for (Batch batch : batches.values()) {
+// batch.geometry.setMaterial(material);
+// }
+ throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching");
+ }
+
+ /**
+ * Returns the material that is used for the first batch of this BatchNode
+ *
+ * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
+ *
+ * @return the material that is used for the first batch of this BatchNode
+ *
+ * @see #setMaterial(com.jme3.material.Material)
+ */
+ public Material getMaterial() {
+ if (!batches.isEmpty()) {
+ Batch b = batches.get(batches.keySet().iterator().next());
+ return b.geometry.getMaterial();
+ }
+ return null;//material;
+ }
+
+// /**
+// * Sets the material to the a specific batch of this BatchNode
+// *
+// *
+// * @param material the material to use for this geometry
+// */
+// public void setMaterial(Material material,int batchIndex) {
+// if (!batches.isEmpty()) {
+//
+// }
+//
+// }
+//
+// /**
+// * Returns the material that is used for the first batch of this BatchNode
+// *
+// * use getMaterial(Material material,int batchIndex) to get a material from a specific batch
+// *
+// * @return the material that is used for the first batch of this BatchNode
+// *
+// * @see #setMaterial(com.jme3.material.Material)
+// */
+// public Material getMaterial(int batchIndex) {
+// if (!batches.isEmpty()) {
+// Batch b = batches.get(batches.keySet().iterator().next());
+// return b.geometry.getMaterial();
+// }
+// return null;//material;
+// }
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+//
+// if (material != null) {
+// oc.write(material.getAssetName(), "materialName", null);
+// }
+// oc.write(material, "material", null);
+
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+
+
+// material = null;
+// String matName = ic.readString("materialName", null);
+// if (matName != null) {
+// // Material name is set,
+// // Attempt to load material via J3M
+// try {
+// material = im.getAssetManager().loadMaterial(matName);
+// } catch (AssetNotFoundException ex) {
+// // Cannot find J3M file.
+// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.",
+// matName);
+// }
+// }
+// // If material is NULL, try to load it from the geometry
+// if (material == null) {
+// material = (Material) ic.readSavable("material", null);
+// }
+
+ }
+
+ /**
+ * Merges all geometries in the collection into
+ * the output mesh. Does not take into account materials.
+ *
+ * @param geometries
+ * @param outMesh
+ */
+ private void mergeGeometries(Mesh outMesh, List<Geometry> geometries) {
+ int[] compsForBuf = new int[VertexBuffer.Type.values().length];
+ VertexBuffer.Format[] formatForBuf = new VertexBuffer.Format[compsForBuf.length];
+
+ int totalVerts = 0;
+ int totalTris = 0;
+ int totalLodLevels = 0;
+
+ Mesh.Mode mode = null;
+ for (Geometry geom : geometries) {
+ totalVerts += geom.getVertexCount();
+ totalTris += geom.getTriangleCount();
+ totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels());
+ if (maxVertCount < geom.getVertexCount()) {
+ maxVertCount = geom.getVertexCount();
+ }
+ Mesh.Mode listMode;
+ int components;
+ switch (geom.getMesh().getMode()) {
+ case Points:
+ listMode = Mesh.Mode.Points;
+ components = 1;
+ break;
+ case LineLoop:
+ case LineStrip:
+ case Lines:
+ listMode = Mesh.Mode.Lines;
+ components = 2;
+ break;
+ case TriangleFan:
+ case TriangleStrip:
+ case Triangles:
+ listMode = Mesh.Mode.Triangles;
+ components = 3;
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+
+ for (Entry<VertexBuffer> entry : geom.getMesh().getBuffers()) {
+ compsForBuf[entry.getKey()] = entry.getValue().getNumComponents();
+ formatForBuf[entry.getKey()] = entry.getValue().getFormat();
+ }
+
+ if (mode != null && mode != listMode) {
+ throw new UnsupportedOperationException("Cannot combine different"
+ + " primitive types: " + mode + " != " + listMode);
+ }
+ mode = listMode;
+ compsForBuf[VertexBuffer.Type.Index.ordinal()] = components;
+ }
+
+ outMesh.setMode(mode);
+ if (totalVerts >= 65536) {
+ // make sure we create an UnsignedInt buffer so
+ // we can fit all of the meshes
+ formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt;
+ } else {
+ formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort;
+ }
+
+ // generate output buffers based on retrieved info
+ for (int i = 0; i < compsForBuf.length; i++) {
+ if (compsForBuf[i] == 0) {
+ continue;
+ }
+
+ Buffer data;
+ if (i == VertexBuffer.Type.Index.ordinal()) {
+ data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalTris);
+ } else {
+ data = VertexBuffer.createBuffer(formatForBuf[i], compsForBuf[i], totalVerts);
+ }
+
+ VertexBuffer vb = new VertexBuffer(VertexBuffer.Type.values()[i]);
+ vb.setupData(VertexBuffer.Usage.Dynamic, compsForBuf[i], formatForBuf[i], data);
+ outMesh.setBuffer(vb);
+ }
+
+ int globalVertIndex = 0;
+ int globalTriIndex = 0;
+
+ for (Geometry geom : geometries) {
+ Mesh inMesh = geom.getMesh();
+ geom.batch(this, globalVertIndex);
+
+ int geomVertCount = inMesh.getVertexCount();
+ int geomTriCount = inMesh.getTriangleCount();
+
+ for (int bufType = 0; bufType < compsForBuf.length; bufType++) {
+ VertexBuffer inBuf = inMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+ VertexBuffer outBuf = outMesh.getBuffer(VertexBuffer.Type.values()[bufType]);
+
+ if (outBuf == null) {
+ continue;
+ }
+
+ if (VertexBuffer.Type.Index.ordinal() == bufType) {
+ int components = compsForBuf[bufType];
+
+ IndexBuffer inIdx = inMesh.getIndicesAsList();
+ IndexBuffer outIdx = outMesh.getIndexBuffer();
+
+ for (int tri = 0; tri < geomTriCount; tri++) {
+ for (int comp = 0; comp < components; comp++) {
+ int idx = inIdx.get(tri * components + comp) + globalVertIndex;
+ outIdx.put((globalTriIndex + tri) * components + comp, idx);
+ }
+ }
+ } else if (VertexBuffer.Type.Position.ordinal() == bufType) {
+ FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+ FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+ doCopyBuffer(inPos, globalVertIndex, outPos, 3);
+ } else if (VertexBuffer.Type.Normal.ordinal() == bufType || VertexBuffer.Type.Tangent.ordinal() == bufType) {
+ FloatBuffer inPos = (FloatBuffer) inBuf.getData();
+ FloatBuffer outPos = (FloatBuffer) outBuf.getData();
+ doCopyBuffer(inPos, globalVertIndex, outPos, compsForBuf[bufType]);
+ if (VertexBuffer.Type.Tangent.ordinal() == bufType) {
+ useTangents = true;
+ }
+ } else {
+ inBuf.copyElements(0, outBuf, globalVertIndex, geomVertCount);
+// for (int vert = 0; vert < geomVertCount; vert++) {
+// int curGlobalVertIndex = globalVertIndex + vert;
+// inBuf.copyElement(vert, outBuf, curGlobalVertIndex);
+// }
+ }
+ }
+
+ globalVertIndex += geomVertCount;
+ globalTriIndex += geomTriCount;
+ }
+ }
+
+ private void doTransforms(FloatBuffer bufPos, FloatBuffer bufNorm, int start, int end, Matrix4f transform) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+ Vector3f norm = vars.vect2;
+
+ int length = (end - start) * 3;
+
+ // offset is given in element units
+ // convert to be in component units
+ int offset = start * 3;
+ bufPos.position(offset);
+ bufNorm.position(offset);
+ bufPos.get(tmpFloat, 0, length);
+ bufNorm.get(tmpFloatN, 0, length);
+ int index = 0;
+ while (index < length) {
+ pos.x = tmpFloat[index];
+ norm.x = tmpFloatN[index++];
+ pos.y = tmpFloat[index];
+ norm.y = tmpFloatN[index++];
+ pos.z = tmpFloat[index];
+ norm.z = tmpFloatN[index];
+
+ transform.mult(pos, pos);
+ transform.multNormal(norm, norm);
+
+ index -= 2;
+ tmpFloat[index] = pos.x;
+ tmpFloatN[index++] = norm.x;
+ tmpFloat[index] = pos.y;
+ tmpFloatN[index++] = norm.y;
+ tmpFloat[index] = pos.z;
+ tmpFloatN[index++] = norm.z;
+
+ }
+ vars.release();
+ bufPos.position(offset);
+ //using bulk put as it's faster
+ bufPos.put(tmpFloat, 0, length);
+ bufNorm.position(offset);
+ //using bulk put as it's faster
+ bufNorm.put(tmpFloatN, 0, length);
+ }
+
+ private void doTransformsTangents(FloatBuffer bufPos, FloatBuffer bufNorm, FloatBuffer bufTangents, int start, int end, Matrix4f transform) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+ Vector3f norm = vars.vect2;
+ Vector3f tan = vars.vect3;
+
+ int length = (end - start) * 3;
+ int tanLength = (end - start) * 4;
+
+ // offset is given in element units
+ // convert to be in component units
+ int offset = start * 3;
+ int tanOffset = start * 4;
+
+ bufPos.position(offset);
+ bufNorm.position(offset);
+ bufTangents.position(tanOffset);
+ bufPos.get(tmpFloat, 0, length);
+ bufNorm.get(tmpFloatN, 0, length);
+ bufTangents.get(tmpFloatT, 0, tanLength);
+
+ int index = 0;
+ int tanIndex = 0;
+ while (index < length) {
+ pos.x = tmpFloat[index];
+ norm.x = tmpFloatN[index++];
+ pos.y = tmpFloat[index];
+ norm.y = tmpFloatN[index++];
+ pos.z = tmpFloat[index];
+ norm.z = tmpFloatN[index];
+
+ tan.x = tmpFloatT[tanIndex++];
+ tan.y = tmpFloatT[tanIndex++];
+ tan.z = tmpFloatT[tanIndex++];
+
+ transform.mult(pos, pos);
+ transform.multNormal(norm, norm);
+ transform.multNormal(tan, tan);
+
+ index -= 2;
+ tanIndex -= 3;
+
+ tmpFloat[index] = pos.x;
+ tmpFloatN[index++] = norm.x;
+ tmpFloat[index] = pos.y;
+ tmpFloatN[index++] = norm.y;
+ tmpFloat[index] = pos.z;
+ tmpFloatN[index++] = norm.z;
+
+ tmpFloatT[tanIndex++] = tan.x;
+ tmpFloatT[tanIndex++] = tan.y;
+ tmpFloatT[tanIndex++] = tan.z;
+
+ //Skipping 4th element of tangent buffer (handedness)
+ tanIndex++;
+
+ }
+ vars.release();
+ bufPos.position(offset);
+ //using bulk put as it's faster
+ bufPos.put(tmpFloat, 0, length);
+ bufNorm.position(offset);
+ //using bulk put as it's faster
+ bufNorm.put(tmpFloatN, 0, length);
+ bufTangents.position(tanOffset);
+ //using bulk put as it's faster
+ bufTangents.put(tmpFloatT, 0, tanLength);
+ }
+
+ private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) {
+ TempVars vars = TempVars.get();
+ Vector3f pos = vars.vect1;
+
+ // offset is given in element units
+ // convert to be in component units
+ offset *= componentSize;
+
+ for (int i = 0; i < inBuf.capacity() / componentSize; i++) {
+ pos.x = inBuf.get(i * componentSize + 0);
+ pos.y = inBuf.get(i * componentSize + 1);
+ pos.z = inBuf.get(i * componentSize + 2);
+
+ outBuf.put(offset + i * componentSize + 0, pos.x);
+ outBuf.put(offset + i * componentSize + 1, pos.y);
+ outBuf.put(offset + i * componentSize + 2, pos.z);
+ }
+ vars.release();
+ }
+
+ protected class Batch {
+
+ Geometry geometry;
+ boolean needMeshUpdate = false;
+ }
+
+ protected void setNeedsFullRebatch(boolean needsFullRebatch) {
+ this.needsFullRebatch = needsFullRebatch;
+ }
+
+ public int getOffsetIndex(Geometry batchedGeometry){
+ return batchedGeometry.startIndex;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/CameraNode.java b/engine/src/core/com/jme3/scene/CameraNode.java
new file mode 100644
index 0000000..c6bb48b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CameraNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.renderer.Camera;
+import com.jme3.scene.control.CameraControl;
+import com.jme3.scene.control.CameraControl.ControlDirection;
+
+/**
+ * <code>CameraNode</code> simply uses {@link CameraControl} to implement
+ * linking of camera and node data.
+ *
+ * @author Tim8Dev
+ */
+public class CameraNode extends Node {
+
+ private CameraControl camControl;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public CameraNode() {
+ }
+
+ public CameraNode(String name, Camera camera) {
+ this(name, new CameraControl(camera));
+ }
+
+ public CameraNode(String name, CameraControl control) {
+ super(name);
+ addControl(control);
+ camControl = control;
+ }
+
+ public void setEnabled(boolean enabled) {
+ camControl.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return camControl.isEnabled();
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ camControl.setControlDir(controlDir);
+ }
+
+ public void setCamera(Camera camera) {
+ camControl.setCamera(camera);
+ }
+
+ public ControlDirection getControlDir() {
+ return camControl.getControlDir();
+ }
+
+ public Camera getCamera() {
+ return camControl.getCamera();
+ }
+
+// @Override
+// public void lookAt(Vector3f position, Vector3f upVector) {
+// this.lookAt(position, upVector);
+// camControl.getCamera().lookAt(position, upVector);
+// }
+}
diff --git a/engine/src/core/com/jme3/scene/CollisionData.java b/engine/src/core/com/jme3/scene/CollisionData.java
new file mode 100644
index 0000000..914bf24
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/CollisionData.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.Savable;
+import com.jme3.math.Matrix4f;
+
+/**
+ * <code>CollisionData</code> is an interface that can be used to
+ * do triangle-accurate collision with bounding volumes and rays.
+ *
+ * @author Kirill Vainer
+ */
+public interface CollisionData extends Savable, Cloneable {
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results);
+}
diff --git a/engine/src/core/com/jme3/scene/Geometry.java b/engine/src/core/com/jme3/scene/Geometry.java
new file mode 100644
index 0000000..b02196d
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Geometry.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.material.Material;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>Geometry</code> defines a leaf node of the scene graph. The leaf node
+ * contains the geometric data for rendering objects. It manages all rendering
+ * information such as a {@link Material} object to define how the surface
+ * should be shaded and the {@link Mesh} data to contain the actual geometry.
+ *
+ * @author Kirill Vainer
+ */
+public class Geometry extends Spatial {
+
+ // Version #1: removed shared meshes.
+ // models loaded with shared mesh will be automatically fixed.
+ public static final int SAVABLE_VERSION = 1;
+
+ private static final Logger logger = Logger.getLogger(Geometry.class.getName());
+ protected Mesh mesh;
+ protected transient int lodLevel = 0;
+ protected Material material;
+ /**
+ * When true, the geometry's transform will not be applied.
+ */
+ protected boolean ignoreTransform = false;
+ protected transient Matrix4f cachedWorldMat = new Matrix4f();
+ /**
+ * used when geometry is batched
+ */
+ protected BatchNode batchNode = null;
+ /**
+ * the start index of this geom's mesh in the batchNode mesh
+ */
+ protected int startIndex;
+ /**
+ * the previous transforms of the geometry used to compute world transforms
+ */
+ protected Transform prevBatchTransforms = null;
+ /**
+ * the cached offset matrix used when the geometry is batched
+ */
+ protected Matrix4f cachedOffsetMat = null;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Geometry() {
+ }
+
+ /**
+ * Create a geometry node without any mesh data.
+ * Both the mesh and the material are null, the geometry
+ * cannot be rendered until those are set.
+ *
+ * @param name The name of this geometry
+ */
+ public Geometry(String name) {
+ super(name);
+ }
+
+ /**
+ * Create a geometry node with mesh data.
+ * The material of the geometry is null, it cannot
+ * be rendered until it is set.
+ *
+ * @param name The name of this geometry
+ * @param mesh The mesh data for this geometry
+ */
+ public Geometry(String name, Mesh mesh) {
+ this(name);
+ if (mesh == null) {
+ throw new NullPointerException();
+ }
+
+ this.mesh = mesh;
+ }
+
+ /**
+ * @return If ignoreTransform mode is set.
+ *
+ * @see Geometry#setIgnoreTransform(boolean)
+ */
+ public boolean isIgnoreTransform() {
+ return ignoreTransform;
+ }
+
+ /**
+ * @param ignoreTransform If true, the geometry's transform will not be applied.
+ */
+ public void setIgnoreTransform(boolean ignoreTransform) {
+ this.ignoreTransform = ignoreTransform;
+ }
+
+ /**
+ * Sets the LOD level to use when rendering the mesh of this geometry.
+ * Level 0 indicates that the default index buffer should be used,
+ * levels [1, LodLevels + 1] represent the levels set on the mesh
+ * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
+ *
+ * @param lod The lod level to set
+ */
+ @Override
+ public void setLodLevel(int lod) {
+ if (mesh.getNumLodLevels() == 0) {
+ throw new IllegalStateException("LOD levels are not set on this mesh");
+ }
+
+ if (lod < 0 || lod >= mesh.getNumLodLevels()) {
+ throw new IllegalArgumentException("LOD level is out of range: " + lod);
+ }
+
+ lodLevel = lod;
+ }
+
+ /**
+ * Returns the LOD level set with {@link #setLodLevel(int) }.
+ *
+ * @return the LOD level set
+ */
+ public int getLodLevel() {
+ return lodLevel;
+ }
+
+ /**
+ * Returns this geometry's mesh vertex count.
+ *
+ * @return this geometry's mesh vertex count.
+ *
+ * @see Mesh#getVertexCount()
+ */
+ public int getVertexCount() {
+ return mesh.getVertexCount();
+ }
+
+ /**
+ * Returns this geometry's mesh triangle count.
+ *
+ * @return this geometry's mesh triangle count.
+ *
+ * @see Mesh#getTriangleCount()
+ */
+ public int getTriangleCount() {
+ return mesh.getTriangleCount();
+ }
+
+ /**
+ * Sets the mesh to use for this geometry when rendering.
+ *
+ * @param mesh the mesh to use for this geometry
+ *
+ * @throws IllegalArgumentException If mesh is null
+ */
+ public void setMesh(Mesh mesh) {
+ if (mesh == null) {
+ throw new IllegalArgumentException();
+ }
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the mesh of a batched geometry");
+ }
+
+ this.mesh = mesh;
+ setBoundRefresh();
+ }
+
+ /**
+ * Returns the mseh to use for this geometry
+ *
+ * @return the mseh to use for this geometry
+ *
+ * @see #setMesh(com.jme3.scene.Mesh)
+ */
+ public Mesh getMesh() {
+ return mesh;
+ }
+
+ /**
+ * Sets the material to use for this geometry.
+ *
+ * @param material the material to use for this geometry
+ */
+ @Override
+ public void setMaterial(Material material) {
+ if (isBatched()) {
+ throw new UnsupportedOperationException("Cannot set the material of a batched geometry, change the material of the parent BatchNode.");
+ }
+ this.material = material;
+ }
+
+ /**
+ * Returns the material that is used for this geometry.
+ *
+ * @return the material that is used for this geometry
+ *
+ * @see #setMaterial(com.jme3.material.Material)
+ */
+ public Material getMaterial() {
+ return material;
+ }
+
+ /**
+ * @return The bounding volume of the mesh, in model space.
+ */
+ public BoundingVolume getModelBound() {
+ return mesh.getBound();
+ }
+
+ /**
+ * Updates the bounding volume of the mesh. Should be called when the
+ * mesh has been modified.
+ */
+ public void updateModelBound() {
+ mesh.updateBound();
+ setBoundRefresh();
+ }
+
+ /**
+ * <code>updateWorldBound</code> updates the bounding volume that contains
+ * this geometry. The location of the geometry is based on the location of
+ * all this node's parents.
+ *
+ * @see Spatial#updateWorldBound()
+ */
+ @Override
+ protected void updateWorldBound() {
+ super.updateWorldBound();
+ if (mesh == null) {
+ throw new NullPointerException("Geometry: " + getName() + " has null mesh");
+ }
+
+ if (mesh.getBound() != null) {
+ if (ignoreTransform) {
+ // we do not transform the model bound by the world transform,
+ // just use the model bound as-is
+ worldBound = mesh.getBound().clone(worldBound);
+ } else {
+ worldBound = mesh.getBound().transform(worldTransform, worldBound);
+ }
+ }
+ }
+
+ @Override
+ protected void updateWorldTransforms() {
+
+ super.updateWorldTransforms();
+ computeWorldMatrix();
+
+ if (isBatched()) {
+ computeOffsetTransform();
+ batchNode.updateSubBatch(this);
+ prevBatchTransforms.set(batchNode.getTransforms(this));
+
+ }
+ // geometry requires lights to be sorted
+ worldLights.sort(true);
+ }
+
+ /**
+ * Batch this geometry, should only be called by the BatchNode.
+ * @param node the batchNode
+ * @param startIndex the starting index of this geometry in the batched mesh
+ */
+ protected void batch(BatchNode node, int startIndex) {
+ this.batchNode = node;
+ this.startIndex = startIndex;
+ prevBatchTransforms = new Transform();
+ cachedOffsetMat = new Matrix4f();
+ setCullHint(CullHint.Always);
+ }
+
+ /**
+ * unBatch this geometry.
+ */
+ protected void unBatch() {
+ this.startIndex = 0;
+ prevBatchTransforms = null;
+ cachedOffsetMat = null;
+ //once the geometry is removed from the screnegraph the batchNode needs to be rebatched.
+ this.batchNode.setNeedsFullRebatch(true);
+ this.batchNode = null;
+ setCullHint(CullHint.Dynamic);
+ }
+
+ @Override
+ public boolean removeFromParent() {
+ boolean removed = super.removeFromParent();
+ //if the geometry is batched we also have to unbatch it
+ if (isBatched()) {
+ unBatch();
+ }
+ return removed;
+ }
+
+ /**
+ * Recomputes the cached offset matrix used when the geometry is batched *
+ */
+ public void computeOffsetTransform() {
+ TempVars vars = TempVars.get();
+ Matrix4f tmpMat = vars.tempMat42;
+
+ // Compute the cached world matrix
+ cachedOffsetMat.loadIdentity();
+ cachedOffsetMat.setRotationQuaternion(prevBatchTransforms.getRotation());
+ cachedOffsetMat.setTranslation(prevBatchTransforms.getTranslation());
+
+
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(prevBatchTransforms.getScale());
+ cachedOffsetMat.multLocal(scaleMat);
+ cachedOffsetMat.invertLocal();
+
+ tmpMat.loadIdentity();
+ tmpMat.setRotationQuaternion(batchNode.getTransforms(this).getRotation());
+ tmpMat.setTranslation(batchNode.getTransforms(this).getTranslation());
+ scaleMat.loadIdentity();
+ scaleMat.scale(batchNode.getTransforms(this).getScale());
+ tmpMat.multLocal(scaleMat);
+
+ tmpMat.mult(cachedOffsetMat, cachedOffsetMat);
+
+ vars.release();
+ }
+
+ /**
+ * Indicate that the transform of this spatial has changed and that
+ * a refresh is required.
+ */
+ @Override
+ protected void setTransformRefresh() {
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ }
+
+ /**
+ * Recomputes the matrix returned by {@link Geometry#getWorldMatrix() }.
+ * This will require a localized transform update for this geometry.
+ */
+ public void computeWorldMatrix() {
+ // Force a local update of the geometry's transform
+ checkDoTransformUpdate();
+
+ // Compute the cached world matrix
+ cachedWorldMat.loadIdentity();
+ cachedWorldMat.setRotationQuaternion(worldTransform.getRotation());
+ cachedWorldMat.setTranslation(worldTransform.getTranslation());
+
+ TempVars vars = TempVars.get();
+ Matrix4f scaleMat = vars.tempMat4;
+ scaleMat.loadIdentity();
+ scaleMat.scale(worldTransform.getScale());
+ cachedWorldMat.multLocal(scaleMat);
+ vars.release();
+ }
+
+ /**
+ * A {@link Matrix4f matrix} that transforms the {@link Geometry#getMesh() mesh}
+ * from model space to world space. This matrix is computed based on the
+ * {@link Geometry#getWorldTransform() world transform} of this geometry.
+ * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() }
+ * before using this method.
+ *
+ * @return Matrix to transform from local space to world space
+ */
+ public Matrix4f getWorldMatrix() {
+ return cachedWorldMat;
+ }
+
+ /**
+ * Sets the model bound to use for this geometry.
+ * This alters the bound used on the mesh as well via
+ * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and
+ * forces the world bounding volume to be recomputed.
+ *
+ * @param modelBound The model bound to set
+ */
+ @Override
+ public void setModelBound(BoundingVolume modelBound) {
+ this.worldBound = null;
+ mesh.setBound(modelBound);
+ setBoundRefresh();
+
+ // NOTE: Calling updateModelBound() would cause the mesh
+ // to recompute the bound based on the geometry thus making
+ // this call useless!
+ //updateModelBound();
+ }
+
+ public int collideWith(Collidable other, CollisionResults results) {
+ // Force bound to update
+ checkDoBoundUpdate();
+ // Update transform, and compute cached world matrix
+ computeWorldMatrix();
+
+ assert (refreshFlags & (RF_BOUND | RF_TRANSFORM)) == 0;
+
+ if (mesh != null) {
+ // NOTE: BIHTree in mesh already checks collision with the
+ // mesh's bound
+ int prevSize = results.size();
+ int added = mesh.collideWith(other, cachedWorldMat, worldBound, results);
+ int newSize = results.size();
+ for (int i = prevSize; i < newSize; i++) {
+ results.getCollisionDirect(i).setGeometry(this);
+ }
+ return added;
+ }
+ return 0;
+ }
+
+ @Override
+ public void depthFirstTraversal(SceneGraphVisitor visitor) {
+ visitor.visit(this);
+ }
+
+ @Override
+ protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+ }
+
+ public boolean isBatched() {
+ return batchNode != null;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone(boolean cloneMaterial) {
+ Geometry geomClone = (Geometry) super.clone(cloneMaterial);
+ geomClone.cachedWorldMat = cachedWorldMat.clone();
+ if (material != null) {
+ if (cloneMaterial) {
+ geomClone.material = material.clone();
+ } else {
+ geomClone.material = material;
+ }
+ }
+
+ if (mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) {
+ geomClone.mesh = mesh.cloneForAnim();
+ }
+
+ return geomClone;
+ }
+
+ /**
+ * This version of clone is a shallow clone, in other words, the
+ * same mesh is referenced as the original geometry.
+ * Exception: if the mesh is marked as being a software
+ * animated mesh, (bind pose is set) then the positions
+ * and normals are deep copied.
+ */
+ @Override
+ public Geometry clone() {
+ return clone(true);
+ }
+
+ /**
+ * Creates a deep clone of the geometry,
+ * this creates an identical copy of the mesh
+ * with the vertexbuffer data duplicated.
+ */
+ @Override
+ public Spatial deepClone() {
+ Geometry geomClone = clone(true);
+ geomClone.mesh = mesh.deepClone();
+ return geomClone;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(mesh, "mesh", null);
+ if (material != null) {
+ oc.write(material.getAssetName(), "materialName", null);
+ }
+ oc.write(material, "material", null);
+ oc.write(ignoreTransform, "ignoreTransform", false);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ mesh = (Mesh) ic.readSavable("mesh", null);
+
+ material = null;
+ String matName = ic.readString("materialName", null);
+ if (matName != null) {
+ // Material name is set,
+ // Attempt to load material via J3M
+ try {
+ material = im.getAssetManager().loadMaterial(matName);
+ } catch (AssetNotFoundException ex) {
+ // Cannot find J3M file.
+ logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key});
+ }
+ }
+ // If material is NULL, try to load it from the geometry
+ if (material == null) {
+ material = (Material) ic.readSavable("material", null);
+ }
+ ignoreTransform = ic.readBoolean("ignoreTransform", false);
+
+ if (ic.getSavableVersion(Geometry.class) == 0){
+ // Fix shared mesh (if set)
+ Mesh sharedMesh = getUserData(UserData.JME_SHAREDMESH);
+ if (sharedMesh != null){
+ getMesh().extractVertexData(sharedMesh);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/LightNode.java b/engine/src/core/com/jme3/scene/LightNode.java
new file mode 100644
index 0000000..5e6dc76
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/LightNode.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.light.Light;
+import com.jme3.scene.control.LightControl;
+import com.jme3.scene.control.LightControl.ControlDirection;
+
+/**
+ * <code>LightNode</code> is used to link together a {@link Light} object
+ * with a {@link Node} object.
+ *
+ * @author Tim8Dev
+ */
+public class LightNode extends Node {
+
+ private LightControl lightControl;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public LightNode() {
+ }
+
+ public LightNode(String name, Light light) {
+ this(name, new LightControl(light));
+ }
+
+ public LightNode(String name, LightControl control) {
+ super(name);
+ addControl(control);
+ lightControl = control;
+ }
+
+ /**
+ * Enable or disable the <code>LightNode</code> functionality.
+ *
+ * @param enabled If false, the functionality of LightNode will
+ * be disabled.
+ */
+ public void setEnabled(boolean enabled) {
+ lightControl.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return lightControl.isEnabled();
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ lightControl.setControlDir(controlDir);
+ }
+
+ public void setLight(Light light) {
+ lightControl.setLight(light);
+ }
+
+ public ControlDirection getControlDir() {
+ return lightControl.getControlDir();
+ }
+
+ public Light getLight() {
+ return lightControl.getLight();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/Mesh.java b/engine/src/core/com/jme3/scene/Mesh.java
new file mode 100644
index 0000000..6c587f2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Mesh.java
@@ -0,0 +1,1315 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.bih.BIHTree;
+import com.jme3.export.*;
+import com.jme3.material.RenderState;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Triangle;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.*;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * <code>Mesh</code> is used to store rendering data.
+ * <p>
+ * All visible elements in a scene are represented by meshes.
+ * Meshes may contain three types of geometric primitives:
+ * <ul>
+ * <li>Points - Every vertex represents a single point in space,
+ * the size of each point is specified via {@link Mesh#setPointSize(float) }.
+ * Points can also be used for {@link RenderState#setPointSprite(boolean) point
+ * sprite} mode.</li>
+ * <li>Lines - 2 vertices represent a line segment, with the width specified
+ * via {@link Mesh#setLineWidth(float) }.</li>
+ * <li>Triangles - 3 vertices represent a solid triangle primitive. </li>
+ * </ul>
+ *
+ * @author Kirill Vainer
+ */
+public class Mesh implements Savable, Cloneable {
+
+ /**
+ * The mode of the Mesh specifies both the type of primitive represented
+ * by the mesh and how the data should be interpreted.
+ */
+ public enum Mode {
+ /**
+ * A primitive is a single point in space. The size of the points
+ * can be specified with {@link Mesh#setPointSize(float) }.
+ */
+ Points(true),
+
+ /**
+ * A primitive is a line segment. Every two vertices specify
+ * a single line. {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
+ Lines(true),
+
+ /**
+ * A primitive is a line segment. The first two vertices specify
+ * a single line, while subsequent vertices are combined with the
+ * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can
+ * be used to set the width of the lines.
+ */
+ LineStrip(false),
+
+ /**
+ * Identical to {@link #LineStrip} except that at the end
+ * the last vertex is connected with the first to form a line.
+ * {@link Mesh#setLineWidth(float) } can be used
+ * to set the width of the lines.
+ */
+ LineLoop(false),
+
+ /**
+ * A primitive is a triangle. Each 3 vertices specify a single
+ * triangle.
+ */
+ Triangles(true),
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, while subsequent vertices are combined with
+ * the previous two to form a triangle.
+ */
+ TriangleStrip(false),
+
+ /**
+ * Similar to {@link #Triangles}, the first 3 vertices
+ * specify a triangle, each 2 subsequent vertices are combined
+ * with the very first vertex to make a triangle.
+ */
+ TriangleFan(false),
+
+ /**
+ * A combination of various triangle modes. It is best to avoid
+ * using this mode as it may not be supported by all renderers.
+ * The {@link Mesh#setModeStart(int[]) mode start points} and
+ * {@link Mesh#setElementLengths(int[]) element lengths} must
+ * be specified for this mode.
+ */
+ Hybrid(false);
+
+ private boolean listMode = false;
+
+ private Mode(boolean listMode){
+ this.listMode = listMode;
+ }
+
+ /**
+ * Returns true if the specified mode is a list mode (meaning
+ * ,it specifies the indices as a linear list and not some special
+ * format).
+ * Will return true for the types {@link #Points}, {@link #Lines} and
+ * {@link #Triangles}.
+ *
+ * @return true if the mode is a list type mode
+ */
+ public boolean isListMode(){
+ return listMode;
+ }
+ }
+
+ /**
+ * The bounding volume that contains the mesh entirely.
+ * By default a BoundingBox (AABB).
+ */
+ private BoundingVolume meshBound = new BoundingBox();
+
+ private CollisionData collisionTree = null;
+
+ private SafeArrayList<VertexBuffer> buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ private IntMap<VertexBuffer> buffers = new IntMap<VertexBuffer>();
+ private VertexBuffer[] lodLevels;
+ private float pointSize = 1;
+ private float lineWidth = 1;
+
+ private transient int vertexArrayID = -1;
+
+ private int vertCount = -1;
+ private int elementCount = -1;
+ private int maxNumWeights = -1; // only if using skeletal animation
+
+ private int[] elementLengths;
+ private int[] modeStart;
+
+ private Mode mode = Mode.Triangles;
+
+ /**
+ * Creates a new mesh with no {@link VertexBuffer vertex buffers}.
+ */
+ public Mesh(){
+ }
+
+ /**
+ * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex
+ * buffers} are shared between this and the clone mesh, the rest
+ * of the data is cloned.
+ *
+ * @return A shallow clone of the mesh
+ */
+ @Override
+ public Mesh clone() {
+ try {
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound.clone();
+ clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.buffers = buffers.clone();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class,buffersList);
+ clone.vertexArrayID = -1;
+ if (elementLengths != null) {
+ clone.elementLengths = elementLengths.clone();
+ }
+ if (modeStart != null) {
+ clone.modeStart = modeStart.clone();
+ }
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Creates a deep clone of this mesh.
+ * The {@link VertexBuffer vertex buffers} and the data inside them
+ * is cloned.
+ *
+ * @return a deep clone of this mesh.
+ */
+ public Mesh deepClone(){
+ try{
+ Mesh clone = (Mesh) super.clone();
+ clone.meshBound = meshBound != null ? meshBound.clone() : null;
+
+ // TODO: Collision tree cloning
+ //clone.collisionTree = collisionTree != null ? collisionTree : null;
+ clone.collisionTree = null; // it will get re-generated in any case
+
+ clone.buffers = new IntMap<VertexBuffer>();
+ clone.buffersList = new SafeArrayList<VertexBuffer>(VertexBuffer.class);
+ for (Entry<VertexBuffer> ent : buffers){
+ VertexBuffer bufClone = ent.getValue().clone();
+ clone.buffers.put(ent.getKey(), bufClone);
+ clone.buffersList.add(bufClone);
+ }
+
+ clone.vertexArrayID = -1;
+ clone.vertCount = -1;
+ clone.elementCount = -1;
+
+ // although this could change
+ // if the bone weight/index buffers are modified
+ clone.maxNumWeights = maxNumWeights;
+
+ clone.elementLengths = elementLengths != null ? elementLengths.clone() : null;
+ clone.modeStart = modeStart != null ? modeStart.clone() : null;
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Clone the mesh for animation use.
+ * This creates a shallow clone of the mesh, sharing most
+ * of the {@link VertexBuffer vertex buffer} data, however the
+ * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers
+ * are deeply cloned.
+ *
+ * @return A clone of the mesh for animation use.
+ */
+ public Mesh cloneForAnim(){
+ Mesh clone = clone();
+ if (getBuffer(Type.BindPosePosition) != null){
+ VertexBuffer oldPos = getBuffer(Type.Position);
+
+ // NOTE: creates deep clone
+ VertexBuffer newPos = oldPos.clone();
+ clone.clearBuffer(Type.Position);
+ clone.setBuffer(newPos);
+
+ if (getBuffer(Type.BindPoseNormal) != null){
+ VertexBuffer oldNorm = getBuffer(Type.Normal);
+ VertexBuffer newNorm = oldNorm.clone();
+ clone.clearBuffer(Type.Normal);
+ clone.setBuffer(newNorm);
+
+ if (getBuffer(Type.BindPoseTangent) != null){
+ VertexBuffer oldTang = getBuffer(Type.Tangent);
+ VertexBuffer newTang = oldTang.clone();
+ clone.clearBuffer(Type.Tangent);
+ clone.setBuffer(newTang);
+ }
+ }
+ }
+ return clone;
+ }
+
+ /**
+ * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal},
+ * and {@link Type#BindPoseTangent}
+ * buffers for this mesh by duplicating them based on the position and normal
+ * buffers already set on the mesh.
+ * This method does nothing if the mesh has no bone weight or index
+ * buffers.
+ *
+ * @param forSoftwareAnim Should be true if the bind pose is to be generated.
+ */
+ public void generateBindPose(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
+ VertexBuffer pos = getBuffer(Type.Position);
+ if (pos == null || getBuffer(Type.BoneIndex) == null) {
+ // ignore, this mesh doesn't have positional data
+ // or it doesn't have bone-vertex assignments, so its not animated
+ return;
+ }
+
+ VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
+ bindPos.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(pos.getData()));
+ setBuffer(bindPos);
+
+ // XXX: note that this method also sets stream mode
+ // so that animation is faster. this is not needed for hardware skinning
+ pos.setUsage(Usage.Stream);
+
+ VertexBuffer norm = getBuffer(Type.Normal);
+ if (norm != null) {
+ VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
+ bindNorm.setupData(Usage.CpuOnly,
+ 3,
+ Format.Float,
+ BufferUtils.clone(norm.getData()));
+ setBuffer(bindNorm);
+ norm.setUsage(Usage.Stream);
+ }
+
+ VertexBuffer tangents = getBuffer(Type.Tangent);
+ if (tangents != null) {
+ VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+ bindTangents.setupData(Usage.CpuOnly,
+ 4,
+ Format.Float,
+ BufferUtils.clone(tangents.getData()));
+ setBuffer(bindTangents);
+ tangents.setUsage(Usage.Stream);
+ }
+ }
+ }
+
+ /**
+ * Prepares the mesh for software skinning by converting the bone index
+ * and weight buffers to heap buffers.
+ *
+ * @param forSoftwareAnim Should be true to enable the conversion.
+ */
+ public void prepareForAnim(boolean forSoftwareAnim){
+ if (forSoftwareAnim){
+ // convert indices
+ VertexBuffer indices = getBuffer(Type.BoneIndex);
+ ByteBuffer originalIndex = (ByteBuffer) indices.getData();
+ ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity());
+ originalIndex.clear();
+ arrayIndex.put(originalIndex);
+ indices.updateData(arrayIndex);
+
+ // convert weights
+ VertexBuffer weights = getBuffer(Type.BoneWeight);
+ FloatBuffer originalWeight = (FloatBuffer) weights.getData();
+ FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity());
+ originalWeight.clear();
+ arrayWeight.put(originalWeight);
+ weights.updateData(arrayWeight);
+ }
+ }
+
+ /**
+ * Set the LOD (level of detail) index buffers on this mesh.
+ *
+ * @param lodLevels The LOD levels to set
+ */
+ public void setLodLevels(VertexBuffer[] lodLevels){
+ this.lodLevels = lodLevels;
+ }
+
+ /**
+ * @return The number of LOD levels set on this mesh, including the main
+ * index buffer, returns zero if there are no lod levels.
+ */
+ public int getNumLodLevels(){
+ return lodLevels != null ? lodLevels.length : 0;
+ }
+
+ /**
+ * Returns the lod level at the given index.
+ *
+ * @param lod The lod level index, this does not include
+ * the main index buffer.
+ * @return The LOD index buffer at the index
+ *
+ * @throws IndexOutOfBoundsException If the index is outside of the
+ * range [0, {@link #getNumLodLevels()}].
+ *
+ * @see #setLodLevels(com.jme3.scene.VertexBuffer[])
+ */
+ public VertexBuffer getLodLevel(int lod){
+ return lodLevels[lod];
+ }
+
+ /**
+ * Get the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return element lengths
+ */
+ public int[] getElementLengths() {
+ return elementLengths;
+ }
+
+ /**
+ * Set the element lengths for {@link Mode#Hybrid} mesh mode.
+ *
+ * @param elementLengths The element lengths to set
+ */
+ public void setElementLengths(int[] elementLengths) {
+ this.elementLengths = elementLengths;
+ }
+
+ /**
+ * Set the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public int[] getModeStart() {
+ return modeStart;
+ }
+
+ /**
+ * Get the mode start indices for {@link Mode#Hybrid} mesh mode.
+ *
+ * @return mode start indices
+ */
+ public void setModeStart(int[] modeStart) {
+ this.modeStart = modeStart;
+ }
+
+ /**
+ * Returns the mesh mode
+ *
+ * @return the mesh mode
+ *
+ * @see #setMode(com.jme3.scene.Mesh.Mode)
+ */
+ public Mode getMode() {
+ return mode;
+ }
+
+ /**
+ * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}.
+ *
+ * @param mode The new mode to set
+ *
+ * @see Mode
+ */
+ public void setMode(Mode mode) {
+ this.mode = mode;
+ updateCounts();
+ }
+
+ /**
+ * Returns the maximum number of weights per vertex on this mesh.
+ *
+ * @return maximum number of weights per vertex
+ *
+ * @see #setMaxNumWeights(int)
+ */
+ public int getMaxNumWeights() {
+ return maxNumWeights;
+ }
+
+ /**
+ * Set the maximum number of weights per vertex on this mesh.
+ * Only relevant if this mesh has bone index/weight buffers.
+ * This value should be between 0 and 4.
+ *
+ * @param maxNumWeights
+ */
+ public void setMaxNumWeights(int maxNumWeights) {
+ this.maxNumWeights = maxNumWeights;
+ }
+
+ /**
+ * Returns the size of points for point meshes
+ *
+ * @return the size of points
+ *
+ * @see #setPointSize(float)
+ */
+ public float getPointSize() {
+ return pointSize;
+ }
+
+ /**
+ * Set the size of points for meshes of mode {@link Mode#Points}.
+ * The point size is specified as on-screen pixels, the default
+ * value is 1.0. The point size
+ * does nothing if {@link RenderState#setPointSprite(boolean) point sprite}
+ * render state is enabled, in that case, the vertex shader must specify the
+ * point size by writing to <code>gl_PointSize</code>.
+ *
+ * @param pointSize The size of points
+ */
+ public void setPointSize(float pointSize) {
+ this.pointSize = pointSize;
+ }
+
+ /**
+ * Returns the line width for line meshes.
+ *
+ * @return the line width
+ */
+ public float getLineWidth() {
+ return lineWidth;
+ }
+
+ /**
+ * Specify the line width for meshes of the line modes, such
+ * as {@link Mode#Lines}. The line width is specified as on-screen pixels,
+ * the default value is 1.0.
+ *
+ * @param lineWidth The line width
+ */
+ public void setLineWidth(float lineWidth) {
+ this.lineWidth = lineWidth;
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will not be modified (a hint).
+ * Sets the usage mode to {@link Usage#Static}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setStatic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Static);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified occasionally (a hint).
+ * Sets the usage mode to {@link Usage#Dynamic}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setDynamic() {
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Dynamic);
+ }
+ }
+
+ /**
+ * Indicates to the GPU that this mesh will be modified every frame (a hint).
+ * Sets the usage mode to {@link Usage#Stream}
+ * for all {@link VertexBuffer vertex buffers} on this Mesh.
+ */
+ public void setStreamed(){
+ for (Entry<VertexBuffer> entry : buffers){
+ entry.getValue().setUsage(Usage.Stream);
+ }
+ }
+
+ /**
+ * Interleaves the data in this mesh. This operation cannot be reversed.
+ * Some GPUs may prefer the data in this format, however it is a good idea
+ * to <em>avoid</em> using this method as it disables some engine features.
+ */
+ public void setInterleaved(){
+ ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>();
+ for (Entry<VertexBuffer> entry : buffers){
+ vbs.add(entry.getValue());
+ }
+// ArrayList<VertexBuffer> vbs = new ArrayList<VertexBuffer>(buffers.values());
+ // index buffer not included when interleaving
+ vbs.remove(getBuffer(Type.Index));
+
+ int stride = 0; // aka bytes per vertex
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+// if (vb.getFormat() != Format.Float){
+// throw new UnsupportedOperationException("Cannot interleave vertex buffer.\n" +
+// "Contains not-float data.");
+// }
+ stride += vb.componentsLength;
+ vb.getData().clear(); // reset position & limit (used later)
+ }
+
+ VertexBuffer allData = new VertexBuffer(Type.InterleavedData);
+ ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount());
+ allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf);
+
+ // adding buffer directly so that no update counts is forced
+ buffers.put(Type.InterleavedData.ordinal(), allData);
+ buffersList.add(allData);
+
+ for (int vert = 0; vert < getVertexCount(); vert++){
+ for (int i = 0; i < vbs.size(); i++){
+ VertexBuffer vb = vbs.get(i);
+ switch (vb.getFormat()){
+ case Float:
+ FloatBuffer fb = (FloatBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putFloat(fb.get());
+ }
+ break;
+ case Byte:
+ case UnsignedByte:
+ ByteBuffer bb = (ByteBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.put(bb.get());
+ }
+ break;
+ case Half:
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sb = (ShortBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putShort(sb.get());
+ }
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer ib = (IntBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putInt(ib.get());
+ }
+ break;
+ case Double:
+ DoubleBuffer db = (DoubleBuffer) vb.getData();
+ for (int comp = 0; comp < vb.components; comp++){
+ dataBuf.putDouble(db.get());
+ }
+ break;
+ }
+ }
+ }
+
+ int offset = 0;
+ for (VertexBuffer vb : vbs){
+ vb.setOffset(offset);
+ vb.setStride(stride);
+
+ vb.updateData(null);
+ //vb.setupData(vb.usage, vb.components, vb.format, null);
+ offset += vb.componentsLength;
+ }
+ }
+
+ private int computeNumElements(int bufSize){
+ switch (mode){
+ case Triangles:
+ return bufSize / 3;
+ case TriangleFan:
+ case TriangleStrip:
+ return bufSize - 2;
+ case Points:
+ return bufSize;
+ case Lines:
+ return bufSize / 2;
+ case LineLoop:
+ return bufSize;
+ case LineStrip:
+ return bufSize - 1;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /**
+ * Update the {@link #getVertexCount() vertex} and
+ * {@link #getTriangleCount() triangle} counts for this mesh
+ * based on the current data. This method should be called
+ * after the {@link Buffer#capacity() capacities} of the mesh's
+ * {@link VertexBuffer vertex buffers} has been altered.
+ *
+ * @throws IllegalStateException If this mesh is in
+ * {@link #setInterleaved() interleaved} format.
+ */
+ public void updateCounts(){
+ if (getBuffer(Type.InterleavedData) != null)
+ throw new IllegalStateException("Should update counts before interleave");
+
+ VertexBuffer pb = getBuffer(Type.Position);
+ VertexBuffer ib = getBuffer(Type.Index);
+ if (pb != null){
+ vertCount = pb.getData().capacity() / pb.getNumComponents();
+ }
+ if (ib != null){
+ elementCount = computeNumElements(ib.getData().capacity());
+ }else{
+ elementCount = computeNumElements(vertCount);
+ }
+ }
+
+ /**
+ * Returns the triangle count for the given LOD level.
+ *
+ * @param lod The lod level to look up
+ * @return The triangle count for that LOD level
+ */
+ public int getTriangleCount(int lod){
+ if (lodLevels != null){
+ if (lod < 0)
+ throw new IllegalArgumentException("LOD level cannot be < 0");
+
+ if (lod >= lodLevels.length)
+ throw new IllegalArgumentException("LOD level "+lod+" does not exist!");
+
+ return computeNumElements(lodLevels[lod].getData().capacity());
+ }else if (lod == 0){
+ return elementCount;
+ }else{
+ throw new IllegalArgumentException("There are no LOD levels on the mesh!");
+ }
+ }
+
+ /**
+ * Returns how many triangles or elements are on this Mesh.
+ * This value is only updated when {@link #updateCounts() } is called.
+ * If the mesh mode is not a triangle mode, then this returns the
+ * number of elements/primitives, e.g. how many lines or how many points,
+ * instead of how many triangles.
+ *
+ * @return how many triangles/elements are on this Mesh.
+ */
+ public int getTriangleCount(){
+ return elementCount;
+ }
+
+ /**
+ * Returns the number of vertices on this mesh.
+ * The value is computed based on the position buffer, which
+ * must be set on all meshes.
+ *
+ * @return Number of vertices on the mesh
+ */
+ public int getVertexCount(){
+ return vertCount;
+ }
+
+ /**
+ * Gets the triangle vertex positions at the given triangle index
+ * and stores them into the v1, v2, v3 arguments.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param v1 Vector to contain first vertex position
+ * @param v2 Vector to contain second vertex position
+ * @param v3 Vector to contain third vertex position
+ */
+ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){
+ VertexBuffer pb = getBuffer(Type.Position);
+ IndexBuffer ib = getIndicesAsList();
+ if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){
+ FloatBuffer fpb = (FloatBuffer) pb.getData();
+
+ // aquire triangle's vertex indices
+ int vertIndex = index * 3;
+ int vert1 = ib.get(vertIndex);
+ int vert2 = ib.get(vertIndex+1);
+ int vert3 = ib.get(vertIndex+2);
+
+ BufferUtils.populateFromBuffer(v1, fpb, vert1);
+ BufferUtils.populateFromBuffer(v2, fpb, vert2);
+ BufferUtils.populateFromBuffer(v3, fpb, vert3);
+ }else{
+ throw new UnsupportedOperationException("Position buffer not set or "
+ + " has incompatible format");
+ }
+ }
+
+ /**
+ * Gets the triangle vertex positions at the given triangle index
+ * and stores them into the {@link Triangle} argument.
+ * Also sets the triangle index to the <code>index</code> argument.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param tri The triangle to store the positions in
+ */
+ public void getTriangle(int index, Triangle tri){
+ getTriangle(index, tri.get1(), tri.get2(), tri.get3());
+ tri.setIndex(index);
+ tri.setNormal(null);
+ }
+
+ /**
+ * Gets the triangle vertex indices at the given triangle index
+ * and stores them into the given int array.
+ *
+ * @param index The index of the triangle.
+ * Should be between 0 and {@link #getTriangleCount()}.
+ *
+ * @param indices Indices of the triangle's vertices
+ */
+ public void getTriangle(int index, int[] indices){
+ IndexBuffer ib = getIndicesAsList();
+
+ // acquire triangle's vertex indices
+ int vertIndex = index * 3;
+ indices[0] = ib.get(vertIndex);
+ indices[1] = ib.get(vertIndex+1);
+ indices[2] = ib.get(vertIndex+2);
+ }
+
+ /**
+ * Returns the mesh's VAO ID. Internal use only.
+ */
+ public int getId(){
+ return vertexArrayID;
+ }
+
+ /**
+ * Sets the mesh's VAO ID. Internal use only.
+ */
+ public void setId(int id){
+ if (vertexArrayID != -1)
+ throw new IllegalStateException("ID has already been set.");
+
+ vertexArrayID = id;
+ }
+
+ /**
+ * Generates a collision tree for the mesh.
+ * Called automatically by {@link #collideWith(com.jme3.collision.Collidable,
+ * com.jme3.math.Matrix4f,
+ * com.jme3.bounding.BoundingVolume,
+ * com.jme3.collision.CollisionResults) }.
+ */
+ public void createCollisionData(){
+ BIHTree tree = new BIHTree(this);
+ tree.construct();
+ collisionTree = tree;
+ }
+
+ /**
+ * Handles collision detection, internal use only.
+ * User code should only use collideWith() on scene
+ * graph elements such as {@link Spatial}s.
+ */
+ public int collideWith(Collidable other,
+ Matrix4f worldMatrix,
+ BoundingVolume worldBound,
+ CollisionResults results){
+
+ if (collisionTree == null){
+ createCollisionData();
+ }
+
+ return collisionTree.collideWith(other, worldMatrix, worldBound, results);
+ }
+
+ /**
+ * Set a floating point {@link VertexBuffer} on the mesh.
+ *
+ * @param type The type of {@link VertexBuffer},
+ * e.g. {@link Type#Position}, {@link Type#Normal}, etc.
+ *
+ * @param components Number of components on the vertex buffer, should
+ * be between 1 and 4.
+ *
+ * @param buf The floating point data to contain
+ */
+ public void setBuffer(Type type, int components, FloatBuffer buf) {
+// VertexBuffer vb = buffers.get(type);
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ if (buf == null)
+ return;
+
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+// buffers.put(type, vb);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ }else{
+ vb.setupData(Usage.Dynamic, components, Format.Float, buf);
+ }
+ updateCounts();
+ }
+
+ public void setBuffer(Type type, int components, float[] buf){
+ setBuffer(type, components, BufferUtils.createFloatBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, IntBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedInt, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, int[] buf){
+ setBuffer(type, components, BufferUtils.createIntBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ShortBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedShort, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, byte[] buf){
+ setBuffer(type, components, BufferUtils.createByteBuffer(buf));
+ }
+
+ public void setBuffer(Type type, int components, ByteBuffer buf) {
+ VertexBuffer vb = buffers.get(type.ordinal());
+ if (vb == null){
+ vb = new VertexBuffer(type);
+ vb.setupData(Usage.Dynamic, components, Format.UnsignedByte, buf);
+ buffers.put(type.ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(VertexBuffer vb){
+ if (buffers.containsKey(vb.getBufferType().ordinal()))
+ throw new IllegalArgumentException("Buffer type already set: "+vb.getBufferType());
+
+ buffers.put(vb.getBufferType().ordinal(), vb);
+ buffersList.add(vb);
+ updateCounts();
+ }
+
+ /**
+ * Clears or unsets the {@link VertexBuffer} set on this mesh
+ * with the given type.
+ * Does nothing if the vertex buffer type is not set initially
+ *
+ * @param type The type to remove
+ */
+ public void clearBuffer(VertexBuffer.Type type){
+ VertexBuffer vb = buffers.remove(type.ordinal());
+ if (vb != null){
+ buffersList.remove(vb);
+ updateCounts();
+ }
+ }
+
+ public void setBuffer(Type type, int components, short[] buf){
+ setBuffer(type, components, BufferUtils.createShortBuffer(buf));
+ }
+
+ /**
+ * Get the {@link VertexBuffer} stored on this mesh with the given
+ * type.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public VertexBuffer getBuffer(Type type){
+ return buffers.get(type.ordinal());
+ }
+
+ /**
+ * Get the {@link VertexBuffer} data stored on this mesh in float
+ * format.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public FloatBuffer getFloatBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ return (FloatBuffer) vb.getData();
+ }
+
+ /**
+ * Get the {@link VertexBuffer} data stored on this mesh in short
+ * format.
+ *
+ * @param type The type of VertexBuffer
+ * @return the VertexBuffer data, or null if not set
+ */
+ public ShortBuffer getShortBuffer(Type type) {
+ VertexBuffer vb = getBuffer(type);
+ if (vb == null)
+ return null;
+
+ return (ShortBuffer) vb.getData();
+ }
+
+ /**
+ * Acquires an index buffer that will read the vertices on the mesh
+ * as a list.
+ *
+ * @return A virtual or wrapped index buffer to read the data as a list
+ */
+ public IndexBuffer getIndicesAsList(){
+ if (mode == Mode.Hybrid)
+ throw new UnsupportedOperationException("Hybrid mode not supported");
+
+ IndexBuffer ib = getIndexBuffer();
+ if (ib != null){
+ if (mode.isListMode()){
+ // already in list mode
+ return ib;
+ }else{
+ // not in list mode but it does have an index buffer
+ // wrap it so the data is converted to list format
+ return new WrappedIndexBuffer(this);
+ }
+ }else{
+ // return a virtual index buffer that will supply
+ // "fake" indices in list format
+ return new VirtualIndexBuffer(vertCount, mode);
+ }
+ }
+
+ /**
+ * Get the index buffer for this mesh.
+ * Will return <code>null</code> if no index buffer is set.
+ *
+ * @return The index buffer of this mesh.
+ *
+ * @see Type#Index
+ */
+ public IndexBuffer getIndexBuffer() {
+ VertexBuffer vb = getBuffer(Type.Index);
+ if (vb == null)
+ return null;
+
+ Buffer buf = vb.getData();
+ if (buf instanceof ByteBuffer) {
+ return new IndexByteBuffer((ByteBuffer) buf);
+ } else if (buf instanceof ShortBuffer) {
+ return new IndexShortBuffer((ShortBuffer) buf);
+ } else if (buf instanceof IntBuffer) {
+ return new IndexIntBuffer((IntBuffer) buf);
+ } else {
+ throw new UnsupportedOperationException("Index buffer type unsupported: "+ buf.getClass());
+ }
+ }
+
+ /**
+ * Extracts the vertex attributes from the given mesh into
+ * this mesh, by using this mesh's {@link #getIndexBuffer() index buffer}
+ * to index into the attributes of the other mesh.
+ * Note that this will also change this mesh's index buffer so that
+ * the references to the vertex data match the new indices.
+ *
+ * @param other The mesh to extract the vertex data from
+ */
+ public void extractVertexData(Mesh other) {
+ // Determine the number of unique vertices need to
+ // be created. Also determine the mappings
+ // between old indices to new indices (since we avoid duplicating
+ // vertices, this is a map and not an array).
+ VertexBuffer oldIdxBuf = getBuffer(Type.Index);
+ IndexBuffer indexBuf = getIndexBuffer();
+ int numIndices = indexBuf.size();
+
+ IntMap<Integer> oldIndicesToNewIndices = new IntMap<Integer>(numIndices);
+ ArrayList<Integer> newIndicesToOldIndices = new ArrayList<Integer>();
+ int newIndex = 0;
+
+ for (int i = 0; i < numIndices; i++) {
+ int oldIndex = indexBuf.get(i);
+
+ if (!oldIndicesToNewIndices.containsKey(oldIndex)) {
+ // this vertex has not been added, so allocate a
+ // new index for it and add it to the map
+ oldIndicesToNewIndices.put(oldIndex, newIndex);
+ newIndicesToOldIndices.add(oldIndex);
+
+ // increment to have the next index
+ newIndex++;
+ }
+ }
+
+ // Number of unique verts to be created now available
+ int newNumVerts = newIndicesToOldIndices.size();
+
+ if (newIndex != newNumVerts) {
+ throw new AssertionError();
+ }
+
+ // Create the new index buffer.
+ // Do not overwrite the old one because we might be able to
+ // convert from int index buffer to short index buffer
+ IndexBuffer newIndexBuf;
+ if (newNumVerts >= 65536) {
+ newIndexBuf = new IndexIntBuffer(BufferUtils.createIntBuffer(numIndices));
+ } else {
+ newIndexBuf = new IndexShortBuffer(BufferUtils.createShortBuffer(numIndices));
+ }
+
+ for (int i = 0; i < numIndices; i++) {
+ // Map the old indices to the new indices
+ int oldIndex = indexBuf.get(i);
+ newIndex = oldIndicesToNewIndices.get(oldIndex);
+
+ newIndexBuf.put(i, newIndex);
+ }
+
+ VertexBuffer newIdxBuf = new VertexBuffer(Type.Index);
+ newIdxBuf.setupData(oldIdxBuf.getUsage(),
+ oldIdxBuf.getNumComponents(),
+ newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort,
+ newIndexBuf.getBuffer());
+ clearBuffer(Type.Index);
+ setBuffer(newIdxBuf);
+
+ // Now, create the vertex buffers
+ SafeArrayList<VertexBuffer> oldVertexData = other.getBufferList();
+ for (VertexBuffer oldVb : oldVertexData) {
+ if (oldVb.getBufferType() == VertexBuffer.Type.Index) {
+ // ignore the index buffer
+ continue;
+ }
+
+ // Create a new vertex buffer with similar configuration, but
+ // with the capacity of number of unique vertices
+ Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts);
+
+ VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType());
+ newVb.setNormalized(oldVb.isNormalized());
+ newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer);
+
+ // Copy the vertex data from the old buffer into the new buffer
+ for (int i = 0; i < newNumVerts; i++) {
+ int oldIndex = newIndicesToOldIndices.get(i);
+
+ // Copy the vertex attribute from the old index
+ // to the new index
+ oldVb.copyElement(oldIndex, newVb, i);
+ }
+
+ // Set the buffer on the mesh
+ clearBuffer(newVb.getBufferType());
+ setBuffer(newVb);
+ }
+
+ // Copy max weights per vertex as well
+ setMaxNumWeights(other.getMaxNumWeights());
+
+ // The data has been copied over, update informations
+ updateCounts();
+ updateBound();
+ }
+
+ /**
+ * Scales the texture coordinate buffer on this mesh by the given
+ * scale factor.
+ * <p>
+ * Note that values above 1 will cause the
+ * texture to tile, while values below 1 will cause the texture
+ * to stretch.
+ * </p>
+ *
+ * @param scaleFactor The scale factor to scale by. Every texture
+ * coordinate is multiplied by this vector to get the result.
+ *
+ * @throws IllegalStateException If there's no texture coordinate
+ * buffer on the mesh
+ * @throws UnsupportedOperationException If the texture coordinate
+ * buffer is not in 2D float format.
+ */
+ public void scaleTextureCoordinates(Vector2f scaleFactor){
+ VertexBuffer tc = getBuffer(Type.TexCoord);
+ if (tc == null)
+ throw new IllegalStateException("The mesh has no texture coordinates");
+
+ if (tc.getFormat() != VertexBuffer.Format.Float)
+ throw new UnsupportedOperationException("Only float texture coord format is supported");
+
+ if (tc.getNumComponents() != 2)
+ throw new UnsupportedOperationException("Only 2D texture coords are supported");
+
+ FloatBuffer fb = (FloatBuffer) tc.getData();
+ fb.clear();
+ for (int i = 0; i < fb.capacity() / 2; i++){
+ float x = fb.get();
+ float y = fb.get();
+ fb.position(fb.position()-2);
+ x *= scaleFactor.getX();
+ y *= scaleFactor.getY();
+ fb.put(x).put(y);
+ }
+ fb.clear();
+ tc.updateData(fb);
+ }
+
+ /**
+ * Updates the bounding volume of this mesh.
+ * The method does nothing if the mesh has no {@link Type#Position} buffer.
+ * It is expected that the position buffer is a float buffer with 3 components.
+ */
+ public void updateBound(){
+ VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position);
+ if (meshBound != null && posBuf != null){
+ meshBound.computeFromPoints((FloatBuffer)posBuf.getData());
+ }
+ }
+
+ /**
+ * Returns the {@link BoundingVolume} of this Mesh.
+ * By default the bounding volume is a {@link BoundingBox}.
+ *
+ * @return the bounding volume of this mesh
+ */
+ public BoundingVolume getBound() {
+ return meshBound;
+ }
+
+ /**
+ * Sets the {@link BoundingVolume} for this Mesh.
+ * The bounding volume is recomputed by calling {@link #updateBound() }.
+ *
+ * @param modelBound The model bound to set
+ */
+ public void setBound(BoundingVolume modelBound) {
+ meshBound = modelBound;
+ }
+
+ /**
+ * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh.
+ * The integer key for the map is the {@link Enum#ordinal() ordinal}
+ * of the vertex buffer's {@link Type}.
+ * Note that the returned map is a reference to the map used internally,
+ * modifying it will cause undefined results.
+ *
+ * @return map of vertex buffers on this mesh.
+ */
+ public IntMap<VertexBuffer> getBuffers(){
+ return buffers;
+ }
+
+ /**
+ * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh.
+ * Using a list instead an IntMap via the {@link #getBuffers() } method is
+ * better for iteration as there's no need to create an iterator instance.
+ * Note that the returned list is a reference to the list used internally,
+ * modifying it will cause undefined results.
+ *
+ * @return list of vertex buffers on this mesh.
+ */
+ public SafeArrayList<VertexBuffer> getBufferList(){
+ return buffersList;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule out = ex.getCapsule(this);
+
+// HashMap<String, VertexBuffer> map = new HashMap<String, VertexBuffer>();
+// for (Entry<VertexBuffer> buf : buffers){
+// if (buf.getValue() != null)
+// map.put(buf.getKey()+"a", buf.getValue());
+// }
+// out.writeStringSavableMap(map, "buffers", null);
+
+ out.write(meshBound, "modelBound", null);
+ out.write(vertCount, "vertCount", -1);
+ out.write(elementCount, "elementCount", -1);
+ out.write(maxNumWeights, "max_num_weights", -1);
+ out.write(mode, "mode", Mode.Triangles);
+ out.write(collisionTree, "collisionTree", null);
+ out.write(elementLengths, "elementLengths", null);
+ out.write(modeStart, "modeStart", null);
+ out.write(pointSize, "pointSize", 1f);
+
+ out.writeIntSavableMap(buffers, "buffers", null);
+ out.write(lodLevels, "lodLevels", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule in = im.getCapsule(this);
+ meshBound = (BoundingVolume) in.readSavable("modelBound", null);
+ vertCount = in.readInt("vertCount", -1);
+ elementCount = in.readInt("elementCount", -1);
+ maxNumWeights = in.readInt("max_num_weights", -1);
+ mode = in.readEnum("mode", Mode.class, Mode.Triangles);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ collisionTree = (BIHTree) in.readSavable("collisionTree", null);
+ elementLengths = in.readIntArray("elementLengths", null);
+ modeStart = in.readIntArray("modeStart", null);
+ pointSize = in.readFloat("pointSize", 1f);
+
+// in.readStringSavableMap("buffers", null);
+ buffers = (IntMap<VertexBuffer>) in.readIntSavableMap("buffers", null);
+ for (Entry<VertexBuffer> entry : buffers){
+ buffersList.add(entry.getValue());
+ }
+
+ Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null);
+ if (lodLevelsSavable != null) {
+ lodLevels = new VertexBuffer[lodLevelsSavable.length];
+ System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/Node.java b/engine/src/core/com/jme3/scene/Node.java
new file mode 100644
index 0000000..bb7534a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Node.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.Savable;
+import com.jme3.material.Material;
+import com.jme3.util.SafeArrayList;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+
+/**
+ * <code>Node</code> 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.
+ *
+ * @author Mark Powell
+ * @author Gregg Patton
+ * @author Joshua Slack
+ */
+public class Node extends Spatial implements Savable {
+
+ private static final Logger logger = Logger.getLogger(Node.class.getName());
+
+
+ /**
+ * This node's children.
+ */
+ protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Node() {
+ }
+
+ /**
+ * Constructor instantiates a new <code>Node</code> with a default empty
+ * list for containing children.
+ *
+ * @param name
+ * the name of the scene element. This is required for
+ * identification and comparision purposes.
+ */
+ public Node(String name) {
+ super(name);
+ }
+
+ /**
+ *
+ * <code>getQuantity</code> returns the number of children this node
+ * maintains.
+ *
+ * @return the number of children this node maintains.
+ */
+ public int getQuantity() {
+ return children.size();
+ }
+
+ @Override
+ protected void setTransformRefresh(){
+ super.setTransformRefresh();
+ for (Spatial child : children.getArray()){
+ if ((child.refreshFlags & RF_TRANSFORM) != 0)
+ continue;
+
+ child.setTransformRefresh();
+ }
+ }
+
+ @Override
+ protected void setLightListRefresh(){
+ super.setLightListRefresh();
+ for (Spatial child : children.getArray()){
+ if ((child.refreshFlags & RF_LIGHTLIST) != 0)
+ continue;
+
+ child.setLightListRefresh();
+ }
+ }
+
+ @Override
+ protected void updateWorldBound(){
+ super.updateWorldBound();
+
+ // for a node, the world bound is a combination of all it's children
+ // bounds
+ BoundingVolume resultBound = null;
+ for (Spatial child : children.getArray()) {
+ // child bound is assumed to be updated
+ assert (child.refreshFlags & RF_BOUND) == 0;
+ if (resultBound != null) {
+ // merge current world bound with child world bound
+ resultBound.mergeLocal(child.getWorldBound());
+ } else {
+ // set world bound to first non-null child world bound
+ if (child.getWorldBound() != null) {
+ resultBound = child.getWorldBound().clone(this.worldBound);
+ }
+ }
+ }
+ this.worldBound = resultBound;
+ }
+
+ @Override
+ public void updateLogicalState(float tpf){
+ super.updateLogicalState(tpf);
+
+ if (children.isEmpty()) {
+ return;
+ }
+
+ for (Spatial child : children.getArray()) {
+ child.updateLogicalState(tpf);
+ }
+ }
+
+ @Override
+ public void updateGeometricState(){
+ if ((refreshFlags & RF_LIGHTLIST) != 0){
+ updateWorldLightList();
+ }
+
+ if ((refreshFlags & RF_TRANSFORM) != 0){
+ // combine with parent transforms- same for all spatial
+ // subclasses.
+ updateWorldTransforms();
+ }
+
+ if (!children.isEmpty()) {
+ // the important part- make sure child geometric state is refreshed
+ // first before updating own world bound. This saves
+ // a round-trip later on.
+ // NOTE 9/19/09
+ // Although it does save a round trip,
+ for (Spatial child : children.getArray()) {
+ child.updateGeometricState();
+ }
+ }
+
+ if ((refreshFlags & RF_BOUND) != 0){
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ /**
+ * <code>getTriangleCount</code> returns the number of triangles contained
+ * in all sub-branches of this node that contain geometry.
+ *
+ * @return the triangle count of this branch.
+ */
+ @Override
+ public int getTriangleCount() {
+ int count = 0;
+ if(children != null) {
+ for(int i = 0; i < children.size(); i++) {
+ count += children.get(i).getTriangleCount();
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * <code>getVertexCount</code> returns the number of vertices contained
+ * in all sub-branches of this node that contain geometry.
+ *
+ * @return the vertex count of this branch.
+ */
+ @Override
+ public int getVertexCount() {
+ int count = 0;
+ if(children != null) {
+ for(int i = 0; i < children.size(); i++) {
+ count += children.get(i).getVertexCount();
+ }
+ }
+
+ return count;
+ }
+
+ /**
+ * <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.
+ * @throws NullPointerException If child is null.
+ */
+ public int attachChild(Spatial child) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() != this && child != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ children.add(child);
+
+ // XXX: Not entirely correct? Forces bound update up the
+ // tree stemming from the attached child. Also forces
+ // transform update down the tree-
+ child.setTransformRefresh();
+ child.setLightListRefresh();
+ if (logger.isLoggable(Level.INFO)) {
+ logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+ new Object[]{child.getName(), 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.
+ * @throws NullPointerException if child is null.
+ */
+ public int attachChildAt(Spatial child, int index) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() != this && child != this) {
+ if (child.getParent() != null) {
+ child.getParent().detachChild(child);
+ }
+ child.setParent(this);
+ children.add(index, child);
+ child.setTransformRefresh();
+ child.setLightListRefresh();
+ if (logger.isLoggable(Level.INFO)) {
+ logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
+ new Object[]{child.getName(), getName()});
+ }
+ }
+
+ return children.size();
+ }
+
+ /**
+ * <code>detachChild</code> removes a given child from the node's list.
+ * This child will no longer 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(Spatial child) {
+ if (child == null)
+ throw new NullPointerException();
+
+ if (child.getParent() == this) {
+ 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(String childName) {
+ if (childName == null)
+ throw new NullPointerException();
+
+ for (int x = 0, max = children.size(); x < max; x++) {
+ Spatial child = children.get(x);
+ if (childName.equals(child.getName())) {
+ detachChildAt( x );
+ return x;
+ }
+ }
+ 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(int index) {
+ Spatial child = children.remove(index);
+ if ( child != null ) {
+ child.setParent( null );
+ logger.log(Level.INFO, "{0}: Child removed.", this.toString());
+
+ // since a child with a bound was detached;
+ // our own bound will probably change.
+ setBoundRefresh();
+
+ // our world transform no longer influences the child.
+ // XXX: Not neccessary? Since child will have transform updated
+ // when attached anyway.
+ child.setTransformRefresh();
+ // lights are also inherited from parent
+ child.setLightListRefresh();
+ }
+ return child;
+ }
+
+ /**
+ *
+ * <code>detachAllChildren</code> removes all children attached to this
+ * node.
+ */
+ public void detachAllChildren() {
+ for ( int i = children.size() - 1; i >= 0; i-- ) {
+ detachChildAt(i);
+ }
+ logger.log(Level.INFO, "{0}: All children removed.", this.toString());
+ }
+
+ /**
+ * <code>getChildIndex</code> returns the index of the given spatial
+ * in this node's list of children.
+ * @param sp
+ * The spatial to look up
+ * @return
+ * The index of the spatial in the node's children, or -1
+ * if the spatial is not attached to this node
+ */
+ public int getChildIndex(Spatial sp) {
+ return children.indexOf(sp);
+ }
+
+ /**
+ * More efficient than e.g detaching and attaching as no updates are needed.
+ *
+ * @param index1 The index of the first child to swap
+ * @param index2 The index of the second child to swap
+ */
+ public void swapChildren(int index1, int index2) {
+ Spatial c2 = children.get(index2);
+ Spatial c1 = children.remove(index1);
+ children.add(index1, c2);
+ children.remove(index2);
+ children.add(index2, c1);
+ }
+
+ /**
+ *
+ * <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(int i) {
+ return children.get(i);
+ }
+
+ /**
+ * <code>getChild</code> returns the first child found with exactly the
+ * given name (case sensitive.)
+ *
+ * @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(String name) {
+ if (name == null)
+ return null;
+
+ for (Spatial child : children.getArray()) {
+ if (name.equals(child.getName())) {
+ return child;
+ } else if(child instanceof Node) {
+ 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(Spatial spat) {
+ if (children.contains(spat))
+ return true;
+
+ for (Spatial child : children.getArray()) {
+ if (child instanceof Node && ((Node) child).hasChild(spat))
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns all children to this node. Note that modifying that given
+ * list is not allowed.
+ *
+ * @return a list containing all children to this node
+ */
+ public List<Spatial> getChildren() {
+ return children;
+ }
+
+ @Override
+ public void setMaterial(Material mat){
+ for (int i = 0; i < children.size(); i++){
+ children.get(i).setMaterial(mat);
+ }
+ }
+
+ @Override
+ public void setLodLevel(int lod){
+ super.setLodLevel(lod);
+ for (Spatial child : children.getArray()) {
+ child.setLodLevel(lod);
+ }
+ }
+
+ public int collideWith(Collidable other, CollisionResults results){
+ int total = 0;
+ for (Spatial child : children.getArray()){
+ total += child.collideWith(other, results);
+ }
+ return total;
+ }
+
+
+ /**
+ * Returns flat list of Spatials implementing the specified class AND
+ * with name matching the specified pattern.
+ * </P> <P>
+ * Note that we are <i>matching</i> the pattern, therefore the pattern
+ * must match the entire pattern (i.e. it behaves as if it is sandwiched
+ * between "^" and "$").
+ * You can set regex modes, like case insensitivity, by using the (?X)
+ * or (?X:Y) constructs.
+ * </P> <P>
+ * By design, it is always safe to code loops like:<CODE><PRE>
+ * for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
+ * </PRE></CODE>
+ * </P> <P>
+ * "Descendants" does not include self, per the definition of the word.
+ * To test for descendants AND self, you must do a
+ * <code>node.matches(aClass, aRegex)</code> +
+ * <code>node.descendantMatches(aClass, aRegex)</code>.
+ * <P>
+ *
+ * @param spatialSubclass Subclass which matching Spatials must implement.
+ * Null causes all Spatials to qualify.
+ * @param nameRegex Regular expression to match Spatial name against.
+ * Null causes all Names to qualify.
+ * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
+ *
+ * @see java.util.regex.Pattern
+ * @see Spatial#matches(java.lang.Class, java.lang.String)
+ */
+ @SuppressWarnings("unchecked")
+ public <T extends Spatial>List<T> descendantMatches(
+ Class<T> spatialSubclass, String nameRegex) {
+ List<T> newList = new ArrayList<T>();
+ if (getQuantity() < 1) return newList;
+ for (Spatial child : getChildren()) {
+ if (child.matches(spatialSubclass, nameRegex))
+ newList.add((T)child);
+ if (child instanceof Node)
+ newList.addAll(((Node) child).descendantMatches(
+ spatialSubclass, nameRegex));
+ }
+ return newList;
+ }
+
+ /**
+ * Convenience wrapper.
+ *
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
+ */
+ public <T extends Spatial>List<T> descendantMatches(
+ Class<T> spatialSubclass) {
+ return descendantMatches(spatialSubclass, null);
+ }
+
+ /**
+ * Convenience wrapper.
+ *
+ * @see #descendantMatches(java.lang.Class, java.lang.String)
+ */
+ public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
+ return descendantMatches(null, nameRegex);
+ }
+
+ @Override
+ public Node clone(boolean cloneMaterials){
+ Node nodeClone = (Node) super.clone(cloneMaterials);
+// nodeClone.children = new ArrayList<Spatial>();
+// for (Spatial child : children){
+// Spatial childClone = child.clone();
+// childClone.parent = nodeClone;
+// nodeClone.children.add(childClone);
+// }
+ return nodeClone;
+ }
+
+ @Override
+ public Spatial deepClone(){
+ Node nodeClone = (Node) super.clone();
+ nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+ for (Spatial child : children){
+ Spatial childClone = child.deepClone();
+ childClone.parent = nodeClone;
+ nodeClone.children.add(childClone);
+ }
+ return nodeClone;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ // XXX: Load children before loading itself!!
+ // This prevents empty children list if controls query
+ // it in Control.setSpatial().
+
+ children = new SafeArrayList( Spatial.class,
+ e.getCapsule(this).readSavableArrayList("children", null) );
+
+ // go through children and set parent to this node
+ if (children != null) {
+ for (Spatial child : children.getArray()) {
+ child.parent = this;
+ }
+ }
+
+ super.read(e);
+ }
+
+ @Override
+ public void setModelBound(BoundingVolume modelBound) {
+ if(children != null) {
+ for (Spatial child : children.getArray()) {
+ child.setModelBound(modelBound != null ? modelBound.clone(null) : null);
+ }
+ }
+ }
+
+ @Override
+ public void updateModelBound() {
+ if(children != null) {
+ for (Spatial child : children.getArray()) {
+ child.updateModelBound();
+ }
+ }
+ }
+
+ @Override
+ public void depthFirstTraversal(SceneGraphVisitor visitor) {
+ for (Spatial child : children.getArray()) {
+ child.depthFirstTraversal(visitor);
+ }
+ visitor.visit(this);
+ }
+
+ @Override
+ protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
+ queue.addAll(children);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitor.java b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
new file mode 100644
index 0000000..35cc115
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitor.java
@@ -0,0 +1,16 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public interface SceneGraphVisitor {
+ /**
+ * Called when a spatial is visited in the scene graph.
+ *
+ * @param spatial The visited spatial
+ */
+ public void visit(Spatial spatial);
+}
diff --git a/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
new file mode 100644
index 0000000..b91a8fb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SceneGraphVisitorAdapter.java
@@ -0,0 +1,35 @@
+package com.jme3.scene;
+
+/**
+ * <code>SceneGraphVisitorAdapter</code> is used to traverse the scene
+ * graph tree. The adapter version of the interface simply separates
+ * between the {@link Geometry geometries} and the {@link Node nodes} by
+ * supplying visit methods that take them.
+ * Use by calling {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor) }
+ * or {@link Spatial#breadthFirstTraversal(com.jme3.scene.SceneGraphVisitor)}.
+ */
+public class SceneGraphVisitorAdapter implements SceneGraphVisitor {
+
+ /**
+ * Called when a {@link Geometry} is visited.
+ *
+ * @param geom The visited geometry
+ */
+ public void visit(Geometry geom) {}
+
+ /**
+ * Called when a {@link visit} is visited.
+ *
+ * @param geom The visited node
+ */
+ public void visit(Node geom) {}
+
+ @Override
+ public final void visit(Spatial spatial) {
+ if (spatial instanceof Geometry) {
+ visit((Geometry)spatial);
+ } else {
+ visit((Node)spatial);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/SimpleBatchNode.java b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
new file mode 100644
index 0000000..0f1ed29
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/SimpleBatchNode.java
@@ -0,0 +1,56 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.scene;
+
+import com.jme3.math.Transform;
+
+/**
+ *
+ * SimpleBatchNode comes with some restrictions, but can yield better performances.
+ * Geometries to be batched has to be attached directly to the BatchNode
+ * You can't attach a Node to a SimpleBatchNode
+ * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure.
+ * @see BatchNode
+ * @author Nehon
+ */
+public class SimpleBatchNode extends BatchNode {
+
+ public SimpleBatchNode() {
+ super();
+ }
+
+ public SimpleBatchNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public int attachChild(Spatial child) {
+
+ if (!(child instanceof Geometry)) {
+ throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure");
+ }
+
+ return super.attachChild(child);
+ }
+
+ @Override
+ protected void setTransformRefresh() {
+
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ for (Batch batch : batches.values()) {
+ batch.geometry.setTransformRefresh();
+ }
+ }
+
+ protected Transform getTransforms(Geometry geom){
+ return geom.getLocalTransform();
+ }
+
+ @Override
+ public void batch() {
+ doBatch();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java
new file mode 100644
index 0000000..3785d6e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/Spatial.java
@@ -0,0 +1,1478 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.export.*;
+import com.jme3.light.Light;
+import com.jme3.light.LightList;
+import com.jme3.material.Material;
+import com.jme3.math.*;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.control.Control;
+import com.jme3.util.SafeArrayList;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * <code>Spatial</code> defines the base class for scene graph nodes. It
+ * maintains a link to a parent, it's local transforms and the world's
+ * transforms. All other scene graph elements, such as {@link Node} and
+ * {@link Geometry} are subclasses of <code>Spatial</code>.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Revision: 4075 $, $Data$
+ */
+public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
+
+ private static final Logger logger = Logger.getLogger(Spatial.class.getName());
+
+ /**
+ * Specifies how frustum culling should be handled by
+ * this spatial.
+ */
+ public enum CullHint {
+
+ /**
+ * Do whatever our parent does. If no parent, default to {@link #Dynamic}.
+ */
+ Inherit,
+ /**
+ * Do not draw if we are not at least partially within the view frustum
+ * of the camera. This is determined via the defined
+ * Camera planes whether or not this Spatial should be culled.
+ */
+ Dynamic,
+ /**
+ * Always cull this from the view, throwing away this object
+ * and any children from rendering commands.
+ */
+ Always,
+ /**
+ * Never cull this from view, always draw it.
+ * Note that we will still get culled if our parent is culled.
+ */
+ Never;
+ }
+
+ /**
+ * Specifies if this spatial should be batched
+ */
+ public enum BatchHint {
+
+ /**
+ * Do whatever our parent does. If no parent, default to {@link #Always}.
+ */
+ Inherit,
+ /**
+ * This spatial will always be batched when attached to a BatchNode.
+ */
+ Always,
+ /**
+ * This spatial will never be batched when attached to a BatchNode.
+ */
+ Never;
+ }
+ /**
+ * Refresh flag types
+ */
+ protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
+ RF_BOUND = 0x02,
+ RF_LIGHTLIST = 0x04; // changes in light lists
+ protected CullHint cullHint = CullHint.Inherit;
+ protected BatchHint batchHint = BatchHint.Inherit;
+ /**
+ * Spatial's bounding volume relative to the world.
+ */
+ protected BoundingVolume worldBound;
+ /**
+ * LightList
+ */
+ protected LightList localLights;
+ protected transient LightList worldLights;
+ /**
+ * This spatial's name.
+ */
+ protected String name;
+ // scale values
+ protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
+ protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
+ protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
+ public transient float queueDistance = Float.NEGATIVE_INFINITY;
+ protected Transform localTransform;
+ protected Transform worldTransform;
+ protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class);
+ protected HashMap<String, Savable> userData = null;
+ /**
+ * Used for smart asset caching
+ *
+ * @see AssetKey#useSmartCache()
+ */
+ protected AssetKey key;
+ /**
+ * Spatial's parent, or null if it has none.
+ */
+ protected transient Node parent;
+ /**
+ * Refresh flags. Indicate what data of the spatial need to be
+ * updated to reflect the correct state.
+ */
+ protected transient int refreshFlags = 0;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Spatial() {
+ localTransform = new Transform();
+ worldTransform = new Transform();
+
+ localLights = new LightList(this);
+ worldLights = new LightList(this);
+
+ refreshFlags |= RF_BOUND;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Spatial</code> object setting the
+ * rotation, translation and scale value to defaults.
+ *
+ * @param name
+ * the name of the scene element. This is required for
+ * identification and comparison purposes.
+ */
+ public Spatial(String name) {
+ this();
+ this.name = name;
+ }
+
+ public void setKey(AssetKey key) {
+ this.key = key;
+ }
+
+ public AssetKey getKey() {
+ return key;
+ }
+
+ /**
+ * Indicate that the transform of this spatial has changed and that
+ * a refresh is required.
+ */
+ protected void setTransformRefresh() {
+ refreshFlags |= RF_TRANSFORM;
+ setBoundRefresh();
+ }
+
+ protected void setLightListRefresh() {
+ refreshFlags |= RF_LIGHTLIST;
+ }
+
+ /**
+ * Indicate that the bounding of this spatial has changed and that
+ * a refresh is required.
+ */
+ protected void setBoundRefresh() {
+ refreshFlags |= RF_BOUND;
+
+ // XXX: Replace with a recursive call?
+ Spatial p = parent;
+ while (p != null) {
+ if ((p.refreshFlags & RF_BOUND) != 0) {
+ return;
+ }
+
+ p.refreshFlags |= RF_BOUND;
+ p = p.parent;
+ }
+ }
+
+ /**
+ * <code>checkCulling</code> checks the spatial with the camera to see if it
+ * should be culled.
+ * <p>
+ * This method is called by the renderer. Usually it should not be called
+ * directly.
+ *
+ * @param cam The camera to check against.
+ * @return true if inside or intersecting camera frustum
+ * (should be rendered), false if outside.
+ */
+ public boolean checkCulling(Camera cam) {
+ if (refreshFlags != 0) {
+ throw new IllegalStateException("Scene graph is not properly updated for rendering.\n"
+ + "State was changed after rootNode.updateGeometricState() call. \n"
+ + "Make sure you do not modify the scene from another thread!\n"
+ + "Problem spatial name: " + getName());
+ }
+
+ CullHint cm = getCullHint();
+ assert cm != CullHint.Inherit;
+ if (cm == Spatial.CullHint.Always) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
+ return false;
+ } else if (cm == Spatial.CullHint.Never) {
+ setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
+ return true;
+ }
+
+ // check to see if we can cull this node
+ frustrumIntersects = (parent != null ? parent.frustrumIntersects
+ : Camera.FrustumIntersect.Intersects);
+
+ if (frustrumIntersects == Camera.FrustumIntersect.Intersects) {
+ if (getQueueBucket() == Bucket.Gui) {
+ return cam.containsGui(getWorldBound());
+ } else {
+ frustrumIntersects = cam.contains(getWorldBound());
+ }
+ }
+
+ return frustrumIntersects != Camera.FrustumIntersect.Outside;
+ }
+
+ /**
+ * Sets the name of this spatial.
+ *
+ * @param name
+ * The spatial's new name.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the name of this spatial.
+ *
+ * @return This spatial's name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the local {@link LightList}, which are the lights
+ * that were directly attached to this <code>Spatial</code> through the
+ * {@link #addLight(com.jme3.light.Light) } and
+ * {@link #removeLight(com.jme3.light.Light) } methods.
+ *
+ * @return The local light list
+ */
+ public LightList getLocalLightList() {
+ return localLights;
+ }
+
+ /**
+ * Returns the world {@link LightList}, containing the lights
+ * combined from all this <code>Spatial's</code> parents up to and including
+ * this <code>Spatial</code>'s lights.
+ *
+ * @return The combined world light list
+ */
+ public LightList getWorldLightList() {
+ return worldLights;
+ }
+
+ /**
+ * <code>getWorldRotation</code> retrieves the absolute rotation of the
+ * Spatial.
+ *
+ * @return the Spatial's world rotation quaternion.
+ */
+ public Quaternion getWorldRotation() {
+ checkDoTransformUpdate();
+ return worldTransform.getRotation();
+ }
+
+ /**
+ * <code>getWorldTranslation</code> retrieves the absolute translation of
+ * the spatial.
+ *
+ * @return the Spatial's world tranlsation vector.
+ */
+ public Vector3f getWorldTranslation() {
+ checkDoTransformUpdate();
+ return worldTransform.getTranslation();
+ }
+
+ /**
+ * <code>getWorldScale</code> retrieves the absolute scale factor of the
+ * spatial.
+ *
+ * @return the Spatial's world scale factor.
+ */
+ public Vector3f getWorldScale() {
+ checkDoTransformUpdate();
+ return worldTransform.getScale();
+ }
+
+ /**
+ * <code>getWorldTransform</code> retrieves the world transformation
+ * of the spatial.
+ *
+ * @return the world transform.
+ */
+ public Transform getWorldTransform() {
+ checkDoTransformUpdate();
+ return worldTransform;
+ }
+
+ /**
+ * <code>rotateUpTo</code> is a utility function that alters the
+ * local rotation to point the Y axis in the direction given by newUp.
+ *
+ * @param newUp
+ * the up vector to use - assumed to be a unit vector.
+ */
+ public void rotateUpTo(Vector3f newUp) {
+ TempVars vars = TempVars.get();
+
+ Vector3f compVecA = vars.vect1;
+ Quaternion q = vars.quat1;
+
+ // First figure out the current up vector.
+ Vector3f upY = compVecA.set(Vector3f.UNIT_Y);
+ Quaternion rot = localTransform.getRotation();
+ rot.multLocal(upY);
+
+ // get angle between vectors
+ float angle = upY.angleBetween(newUp);
+
+ // figure out rotation axis by taking cross product
+ Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal();
+
+ // Build a rotation quat and apply current local rotation.
+ q.fromAngleNormalAxis(angle, rotAxis);
+ q.mult(rot, rot);
+
+ vars.release();
+
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>lookAt</code> is a convenience method for auto-setting the local
+ * rotation based on a position and an up vector. It computes the rotation
+ * to transform the z-axis to point onto 'position' and the y-axis to 'up'.
+ * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) }
+ * this method takes a world position to look at and not a relative direction.
+ *
+ * @param position
+ * where to look at in terms of world coordinates
+ * @param upVector
+ * a vector indicating the (local) up direction. (typically {0,
+ * 1, 0} in jME.)
+ */
+ public void lookAt(Vector3f position, Vector3f upVector) {
+ Vector3f worldTranslation = getWorldTranslation();
+
+ TempVars vars = TempVars.get();
+
+ Vector3f compVecA = vars.vect4;
+ vars.release();
+
+ compVecA.set(position).subtractLocal(worldTranslation);
+ getLocalRotation().lookAt(compVecA, upVector);
+
+ setTransformRefresh();
+ }
+
+ /**
+ * Should be overridden by Node and Geometry.
+ */
+ protected void updateWorldBound() {
+ // the world bound of a leaf is the same as it's model bound
+ // for a node, the world bound is a combination of all it's children
+ // bounds
+ // -> handled by subclass
+ refreshFlags &= ~RF_BOUND;
+ }
+
+ protected void updateWorldLightList() {
+ if (parent == null) {
+ worldLights.update(localLights, null);
+ refreshFlags &= ~RF_LIGHTLIST;
+ } else {
+ if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
+ worldLights.update(localLights, parent.worldLights);
+ refreshFlags &= ~RF_LIGHTLIST;
+ } else {
+ assert false;
+ }
+ }
+ }
+
+ /**
+ * Should only be called from updateGeometricState().
+ * In most cases should not be subclassed.
+ */
+ protected void updateWorldTransforms() {
+ if (parent == null) {
+ worldTransform.set(localTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ } else {
+ // check if transform for parent is updated
+ assert ((parent.refreshFlags & RF_TRANSFORM) == 0);
+ worldTransform.set(localTransform);
+ worldTransform.combineWithParent(parent.worldTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ }
+ }
+
+ /**
+ * Computes the world transform of this Spatial in the most
+ * efficient manner possible.
+ */
+ void checkDoTransformUpdate() {
+ if ((refreshFlags & RF_TRANSFORM) == 0) {
+ return;
+ }
+
+ if (parent == null) {
+ worldTransform.set(localTransform);
+ refreshFlags &= ~RF_TRANSFORM;
+ } else {
+ TempVars vars = TempVars.get();
+
+ Spatial[] stack = vars.spatialStack;
+ Spatial rootNode = this;
+ int i = 0;
+ while (true) {
+ Spatial hisParent = rootNode.parent;
+ if (hisParent == null) {
+ rootNode.worldTransform.set(rootNode.localTransform);
+ rootNode.refreshFlags &= ~RF_TRANSFORM;
+ i--;
+ break;
+ }
+
+ stack[i] = rootNode;
+
+ if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) {
+ break;
+ }
+
+ rootNode = hisParent;
+ i++;
+ }
+
+ vars.release();
+
+ for (int j = i; j >= 0; j--) {
+ rootNode = stack[j];
+ //rootNode.worldTransform.set(rootNode.localTransform);
+ //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform);
+ //rootNode.refreshFlags &= ~RF_TRANSFORM;
+ rootNode.updateWorldTransforms();
+ }
+ }
+ }
+
+ /**
+ * Computes this Spatial's world bounding volume in the most efficient
+ * manner possible.
+ */
+ void checkDoBoundUpdate() {
+ if ((refreshFlags & RF_BOUND) == 0) {
+ return;
+ }
+
+ checkDoTransformUpdate();
+
+ // Go to children recursively and update their bound
+ if (this instanceof Node) {
+ Node node = (Node) this;
+ int len = node.getQuantity();
+ for (int i = 0; i < len; i++) {
+ Spatial child = node.getChild(i);
+ child.checkDoBoundUpdate();
+ }
+ }
+
+ // All children's bounds have been updated. Update my own now.
+ updateWorldBound();
+ }
+
+ private void runControlUpdate(float tpf) {
+ if (controls.isEmpty()) {
+ return;
+ }
+
+ for (Control c : controls.getArray()) {
+ c.update(tpf);
+ }
+ }
+
+ /**
+ * Called when the Spatial is about to be rendered, to notify
+ * controls attached to this Spatial using the Control.render() method.
+ *
+ * @param rm The RenderManager rendering the Spatial.
+ * @param vp The ViewPort to which the Spatial is being rendered to.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#getControl(java.lang.Class)
+ */
+ public void runControlRender(RenderManager rm, ViewPort vp) {
+ if (controls.isEmpty()) {
+ return;
+ }
+
+ for (Control c : controls.getArray()) {
+ c.render(rm, vp);
+ }
+ }
+
+ /**
+ * Add a control to the list of controls.
+ * @param control The control to add.
+ *
+ * @see Spatial#removeControl(java.lang.Class)
+ */
+ public void addControl(Control control) {
+ controls.add(control);
+ control.setSpatial(this);
+ }
+
+ /**
+ * Removes the first control that is an instance of the given class.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public void removeControl(Class<? extends Control> controlType) {
+ for (int i = 0; i < controls.size(); i++) {
+ if (controlType.isAssignableFrom(controls.get(i).getClass())) {
+ Control control = controls.remove(i);
+ control.setSpatial(null);
+ }
+ }
+ }
+
+ /**
+ * Removes the given control from this spatial's controls.
+ *
+ * @param control The control to remove
+ * @return True if the control was successfuly removed. False if
+ * the control is not assigned to this spatial.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public boolean removeControl(Control control) {
+ boolean result = controls.remove(control);
+ if (result) {
+ control.setSpatial(null);
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the first control that is an instance of the given class,
+ * or null if no such control exists.
+ *
+ * @param controlType The superclass of the control to look for.
+ * @return The first instance in the list of the controlType class, or null.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public <T extends Control> T getControl(Class<T> controlType) {
+ for (Control c : controls.getArray()) {
+ if (controlType.isAssignableFrom(c.getClass())) {
+ return (T) c;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the control at the given index in the list.
+ *
+ * @param index The index of the control in the list to find.
+ * @return The control at the given index.
+ *
+ * @throws IndexOutOfBoundsException
+ * If the index is outside the range [0, getNumControls()-1]
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public Control getControl(int index) {
+ return controls.get(index);
+ }
+
+ /**
+ * @return The number of controls attached to this Spatial.
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ * @see Spatial#removeControl(java.lang.Class)
+ */
+ public int getNumControls() {
+ return controls.size();
+ }
+
+ /**
+ * <code>updateLogicalState</code> calls the <code>update()</code> method
+ * for all controls attached to this Spatial.
+ *
+ * @param tpf Time per frame.
+ *
+ * @see Spatial#addControl(com.jme3.scene.control.Control)
+ */
+ public void updateLogicalState(float tpf) {
+ runControlUpdate(tpf);
+ }
+
+ /**
+ * <code>updateGeometricState</code> updates the lightlist,
+ * computes the world transforms, and computes the world bounds
+ * for this Spatial.
+ * Calling this when the Spatial is attached to a node
+ * will cause undefined results. User code should only call this
+ * method on Spatials having no parent.
+ *
+ * @see Spatial#getWorldLightList()
+ * @see Spatial#getWorldTransform()
+ * @see Spatial#getWorldBound()
+ */
+ public void updateGeometricState() {
+ // assume that this Spatial is a leaf, a proper implementation
+ // for this method should be provided by Node.
+
+ // NOTE: Update world transforms first because
+ // bound transform depends on them.
+ if ((refreshFlags & RF_LIGHTLIST) != 0) {
+ updateWorldLightList();
+ }
+ if ((refreshFlags & RF_TRANSFORM) != 0) {
+ updateWorldTransforms();
+ }
+ if ((refreshFlags & RF_BOUND) != 0) {
+ updateWorldBound();
+ }
+
+ assert refreshFlags == 0;
+ }
+
+ /**
+ * Convert a vector (in) from this spatials' 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 Vector3f localToWorld(final Vector3f in, Vector3f store) {
+ checkDoTransformUpdate();
+ return worldTransform.transformVector(in, store);
+ }
+
+ /**
+ * Convert a vector (in) from world coordinate space to this spatials' local
+ * coordinate space.
+ *
+ * @param in
+ * vector to read from
+ * @param store
+ * where to write the result
+ * @return the result (store)
+ */
+ public Vector3f worldToLocal(final Vector3f in, final Vector3f store) {
+ checkDoTransformUpdate();
+ return worldTransform.transformInverseVector(in, store);
+ }
+
+ /**
+ * <code>getParent</code> retrieves 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(Node parent) {
+ this.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(Node ancestor) {
+ if (parent == null) {
+ return false;
+ } else if (parent.equals(ancestor)) {
+ return true;
+ } else {
+ return parent.hasAncestor(ancestor);
+ }
+ }
+
+ /**
+ * <code>getLocalRotation</code> retrieves the local rotation of this
+ * node.
+ *
+ * @return the local rotation of this node.
+ */
+ public Quaternion getLocalRotation() {
+ return localTransform.getRotation();
+ }
+
+ /**
+ * <code>setLocalRotation</code> sets the local rotation of this node
+ * by using a {@link Matrix3f}.
+ *
+ * @param rotation
+ * the new local rotation.
+ */
+ public void setLocalRotation(Matrix3f rotation) {
+ localTransform.getRotation().fromRotationMatrix(rotation);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalRotation</code> sets the local rotation of this node.
+ *
+ * @param quaternion
+ * the new local rotation.
+ */
+ public void setLocalRotation(Quaternion quaternion) {
+ localTransform.setRotation(quaternion);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalScale</code> retrieves the local scale of this node.
+ *
+ * @return the local scale of this node.
+ */
+ public Vector3f getLocalScale() {
+ return localTransform.getScale();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ *
+ * @param localScale
+ * the new local scale, applied to x, y and z
+ */
+ public void setLocalScale(float localScale) {
+ localTransform.setScale(localScale);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ */
+ public void setLocalScale(float x, float y, float z) {
+ localTransform.setScale(x, y, z);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalScale</code> sets the local scale of this node.
+ *
+ * @param localScale
+ * the new local scale.
+ */
+ public void setLocalScale(Vector3f localScale) {
+ localTransform.setScale(localScale);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalTranslation</code> retrieves the local translation of
+ * this node.
+ *
+ * @return the local translation of this node.
+ */
+ public Vector3f getLocalTranslation() {
+ return localTransform.getTranslation();
+ }
+
+ /**
+ * <code>setLocalTranslation</code> sets the local translation of this
+ * spatial.
+ *
+ * @param localTranslation
+ * the local translation of this spatial.
+ */
+ public void setLocalTranslation(Vector3f localTranslation) {
+ this.localTransform.setTranslation(localTranslation);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalTranslation</code> sets the local translation of this
+ * spatial.
+ */
+ public void setLocalTranslation(float x, float y, float z) {
+ this.localTransform.setTranslation(x, y, z);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>setLocalTransform</code> sets the local transform of this
+ * spatial.
+ */
+ public void setLocalTransform(Transform t) {
+ this.localTransform.set(t);
+ setTransformRefresh();
+ }
+
+ /**
+ * <code>getLocalTransform</code> retrieves the local transform of
+ * this spatial.
+ *
+ * @return the local transform of this spatial.
+ */
+ public Transform getLocalTransform() {
+ return localTransform;
+ }
+
+ /**
+ * Applies the given material to the Spatial, this will propagate the
+ * material down to the geometries in the scene graph.
+ *
+ * @param material The material to set.
+ */
+ public void setMaterial(Material material) {
+ }
+
+ /**
+ * <code>addLight</code> adds the given light to the Spatial; causing
+ * all child Spatials to be effected by it.
+ *
+ * @param light The light to add.
+ */
+ public void addLight(Light light) {
+ localLights.add(light);
+ setLightListRefresh();
+ }
+
+ /**
+ * <code>removeLight</code> removes the given light from the Spatial.
+ *
+ * @param light The light to remove.
+ * @see Spatial#addLight(com.jme3.light.Light)
+ */
+ public void removeLight(Light light) {
+ localLights.remove(light);
+ setLightListRefresh();
+ }
+
+ /**
+ * Translates the spatial by the given translation vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial move(float x, float y, float z) {
+ this.localTransform.getTranslation().addLocal(x, y, z);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Translates the spatial by the given translation vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial move(Vector3f offset) {
+ this.localTransform.getTranslation().addLocal(offset);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Scales the spatial by the given value
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial scale(float s) {
+ return scale(s, s, s);
+ }
+
+ /**
+ * Scales the spatial by the given scale vector.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial scale(float x, float y, float z) {
+ this.localTransform.getScale().multLocal(x, y, z);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Rotates the spatial by the given rotation.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial rotate(Quaternion rot) {
+ this.localTransform.getRotation().multLocal(rot);
+ setTransformRefresh();
+
+ return this;
+ }
+
+ /**
+ * Rotates the spatial by the yaw, roll and pitch angles (in radians),
+ * in the local coordinate space.
+ *
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial rotate(float yaw, float roll, float pitch) {
+ TempVars vars = TempVars.get();
+ Quaternion q = vars.quat1;
+ q.fromAngles(yaw, roll, pitch);
+ rotate(q);
+ vars.release();
+
+ return this;
+ }
+
+ /**
+ * Centers the spatial in the origin of the world bound.
+ * @return The spatial on which this method is called, e.g <code>this</code>.
+ */
+ public Spatial center() {
+ Vector3f worldTrans = getWorldTranslation();
+ Vector3f worldCenter = getWorldBound().getCenter();
+
+ Vector3f absTrans = worldTrans.subtract(worldCenter);
+ setLocalTranslation(absTrans);
+
+ return this;
+ }
+
+ /**
+ * @see #setCullHint(CullHint)
+ * @return the cull mode of this spatial, or if set to CullHint.Inherit,
+ * the cullmode of it's parent.
+ */
+ public CullHint getCullHint() {
+ if (cullHint != CullHint.Inherit) {
+ return cullHint;
+ } else if (parent != null) {
+ return parent.getCullHint();
+ } else {
+ return CullHint.Dynamic;
+ }
+ }
+
+ public BatchHint getBatchHint() {
+ if (batchHint != BatchHint.Inherit) {
+ return batchHint;
+ } else if (parent != null) {
+ return parent.getBatchHint();
+ } else {
+ return BatchHint.Always;
+ }
+ }
+
+ /**
+ * Returns this spatial's renderqueue bucket. If the mode is set to inherit,
+ * then the spatial gets its renderqueue bucket from its parent.
+ *
+ * @return The spatial's current renderqueue mode.
+ */
+ public RenderQueue.Bucket getQueueBucket() {
+ if (queueBucket != RenderQueue.Bucket.Inherit) {
+ return queueBucket;
+ } else if (parent != null) {
+ return parent.getQueueBucket();
+ } else {
+ return RenderQueue.Bucket.Opaque;
+ }
+ }
+
+ /**
+ * @return The shadow mode of this spatial, if the local shadow
+ * mode is set to inherit, then the parent's shadow mode is returned.
+ *
+ * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+ * @see ShadowMode
+ */
+ public RenderQueue.ShadowMode getShadowMode() {
+ if (shadowMode != RenderQueue.ShadowMode.Inherit) {
+ return shadowMode;
+ } else if (parent != null) {
+ return parent.getShadowMode();
+ } else {
+ return ShadowMode.Off;
+ }
+ }
+
+ /**
+ * Sets the level of detail to use when rendering this Spatial,
+ * this call propagates to all geometries under this Spatial.
+ *
+ * @param lod The lod level to set.
+ */
+ public void setLodLevel(int lod) {
+ }
+
+ /**
+ * <code>updateModelBound</code> recalculates the bounding object for this
+ * Spatial.
+ */
+ public abstract void updateModelBound();
+
+ /**
+ * <code>setModelBound</code> sets the bounding object for this Spatial.
+ *
+ * @param modelBound
+ * the bounding object for this spatial.
+ */
+ public abstract void setModelBound(BoundingVolume modelBound);
+
+ /**
+ * @return The sum of all verticies under this Spatial.
+ */
+ public abstract int getVertexCount();
+
+ /**
+ * @return The sum of all triangles under this Spatial.
+ */
+ public abstract int getTriangleCount();
+
+ /**
+ * @return A clone of this Spatial, the scene graph in its entirety
+ * is cloned and can be altered independently of the original scene graph.
+ *
+ * Note that meshes of geometries are not cloned explicitly, they
+ * are shared if static, or specially cloned if animated.
+ *
+ * All controls will be cloned using the Control.cloneForSpatial method
+ * on the clone.
+ *
+ * @see Mesh#cloneForAnim()
+ */
+ public Spatial clone(boolean cloneMaterial) {
+ try {
+ Spatial clone = (Spatial) super.clone();
+ if (worldBound != null) {
+ clone.worldBound = worldBound.clone();
+ }
+ clone.worldLights = worldLights.clone();
+ clone.localLights = localLights.clone();
+
+ // Set the new owner of the light lists
+ clone.localLights.setOwner(clone);
+ clone.worldLights.setOwner(clone);
+
+ // No need to force cloned to update.
+ // This node already has the refresh flags
+ // set below so it will have to update anyway.
+ clone.worldTransform = worldTransform.clone();
+ clone.localTransform = localTransform.clone();
+
+ if (clone instanceof Node) {
+ Node node = (Node) this;
+ Node nodeClone = (Node) clone;
+ nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
+ for (Spatial child : node.children) {
+ Spatial childClone = child.clone(cloneMaterial);
+ childClone.parent = nodeClone;
+ nodeClone.children.add(childClone);
+ }
+ }
+
+ clone.parent = null;
+ clone.setBoundRefresh();
+ clone.setTransformRefresh();
+ clone.setLightListRefresh();
+
+ clone.controls = new SafeArrayList<Control>(Control.class);
+ for (int i = 0; i < controls.size(); i++) {
+ clone.controls.add(controls.get(i).cloneForSpatial(clone));
+ }
+
+ if (userData != null) {
+ clone.userData = (HashMap<String, Savable>) userData.clone();
+ }
+
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * @return A clone of this Spatial, the scene graph in its entirety
+ * is cloned and can be altered independently of the original scene graph.
+ *
+ * Note that meshes of geometries are not cloned explicitly, they
+ * are shared if static, or specially cloned if animated.
+ *
+ * All controls will be cloned using the Control.cloneForSpatial method
+ * on the clone.
+ *
+ * @see Mesh#cloneForAnim()
+ */
+ @Override
+ public Spatial clone() {
+ return clone(true);
+ }
+
+ /**
+ * @return Similar to Spatial.clone() except will create a deep clone
+ * of all geometry's meshes, normally this method shouldn't be used
+ * instead use Spatial.clone()
+ *
+ * @see Spatial#clone()
+ */
+ public abstract Spatial deepClone();
+
+ public void setUserData(String key, Object data) {
+ if (userData == null) {
+ userData = new HashMap<String, Savable>();
+ }
+
+ if (data instanceof Savable) {
+ userData.put(key, (Savable) data);
+ } else {
+ userData.put(key, new UserData(UserData.getObjectType(data), data));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getUserData(String key) {
+ if (userData == null) {
+ return null;
+ }
+
+ Savable s = userData.get(key);
+ if (s instanceof UserData) {
+ return (T) ((UserData) s).getValue();
+ } else {
+ return (T) s;
+ }
+ }
+
+ public Collection<String> getUserDataKeys() {
+ if (userData != null) {
+ return userData.keySet();
+ }
+
+ return Collections.EMPTY_SET;
+ }
+
+ /**
+ * Note that we are <i>matching</i> the pattern, therefore the pattern
+ * must match the entire pattern (i.e. it behaves as if it is sandwiched
+ * between "^" and "$").
+ * You can set regex modes, like case insensitivity, by using the (?X)
+ * or (?X:Y) constructs.
+ *
+ * @param spatialSubclass Subclass which this must implement.
+ * Null causes all Spatials to qualify.
+ * @param nameRegex Regular expression to match this name against.
+ * Null causes all Names to qualify.
+ * @return true if this implements the specified class and this's name
+ * matches the specified pattern.
+ *
+ * @see java.util.regex.Pattern
+ */
+ public boolean matches(Class<? extends Spatial> spatialSubclass,
+ String nameRegex) {
+ if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
+ return false;
+ }
+
+ if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule capsule = ex.getCapsule(this);
+ capsule.write(name, "name", null);
+ capsule.write(worldBound, "world_bound", null);
+ capsule.write(cullHint, "cull_mode", CullHint.Inherit);
+ capsule.write(batchHint, "batch_hint", BatchHint.Inherit);
+ capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit);
+ capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit);
+ capsule.write(localTransform, "transform", Transform.IDENTITY);
+ capsule.write(localLights, "lights", null);
+
+ // Shallow clone the controls array to convert its type.
+ capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null);
+ capsule.writeStringSavableMap(userData, "user_data", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+
+ name = ic.readString("name", null);
+ worldBound = (BoundingVolume) ic.readSavable("world_bound", null);
+ cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit);
+ batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit);
+ queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class,
+ RenderQueue.Bucket.Inherit);
+ shadowMode = ic.readEnum("shadow_mode", ShadowMode.class,
+ ShadowMode.Inherit);
+
+ localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY);
+
+ localLights = (LightList) ic.readSavable("lights", null);
+ localLights.setOwner(this);
+
+ //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
+ //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
+ //The SkeletonControl must be the last in the stack so we add the list of all other control before it.
+ //When backward compatibility won't be needed anymore this can be replaced by :
+ //controls = ic.readSavableArrayList("controlsList", null));
+ controls.addAll(0, ic.readSavableArrayList("controlsList", null));
+
+ userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
+ }
+
+ /**
+ * <code>getWorldBound</code> retrieves the world bound at this node
+ * level.
+ *
+ * @return the world bound at this level.
+ */
+ public BoundingVolume getWorldBound() {
+ checkDoBoundUpdate();
+ return worldBound;
+ }
+
+ /**
+ * <code>setCullHint</code> sets how scene culling should work on this
+ * spatial during drawing. 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(CullHint hint) {
+ cullHint = hint;
+ }
+
+ /**
+ * <code>setBatchHint</code> sets how batching should work on this
+ * spatial. 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 BatchHint.Never, BatchHint.Always, BatchHint.Inherit
+ */
+ public void setBatchHint(BatchHint hint) {
+ batchHint = hint;
+ }
+
+ /**
+ * @return the cullmode set on this Spatial
+ */
+ public CullHint getLocalCullHint() {
+ return cullHint;
+ }
+
+ /**
+ * @return the batchHint set on this Spatial
+ */
+ public BatchHint getLocalBatchHint() {
+ return batchHint;
+ }
+
+ /**
+ * <code>setQueueBucket</code> determines at what phase of the
+ * rendering process this Spatial will rendered. See the
+ * {@link Bucket} enum for an explanation of the various
+ * render queue buckets.
+ *
+ * @param queueBucket
+ * The bucket to use for this Spatial.
+ */
+ public void setQueueBucket(RenderQueue.Bucket queueBucket) {
+ this.queueBucket = queueBucket;
+ }
+
+ /**
+ * Sets the shadow mode of the spatial
+ * The shadow mode determines how the spatial should be shadowed,
+ * when a shadowing technique is used. See the
+ * documentation for the class {@link ShadowMode} for more information.
+ *
+ * @see ShadowMode
+ *
+ * @param shadowMode The local shadow mode to set.
+ */
+ public void setShadowMode(RenderQueue.ShadowMode shadowMode) {
+ this.shadowMode = shadowMode;
+ }
+
+ /**
+ * @return The locally set queue bucket mode
+ *
+ * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket)
+ */
+ public RenderQueue.Bucket getLocalQueueBucket() {
+ return queueBucket;
+ }
+
+ /**
+ * @return The locally set shadow mode
+ *
+ * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode)
+ */
+ public RenderQueue.ShadowMode getLocalShadowMode() {
+ return shadowMode;
+ }
+
+ /**
+ * 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 getLastFrustumIntersection() {
+ return frustrumIntersects;
+ }
+
+ /**
+ * 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.
+ *
+ * @param intersects
+ * the new value
+ */
+ public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) {
+ frustrumIntersects = intersects;
+ }
+
+ /**
+ * Returns the Spatial's name followed by the class of the spatial <br>
+ * Example: "MyNode (com.jme3.scene.Spatial)
+ *
+ * @return Spatial's name followed by the class of the Spatial
+ */
+ @Override
+ public String toString() {
+ return name + " (" + this.getClass().getSimpleName() + ')';
+ }
+
+ /**
+ * Creates a transform matrix that will convert from this spatials'
+ * local coordinate space to the world coordinate space
+ * based on the world transform.
+ *
+ * @param store Matrix where to store the result, if null, a new one
+ * will be created and returned.
+ *
+ * @return store if not null, otherwise, a new matrix containing the result.
+ *
+ * @see Spatial#getWorldTransform()
+ */
+ public Matrix4f getLocalToWorldMatrix(Matrix4f store) {
+ if (store == null) {
+ store = new Matrix4f();
+ } else {
+ store.loadIdentity();
+ }
+ // multiply with scale first, then rotate, finally translate (cf.
+ // Eberly)
+ store.scale(getWorldScale());
+ store.multLocal(getWorldRotation());
+ store.setTranslation(getWorldTranslation());
+ return store;
+ }
+
+ /**
+ * Visit each scene graph element ordered by DFS
+ * @param visitor
+ */
+ public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
+
+ /**
+ * Visit each scene graph element ordered by BFS
+ * @param visitor
+ */
+ public void breadthFirstTraversal(SceneGraphVisitor visitor) {
+ Queue<Spatial> queue = new LinkedList<Spatial>();
+ queue.add(this);
+
+ while (!queue.isEmpty()) {
+ Spatial s = queue.poll();
+ visitor.visit(s);
+ s.breadthFirstTraversal(visitor, queue);
+ }
+ }
+
+ protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
+}
diff --git a/engine/src/core/com/jme3/scene/UserData.java b/engine/src/core/com/jme3/scene/UserData.java
new file mode 100644
index 0000000..855a31c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/UserData.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+/**
+ * <code>UserData</code> is used to contain user data objects
+ * set on spatials (primarily primitives) that do not implement
+ * the {@link Savable} interface. Note that attempting
+ * to export any models which have non-savable objects
+ * attached to them will fail.
+ */
+public final class UserData implements Savable {
+
+ /**
+ * Boolean type on Geometries to indicate that physics collision
+ * shape generation should ignore them.
+ */
+ public static final String JME_PHYSICSIGNORE = "JmePhysicsIgnore";
+
+ /**
+ * For geometries using shared mesh, this will specify the shared
+ * mesh reference.
+ */
+ public static final String JME_SHAREDMESH = "JmeSharedMesh";
+
+ protected byte type;
+ protected Object value;
+
+ public UserData() {
+ }
+
+ /**
+ * Creates a new <code>UserData</code> with the given
+ * type and value.
+ *
+ * @param type Type of data, should be between 0 and 4.
+ * @param value Value of the data
+ */
+ public UserData(byte type, Object value) {
+ assert type >= 0 && type <= 4;
+ this.type = type;
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ public static byte getObjectType(Object type) {
+ if (type instanceof Integer) {
+ return 0;
+ } else if (type instanceof Float) {
+ return 1;
+ } else if (type instanceof Boolean) {
+ return 2;
+ } else if (type instanceof String) {
+ return 3;
+ } else if (type instanceof Long) {
+ return 4;
+ } else {
+ throw new IllegalArgumentException("Unsupported type: " + type.getClass().getName());
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(type, "type", (byte)0);
+
+ switch (type) {
+ case 0:
+ int i = (Integer) value;
+ oc.write(i, "intVal", 0);
+ break;
+ case 1:
+ float f = (Float) value;
+ oc.write(f, "floatVal", 0f);
+ break;
+ case 2:
+ boolean b = (Boolean) value;
+ oc.write(b, "boolVal", false);
+ break;
+ case 3:
+ String s = (String) value;
+ oc.write(s, "strVal", null);
+ break;
+ case 4:
+ Long l = (Long) value;
+ oc.write(l, "longVal", 0l);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ type = ic.readByte("type", (byte) 0);
+
+ switch (type) {
+ case 0:
+ value = ic.readInt("intVal", 0);
+ break;
+ case 1:
+ value = ic.readFloat("floatVal", 0f);
+ break;
+ case 2:
+ value = ic.readBoolean("boolVal", false);
+ break;
+ case 3:
+ value = ic.readString("strVal", null);
+ break;
+ case 4:
+ value = ic.readLong("longVal", 0l);
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/VertexBuffer.java b/engine/src/core/com/jme3/scene/VertexBuffer.java
new file mode 100644
index 0000000..1a844bb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/VertexBuffer.java
@@ -0,0 +1,1025 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene;
+
+import com.jme3.export.*;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.nio.*;
+
+/**
+ * A <code>VertexBuffer</code> contains a particular type of geometry
+ * data used by {@link Mesh}es. Every VertexBuffer set on a <code>Mesh</code>
+ * is sent as an attribute to the vertex shader to be processed.
+ * <p>
+ * Several terms are used throughout the javadoc for this class, explanation:
+ * <ul>
+ * <li>Element - A single element is the largest individual object
+ * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position
+ * data, then an element will be a single 3D vector.</li>
+ * <li>Component - A component represents the parts inside an element.
+ * For a 3D vector, a single component is one of the dimensions, X, Y or Z.</li>
+ * </ul>
+ */
+public class VertexBuffer extends NativeObject implements Savable, Cloneable {
+
+ /**
+ * Type of buffer. Specifies the actual attribute it defines.
+ */
+ public static enum Type {
+ /**
+ * Position of the vertex (3 floats)
+ */
+ Position,
+
+ /**
+ * The size of the point when using point buffers (float).
+ */
+ Size,
+
+ /**
+ * Normal vector, normalized (3 floats).
+ */
+ Normal,
+
+ /**
+ * Texture coordinate (2 float)
+ */
+ TexCoord,
+
+ /**
+ * Color and Alpha (4 floats)
+ */
+ Color,
+
+ /**
+ * Tangent vector, normalized (4 floats) (x,y,z,w)
+ * the w component is called the binormal parity, is not normalized and is either 1f or -1f
+ * It's used to compuste the direction on the binormal verctor on the GPU at render time.
+ */
+ Tangent,
+
+ /**
+ * Binormal vector, normalized (3 floats, optional)
+ */
+ Binormal,
+
+ /**
+ * Specifies the source data for various vertex buffers
+ * when interleaving is used. By default the format is
+ * byte.
+ */
+ InterleavedData,
+
+ /**
+ * Do not use.
+ */
+ @Deprecated
+ MiscAttrib,
+
+ /**
+ * Specifies the index buffer, must contain integer data
+ * (ubyte, ushort, or uint).
+ */
+ Index,
+
+ /**
+ * Initial vertex position, used with animation.
+ * Should have the same format and size as {@link Type#Position}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPosePosition,
+
+ /**
+ * Initial vertex normals, used with animation.
+ * Should have the same format and size as {@link Type#Normal}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPoseNormal,
+
+ /**
+ * Bone weights, used with animation (4 floats).
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BoneWeight,
+
+ /**
+ * Bone indices, used with animation (4 ubytes).
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BoneIndex,
+
+ /**
+ * Texture coordinate #2
+ */
+ TexCoord2,
+
+ /**
+ * Texture coordinate #3
+ */
+ TexCoord3,
+
+ /**
+ * Texture coordinate #4
+ */
+ TexCoord4,
+
+ /**
+ * Texture coordinate #5
+ */
+ TexCoord5,
+
+ /**
+ * Texture coordinate #6
+ */
+ TexCoord6,
+
+ /**
+ * Texture coordinate #7
+ */
+ TexCoord7,
+
+ /**
+ * Texture coordinate #8
+ */
+ TexCoord8,
+
+ /**
+ * Initial vertex tangents, used with animation.
+ * Should have the same format and size as {@link Type#Tangent}.
+ * If used with software skinning, the usage should be
+ * {@link Usage#CpuOnly}, and the buffer should be allocated
+ * on the heap.
+ */
+ BindPoseTangent,
+ }
+
+ /**
+ * The usage of the VertexBuffer, specifies how often the buffer
+ * is used. This can determine if a vertex buffer is placed in VRAM
+ * or held in video memory, but no guarantees are made- it's only a hint.
+ */
+ public static enum Usage {
+
+ /**
+ * Mesh data is sent once and very rarely updated.
+ */
+ Static,
+
+ /**
+ * Mesh data is updated occasionally (once per frame or less).
+ */
+ Dynamic,
+
+ /**
+ * Mesh data is updated every frame.
+ */
+ Stream,
+
+ /**
+ * Mesh data is <em>not</em> sent to GPU at all. It is only
+ * used by the CPU.
+ */
+ CpuOnly;
+ }
+
+ /**
+ * Specifies format of the data stored in the buffer.
+ * This should directly correspond to the buffer's class, for example,
+ * an {@link Format#UnsignedShort} formatted buffer should use the
+ * class {@link ShortBuffer} (e.g. the closest resembling type).
+ * For the {@link Format#Half} type, {@link ByteBuffer}s should
+ * be used.
+ */
+ public static enum Format {
+ /**
+ * Half precision floating point.
+ * 2 bytes, signed.
+ */
+ Half(2),
+
+ /**
+ * Single precision floating point.
+ * 4 bytes, signed
+ */
+ Float(4),
+
+ /**
+ * Double precision floating point.
+ * 8 bytes, signed. May not
+ * be supported by all GPUs.
+ */
+ Double(8),
+
+ /**
+ * 1 byte integer, signed.
+ */
+ Byte(1),
+
+ /**
+ * 1 byte integer, unsigned.
+ */
+ UnsignedByte(1),
+
+ /**
+ * 2 byte integer, signed.
+ */
+ Short(2),
+
+ /**
+ * 2 byte integer, unsigned.
+ */
+ UnsignedShort(2),
+
+ /**
+ * 4 byte integer, signed.
+ */
+ Int(4),
+
+ /**
+ * 4 byte integer, unsigned.
+ */
+ UnsignedInt(4);
+
+ private int componentSize = 0;
+
+ Format(int componentSize){
+ this.componentSize = componentSize;
+ }
+
+ /**
+ * Returns the size in bytes of this data type.
+ *
+ * @return Size in bytes of this data type.
+ */
+ public int getComponentSize(){
+ return componentSize;
+ }
+ }
+
+ protected int offset = 0;
+ protected int lastLimit = 0;
+ protected int stride = 0;
+ protected int components = 0;
+
+ /**
+ * derived from components * format.getComponentSize()
+ */
+ protected transient int componentsLength = 0;
+ protected Buffer data = null;
+ protected Usage usage;
+ protected Type bufType;
+ protected Format format;
+ protected boolean normalized = false;
+ protected transient boolean dataSizeChanged = false;
+
+ /**
+ * Creates an empty, uninitialized buffer.
+ * Must call setupData() to initialize.
+ */
+ public VertexBuffer(Type type){
+ super(VertexBuffer.class);
+ this.bufType = type;
+ }
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public VertexBuffer(){
+ super(VertexBuffer.class);
+ }
+
+ protected VertexBuffer(int id){
+ super(VertexBuffer.class, id);
+ }
+
+ /**
+ * @return The offset after which the data is sent to the GPU.
+ *
+ * @see #setOffset(int)
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * @param offset Specify the offset (in bytes) from the start of the buffer
+ * after which the data is sent to the GPU.
+ */
+ public void setOffset(int offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * @return The stride (in bytes) for the data.
+ *
+ * @see #setStride(int)
+ */
+ public int getStride() {
+ return stride;
+ }
+
+ /**
+ * Set the stride (in bytes) for the data.
+ * <p>
+ * If the data is packed in the buffer, then stride is 0, if there's other
+ * data that is between the current component and the next component in the
+ * buffer, then this specifies the size in bytes of that additional data.
+ *
+ * @param stride the stride (in bytes) for the data
+ */
+ public void setStride(int stride) {
+ this.stride = stride;
+ }
+
+ /**
+ * Returns the raw internal data buffer used by this VertexBuffer.
+ * This buffer is not safe to call from multiple threads since buffers
+ * have their own internal position state that cannot be shared.
+ * Call getData().duplicate(), getData().asReadOnlyBuffer(), or
+ * the more convenient getDataReadOnly() if the buffer may be accessed
+ * from multiple threads.
+ *
+ * @return A native buffer, in the specified {@link Format format}.
+ */
+ public Buffer getData(){
+ return data;
+ }
+
+ /**
+ * Returns a safe read-only version of this VertexBuffer's data. The
+ * contents of the buffer will reflect whatever changes are made on
+ * other threads (eventually) but these should not be used in that way.
+ * This method provides a read-only buffer that is safe to _read_ from
+ * a separate thread since it has its own book-keeping state (position, limit, etc.)
+ *
+ * @return A rewound native buffer in the specified {@link Format format}
+ * that is safe to read from a separate thread from other readers.
+ */
+ public Buffer getDataReadOnly() {
+
+ if (data == null) {
+ return null;
+ }
+
+ // Create a read-only duplicate(). Note: this does not copy
+ // the underlying memory, it just creates a new read-only wrapper
+ // with its own buffer position state.
+
+ // Unfortunately, this is not 100% straight forward since Buffer
+ // does not have an asReadOnlyBuffer() method.
+ Buffer result;
+ if( data instanceof ByteBuffer ) {
+ result = ((ByteBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof FloatBuffer ) {
+ result = ((FloatBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof ShortBuffer ) {
+ result = ((ShortBuffer)data).asReadOnlyBuffer();
+ } else if( data instanceof IntBuffer ) {
+ result = ((IntBuffer)data).asReadOnlyBuffer();
+ } else {
+ throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data );
+ }
+
+ // Make sure the caller gets a consistent view since we may
+ // have grabbed this buffer while another thread was reading
+ // the raw data.
+ result.rewind();
+
+ return result;
+ }
+
+ /**
+ * @return The usage of this buffer. See {@link Usage} for more
+ * information.
+ */
+ public Usage getUsage(){
+ return usage;
+ }
+
+ /**
+ * @param usage The usage of this buffer. See {@link Usage} for more
+ * information.
+ */
+ public void setUsage(Usage usage){
+// if (id != -1)
+// throw new UnsupportedOperationException("Data has already been sent. Cannot set usage.");
+
+ this.usage = usage;
+ }
+
+ /**
+ * @param normalized Set to true if integer components should be converted
+ * from their maximal range into the range 0.0 - 1.0 when converted to
+ * a floating-point value for the shader.
+ * E.g. if the {@link Format} is {@link Format#UnsignedInt}, then
+ * the components will be converted to the range 0.0 - 1.0 by dividing
+ * every integer by 2^32.
+ */
+ public void setNormalized(boolean normalized){
+ this.normalized = normalized;
+ }
+
+ /**
+ * @return True if integer components should be converted to the range 0-1.
+ * @see VertexBuffer#setNormalized(boolean)
+ */
+ public boolean isNormalized(){
+ return normalized;
+ }
+
+ /**
+ * @return The type of information that this buffer has.
+ */
+ public Type getBufferType(){
+ return bufType;
+ }
+
+ /**
+ * @return The {@link Format format}, or data type of the data.
+ */
+ public Format getFormat(){
+ return format;
+ }
+
+ /**
+ * @return The number of components of the given {@link Format format} per
+ * element.
+ */
+ public int getNumComponents(){
+ return components;
+ }
+
+ /**
+ * @return The total number of data elements in the data buffer.
+ */
+ public int getNumElements(){
+ int elements = data.capacity() / components;
+ if (format == Format.Half)
+ elements /= 2;
+ return elements;
+ }
+
+ /**
+ * Called to initialize the data in the <code>VertexBuffer</code>. Must only
+ * be called once.
+ *
+ * @param usage The usage for the data, or how often will the data
+ * be updated per frame. See the {@link Usage} enum.
+ * @param components The number of components per element.
+ * @param format The {@link Format format}, or data-type of a single
+ * component.
+ * @param data A native buffer, the format of which matches the {@link Format}
+ * argument.
+ */
+ public void setupData(Usage usage, int components, Format format, Buffer data){
+ if (id != -1)
+ throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again.");
+
+ if (usage == null || format == null || data == null)
+ throw new IllegalArgumentException("None of the arguments can be null");
+
+ if (data.isReadOnly())
+ throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+
+ if (components < 1 || components > 4)
+ throw new IllegalArgumentException("components must be between 1 and 4");
+
+ this.data = data;
+ this.components = components;
+ this.usage = usage;
+ this.format = format;
+ this.componentsLength = components * format.getComponentSize();
+ this.lastLimit = data.limit();
+ setUpdateNeeded();
+ }
+
+ /**
+ * Called to update the data in the buffer with new data. Can only
+ * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) }
+ * has been called. Note that it is fine to call this method on the
+ * data already set, e.g. vb.updateData(vb.getData()), this will just
+ * set the proper update flag indicating the data should be sent to the GPU
+ * again.
+ * It is allowed to specify a buffer with different capacity than the
+ * originally set buffer.
+ *
+ * @param data The data buffer to set
+ */
+ public void updateData(Buffer data){
+ if (id != -1){
+ // request to update data is okay
+ }
+
+ // Check if the data buffer is read-only which is a sign
+ // of a bug on the part of the caller
+ if (data != null && data.isReadOnly()) {
+ throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
+ }
+
+ // will force renderer to call glBufferData again
+ if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){
+ dataSizeChanged = true;
+ lastLimit = data.limit();
+ }
+
+ this.data = data;
+ setUpdateNeeded();
+ }
+
+ /**
+ * Returns true if the data size of the VertexBuffer has changed.
+ * Internal use only.
+ * @return true if the data size has changed
+ */
+ public boolean hasDataSizeChanged() {
+ return dataSizeChanged;
+ }
+
+ @Override
+ public void clearUpdateNeeded(){
+ super.clearUpdateNeeded();
+ dataSizeChanged = false;
+ }
+
+ /**
+ * Converts single floating-point data to {@link Format#Half half} floating-point data.
+ */
+ public void convertToHalf(){
+ if (id != -1)
+ throw new UnsupportedOperationException("Data has already been sent.");
+
+ if (format != Format.Float)
+ throw new IllegalStateException("Format must be float!");
+
+ int numElements = data.capacity() / components;
+ format = Format.Half;
+ this.componentsLength = components * format.getComponentSize();
+
+ ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements);
+ halfData.rewind();
+
+ FloatBuffer floatData = (FloatBuffer) data;
+ floatData.rewind();
+
+ for (int i = 0; i < floatData.capacity(); i++){
+ float f = floatData.get(i);
+ short half = FastMath.convertFloatToHalf(f);
+ halfData.putShort(half);
+ }
+ this.data = halfData;
+ setUpdateNeeded();
+ dataSizeChanged = true;
+ }
+
+ /**
+ * Reduces the capacity of the buffer to the given amount
+ * of elements, any elements at the end of the buffer are truncated
+ * as necessary.
+ *
+ * @param numElements The number of elements to reduce to.
+ */
+ public void compact(int numElements){
+ int total = components * numElements;
+ data.clear();
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bbuf = (ByteBuffer) data;
+ bbuf.limit(total);
+ ByteBuffer bnewBuf = BufferUtils.createByteBuffer(total);
+ bnewBuf.put(bbuf);
+ data = bnewBuf;
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sbuf = (ShortBuffer) data;
+ sbuf.limit(total);
+ ShortBuffer snewBuf = BufferUtils.createShortBuffer(total);
+ snewBuf.put(sbuf);
+ data = snewBuf;
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer ibuf = (IntBuffer) data;
+ ibuf.limit(total);
+ IntBuffer inewBuf = BufferUtils.createIntBuffer(total);
+ inewBuf.put(ibuf);
+ data = inewBuf;
+ break;
+ case Float:
+ FloatBuffer fbuf = (FloatBuffer) data;
+ fbuf.limit(total);
+ FloatBuffer fnewBuf = BufferUtils.createFloatBuffer(total);
+ fnewBuf.put(fbuf);
+ data = fnewBuf;
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ data.clear();
+ setUpdateNeeded();
+ dataSizeChanged = true;
+ }
+
+ /**
+ * Modify a component inside an element.
+ * The <code>val</code> parameter must be in the buffer's format:
+ * {@link Format}.
+ *
+ * @param elementIndex The element index to modify
+ * @param componentIndex The component index to modify
+ * @param val The value to set, either byte, short, int or float depending
+ * on the {@link Format}.
+ */
+ public void setElementComponent(int elementIndex, int componentIndex, Object val){
+ int inPos = elementIndex * components;
+ int elementPos = componentIndex;
+
+ if (format == Format.Half){
+ inPos *= 2;
+ elementPos *= 2;
+ }
+
+ data.clear();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) data;
+ bin.put(inPos + elementPos, (Byte)val);
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) data;
+ sin.put(inPos + elementPos, (Short)val);
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) data;
+ iin.put(inPos + elementPos, (Integer)val);
+ break;
+ case Float:
+ FloatBuffer fin = (FloatBuffer) data;
+ fin.put(inPos + elementPos, (Float)val);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Get the component inside an element.
+ *
+ * @param elementIndex The element index
+ * @param componentIndex The component index
+ * @return The component, as one of the primitive types, byte, short,
+ * int or float.
+ */
+ public Object getElementComponent(int elementIndex, int componentIndex){
+ int inPos = elementIndex * components;
+ int elementPos = componentIndex;
+
+ if (format == Format.Half){
+ inPos *= 2;
+ elementPos *= 2;
+ }
+
+ Buffer srcData = getDataReadOnly();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) srcData;
+ return bin.get(inPos + elementPos);
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) srcData;
+ return sin.get(inPos + elementPos);
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) srcData;
+ return iin.get(inPos + elementPos);
+ case Float:
+ FloatBuffer fin = (FloatBuffer) srcData;
+ return fin.get(inPos + elementPos);
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Copies a single element of data from this <code>VertexBuffer</code>
+ * to the given output VertexBuffer.
+ *
+ * @param inIndex The input element index
+ * @param outVb The buffer to copy to
+ * @param outIndex The output element index
+ *
+ * @throws IllegalArgumentException If the formats of the buffers do not
+ * match.
+ */
+ public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){
+ copyElements(inIndex, outVb, outIndex, 1);
+ }
+
+ /**
+ * Copies a sequence of elements of data from this <code>VertexBuffer</code>
+ * to the given output VertexBuffer.
+ *
+ * @param inIndex The input element index
+ * @param outVb The buffer to copy to
+ * @param outIndex The output element index
+ * @param len The number of elements to copy
+ *
+ * @throws IllegalArgumentException If the formats of the buffers do not
+ * match.
+ */
+ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){
+ if (outVb.format != format || outVb.components != components)
+ throw new IllegalArgumentException("Buffer format mismatch. Cannot copy");
+
+ int inPos = inIndex * components;
+ int outPos = outIndex * components;
+ int elementSz = components;
+ if (format == Format.Half){
+ // because half is stored as bytebuf but its 2 bytes long
+ inPos *= 2;
+ outPos *= 2;
+ elementSz *= 2;
+ }
+
+ // Make sure to grab a read-only copy in case some other
+ // thread is also accessing the buffer and messing with its
+ // position()
+ Buffer srcData = getDataReadOnly();
+ outVb.data.clear();
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ case Half:
+ ByteBuffer bin = (ByteBuffer) srcData;
+ ByteBuffer bout = (ByteBuffer) outVb.data;
+ bin.position(inPos).limit(inPos + elementSz * len);
+ bout.position(outPos).limit(outPos + elementSz * len);
+ bout.put(bin);
+ break;
+ case Short:
+ case UnsignedShort:
+ ShortBuffer sin = (ShortBuffer) srcData;
+ ShortBuffer sout = (ShortBuffer) outVb.data;
+ sin.position(inPos).limit(inPos + elementSz * len);
+ sout.position(outPos).limit(outPos + elementSz * len);
+ sout.put(sin);
+ break;
+ case Int:
+ case UnsignedInt:
+ IntBuffer iin = (IntBuffer) srcData;
+ IntBuffer iout = (IntBuffer) outVb.data;
+ iin.position(inPos).limit(inPos + elementSz * len);
+ iout.position(outPos).limit(outPos + elementSz * len);
+ iout.put(iin);
+ break;
+ case Float:
+ FloatBuffer fin = (FloatBuffer) srcData;
+ FloatBuffer fout = (FloatBuffer) outVb.data;
+ fin.position(inPos).limit(inPos + elementSz * len);
+ fout.position(outPos).limit(outPos + elementSz * len);
+ fout.put(fin);
+ break;
+ default:
+ throw new UnsupportedOperationException("Unrecognized buffer format: "+format);
+ }
+
+ // Clear the output buffer to rewind it and reset its
+ // limit from where we shortened it above.
+ outVb.data.clear();
+ }
+
+ /**
+ * Creates a {@link Buffer} that satisfies the given type and size requirements
+ * of the parameters. The buffer will be of the type specified by
+ * {@link Format format} and would be able to contain the given number
+ * of elements with the given number of components in each element.
+ */
+ public static Buffer createBuffer(Format format, int components, int numElements){
+ if (components < 1 || components > 4)
+ throw new IllegalArgumentException("Num components must be between 1 and 4");
+
+ int total = numElements * components;
+
+ switch (format){
+ case Byte:
+ case UnsignedByte:
+ return BufferUtils.createByteBuffer(total);
+ case Half:
+ return BufferUtils.createByteBuffer(total * 2);
+ case Short:
+ case UnsignedShort:
+ return BufferUtils.createShortBuffer(total);
+ case Int:
+ case UnsignedInt:
+ return BufferUtils.createIntBuffer(total);
+ case Float:
+ return BufferUtils.createFloatBuffer(total);
+ case Double:
+ return BufferUtils.createDoubleBuffer(total);
+ default:
+ throw new UnsupportedOperationException("Unrecoginized buffer format: "+format);
+ }
+ }
+
+ /**
+ * Creates a deep clone of the {@link VertexBuffer}.
+ *
+ * @return Deep clone of this buffer
+ */
+ @Override
+ public VertexBuffer clone(){
+ // NOTE: Superclass GLObject automatically creates shallow clone
+ // e.g re-use ID.
+ VertexBuffer vb = (VertexBuffer) super.clone();
+ vb.handleRef = new Object();
+ vb.id = -1;
+ if (data != null) {
+ // Make sure to pass a read-only buffer to clone so that
+ // the position information doesn't get clobbered by another
+ // reading thread during cloning (and vice versa) since this is
+ // a purely read-only operation.
+ vb.updateData(BufferUtils.clone(getDataReadOnly()));
+ }
+
+ return vb;
+ }
+
+ /**
+ * Creates a deep clone of this VertexBuffer but overrides the
+ * {@link Type}.
+ *
+ * @param overrideType The type of the cloned VertexBuffer
+ * @return A deep clone of the buffer
+ */
+ public VertexBuffer clone(Type overrideType){
+ VertexBuffer vb = new VertexBuffer(overrideType);
+ vb.components = components;
+ vb.componentsLength = componentsLength;
+
+ // Make sure to pass a read-only buffer to clone so that
+ // the position information doesn't get clobbered by another
+ // reading thread during cloning (and vice versa) since this is
+ // a purely read-only operation.
+ vb.data = BufferUtils.clone(getDataReadOnly());
+ vb.format = format;
+ vb.handleRef = new Object();
+ vb.id = -1;
+ vb.normalized = normalized;
+ vb.offset = offset;
+ vb.stride = stride;
+ vb.updateNeeded = true;
+ vb.usage = usage;
+ return vb;
+ }
+
+ @Override
+ public String toString(){
+ String dataTxt = null;
+ if (data != null){
+ dataTxt = ", elements="+data.capacity();
+ }
+ return getClass().getSimpleName() + "[fmt="+format.name()
+ +", type="+bufType.name()
+ +", usage="+usage.name()
+ +dataTxt+"]";
+ }
+
+ @Override
+ public void resetObject() {
+// assert this.id != -1;
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((Renderer)rendererObject).deleteBuffer(this);
+ }
+
+ @Override
+ public NativeObject createDestructableClone(){
+ return new VertexBuffer(id);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(components, "components", 0);
+ oc.write(usage, "usage", Usage.Dynamic);
+ oc.write(bufType, "buffer_type", null);
+ oc.write(format, "format", Format.Float);
+ oc.write(normalized, "normalized", false);
+ oc.write(offset, "offset", 0);
+ oc.write(stride, "stride", 0);
+
+ String dataName = "data" + format.name();
+ Buffer roData = getDataReadOnly();
+ switch (format){
+ case Float:
+ oc.write((FloatBuffer) roData, dataName, null);
+ break;
+ case Short:
+ case UnsignedShort:
+ oc.write((ShortBuffer) roData, dataName, null);
+ break;
+ case UnsignedByte:
+ case Byte:
+ case Half:
+ oc.write((ByteBuffer) roData, dataName, null);
+ break;
+ case Int:
+ case UnsignedInt:
+ oc.write((IntBuffer) roData, dataName, null);
+ break;
+ default:
+ throw new IOException("Unsupported export buffer format: "+format);
+ }
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ components = ic.readInt("components", 0);
+ usage = ic.readEnum("usage", Usage.class, Usage.Dynamic);
+ bufType = ic.readEnum("buffer_type", Type.class, null);
+ format = ic.readEnum("format", Format.class, Format.Float);
+ normalized = ic.readBoolean("normalized", false);
+ offset = ic.readInt("offset", 0);
+ stride = ic.readInt("stride", 0);
+ componentsLength = components * format.getComponentSize();
+
+ String dataName = "data" + format.name();
+ switch (format){
+ case Float:
+ data = ic.readFloatBuffer(dataName, null);
+ break;
+ case Short:
+ case UnsignedShort:
+ data = ic.readShortBuffer(dataName, null);
+ break;
+ case UnsignedByte:
+ case Byte:
+ case Half:
+ data = ic.readByteBuffer(dataName, null);
+ break;
+ case Int:
+ case UnsignedInt:
+ data = ic.readIntBuffer(dataName, null);
+ break;
+ default:
+ throw new IOException("Unsupported import buffer format: "+format);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AbstractControl.java b/engine/src/core/com/jme3/scene/control/AbstractControl.java
new file mode 100644
index 0000000..2887ec9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AbstractControl.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * An abstract implementation of the Control interface.
+ *
+ * @author Kirill Vainer
+ */
+public abstract class AbstractControl implements Control {
+
+ protected boolean enabled = true;
+ protected Spatial spatial;
+
+ public AbstractControl(){
+ }
+
+ public void setSpatial(Spatial spatial) {
+ if (this.spatial != null && spatial != null) {
+ throw new IllegalStateException("This control has already been added to a Spatial");
+ }
+ this.spatial = spatial;
+ }
+
+ public Spatial getSpatial(){
+ return spatial;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * To be implemented in subclass.
+ */
+ protected abstract void controlUpdate(float tpf);
+
+ /**
+ * To be implemented in subclass.
+ */
+ protected abstract void controlRender(RenderManager rm, ViewPort vp);
+
+ public void update(float tpf) {
+ if (!enabled)
+ return;
+
+ controlUpdate(tpf);
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ if (!enabled)
+ return;
+
+ controlRender(rm, vp);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(enabled, "enabled", true);
+ oc.write(spatial, "spatial", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule ic = im.getCapsule(this);
+ enabled = ic.readBoolean("enabled", true);
+ spatial = (Spatial) ic.readSavable("spatial", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/AreaUtils.java b/engine/src/core/com/jme3/scene/control/AreaUtils.java
new file mode 100644
index 0000000..47e7c5f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/AreaUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.FastMath;
+
+/**
+ * <code>AreaUtils</code> is used to calculate the area of various objects, such as bounding volumes. These
+ * functions are very loose approximations.
+ * @author Joshua Slack
+ * @version $Id: AreaUtils.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+
+public class AreaUtils {
+
+ /**
+ * calcScreenArea -- in Pixels
+ * Aproximates the screen area of a bounding volume. If the volume isn't a
+ * BoundingSphere, BoundingBox, or OrientedBoundingBox 0 is returned.
+ *
+ * @param bound The bounds to calculate the volume from.
+ * @param distance The distance from camera to object.
+ * @param screenWidth The width of the screen.
+ * @return The area in pixels on the screen of the bounding volume.
+ */
+ public static float calcScreenArea(BoundingVolume bound, float distance, float screenWidth) {
+ if (bound.getType() == BoundingVolume.Type.Sphere){
+ return calcScreenArea((BoundingSphere) bound, distance, screenWidth);
+ }else if (bound.getType() == BoundingVolume.Type.AABB){
+ return calcScreenArea((BoundingBox) bound, distance, screenWidth);
+ }
+ return 0.0f;
+ }
+
+ private static float calcScreenArea(BoundingSphere bound, float distance, float screenWidth) {
+ // Where is the center point and a radius point that lies in a plan parallel to the view plane?
+// // Calc radius based on these two points and plug into circle area formula.
+// Vector2f centerSP = null;
+// Vector2f outerSP = null;
+// float radiusSq = centerSP.subtract(outerSP).lengthSquared();
+ float radius = (bound.getRadius() * screenWidth) / (distance * 2);
+ return radius * radius * FastMath.PI;
+ }
+
+ private static float calcScreenArea(BoundingBox bound, float distance, float screenWidth) {
+ // Calc as if we are a BoundingSphere for now...
+ float radiusSquare = bound.getXExtent() * bound.getXExtent()
+ + bound.getYExtent() * bound.getYExtent()
+ + bound.getZExtent() * bound.getZExtent();
+ return ((radiusSquare * screenWidth * screenWidth) / (distance * distance * 4)) * FastMath.PI;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/BillboardControl.java b/engine/src/core/com/jme3/scene/control/BillboardControl.java
new file mode 100644
index 0000000..40c2fea
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/BillboardControl.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+public class BillboardControl extends AbstractControl {
+
+ private Matrix3f orient;
+ private Vector3f look;
+ private Vector3f left;
+ private Alignment alignment;
+
+ /**
+ * Determines how the billboard is aligned to the screen/camera.
+ */
+ public enum Alignment {
+ /**
+ * Aligns this Billboard to the screen.
+ */
+ Screen,
+
+ /**
+ * Aligns this Billboard to the camera position.
+ */
+ Camera,
+
+ /**
+ * Aligns this Billboard to the screen, but keeps the Y axis fixed.
+ */
+ AxialY,
+
+ /**
+ * Aligns this Billboard to the screen, but keeps the Z axis fixed.
+ */
+ AxialZ;
+ }
+
+ public BillboardControl() {
+ super();
+ orient = new Matrix3f();
+ look = new Vector3f();
+ left = new Vector3f();
+ alignment = Alignment.Screen;
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ BillboardControl control = new BillboardControl();
+ control.alignment = this.alignment;
+ control.setSpatial(spatial);
+ return control;
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ Camera cam = vp.getCamera();
+ rotateBillboard(cam);
+ }
+
+ private void fixRefreshFlags(){
+ // force transforms to update below this node
+ spatial.updateGeometricState();
+
+ // force world bound to update
+ Spatial rootNode = spatial;
+ while (rootNode.getParent() != null){
+ rootNode = rootNode.getParent();
+ }
+ rootNode.getWorldBound();
+ }
+
+ /**
+ * rotate the billboard based on the type set
+ *
+ * @param cam
+ * Camera
+ */
+ private void rotateBillboard(Camera cam) {
+ switch (alignment) {
+ case AxialY:
+ rotateAxial(cam, Vector3f.UNIT_Y);
+ break;
+ case AxialZ:
+ rotateAxial(cam, Vector3f.UNIT_Z);
+ break;
+ case Screen:
+ rotateScreenAligned(cam);
+ break;
+ case Camera:
+ rotateCameraAligned(cam);
+ break;
+ }
+ }
+
+ /**
+ * Aligns this Billboard so that it points to the camera position.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateCameraAligned(Camera camera) {
+ look.set(camera.getLocation()).subtractLocal(
+ spatial.getWorldTranslation());
+ // coopt left for our own purposes.
+ Vector3f xzp = left;
+ // The xzp vector is the projection of the look vector on the xz plane
+ xzp.set(look.x, 0, look.z);
+
+ // check for undefined rotation...
+ if (xzp.equals(Vector3f.ZERO)) {
+ return;
+ }
+
+ look.normalizeLocal();
+ xzp.normalizeLocal();
+ float cosp = look.dot(xzp);
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, xzp.z);
+ orient.set(0, 1, xzp.x * -look.y);
+ orient.set(0, 2, xzp.x * cosp);
+ orient.set(1, 0, 0);
+ orient.set(1, 1, cosp);
+ orient.set(1, 2, look.y);
+ orient.set(2, 0, -xzp.x);
+ orient.set(2, 1, xzp.z * -look.y);
+ orient.set(2, 2, xzp.z * cosp);
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ spatial.setLocalRotation(orient);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Rotate the billboard so it points directly opposite the direction the
+ * camera's facing
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateScreenAligned(Camera camera) {
+ // 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);
+ Node parent = spatial.getParent();
+ Quaternion rot=new Quaternion().fromRotationMatrix(orient);
+ if ( parent != null ) {
+ rot = parent.getWorldRotation().inverse().multLocal(rot);
+ rot.normalizeLocal();
+ }
+ spatial.setLocalRotation(rot);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Rotate the billboard towards the camera, but keeping a given axis fixed.
+ *
+ * @param camera
+ * Camera
+ */
+ private void rotateAxial(Camera camera, Vector3f axis) {
+ // 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(
+ spatial.getWorldTranslation());
+ spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own
+ // purposes.
+ left.x *= 1.0f / spatial.getWorldScale().x;
+ left.y *= 1.0f / spatial.getWorldScale().y;
+ left.z *= 1.0f / spatial.getWorldScale().z;
+
+ // squared length of the camera projection in the xz-plane
+ float lengthSquared = left.x * left.x + left.z * left.z;
+ if (lengthSquared < FastMath.FLT_EPSILON) {
+ // camera on the billboard axis, rotation not defined
+ return;
+ }
+
+ // unitize the projection
+ float invLength = FastMath.invSqrt(lengthSquared);
+ if (axis.y == 1) {
+ left.x *= invLength;
+ left.y = 0.0f;
+ left.z *= invLength;
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, left.z);
+ orient.set(0, 1, 0);
+ orient.set(0, 2, left.x);
+ orient.set(1, 0, 0);
+ orient.set(1, 1, 1);
+ orient.set(1, 2, 0);
+ orient.set(2, 0, -left.x);
+ orient.set(2, 1, 0);
+ orient.set(2, 2, left.z);
+ } else if (axis.z == 1) {
+ left.x *= invLength;
+ left.y *= invLength;
+ left.z = 0.0f;
+
+ // compute the local orientation matrix for the billboard
+ orient.set(0, 0, left.y);
+ orient.set(0, 1, left.x);
+ orient.set(0, 2, 0);
+ orient.set(1, 0, -left.y);
+ orient.set(1, 1, left.x);
+ orient.set(1, 2, 0);
+ orient.set(2, 0, 0);
+ orient.set(2, 1, 0);
+ orient.set(2, 2, 1);
+ }
+
+ // The billboard must be oriented to face the camera before it is
+ // transformed into the world.
+ spatial.setLocalRotation(orient);
+ fixRefreshFlags();
+ }
+
+ /**
+ * Returns the alignment this Billboard is set too.
+ *
+ * @return The alignment of rotation, AxialY, AxialZ, Camera or Screen.
+ */
+ public Alignment getAlignment() {
+ return alignment;
+ }
+
+ /**
+ * Sets the type of rotation this Billboard will have. The alignment can
+ * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will
+ * assume no billboard rotation.
+ */
+ public void setAlignment(Alignment alignment) {
+ this.alignment = alignment;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(orient, "orient", null);
+ capsule.write(look, "look", null);
+ capsule.write(left, "left", null);
+ capsule.write(alignment, "alignment", Alignment.Screen);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ orient = (Matrix3f) capsule.readSavable("orient", null);
+ look = (Vector3f) capsule.readSavable("look", null);
+ left = (Vector3f) capsule.readSavable("left", null);
+ alignment = capsule.readEnum("alignment", Alignment.class, Alignment.Screen);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/control/CameraControl.java b/engine/src/core/com/jme3/scene/control/CameraControl.java
new file mode 100644
index 0000000..5dd559c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/CameraControl.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class CameraControl extends AbstractControl {
+
+ public static enum ControlDirection {
+
+ /**
+ * Means, that the Camera's transform is "copied"
+ * to the Transform of the Spatial.
+ */
+ CameraToSpatial,
+ /**
+ * Means, that the Spatial's transform is "copied"
+ * to the Transform of the Camera.
+ */
+ SpatialToCamera;
+ }
+ private Camera camera;
+ private ControlDirection controlDir = ControlDirection.CameraToSpatial;
+
+ /**
+ * Constructor used for Serialization.
+ */
+ public CameraControl() {
+ }
+
+ /**
+ * @param camera The Camera to be synced.
+ */
+ public CameraControl(Camera camera) {
+ this.camera = camera;
+ }
+
+ /**
+ * @param camera The Camera to be synced.
+ */
+ public CameraControl(Camera camera, ControlDirection controlDir) {
+ this.camera = camera;
+ this.controlDir = controlDir;
+ }
+
+ public Camera getCamera() {
+ return camera;
+ }
+
+ public void setCamera(Camera camera) {
+ this.camera = camera;
+ }
+
+ public ControlDirection getControlDir() {
+ return controlDir;
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ this.controlDir = controlDir;
+ }
+
+ // fields used, when inversing ControlDirection:
+ @Override
+ protected void controlUpdate(float tpf) {
+ if (spatial != null && camera != null) {
+ switch (controlDir) {
+ case SpatialToCamera:
+ camera.setLocation(spatial.getWorldTranslation());
+ camera.setRotation(spatial.getWorldRotation());
+ break;
+ case CameraToSpatial:
+ // set the localtransform, so that the worldtransform would be equal to the camera's transform.
+ // Location:
+ TempVars vars = TempVars.get();
+
+ Vector3f vecDiff = vars.vect1.set(camera.getLocation()).subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+
+ // Rotation:
+ Quaternion worldDiff = vars.quat1.set(camera.getRotation()).subtractLocal(spatial.getWorldRotation());
+ spatial.setLocalRotation(worldDiff.addLocal(spatial.getLocalRotation()));
+ vars.release();
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ // nothing to do
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial newSpatial) {
+ CameraControl control = new CameraControl(camera, controlDir);
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ return control;
+ }
+ private static final String CONTROL_DIR_NAME = "controlDir";
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+ ControlDirection.class, ControlDirection.SpatialToCamera);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+ ControlDirection.SpatialToCamera);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/Control.java b/engine/src/core/com/jme3/scene/control/Control.java
new file mode 100644
index 0000000..b5b0057
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/Control.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.export.Savable;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+
+/**
+ * An interface for scene-graph controls.
+ * <p>
+ * <code>Control</code>s are used to specify certain update and render logic
+ * for a {@link Spatial}.
+ *
+ * @author Kirill Vainer
+ */
+public interface Control extends Savable {
+
+ /**
+ * Creates a clone of the Control, the given Spatial is the cloned
+ * version of the spatial to which this control is attached to.
+ * @param spatial
+ * @return A clone of this control for the spatial
+ */
+ public Control cloneForSpatial(Spatial spatial);
+
+ /**
+ * @param spatial the spatial to be controlled. This should not be called
+ * from user code.
+ */
+ public void setSpatial(Spatial spatial);
+
+ /**
+ * @param enabled Enable or disable the control. If disabled, update()
+ * should do nothing.
+ */
+ public void setEnabled(boolean enabled);
+
+ /**
+ * @return True if enabled, false otherwise.
+ * @see Control#setEnabled(boolean)
+ */
+ public boolean isEnabled();
+
+ /**
+ * Updates the control. This should not be called from user code.
+ * @param tpf Time per frame.
+ */
+ public void update(float tpf);
+
+ /**
+ * Should be called prior to queuing the spatial by the RenderManager. This
+ * should not be called from user code.
+ *
+ * @param rm
+ * @param vp
+ */
+ public void render(RenderManager rm, ViewPort vp);
+}
diff --git a/engine/src/core/com/jme3/scene/control/LightControl.java b/engine/src/core/com/jme3/scene/control/LightControl.java
new file mode 100644
index 0000000..2859c9b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LightControl.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * This Control maintains a reference to a Camera,
+ * which will be synched with the position (worldTranslation)
+ * of the current spatial.
+ * @author tim
+ */
+public class LightControl extends AbstractControl {
+
+ public static enum ControlDirection {
+
+ /**
+ * Means, that the Light's transform is "copied"
+ * to the Transform of the Spatial.
+ */
+ LightToSpatial,
+ /**
+ * Means, that the Spatial's transform is "copied"
+ * to the Transform of the light.
+ */
+ SpatialToLight;
+ }
+ private Light light;
+ private ControlDirection controlDir = ControlDirection.SpatialToLight;
+
+ /**
+ * Constructor used for Serialization.
+ */
+ public LightControl() {
+ }
+
+ /**
+ * @param light The light to be synced.
+ */
+ public LightControl(Light light) {
+ this.light = light;
+ }
+
+ /**
+ * @param light The light to be synced.
+ */
+ public LightControl(Light light, ControlDirection controlDir) {
+ this.light = light;
+ this.controlDir = controlDir;
+ }
+
+ public Light getLight() {
+ return light;
+ }
+
+ public void setLight(Light light) {
+ this.light = light;
+ }
+
+ public ControlDirection getControlDir() {
+ return controlDir;
+ }
+
+ public void setControlDir(ControlDirection controlDir) {
+ this.controlDir = controlDir;
+ }
+
+ // fields used, when inversing ControlDirection:
+ @Override
+ protected void controlUpdate(float tpf) {
+ if (spatial != null && light != null) {
+ switch (controlDir) {
+ case SpatialToLight:
+ spatialTolight(light);
+ break;
+ case LightToSpatial:
+ lightToSpatial(light);
+ break;
+ }
+ }
+ }
+
+ private void spatialTolight(Light light) {
+ if (light instanceof PointLight) {
+ ((PointLight) light).setPosition(spatial.getWorldTranslation());
+ }
+ TempVars vars = TempVars.get();
+
+ if (light instanceof DirectionalLight) {
+ ((DirectionalLight) light).setDirection(vars.vect1.set(spatial.getWorldTranslation()).multLocal(-1.0f));
+ }
+ vars.release();
+ //TODO add code for Spot light here when it's done
+// if( light instanceof SpotLight){
+// ((SpotLight)light).setPosition(spatial.getWorldTranslation());
+// ((SpotLight)light).setRotation(spatial.getWorldRotation());
+// }
+
+ }
+
+ private void lightToSpatial(Light light) {
+ TempVars vars = TempVars.get();
+ if (light instanceof PointLight) {
+
+ PointLight pLight = (PointLight) light;
+
+ Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+ }
+
+ if (light instanceof DirectionalLight) {
+ DirectionalLight dLight = (DirectionalLight) light;
+ vars.vect1.set(dLight.getDirection()).multLocal(-1.0f);
+ Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation());
+ spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation()));
+ }
+ vars.release();
+ //TODO add code for Spot light here when it's done
+
+
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+ // nothing to do
+ }
+
+ @Override
+ public Control cloneForSpatial(Spatial newSpatial) {
+ LightControl control = new LightControl(light, controlDir);
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ return control;
+ }
+ private static final String CONTROL_DIR_NAME = "controlDir";
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ im.getCapsule(this).readEnum(CONTROL_DIR_NAME,
+ ControlDirection.class, ControlDirection.SpatialToLight);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ ex.getCapsule(this).write(controlDir, CONTROL_DIR_NAME,
+ ControlDirection.SpatialToLight);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/control/LodControl.java b/engine/src/core/com/jme3/scene/control/LodControl.java
new file mode 100644
index 0000000..6cbfefb
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/LodControl.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.control;
+
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Determines what Level of Detail a spatial should be, based on how many pixels
+ * on the screen the spatial is taking up. The more pixels covered, the more detailed
+ * the spatial should be.
+ * It calculates the area of the screen that the spatial covers by using its bounding box.
+ * When initializing, it will ask the spatial for how many triangles it has for each LOD.
+ * It then uses that, along with the trisPerPixel value to determine what LOD it should be at.
+ * It requires the camera to do this.
+ * The controlRender method is called each frame and will update the spatial's LOD
+ * if the camera has moved by a specified amount.
+ */
+public class LodControl extends AbstractControl implements Cloneable {
+
+ private float trisPerPixel = 1f;
+ private float distTolerance = 1f;
+ private float lastDistance = 0f;
+ private int lastLevel = 0;
+ private int numLevels;
+ private int[] numTris;
+
+ /**
+ * Creates a new <code>LodControl</code>.
+ */
+ public LodControl(){
+ }
+
+ /**
+ * Returns the distance tolerance for changing LOD.
+ *
+ * @return the distance tolerance for changing LOD.
+ *
+ * @see #setDistTolerance(float)
+ */
+ public float getDistTolerance() {
+ return distTolerance;
+ }
+
+ /**
+ * Specifies the distance tolerance for changing the LOD level on the geometry.
+ * The LOD level will only get changed if the geometry has moved this
+ * distance beyond the current LOD level.
+ *
+ * @param distTolerance distance tolerance for changing LOD
+ */
+ public void setDistTolerance(float distTolerance) {
+ this.distTolerance = distTolerance;
+ }
+
+ /**
+ * Returns the triangles per pixel value.
+ *
+ * @return the triangles per pixel value.
+ *
+ * @see #setTrisPerPixel(float)
+ */
+ public float getTrisPerPixel() {
+ return trisPerPixel;
+ }
+
+ /**
+ * Sets the triangles per pixel value.
+ * The <code>LodControl</code> will use this value as an error metric
+ * to determine which LOD level to use based on the geometry's
+ * area on the screen.
+ *
+ * @param trisPerPixel triangles per pixel
+ */
+ public void setTrisPerPixel(float trisPerPixel) {
+ this.trisPerPixel = trisPerPixel;
+ }
+
+ @Override
+ public void setSpatial(Spatial spatial){
+ if (!(spatial instanceof Geometry))
+ throw new IllegalArgumentException("LodControl can only be attached to Geometry!");
+
+ super.setSpatial(spatial);
+ Geometry geom = (Geometry) spatial;
+ Mesh mesh = geom.getMesh();
+ numLevels = mesh.getNumLodLevels();
+ numTris = new int[numLevels];
+ for (int i = numLevels - 1; i >= 0; i--)
+ numTris[i] = mesh.getTriangleCount(i);
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ try {
+ LodControl clone = (LodControl) super.clone();
+ clone.lastDistance = 0;
+ clone.lastLevel = 0;
+ clone.numTris = numTris != null ? numTris.clone() : null;
+ return clone;
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ }
+
+ protected void controlRender(RenderManager rm, ViewPort vp){
+ BoundingVolume bv = spatial.getWorldBound();
+
+ Camera cam = vp.getCamera();
+ float atanNH = FastMath.atan(cam.getFrustumNear() * cam.getFrustumTop());
+ float ratio = (FastMath.PI / (8f * atanNH));
+ float newDistance = bv.distanceTo(vp.getCamera().getLocation()) / ratio;
+ int level;
+
+ if (Math.abs(newDistance - lastDistance) <= distTolerance)
+ level = lastLevel; // we haven't moved relative to the model, send the old measurement back.
+ else if (lastDistance > newDistance && lastLevel == 0)
+ level = lastLevel; // we're already at the lowest setting and we just got closer to the model, no need to keep trying.
+ else if (lastDistance < newDistance && lastLevel == numLevels - 1)
+ level = lastLevel; // we're already at the highest setting and we just got further from the model, no need to keep trying.
+ else{
+ lastDistance = newDistance;
+
+ // estimate area of polygon via bounding volume
+ float area = AreaUtils.calcScreenArea(bv, lastDistance, cam.getWidth());
+ float trisToDraw = area * trisPerPixel;
+ level = numLevels - 1;
+ for (int i = numLevels; --i >= 0;){
+ if (trisToDraw - numTris[i] < 0){
+ break;
+ }
+ level = i;
+ }
+ lastLevel = level;
+ }
+
+ spatial.setLodLevel(level);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(trisPerPixel, "trisPerPixel", 1f);
+ oc.write(distTolerance, "distTolerance", 1f);
+ oc.write(numLevels, "numLevels", 0);
+ oc.write(numTris, "numTris", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ trisPerPixel = ic.readFloat("trisPerPixel", 1f);
+ distTolerance = ic.readFloat("distTolerance", 1f);
+ numLevels = ic.readInt("numLevels", 0);
+ numTris = ic.readIntArray("numTris", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/UpdateControl.java b/engine/src/core/com/jme3/scene/control/UpdateControl.java
new file mode 100644
index 0000000..7fbca01
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/UpdateControl.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.control;
+
+import com.jme3.app.AppTask;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Spatial;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Future;
+
+/**
+ * Allows for enqueueing tasks onto the update loop / rendering thread.
+ *
+ * Usage:
+ * mySpatial.addControl(new UpdateControl()); // add it once
+ * mySpatial.getControl(UpdateControl.class).enqueue(new Callable() {
+ * public Object call() throws Exception {
+ * // do stuff here
+ * return null;
+ * }
+ * });
+ *
+ * @author Brent Owens
+ */
+public class UpdateControl extends AbstractControl {
+
+ private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
+
+ /**
+ * Enqueues a task/callable object to execute in the jME3
+ * rendering thread.
+ */
+ public <V> Future<V> enqueue(Callable<V> callable) {
+ AppTask<V> task = new AppTask<V>(callable);
+ taskQueue.add(task);
+ return task;
+ }
+
+ @Override
+ protected void controlUpdate(float tpf) {
+ AppTask<?> task = taskQueue.poll();
+ toploop: do {
+ if (task == null) break;
+ while (task.isCancelled()) {
+ task = taskQueue.poll();
+ if (task == null) break toploop;
+ }
+ task.invoke();
+ } while (((task = taskQueue.poll()) != null));
+ }
+
+ @Override
+ protected void controlRender(RenderManager rm, ViewPort vp) {
+
+ }
+
+ public Control cloneForSpatial(Spatial newSpatial) {
+ UpdateControl control = new UpdateControl();
+ control.setSpatial(newSpatial);
+ control.setEnabled(isEnabled());
+ control.taskQueue.addAll(taskQueue);
+ return control;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/control/package.html b/engine/src/core/com/jme3/scene/control/package.html
new file mode 100644
index 0000000..a387840
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/control/package.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.control</code> package provides
+{@link com.jme3.scene.control.Control controls}.
+Controls represent the "logical" programming of scene graph elements, containing
+callbacks for when a {@link com.jme3.scene.Spatial} is rendered or updated
+by the engine.
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/debug/Arrow.java b/engine/src/core/com/jme3/scene/debug/Arrow.java
new file mode 100644
index 0000000..1c3dd94
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Arrow.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.debug;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.FloatBuffer;
+
+/**
+ * The <code>Arrow</code> debug shape represents an arrow.
+ * An arrow is simply a line going from the original toward an extent
+ * and at the tip there will be triangle-like shape.
+ *
+ * @author Kirill Vainer
+ */
+public class Arrow extends Mesh {
+
+ private Quaternion tempQuat = new Quaternion();
+ private Vector3f tempVec = new Vector3f();
+
+ private static final float[] positions = new float[]{
+ 0, 0, 0,
+ 0, 0, 1, // tip
+ 0.05f, 0, 0.9f, // tip right
+ -0.05f, 0, 0.9f, // tip left
+ 0, 0.05f, 0.9f, // tip top
+ 0, -0.05f, 0.9f, // tip buttom
+ };
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Arrow() {
+ }
+
+ /**
+ * Creates an arrow mesh with the given extent.
+ * The arrow will start at the origin (0,0,0) and finish
+ * at the given extent.
+ *
+ * @param extent Extent of the arrow from origin
+ */
+ public Arrow(Vector3f extent) {
+ float len = extent.length();
+ Vector3f dir = extent.normalize();
+
+ tempQuat.lookAt(dir, Vector3f.UNIT_Y);
+ tempQuat.normalizeLocal();
+
+ float[] newPositions = new float[positions.length];
+ for (int i = 0; i < positions.length; i += 3) {
+ Vector3f vec = tempVec.set(positions[i],
+ positions[i + 1],
+ positions[i + 2]);
+ vec.multLocal(len);
+ tempQuat.mult(vec, vec);
+
+ newPositions[i] = vec.getX();
+ newPositions[i + 1] = vec.getY();
+ newPositions[i + 2] = vec.getZ();
+ }
+
+ setBuffer(Type.Position, 3, newPositions);
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 1, 3,
+ 1, 4,
+ 1, 5,});
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+ /**
+ * Sets the arrow's extent.
+ * This will modify the buffers on the mesh.
+ *
+ * @param extent the arrow's extent.
+ */
+ public void setArrowExtent(Vector3f extent) {
+ float len = extent.length();
+// Vector3f dir = extent.normalize();
+
+ tempQuat.lookAt(extent, Vector3f.UNIT_Y);
+ tempQuat.normalizeLocal();
+
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer buffer = (FloatBuffer)pvb.getData();
+ buffer.rewind();
+ for (int i = 0; i < positions.length; i += 3) {
+ Vector3f vec = tempVec.set(positions[i],
+ positions[i + 1],
+ positions[i + 2]);
+ vec.multLocal(len);
+ tempQuat.mult(vec, vec);
+
+ buffer.put(vec.x);
+ buffer.put(vec.y);
+ buffer.put(vec.z);
+ }
+
+ pvb.updateData(buffer);
+
+ updateBound();
+ updateCounts();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/Grid.java b/engine/src/core/com/jme3/scene/debug/Grid.java
new file mode 100644
index 0000000..ea6225c
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/Grid.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Simple grid shape.
+ *
+ * @author Kirill Vainer
+ */
+public class Grid extends Mesh {
+
+ /**
+ * Creates a grid debug shape.
+ * @param xLines
+ * @param yLines
+ * @param lineDist
+ */
+ public Grid(int xLines, int yLines, float lineDist){
+ xLines -= 2;
+ yLines -= 2;
+ int lineCount = xLines + yLines + 4;
+
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(6 * lineCount);
+ ShortBuffer sib = BufferUtils.createShortBuffer(2 * lineCount);
+
+ float xLineLen = (yLines + 1) * lineDist;
+ float yLineLen = (xLines + 1) * lineDist;
+ int curIndex = 0;
+
+ // add lines along X
+ for (int i = 0; i < xLines + 2; i++){
+ float y = (i) * lineDist;
+
+ // positions
+ fpb.put(0) .put(0).put(y);
+ fpb.put(xLineLen).put(0).put(y);
+
+ // indices
+ sib.put( (short) (curIndex++) );
+ sib.put( (short) (curIndex++) );
+ }
+
+ // add lines along Y
+ for (int i = 0; i < yLines + 2; i++){
+ float x = (i) * lineDist;
+
+ // positions
+ fpb.put(x).put(0).put(0);
+ fpb.put(x).put(0).put(yLineLen);
+
+ // indices
+ sib.put( (short) (curIndex++) );
+ sib.put( (short) (curIndex++) );
+ }
+
+ fpb.flip();
+ sib.flip();
+
+ setBuffer(Type.Position, 3, fpb);
+ setBuffer(Type.Index, 2, sib);
+
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
new file mode 100644
index 0000000..07efdc9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonDebugger.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Skeleton;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+
+public class SkeletonDebugger extends Node {
+
+ private SkeletonWire wires;
+ private SkeletonPoints points;
+ private Skeleton skeleton;
+
+ public SkeletonDebugger(String name, Skeleton skeleton){
+ super(name);
+
+ this.skeleton = skeleton;
+ wires = new SkeletonWire(skeleton);
+ points = new SkeletonPoints(skeleton);
+
+ attachChild(new Geometry(name+"_wires", wires));
+ attachChild(new Geometry(name+"_points", points));
+
+ setQueueBucket(Bucket.Transparent);
+ }
+
+ public SkeletonDebugger(){
+ }
+
+ @Override
+ public void updateLogicalState(float tpf){
+ super.updateLogicalState(tpf);
+
+// skeleton.resetAndUpdate();
+ wires.updateGeometry();
+ points.updateGeometry();
+ }
+
+ public SkeletonPoints getPoints() {
+ return points;
+ }
+
+ public SkeletonWire getWires() {
+ return wires;
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
new file mode 100644
index 0000000..2e49ce5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonPoints.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class SkeletonPoints extends Mesh {
+
+ private Skeleton skeleton;
+
+ public SkeletonPoints(Skeleton skeleton){
+ this.skeleton = skeleton;
+
+ setMode(Mode.Points);
+
+ VertexBuffer pb = new VertexBuffer(Type.Position);
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+ pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+ setBuffer(pb);
+
+ setPointSize(7);
+
+ updateCounts();
+ }
+
+ public void updateGeometry(){
+ VertexBuffer vb = getBuffer(Type.Position);
+ FloatBuffer posBuf = getFloatBuffer(Type.Position);
+ posBuf.clear();
+ for (int i = 0; i < skeleton.getBoneCount(); i++){
+ Bone bone = skeleton.getBone(i);
+ Vector3f bonePos = bone.getModelSpacePosition();
+
+ posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+ }
+ posBuf.flip();
+ vb.updateData(posBuf);
+
+ updateBound();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/SkeletonWire.java b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
new file mode 100644
index 0000000..0796334
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/SkeletonWire.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class SkeletonWire extends Mesh {
+
+ private int numConnections = 0;
+ private Skeleton skeleton;
+
+ private void countConnections(Bone bone){
+ for (Bone child : bone.getChildren()){
+ numConnections ++;
+ countConnections(child);
+ }
+ }
+
+ private void writeConnections(ShortBuffer indexBuf, Bone bone){
+ for (Bone child : bone.getChildren()){
+ // write myself
+ indexBuf.put( (short) skeleton.getBoneIndex(bone) );
+ // write the child
+ indexBuf.put( (short) skeleton.getBoneIndex(child) );
+
+ writeConnections(indexBuf, child);
+ }
+ }
+
+ public SkeletonWire(Skeleton skeleton){
+ this.skeleton = skeleton;
+ for (Bone bone : skeleton.getRoots())
+ countConnections(bone);
+
+ setMode(Mode.Lines);
+
+ VertexBuffer pb = new VertexBuffer(Type.Position);
+ FloatBuffer fpb = BufferUtils.createFloatBuffer(skeleton.getBoneCount() * 3);
+ pb.setupData(Usage.Stream, 3, Format.Float, fpb);
+ setBuffer(pb);
+
+ VertexBuffer ib = new VertexBuffer(Type.Index);
+ ShortBuffer sib = BufferUtils.createShortBuffer(numConnections * 2);
+ ib.setupData(Usage.Static, 2, Format.UnsignedShort, sib);
+ setBuffer(ib);
+
+ for (Bone bone : skeleton.getRoots())
+ writeConnections(sib, bone);
+ sib.flip();
+
+ updateCounts();
+ }
+
+ public void updateGeometry(){
+ VertexBuffer vb = getBuffer(Type.Position);
+ FloatBuffer posBuf = getFloatBuffer(Type.Position);
+ posBuf.clear();
+ for (int i = 0; i < skeleton.getBoneCount(); i++){
+ Bone bone = skeleton.getBone(i);
+ Vector3f bonePos = bone.getModelSpacePosition();
+
+ posBuf.put(bonePos.getX()).put(bonePos.getY()).put(bonePos.getZ());
+ }
+ posBuf.flip();
+ vb.updateData(posBuf);
+
+ updateBound();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireBox.java b/engine/src/core/com/jme3/scene/debug/WireBox.java
new file mode 100644
index 0000000..50af28a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireBox.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireBox extends Mesh {
+
+ public WireBox(){
+ this(1,1,1);
+ }
+
+ public WireBox(float xExt, float yExt, float zExt){
+ updatePositions(xExt,yExt,zExt);
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 2, 3,
+ 3, 0,
+
+ 4, 5,
+ 5, 6,
+ 6, 7,
+ 7, 4,
+
+ 0, 4,
+ 1, 5,
+ 2, 6,
+ 3, 7,
+ }
+ );
+ setMode(Mode.Lines);
+
+ updateCounts();
+ }
+
+ public void updatePositions(float xExt, float yExt, float zExt){
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer pb;
+ if (pvb == null){
+ pvb = new VertexBuffer(Type.Position);
+ pb = BufferUtils.createVector3Buffer(8);
+ pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);
+ setBuffer(pvb);
+ }else{
+ pb = (FloatBuffer) pvb.getData();
+ pvb.updateData(pb);
+ }
+ pb.rewind();
+ pb.put(
+ new float[]{
+ -xExt, -yExt, zExt,
+ xExt, -yExt, zExt,
+ xExt, yExt, zExt,
+ -xExt, yExt, zExt,
+
+ -xExt, -yExt, -zExt,
+ xExt, -yExt, -zExt,
+ xExt, yExt, -zExt,
+ -xExt, yExt, -zExt,
+ }
+ );
+ updateBound();
+ }
+
+ public void fromBoundingBox(BoundingBox bbox){
+ updatePositions(bbox.getXExtent(), bbox.getYExtent(), bbox.getZExtent());
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireFrustum.java b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
new file mode 100644
index 0000000..7a86d90
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireFrustum.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.debug;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+public class WireFrustum extends Mesh {
+
+ public WireFrustum(Vector3f[] points){
+ if (points != null)
+ setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+
+ setBuffer(Type.Index, 2,
+ new short[]{
+ 0, 1,
+ 1, 2,
+ 2, 3,
+ 3, 0,
+
+ 4, 5,
+ 5, 6,
+ 6, 7,
+ 7, 4,
+
+ 0, 4,
+ 1, 5,
+ 2, 6,
+ 3, 7,
+ }
+ );
+ setMode(Mode.Lines);
+ }
+
+ public void update(Vector3f[] points){
+ VertexBuffer vb = getBuffer(Type.Position);
+ if (vb == null){
+ setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points));
+ return;
+ }
+
+ FloatBuffer b = BufferUtils.createFloatBuffer(points);
+ FloatBuffer a = (FloatBuffer) vb.getData();
+ b.rewind();
+ a.rewind();
+ a.put(b);
+ a.rewind();
+
+ vb.updateData(a);
+
+ updateBound();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/debug/WireSphere.java b/engine/src/core/com/jme3/scene/debug/WireSphere.java
new file mode 100644
index 0000000..df863e2
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/debug/WireSphere.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.debug;
+
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.math.FastMath;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class WireSphere extends Mesh {
+
+ private static final int samples = 30;
+ private static final int zSamples = 10;
+
+ public WireSphere() {
+ this(1);
+ }
+
+ public WireSphere(float radius) {
+ updatePositions(radius);
+ ShortBuffer ib = BufferUtils.createShortBuffer(samples * 2 * 2 + zSamples * samples * 2 /*+ 3 * 2*/);
+ setBuffer(Type.Index, 2, ib);
+
+// ib.put(new byte[]{
+// (byte) 0, (byte) 1,
+// (byte) 2, (byte) 3,
+// (byte) 4, (byte) 5,
+// });
+
+// int curNum = 3 * 2;
+ int curNum = 0;
+ for (int j = 0; j < 2 + zSamples; j++) {
+ for (int i = curNum; i < curNum + samples - 1; i++) {
+ ib.put((short) i).put((short) (i + 1));
+ }
+ ib.put((short) (curNum + samples - 1)).put((short) curNum);
+ curNum += samples;
+ }
+
+ setMode(Mode.Lines);
+
+ updateBound();
+ updateCounts();
+ }
+
+ public void updatePositions(float radius) {
+ VertexBuffer pvb = getBuffer(Type.Position);
+ FloatBuffer pb;
+
+ if (pvb == null) {
+ pvb = new VertexBuffer(Type.Position);
+ pb = BufferUtils.createVector3Buffer(samples * 2 + samples * zSamples /*+ 6 * 3*/);
+ pvb.setupData(Usage.Dynamic, 3, Format.Float, pb);
+ setBuffer(pvb);
+ } else {
+ pb = (FloatBuffer) pvb.getData();
+ }
+
+ pb.rewind();
+
+ // X axis
+// pb.put(radius).put(0).put(0);
+// pb.put(-radius).put(0).put(0);
+//
+// // Y axis
+// pb.put(0).put(radius).put(0);
+// pb.put(0).put(-radius).put(0);
+//
+// // Z axis
+// pb.put(0).put(0).put(radius);
+// pb.put(0).put(0).put(-radius);
+
+ float rate = FastMath.TWO_PI / (float) samples;
+ float angle = 0;
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+ pb.put(x).put(y).put(0);
+ angle += rate;
+ }
+
+ angle = 0;
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+ pb.put(0).put(x).put(y);
+ angle += rate;
+ }
+
+ float zRate = (radius * 2) / (float) (zSamples);
+ float zHeight = -radius + (zRate / 2f);
+
+
+ float rb = 1f / zSamples;
+ float b = rb / 2f;
+
+ for (int k = 0; k < zSamples; k++) {
+ angle = 0;
+ float scale = FastMath.sin(b * FastMath.PI);
+ for (int i = 0; i < samples; i++) {
+ float x = radius * FastMath.cos(angle);
+ float y = radius * FastMath.sin(angle);
+
+ pb.put(x * scale).put(zHeight).put(y * scale);
+
+ angle += rate;
+ }
+ zHeight += zRate;
+ b += rb;
+ }
+ }
+
+ /**
+ * Create a WireSphere from a BoundingSphere
+ *
+ * @param bsph
+ * BoundingSphere used to create the WireSphere
+ *
+ */
+ public void fromBoundingSphere(BoundingSphere bsph) {
+ updatePositions(bsph.getRadius());
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
new file mode 100644
index 0000000..5c41c04
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexBuffer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import com.jme3.util.BufferUtils;
+import java.nio.Buffer;
+
+/**
+ * <code>IndexBuffer</code> is an abstraction for integer index buffers,
+ * it is used to retrieve indices without knowing in which format they
+ * are stored (ushort or uint).
+ *
+ * @author lex
+ */
+public abstract class IndexBuffer {
+
+ /**
+ * Creates an index buffer that can contain the given amount
+ * of vertices.
+ * Returns {@link IndexShortBuffer}
+ *
+ * @param vertexCount The amount of vertices to contain
+ * @param indexCount The amount of indices
+ * to contain.
+ * @return A new index buffer
+ */
+ public static IndexBuffer createIndexBuffer(int vertexCount, int indexCount){
+ if (vertexCount > 65535){
+ return new IndexIntBuffer(BufferUtils.createIntBuffer(indexCount));
+ }else{
+ return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount));
+ }
+ }
+
+ /**
+ * Returns the vertex index for the given index in the index buffer.
+ *
+ * @param i The index inside the index buffer
+ * @return
+ */
+ public abstract int get(int i);
+
+ /**
+ * Puts the vertex index at the index buffer's index.
+ * Implementations may throw an {@link UnsupportedOperationException}
+ * if modifying the IndexBuffer is not supported (e.g. virtual index
+ * buffers).
+ */
+ public abstract void put(int i, int value);
+
+ /**
+ * Returns the size of the index buffer.
+ *
+ * @return the size of the index buffer.
+ */
+ public abstract int size();
+
+ /**
+ * Returns the underlying data-type specific {@link Buffer}.
+ * Implementations may return null if there's no underlying
+ * buffer.
+ *
+ * @return the underlying {@link Buffer}.
+ */
+ public abstract Buffer getBuffer();
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
new file mode 100644
index 0000000..fd3bec4
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexByteBuffer.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ByteBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexByteBuffer extends IndexBuffer {
+
+ private ByteBuffer buf;
+
+ public IndexByteBuffer(ByteBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i) & 0x000000FF;
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, (byte) value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
new file mode 100644
index 0000000..3369301
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexIntBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+
+/**
+ * IndexBuffer implementation for {@link IntBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexIntBuffer extends IndexBuffer {
+
+ private IntBuffer buf;
+
+ public IndexIntBuffer(IntBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i);
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
new file mode 100644
index 0000000..5017e6f
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/IndexShortBuffer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.mesh;
+
+import java.nio.Buffer;
+import java.nio.ShortBuffer;
+
+/**
+ * IndexBuffer implementation for {@link ShortBuffer}s.
+ *
+ * @author lex
+ */
+public class IndexShortBuffer extends IndexBuffer {
+
+ private ShortBuffer buf;
+
+ public IndexShortBuffer(ShortBuffer buffer) {
+ this.buf = buffer;
+ }
+
+ @Override
+ public int get(int i) {
+ return buf.get(i) & 0x0000FFFF;
+ }
+
+ @Override
+ public void put(int i, int value) {
+ buf.put(i, (short) value);
+ }
+
+ @Override
+ public int size() {
+ return buf.limit();
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return buf;
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
new file mode 100644
index 0000000..c1499cc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/VirtualIndexBuffer.java
@@ -0,0 +1,106 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh.Mode;
+import java.nio.Buffer;
+
+/**
+ * IndexBuffer implementation that generates vertex indices sequentially
+ * based on a specific Mesh {@link Mode}.
+ * The generated indices are as if the mesh is in the given mode
+ * but contains no index buffer, thus this implementation will
+ * return the indices if the index buffer was there and contained sequential
+ * triangles.
+ * Example:
+ * <ul>
+ * <li>{@link Mode#Triangles}: 0, 1, 2 | 3, 4, 5 | 6, 7, 8 | ...</li>
+ * <li>{@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...</li>
+ * <li>{@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...</li>
+ * </ul>
+ *
+ * @author Kirill Vainer
+ */
+public class VirtualIndexBuffer extends IndexBuffer {
+
+ protected int numVerts = 0;
+ protected int numIndices = 0;
+ protected Mode meshMode;
+
+ public VirtualIndexBuffer(int numVerts, Mode meshMode){
+ this.numVerts = numVerts;
+ this.meshMode = meshMode;
+ switch (meshMode) {
+ case Points:
+ numIndices = numVerts;
+ return;
+ case LineLoop:
+ numIndices = (numVerts - 1) * 2 + 1;
+ return;
+ case LineStrip:
+ numIndices = (numVerts - 1) * 2;
+ return;
+ case Lines:
+ numIndices = numVerts;
+ return;
+ case TriangleFan:
+ numIndices = (numVerts - 2) * 3;
+ return;
+ case TriangleStrip:
+ numIndices = (numVerts - 2) * 3;
+ return;
+ case Triangles:
+ numIndices = numVerts;
+ return;
+ case Hybrid:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int get(int i) {
+ if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){
+ return i;
+ }else if (meshMode == Mode.LineStrip){
+ return (i + 1) / 2;
+ }else if (meshMode == Mode.LineLoop){
+ return (i == (numVerts-1)) ? 0 : ((i + 1) / 2);
+ }else if (meshMode == Mode.TriangleStrip){
+ int triIndex = i/3;
+ int vertIndex = i%3;
+ boolean isBack = (i/3)%2==1;
+ if (!isBack){
+ return triIndex + vertIndex;
+ }else{
+ switch (vertIndex){
+ case 0: return triIndex + 1;
+ case 1: return triIndex;
+ case 2: return triIndex + 2;
+ default: throw new AssertionError();
+ }
+ }
+ }else if (meshMode == Mode.TriangleFan){
+ int vertIndex = i%3;
+ if (vertIndex == 0)
+ return 0;
+ else
+ return (i / 3) + vertIndex;
+ }else{
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public void put(int i, int value) {
+ throw new UnsupportedOperationException("Does not represent index buffer");
+ }
+
+ @Override
+ public int size() {
+ return numIndices;
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return null;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
new file mode 100644
index 0000000..056a1cd
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/WrappedIndexBuffer.java
@@ -0,0 +1,86 @@
+package com.jme3.scene.mesh;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Mesh.Mode;
+import com.jme3.scene.VertexBuffer.Type;
+import java.nio.Buffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * <code>WrappedIndexBuffer</code> converts vertex indices from a non list based
+ * mesh mode such as {@link Mode#TriangleStrip} or {@link Mode#LineLoop}
+ * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}.
+ * As it is often more convenient to read vertex data in list format
+ * than in a non-list format, using this class is recommended to avoid
+ * convoluting classes used to process mesh data from an external source.
+ *
+ * @author Kirill Vainer
+ */
+public class WrappedIndexBuffer extends VirtualIndexBuffer {
+
+ private final IndexBuffer ib;
+
+ public WrappedIndexBuffer(Mesh mesh){
+ super(mesh.getVertexCount(), mesh.getMode());
+ this.ib = mesh.getIndexBuffer();
+ switch (meshMode){
+ case Points:
+ numIndices = mesh.getTriangleCount();
+ break;
+ case Lines:
+ case LineLoop:
+ case LineStrip:
+ numIndices = mesh.getTriangleCount() * 2;
+ break;
+ case Triangles:
+ case TriangleStrip:
+ case TriangleFan:
+ numIndices = mesh.getTriangleCount() * 3;
+ break;
+ default:
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ @Override
+ public int get(int i) {
+ int superIdx = super.get(i);
+ return ib.get(superIdx);
+ }
+
+ @Override
+ public Buffer getBuffer() {
+ return ib.getBuffer();
+ }
+
+ public static void convertToList(Mesh mesh){
+ IndexBuffer inBuf = mesh.getIndicesAsList();
+ IndexBuffer outBuf = IndexBuffer.createIndexBuffer(mesh.getVertexCount(),
+ inBuf.size());
+
+ for (int i = 0; i < inBuf.size(); i++){
+ outBuf.put(i, inBuf.get(i));
+ }
+
+ mesh.clearBuffer(Type.Index);
+ switch (mesh.getMode()){
+ case LineLoop:
+ case LineStrip:
+ mesh.setMode(Mode.Lines);
+ break;
+ case TriangleStrip:
+ case TriangleFan:
+ mesh.setMode(Mode.Triangles);
+ break;
+ default:
+ break;
+ }
+ if (outBuf instanceof IndexIntBuffer){
+ mesh.setBuffer(Type.Index, 3, (IntBuffer)outBuf.getBuffer());
+ }else{
+ mesh.setBuffer(Type.Index, 3, (ShortBuffer)outBuf.getBuffer());
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/mesh/package.html b/engine/src/core/com/jme3/scene/mesh/package.html
new file mode 100644
index 0000000..5362c52
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/mesh/package.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.scene.mesh</code> package contains utilities
+for reading from {@link com.jme3.scene.mesh.IndexBuffer index buffers}.
+Several implementations are provided of the {@link com.jme3.scene.mesh.IndexBuffer}
+class:
+<ul>
+ <li>{@link com.jme3.scene.mesh.IndexByteBuffer} - For reading 8-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.IndexShortBuffer} - For reading 16-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.IndexIntBuffer} - For reading 32-bit index buffers</li>
+ <li>{@link com.jme3.scene.mesh.VirtualIndexBuffer} - For reading "virtual indices", for
+ those meshes that do not have an index buffer</li>
+ <li>{@link com.jme3.scene.mesh.WrappedIndexBuffer} - For converting from
+ non-list based mode indices to list based</li>
+</ul>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/package.html b/engine/src/core/com/jme3/scene/package.html
new file mode 100644
index 0000000..53f8105
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/package.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+
+<head>
+<title></title>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+</head>
+<body>
+
+The <code>com.jme3.input</code> package contains the scene graph implementation
+in jMonkeyEngine.
+
+<p>
+ The scene graph is the most important package in jME, as it is the API
+ used to manage scene elements so that they can be rendered.
+ The {@link com.jme3.scene.Spatial} class provides a common base class
+ for all scene graph elements. The {@link com.jme3.scene.Node} class provides
+ the "branches" in the graph, used to organize elements in a tree
+ hierarchy. The {@link com.jme3.scene.Geometry} is the leaf class that
+ will contain a {@link com.jme3.scene.Mesh} object (geometry data
+ such as vertex positions, normals, etc) and a {@link com.jme3.scene.Material}
+ object containing information on how the geometry should be shaded.
+</p>
+
+</body>
+</html>
diff --git a/engine/src/core/com/jme3/scene/shape/AbstractBox.java b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
new file mode 100644
index 0000000..6c68dc5
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/AbstractBox.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import java.io.IOException;
+
+/**
+ * An eight sided box.
+ * <p>
+ * A {@code Box} is defined by a minimal point and a maximal point. The eight
+ * vertices that make the box are then computed, they are computed in such
+ * a way as to generate an axis-aligned box.
+ * <p>
+ * This class does not control how the geometry data is generated, see {@link Box}
+ * for that.
+ *
+ * @author <a href="mailto:ianp@ianp.org">Ian Phillips</a>
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public abstract class AbstractBox extends Mesh {
+
+ public final Vector3f center = new Vector3f(0f, 0f, 0f);
+
+ public float xExtent, yExtent, zExtent;
+
+ public AbstractBox() {
+ super();
+ }
+
+ /**
+ * Gets the array or vectors representing the 8 vertices of the box.
+ *
+ * @return a newly created array of vertex vectors.
+ */
+ protected final Vector3f[] computeVertices() {
+ Vector3f[] axes = {
+ Vector3f.UNIT_X.mult(xExtent),
+ Vector3f.UNIT_Y.mult(yExtent),
+ Vector3f.UNIT_Z.mult(zExtent)
+ };
+ return new Vector3f[] {
+ center.subtract(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).subtractLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+ center.subtract(axes[0]).addLocal(axes[1]).subtractLocal(axes[2]),
+ center.add(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+ center.subtract(axes[0]).subtractLocal(axes[1]).addLocal(axes[2]),
+ center.add(axes[0]).addLocal(axes[1]).addLocal(axes[2]),
+ center.subtract(axes[0]).addLocal(axes[1]).addLocal(axes[2])
+ };
+ }
+
+ /**
+ * Convert the indices into the list of vertices that define the box's geometry.
+ */
+ protected abstract void duUpdateGeometryIndices();
+
+ /**
+ * Update the normals of each of the box's planes.
+ */
+ protected abstract void duUpdateGeometryNormals();
+
+ /**
+ * Update the points that define the texture of the box.
+ * <p>
+ * 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 face.
+ */
+ protected abstract void duUpdateGeometryTextures();
+
+ /**
+ * Update the position of the vertices that define the box.
+ * <p>
+ * These eight points are determined from the minimum and maximum point.
+ */
+ protected abstract void duUpdateGeometryVertices();
+
+ /**
+ * Get the center point of this box.
+ */
+ public final Vector3f getCenter() {
+ return center;
+ }
+
+ /**
+ * Get the x-axis size (extent) of this box.
+ */
+ public final float getXExtent() {
+ return xExtent;
+ }
+
+ /**
+ * Get the y-axis size (extent) of this box.
+ */
+ public final float getYExtent() {
+ return yExtent;
+ }
+
+ /**
+ * Get the z-axis size (extent) of this box.
+ */
+ public final float getZExtent() {
+ return zExtent;
+ }
+
+ /**
+ * Rebuilds the box after a property has been directly altered.
+ * <p>
+ * For example, if you call {@code getXExtent().x = 5.0f} then you will
+ * need to call this method afterwards in order to update the box.
+ */
+ public final void updateGeometry() {
+ duUpdateGeometryVertices();
+ duUpdateGeometryNormals();
+ duUpdateGeometryTextures();
+ duUpdateGeometryIndices();
+ }
+
+ /**
+ * Rebuilds this box based on a new set of parameters.
+ * <p>
+ * Note that the actual sides will be twice the given extent values because
+ * the box extends in both directions from the center for each extent.
+ *
+ * @param center the center of the box.
+ * @param x the x extent of the box, in each directions.
+ * @param y the y extent of the box, in each directions.
+ * @param z the z extent of the box, in each directions.
+ */
+ public final void updateGeometry(Vector3f center, float x, float y, float z) {
+ if (center != null) {this.center.set(center); }
+ this.xExtent = x;
+ this.yExtent = y;
+ this.zExtent = z;
+ updateGeometry();
+ }
+
+ /**
+ * Rebuilds this box based on a new set of parameters.
+ * <p>
+ * The box is updated so that the two opposite corners are {@code minPoint}
+ * and {@code maxPoint}, the other corners are created from those two positions.
+ *
+ * @param minPoint the new minimum point of the box.
+ * @param maxPoint the new maximum point of the box.
+ */
+ public final void updateGeometry(Vector3f minPoint, Vector3f maxPoint) {
+ center.set(maxPoint).addLocal(minPoint).multLocal(0.5f);
+ float x = maxPoint.x - center.x;
+ float y = maxPoint.y - center.y;
+ float z = maxPoint.z - center.z;
+ updateGeometry(center, x, y, z);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ xExtent = capsule.readFloat("xExtent", 0);
+ yExtent = capsule.readFloat("yExtent", 0);
+ zExtent = capsule.readFloat("zExtent", 0);
+ center.set((Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone()));
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(xExtent, "xExtent", 0);
+ capsule.write(yExtent, "yExtent", 0);
+ capsule.write(zExtent, "zExtent", 0);
+ capsule.write(center, "center", Vector3f.ZERO);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Box.java b/engine/src/core/com/jme3/scene/shape/Box.java
new file mode 100644
index 0000000..307547e
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Box.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Box extends AbstractBox {
+
+ private static final short[] GEOMETRY_INDICES_DATA = {
+ 2, 1, 0, 3, 2, 0, // back
+ 6, 5, 4, 7, 6, 4, // right
+ 10, 9, 8, 11, 10, 8, // front
+ 14, 13, 12, 15, 14, 12, // left
+ 18, 17, 16, 19, 18, 16, // top
+ 22, 21, 20, 23, 22, 20 // bottom
+ };
+
+ private static final float[] GEOMETRY_NORMALS_DATA = {
+ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, // back
+ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // right
+ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // front
+ -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // left
+ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // top
+ 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0 // bottom
+ };
+
+ private static final float[] GEOMETRY_TEXTURE_DATA = {
+ 1, 0, 0, 0, 0, 1, 1, 1, // back
+ 1, 0, 0, 0, 0, 1, 1, 1, // right
+ 1, 0, 0, 0, 0, 1, 1, 1, // front
+ 1, 0, 0, 0, 0, 1, 1, 1, // left
+ 1, 0, 0, 0, 0, 1, 1, 1, // top
+ 1, 0, 0, 0, 0, 1, 1, 1 // bottom
+ };
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has a center of 0,0,0 and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public Box(float x, float y, float z) {
+ super();
+ updateGeometry(Vector3f.ZERO, x, y, z);
+ }
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has the given center and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param center the center of the box.
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public Box(Vector3f center, float x, float y, float z) {
+ super();
+ updateGeometry(center, x, y, z);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Box</code> object.
+ * <p>
+ * 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 {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+ * methods to define those properties.
+ *
+ * @param min the minimum point that defines the box.
+ * @param max the maximum point that defines the box.
+ */
+ public Box(Vector3f min, Vector3f max) {
+ super();
+ updateGeometry(min, max);
+ }
+
+ /**
+ * Empty constructor for serialization only. Do not use.
+ */
+ public Box(){
+ super();
+ }
+
+ /**
+ * Creates a clone of this box.
+ * <p>
+ * The cloned box will have '_clone' appended to it's name, but all other
+ * properties will be the same as this box.
+ */
+ @Override
+ public Box clone() {
+ return new Box(center.clone(), xExtent, yExtent, zExtent);
+ }
+
+ protected void duUpdateGeometryIndices() {
+ if (getBuffer(Type.Index) == null){
+ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryNormals() {
+ if (getBuffer(Type.Normal) == null){
+ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryTextures() {
+ if (getBuffer(Type.TexCoord) == null){
+ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryVertices() {
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(24);
+ Vector3f[] v = computeVertices();
+ fpb.put(new float[] {
+ v[0].x, v[0].y, v[0].z, v[1].x, v[1].y, v[1].z, v[2].x, v[2].y, v[2].z, v[3].x, v[3].y, v[3].z, // back
+ v[1].x, v[1].y, v[1].z, v[4].x, v[4].y, v[4].z, v[6].x, v[6].y, v[6].z, v[2].x, v[2].y, v[2].z, // right
+ v[4].x, v[4].y, v[4].z, v[5].x, v[5].y, v[5].z, v[7].x, v[7].y, v[7].z, v[6].x, v[6].y, v[6].z, // front
+ v[5].x, v[5].y, v[5].z, v[0].x, v[0].y, v[0].z, v[3].x, v[3].y, v[3].z, v[7].x, v[7].y, v[7].z, // left
+ v[2].x, v[2].y, v[2].z, v[6].x, v[6].y, v[6].z, v[7].x, v[7].y, v[7].z, v[3].x, v[3].y, v[3].z, // top
+ v[0].x, v[0].y, v[0].z, v[5].x, v[5].y, v[5].z, v[4].x, v[4].y, v[4].z, v[1].x, v[1].y, v[1].z // bottom
+ });
+ setBuffer(Type.Position, 3, fpb);
+ updateBound();
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Curve.java b/engine/src/core/com/jme3/scene/shape/Curve.java
new file mode 100644
index 0000000..fbeda59
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Curve.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.shape;
+
+import com.jme3.math.Spline;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A <code>Curve</code> is a visual, line-based representation of a {@link Spline}.
+ * The underlying Spline will be sampled N times where N is the number of
+ * segments as specified in the constructor. Each segment will represent
+ * one line in the generated mesh.
+ *
+ * @author Nehon
+ */
+public class Curve extends Mesh {
+
+ private Spline spline;
+ private Vector3f temp = new Vector3f();
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Curve(){
+ }
+
+ /**
+ * Create a curve mesh.
+ * Use a CatmullRom spline model that does not cycle.
+ *
+ * @param controlPoints the control points to use to create this curve
+ * @param nbSubSegments the number of subsegments between the control points
+ */
+ public Curve(Vector3f[] controlPoints, int nbSubSegments) {
+ this(new Spline(Spline.SplineType.CatmullRom, controlPoints, 10, false), nbSubSegments);
+ }
+
+ /**
+ * Create a curve mesh from a Spline
+ *
+ * @param spline the spline to use
+ * @param nbSubSegments the number of subsegments between the control points
+ */
+ public Curve(Spline spline, int nbSubSegments) {
+ super();
+ this.spline = spline;
+ switch (spline.getType()) {
+ case CatmullRom:
+ this.createCatmullRomMesh(nbSubSegments);
+ break;
+ case Bezier:
+ this.createBezierMesh(nbSubSegments);
+ break;
+ case Nurb:
+ this.createNurbMesh(nbSubSegments);
+ break;
+ case Linear:
+ default:
+ this.createLinearMesh();
+ break;
+ }
+ }
+
+ private void createCatmullRomMesh(int nbSubSegments) {
+ float[] array = new float[((spline.getControlPoints().size() - 1) * nbSubSegments + 1) * 3];
+ short[] indices = new short[(spline.getControlPoints().size() - 1) * nbSubSegments * 2];
+ int i = 0;
+ int cptCP = 0;
+ for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+ Vector3f vector3f = it.next();
+ array[i] = vector3f.x;
+ i++;
+ array[i] = vector3f.y;
+ i++;
+ array[i] = vector3f.z;
+ i++;
+ if (it.hasNext()) {
+ for (int j = 1; j < nbSubSegments; j++) {
+ spline.interpolate((float) j / nbSubSegments, cptCP, temp);
+ array[i] = temp.getX();
+ i++;
+ array[i] = temp.getY();
+ i++;
+ array[i] = temp.getZ();
+ i++;
+ }
+ }
+ cptCP++;
+ }
+
+ i = 0;
+ int k = 0;
+ for (int j = 0; j < (spline.getControlPoints().size() - 1) * nbSubSegments; j++) {
+ k = j;
+ indices[i] = (short) k;
+ i++;
+ k++;
+ indices[i] = (short) k;
+ i++;
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);//(spline.getControlPoints().size() - 1) * nbSubSegments * 2
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method creates the Bezier path for this curve.
+ *
+ * @param nbSubSegments
+ * amount of subsegments between position control points
+ */
+ private void createBezierMesh(int nbSubSegments) {
+ if(nbSubSegments==0) {
+ nbSubSegments = 1;
+ }
+ int centerPointsAmount = (spline.getControlPoints().size() + 2) / 3;
+
+ //calculating vertices
+ float[] array = new float[((centerPointsAmount - 1) * nbSubSegments + 1) * 3];
+ int currentControlPoint = 0;
+ List<Vector3f> controlPoints = spline.getControlPoints();
+ int lineIndex = 0;
+ for (int i = 0; i < centerPointsAmount - 1; ++i) {
+ Vector3f vector3f = controlPoints.get(currentControlPoint);
+ array[lineIndex++] = vector3f.x;
+ array[lineIndex++] = vector3f.y;
+ array[lineIndex++] = vector3f.z;
+ for (int j = 1; j < nbSubSegments; ++j) {
+ spline.interpolate((float) j / nbSubSegments, currentControlPoint, temp);
+ array[lineIndex++] = temp.getX();
+ array[lineIndex++] = temp.getY();
+ array[lineIndex++] = temp.getZ();
+ }
+ currentControlPoint += 3;
+ }
+ Vector3f vector3f = controlPoints.get(currentControlPoint);
+ array[lineIndex++] = vector3f.x;
+ array[lineIndex++] = vector3f.y;
+ array[lineIndex++] = vector3f.z;
+
+ //calculating indexes
+ int i = 0, k = 0;
+ short[] indices = new short[(centerPointsAmount - 1) * nbSubSegments << 1];
+ for (int j = 0; j < (centerPointsAmount - 1) * nbSubSegments; ++j) {
+ k = j;
+ indices[i++] = (short) k;
+ ++k;
+ indices[i++] = (short) k;
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method creates the Nurb path for this curve.
+ * @param nbSubSegments
+ * amount of subsegments between position control points
+ */
+ private void createNurbMesh(int nbSubSegments) {
+ float minKnot = spline.getMinNurbKnot();
+ float maxKnot = spline.getMaxNurbKnot();
+ float deltaU = (maxKnot - minKnot)/nbSubSegments;
+
+ float[] array = new float[(nbSubSegments + 1) * 3];
+
+ float u = minKnot;
+ Vector3f interpolationResult = new Vector3f();
+ for(int i=0;i<array.length;i+=3) {
+ spline.interpolate(u, 0, interpolationResult);
+ array[i] = interpolationResult.x;
+ array[i + 1] = interpolationResult.y;
+ array[i + 2] = interpolationResult.z;
+ u += deltaU;
+ }
+
+ //calculating indexes
+ int i = 0;
+ short[] indices = new short[nbSubSegments << 1];
+ for (int j = 0; j < nbSubSegments; ++j) {
+ indices[i++] = (short) j;
+ indices[i++] = (short) (j + 1);
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ private void createLinearMesh() {
+ float[] array = new float[spline.getControlPoints().size() * 3];
+ short[] indices = new short[(spline.getControlPoints().size() - 1) * 2];
+ int i = 0;
+ int cpt = 0;
+ int k = 0;
+ int j = 0;
+ for (Iterator<Vector3f> it = spline.getControlPoints().iterator(); it.hasNext();) {
+ Vector3f vector3f = it.next();
+ array[i] = vector3f.getX();
+ i++;
+ array[i] = vector3f.getY();
+ i++;
+ array[i] = vector3f.getZ();
+ i++;
+ if (it.hasNext()) {
+ k = j;
+ indices[cpt] = (short) k;
+ cpt++;
+ k++;
+ indices[cpt] = (short) k;
+ cpt++;
+ j++;
+ }
+ }
+
+ this.setMode(Mesh.Mode.Lines);
+ this.setBuffer(VertexBuffer.Type.Position, 3, array);
+ this.setBuffer(VertexBuffer.Type.Index, 2, indices);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ /**
+ * This method returns the length of the curve.
+ * @return the length of the curve
+ */
+ public float getLength() {
+ return spline.getTotalLength();
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Cylinder.java b/engine/src/core/com/jme3/scene/shape/Cylinder.java
new file mode 100644
index 0000000..85bb970
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Cylinder.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Cylinder.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+import static com.jme3.util.BufferUtils.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple cylinder, defined by it's height and radius.
+ * (Ported to jME3)
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Cylinder extends Mesh {
+
+ private int axisSamples;
+
+ private int radialSamples;
+
+ private float radius;
+ private float radius2;
+
+ private float height;
+ private boolean closed;
+ private boolean inverted;
+
+ /**
+ * Default constructor for serialization only. Do not use.
+ */
+ 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 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(int axisSamples, int radialSamples,
+ float radius, float height) {
+ this(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 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(int axisSamples, int radialSamples,
+ float radius, float height, boolean closed) {
+ this(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 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(int axisSamples, int radialSamples,
+ float radius, float height, boolean closed, boolean inverted) {
+ this(axisSamples, radialSamples, radius, radius, height, closed, inverted);
+ }
+
+ public Cylinder(int axisSamples, int radialSamples,
+ float radius, float radius2, float height, boolean closed, boolean inverted) {
+ super();
+ updateGeometry(axisSamples, radialSamples, radius, radius2, height, closed, inverted);
+ }
+
+ /**
+ * @return the number of samples along the cylinder axis
+ */
+ public int getAxisSamples() {
+ return axisSamples;
+ }
+
+ /**
+ * @return Returns the height.
+ */
+ public float getHeight() {
+ return height;
+ }
+
+ /**
+ * @return number of samples around cylinder
+ */
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ /**
+ * @return Returns the radius.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ public float getRadius2() {
+ return radius2;
+ }
+
+ /**
+ * @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;
+ }
+
+ /**
+ * Rebuilds the cylinder based on a new set of parameters.
+ *
+ * @param axisSamples the number of samples along the axis.
+ * @param radialSamples the number of samples around the radial.
+ * @param radius the radius of the bottom of the cylinder.
+ * @param radius2 the radius of the top of the cylinder.
+ * @param height the cylinder's height.
+ * @param closed should the cylinder have top and bottom surfaces.
+ * @param inverted is the cylinder is meant to be viewed from the inside.
+ */
+ public void updateGeometry(int axisSamples, int radialSamples,
+ float radius, float radius2, float height, boolean closed, boolean inverted) {
+ this.axisSamples = axisSamples + (closed ? 2 : 0);
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+ this.radius2 = radius2;
+ this.height = height;
+ this.closed = closed;
+ this.inverted = inverted;
+
+// VertexBuffer pvb = getBuffer(Type.Position);
+// VertexBuffer nvb = getBuffer(Type.Normal);
+// VertexBuffer tvb = getBuffer(Type.TexCoord);
+
+ // Vertices
+ int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0);
+
+ setBuffer(Type.Position, 3, createVector3Buffer(getFloatBuffer(Type.Position), vertCount));
+
+ // Normals
+ setBuffer(Type.Normal, 3, createVector3Buffer(getFloatBuffer(Type.Normal), vertCount));
+
+ // Texture co-ordinates
+ setBuffer(Type.TexCoord, 2, createVector2Buffer(vertCount));
+
+ int triCount = ((closed ? 2 : 0) + 2 * (axisSamples - 1)) * radialSamples;
+
+ setBuffer(Type.Index, 3, createShortBuffer(getShortBuffer(Type.Index), 3 * triCount));
+
+ // generate geometry
+ float inverseRadial = 1.0f / radialSamples;
+ float inverseAxisLess = 1.0f / (closed ? axisSamples - 3 : axisSamples - 1);
+ float inverseAxisLessTexture = 1.0f / (axisSamples - 1);
+ float halfHeight = 0.5f * height;
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a cylinder slice.
+ float[] sin = new float[radialSamples + 1];
+ float[] cos = new float[radialSamples + 1];
+
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ float angle = FastMath.TWO_PI * inverseRadial * radialCount;
+ cos[radialCount] = FastMath.cos(angle);
+ sin[radialCount] = FastMath.sin(angle);
+ }
+ sin[radialSamples] = sin[0];
+ cos[radialSamples] = cos[0];
+
+ // calculate normals
+ Vector3f[] vNormals = null;
+ Vector3f vNormal = Vector3f.UNIT_Z;
+
+ if ((height != 0.0f) && (radius != radius2)) {
+ vNormals = new Vector3f[radialSamples];
+ Vector3f vHeight = Vector3f.UNIT_Z.mult(height);
+ Vector3f vRadial = new Vector3f();
+
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ vRadial.set(cos[radialCount], sin[radialCount], 0.0f);
+ Vector3f vRadius = vRadial.mult(radius);
+ Vector3f vRadius2 = vRadial.mult(radius2);
+ Vector3f vMantle = vHeight.subtract(vRadius2.subtract(vRadius));
+ Vector3f vTangent = vRadial.cross(Vector3f.UNIT_Z);
+ vNormals[radialCount] = vMantle.cross(vTangent).normalize();
+ }
+ }
+
+ FloatBuffer nb = getFloatBuffer(Type.Normal);
+ FloatBuffer pb = getFloatBuffer(Type.Position);
+ FloatBuffer tb = getFloatBuffer(Type.TexCoord);
+
+ // generate the cylinder itself
+ Vector3f tempNormal = new Vector3f();
+ for (int axisCount = 0, i = 0; axisCount < axisSamples; axisCount++, i++) {
+ float axisFraction;
+ float 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;
+ }
+ }
+
+ // compute center of slice
+ float z = -halfHeight + height * axisFraction;
+ Vector3f sliceCenter = new Vector3f(0, 0, z);
+
+ // compute slice vertices with duplication at end point
+ int save = i;
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++, i++) {
+ float radialFraction = radialCount * inverseRadial; // in [0,1)
+ tempNormal.set(cos[radialCount], sin[radialCount], 0.0f);
+
+ if (vNormals != null) {
+ vNormal = vNormals[radialCount];
+ } else if (radius == radius2) {
+ vNormal = tempNormal;
+ }
+
+ if (topBottom == 0) {
+ if (!inverted)
+ nb.put(vNormal.x).put(vNormal.y).put(vNormal.z);
+ else
+ nb.put(-vNormal.x).put(-vNormal.y).put(-vNormal.z);
+ } else {
+ nb.put(0).put(0).put(topBottom * (inverted ? -1 : 1));
+ }
+
+ tempNormal.multLocal((radius - radius2) * axisFraction + radius2)
+ .addLocal(sliceCenter);
+ pb.put(tempNormal.x).put(tempNormal.y).put(tempNormal.z);
+
+ tb.put((inverted ? 1 - radialFraction : radialFraction))
+ .put(axisFractionTexture);
+ }
+
+ BufferUtils.copyInternalVector3(pb, save, i);
+ BufferUtils.copyInternalVector3(nb, save, i);
+
+ tb.put((inverted ? 0.0f : 1.0f))
+ .put(axisFractionTexture);
+ }
+
+ if (closed) {
+ pb.put(0).put(0).put(-halfHeight); // bottom center
+ nb.put(0).put(0).put(-1 * (inverted ? -1 : 1));
+ tb.put(0.5f).put(0);
+ pb.put(0).put(0).put(halfHeight); // top center
+ nb.put(0).put(0).put(1 * (inverted ? -1 : 1));
+ tb.put(0.5f).put(1);
+ }
+
+ IndexBuffer ib = getIndexBuffer();
+ int index = 0;
+ // 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) {
+ ib.put(index++, i0++);
+ ib.put(index++, vertCount - 2);
+ ib.put(index++, i1++);
+ } else {
+ ib.put(index++, i0++);
+ ib.put(index++, i1++);
+ ib.put(index++, vertCount - 2);
+ }
+ } else if (closed && axisCount == axisSamples - 2) {
+ ib.put(index++, i2++);
+ ib.put(index++, inverted ? vertCount - 1 : i3++);
+ ib.put(index++, inverted ? i3++ : vertCount - 1);
+ } else {
+ ib.put(index++, i0++);
+ ib.put(index++, inverted ? i2 : i1);
+ ib.put(index++, inverted ? i1 : i2);
+ ib.put(index++, i1++);
+ ib.put(index++, inverted ? i2++ : i3++);
+ ib.put(index++, inverted ? i3++ : i2++);
+ }
+ }
+ }
+
+ updateBound();
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ axisSamples = capsule.readInt("axisSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ radius2 = capsule.readFloat("radius2", 0);
+ height = capsule.readFloat("height", 0);
+ closed = capsule.readBoolean("closed", false);
+ inverted = capsule.readBoolean("inverted", false);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ 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);
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Dome.java b/engine/src/core/com/jme3/scene/shape/Dome.java
new file mode 100644
index 0000000..6f429fc
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Dome.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// $Id: Dome.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A hemisphere.
+ *
+ * @author Peter Andersson
+ * @author Joshua Slack (Original sphere code that was adapted)
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Dome extends Mesh {
+
+ private int planes;
+ private int radialSamples;
+ /** The radius of the dome */
+ private float radius;
+ /** The center of the dome */
+ private Vector3f center;
+ private boolean insideView = true;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Dome() {
+ }
+
+ /**
+ * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin
+ * and only visible from the inside.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * Radius of the dome.
+ * @see #Dome(java.lang.String, com.jme.math.Vector3f, int, int, float)
+ */
+ public Dome(int planes, int radialSamples, float radius) {
+ this(new Vector3f(0, 0, 0), planes, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a dome visible from the inside, e.g. for use as a SkyDome.
+ * All geometry data buffers are updated automatically. <br>
+ * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+ * Increasing planes and radialSamples increase the quality of the dome.
+ *
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * The radius of the dome.
+ */
+ public Dome(Vector3f center, int planes, int radialSamples,
+ float radius) {
+ super();
+ updateGeometry(center, planes, radialSamples, radius, true);
+ }
+
+ /**
+ * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones.
+ * All geometry data buffers are updated automatically. <br>
+ * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2.
+ * Setting higher values for planes and radialSamples increases
+ * the quality of the half-sphere.
+ *
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis. Must be >= 2.
+ * Influences how round the arch of the dome is.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * Influences how round the base of the dome is.
+ * @param radius
+ * The radius of the dome.
+ * @param insideView
+ * If true, the dome is only visible from the inside, like a SkyDome.
+ * If false, the dome is only visible from the outside.
+ */
+ public Dome(Vector3f center, int planes, int radialSamples,
+ float radius, boolean insideView) {
+ super();
+ updateGeometry(center, planes, radialSamples, radius, insideView);
+ }
+
+ public Vector3f getCenter() {
+ return center;
+ }
+
+ /**
+ * Get the number of planar segments along the z-axis of the dome.
+ */
+ public int getPlanes() {
+ return planes;
+ }
+
+ /**
+ * Get the number of samples radially around the main axis of the dome.
+ */
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ /**
+ * Get the radius of the dome.
+ */
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * Are the triangles connected in such a way as to present a view out from the dome or not.
+ */
+ public boolean isInsideView() {
+ return insideView;
+ }
+
+ /**
+ * Rebuilds the dome with a new set of parameters.
+ *
+ * @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 insideView should the dome be set up to be viewed from the inside looking out.
+ */
+ public void updateGeometry(Vector3f center, int planes,
+ int radialSamples, float radius, boolean insideView) {
+ this.insideView = insideView;
+ this.center = center != null ? center : new Vector3f(0, 0, 0);
+ this.planes = planes;
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+
+ int vertCount = ((planes - 1) * (radialSamples + 1)) + 1;
+
+ // Allocate vertices, allocating one extra in each radial to get the
+ // correct texture coordinates
+// setVertexCount();
+// setVertexBuffer(createVector3Buffer(getVertexCount()));
+
+ // allocate normals
+// setNormalBuffer(createVector3Buffer(getVertexCount()));
+
+ // allocate texture coordinates
+// getTextureCoords().set(0, new TexCoords(createVector2Buffer(getVertexCount())));
+
+ FloatBuffer vb = BufferUtils.createVector3Buffer(vertCount);
+ FloatBuffer nb = BufferUtils.createVector3Buffer(vertCount);
+ FloatBuffer tb = BufferUtils.createVector2Buffer(vertCount);
+ setBuffer(Type.Position, 3, vb);
+ setBuffer(Type.Normal, 3, nb);
+ setBuffer(Type.TexCoord, 2, tb);
+
+ // generate geometry
+ float fInvRS = 1.0f / radialSamples;
+ float fYFactor = 1.0f / (planes - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a dome slice.
+ float[] afSin = new float[(radialSamples)];
+ float[] afCos = new float[(radialSamples)];
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fAngle = FastMath.TWO_PI * fInvRS * iR;
+ afCos[iR] = FastMath.cos(fAngle);
+ afSin[iR] = FastMath.sin(fAngle);
+ }
+
+ TempVars vars = TempVars.get();
+ Vector3f tempVc = vars.vect3;
+ Vector3f tempVb = vars.vect2;
+ Vector3f tempVa = vars.vect1;
+
+ // generate the dome itself
+ int i = 0;
+ for (int iY = 0; iY < (planes - 1); iY++, i++) {
+ float fYFraction = fYFactor * iY; // in (0,1)
+ float fY = radius * fYFraction;
+ // compute center of slice
+ Vector3f kSliceCenter = tempVb.set(center);
+ kSliceCenter.y += fY;
+
+ // compute radius of slice
+ float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius - fY * fY));
+
+ // compute slice vertices
+ Vector3f kNormal;
+ int iSave = i;
+ for (int iR = 0; iR < radialSamples; iR++, i++) {
+ float fRadialFraction = iR * fInvRS; // in [0,1)
+ Vector3f kRadial = tempVc.set(afCos[iR], 0, afSin[iR]);
+ kRadial.mult(fSliceRadius, tempVa);
+ vb.put(kSliceCenter.x + tempVa.x).put(
+ kSliceCenter.y + tempVa.y).put(
+ kSliceCenter.z + tempVa.z);
+
+ BufferUtils.populateFromBuffer(tempVa, vb, i);
+ kNormal = tempVa.subtractLocal(center);
+ kNormal.normalizeLocal();
+ if (insideView) {
+ nb.put(kNormal.x).put(kNormal.y).put(kNormal.z);
+ } else {
+ nb.put(-kNormal.x).put(-kNormal.y).put(-kNormal.z);
+ }
+
+ tb.put(fRadialFraction).put(fYFraction);
+ }
+ BufferUtils.copyInternalVector3(vb, iSave, i);
+ BufferUtils.copyInternalVector3(nb, iSave, i);
+ tb.put(1.0f).put(fYFraction);
+ }
+
+ vars.release();
+
+ // pole
+ vb.put(center.x).put(center.y + radius).put(center.z);
+ nb.put(0).put(insideView ? 1 : -1).put(0);
+ tb.put(0.5f).put(1.0f);
+
+ // allocate connectivity
+ int triCount = (planes - 2) * radialSamples * 2 + radialSamples;
+ ShortBuffer ib = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, ib);
+
+ // generate connectivity
+ int index = 0;
+ // Generate only for middle planes
+ for (int plane = 1; plane < (planes - 1); plane++) {
+ int bottomPlaneStart = ((plane - 1) * (radialSamples + 1));
+ int topPlaneStart = (plane * (radialSamples + 1));
+ for (int sample = 0; sample < radialSamples; sample++, index += 6) {
+ if (insideView){
+ ib.put((short) (bottomPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ }else{
+ ib.put((short) (bottomPlaneStart + sample));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (bottomPlaneStart + sample + 1));
+ ib.put((short) (topPlaneStart + sample));
+ ib.put((short) (topPlaneStart + sample + 1));
+ }
+ }
+ }
+
+ // pole triangles
+ int bottomPlaneStart = (planes - 2) * (radialSamples + 1);
+ for (int samples = 0; samples < radialSamples; samples++, index += 3) {
+ if (insideView){
+ ib.put((short) (bottomPlaneStart + samples));
+ ib.put((short) (bottomPlaneStart + samples + 1));
+ ib.put((short) (vertCount - 1));
+ }else{
+ ib.put((short) (bottomPlaneStart + samples));
+ ib.put((short) (vertCount - 1));
+ ib.put((short) (bottomPlaneStart + samples + 1));
+ }
+ }
+
+ updateBound();
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ planes = capsule.readInt("planes", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone());
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(planes, "planes", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(center, "center", Vector3f.ZERO);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Line.java b/engine/src/core/com/jme3/scene/shape/Line.java
new file mode 100644
index 0000000..1edbba7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Line.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ * A simple line implementation with a start and an end.
+ *
+ * @author Brent Owens
+ */
+public class Line extends Mesh {
+
+ private Vector3f start;
+ private Vector3f end;
+
+ public Line() {
+ }
+
+ public Line(Vector3f start, Vector3f end) {
+ setMode(Mode.Lines);
+ updateGeometry(start, end);
+ }
+
+ protected void updateGeometry(Vector3f start, Vector3f end) {
+ this.start = start;
+ this.end = end;
+ setBuffer(Type.Position, 3, new float[]{start.x, start.y, start.z,
+ end.x, end.y, end.z,});
+
+
+ setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+ 1, 1});
+
+ setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+ 0, 0, 1});
+
+ setBuffer(Type.Index, 3, new short[]{0, 1});
+
+ updateBound();
+ }
+
+ /**
+ * Update the start and end points of the line.
+ */
+ public void updatePoints(Vector3f start, Vector3f end) {
+ VertexBuffer posBuf = getBuffer(Type.Position);
+
+ FloatBuffer fb = (FloatBuffer) posBuf.getData();
+
+ fb.put(start.x).put(start.y).put(start.z);
+ fb.put(end.x).put(end.y).put(end.z);
+
+ posBuf.updateData(fb);
+
+ updateBound();
+ }
+
+ public Vector3f getEnd() {
+ return end;
+ }
+
+ public Vector3f getStart() {
+ return start;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule out = ex.getCapsule(this);
+
+ out.write(start, "startVertex", null);
+ out.write(end, "endVertex", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule in = im.getCapsule(this);
+
+ start = (Vector3f) in.readSavable("startVertex", null);
+ end = (Vector3f) in.readSavable("endVertex", null);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/PQTorus.java b/engine/src/core/com/jme3/scene/shape/PQTorus.java
new file mode 100644
index 0000000..26baed7
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/PQTorus.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: PQTorus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import static com.jme3.util.BufferUtils.*;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * A parameterized torus, also known as a <em>pq</em> torus.
+ *
+ * @author Joshua Slack, Eric Woroshow
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class PQTorus extends Mesh {
+
+ private float p, q;
+
+ private float radius, width;
+
+ private int steps, radialSamples;
+
+ public PQTorus() {
+ }
+
+ /**
+ * Creates a parameterized torus.
+ * <p>
+ * Steps and radialSamples are both degree of accuracy values.
+ *
+ * @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(float p, float q, float radius, float width,
+ int steps, int radialSamples) {
+ super();
+ updateGeometry(p, q, radius, width, steps, radialSamples);
+ }
+
+ public float getP() {
+ return p;
+ }
+
+ public float getQ() {
+ return q;
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public int getSteps() {
+ return steps;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ p = capsule.readFloat("p", 0);
+ q = capsule.readFloat("q", 0);
+ radius = capsule.readFloat("radius", 0);
+ width = capsule.readFloat("width", 0);
+ steps = capsule.readInt("steps", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ }
+
+ /**
+ * Rebuilds this torus based on a new set of parameters.
+ *
+ * @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 void updateGeometry(float p, float q, float radius, float width, int steps, int radialSamples) {
+ this.p = p;
+ this.q = q;
+ this.radius = radius;
+ this.width = width;
+ this.steps = steps;
+ this.radialSamples = radialSamples;
+
+ final float thetaStep = (FastMath.TWO_PI / steps);
+ final float betaStep = (FastMath.TWO_PI / radialSamples);
+ Vector3f[] torusPoints = new Vector3f[steps];
+
+ // Allocate all of the required buffers
+ int vertCount = radialSamples * steps;
+
+ FloatBuffer fpb = createVector3Buffer(vertCount);
+ FloatBuffer fnb = createVector3Buffer(vertCount);
+ FloatBuffer ftb = createVector2Buffer(vertCount);
+
+ Vector3f pointB = new Vector3f(), T = new Vector3f(), N = new Vector3f(), B = new Vector3f();
+ Vector3f tempNorm = new Vector3f();
+ float r, x, y, z, theta = 0.0f, beta = 0.0f;
+ int nvertex = 0;
+
+ // Move along the length of the pq torus
+ for (int i = 0; i < steps; i++) {
+ theta += thetaStep;
+ float circleFraction = ((float) i) / (float) steps;
+
+ // Find the point on the torus
+ r = (0.5f * (2.0f + FastMath.sin(q * theta)) * radius);
+ x = (r * FastMath.cos(p * theta) * radius);
+ y = (r * FastMath.sin(p * theta) * radius);
+ z = (r * FastMath.cos(q * theta) * radius);
+ torusPoints[i] = new Vector3f(x, y, z);
+
+ // Now find a point slightly farther along the torus
+ r = (0.5f * (2.0f + FastMath.sin(q * (theta + 0.01f))) * radius);
+ x = (r * FastMath.cos(p * (theta + 0.01f)) * radius);
+ y = (r * FastMath.sin(p * (theta + 0.01f)) * radius);
+ z = (r * FastMath.cos(q * (theta + 0.01f)) * radius);
+ pointB = new Vector3f(x, y, z);
+
+ // Approximate the Frenet Frame
+ T = pointB.subtract(torusPoints[i]);
+ N = torusPoints[i].add(pointB);
+ B = T.cross(N);
+ N = B.cross(T);
+
+ // Normalise the two vectors and then use them to create an oriented circle
+ N = N.normalize();
+ B = B.normalize();
+ beta = 0.0f;
+ for (int j = 0; j < radialSamples; j++, nvertex++) {
+ beta += betaStep;
+ float cx = FastMath.cos(beta) * width;
+ float cy = FastMath.sin(beta) * width;
+ float radialFraction = ((float) j) / radialSamples;
+ tempNorm.x = (cx * N.x + cy * B.x);
+ tempNorm.y = (cx * N.y + cy * B.y);
+ tempNorm.z = (cx * N.z + cy * B.z);
+ fnb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+ tempNorm.addLocal(torusPoints[i]);
+ fpb.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
+ ftb.put(radialFraction).put(circleFraction);
+ }
+ }
+
+ // Update the indices data
+ ShortBuffer sib = createShortBuffer(6 * vertCount);
+ for (int i = 0; i < vertCount; i++) {
+ sib.put(new short[] {
+ (short)(i),
+ (short)(i - radialSamples),
+ (short)(i + 1),
+ (short)(i + 1),
+ (short)(i - radialSamples),
+ (short)(i - radialSamples + 1)
+ });
+ }
+ for (int i = 0, len = sib.capacity(); i < len; i++) {
+ int ind = sib.get(i);
+ if (ind < 0) {
+ ind += vertCount;
+ sib.put(i, (short) ind);
+ } else if (ind >= vertCount) {
+ ind -= vertCount;
+ sib.put(i, (short) ind);
+ }
+ }
+ sib.rewind();
+
+ setBuffer(Type.Position, 3, fpb);
+ setBuffer(Type.Normal, 3, fnb);
+ setBuffer(Type.TexCoord, 2, ftb);
+ setBuffer(Type.Index, 3, sib);
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ 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);
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Quad.java b/engine/src/core/com/jme3/scene/shape/Quad.java
new file mode 100644
index 0000000..1945358
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Quad.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.shape;
+
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+
+/**
+ * <code>Quad</code> represents a rectangular plane in space
+ * defined by 4 vertices. The quad's lower-left side is contained
+ * at the local space origin (0, 0, 0), while the upper-right
+ * side is located at the width/height coordinates (width, height, 0).
+ *
+ * @author Kirill Vainer
+ */
+public class Quad extends Mesh {
+
+ private float width;
+ private float height;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Quad(){
+ }
+
+ /**
+ * Create a quad with the given width and height. The quad
+ * is always created in the XY plane.
+ *
+ * @param width The X extent or width
+ * @param height The Y extent or width
+ */
+ public Quad(float width, float height){
+ updateGeometry(width, height);
+ }
+
+ /**
+ * Create a quad with the given width and height. The quad
+ * is always created in the XY plane.
+ *
+ * @param width The X extent or width
+ * @param height The Y extent or width
+ * @param flipCoords If true, the texture coordinates will be flipped
+ * along the Y axis.
+ */
+ public Quad(float width, float height, boolean flipCoords){
+ updateGeometry(width, height, flipCoords);
+ }
+
+ public float getHeight() {
+ return height;
+ }
+
+ public float getWidth() {
+ return width;
+ }
+
+ public void updateGeometry(float width, float height){
+ updateGeometry(width, height, false);
+ }
+
+ public void updateGeometry(float width, float height, boolean flipCoords) {
+ this.width = width;
+ this.height = height;
+ setBuffer(Type.Position, 3, new float[]{0, 0, 0,
+ width, 0, 0,
+ width, height, 0,
+ 0, height, 0
+ });
+
+
+ if (flipCoords){
+ setBuffer(Type.TexCoord, 2, new float[]{0, 1,
+ 1, 1,
+ 1, 0,
+ 0, 0});
+ }else{
+ setBuffer(Type.TexCoord, 2, new float[]{0, 0,
+ 1, 0,
+ 1, 1,
+ 0, 1});
+ }
+ setBuffer(Type.Normal, 3, new float[]{0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1,
+ 0, 0, 1});
+ if (height < 0){
+ setBuffer(Type.Index, 3, new short[]{0, 2, 1,
+ 0, 3, 2});
+ }else{
+ setBuffer(Type.Index, 3, new short[]{0, 1, 2,
+ 0, 2, 3});
+ }
+
+ updateBound();
+ }
+
+
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Sphere.java b/engine/src/core/com/jme3/scene/shape/Sphere.java
new file mode 100644
index 0000000..f8a5281
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Sphere.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// $Id: Sphere.java 4163 2009-03-25 01:14:55Z matt.yellen $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * <code>Sphere</code> represents a 3D object with all points equidistance
+ * from a center point.
+ *
+ * @author Joshua Slack
+ * @version $Revision: 4163 $, $Date: 2009-03-24 21:14:55 -0400 (Tue, 24 Mar 2009) $
+ */
+public class Sphere extends Mesh {
+
+ public enum TextureMode {
+
+ /**
+ * Wrap texture radially and along z-axis
+ */
+ Original,
+ /**
+ * Wrap texure radially, but spherically project along z-axis
+ */
+ Projected,
+ /**
+ * Apply texture to each pole. Eliminates polar distortion,
+ * but mirrors the texture across the equator
+ */
+ Polar
+ }
+ protected int vertCount;
+ protected int triCount;
+ protected int zSamples;
+ protected int radialSamples;
+ protected boolean useEvenSlices;
+ protected boolean interior;
+ /** the distance from the center point each point falls on */
+ public float radius;
+ protected TextureMode textureMode = TextureMode.Original;
+
+ /**
+ * Serialization only. Do not use.
+ */
+ public Sphere() {
+ }
+
+ /**
+ * Constructs a sphere. All geometry data buffers are updated automatically.
+ * Both zSamples and radialSamples increase the quality of the generated
+ * 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(int zSamples, int radialSamples, float radius) {
+ this(zSamples, radialSamples, radius, false, false);
+ }
+
+ /**
+ * Constructs a sphere. Additional arg to evenly space latitudinal slices
+ *
+ * @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 useEvenSlices
+ * Slice sphere evenly along the Z axis
+ * @param interior
+ * Not yet documented
+ */
+ public Sphere(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {
+ updateGeometry(zSamples, radialSamples, radius, useEvenSlices, interior);
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ public float getRadius() {
+ return radius;
+ }
+
+ /**
+ * @return Returns the textureMode.
+ */
+ public TextureMode getTextureMode() {
+ return textureMode;
+ }
+
+ public int getZSamples() {
+ return zSamples;
+ }
+
+ /**
+ * builds the vertices based on the radius, radial and zSamples.
+ */
+ private void setGeometryData() {
+ // allocate vertices
+ vertCount = (zSamples - 2) * (radialSamples + 1) + 2;
+
+ FloatBuffer posBuf = BufferUtils.createVector3Buffer(vertCount);
+
+ // allocate normals if requested
+ FloatBuffer normBuf = BufferUtils.createVector3Buffer(vertCount);
+
+ // allocate texture coordinates
+ FloatBuffer texBuf = BufferUtils.createVector2Buffer(vertCount);
+
+ setBuffer(Type.Position, 3, posBuf);
+ setBuffer(Type.Normal, 3, normBuf);
+ setBuffer(Type.TexCoord, 2, texBuf);
+
+ // generate geometry
+ float fInvRS = 1.0f / radialSamples;
+ float fZFactor = 2.0f / (zSamples - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a sphere slice.
+ float[] afSin = new float[(radialSamples + 1)];
+ float[] afCos = new float[(radialSamples + 1)];
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fAngle = FastMath.TWO_PI * fInvRS * iR;
+ afCos[iR] = FastMath.cos(fAngle);
+ afSin[iR] = FastMath.sin(fAngle);
+ }
+ afSin[radialSamples] = afSin[0];
+ afCos[radialSamples] = afCos[0];
+
+ TempVars vars = TempVars.get();
+ Vector3f tempVa = vars.vect1;
+ Vector3f tempVb = vars.vect2;
+ Vector3f tempVc = vars.vect3;
+
+ // generate the sphere itself
+ int i = 0;
+ for (int iZ = 1; iZ < (zSamples - 1); iZ++) {
+ float fAFraction = FastMath.HALF_PI * (-1.0f + fZFactor * iZ); // in (-pi/2, pi/2)
+ float fZFraction;
+ if (useEvenSlices) {
+ fZFraction = -1.0f + fZFactor * iZ; // in (-1, 1)
+ } else {
+ fZFraction = FastMath.sin(fAFraction); // in (-1,1)
+ }
+ float fZ = radius * fZFraction;
+
+ // compute center of slice
+ Vector3f kSliceCenter = tempVb.set(Vector3f.ZERO);
+ kSliceCenter.z += fZ;
+
+ // compute radius of slice
+ float fSliceRadius = FastMath.sqrt(FastMath.abs(radius * radius
+ - fZ * fZ));
+
+ // compute slice vertices with duplication at end point
+ Vector3f kNormal;
+ int iSave = i;
+ for (int iR = 0; iR < radialSamples; iR++) {
+ float fRadialFraction = iR * fInvRS; // in [0,1)
+ Vector3f kRadial = tempVc.set(afCos[iR], afSin[iR], 0);
+ kRadial.mult(fSliceRadius, tempVa);
+ posBuf.put(kSliceCenter.x + tempVa.x).put(
+ kSliceCenter.y + tempVa.y).put(
+ kSliceCenter.z + tempVa.z);
+
+ BufferUtils.populateFromBuffer(tempVa, posBuf, i);
+ kNormal = tempVa;
+ kNormal.normalizeLocal();
+ if (!interior) // allow interior texture vs. exterior
+ {
+ normBuf.put(kNormal.x).put(kNormal.y).put(
+ kNormal.z);
+ } else {
+ normBuf.put(-kNormal.x).put(-kNormal.y).put(
+ -kNormal.z);
+ }
+
+ if (textureMode == TextureMode.Original) {
+ texBuf.put(fRadialFraction).put(
+ 0.5f * (fZFraction + 1.0f));
+ } else if (textureMode == TextureMode.Projected) {
+ texBuf.put(fRadialFraction).put(
+ FastMath.INV_PI
+ * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
+ } else if (textureMode == TextureMode.Polar) {
+ float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;
+ float u = r * afCos[iR] + 0.5f;
+ float v = r * afSin[iR] + 0.5f;
+ texBuf.put(u).put(v);
+ }
+
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(posBuf, iSave, i);
+ BufferUtils.copyInternalVector3(normBuf, iSave, i);
+
+ if (textureMode == TextureMode.Original) {
+ texBuf.put(1.0f).put(
+ 0.5f * (fZFraction + 1.0f));
+ } else if (textureMode == TextureMode.Projected) {
+ texBuf.put(1.0f).put(
+ FastMath.INV_PI
+ * (FastMath.HALF_PI + FastMath.asin(fZFraction)));
+ } else if (textureMode == TextureMode.Polar) {
+ float r = (FastMath.HALF_PI - FastMath.abs(fAFraction)) / FastMath.PI;
+ texBuf.put(r + 0.5f).put(0.5f);
+ }
+
+ i++;
+ }
+
+ vars.release();
+
+ // south pole
+ posBuf.position(i * 3);
+ posBuf.put(0f).put(0f).put(-radius);
+
+ normBuf.position(i * 3);
+ if (!interior) {
+ normBuf.put(0).put(0).put(-1); // allow for inner
+ } // texture orientation
+ // later.
+ else {
+ normBuf.put(0).put(0).put(1);
+ }
+
+ texBuf.position(i * 2);
+
+ if (textureMode == TextureMode.Polar) {
+ texBuf.put(0.5f).put(0.5f);
+ } else {
+ texBuf.put(0.5f).put(0.0f);
+ }
+
+ i++;
+
+ // north pole
+ posBuf.put(0).put(0).put(radius);
+
+ if (!interior) {
+ normBuf.put(0).put(0).put(1);
+ } else {
+ normBuf.put(0).put(0).put(-1);
+ }
+
+ if (textureMode == TextureMode.Polar) {
+ texBuf.put(0.5f).put(0.5f);
+ } else {
+ texBuf.put(0.5f).put(1.0f);
+ }
+
+ updateBound();
+ setStatic();
+ }
+
+ /**
+ * sets the indices for rendering the sphere.
+ */
+ private void setIndexData() {
+ // allocate connectivity
+ triCount = 2 * (zSamples - 2) * radialSamples;
+ ShortBuffer idxBuf = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, idxBuf);
+
+ // generate connectivity
+ int index = 0;
+ 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++, index += 6) {
+ if (!interior) {
+ idxBuf.put((short) i0++);
+ idxBuf.put((short) i1);
+ idxBuf.put((short) i2);
+ idxBuf.put((short) i1++);
+ idxBuf.put((short) i3++);
+ idxBuf.put((short) i2++);
+ } else { // inside view
+ idxBuf.put((short) i0++);
+ idxBuf.put((short) i2);
+ idxBuf.put((short) i1);
+ idxBuf.put((short) i1++);
+ idxBuf.put((short) i2++);
+ idxBuf.put((short) i3++);
+ }
+ }
+ }
+
+ // south pole triangles
+ for (int i = 0; i < radialSamples; i++, index += 3) {
+ if (!interior) {
+ idxBuf.put((short) i);
+ idxBuf.put((short) (vertCount - 2));
+ idxBuf.put((short) (i + 1));
+ } else { // inside view
+ idxBuf.put((short) i);
+ idxBuf.put((short) (i + 1));
+ idxBuf.put((short) (vertCount - 2));
+ }
+ }
+
+ // north pole triangles
+ int iOffset = (zSamples - 3) * (radialSamples + 1);
+ for (int i = 0; i < radialSamples; i++, index += 3) {
+ if (!interior) {
+ idxBuf.put((short) (i + iOffset));
+ idxBuf.put((short) (i + 1 + iOffset));
+ idxBuf.put((short) (vertCount - 1));
+ } else { // inside view
+ idxBuf.put((short) (i + iOffset));
+ idxBuf.put((short) (vertCount - 1));
+ idxBuf.put((short) (i + 1 + iOffset));
+ }
+ }
+ }
+
+ /**
+ * @param textureMode
+ * The textureMode to set.
+ */
+ public void setTextureMode(TextureMode textureMode) {
+ this.textureMode = textureMode;
+ setGeometryData();
+ }
+
+ /**
+ * Changes the information of the sphere into the given values.
+ *
+ * @param zSamples the number of zSamples of the sphere.
+ * @param radialSamples the number of radial samples of the sphere.
+ * @param radius the radius of the sphere.
+ */
+ public void updateGeometry(int zSamples, int radialSamples, float radius) {
+ updateGeometry(zSamples, radialSamples, radius, false, false);
+ }
+
+ public void updateGeometry(int zSamples, int radialSamples, float radius, boolean useEvenSlices, boolean interior) {
+ this.zSamples = zSamples;
+ this.radialSamples = radialSamples;
+ this.radius = radius;
+ this.useEvenSlices = useEvenSlices;
+ this.interior = interior;
+ setGeometryData();
+ setIndexData();
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ zSamples = capsule.readInt("zSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ radius = capsule.readFloat("radius", 0);
+ useEvenSlices = capsule.readBoolean("useEvenSlices", false);
+ textureMode = capsule.readEnum("textureMode", TextureMode.class, TextureMode.Original);
+ interior = capsule.readBoolean("interior", false);
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(zSamples, "zSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(radius, "radius", 0);
+ capsule.write(useEvenSlices, "useEvenSlices", false);
+ capsule.write(textureMode, "textureMode", TextureMode.Original);
+ capsule.write(interior, "interior", false);
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/StripBox.java b/engine/src/core/com/jme3/scene/shape/StripBox.java
new file mode 100644
index 0000000..81abff9
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/StripBox.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Box.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.math.Vector3f;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.nio.FloatBuffer;
+
+/**
+ * A box with solid (filled) faces.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class StripBox extends AbstractBox {
+
+ private static final short[] GEOMETRY_INDICES_DATA =
+ { 1, 0, 4,
+ 5,
+ 7,
+ 0,
+ 3,
+ 1,
+ 2,
+ 4,
+ 6,
+ 7,
+ 2,
+ 3 };
+
+ private static final float[] GEOMETRY_TEXTURE_DATA = {
+ 1, 0,
+ 0, 0,
+ 0, 1,
+ 1, 1,
+
+ 1, 0,
+ 0, 0,
+ 1, 1,
+ 0, 1
+ };
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has a center of 0,0,0 and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public StripBox(float x, float y, float z) {
+ super();
+ updateGeometry(Vector3f.ZERO, x, y, z);
+ }
+
+ /**
+ * Creates a new box.
+ * <p>
+ * The box has the given center and extends in the out from the center by
+ * the given amount in <em>each</em> direction. So, for example, a box
+ * with extent of 0.5 would be the unit cube.
+ *
+ * @param center the center of the box.
+ * @param x the size of the box along the x axis, in both directions.
+ * @param y the size of the box along the y axis, in both directions.
+ * @param z the size of the box along the z axis, in both directions.
+ */
+ public StripBox(Vector3f center, float x, float y, float z) {
+ super();
+ updateGeometry(center, x, y, z);
+ }
+
+ /**
+ * Constructor instantiates a new <code>Box</code> object.
+ * <p>
+ * 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 {@link #setLocalTranslation()} and {@link #setLocalRotation()}
+ * methods to define those properties.
+ *
+ * @param min the minimum point that defines the box.
+ * @param max the maximum point that defines the box.
+ */
+ public StripBox(Vector3f min, Vector3f max) {
+ super();
+ updateGeometry(min, max);
+ }
+
+ /**
+ * Empty constructor for serialization only. Do not use.
+ */
+ public StripBox(){
+ super();
+ }
+
+ /**
+ * Creates a clone of this box.
+ * <p>
+ * The cloned box will have ‘_clone’ appended to it’s name, but all other
+ * properties will be the same as this box.
+ */
+ @Override
+ public StripBox clone() {
+ return new StripBox(center.clone(), xExtent, yExtent, zExtent);
+ }
+
+ protected void duUpdateGeometryIndices() {
+ if (getBuffer(Type.Index) == null){
+ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryNormals() {
+ if (getBuffer(Type.Normal) == null){
+ float[] normals = new float[8 * 3];
+
+ Vector3f[] vert = computeVertices();
+ Vector3f norm = new Vector3f();
+
+ for (int i = 0; i < 8; i++) {
+ norm.set(vert[i]).normalizeLocal();
+
+ normals[i * 3 + 0] = norm.x;
+ normals[i * 3 + 1] = norm.x;
+ normals[i * 3 + 2] = norm.x;
+ }
+
+ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals));
+ }
+ }
+
+ protected void duUpdateGeometryTextures() {
+ if (getBuffer(Type.TexCoord) == null){
+ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA));
+ }
+ }
+
+ protected void duUpdateGeometryVertices() {
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3);
+ Vector3f[] v = computeVertices();
+ fpb.put(new float[] {
+ v[0].x, v[0].y, v[0].z,
+ v[1].x, v[1].y, v[1].z,
+ v[2].x, v[2].y, v[2].z,
+ v[3].x, v[3].y, v[3].z,
+ v[4].x, v[4].y, v[4].z,
+ v[5].x, v[5].y, v[5].z,
+ v[6].x, v[6].y, v[6].z,
+ v[7].x, v[7].y, v[7].z,
+ });
+ setBuffer(Type.Position, 3, fpb);
+ setMode(Mode.TriangleStrip);
+ updateBound();
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/scene/shape/Surface.java b/engine/src/core/com/jme3/scene/shape/Surface.java
new file mode 100644
index 0000000..77d259b
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Surface.java
@@ -0,0 +1,283 @@
+package com.jme3.scene.shape;
+
+import com.jme3.math.CurveAndSurfaceMath;
+import com.jme3.math.FastMath;
+import com.jme3.math.Spline.SplineType;
+import com.jme3.math.Vector3f;
+import com.jme3.math.Vector4f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.util.BufferUtils;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class represents a surface described by knots, weights and control points.
+ * Currently the following types are supported:
+ * a) NURBS
+ * @author Marcin Roguski (Kealthas)
+ */
+public class Surface extends Mesh {
+
+ private SplineType type; //the type of the surface
+ private List<List<Vector4f>> controlPoints; //space control points and their weights
+ private List<Float>[] knots; //knots of the surface
+ private int basisUFunctionDegree; //the degree of basis U function
+ private int basisVFunctionDegree; //the degree of basis V function
+ private int uSegments; //the amount of U segments
+ private int vSegments; //the amount of V segments
+
+ /**
+ * Constructor. Constructs required surface.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ * @param basisUFunctionDegree the degree of basis U function
+ * @param basisVFunctionDegree the degree of basis V function
+ */
+ private Surface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {
+ this.validateInputData(controlPoints, nurbKnots, uSegments, vSegments);
+ this.type = SplineType.Nurb;
+ this.uSegments = uSegments;
+ this.vSegments = vSegments;
+ this.controlPoints = controlPoints;
+ this.knots = nurbKnots;
+ this.basisUFunctionDegree = basisUFunctionDegree;
+ CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[0], basisUFunctionDegree);
+ if (nurbKnots[1] != null) {
+ this.basisVFunctionDegree = basisVFunctionDegree;
+ CurveAndSurfaceMath.prepareNurbsKnots(nurbKnots[1], basisVFunctionDegree);
+ }
+
+ this.buildSurface();
+ }
+
+ /**
+ * This method creates a NURBS surface.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ * @param basisUFunctionDegree the degree of basis U function
+ * @param basisVFunctionDegree the degree of basis V function
+ * @return an instance of NURBS surface
+ */
+ public static final Surface createNurbsSurface(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree) {
+ Surface result = new Surface(controlPoints, nurbKnots, uSegments, vSegments, basisUFunctionDegree, basisVFunctionDegree);
+ result.type = SplineType.Nurb;
+ return result;
+ }
+
+ /**
+ * This method creates the surface.
+ */
+ private void buildSurface() {
+ boolean smooth = true;//TODO: take smoothing into consideration
+ float minUKnot = this.getMinUNurbKnot();
+ float maxUKnot = this.getMaxUNurbKnot();
+ float deltaU = (maxUKnot - minUKnot) / uSegments;
+
+ float minVKnot = this.getMinVNurbKnot();
+ float maxVKnot = this.getMaxVNurbKnot();
+ float deltaV = (maxVKnot - minVKnot) / vSegments;
+
+ Vector3f[] vertices = new Vector3f[(uSegments + 1) * (vSegments + 1)];
+
+ float u = minUKnot, v = minVKnot;
+ int arrayIndex = 0;
+
+ for (int i = 0; i <= vSegments; ++i) {
+ for (int j = 0; j <= uSegments; ++j) {
+ Vector3f interpolationResult = new Vector3f();
+ CurveAndSurfaceMath.interpolate(u, v, controlPoints, knots, basisUFunctionDegree, basisVFunctionDegree, interpolationResult);
+ vertices[arrayIndex++] = interpolationResult;
+ u += deltaU;
+ }
+ u = minUKnot;
+ v += deltaV;
+ }
+
+ //adding indexes
+ int uVerticesAmount = uSegments + 1;
+ int[] indices = new int[uSegments * vSegments * 6];
+ arrayIndex = 0;
+ for (int i = 0; i < vSegments; ++i) {
+ for (int j = 0; j < uSegments; ++j) {
+ indices[arrayIndex++] = j + i * uVerticesAmount;
+ indices[arrayIndex++] = j + i * uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;
+ indices[arrayIndex++] = j + i * uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount + 1;
+ indices[arrayIndex++] = j + i * uVerticesAmount + uVerticesAmount;
+ }
+ }
+
+ //normalMap merges normals of faces that will be rendered smooth
+ Map<Vector3f, Vector3f> normalMap = new HashMap<Vector3f, Vector3f>(vertices.length);
+ for (int i = 0; i < indices.length; i += 3) {
+ Vector3f n = FastMath.computeNormal(vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
+ this.addNormal(n, normalMap, smooth, vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]]);
+ }
+ //preparing normal list (the order of normals must match the order of vertices)
+ float[] normals = new float[vertices.length * 3];
+ arrayIndex = 0;
+ for (int i = 0; i < vertices.length; ++i) {
+ Vector3f n = normalMap.get(vertices[i]);
+ normals[arrayIndex++] = n.x;
+ normals[arrayIndex++] = n.y;
+ normals[arrayIndex++] = n.z;
+ }
+
+ this.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
+ this.setBuffer(VertexBuffer.Type.Index, 3, indices);
+ this.setBuffer(VertexBuffer.Type.Normal, 3, normals);
+ this.updateBound();
+ this.updateCounts();
+ }
+
+ public List<List<Vector4f>> getControlPoints() {
+ return controlPoints;
+ }
+
+ /**
+ * This method returns the amount of U control points.
+ * @return the amount of U control points
+ */
+ public int getUControlPointsAmount() {
+ return controlPoints.size();
+ }
+
+ /**
+ * This method returns the amount of V control points.
+ * @return the amount of V control points
+ */
+ public int getVControlPointsAmount() {
+ return controlPoints.get(0) == null ? 0 : controlPoints.get(0).size();
+ }
+
+ /**
+ * This method returns the degree of basis U function.
+ * @return the degree of basis U function
+ */
+ public int getBasisUFunctionDegree() {
+ return basisUFunctionDegree;
+ }
+
+ /**
+ * This method returns the degree of basis V function.
+ * @return the degree of basis V function
+ */
+ public int getBasisVFunctionDegree() {
+ return basisVFunctionDegree;
+ }
+
+ /**
+ * This method returns the knots for specified dimension (U knots - value: '0',
+ * V knots - value: '1').
+ * @param dim an integer specifying if the U or V knots are required
+ * @return an array of knots
+ */
+ public List<Float> getKnots(int dim) {
+ return knots[dim];
+ }
+
+ /**
+ * This method returns the type of the surface.
+ * @return the type of the surface
+ */
+ public SplineType getType() {
+ return type;
+ }
+
+ /**
+ * This method returns the minimum nurb curve U knot value.
+ * @return the minimum nurb curve knot value
+ */
+ private float getMinUNurbKnot() {
+ return knots[0].get(basisUFunctionDegree - 1);
+ }
+
+ /**
+ * This method returns the maximum nurb curve U knot value.
+ * @return the maximum nurb curve knot value
+ */
+ private float getMaxUNurbKnot() {
+ return knots[0].get(knots[0].size() - basisUFunctionDegree);
+ }
+
+ /**
+ * This method returns the minimum nurb curve U knot value.
+ * @return the minimum nurb curve knot value
+ */
+ private float getMinVNurbKnot() {
+ return knots[1].get(basisVFunctionDegree - 1);
+ }
+
+ /**
+ * This method returns the maximum nurb curve U knot value.
+ * @return the maximum nurb curve knot value
+ */
+ private float getMaxVNurbKnot() {
+ return knots[1].get(knots[1].size() - basisVFunctionDegree);
+ }
+
+ /**
+ * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth.
+ * @param normalToAdd
+ * a normal to be added
+ * @param normalMap
+ * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector
+ * @param smooth
+ * the variable that indicates wheather to merge normals (creating the smooth mesh) or not
+ * @param vertices
+ * a list of vertices read from the blender file
+ */
+ private void addNormal(Vector3f normalToAdd, Map<Vector3f, Vector3f> normalMap, boolean smooth, Vector3f... vertices) {
+ for (Vector3f v : vertices) {
+ Vector3f n = normalMap.get(v);
+ if (!smooth || n == null) {
+ normalMap.put(v, normalToAdd.clone());
+ } else {
+ n.addLocal(normalToAdd).normalizeLocal();
+ }
+ }
+ }
+
+ /**
+ * This method validates the input data. It throws {@link IllegalArgumentException} if
+ * the data is invalid.
+ * @param controlPoints space control points
+ * @param nurbKnots knots of the surface
+ * @param uSegments the amount of U segments
+ * @param vSegments the amount of V segments
+ */
+ private void validateInputData(List<List<Vector4f>> controlPoints, List<Float>[] nurbKnots,
+ int uSegments, int vSegments) {
+ int uPointsAmount = controlPoints.get(0).size();
+ for (int i = 1; i < controlPoints.size(); ++i) {
+ if (controlPoints.get(i).size() != uPointsAmount) {
+ throw new IllegalArgumentException("The amount of 'U' control points is invalid!");
+ }
+ }
+ if (uSegments <= 0) {
+ throw new IllegalArgumentException("U segments amount should be positive!");
+ }
+ if (vSegments < 0) {
+ throw new IllegalArgumentException("V segments amount cannot be negative!");
+ }
+ if (nurbKnots.length != 2) {
+ throw new IllegalArgumentException("Nurb surface should have two rows of knots!");
+ }
+ for (int i = 0; i < nurbKnots.length; ++i) {
+ for (int j = 0; j < nurbKnots[i].size() - 1; ++j) {
+ if (nurbKnots[i].get(j) > nurbKnots[i].get(j + 1)) {
+ throw new IllegalArgumentException("The knots' values cannot decrease!");
+ }
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/scene/shape/Torus.java b/engine/src/core/com/jme3/scene/shape/Torus.java
new file mode 100644
index 0000000..8b9285a
--- /dev/null
+++ b/engine/src/core/com/jme3/scene/shape/Torus.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// $Id: Torus.java 4131 2009-03-19 20:15:28Z blaine.dev $
+package com.jme3.scene.shape;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * An ordinary (single holed) torus.
+ * <p>
+ * The center is by default the origin.
+ *
+ * @author Mark Powell
+ * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $
+ */
+public class Torus extends Mesh {
+
+ private int circleSamples;
+
+ private int radialSamples;
+
+ private float innerRadius;
+
+ private float outerRadius;
+
+ public Torus() {
+ }
+
+ /**
+ * Constructs a new Torus. Center is the origin, but the Torus may be
+ * transformed.
+ *
+ * @param circleSamples
+ * The number of samples along the circles.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param innerRadius
+ * The radius of the inner begining of the Torus.
+ * @param outerRadius
+ * The radius of the outter end of the Torus.
+ */
+ public Torus(int circleSamples, int radialSamples,
+ float innerRadius, float outerRadius) {
+ super();
+ updateGeometry(circleSamples, radialSamples, innerRadius, outerRadius);
+ }
+
+ public int getCircleSamples() {
+ return circleSamples;
+ }
+
+ public float getInnerRadius() {
+ return innerRadius;
+ }
+
+ public float getOuterRadius() {
+ return outerRadius;
+ }
+
+ public int getRadialSamples() {
+ return radialSamples;
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ circleSamples = capsule.readInt("circleSamples", 0);
+ radialSamples = capsule.readInt("radialSamples", 0);
+ innerRadius = capsule.readFloat("innerRadius", 0);
+ outerRadius = capsule.readFloat("outerRaidus", 0);
+ }
+
+ private void setGeometryData() {
+ // allocate vertices
+ int vertCount = (circleSamples + 1) * (radialSamples + 1);
+ FloatBuffer fpb = BufferUtils.createVector3Buffer(vertCount);
+ setBuffer(Type.Position, 3, fpb);
+
+ // allocate normals if requested
+ FloatBuffer fnb = BufferUtils.createVector3Buffer(vertCount);
+ setBuffer(Type.Normal, 3, fnb);
+
+ // allocate texture coordinates
+ FloatBuffer ftb = BufferUtils.createVector2Buffer(vertCount);
+ setBuffer(Type.TexCoord, 2, ftb);
+
+ // generate geometry
+ float inverseCircleSamples = 1.0f / circleSamples;
+ float inverseRadialSamples = 1.0f / radialSamples;
+ int i = 0;
+ // generate the cylinder itself
+ Vector3f radialAxis = new Vector3f(), torusMiddle = new Vector3f(), tempNormal = new Vector3f();
+ for (int circleCount = 0; circleCount < circleSamples; circleCount++) {
+ // compute center point on torus circle at specified angle
+ float circleFraction = circleCount * inverseCircleSamples;
+ float theta = FastMath.TWO_PI * circleFraction;
+ float cosTheta = FastMath.cos(theta);
+ float sinTheta = FastMath.sin(theta);
+ radialAxis.set(cosTheta, sinTheta, 0);
+ radialAxis.mult(outerRadius, torusMiddle);
+
+ // compute slice vertices with duplication at end point
+ int iSave = i;
+ for (int radialCount = 0; radialCount < radialSamples; radialCount++) {
+ float radialFraction = radialCount * inverseRadialSamples;
+ // in [0,1)
+ float phi = FastMath.TWO_PI * radialFraction;
+ float cosPhi = FastMath.cos(phi);
+ float sinPhi = FastMath.sin(phi);
+ tempNormal.set(radialAxis).multLocal(cosPhi);
+ tempNormal.z += sinPhi;
+ fnb.put(tempNormal.x).put(tempNormal.y).put(
+ tempNormal.z);
+
+ tempNormal.multLocal(innerRadius).addLocal(torusMiddle);
+ fpb.put(tempNormal.x).put(tempNormal.y).put(
+ tempNormal.z);
+
+ ftb.put(radialFraction).put(circleFraction);
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(fpb, iSave, i);
+ BufferUtils.copyInternalVector3(fnb, iSave, i);
+
+ ftb.put(1.0f).put(circleFraction);
+
+ i++;
+ }
+
+ // duplicate the cylinder ends to form a torus
+ for (int iR = 0; iR <= radialSamples; iR++, i++) {
+ BufferUtils.copyInternalVector3(fpb, iR, i);
+ BufferUtils.copyInternalVector3(fnb, iR, i);
+ BufferUtils.copyInternalVector2(ftb, iR, i);
+ ftb.put(i * 2 + 1, 1.0f);
+ }
+ }
+
+ private void setIndexData() {
+ // allocate connectivity
+ int triCount = 2 * circleSamples * radialSamples;
+
+ ShortBuffer sib = BufferUtils.createShortBuffer(3 * triCount);
+ setBuffer(Type.Index, 3, sib);
+
+ int i;
+ // generate connectivity
+ int connectionStart = 0;
+ int index = 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++, index += 6) {
+// if (true) {
+ sib.put((short)i0++);
+ sib.put((short)i2);
+ sib.put((short)i1);
+ sib.put((short)i1++);
+ sib.put((short)i2++);
+ sib.put((short)i3++);
+
+// getIndexBuffer().put(i0++);
+// getIndexBuffer().put(i2);
+// getIndexBuffer().put(i1);
+// getIndexBuffer().put(i1++);
+// getIndexBuffer().put(i2++);
+// getIndexBuffer().put(i3++);
+// } else {
+// getIndexBuffer().put(i0++);
+// getIndexBuffer().put(i1);
+// getIndexBuffer().put(i2);
+// getIndexBuffer().put(i1++);
+// getIndexBuffer().put(i3++);
+// getIndexBuffer().put(i2++);
+// }
+ }
+ }
+ }
+
+ /**
+ * Rebuilds this torus based on a new set of parameters.
+ *
+ * @param circleSamples the number of samples along the circles.
+ * @param radialSamples the number of samples along the radial.
+ * @param innerRadius the radius of the inner begining of the Torus.
+ * @param outerRadius the radius of the outter end of the Torus.
+ */
+ public void updateGeometry(int circleSamples, int radialSamples, float innerRadius, float outerRadius) {
+ this.circleSamples = circleSamples;
+ this.radialSamples = radialSamples;
+ this.innerRadius = innerRadius;
+ this.outerRadius = outerRadius;
+ setGeometryData();
+ setIndexData();
+ updateBound();
+ updateCounts();
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(circleSamples, "circleSamples", 0);
+ capsule.write(radialSamples, "radialSamples", 0);
+ capsule.write(innerRadius, "innerRadius", 0);
+ capsule.write(outerRadius, "outerRadius", 0);
+ }
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/shader/Attribute.java b/engine/src/core/com/jme3/shader/Attribute.java
new file mode 100644
index 0000000..4b6a095
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Attribute.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+/**
+ * An attribute is a shader variable mapping to a VertexBuffer data
+ * on the CPU.
+ *
+ * @author Kirill Vainer
+ */
+public class Attribute extends ShaderVariable {
+}
diff --git a/engine/src/core/com/jme3/shader/DefineList.java b/engine/src/core/com/jme3/shader/DefineList.java
new file mode 100644
index 0000000..abd3661
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/DefineList.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.*;
+import java.io.IOException;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+public class DefineList implements Savable {
+
+ private final SortedMap<String, String> defines = new TreeMap<String, String>();
+ private String compiled = null;
+
+ public void write(JmeExporter ex) throws IOException{
+ OutputCapsule oc = ex.getCapsule(this);
+
+ String[] keys = new String[defines.size()];
+ String[] vals = new String[defines.size()];
+
+ int i = 0;
+ for (Map.Entry<String, String> define : defines.entrySet()){
+ keys[i] = define.getKey();
+ vals[i] = define.getValue();
+ i++;
+ }
+
+ oc.write(keys, "keys", null);
+ oc.write(vals, "vals", null);
+
+ // for compatability only with older versions
+ oc.write(compiled, "compiled", null);
+ }
+
+ public void read(JmeImporter im) throws IOException{
+ InputCapsule ic = im.getCapsule(this);
+
+ String[] keys = ic.readStringArray("keys", null);
+ String[] vals = ic.readStringArray("vals", null);
+ for (int i = 0; i < keys.length; i++){
+ defines.put(keys[i], vals[i]);
+ }
+
+ compiled = ic.readString("compiled", null);
+ }
+
+ public void clear() {
+ defines.clear();
+ compiled = "";
+ }
+
+ public String get(String key){
+ // I do not see the point of forcing a rebuild on get()
+ // so I'm commenting it out. -pspeed
+ //compiled = null;
+ return defines.get(key);
+ }
+
+// public void set(String key, String val){
+// compiled = null;
+// defines.put(key, val);
+// }
+
+ public boolean set(String key, VarType type, Object val){
+ if (val == null){
+ defines.remove(key);
+ compiled = null;
+ return true;
+ }
+
+ switch (type){
+ case Boolean:
+ if ( ((Boolean) val).booleanValue() ) {
+ // same literal, != should work
+ if( defines.put(key, "1") != "1" ) {
+ compiled = null;
+ return true;
+ }
+ } else if (defines.containsKey(key)) {
+ defines.remove(key);
+ compiled = null;
+ return true;
+ }
+
+ break;
+ case Float:
+ case Int:
+ String original = defines.put(key, val.toString());
+ if (!val.equals(original)) {
+ compiled = null;
+ return true;
+ }
+ break;
+ default:
+ // same literal, != should work
+ if (defines.put(key, "1") != "1") {
+ compiled = null;
+ return true;
+ }
+ break;
+ }
+
+ return false;
+ }
+
+ public boolean remove(String key){
+ if (defines.remove(key) != null) {
+ compiled = null;
+ return true;
+ }
+
+ return false;
+ }
+
+ public void addFrom(DefineList other){
+ if (other == null)
+ return;
+
+ compiled = null;
+ defines.putAll(other.defines);
+ }
+
+ public String getCompiled(){
+ if (compiled == null){
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, String> entry : defines.entrySet()){
+ sb.append("#define ").append(entry.getKey()).append(" ");
+ sb.append(entry.getValue()).append('\n');
+ }
+ compiled = sb.toString();
+ }
+ return compiled;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
+ for (Map.Entry<String, String> entry : defines.entrySet()) {
+ sb.append(entry.getKey());
+ if (i != defines.size() - 1)
+ sb.append(", ");
+
+ i++;
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/Shader.java b/engine/src/core/com/jme3/shader/Shader.java
new file mode 100644
index 0000000..265eed6
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Shader.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.*;
+import com.jme3.renderer.Renderer;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.util.IntMap;
+import com.jme3.util.IntMap.Entry;
+import com.jme3.util.ListMap;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+public final class Shader extends NativeObject implements Savable {
+
+ private String language;
+
+ /**
+ * True if the shader is fully compiled & linked.
+ * (e.g no GL error will be invoked if used).
+ */
+ private boolean usable = false;
+
+ /**
+ * A list of all shaders currently attached.
+ */
+ private ArrayList<ShaderSource> shaderList;
+
+ /**
+ * Maps uniform name to the uniform variable.
+ */
+// private HashMap<String, Uniform> uniforms;
+ private ListMap<String, Uniform> uniforms;
+
+ /**
+ * Maps attribute name to the location of the attribute in the shader.
+ */
+ private IntMap<Attribute> attribs;
+
+ /**
+ * Type of shader. The shader will control the pipeline of it's type.
+ */
+ public static enum ShaderType {
+ /**
+ * Control fragment rasterization. (e.g color of pixel).
+ */
+ Fragment,
+
+ /**
+ * Control vertex processing. (e.g transform of model to clip space)
+ */
+ Vertex,
+
+ /**
+ * Control geometry assembly. (e.g compile a triangle list from input data)
+ */
+ Geometry;
+ }
+
+ /**
+ * Shader source describes a shader object in OpenGL. Each shader source
+ * is assigned a certain pipeline which it controls (described by it's type).
+ */
+ public static class ShaderSource extends NativeObject implements Savable {
+
+ ShaderType shaderType;
+
+ boolean usable = false;
+ String name = null;
+ String source = null;
+ String defines = null;
+
+ public ShaderSource(ShaderType type){
+ super(ShaderSource.class);
+ this.shaderType = type;
+ if (type == null)
+ throw new NullPointerException("The shader type must be specified");
+ }
+
+ protected ShaderSource(ShaderSource ss){
+ super(ShaderSource.class, ss.id);
+ this.shaderType = ss.shaderType;
+ usable = false;
+ name = ss.name;
+ // forget source & defines
+ }
+
+ public ShaderSource(){
+ super(ShaderSource.class);
+ }
+
+ public void write(JmeExporter ex) throws IOException{
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(shaderType, "shaderType", null);
+ oc.write(name, "name", null);
+ oc.write(source, "source", null);
+ oc.write(defines, "defines", null);
+ }
+
+ public void read(JmeImporter im) throws IOException{
+ InputCapsule ic = im.getCapsule(this);
+ shaderType = ic.readEnum("shaderType", ShaderType.class, null);
+ name = ic.readString("name", null);
+ source = ic.readString("source", null);
+ defines = ic.readString("defines", null);
+ }
+
+ public void setName(String name){
+ this.name = name;
+ }
+
+ public String getName(){
+ return name;
+ }
+
+ public ShaderType getType() {
+ return shaderType;
+ }
+
+ public void setSource(String source){
+ if (source == null)
+ throw new NullPointerException("Shader source cannot be null");
+
+ this.source = source;
+ setUpdateNeeded();
+ }
+
+ public void setDefines(String defines){
+ if (defines == null)
+ throw new NullPointerException("Shader defines cannot be null");
+
+ this.defines = defines;
+ setUpdateNeeded();
+ }
+
+ public String getSource(){
+ return source;
+ }
+
+ public String getDefines(){
+ return defines;
+ }
+
+ public boolean isUsable(){
+ return usable;
+ }
+
+ public void setUsable(boolean usable){
+ this.usable = usable;
+ }
+
+ @Override
+ public String toString(){
+ String nameTxt = "";
+ if (name != null)
+ nameTxt = "name="+name+", ";
+ if (defines != null)
+ nameTxt += "defines, ";
+
+
+ return getClass().getSimpleName() + "["+nameTxt+"type="
+ + shaderType.name()+"]";
+ }
+
+ public void resetObject(){
+ id = -1;
+ usable = false;
+ setUpdateNeeded();
+ }
+
+ public void deleteObject(Object rendererObject){
+ ((Renderer)rendererObject).deleteShaderSource(ShaderSource.this);
+ }
+
+ public NativeObject createDestructableClone(){
+ return new ShaderSource(ShaderSource.this);
+ }
+ }
+
+ /**
+ * Create an empty shader.
+ */
+ public Shader(String language){
+ super(Shader.class);
+ this.language = language;
+ shaderList = new ArrayList<ShaderSource>();
+// uniforms = new HashMap<String, Uniform>();
+ uniforms = new ListMap<String, Uniform>();
+ attribs = new IntMap<Attribute>();
+ }
+
+ /**
+ * Do not use this constructor. Serialization purposes only.
+ */
+ public Shader(){
+ super(Shader.class);
+ }
+
+ protected Shader(Shader s){
+ super(Shader.class, s.id);
+ shaderList = new ArrayList<ShaderSource>();
+ //uniforms = new ListMap<String, Uniform>();
+ //attribs = new IntMap<Attribute>();
+
+ // NOTE: Because ShaderSources are registered separately with
+ // the GLObjectManager
+ for (ShaderSource source : s.shaderList){
+ shaderList.add( (ShaderSource)source.createDestructableClone() );
+ }
+ }
+
+ public void write(JmeExporter ex) throws IOException{
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(language, "language", null);
+ oc.writeSavableArrayList(shaderList, "shaderList", null);
+ oc.writeIntSavableMap(attribs, "attribs", null);
+ oc.writeStringSavableMap(uniforms, "uniforms", null);
+ }
+
+ public void read(JmeImporter im) throws IOException{
+ InputCapsule ic = im.getCapsule(this);
+ language = ic.readString("language", null);
+ shaderList = ic.readSavableArrayList("shaderList", null);
+ attribs = (IntMap<Attribute>) ic.readIntSavableMap("attribs", null);
+
+ HashMap<String, Uniform> uniMap = (HashMap<String, Uniform>) ic.readStringSavableMap("uniforms", null);
+ uniforms = new ListMap<String, Uniform>(uniMap);
+ }
+
+ /**
+ * Creates a deep clone of the shader, where the sources are available
+ * but have not been compiled yet. Does not copy the uniforms or attribs.
+ * @return
+ */
+// public Shader createDeepClone(String defines){
+// Shader newShader = new Shader(language);
+// for (ShaderSource source : shaderList){
+// if (!source.getDefines().equals(defines)){
+// // need to clone the shadersource so
+// // the correct defines can be placed
+// ShaderSource newSource = new ShaderSource(source.getType());
+// newSource.setSource(source.getSource());
+// newSource.setDefines(defines);
+// newShader.addSource(newSource);
+// }else{
+// // no need to clone source, also saves
+// // having to compile the shadersource
+// newShader.addSource(source);
+// }
+// }
+// return newShader;
+// }
+
+ /**
+ * Adds source code to a certain pipeline.
+ *
+ * @param type The pipeline to control
+ * @param source The shader source code (in GLSL).
+ */
+ public void addSource(ShaderType type, String name, String source, String defines){
+ ShaderSource shader = new ShaderSource(type);
+ shader.setSource(source);
+ shader.setName(name);
+ if (defines != null)
+ shader.setDefines(defines);
+
+ shaderList.add(shader);
+ setUpdateNeeded();
+ }
+
+ public void addSource(ShaderType type, String source, String defines){
+ addSource(type, null, source, defines);
+ }
+
+ public void addSource(ShaderType type, String source){
+ addSource(type, source, null);
+ }
+
+ /**
+ * Adds an existing shader source to this shader.
+ * @param source
+ */
+ private void addSource(ShaderSource source){
+ shaderList.add(source);
+ setUpdateNeeded();
+ }
+
+ public Uniform getUniform(String name){
+ Uniform uniform = uniforms.get(name);
+ if (uniform == null){
+ uniform = new Uniform();
+ uniform.name = name;
+ uniforms.put(name, uniform);
+ }
+ return uniform;
+ }
+
+ public void removeUniform(String name){
+ uniforms.remove(name);
+ }
+
+ public Attribute getAttribute(VertexBuffer.Type attribType){
+ int ordinal = attribType.ordinal();
+ Attribute attrib = attribs.get(ordinal);
+ if (attrib == null){
+ attrib = new Attribute();
+ attrib.name = attribType.name();
+ attribs.put(ordinal, attrib);
+ }
+ return attrib;
+ }
+
+// public Collection<Uniform> getUniforms(){
+// return uniforms.values();
+// }
+
+ public ListMap<String, Uniform> getUniformMap(){
+ return uniforms;
+ }
+
+// public Collection<Attribute> getAttributes() {
+// return attribs.
+// }
+
+ public Collection<ShaderSource> getSources(){
+ return shaderList;
+ }
+
+ public String getLanguage(){
+ return language;
+ }
+
+ @Override
+ public String toString(){
+ return getClass().getSimpleName() + "[language="+language
+ + ", numSources="+shaderList.size()
+ + ", numUniforms="+uniforms.size()
+ + ", shaderSources="+getSources()+"]";
+ }
+
+ /**
+ * Clears all sources. Assuming that they have already been detached and
+ * removed on the GL side.
+ */
+ public void resetSources(){
+ shaderList.clear();
+ }
+
+ /**
+ * Returns true if this program and all it's shaders have been compiled,
+ * linked and validated successfuly.
+ */
+ public boolean isUsable(){
+ return usable;
+ }
+
+ /**
+ * Sets if the program can be used. Should only be called by the Renderer.
+ * @param usable
+ */
+ public void setUsable(boolean usable){
+ this.usable = usable;
+ }
+
+ /**
+ * Usually called when the shader itself changes or during any
+ * time when the var locations need to be refreshed.
+ */
+ public void resetLocations(){
+ // NOTE: Shader sources will be reset seperately from the shader itself.
+ for (Uniform uniform : uniforms.values()){
+ uniform.reset(); // fixes issue with re-initialization
+ }
+ for (Entry<Attribute> entry : attribs){
+ entry.getValue().location = -2;
+ }
+ }
+
+ @Override
+ public void setUpdateNeeded(){
+ super.setUpdateNeeded();
+ resetLocations();
+ }
+
+ /**
+ * Called by the object manager to reset all object IDs. This causes
+ * the shader to be reuploaded to the GPU incase the display was restarted.
+ */
+ @Override
+ public void resetObject() {
+ this.id = -1;
+ this.usable = false;
+
+ for (ShaderSource source : shaderList){
+ source.resetObject();
+ }
+
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((Renderer)rendererObject).deleteShader(this);
+ }
+
+ public NativeObject createDestructableClone(){
+ return new Shader(this);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/ShaderKey.java b/engine/src/core/com/jme3/shader/ShaderKey.java
new file mode 100644
index 0000000..a4c18eb
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderKey.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+public class ShaderKey extends AssetKey<Shader> {
+
+ protected String fragName;
+ protected DefineList defines;
+ protected String language;
+
+ public ShaderKey(){
+ }
+
+ public ShaderKey(String vertName, String fragName, DefineList defines, String lang){
+ super(vertName);
+ this.fragName = fragName;
+ this.defines = defines;
+ this.language = lang;
+ }
+
+ @Override
+ public String toString(){
+ return "V="+name + " F=" + fragName + (defines != null ? defines : "");
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null){
+ return false;
+ }
+ if (getClass() != obj.getClass()){
+ return false;
+ }
+
+ final ShaderKey other = (ShaderKey) obj;
+ if (name.equals(other.name) && fragName.equals(other.fragName)){
+// return true;
+ if (defines != null && other.defines != null)
+ return defines.getCompiled().equals(other.defines.getCompiled());
+ else if (defines != null || other.defines != null)
+ return false;
+ else
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 41 * hash + name.hashCode();
+ hash = 41 * hash + fragName.hashCode();
+ hash = 41 * hash + (defines != null ? defines.getCompiled().hashCode() : 0);
+ return hash;
+ }
+
+ public DefineList getDefines() {
+ return defines;
+ }
+
+ public String getVertName(){
+ return name;
+ }
+
+ public String getFragName() {
+ return fragName;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(fragName, "fragment_name", null);
+ oc.write(language, "language", null);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ fragName = ic.readString("fragment_name", null);
+ language = ic.readString("language", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/ShaderUtils.java b/engine/src/core/com/jme3/shader/ShaderUtils.java
new file mode 100644
index 0000000..8aecdeb
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderUtils.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public class ShaderUtils {
+
+ public static String convertToGLSL130(String input, boolean isFrag){
+ StringBuilder sb = new StringBuilder();
+ sb.append("#version 130\n");
+ if (isFrag){
+ input = input.replaceAll("varying", "in");
+ }else{
+ input = input.replaceAll("attribute", "in");
+ input = input.replaceAll("varying", "out");
+ }
+ sb.append(input);
+ return sb.toString();
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/ShaderVariable.java b/engine/src/core/com/jme3/shader/ShaderVariable.java
new file mode 100644
index 0000000..7dbd2e2
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/ShaderVariable.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.*;
+import java.io.IOException;
+
+public class ShaderVariable implements Savable {
+
+ // if -2, location not known
+ // if -1, not defined in shader
+ // if >= 0, uniform defined and available.
+ protected int location = -2;
+
+ /**
+ * Name of the uniform as was declared in the shader.
+ * E.g name = "g_WorldMatrix" if the decleration was
+ * "uniform mat4 g_WorldMatrix;".
+ */
+ protected String name = null;
+
+ /**
+ * True if the shader value was changed.
+ */
+ protected boolean updateNeeded = true;;
+
+ public void write(JmeExporter ex) throws IOException{
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(name, "name", null);
+ }
+
+ public void read(JmeImporter im) throws IOException{
+ InputCapsule ic = im.getCapsule(this);
+ name = ic.readString("name", null);
+ }
+
+ public void setLocation(int location){
+ this.location = location;
+ }
+
+ public int getLocation(){
+ return location;
+ }
+
+ public void setName(String name){
+ this.name = name;
+ }
+
+ public String getName(){
+ return name;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/Uniform.java b/engine/src/core/com/jme3/shader/Uniform.java
new file mode 100644
index 0000000..228e097
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/Uniform.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.*;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+public class Uniform extends ShaderVariable {
+
+ private static final Integer ZERO_INT = Integer.valueOf(0);
+ private static final Float ZERO_FLT = Float.valueOf(0);
+ private static final FloatBuffer ZERO_BUF = BufferUtils.createFloatBuffer(4*4);
+
+ /**
+ * Currently set value of the uniform.
+ */
+ protected Object value = null;
+ protected FloatBuffer multiData = null;
+
+ /**
+ * Type of uniform
+ */
+ protected VarType varType;
+
+ /**
+ * Binding to a renderer value, or null if user-defined uniform
+ */
+ protected UniformBinding binding;
+
+ protected boolean setByCurrentMaterial = false;
+// protected Object lastChanger = null;
+
+ @Override
+ public void write(JmeExporter ex) throws IOException{
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(varType, "varType", null);
+ oc.write(binding, "binding", null);
+ switch (varType){
+ case Boolean:
+ oc.write( ((Boolean)value).booleanValue(), "valueBoolean", false );
+ break;
+ case Float:
+ oc.write( ((Float)value).floatValue(), "valueFloat", 0);
+ break;
+ case FloatArray:
+ oc.write( (FloatBuffer)value, "valueFloatArray", null);
+ break;
+ case Int:
+ oc.write( ((Integer)value).intValue(), "valueInt", 0);
+ break;
+ case Matrix3:
+ oc.write( (Matrix3f)value, "valueMatrix3", null);
+ break;
+ case Matrix3Array:
+ case Matrix4Array:
+ case Vector2Array:
+ throw new UnsupportedOperationException("Come again?");
+ case Matrix4:
+ oc.write( (Matrix4f)value, "valueMatrix4", null);
+ break;
+ case Vector2:
+ oc.write( (Vector2f)value, "valueVector2", null);
+ break;
+ case Vector3:
+ oc.write( (Vector3f)value, "valueVector3", null);
+ break;
+ case Vector3Array:
+ oc.write( (FloatBuffer)value, "valueVector3Array", null);
+ break;
+ case Vector4:
+ oc.write( (ColorRGBA)value, "valueVector4", null);
+ break;
+ case Vector4Array:
+ oc.write( (FloatBuffer)value, "valueVector4Array", null);
+ break;
+ }
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException{
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ varType = ic.readEnum("varType", VarType.class, null);
+ binding = ic.readEnum("binding", UniformBinding.class, null);
+ switch (varType){
+ case Boolean:
+ value = ic.readBoolean("valueBoolean", false);
+ break;
+ case Float:
+ value = ic.readFloat("valueFloat", 0);
+ break;
+ case FloatArray:
+ value = ic.readFloatBuffer("valueFloatArray", null);
+ break;
+ case Int:
+ value = ic.readInt("valueInt", 0);
+ break;
+ case Matrix3:
+ multiData = ic.readFloatBuffer("valueMatrix3", null);
+ value = multiData;
+ break;
+ case Matrix4:
+ multiData = ic.readFloatBuffer("valueMatrix4", null);
+ value = multiData;
+ break;
+ case Vector2:
+ value = ic.readSavable("valueVector2", null);
+ break;
+ case Vector3:
+ value = ic.readSavable("valueVector3", null);
+ break;
+ case Vector3Array:
+ value = ic.readFloatBuffer("valueVector3Array", null);
+ break;
+ case Vector4:
+ value = ic.readSavable("valueVector4", null);
+ break;
+ case Vector4Array:
+ value = ic.readFloatBuffer("valueVector4Array", null);
+ break;
+ }
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ if (name != null){
+ sb.append("Uniform[name=");
+ sb.append(name);
+ if (varType != null){
+ sb.append(", type=");
+ sb.append(varType);
+ sb.append(", value=");
+ sb.append(value);
+ }else{
+ sb.append(", value=<not set>");
+ }
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ public void setBinding(UniformBinding binding){
+ this.binding = binding;
+ }
+
+ public UniformBinding getBinding(){
+ return binding;
+ }
+
+ public VarType getVarType() {
+ return varType;
+ }
+
+ public Object getValue(){
+ return value;
+ }
+
+ public boolean isSetByCurrentMaterial() {
+ return setByCurrentMaterial;
+ }
+
+ public void clearSetByCurrentMaterial(){
+ setByCurrentMaterial = false;
+ }
+
+// public void setLastChanger(Object lastChanger){
+// this.lastChanger = lastChanger;
+// }
+//
+// public Object getLastChanger(){
+// return lastChanger;
+// }
+
+ public void clearValue(){
+ updateNeeded = true;
+
+ if (multiData != null){
+ ZERO_BUF.clear();
+ multiData.clear();
+
+ while (multiData.remaining() > 0){
+ ZERO_BUF.limit( Math.min(multiData.remaining(), 16) );
+ multiData.put(ZERO_BUF);
+ }
+
+ multiData.clear();
+
+ return;
+ }
+
+ if (varType == null)
+ return;
+
+ switch (varType){
+ case Int:
+ this.value = ZERO_INT;
+ break;
+ case Boolean:
+ this.value = Boolean.FALSE;
+ break;
+ case Float:
+ this.value = ZERO_FLT;
+ break;
+ case Vector2:
+ this.value = Vector2f.ZERO;
+ break;
+ case Vector3:
+ this.value = Vector3f.ZERO;
+ break;
+ case Vector4:
+ if (this.value instanceof ColorRGBA){
+ this.value = ColorRGBA.BlackNoAlpha;
+ }else{
+ this.value = Quaternion.ZERO;
+ }
+ break;
+ default:
+ break; // won't happen because those are either textures
+ // or multidata types
+ }
+ }
+
+ public void setValue(VarType type, Object value){
+ if (location == -1)
+ return;
+
+ if (varType != null && varType != type)
+ throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+
+ if (value == null)
+ throw new NullPointerException();
+
+ setByCurrentMaterial = true;
+
+ switch (type){
+ case Matrix3:
+ Matrix3f m3 = (Matrix3f) value;
+ if (multiData == null)
+ multiData = BufferUtils.createFloatBuffer(9);
+
+ m3.fillFloatBuffer(multiData, true);
+ multiData.clear();
+ break;
+ case Matrix4:
+ Matrix4f m4 = (Matrix4f) value;
+ if (multiData == null)
+ multiData = BufferUtils.createFloatBuffer(16);
+
+ m4.fillFloatBuffer(multiData, true);
+ multiData.clear();
+ break;
+ case FloatArray:
+ float[] fa = (float[]) value;
+ if (multiData == null){
+ multiData = BufferUtils.createFloatBuffer(fa);
+ }else{
+ multiData = BufferUtils.ensureLargeEnough(multiData, fa.length);
+ }
+
+ multiData.put(fa);
+ multiData.clear();
+ break;
+ case Vector2Array:
+ Vector2f[] v2a = (Vector2f[]) value;
+ if (multiData == null){
+ multiData = BufferUtils.createFloatBuffer(v2a);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, v2a.length * 2);
+ }
+
+ for (int i = 0; i < v2a.length; i++)
+ BufferUtils.setInBuffer(v2a[i], multiData, i);
+
+ multiData.clear();
+ break;
+ case Vector3Array:
+ Vector3f[] v3a = (Vector3f[]) value;
+ if (multiData == null){
+ multiData = BufferUtils.createFloatBuffer(v3a);
+ } else{
+ multiData = BufferUtils.ensureLargeEnough(multiData, v3a.length * 3);
+ }
+
+ for (int i = 0; i < v3a.length; i++)
+ BufferUtils.setInBuffer(v3a[i], multiData, i);
+
+ multiData.clear();
+ break;
+ case Vector4Array:
+ Quaternion[] v4a = (Quaternion[]) value;
+ if (multiData == null){
+ multiData = BufferUtils.createFloatBuffer(v4a);
+ } else {
+ multiData = BufferUtils.ensureLargeEnough(multiData, v4a.length * 4);
+ }
+
+ for (int i = 0; i < v4a.length; i++)
+ BufferUtils.setInBuffer(v4a[i], multiData, i);
+
+ multiData.clear();
+ break;
+ case Matrix3Array:
+ Matrix3f[] m3a = (Matrix3f[]) value;
+
+ if (multiData == null)
+ multiData = BufferUtils.createFloatBuffer(m3a.length * 9);
+ else{
+ multiData = BufferUtils.ensureLargeEnough(multiData, m3a.length * 9);
+ }
+
+ for (int i = 0; i < m3a.length; i++)
+ m3a[i].fillFloatBuffer(multiData, true);
+
+ multiData.clear();
+ break;
+ case Matrix4Array:
+ Matrix4f[] m4a = (Matrix4f[]) value;
+
+ if (multiData == null)
+ multiData = BufferUtils.createFloatBuffer(m4a.length * 16);
+ else{
+ multiData = BufferUtils.ensureLargeEnough(multiData, m4a.length * 16);
+ }
+
+ for (int i = 0; i < m4a.length; i++)
+ m4a[i].fillFloatBuffer(multiData, true);
+
+ multiData.clear();
+ break;
+ // Only use check if equals optimization for primitive values
+ case Int:
+ case Float:
+ case Boolean:
+ if (this.value != null && this.value.equals(value))
+ return;
+
+ this.value = value;
+ break;
+ default:
+ this.value = value;
+ break;
+ }
+
+ if (multiData != null)
+ this.value = multiData;
+
+ varType = type;
+ updateNeeded = true;
+ }
+
+ public void setVector4Length(int length){
+ if (location == -1)
+ return;
+
+ FloatBuffer fb = (FloatBuffer) value;
+ if (fb == null || fb.capacity() < length){
+ value = BufferUtils.createFloatBuffer(length * 4);
+ }
+
+ varType = VarType.Vector4Array;
+ updateNeeded = true;
+ setByCurrentMaterial = true;
+ }
+
+ public void setVector4InArray(float x, float y, float z, float w, int index){
+ if (location == -1)
+ return;
+
+ if (varType != null && varType != VarType.Vector4Array)
+ throw new IllegalArgumentException("Expected a "+varType.name()+" value!");
+
+ FloatBuffer fb = (FloatBuffer) value;
+ fb.position(index * 4);
+ fb.put(x).put(y).put(z).put(w);
+ fb.rewind();
+ updateNeeded = true;
+ setByCurrentMaterial = true;
+ }
+
+ public boolean isUpdateNeeded(){
+ return updateNeeded;
+ }
+
+ public void clearUpdateNeeded(){
+ updateNeeded = false;
+ }
+
+ public void reset(){
+ setByCurrentMaterial = false;
+ location = -2;
+ updateNeeded = true;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shader/UniformBinding.java b/engine/src/core/com/jme3/shader/UniformBinding.java
new file mode 100644
index 0000000..bdca35b
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/UniformBinding.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public enum UniformBinding {
+
+ /**
+ * The world matrix. Converts Model space to World space.
+ * Type: mat4
+ */
+ WorldMatrix,
+
+ /**
+ * The view matrix. Converts World space to View space.
+ * Type: mat4
+ */
+ ViewMatrix,
+
+ /**
+ * The projection matrix. Converts View space to Clip/Projection space.
+ * Type: mat4
+ */
+ ProjectionMatrix,
+
+ /**
+ * The world view matrix. Converts Model space to View space.
+ * Type: mat4
+ */
+ WorldViewMatrix,
+
+ /**
+ * The normal matrix. The inverse transpose of the worldview matrix.
+ * Converts normals from model space to view space.
+ * Type: mat3
+ */
+ NormalMatrix,
+
+ /**
+ * The world view projection matrix. Converts Model space to Clip/Projection
+ * space.
+ * Type: mat4
+ */
+ WorldViewProjectionMatrix,
+
+ /**
+ * The view projection matrix. Converts Model space to Clip/Projection
+ * space.
+ * Type: mat4
+ */
+ ViewProjectionMatrix,
+
+
+ WorldMatrixInverse,
+ ViewMatrixInverse,
+ ProjectionMatrixInverse,
+ ViewProjectionMatrixInverse,
+ WorldViewMatrixInverse,
+ NormalMatrixInverse,
+ WorldViewProjectionMatrixInverse,
+
+ /**
+ * Contains the four viewport parameters in this order:
+ * X = Left,
+ * Y = Top,
+ * Z = Right,
+ * W = Bottom.
+ * Type: vec4
+ */
+ ViewPort,
+
+ /**
+ * The near and far values for the camera frustum.
+ * X = Near
+ * Y = Far.
+ * Type: vec2
+ */
+ FrustumNearFar,
+
+ /**
+ * The width and height of the camera.
+ * Type: vec2
+ */
+ Resolution,
+
+ /**
+ * Aspect ratio of the resolution currently set. Width/Height.
+ * Type: float
+ */
+ Aspect,
+
+ /**
+ * Camera position in world space.
+ * Type: vec3
+ */
+ CameraPosition,
+
+ /**
+ * Direction of the camera.
+ * Type: vec3
+ */
+ CameraDirection,
+
+ /**
+ * Left vector of the camera.
+ * Type: vec3
+ */
+ CameraLeft,
+
+ /**
+ * Up vector of the camera.
+ * Type: vec3
+ */
+ CameraUp,
+
+ /**
+ * Time in seconds since the application was started.
+ * Type: float
+ */
+ Time,
+
+ /**
+ * Time in seconds that the last frame took.
+ * Type: float
+ */
+ Tpf,
+
+ /**
+ * Frames per second.
+ * Type: float
+ */
+ FrameRate,
+}
diff --git a/engine/src/core/com/jme3/shader/VarType.java b/engine/src/core/com/jme3/shader/VarType.java
new file mode 100644
index 0000000..723bc93
--- /dev/null
+++ b/engine/src/core/com/jme3/shader/VarType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.shader;
+
+public enum VarType {
+
+ Float,
+ Vector2,
+ Vector3,
+ Vector4,
+
+ FloatArray(true,false),
+ Vector2Array(true,false),
+ Vector3Array(true,false),
+ Vector4Array(true,false),
+
+ Boolean,
+
+ Matrix3(true,false),
+ Matrix4(true,false),
+
+ Matrix3Array(true,false),
+ Matrix4Array(true,false),
+
+ TextureBuffer(false,true),
+ Texture2D(false,true),
+ Texture3D(false,true),
+ TextureArray(false,true),
+ TextureCubeMap(false,true),
+ Int;
+
+ private boolean usesMultiData = false;
+ private boolean textureType = false;
+
+ VarType(){
+ }
+
+ VarType(boolean multiData, boolean textureType){
+ usesMultiData = multiData;
+ this.textureType = textureType;
+ }
+
+ public boolean isTextureType() {
+ return textureType;
+ }
+
+ public boolean usesMultiData() {
+ return usesMultiData;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
new file mode 100644
index 0000000..3fd01c9
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/BasicShadowRenderer.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * BasicShadowRenderer uses standard shadow mapping with one map
+ * it's useful to render shadows in a small scene, but edges might look a bit jagged.
+ *
+ * @author Kirill Vainer
+ */
+public class BasicShadowRenderer implements SceneProcessor {
+
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private FrameBuffer shadowFB;
+ private Texture2D shadowMap;
+ private Camera shadowCam;
+ private Material preshadowMat;
+ private Material postshadowMat;
+ private Picture dispPic = new Picture("Picture");
+ private boolean noOccluders = false;
+ private Vector3f[] points = new Vector3f[8];
+ private Vector3f direction = new Vector3f();
+
+ /**
+ * Creates a BasicShadowRenderer
+ * @param manager the asset manager
+ * @param size the size of the shadow map (the map is square)
+ */
+ public BasicShadowRenderer(AssetManager manager, int size) {
+ shadowFB = new FrameBuffer(size, size, 1);
+ shadowMap = new Texture2D(size, size, Format.Depth);
+ shadowFB.setDepthTexture(shadowMap);
+ shadowCam = new Camera(size, size);
+
+ preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
+ postshadowMat = new Material(manager, "Common/MatDefs/Shadow/PostShadow.j3md");
+ postshadowMat.setTexture("ShadowMap", shadowMap);
+
+ dispPic.setTexture(manager, shadowMap, false);
+
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ renderManager = rm;
+ viewPort = vp;
+
+ reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight());
+ }
+
+ public boolean isInitialized() {
+ return viewPort != null;
+ }
+
+ /**
+ * returns the light direction used for this processor
+ * @return
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * sets the light direction to use to computs shadows
+ * @param direction
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction).normalizeLocal();
+ }
+
+ /**
+ * debug only
+ * @return
+ */
+ public Vector3f[] getPoints() {
+ return points;
+ }
+
+ /**
+ * debug only
+ * returns the shadow camera
+ * @return
+ */
+ public Camera getShadowCamera() {
+ return shadowCam;
+ }
+
+ public void postQueue(RenderQueue rq) {
+ GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
+ if (occluders.size() == 0) {
+ noOccluders = true;
+ return;
+ } else {
+ noOccluders = false;
+ }
+
+ GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
+
+ // update frustum points based on current camera
+ Camera viewCam = viewPort.getCamera();
+ ShadowUtil.updateFrustumPoints(viewCam,
+ viewCam.getFrustumNear(),
+ viewCam.getFrustumFar(),
+ 1.0f,
+ points);
+
+ Vector3f frustaCenter = new Vector3f();
+ for (Vector3f point : points) {
+ frustaCenter.addLocal(point);
+ }
+ frustaCenter.multLocal(1f / 8f);
+
+ // update light direction
+ shadowCam.setProjectionMatrix(null);
+ shadowCam.setParallelProjection(true);
+// shadowCam.setFrustumPerspective(45, 1, 1, 20);
+
+ shadowCam.lookAtDirection(direction, Vector3f.UNIT_Y);
+ shadowCam.update();
+ shadowCam.setLocation(frustaCenter);
+ shadowCam.update();
+ shadowCam.updateViewProjection();
+
+ // render shadow casters to shadow map
+ ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points);
+
+ Renderer r = renderManager.getRenderer();
+ renderManager.setCamera(shadowCam, false);
+ renderManager.setForcedMaterial(preshadowMat);
+
+ r.setFrameBuffer(shadowFB);
+ r.clearBuffers(false, true, false);
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Cast, renderManager, shadowCam, true);
+ r.setFrameBuffer(viewPort.getOutputFrameBuffer());
+
+ renderManager.setForcedMaterial(null);
+ renderManager.setCamera(viewCam, false);
+ }
+
+ /**
+ * debug only
+ * @return
+ */
+ public Picture getDisplayPicture() {
+ return dispPic;
+ }
+
+ public void postFrame(FrameBuffer out) {
+ if (!noOccluders) {
+ postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix());
+ renderManager.setForcedMaterial(postshadowMat);
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, viewPort.getCamera(), true);
+ renderManager.setForcedMaterial(null);
+ }
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void cleanup() {
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ dispPic.setPosition(w / 20f, h / 20f);
+ dispPic.setWidth(w / 5f);
+ dispPic.setHeight(h / 5f);
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
new file mode 100644
index 0000000..9fa95cb
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowRenderer.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
+ * <p/>
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * <p/>
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * <p/>
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ * <p/>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+import com.jme3.post.SceneProcessor;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.renderer.queue.OpaqueComparator;
+import com.jme3.renderer.queue.RenderQueue;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.WireFrustum;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture.MagFilter;
+import com.jme3.texture.Texture.MinFilter;
+import com.jme3.texture.Texture.ShadowCompareMode;
+import com.jme3.texture.Texture2D;
+import com.jme3.ui.Picture;
+
+/**
+ * PssmShadow renderer use Parrallel Split Shadow Mapping technique (pssm)<br>
+ * It splits the view frustum in several parts and compute a shadow map for each
+ * one.<br> splits are distributed so that the closer they are from the camera,
+ * the smaller they are to maximize the resolution used of the shadow map.<br>
+ * This result in a better quality shadow than standard shadow mapping.<br> for
+ * more informations on this read this
+ * <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a><br>
+ * <p/>
+ * @author Rémy Bouquet aka Nehon
+ */
+public class PssmShadowRenderer implements SceneProcessor {
+
+ /**
+ * <code>FilterMode</code> specifies how shadows are filtered
+ */
+ public enum FilterMode {
+
+ /**
+ * Shadows are not filtered. Nearest sample is used, causing in blocky
+ * shadows.
+ */
+ Nearest,
+ /**
+ * Bilinear filtering is used. Has the potential of being hardware
+ * accelerated on some GPUs
+ */
+ Bilinear,
+ /**
+ * Dither-based sampling is used, very cheap but can look bad
+ * at low resolutions.
+ */
+ Dither,
+ /**
+ * 4x4 percentage-closer filtering is used. Shadows will be smoother
+ * at the cost of performance
+ */
+ PCF4,
+ /**
+ * 8x8 percentage-closer filtering is used. Shadows will be smoother
+ * at the cost of performance
+ */
+ PCF8
+ }
+
+ /**
+ * Specifies the shadow comparison mode
+ */
+ public enum CompareMode {
+
+ /**
+ * Shadow depth comparisons are done by using shader code
+ */
+ Software,
+ /**
+ * Shadow depth comparisons are done by using the GPU's dedicated
+ * shadowing pipeline.
+ */
+ Hardware;
+ }
+ private int nbSplits = 3;
+ private float lambda = 0.65f;
+ private float shadowIntensity = 0.7f;
+ private float zFarOverride = 0;
+ private RenderManager renderManager;
+ private ViewPort viewPort;
+ private FrameBuffer[] shadowFB;
+ private Texture2D[] shadowMaps;
+ private Texture2D dummyTex;
+ private Camera shadowCam;
+ private Material preshadowMat;
+ private Material postshadowMat;
+ private GeometryList splitOccluders = new GeometryList(new OpaqueComparator());
+ private Matrix4f[] lightViewProjectionsMatrices;
+ private ColorRGBA splits;
+ private float[] splitsArray;
+ private boolean noOccluders = false;
+ private Vector3f direction = new Vector3f();
+ private AssetManager assetManager;
+ private boolean debug = false;
+ private float edgesThickness = 1.0f;
+ private FilterMode filterMode;
+ private CompareMode compareMode;
+ private Picture[] dispPic;
+ private Vector3f[] points = new Vector3f[8];
+ private boolean flushQueues = true;
+
+ /**
+ * Create a PSSM Shadow Renderer
+ * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
+ * @param manager the application asset manager
+ * @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
+ * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps).
+ * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps).
+ */
+ public PssmShadowRenderer(AssetManager manager, int size, int nbSplits) {
+ this(manager, size, nbSplits, new Material(manager, "Common/MatDefs/Shadow/PostShadowPSSM.j3md"));
+
+ }
+
+ /**
+ * Create a PSSM Shadow Renderer
+ * More info on the technique at <a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a>
+ * @param manager the application asset manager
+ * @param size the size of the rendered shadowmaps (512,1024,2048, etc...)
+ * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps).
+ * @param postShadowMat the material used for post shadows if you need to override it *
+ */
+ //TODO remove the postShadowMat when we have shader injection....or remove this todo if we are in 2020.
+ public PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Material postShadowMat) {
+ assetManager = manager;
+ nbSplits = Math.max(Math.min(nbSplits, 4), 1);
+ this.nbSplits = nbSplits;
+
+ shadowFB = new FrameBuffer[nbSplits];
+ shadowMaps = new Texture2D[nbSplits];
+ dispPic = new Picture[nbSplits];
+ lightViewProjectionsMatrices = new Matrix4f[nbSplits];
+ splits = new ColorRGBA();
+ splitsArray = new float[nbSplits + 1];
+
+ //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+ dummyTex = new Texture2D(size, size, Format.RGBA8);
+
+ preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md");
+ this.postshadowMat = postShadowMat;
+
+ for (int i = 0; i < nbSplits; i++) {
+ lightViewProjectionsMatrices[i] = new Matrix4f();
+ shadowFB[i] = new FrameBuffer(size, size, 1);
+ shadowMaps[i] = new Texture2D(size, size, Format.Depth);
+
+ shadowFB[i].setDepthTexture(shadowMaps[i]);
+
+ //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash)
+ shadowFB[i].setColorTexture(dummyTex);
+
+ postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]);
+
+ //quads for debuging purpose
+ dispPic[i] = new Picture("Picture" + i);
+ dispPic[i].setTexture(manager, shadowMaps[i], false);
+ }
+
+ setCompareMode(CompareMode.Hardware);
+ setFilterMode(FilterMode.Bilinear);
+ setShadowIntensity(0.7f);
+
+ shadowCam = new Camera(size, size);
+ shadowCam.setParallelProjection(true);
+
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ /**
+ * Sets the filtering mode for shadow edges see {@link FilterMode} for more info
+ * @param filterMode
+ */
+ public void setFilterMode(FilterMode filterMode) {
+ if (filterMode == null) {
+ throw new NullPointerException();
+ }
+
+ if (this.filterMode == filterMode) {
+ return;
+ }
+
+ this.filterMode = filterMode;
+ postshadowMat.setInt("FilterMode", filterMode.ordinal());
+ postshadowMat.setFloat("PCFEdge", edgesThickness);
+ if (compareMode == CompareMode.Hardware) {
+ for (Texture2D shadowMap : shadowMaps) {
+ if (filterMode == FilterMode.Bilinear) {
+ shadowMap.setMagFilter(MagFilter.Bilinear);
+ shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+ } else {
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+ }
+ }
+
+ /**
+ * sets the shadow compare mode see {@link CompareMode} for more info
+ * @param compareMode
+ */
+ public void setCompareMode(CompareMode compareMode) {
+ if (compareMode == null) {
+ throw new NullPointerException();
+ }
+
+ if (this.compareMode == compareMode) {
+ return;
+ }
+
+ this.compareMode = compareMode;
+ for (Texture2D shadowMap : shadowMaps) {
+ if (compareMode == CompareMode.Hardware) {
+ shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual);
+ if (filterMode == FilterMode.Bilinear) {
+ shadowMap.setMagFilter(MagFilter.Bilinear);
+ shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps);
+ } else {
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ } else {
+ shadowMap.setShadowCompareMode(ShadowCompareMode.Off);
+ shadowMap.setMagFilter(MagFilter.Nearest);
+ shadowMap.setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+ postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware);
+ }
+
+ //debug function that create a displayable frustrum
+ private Geometry createFrustum(Vector3f[] pts, int i) {
+ WireFrustum frustum = new WireFrustum(pts);
+ Geometry frustumMdl = new Geometry("f", frustum);
+ frustumMdl.setCullHint(Spatial.CullHint.Never);
+ frustumMdl.setShadowMode(ShadowMode.Off);
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.getAdditionalRenderState().setWireframe(true);
+ frustumMdl.setMaterial(mat);
+ switch (i) {
+ case 0:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink);
+ break;
+ case 1:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red);
+ break;
+ case 2:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green);
+ break;
+ case 3:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue);
+ break;
+ default:
+ frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);
+ break;
+ }
+
+ frustumMdl.updateGeometricState();
+ return frustumMdl;
+ }
+
+ public void initialize(RenderManager rm, ViewPort vp) {
+ renderManager = rm;
+ viewPort = vp;
+ }
+
+ public boolean isInitialized() {
+ return viewPort != null;
+ }
+
+ /**
+ * returns the light direction used by the processor
+ * @return
+ */
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ /**
+ * Sets the light direction to use to compute shadows
+ * @param direction
+ */
+ public void setDirection(Vector3f direction) {
+ this.direction.set(direction).normalizeLocal();
+ }
+
+ @SuppressWarnings("fallthrough")
+ public void postQueue(RenderQueue rq) {
+ GeometryList occluders = rq.getShadowQueueContent(ShadowMode.Cast);
+ if (occluders.size() == 0) {
+ return;
+ }
+
+ GeometryList receivers = rq.getShadowQueueContent(ShadowMode.Receive);
+ if (receivers.size() == 0) {
+ return;
+ }
+
+ Camera viewCam = viewPort.getCamera();
+
+ float zFar = zFarOverride;
+ if (zFar == 0) {
+ zFar = viewCam.getFrustumFar();
+ }
+
+ //We prevent computing the frustum points and splits with zeroed or negative near clip value
+ float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f);
+ ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points);
+
+ //shadowCam.setDirection(direction);
+ shadowCam.getRotation().lookAt(direction, shadowCam.getUp());
+ shadowCam.update();
+ shadowCam.updateViewProjection();
+
+ PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda);
+
+
+ switch (splitsArray.length) {
+ case 5:
+ splits.a = splitsArray[4];
+ case 4:
+ splits.b = splitsArray[3];
+ case 3:
+ splits.g = splitsArray[2];
+ case 2:
+ case 1:
+ splits.r = splitsArray[1];
+ break;
+ }
+
+ Renderer r = renderManager.getRenderer();
+ renderManager.setForcedMaterial(preshadowMat);
+ renderManager.setForcedTechnique("PreShadow");
+
+ for (int i = 0; i < nbSplits; i++) {
+
+ // update frustum points based on current camera and split
+ ShadowUtil.updateFrustumPoints(viewCam, splitsArray[i], splitsArray[i + 1], 1.0f, points);
+
+ //Updating shadow cam with curent split frustra
+ ShadowUtil.updateShadowCamera(occluders, receivers, shadowCam, points, splitOccluders);
+
+ //saving light view projection matrix for this split
+ lightViewProjectionsMatrices[i] = shadowCam.getViewProjectionMatrix().clone();
+ renderManager.setCamera(shadowCam, false);
+
+ r.setFrameBuffer(shadowFB[i]);
+ r.clearBuffers(false, true, false);
+
+ // render shadow casters to shadow map
+ viewPort.getQueue().renderShadowQueue(splitOccluders, renderManager, shadowCam, true);
+ }
+ if (flushQueues) {
+ occluders.clear();
+ }
+ //restore setting for future rendering
+ r.setFrameBuffer(viewPort.getOutputFrameBuffer());
+ renderManager.setForcedMaterial(null);
+ renderManager.setForcedTechnique(null);
+ renderManager.setCamera(viewCam, false);
+
+ }
+
+ //debug only : displays depth shadow maps
+ private void displayShadowMap(Renderer r) {
+ Camera cam = viewPort.getCamera();
+ renderManager.setCamera(cam, true);
+ int h = cam.getHeight();
+ for (int i = 0; i < dispPic.length; i++) {
+ dispPic[i].setPosition(64 * (i + 1) + 128 * i, h / 20f);
+ dispPic[i].setWidth(128);
+ dispPic[i].setHeight(128);
+ dispPic[i].updateGeometricState();
+ renderManager.renderGeometry(dispPic[i]);
+ }
+ renderManager.setCamera(cam, false);
+ }
+
+ /**For dubuging purpose
+ * Allow to "snapshot" the current frustrum to the scene
+ */
+ public void displayDebug() {
+ debug = true;
+ }
+
+ public void postFrame(FrameBuffer out) {
+ Camera cam = viewPort.getCamera();
+ if (!noOccluders) {
+ postshadowMat.setColor("Splits", splits);
+ for (int i = 0; i < nbSplits; i++) {
+ postshadowMat.setMatrix4("LightViewProjectionMatrix" + i, lightViewProjectionsMatrices[i]);
+ }
+ renderManager.setForcedMaterial(postshadowMat);
+
+ viewPort.getQueue().renderShadowQueue(ShadowMode.Receive, renderManager, cam, flushQueues);
+
+ renderManager.setForcedMaterial(null);
+ renderManager.setCamera(cam, false);
+
+ }
+ if (debug) {
+ displayShadowMap(renderManager.getRenderer());
+ }
+ }
+
+ public void preFrame(float tpf) {
+ }
+
+ public void cleanup() {
+ }
+
+ public void reshape(ViewPort vp, int w, int h) {
+ }
+
+ /**
+ * returns the labda parameter<br>
+ * see {@link setLambda(float lambda)}
+ * @return lambda
+ */
+ public float getLambda() {
+ return lambda;
+ }
+
+ /*
+ * Adjust the repartition of the different shadow maps in the shadow extend
+ * usualy goes from 0.0 to 1.0
+ * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged
+ * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend.
+ * the default value is set to 0.65f (theoric optimal value).
+ * @param lambda the lambda value.
+ */
+ public void setLambda(float lambda) {
+ this.lambda = lambda;
+ }
+
+ /**
+ * How far the shadows are rendered in the view
+ * see {@link setShadowZExtend(float zFar)}
+ * @return shadowZExtend
+ */
+ public float getShadowZExtend() {
+ return zFarOverride;
+ }
+
+ /**
+ * Set the distance from the eye where the shadows will be rendered
+ * default value is dynamicaly computed to the shadow casters/receivers union bound zFar, capped to view frustum far value.
+ * @param zFar the zFar values that override the computed one
+ */
+ public void setShadowZExtend(float zFar) {
+ this.zFarOverride = zFar;
+ }
+
+ /**
+ * returns the shdaow intensity<br>
+ * see {@link setShadowIntensity(float shadowIntensity)}
+ * @return shadowIntensity
+ */
+ public float getShadowIntensity() {
+ return shadowIntensity;
+ }
+
+ /**
+ * Set the shadowIntensity, the value should be between 0 and 1,
+ * a 0 value gives a bright and invisilble shadow,
+ * a 1 value gives a pitch black shadow,
+ * default is 0.7
+ * @param shadowIntensity the darkness of the shadow
+ */
+ public void setShadowIntensity(float shadowIntensity) {
+ this.shadowIntensity = shadowIntensity;
+ postshadowMat.setFloat("ShadowIntensity", shadowIntensity);
+ }
+
+ /**
+ * returns the edges thickness <br>
+ * see {@link setEdgesThickness(int edgesThickness)}
+ * @return edgesThickness
+ */
+ public int getEdgesThickness() {
+ return (int) (edgesThickness * 10);
+ }
+
+ /**
+ * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges
+ * @param edgesThickness
+ */
+ public void setEdgesThickness(int edgesThickness) {
+ this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10));
+ this.edgesThickness *= 0.1f;
+ postshadowMat.setFloat("PCFEdge", edgesThickness);
+ }
+
+ /**
+ * returns true if the PssmRenderer flushed the shadow queues
+ * @return flushQueues
+ */
+ public boolean isFlushQueues() {
+ return flushQueues;
+ }
+
+ /**
+ * Set this to false if you want to use several PssmRederers to have multiple shadows cast by multiple light sources.
+ * Make sure the last PssmRenderer in the stack DO flush the queues, but not the others
+ * @param flushQueues
+ */
+ public void setFlushQueues(boolean flushQueues) {
+ this.flushQueues = flushQueues;
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/PssmShadowUtil.java b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
new file mode 100644
index 0000000..2633625
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/PssmShadowUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ * <ul>
+ * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
+ * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
+ * </ul>
+ * for more info.
+ */
+public final class PssmShadowUtil {
+
+ /**
+ * Updates the frustum splits stores in <code>splits</code> using PSSM.
+ */
+ public static void updateFrustumSplits(float[] splits, float near, float far, float lambda) {
+ for (int i = 0; i < splits.length; i++) {
+ float IDM = i / (float) splits.length;
+ float log = near * FastMath.pow((far / near), IDM);
+ float uniform = near + (far - near) * IDM;
+ splits[i] = log * lambda + uniform * (1.0f - lambda);
+ }
+
+ // This is used to improve the correctness of the calculations. Our main near- and farplane
+ // of the camera always stay the same, no matter what happens.
+ splits[0] = near;
+ splits[splits.length - 1] = far;
+ }
+
+ /**
+ * Compute the Zfar in the model vieuw to adjust the Zfar distance for the splits calculation
+ */
+ public static float computeZFar(GeometryList occ, GeometryList recv, Camera cam) {
+ Matrix4f mat = cam.getViewMatrix();
+ BoundingBox bbOcc = ShadowUtil.computeUnionBound(occ, mat);
+ BoundingBox bbRecv = ShadowUtil.computeUnionBound(recv, mat);
+
+ return min(max(bbOcc.getZExtent() - bbOcc.getCenter().z, bbRecv.getZExtent() - bbRecv.getCenter().z), cam.getFrustumFar());
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/ShadowCamera.java b/engine/src/core/com/jme3/shadow/ShadowCamera.java
new file mode 100644
index 0000000..920e456
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowCamera.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.light.DirectionalLight;
+import com.jme3.light.Light;
+import com.jme3.light.PointLight;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+
+/**
+ * Creates a camera according to a light
+ * Handy to compute projection matrix of a light
+ * @author Kirill Vainer
+ */
+public class ShadowCamera {
+
+ private Vector3f[] points = new Vector3f[8];
+ private Light target;
+
+ public ShadowCamera(Light target) {
+ this.target = target;
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new Vector3f();
+ }
+ }
+
+ /**
+ * Updates the camera view direction and position based on the light
+ */
+ public void updateLightCamera(Camera lightCam) {
+ if (target.getType() == Light.Type.Directional) {
+ DirectionalLight dl = (DirectionalLight) target;
+ lightCam.setParallelProjection(true);
+ lightCam.setLocation(Vector3f.ZERO);
+ lightCam.lookAtDirection(dl.getDirection(), Vector3f.UNIT_Y);
+ lightCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ PointLight pl = (PointLight) target;
+ lightCam.setParallelProjection(false);
+ lightCam.setLocation(pl.getPosition());
+ // direction will have to be calculated automatically
+ lightCam.setFrustumPerspective(45, 1, 1, 300);
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/shadow/ShadowUtil.java b/engine/src/core/com/jme3/shadow/ShadowUtil.java
new file mode 100644
index 0000000..5832bda
--- /dev/null
+++ b/engine/src/core/com/jme3/shadow/ShadowUtil.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.shadow;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.queue.GeometryList;
+import com.jme3.scene.Geometry;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Includes various useful shadow mapping functions.
+ *
+ * @see
+ * <ul>
+ * <li><a href="http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/">http://appsrv.cse.cuhk.edu.hk/~fzhang/pssm_vrcia/</a></li>
+ * <li><a href="http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html">http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html</a></li>
+ * </ul>
+ * for more info.
+ */
+public class ShadowUtil {
+
+ /**
+ * Updates a points arrays with the frustum corners of the provided camera.
+ * @param viewCam
+ * @param points
+ */
+ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) {
+ int w = viewCam.getWidth();
+ int h = viewCam.getHeight();
+ float n = viewCam.getFrustumNear();
+ float f = viewCam.getFrustumFar();
+
+ points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), n));
+ points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), n));
+ points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), n));
+ points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), n));
+
+ points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), f));
+ points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), f));
+ points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), f));
+ points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), f));
+ }
+
+ /**
+ * Updates the points array to contain the frustum corners of the given
+ * camera. The nearOverride and farOverride variables can be used
+ * to override the camera's near/far values with own values.
+ *
+ * TODO: Reduce creation of new vectors
+ *
+ * @param viewCam
+ * @param nearOverride
+ * @param farOverride
+ */
+ public static void updateFrustumPoints(Camera viewCam,
+ float nearOverride,
+ float farOverride,
+ float scale,
+ Vector3f[] points) {
+
+ Vector3f pos = viewCam.getLocation();
+ Vector3f dir = viewCam.getDirection();
+ Vector3f up = viewCam.getUp();
+
+ float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear();
+ float near = nearOverride;
+ float far = farOverride;
+ float ftop = viewCam.getFrustumTop();
+ float fright = viewCam.getFrustumRight();
+ float ratio = fright / ftop;
+
+ float near_height;
+ float near_width;
+ float far_height;
+ float far_width;
+
+ if (viewCam.isParallelProjection()) {
+ near_height = ftop;
+ near_width = near_height * ratio;
+ far_height = ftop;
+ far_width = far_height * ratio;
+ } else {
+ near_height = depthHeightRatio * near;
+ near_width = near_height * ratio;
+ far_height = depthHeightRatio * far;
+ far_width = far_height * ratio;
+ }
+
+ Vector3f right = dir.cross(up).normalizeLocal();
+
+ Vector3f temp = new Vector3f();
+ temp.set(dir).multLocal(far).addLocal(pos);
+ Vector3f farCenter = temp.clone();
+ temp.set(dir).multLocal(near).addLocal(pos);
+ Vector3f nearCenter = temp.clone();
+
+ Vector3f nearUp = temp.set(up).multLocal(near_height).clone();
+ Vector3f farUp = temp.set(up).multLocal(far_height).clone();
+ Vector3f nearRight = temp.set(right).multLocal(near_width).clone();
+ Vector3f farRight = temp.set(right).multLocal(far_width).clone();
+
+ points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight);
+ points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight);
+ points[2].set(nearCenter).addLocal(nearUp).addLocal(nearRight);
+ points[3].set(nearCenter).subtractLocal(nearUp).addLocal(nearRight);
+
+ points[4].set(farCenter).subtractLocal(farUp).subtractLocal(farRight);
+ points[5].set(farCenter).addLocal(farUp).subtractLocal(farRight);
+ points[6].set(farCenter).addLocal(farUp).addLocal(farRight);
+ points[7].set(farCenter).subtractLocal(farUp).addLocal(farRight);
+
+ if (scale != 1.0f) {
+ // find center of frustum
+ Vector3f center = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ center.addLocal(points[i]);
+ }
+ center.divideLocal(8f);
+
+ Vector3f cDir = new Vector3f();
+ for (int i = 0; i < 8; i++) {
+ cDir.set(points[i]).subtractLocal(center);
+ cDir.multLocal(scale - 1.0f);
+ points[i].addLocal(cDir);
+ }
+ }
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Transform transform) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ BoundingVolume newVol = vol.transform(transform);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) {
+ bbox.mergeLocal(newVol);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds of a geomList
+ * @param list
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) {
+ BoundingBox bbox = new BoundingBox();
+ BoundingVolume store = null;
+ for (int i = 0; i < list.size(); i++) {
+ BoundingVolume vol = list.get(i).getWorldBound();
+ store = vol.clone().transform(mat, null);
+ //Nehon : prevent NaN and infinity values to screw the final bounding box
+ if (!Float.isNaN(store.getCenter().x) && !Float.isInfinite(store.getCenter().x)) {
+ bbox.mergeLocal(store);
+ }
+ }
+ return bbox;
+ }
+
+ /**
+ * Computes the bounds of multiple bounding volumes
+ * @param bv
+ * @return
+ */
+ public static BoundingBox computeUnionBound(List<BoundingVolume> bv) {
+ BoundingBox bbox = new BoundingBox();
+ for (int i = 0; i < bv.size(); i++) {
+ BoundingVolume vol = bv.get(i);
+ bbox.mergeLocal(vol);
+ }
+ return bbox;
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param transform
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+ for (int i = 0; i < pts.length; i++) {
+ transform.transformVector(pts[i], temp);
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ return new BoundingBox(center, extent.x, extent.y, extent.z);
+ }
+
+ /**
+ * Compute bounds from an array of points
+ * @param pts
+ * @param mat
+ * @return
+ */
+ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) {
+ Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY);
+ Vector3f temp = new Vector3f();
+
+ for (int i = 0; i < pts.length; i++) {
+ float w = mat.multProj(pts[i], temp);
+
+ temp.x /= w;
+ temp.y /= w;
+ // Why was this commented out?
+ temp.z /= w;
+
+ min.minLocal(temp);
+ max.maxLocal(temp);
+ }
+
+ Vector3f center = min.add(max).multLocal(0.5f);
+ Vector3f extent = max.subtract(min).multLocal(0.5f);
+ //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned
+ return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners)
+ *
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) {
+ boolean ortho = shadowCam.isParallelProjection();
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+// splitMin.z = 0;
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = 2.0f / (splitMax.x - splitMin.x);
+ scaleY = 2.0f / (splitMax.y - splitMin.y);
+ offsetX = -0.5f * (splitMax.x + splitMin.x) * scaleX;
+ offsetY = -0.5f * (splitMax.y + splitMin.y) * scaleY;
+ scaleZ = 1.0f / (splitMax.z - splitMin.z);
+ offsetZ = -splitMin.z * scaleZ;
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param receivers
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points) {
+ updateShadowCamera(occluders, receivers, shadowCam, points, null);
+ }
+
+ /**
+ * Updates the shadow camera to properly contain the given
+ * points (which contain the eye camera frustum corners) and the
+ * shadow occluder objects.
+ *
+ * @param occluders
+ * @param shadowCam
+ * @param points
+ */
+ public static void updateShadowCamera(GeometryList occluders,
+ GeometryList receivers,
+ Camera shadowCam,
+ Vector3f[] points,
+ GeometryList splitOccluders) {
+
+ boolean ortho = shadowCam.isParallelProjection();
+
+ shadowCam.setProjectionMatrix(null);
+
+ if (ortho) {
+ shadowCam.setFrustum(-1, 1, -1, 1, 1, -1);
+ } else {
+ shadowCam.setFrustumPerspective(45, 1, 1, 150);
+ }
+
+ // create transform to rotate points to viewspace
+ Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix();
+
+ BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix);
+
+ ArrayList<BoundingVolume> visRecvList = new ArrayList<BoundingVolume>();
+ for (int i = 0; i < receivers.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry receiver = receivers.get(i);
+ BoundingVolume bv = receiver.getWorldBound();
+ BoundingVolume recvBox = bv.transform(viewProjMatrix, null);
+
+ if (splitBB.intersects(recvBox)) {
+ visRecvList.add(recvBox);
+ }
+ }
+
+ ArrayList<BoundingVolume> visOccList = new ArrayList<BoundingVolume>();
+ for (int i = 0; i < occluders.size(); i++) {
+ // convert bounding box to light's viewproj space
+ Geometry occluder = occluders.get(i);
+ BoundingVolume bv = occluder.getWorldBound();
+ BoundingVolume occBox = bv.transform(viewProjMatrix, null);
+
+ boolean intersects = splitBB.intersects(occBox);
+ if (!intersects && occBox instanceof BoundingBox) {
+ BoundingBox occBB = (BoundingBox) occBox;
+ //Kirill 01/10/2011
+ // Extend the occluder further into the frustum
+ // This fixes shadow dissapearing issues when
+ // the caster itself is not in the view camera
+ // but its shadow is in the camera
+ // The number is in world units
+ occBB.setZExtent(occBB.getZExtent() + 50);
+ occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25));
+ if (splitBB.intersects(occBB)) {
+ // To prevent extending the depth range too much
+ // We return the bound to its former shape
+ // Before adding it
+ occBB.setZExtent(occBB.getZExtent() - 50);
+ occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25));
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ } else if (intersects) {
+ visOccList.add(occBox);
+ if (splitOccluders != null) {
+ splitOccluders.add(occluder);
+ }
+ }
+ }
+
+ BoundingBox casterBB = computeUnionBound(visOccList);
+ BoundingBox receiverBB = computeUnionBound(visRecvList);
+
+ //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows
+ if (visOccList.size() != visRecvList.size()) {
+ casterBB.setXExtent(casterBB.getXExtent() + 2.0f);
+ casterBB.setYExtent(casterBB.getYExtent() + 2.0f);
+ casterBB.setZExtent(casterBB.getZExtent() + 2.0f);
+ }
+
+ Vector3f casterMin = casterBB.getMin(null);
+ Vector3f casterMax = casterBB.getMax(null);
+
+ Vector3f receiverMin = receiverBB.getMin(null);
+ Vector3f receiverMax = receiverBB.getMax(null);
+
+ Vector3f splitMin = splitBB.getMin(null);
+ Vector3f splitMax = splitBB.getMax(null);
+
+ splitMin.z = 0;
+
+ if (!ortho) {
+ shadowCam.setFrustumPerspective(45, 1, 1, splitMax.z);
+ }
+
+ Matrix4f projMatrix = shadowCam.getProjectionMatrix();
+
+ Vector3f cropMin = new Vector3f();
+ Vector3f cropMax = new Vector3f();
+
+ // IMPORTANT: Special handling for Z values
+ cropMin.x = max(max(casterMin.x, receiverMin.x), splitMin.x);
+ cropMax.x = min(min(casterMax.x, receiverMax.x), splitMax.x);
+
+ cropMin.y = max(max(casterMin.y, receiverMin.y), splitMin.y);
+ cropMax.y = min(min(casterMax.y, receiverMax.y), splitMax.y);
+
+ cropMin.z = min(casterMin.z, splitMin.z);
+ cropMax.z = min(receiverMax.z, splitMax.z);
+
+
+ // Create the crop matrix.
+ float scaleX, scaleY, scaleZ;
+ float offsetX, offsetY, offsetZ;
+
+ scaleX = (2.0f) / (cropMax.x - cropMin.x);
+ scaleY = (2.0f) / (cropMax.y - cropMin.y);
+
+ offsetX = -0.5f * (cropMax.x + cropMin.x) * scaleX;
+ offsetY = -0.5f * (cropMax.y + cropMin.y) * scaleY;
+
+ scaleZ = 1.0f / (cropMax.z - cropMin.z);
+ offsetZ = -cropMin.z * scaleZ;
+
+
+
+ Matrix4f cropMatrix = new Matrix4f(scaleX, 0f, 0f, offsetX,
+ 0f, scaleY, 0f, offsetY,
+ 0f, 0f, scaleZ, offsetZ,
+ 0f, 0f, 0f, 1f);
+
+
+ Matrix4f result = new Matrix4f();
+ result.set(cropMatrix);
+ result.multLocal(projMatrix);
+
+ shadowCam.setProjectionMatrix(result);
+
+ }
+}
diff --git a/engine/src/core/com/jme3/system/Annotations.java b/engine/src/core/com/jme3/system/Annotations.java
new file mode 100644
index 0000000..0cf72eb
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Annotations.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import checkers.quals.TypeQualifier;
+import java.lang.annotation.*;
+
+/**
+ * This class contains the Annotation definitions for jME3. Mostly these are used
+ * for code error checking.
+ * @author normenhansen
+ */
+public class Annotations {
+
+ /**
+ * Annotation used for math primitive fields, method parameters or method return values.
+ * Specifies that the primitve is read only and should not be changed.
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @TypeQualifier
+ @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.TYPE, ElementType.METHOD})
+ public @interface ReadOnly {
+ }
+
+ /**
+ * Annotation used for methods in math primitives that are destructive to the
+ * object (xxxLocal, setXXX etc.).
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @TypeQualifier
+ @Target({ElementType.METHOD})
+ public @interface Destructive {
+ }
+
+ /**
+ * Annotation used for public methods that are not to be called by users.
+ * Examples include update() methods etc.
+ */
+ public @interface Internal {
+ }
+}
diff --git a/engine/src/core/com/jme3/system/AppSettings.java b/engine/src/core/com/jme3/system/AppSettings.java
new file mode 100644
index 0000000..ad9facc
--- /dev/null
+++ b/engine/src/core/com/jme3/system/AppSettings.java
@@ -0,0 +1,726 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+
+/**
+ * <code>AppSettings</code> provides a store of configuration
+ * to be used by the application.
+ * <p>
+ * By default only the {@link JmeContext context} uses the configuration,
+ * however the user may set and retrieve the settings as well.
+ *
+ * @author Kirill Vainer
+ */
+public final class AppSettings extends HashMap<String, Object> {
+
+ private static final AppSettings defaults = new AppSettings(false);
+
+ /**
+ * Use LWJGL as the display system and force using the OpenGL1.1 renderer.
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ */
+ public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1";
+
+ /**
+ * Use LWJGL as the display system and force using the OpenGL2.0 renderer.
+ * <p>
+ * If the underlying system does not support OpenGL2.0, then the context
+ * initialization will throw an exception.
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ */
+ public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2";
+
+ /**
+ * Use LWJGL as the display system and force using the core OpenGL3.3 renderer.
+ * <p>
+ * If the underlying system does not support OpenGL3.3, then the context
+ * initialization will throw an exception. Note that currently jMonkeyEngine
+ * does not have any shaders that support OpenGL3.3 therefore this
+ * option is not useful.
+ *
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ */
+ public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3";
+
+ /**
+ * Use LWJGL as the display system and allow the context
+ * to choose an appropriate renderer based on system capabilities.
+ * <p>
+ * If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will
+ * be used, otherwise, the OpenGL1.1 renderer is used.
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ */
+ public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any";
+
+ /**
+ * The JOGL renderer is no longer supported by jME.
+ *
+ * @deprecated Use the LWJGL renderer instead.
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ */
+ @Deprecated
+ public static final String JOGL = "JOGL";
+
+ /**
+ * The "NULL" option is no longer supported
+ *
+ * @deprecated Specify the "null" value instead
+ *
+ * @see AppSettings#setRenderer(java.lang.String)
+ * @see AppSettings#setAudioRenderer(java.lang.String)
+ */
+ @Deprecated
+ public static final String NULL = "NULL";
+
+ /**
+ * Use the LWJGL OpenAL based renderer for audio capabilities.
+ *
+ * @see AppSettings#setAudioRenderer(java.lang.String)
+ */
+ public static final String LWJGL_OPENAL = "LWJGL";
+
+ static {
+ defaults.put("Width", 640);
+ defaults.put("Height", 480);
+ defaults.put("BitsPerPixel", 24);
+ defaults.put("Frequency", 60);
+ defaults.put("DepthBits", 24);
+ defaults.put("StencilBits", 0);
+ defaults.put("Samples", 0);
+ defaults.put("Fullscreen", false);
+ defaults.put("Title", "jMonkey Engine 3.0");
+ defaults.put("Renderer", LWJGL_OPENGL2);
+ defaults.put("AudioRenderer", LWJGL_OPENAL);
+ defaults.put("DisableJoysticks", true);
+ defaults.put("UseInput", true);
+ defaults.put("VSync", false);
+ defaults.put("FrameRate", -1);
+ defaults.put("SettingsDialogImage", "/com/jme3/app/Monkey.png");
+ // defaults.put("Icons", null);
+ }
+
+ /**
+ * Create a new instance of <code>AppSettings</code>.
+ * <p>
+ * If <code>loadDefaults</code> is true, then the default settings
+ * will be set on the AppSettings.
+ * Use false if you want to change some settings but you would like the
+ * application to load settings from previous launches.
+ *
+ * @param loadDefaults If default settings are to be loaded.
+ */
+ public AppSettings(boolean loadDefaults) {
+ if (loadDefaults) {
+ putAll(defaults);
+ }
+ }
+
+ /**
+ * Copies all settings from <code>other</code> to <code>this</code>
+ * AppSettings.
+ * <p>
+ * Any settings that are specified in other will overwrite settings
+ * set on this AppSettings.
+ *
+ * @param other The AppSettings to copy the settings from
+ */
+ public void copyFrom(AppSettings other) {
+ this.putAll(other);
+ }
+
+ /**
+ * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except
+ * doesn't overwrite settings that are already set.
+ *
+ * @param other The AppSettings to merge the settings from
+ */
+ public void mergeFrom(AppSettings other) {
+ for (String key : other.keySet()) {
+ if (get(key) == null) {
+ put(key, other.get(key));
+ }
+ }
+ }
+
+ /**
+ * Loads the settings from the given properties input stream.
+ *
+ * @param in The InputStream to load from
+ * @throws IOException If an IOException occurs
+ *
+ * @see #save(java.io.OutputStream)
+ */
+ public void load(InputStream in) throws IOException {
+ Properties props = new Properties();
+ props.load(in);
+ for (Map.Entry<Object, Object> entry : props.entrySet()) {
+ String key = (String) entry.getKey();
+ String val = (String) entry.getValue();
+ if (val != null) {
+ val = val.trim();
+ }
+ if (key.endsWith("(int)")) {
+ key = key.substring(0, key.length() - 5);
+ int iVal = Integer.parseInt(val);
+ putInteger(key, iVal);
+ } else if (key.endsWith("(string)")) {
+ putString(key.substring(0, key.length() - 8), val);
+ } else if (key.endsWith("(bool)")) {
+ boolean bVal = Boolean.parseBoolean(val);
+ putBoolean(key.substring(0, key.length() - 6), bVal);
+ } else {
+ throw new IOException("Cannot parse key: " + key);
+ }
+ }
+ }
+
+ /**
+ * Saves all settings to the given properties output stream.
+ *
+ * @param out The OutputStream to write to
+ * @throws IOException If an IOException occurs
+ *
+ * @see #load(java.io.InputStream)
+ */
+ public void save(OutputStream out) throws IOException {
+ Properties props = new Properties();
+ for (Map.Entry<String, Object> entry : entrySet()) {
+ Object val = entry.getValue();
+ String type;
+ if (val instanceof Integer) {
+ type = "(int)";
+ } else if (val instanceof String) {
+ type = "(string)";
+ } else if (val instanceof Boolean) {
+ type = "(bool)";
+ } else {
+ throw new UnsupportedEncodingException();
+ }
+ props.setProperty(entry.getKey() + type, val.toString());
+ }
+ props.store(out, "jME3 AppSettings");
+ }
+
+ /**
+ * Loads settings previously saved in the Java preferences.
+ *
+ * @param preferencesKey The preferencesKey previously used to save the settings.
+ * @throws BackingStoreException If an exception occurs with the preferences
+ *
+ * @see #save(java.lang.String)
+ */
+ public void load(String preferencesKey) throws BackingStoreException {
+ Preferences prefs = Preferences.userRoot().node(preferencesKey);
+ String[] keys = prefs.keys();
+ if (keys != null) {
+ for (String key : keys) {
+ Object defaultValue = defaults.get(key);
+ if (defaultValue instanceof Integer) {
+ put(key, prefs.getInt(key, (Integer) defaultValue));
+ } else if (defaultValue instanceof String) {
+ put(key, prefs.get(key, (String) defaultValue));
+ } else if (defaultValue instanceof Boolean) {
+ put(key, prefs.getBoolean(key, (Boolean) defaultValue));
+ }
+ }
+ }
+ }
+
+ /**
+ * Saves settings into the Java preferences.
+ * <p>
+ * On the Windows operating system, the preferences are saved in the registry
+ * at the following key:<br>
+ * <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code>
+ *
+ * @param preferencesKey The preferences key to save at. Generally the
+ * application's unique name.
+ *
+ * @throws BackingStoreException If an exception occurs with the preferences
+ */
+ public void save(String preferencesKey) throws BackingStoreException {
+ Preferences prefs = Preferences.userRoot().node(preferencesKey);
+ for (String key : keySet()) {
+ prefs.put(key, get(key).toString());
+ }
+ }
+
+ /**
+ * Get an integer from the settings.
+ * <p>
+ * If the key is not set, then 0 is returned.
+ */
+ public int getInteger(String key) {
+ Integer i = (Integer) get(key);
+ if (i == null) {
+ return 0;
+ }
+
+ return i.intValue();
+ }
+
+ /**
+ * Get a boolean from the settings.
+ * <p>
+ * If the key is not set, then false is returned.
+ */
+ public boolean getBoolean(String key) {
+ Boolean b = (Boolean) get(key);
+ if (b == null) {
+ return false;
+ }
+
+ return b.booleanValue();
+ }
+
+ /**
+ * Get a string from the settings.
+ * <p>
+ * If the key is not set, then null is returned.
+ */
+ public String getString(String key) {
+ String s = (String) get(key);
+ if (s == null) {
+ return null;
+ }
+
+ return s;
+ }
+
+ /**
+ * Set an integer on the settings.
+ */
+ public void putInteger(String key, int value) {
+ put(key, Integer.valueOf(value));
+ }
+
+ /**
+ * Set a boolean on the settings.
+ */
+ public void putBoolean(String key, boolean value) {
+ put(key, Boolean.valueOf(value));
+ }
+
+ /**
+ * Set a string on the settings.
+ */
+ public void putString(String key, String value) {
+ put(key, value);
+ }
+
+ /**
+ * @param frameRate The frame-rate is the upper limit on how high
+ * the application's frames-per-second can go.
+ * (Default: -1 no frame rate limit imposed)
+ */
+ public void setFrameRate(int frameRate) {
+ putInteger("FrameRate", frameRate);
+ }
+
+ /**
+ * @param use If true, the application will initialize and use input.
+ * Set to false for headless applications that do not require keyboard
+ * or mouse input.
+ * (Default: true)
+ */
+ public void setUseInput(boolean use) {
+ putBoolean("UseInput", use);
+ }
+
+ /**
+ * @param use If true, the application will initialize and use joystick
+ * input. Set to false if no joystick input is desired.
+ * (Default: false)
+ */
+ public void setUseJoysticks(boolean use) {
+ putBoolean("DisableJoysticks", !use);
+ }
+
+ /**
+ * Set the graphics renderer to use, one of:<br>
+ * <ul>
+ * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
+ * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li>
+ * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li>
+ * <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate
+ * OpenGL version based on system capabilities</li>
+ * <li>null - Disable graphics rendering</li>
+ * </ul>
+ * @param renderer The renderer to set
+ * (Default: AppSettings.LWJGL_OPENGL2)
+ */
+ public void setRenderer(String renderer) {
+ putString("Renderer", renderer);
+ }
+
+ /**
+ * Set a custom graphics renderer to use. The class should implement
+ * the {@link JmeContext} interface.
+ * @param clazz The custom context class.
+ * (Default: not set)
+ */
+ public void setCustomRenderer(Class<? extends JmeContext> clazz){
+ put("Renderer", "CUSTOM" + clazz.getName());
+ }
+
+ /**
+ * Set the audio renderer to use. One of:<br>
+ * <ul>
+ * <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li>
+ * <li>null - Disable audio</li>
+ * </ul>
+ * @param audioRenderer
+ * (Default: LWJGL)
+ */
+ public void setAudioRenderer(String audioRenderer) {
+ putString("AudioRenderer", audioRenderer);
+ }
+
+ /**
+ * @param value the width for the rendering display.
+ * (Default: 640)
+ */
+ public void setWidth(int value) {
+ putInteger("Width", value);
+ }
+
+ /**
+ * @param value the height for the rendering display.
+ * (Default: 480)
+ */
+ public void setHeight(int value) {
+ putInteger("Height", value);
+ }
+
+ /**
+ * Set the resolution for the rendering display
+ * @param width The width
+ * @param height The height
+ * (Default: 640x480)
+ */
+ public void setResolution(int width, int height) {
+ setWidth(width);
+ setHeight(height);
+ }
+
+ /**
+ * Set the frequency, also known as refresh rate, for the
+ * rendering display.
+ * @param value The frequency
+ * (Default: 60)
+ */
+ public void setFrequency(int value) {
+ putInteger("Frequency", value);
+ }
+
+ /**
+ * Sets the number of depth bits to use.
+ * <p>
+ * The number of depth bits specifies the precision of the depth buffer.
+ * To increase precision, specify 32 bits. To decrease precision, specify
+ * 16 bits. On some platforms 24 bits might not be supported, in that case,
+ * specify 16 bits.<p>
+ * (Default: 24)
+ *
+ * @param value The depth bits
+ */
+ public void setDepthBits(int value){
+ putInteger("DepthBits", value);
+ }
+
+ /**
+ * Set the number of stencil bits.
+ * <p>
+ * This value is only relevant when the stencil buffer is being used.
+ * Specify 8 to indicate an 8-bit stencil buffer, specify 0 to disable
+ * the stencil buffer.
+ * </p>
+ * (Default: 0)
+ *
+ * @param value Number of stencil bits
+ */
+ public void setStencilBits(int value){
+ putInteger("StencilBits", value);
+ }
+
+ /**
+ * Set the bits per pixel for the display. Appropriate
+ * values are 16 for RGB565 color format, or 24 for RGB8 color format.
+ *
+ * @param value The bits per pixel to set
+ * (Default: 24)
+ */
+ public void setBitsPerPixel(int value) {
+ putInteger("BitsPerPixel", value);
+ }
+
+ /**
+ * Set the number of samples per pixel. A value of 1 indicates
+ * each pixel should be single-sampled, higher values indicate
+ * a pixel should be multi-sampled.
+ *
+ * @param value The number of samples
+ * (Default: 1)
+ */
+ public void setSamples(int value) {
+ putInteger("Samples", value);
+ }
+
+ /**
+ * @param title The title of the rendering display
+ * (Default: jMonkeyEngine 3.0)
+ */
+ public void setTitle(String title) {
+ putString("Title", title);
+ }
+
+ /**
+ * @param value true to enable full-screen rendering, false to render in a window
+ * (Default: false)
+ */
+ public void setFullscreen(boolean value) {
+ putBoolean("Fullscreen", value);
+ }
+
+ /**
+ * Set to true to enable vertical-synchronization, limiting and synchronizing
+ * every frame rendered to the monitor's refresh rate.
+ * @param value
+ * (Default: false)
+ */
+ public void setVSync(boolean value) {
+ putBoolean("VSync", value);
+ }
+
+ /**
+ * Enable 3D stereo.
+ * <p>This feature requires hardware support from the GPU driver.
+ * @see <a href="http://en.wikipedia.org/wiki/Quad_buffering">http://en.wikipedia.org/wiki/Quad_buffering</a><br />
+ * Once enabled, filters or scene processors that handle 3D stereo rendering
+ * could use this feature to render using hardware 3D stereo.</p>
+ * (Default: false)
+ */
+ public void setStereo3D(boolean value){
+ putBoolean("Stereo3D", value);
+ }
+
+ /**
+ * Sets the application icons to be used, with the most preferred first.
+ * For Windows you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar,
+ * the latter for the alt-tab icon.
+ * Linux (and similar platforms) expect one 32x32 icon.
+ * Mac OS X should be supplied one 128x128 icon.
+ * <br/>
+ * The icon is used for the settings window, and the LWJGL render window. Not currently supported for JOGL.
+ * Note that a bug in Java 6 (bug ID 6445278, currently hidden but available in Google cache) currently prevents
+ * the icon working for alt-tab on the settings dialog in Windows.
+ *
+ * @param value An array of BufferedImages to use as icons.
+ * (Default: not set)
+ */
+ public void setIcons(Object[] value) {
+ put("Icons", value);
+ }
+
+ /**
+ * Sets the path of the settings dialog image to use.
+ * <p>
+ * The image will be displayed in the settings dialog when the
+ * application is started.
+ * </p>
+ * (Default: /com/jme3/app/Monkey.png)
+ *
+ * @param path The path to the image in the classpath.
+ */
+ public void setSettingsDialogImage(String path) {
+ putString("SettingsDialogImage", path);
+ }
+
+ /**
+ * Get the framerate.
+ * @see #setFrameRate(int)
+ */
+ public int getFrameRate() {
+ return getInteger("FrameRate");
+ }
+
+ /**
+ * Get the use input state.
+ * @see #setUseInput(boolean)
+ */
+ public boolean useInput() {
+ return getBoolean("UseInput");
+ }
+
+ /**
+ * Get the renderer
+ * @see #setRenderer(java.lang.String)
+ */
+ public String getRenderer() {
+ return getString("Renderer");
+ }
+
+ /**
+ * Get the width
+ * @see #setWidth(int)
+ */
+ public int getWidth() {
+ return getInteger("Width");
+ }
+
+ /**
+ * Get the height
+ * @see #setHeight(int)
+ */
+ public int getHeight() {
+ return getInteger("Height");
+ }
+
+ /**
+ * Get the bits per pixel
+ * @see #setBitsPerPixel(int)
+ */
+ public int getBitsPerPixel() {
+ return getInteger("BitsPerPixel");
+ }
+
+ /**
+ * Get the frequency
+ * @see #setFrequency(int)
+ */
+ public int getFrequency() {
+ return getInteger("Frequency");
+ }
+
+ /**
+ * Get the number of depth bits
+ * @see #setDepthBits(int)
+ */
+ public int getDepthBits() {
+ return getInteger("DepthBits");
+ }
+
+ /**
+ * Get the number of stencil bits
+ * @see #setStencilBits(int)
+ */
+ public int getStencilBits() {
+ return getInteger("StencilBits");
+ }
+
+ /**
+ * Get the number of samples
+ * @see #setSamples(int)
+ */
+ public int getSamples() {
+ return getInteger("Samples");
+ }
+
+ /**
+ * Get the application title
+ * @see #setTitle(java.lang.String)
+ */
+ public String getTitle() {
+ return getString("Title");
+ }
+
+ /**
+ * Get the vsync state
+ * @see #setVSync(boolean)
+ */
+ public boolean isVSync() {
+ return getBoolean("VSync");
+ }
+
+ /**
+ * Get the fullscreen state
+ * @see #setFullscreen(boolean)
+ */
+ public boolean isFullscreen() {
+ return getBoolean("Fullscreen");
+ }
+
+ /**
+ * Get the use joysticks state
+ * @see #setUseJoysticks(boolean)
+ */
+ public boolean useJoysticks() {
+ return !getBoolean("DisableJoysticks");
+ }
+
+ /**
+ * Get the audio renderer
+ * @see #setAudioRenderer(java.lang.String)
+ */
+ public String getAudioRenderer() {
+ return getString("AudioRenderer");
+ }
+
+ /**
+ * Get the stereo 3D state
+ * @see #setStereo3D(boolean)
+ */
+ public boolean useStereo3D(){
+ return getBoolean("Stereo3D");
+ }
+
+ /**
+ * Get the icon array
+ * @see #setIcons(java.lang.Object[])
+ */
+ public Object[] getIcons() {
+ return (Object[]) get("Icons");
+ }
+
+ /**
+ * Get the settings dialog image
+ * @see #setSettingsDialogImage(java.lang.String)
+ */
+ public String getSettingsDialogImage() {
+ return getString("SettingsDialogImage");
+ }
+}
diff --git a/engine/src/core/com/jme3/system/JmeContext.java b/engine/src/core/com/jme3/system/JmeContext.java
new file mode 100644
index 0000000..37283a5
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeContext.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.renderer.Renderer;
+
+/**
+ * Represents a rendering context within the engine.
+ */
+public interface JmeContext {
+
+ /**
+ * The type of context.
+ */
+ public enum Type {
+ /**
+ * A display can represent a windowed or a fullscreen-exclusive display.
+ * If windowed, the graphics are rendered to a new on-screen surface
+ * enclosed in a window defined by the operating system. Implementations
+ * are encouraged to not use AWT or Swing to create the OpenGL display
+ * but rather use native operating system functions to set up a native
+ * display with the windowing system.
+ */
+ Display,
+
+ /**
+ * A canvas type context makes a rendering surface available as an
+ * AWT {@link java.awt.Canvas} object that can be embedded in a Swing/AWT
+ * frame. To retrieve the Canvas object, you should cast the context
+ * to {@link JmeCanvasContext}.
+ */
+ Canvas,
+
+ /**
+ * An <code>OffscreenSurface</code> is a context that is not visible
+ * by the user. The application can use the offscreen surface to do
+ * General Purpose GPU computations or render a scene into a buffer
+ * in order to save it as a screenshot, video or send through a network.
+ */
+ OffscreenSurface,
+
+ /**
+ * A <code>Headless</code> context is not visible and does not have
+ * any drawable surface. The implementation does not provide any
+ * display, input, or sound support.
+ */
+ Headless;
+ }
+
+ /**
+ * @return The type of the context.
+ */
+ public Type getType();
+
+ /**
+ * @param settings the display settings to use for the created context. If
+ * the context has already been created, then <code>restart()</code> must be called
+ * for the changes to be applied.
+ */
+ public void setSettings(AppSettings settings);
+
+ /**
+ * Sets the listener that will receive events relating to context
+ * creation, update, and destroy.
+ */
+ public void setSystemListener(SystemListener listener);
+
+ /**
+ * @return The current display settings. Note that they might be
+ * different from the ones set with setDisplaySettings() if the context
+ * was restarted or the settings changed internally.
+ */
+ public AppSettings getSettings();
+
+ /**
+ * @return The renderer for this context, or null if not created yet.
+ */
+ public Renderer getRenderer();
+
+ /**
+ * @return Mouse input implementation. May be null if not available.
+ */
+ public MouseInput getMouseInput();
+
+ /**
+ * @return Keyboard input implementation. May be null if not available.
+ */
+ public KeyInput getKeyInput();
+
+ /**
+ * @return Joystick input implementation. May be null if not available.
+ */
+ public JoyInput getJoyInput();
+
+ /**
+ * @return Touch device input implementation. May be null if not available.
+ */
+ public TouchInput getTouchInput();
+
+ /**
+ * @return The timer for this context, or null if not created yet.
+ */
+ public Timer getTimer();
+
+ /**
+ * Sets the title of the display (if available). This does nothing
+ * for fullscreen, headless, or canvas contexts.
+ * @param title The new title of the display.
+ */
+ public void setTitle(String title);
+
+ /**
+ * @return True if the context has been created but not yet destroyed.
+ */
+ public boolean isCreated();
+
+ /**
+ * @return True if the context contains a valid render surface,
+ * if any of the rendering methods in {@link Renderer} are called
+ * while this is <code>false</code>, then the result is undefined.
+ */
+ public boolean isRenderable();
+
+ /**
+ * @param enabled If enabled, the context will automatically flush
+ * frames to the video card (swap buffers) after an update cycle.
+ */
+ public void setAutoFlushFrames(boolean enabled);
+
+ /**
+ * Creates the context and makes it active.
+ *
+ * @param waitFor If true, will wait until context has initialized.
+ */
+ public void create(boolean waitFor);
+
+ /**
+ * Destroys and then re-creates the context. This should be called after
+ * the display settings have been changed.
+ */
+ public void restart();
+
+ /**
+ * Destroys the context completely, making it inactive.
+ *
+ * @param waitFor If true, will wait until the context is destroyed fully.
+ */
+ public void destroy(boolean waitFor);
+
+}
diff --git a/engine/src/core/com/jme3/system/JmeSystem.java b/engine/src/core/com/jme3/system/JmeSystem.java
new file mode 100644
index 0000000..1a13a42
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeSystem.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioRenderer;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class JmeSystem {
+
+ private static JmeSystemDelegate systemDelegate;
+
+ public static void setSystemDelegate(JmeSystemDelegate systemDelegate) {
+ JmeSystem.systemDelegate = systemDelegate;
+ }
+
+ public static synchronized File getStorageFolder() {
+ checkDelegate();
+ return systemDelegate.getStorageFolder();
+ }
+
+ public static String getFullName() {
+ checkDelegate();
+ return systemDelegate.getFullName();
+ }
+
+ public static InputStream getResourceAsStream(String name) {
+ checkDelegate();
+ return systemDelegate.getResourceAsStream(name);
+ }
+
+ public static URL getResource(String name) {
+ checkDelegate();
+ return systemDelegate.getResource(name);
+ }
+
+ public static boolean trackDirectMemory() {
+ checkDelegate();
+ return systemDelegate.trackDirectMemory();
+ }
+
+ public static void setLowPermissions(boolean lowPerm) {
+ checkDelegate();
+ systemDelegate.setLowPermissions(lowPerm);
+ }
+
+ public static boolean isLowPermissions() {
+ checkDelegate();
+ return systemDelegate.isLowPermissions();
+ }
+
+ public static AssetManager newAssetManager(URL configFile) {
+ checkDelegate();
+ return systemDelegate.newAssetManager(configFile);
+ }
+
+ public static AssetManager newAssetManager() {
+ checkDelegate();
+ return systemDelegate.newAssetManager();
+ }
+
+ public static boolean showSettingsDialog(AppSettings sourceSettings, final boolean loadFromRegistry) {
+ checkDelegate();
+ return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry);
+ }
+
+ public static Platform getPlatform() {
+ checkDelegate();
+ return systemDelegate.getPlatform();
+ }
+
+ public static JmeContext newContext(AppSettings settings, JmeContext.Type contextType) {
+ checkDelegate();
+ return systemDelegate.newContext(settings, contextType);
+ }
+
+ public static AudioRenderer newAudioRenderer(AppSettings settings) {
+ checkDelegate();
+ return systemDelegate.newAudioRenderer(settings);
+ }
+
+ public static void initialize(AppSettings settings) {
+ checkDelegate();
+ systemDelegate.initialize(settings);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static void checkDelegate() {
+ if (systemDelegate == null) {
+ Class<JmeSystemDelegate> systemDelegateClass;
+ try {
+ systemDelegateClass = (Class<JmeSystemDelegate>) Class.forName("com.jme3.system.JmeDesktopSystem");
+ systemDelegate = systemDelegateClass.newInstance();
+ } catch (InstantiationException ex) {
+ Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+ } catch (IllegalAccessException ex) {
+ Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+ } catch (ClassNotFoundException ex) {
+ Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "No JmeSystemDelegate specified, cannot instantiate default JmeDesktopSystem:\n{0}", ex);
+ }
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/system/JmeSystemDelegate.java b/engine/src/core/com/jme3/system/JmeSystemDelegate.java
new file mode 100644
index 0000000..60265ae
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeSystemDelegate.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioRenderer;
+import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Kirill Vainer, normenhansen
+ */
+public abstract class JmeSystemDelegate {
+
+ protected final Logger logger = Logger.getLogger(JmeSystem.class.getName());
+ protected boolean initialized = false;
+ protected boolean lowPermissions = false;
+ protected File storageFolder = null;
+
+ public synchronized File getStorageFolder() {
+ if (lowPermissions) {
+ throw new UnsupportedOperationException("File system access restricted");
+ }
+ if (storageFolder == null) {
+ // Initialize storage folder
+ storageFolder = new File(System.getProperty("user.home"), ".jme3");
+ if (!storageFolder.exists()) {
+ storageFolder.mkdir();
+ }
+ }
+ return storageFolder;
+ }
+
+ public String getFullName() {
+ return JmeVersion.FULL_NAME;
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return this.getClass().getResourceAsStream(name);
+ }
+
+ public URL getResource(String name) {
+ return this.getClass().getResource(name);
+ }
+
+ public boolean trackDirectMemory() {
+ return false;
+ }
+
+ public void setLowPermissions(boolean lowPerm) {
+ lowPermissions = lowPerm;
+ }
+
+ public boolean isLowPermissions() {
+ return lowPermissions;
+ }
+
+ public abstract AssetManager newAssetManager(URL configFile);
+
+ public abstract AssetManager newAssetManager();
+
+ public abstract boolean showSettingsDialog(AppSettings sourceSettings, boolean loadFromRegistry);
+
+ private boolean is64Bit(String arch) {
+ if (arch.equals("x86")) {
+ return false;
+ } else if (arch.equals("amd64")) {
+ return true;
+ } else if (arch.equals("x86_64")) {
+ return true;
+ } else if (arch.equals("ppc") || arch.equals("PowerPC")) {
+ return false;
+ } else if (arch.equals("ppc64")) {
+ return true;
+ } else if (arch.equals("i386") || arch.equals("i686")) {
+ return false;
+ } else if (arch.equals("universal")) {
+ return false;
+ } else {
+ throw new UnsupportedOperationException("Unsupported architecture: " + arch);
+ }
+ }
+
+ public Platform getPlatform() {
+ String os = System.getProperty("os.name").toLowerCase();
+ String arch = System.getProperty("os.arch").toLowerCase();
+ boolean is64 = is64Bit(arch);
+ if (os.contains("windows")) {
+ return is64 ? Platform.Windows64 : Platform.Windows32;
+ } else if (os.contains("linux") || os.contains("freebsd") || os.contains("sunos")) {
+ return is64 ? Platform.Linux64 : Platform.Linux32;
+ } else if (os.contains("mac os x") || os.contains("darwin")) {
+ if (arch.startsWith("ppc")) {
+ return is64 ? Platform.MacOSX_PPC64 : Platform.MacOSX_PPC32;
+ } else {
+ return is64 ? Platform.MacOSX64 : Platform.MacOSX32;
+ }
+ } else {
+ throw new UnsupportedOperationException("The specified platform: " + os + " is not supported.");
+ }
+ }
+
+ public abstract JmeContext newContext(AppSettings settings, JmeContext.Type contextType);
+
+ public abstract AudioRenderer newAudioRenderer(AppSettings settings);
+
+ public abstract void initialize(AppSettings settings);
+}
diff --git a/engine/src/core/com/jme3/system/JmeVersion.java b/engine/src/core/com/jme3/system/JmeVersion.java
new file mode 100644
index 0000000..95d021f
--- /dev/null
+++ b/engine/src/core/com/jme3/system/JmeVersion.java
@@ -0,0 +1,5 @@
+package com.jme3.system;
+
+public class JmeVersion {
+ public static final String FULL_NAME = "jMonkeyEngine 3.0.0 Beta";
+}
diff --git a/engine/src/core/com/jme3/system/NanoTimer.java b/engine/src/core/com/jme3/system/NanoTimer.java
new file mode 100644
index 0000000..13a150a
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NanoTimer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+/**
+ * <code>NanoTimer</code> is a System.nanoTime implementation of <code>Timer</code>.
+ * This is primarily useful for headless applications running on a server.
+ *
+ * @author Matthew D. Hicks
+ */
+public class NanoTimer extends Timer {
+
+ private static final long TIMER_RESOLUTION = 1000000000L;
+ private static final float INVERSE_TIMER_RESOLUTION = 1f/1000000000L;
+
+ private long startTime;
+ private long previousTime;
+ private float tpf;
+ private float fps;
+
+ public NanoTimer() {
+ startTime = System.nanoTime();
+ }
+
+ /**
+ * Returns the time in seconds. The timer starts
+ * at 0.0 seconds.
+ *
+ * @return the current time in seconds
+ */
+ @Override
+ public float getTimeInSeconds() {
+ return getTime() * INVERSE_TIMER_RESOLUTION;
+ }
+
+ public long getTime() {
+ return System.nanoTime() - startTime;
+ }
+
+ public long getResolution() {
+ return TIMER_RESOLUTION;
+ }
+
+ public float getFrameRate() {
+ return fps;
+ }
+
+ public float getTimePerFrame() {
+ return tpf;
+ }
+
+ public void update() {
+ tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION);
+ fps = 1.0f / tpf;
+ previousTime = getTime();
+ }
+
+ public void reset() {
+ startTime = System.nanoTime();
+ previousTime = getTime();
+ }
+}
diff --git a/engine/src/core/com/jme3/system/NullContext.java b/engine/src/core/com/jme3/system/NullContext.java
new file mode 100644
index 0000000..75fdf2b
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NullContext.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.input.JoyInput;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.TouchInput;
+import com.jme3.input.dummy.DummyKeyInput;
+import com.jme3.input.dummy.DummyMouseInput;
+import com.jme3.renderer.Renderer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class NullContext implements JmeContext, Runnable {
+
+ protected static final Logger logger = Logger.getLogger(NullContext.class.getName());
+
+ protected AtomicBoolean created = new AtomicBoolean(false);
+ protected AtomicBoolean needClose = new AtomicBoolean(false);
+ protected final Object createdLock = new Object();
+
+ protected int frameRate;
+ protected AppSettings settings = new AppSettings(true);
+ protected Timer timer;
+ protected SystemListener listener;
+ protected NullRenderer renderer;
+
+ public Type getType() {
+ return Type.Headless;
+ }
+
+ public void setSystemListener(SystemListener listener){
+ this.listener = listener;
+ }
+
+ protected void initInThread(){
+ logger.info("NullContext created.");
+ logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName());
+
+ Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ public void uncaughtException(Thread thread, Throwable thrown) {
+ listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown);
+ }
+ });
+
+ timer = new NanoTimer();
+ renderer = new NullRenderer();
+ synchronized (createdLock){
+ created.set(true);
+ createdLock.notifyAll();
+ }
+
+ listener.initialize();
+ }
+
+ protected void deinitInThread(){
+ listener.destroy();
+ timer = null;
+ synchronized (createdLock){
+ created.set(false);
+ createdLock.notifyAll();
+ }
+ }
+
+ private static long timeThen;
+ private static long timeLate;
+
+ public void sync(int fps) {
+ long timeNow;
+ long gapTo;
+ long savedTimeLate;
+
+ gapTo = timer.getResolution() / fps + timeThen;
+ timeNow = timer.getTime();
+ savedTimeLate = timeLate;
+
+ try {
+ while (gapTo > timeNow + savedTimeLate) {
+ Thread.sleep(1);
+ timeNow = timer.getTime();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ if (gapTo < timeNow) {
+ timeLate = timeNow - gapTo;
+ } else {
+ timeLate = 0;
+ }
+
+ timeThen = timeNow;
+ }
+
+ public void run(){
+ initInThread();
+
+ while (!needClose.get()){
+ listener.update();
+
+ if (frameRate > 0)
+ sync(frameRate);
+ }
+
+ deinitInThread();
+
+ logger.info("NullContext destroyed.");
+ }
+
+ public void destroy(boolean waitFor){
+ needClose.set(true);
+ if (waitFor)
+ waitFor(false);
+ }
+
+ public void create(boolean waitFor){
+ if (created.get()){
+ logger.warning("create() called when NullContext is already created!");
+ return;
+ }
+
+ new Thread(this, "Headless Application Thread").start();
+ if (waitFor)
+ waitFor(true);
+ }
+
+ public void restart() {
+ }
+
+ public void setAutoFlushFrames(boolean enabled){
+ }
+
+ public MouseInput getMouseInput() {
+ return new DummyMouseInput();
+ }
+
+ public KeyInput getKeyInput() {
+ return new DummyKeyInput();
+ }
+
+ public JoyInput getJoyInput() {
+ return null;
+ }
+
+ public TouchInput getTouchInput() {
+ return null;
+ }
+
+ public void setTitle(String title) {
+ }
+
+ public void create(){
+ create(false);
+ }
+
+ public void destroy(){
+ destroy(false);
+ }
+
+ protected void waitFor(boolean createdVal){
+ synchronized (createdLock){
+ while (created.get() != createdVal){
+ try {
+ createdLock.wait();
+ } catch (InterruptedException ex) {
+ }
+ }
+ }
+ }
+
+ public boolean isCreated(){
+ return created.get();
+ }
+
+ public void setSettings(AppSettings settings) {
+ this.settings.copyFrom(settings);
+ frameRate = settings.getFrameRate();
+ if (frameRate <= 0)
+ frameRate = 60; // use default update rate.
+ }
+
+ public AppSettings getSettings(){
+ return settings;
+ }
+
+ public Renderer getRenderer() {
+ return renderer;
+ }
+
+ public Timer getTimer() {
+ return timer;
+ }
+
+ public boolean isRenderable() {
+ return true; // Doesn't really matter if true or false. Either way
+ // RenderManager won't render anything.
+ }
+}
diff --git a/engine/src/core/com/jme3/system/NullRenderer.java b/engine/src/core/com/jme3/system/NullRenderer.java
new file mode 100644
index 0000000..2b1d821
--- /dev/null
+++ b/engine/src/core/com/jme3/system/NullRenderer.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+import com.jme3.light.LightList;
+import com.jme3.material.RenderState;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.renderer.Statistics;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.shader.Shader;
+import com.jme3.shader.Shader.ShaderSource;
+import com.jme3.texture.FrameBuffer;
+import com.jme3.texture.Image;
+import com.jme3.texture.Texture;
+import java.nio.ByteBuffer;
+import java.util.EnumSet;
+
+public class NullRenderer implements Renderer {
+
+ private static final EnumSet<Caps> caps = EnumSet.noneOf(Caps.class);
+ private static final Statistics stats = new Statistics();
+
+ public EnumSet<Caps> getCaps() {
+ return caps;
+ }
+
+ public Statistics getStatistics() {
+ return stats;
+ }
+
+ public void invalidateState(){
+ }
+
+ public void clearBuffers(boolean color, boolean depth, boolean stencil) {
+ }
+
+ public void setBackgroundColor(ColorRGBA color) {
+ }
+
+ public void applyRenderState(RenderState state) {
+ }
+
+ public void setDepthRange(float start, float end) {
+ }
+
+ public void onFrame() {
+ }
+
+ public void setWorldMatrix(Matrix4f worldMatrix) {
+ }
+
+ public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) {
+ }
+
+ public void setViewPort(int x, int y, int width, int height) {
+ }
+
+ public void setClipRect(int x, int y, int width, int height) {
+ }
+
+ public void clearClipRect() {
+ }
+
+ public void setLighting(LightList lights) {
+ }
+
+ public void setShader(Shader shader) {
+ }
+
+ public void deleteShader(Shader shader) {
+ }
+
+ public void deleteShaderSource(ShaderSource source) {
+ }
+
+ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) {
+ }
+
+ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) {
+ }
+
+ public void setMainFrameBufferOverride(FrameBuffer fb) {
+ }
+
+ public void setFrameBuffer(FrameBuffer fb) {
+ }
+
+ public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) {
+ }
+
+ public void deleteFrameBuffer(FrameBuffer fb) {
+ }
+
+ public void setTexture(int unit, Texture tex) {
+ }
+
+ public void updateBufferData(VertexBuffer vb) {
+ }
+
+ public void deleteBuffer(VertexBuffer vb) {
+ }
+
+ public void renderMesh(Mesh mesh, int lod, int count) {
+ }
+
+ public void resetGLObjects() {
+ }
+
+ public void cleanup() {
+ }
+
+ public void deleteImage(Image image) {
+ }
+
+ public void setAlphaToCoverage(boolean value) {
+ }
+
+}
diff --git a/engine/src/core/com/jme3/system/Platform.java b/engine/src/core/com/jme3/system/Platform.java
new file mode 100644
index 0000000..5a3f5c9
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Platform.java
@@ -0,0 +1,65 @@
+package com.jme3.system;
+
+public enum Platform {
+
+ /**
+ * Microsoft Windows 32 bit
+ */
+ Windows32,
+
+ /**
+ * Microsoft Windows 64 bit
+ */
+ Windows64,
+
+ /**
+ * Linux 32 bit
+ */
+ Linux32,
+
+ /**
+ * Linux 64 bit
+ */
+ Linux64,
+
+ /**
+ * Apple Mac OS X 32 bit
+ */
+ MacOSX32,
+
+ /**
+ * Apple Mac OS X 64 bit
+ */
+ MacOSX64,
+
+ /**
+ * Apple Mac OS X 32 bit PowerPC
+ */
+ MacOSX_PPC32,
+
+ /**
+ * Apple Mac OS X 64 bit PowerPC
+ */
+ MacOSX_PPC64,
+
+ /**
+ * Android ARM5
+ */
+ Android_ARM5,
+
+ /**
+ * Android ARM6
+ */
+ Android_ARM6,
+
+ /**
+ * Android ARM7
+ */
+ Android_ARM7,
+
+ /**
+ * Android x86
+ */
+ Android_X86;
+
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/system/SystemListener.java b/engine/src/core/com/jme3/system/SystemListener.java
new file mode 100644
index 0000000..e1ccf57
--- /dev/null
+++ b/engine/src/core/com/jme3/system/SystemListener.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.system;
+
+/**
+ * The <code>ContextListener> provides a means for an application
+ * to receive events relating to a context.
+ */
+public interface SystemListener {
+
+ /**
+ * Callback to indicate the application to initialize. This method
+ * is called in the GL/Rendering thread so any GL-dependent resources
+ * can be initialized.
+ */
+ public void initialize();
+
+ /**
+ * Called to notify the application that the resolution has changed.
+ * @param width
+ * @param height
+ */
+ public void reshape(int width, int height);
+
+ /**
+ * Callback to update the application state, and render the scene
+ * to the back buffer.
+ */
+ public void update();
+
+ /**
+ * Called when the user requests to close the application. This
+ * could happen when he clicks the X button on the window, presses
+ * the Alt-F4 combination, attempts to shutdown the process from
+ * the task manager, or presses ESC.
+ * @param esc If true, the user pressed ESC to close the application.
+ */
+ public void requestClose(boolean esc);
+
+ /**
+ * Called when the application gained focus. The display
+ * implementation is not allowed to call this method before
+ * initialize() has been called or after destroy() has been called.
+ */
+ public void gainFocus();
+
+ /**
+ * Called when the application lost focus. The display
+ * implementation is not allowed to call this method before
+ * initialize() has been called or after destroy() has been called.
+ */
+ public void loseFocus();
+
+ /**
+ * Called when an error has occured. This is typically
+ * invoked when an uncought exception is thrown in the render thread.
+ * @param errorMsg The error message, if any, or null.
+ * @param t Throwable object, or null.
+ */
+ public void handleError(String errorMsg, Throwable t);
+
+ /**
+ * Callback to indicate that the context has been destroyed (either
+ * by the user or requested by the application itself). Typically
+ * cleanup of native resources should happen here. This method is called
+ * in the GL/Rendering thread.
+ */
+ public void destroy();
+}
diff --git a/engine/src/core/com/jme3/system/Timer.java b/engine/src/core/com/jme3/system/Timer.java
new file mode 100644
index 0000000..3ce1ee8
--- /dev/null
+++ b/engine/src/core/com/jme3/system/Timer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.system;
+
+/**
+ * <code>Timer</code> is the base class for a high resolution timer. It is
+ * created from getTimer("display system")
+ *
+ * @author Mark Powell
+ * @version $Id: Timer.java,v 1.18 2007/03/09 10:19:34 rherlitz Exp $
+ */
+public abstract class Timer {
+
+ /**
+ * Returns the current time in ticks. A tick is an arbitrary measure of time
+ * defined by the timer implementation. The number of ticks per second is
+ * given by <code>getResolution()</code>. The timer starts at 0 ticks.
+ *
+ * @return a long value representing the current time
+ */
+ public abstract long getTime();
+
+ /**
+ * Returns the time in seconds. The timer starts
+ * at 0.0 seconds.
+ *
+ * @return the current time in seconds
+ */
+ public float getTimeInSeconds() {
+ return getTime() / (float) getResolution();
+ }
+
+ /**
+ * Returns the resolution of the timer.
+ *
+ * @return the number of timer ticks per second
+ */
+ public abstract long getResolution();
+
+ /**
+ * Returns the "calls per second". If this is called every frame, then it
+ * will return the "frames per second".
+ *
+ * @return The "calls per second".
+ */
+ public abstract float getFrameRate();
+
+ /**
+ * Returns the time, in seconds, between the last call and the current one.
+ *
+ * @return Time between this call and the last one.
+ */
+ public abstract float getTimePerFrame();
+
+ /**
+ * <code>update</code> recalculates the frame rate based on the previous
+ * call to update. It is assumed that update is called each frame.
+ */
+ public abstract void update();
+
+ /**
+ * Reset the timer to 0. Clear any tpf history.
+ */
+ public abstract void reset();
+}
diff --git a/engine/src/core/com/jme3/texture/FrameBuffer.java b/engine/src/core/com/jme3/texture/FrameBuffer.java
new file mode 100644
index 0000000..b52927f
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/FrameBuffer.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.texture;
+
+import com.jme3.renderer.Caps;
+import com.jme3.renderer.Renderer;
+import com.jme3.texture.Image.Format;
+import com.jme3.util.NativeObject;
+import java.util.ArrayList;
+
+/**
+ * <p>
+ * <code>FrameBuffer</code>s are rendering surfaces allowing
+ * off-screen rendering and render-to-texture functionality.
+ * Instead of the scene rendering to the screen, it is rendered into the
+ * FrameBuffer, the result can be either a texture or a buffer.
+ * <p>
+ * A <code>FrameBuffer</code> supports two methods of rendering,
+ * using a {@link Texture} or using a buffer.
+ * When using a texture, the result of the rendering will be rendered
+ * onto the texture, after which the texture can be placed on an object
+ * and rendered as if the texture was uploaded from disk.
+ * When using a buffer, the result is rendered onto
+ * a buffer located on the GPU, the data of this buffer is not accessible
+ * to the user. buffers are useful if one
+ * wishes to retrieve only the color content of the scene, but still desires
+ * depth testing (which requires a depth buffer).
+ * Buffers can be copied to other framebuffers
+ * including the main screen, by using
+ * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer) }.
+ * The content of a {@link RenderBuffer} can be retrieved by using
+ * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }.
+ * <p>
+ * <code>FrameBuffer</code>s have several attachment points, there are
+ * several <em>color</em> attachment points and a single <em>depth</em>
+ * attachment point.
+ * The color attachment points support image formats such as
+ * {@link Format#RGBA8}, allowing rendering the color content of the scene.
+ * The depth attachment point requires a depth image format.
+ *
+ * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer)
+ *
+ * @author Kirill Vainer
+ */
+public class FrameBuffer extends NativeObject {
+
+ private int width = 0;
+ private int height = 0;
+ private int samples = 1;
+ private ArrayList<RenderBuffer> colorBufs = new ArrayList<RenderBuffer>();
+ private RenderBuffer depthBuf = null;
+ private int colorBufIndex = 0;
+
+ /**
+ * <code>RenderBuffer</code> represents either a texture or a
+ * buffer that will be rendered to. <code>RenderBuffer</code>s
+ * are attached to an attachment slot on a <code>FrameBuffer</code>.
+ */
+ public class RenderBuffer {
+
+ Texture tex;
+ Image.Format format;
+ int id = -1;
+ int slot = -1;
+
+ /**
+ * @return The image format of the render buffer.
+ */
+ public Format getFormat() {
+ return format;
+ }
+
+ /**
+ * @return The texture to render to for this <code>RenderBuffer</code>
+ * or null if content should be rendered into a buffer.
+ */
+ public Texture getTexture(){
+ return tex;
+ }
+
+ /**
+ * Do not use.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * Do not use.
+ */
+ public void setId(int id){
+ this.id = id;
+ }
+
+ /**
+ * Do not use.
+ */
+ public int getSlot() {
+ return slot;
+ }
+
+ public void resetObject(){
+ id = -1;
+ }
+
+ public RenderBuffer createDestructableClone(){
+ if (tex != null){
+ return null;
+ }else{
+ RenderBuffer destructClone = new RenderBuffer();
+ destructClone.id = id;
+ return destructClone;
+ }
+ }
+
+ @Override
+ public String toString(){
+ if (tex != null){
+ return "TextureTarget[format=" + format + "]";
+ }else{
+ return "BufferTarget[format=" + format + "]";
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Creates a new FrameBuffer with the given width, height, and number
+ * of samples. If any textures are attached to this FrameBuffer, then
+ * they must have the same number of samples as given in this constructor.
+ * <p>
+ * Note that if the {@link Renderer} does not expose the
+ * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown
+ * if the width and height arguments are not power of two.
+ *
+ * @param width The width to use
+ * @param height The height to use
+ * @param samples The number of samples to use for a multisampled
+ * framebuffer, or 1 if the framebuffer should be singlesampled.
+ *
+ * @throws IllegalArgumentException If width or height are not positive.
+ */
+ public FrameBuffer(int width, int height, int samples){
+ super(FrameBuffer.class);
+ if (width <= 0 || height <= 0)
+ throw new IllegalArgumentException("FrameBuffer must have valid size.");
+
+ this.width = width;
+ this.height = height;
+ this.samples = samples == 0 ? 1 : samples;
+ }
+
+ protected FrameBuffer(FrameBuffer src){
+ super(FrameBuffer.class, src.id);
+ /*
+ for (RenderBuffer renderBuf : src.colorBufs){
+ RenderBuffer clone = renderBuf.createDestructableClone();
+ if (clone != null)
+ this.colorBufs.add(clone);
+ }
+
+ this.depthBuf = src.depthBuf.createDestructableClone();
+ */
+ }
+
+ /**
+ * Enables the use of a depth buffer for this <code>FrameBuffer</code>.
+ *
+ * @param format The format to use for the depth buffer.
+ * @throws IllegalArgumentException If <code>format</code> is not a depth format.
+ */
+ public void setDepthBuffer(Image.Format format){
+ if (id != -1)
+ throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+ if (!format.isDepthFormat())
+ throw new IllegalArgumentException("Depth buffer format must be depth.");
+
+ depthBuf = new RenderBuffer();
+ depthBuf.slot = -100; // -100 == special slot for DEPTH_BUFFER
+ depthBuf.format = format;
+ }
+
+ /**
+ * Enables the use of a color buffer for this <code>FrameBuffer</code>.
+ *
+ * @param format The format to use for the color buffer.
+ * @throws IllegalArgumentException If <code>format</code> is not a color format.
+ */
+ public void setColorBuffer(Image.Format format){
+ if (id != -1)
+ throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+ if (format.isDepthFormat())
+ throw new IllegalArgumentException("Color buffer format must be color/luminance.");
+
+ RenderBuffer colorBuf = new RenderBuffer();
+ colorBuf.slot = 0;
+ colorBuf.format = format;
+
+ colorBufs.clear();
+ colorBufs.add(colorBuf);
+ }
+
+ private void checkSetTexture(Texture tex, boolean depth){
+ Image img = tex.getImage();
+ if (img == null)
+ throw new IllegalArgumentException("Texture not initialized with RTT.");
+
+ if (depth && !img.getFormat().isDepthFormat())
+ throw new IllegalArgumentException("Texture image format must be depth.");
+ else if (!depth && img.getFormat().isDepthFormat())
+ throw new IllegalArgumentException("Texture image format must be color/luminance.");
+
+ // check that resolution matches texture resolution
+ if (width != img.getWidth() || height != img.getHeight())
+ throw new IllegalArgumentException("Texture image resolution " +
+ "must match FB resolution");
+
+ if (samples != tex.getImage().getMultiSamples())
+ throw new IllegalStateException("Texture samples must match framebuffer samples");
+ }
+
+ /**
+ * If enabled, any shaders rendering into this <code>FrameBuffer</code>
+ * will be able to write several results into the renderbuffers
+ * by using the <code>gl_FragData</code> array. Every slot in that
+ * array maps into a color buffer attached to this framebuffer.
+ *
+ * @param enabled True to enable MRT (multiple rendering targets).
+ */
+ public void setMultiTarget(boolean enabled){
+ if (enabled) colorBufIndex = -1;
+ else colorBufIndex = 0;
+ }
+
+ /**
+ * @return True if MRT (multiple rendering targets) is enabled.
+ * @see FrameBuffer#setMultiTarget(boolean)
+ */
+ public boolean isMultiTarget(){
+ return colorBufIndex == -1;
+ }
+
+ /**
+ * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false)
+ * then this specifies the color target to which the scene should be rendered.
+ * <p>
+ * By default the value is 0.
+ *
+ * @param index The color attachment index.
+ * @throws IllegalArgumentException If index is negative or doesn't map
+ * to any attachment on this framebuffer.
+ */
+ public void setTargetIndex(int index){
+ if (index < 0 || index >= 16)
+ throw new IllegalArgumentException("Target index must be between 0 and 16");
+
+ if (colorBufs.size() < index)
+ throw new IllegalArgumentException("The target at " + index + " is not set!");
+
+ colorBufIndex = index;
+ setUpdateNeeded();
+ }
+
+ /**
+ * @return The color target to which the scene should be rendered.
+ *
+ * @see FrameBuffer#setTargetIndex(int)
+ */
+ public int getTargetIndex(){
+ return colorBufIndex;
+ }
+
+ /**
+ * Set the color texture to use for this framebuffer.
+ * This automatically clears all existing textures added previously
+ * with {@link FrameBuffer#addColorTexture(com.jme3.texture.Texture2D) }
+ * and adds this texture as the only target.
+ *
+ * @param tex The color texture to set.
+ */
+ public void setColorTexture(Texture2D tex){
+ clearColorTargets();
+ addColorTexture(tex);
+ }
+
+ /**
+ * Clears all color targets that were set or added previously.
+ */
+ public void clearColorTargets(){
+ colorBufs.clear();
+ }
+
+ /**
+ * Add a color texture to use for this framebuffer.
+ * If MRT is enabled, then each subsequently added texture can be
+ * rendered to through a shader that writes to the array <code>gl_FragData</code>.
+ * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) }
+ * is rendered to by the shader.
+ *
+ * @param tex The texture to add.
+ */
+ public void addColorTexture(Texture2D tex) {
+ if (id != -1)
+ throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+ Image img = tex.getImage();
+ checkSetTexture(tex, false);
+
+ RenderBuffer colorBuf = new RenderBuffer();
+ colorBuf.slot = colorBufs.size();
+ colorBuf.tex = tex;
+ colorBuf.format = img.getFormat();
+
+ colorBufs.add(colorBuf);
+ }
+
+ /**
+ * Set the depth texture to use for this framebuffer.
+ *
+ * @param tex The color texture to set.
+ */
+ public void setDepthTexture(Texture2D tex){
+ if (id != -1)
+ throw new UnsupportedOperationException("FrameBuffer already initialized.");
+
+ Image img = tex.getImage();
+ checkSetTexture(tex, true);
+
+ depthBuf = new RenderBuffer();
+ depthBuf.slot = -100; // indicates GL_DEPTH_ATTACHMENT
+ depthBuf.tex = tex;
+ depthBuf.format = img.getFormat();
+ }
+
+ /**
+ * @return The number of color buffers attached to this texture.
+ */
+ public int getNumColorBuffers(){
+ return colorBufs.size();
+ }
+
+ /**
+ * @param index
+ * @return The color buffer at the given index.
+ */
+ public RenderBuffer getColorBuffer(int index){
+ return colorBufs.get(index);
+ }
+
+ /**
+ * @return The first color buffer attached to this FrameBuffer, or null
+ * if no color buffers are attached.
+ */
+ public RenderBuffer getColorBuffer() {
+ if (colorBufs.isEmpty())
+ return null;
+
+ return colorBufs.get(0);
+ }
+
+ /**
+ * @return The depth buffer attached to this FrameBuffer, or null
+ * if no depth buffer is attached
+ */
+ public RenderBuffer getDepthBuffer() {
+ return depthBuf;
+ }
+
+ /**
+ * @return The height in pixels of this framebuffer.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * @return The width in pixels of this framebuffer.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return The number of samples when using a multisample framebuffer, or
+ * 1 if this is a singlesampled framebuffer.
+ */
+ public int getSamples() {
+ return samples;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt";
+ sb.append("FrameBuffer[format=").append(width).append("x").append(height)
+ .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n");
+ if (depthBuf != null)
+ sb.append("Depth => ").append(depthBuf).append("\n");
+ for (RenderBuffer colorBuf : colorBufs){
+ sb.append("Color(").append(colorBuf.slot)
+ .append(") => ").append(colorBuf).append("\n");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public void resetObject() {
+ this.id = -1;
+
+ for (int i = 0; i < colorBufs.size(); i++) {
+ colorBufs.get(i).resetObject();
+ }
+
+ if (depthBuf != null)
+ depthBuf.resetObject();
+
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((Renderer)rendererObject).deleteFrameBuffer(this);
+ }
+
+ public NativeObject createDestructableClone(){
+ return new FrameBuffer(this);
+ }
+}
diff --git a/engine/src/core/com/jme3/texture/Image.java b/engine/src/core/com/jme3/texture/Image.java
new file mode 100644
index 0000000..5d23522
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Image.java
@@ -0,0 +1,790 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.texture;
+
+import com.jme3.export.*;
+import com.jme3.renderer.Renderer;
+import com.jme3.util.NativeObject;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * <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.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Id: Image.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public class Image extends NativeObject implements Savable /*, Cloneable*/ {
+
+ public enum Format {
+ /**
+ * 8-bit alpha
+ */
+ Alpha8(8),
+
+ /**
+ * 16-bit alpha
+ */
+ Alpha16(16),
+
+ /**
+ * 8-bit grayscale/luminance.
+ */
+ Luminance8(8),
+
+ /**
+ * 16-bit grayscale/luminance.
+ */
+ Luminance16(16),
+
+ /**
+ * half-precision floating-point grayscale/luminance.
+ */
+ Luminance16F(16,true),
+
+ /**
+ * single-precision floating-point grayscale/luminance.
+ */
+ Luminance32F(32,true),
+
+ /**
+ * 8-bit luminance/grayscale and 8-bit alpha.
+ */
+ Luminance8Alpha8(16),
+
+ /**
+ * 16-bit luminance/grayscale and 16-bit alpha.
+ */
+ Luminance16Alpha16(32),
+
+ /**
+ * half-precision floating-point grayscale/luminance and alpha.
+ */
+ Luminance16FAlpha16F(32,true),
+
+ Intensity8(8),
+ Intensity16(16),
+
+ /**
+ * 8-bit blue, green, and red.
+ */
+ BGR8(24), // BGR and ABGR formats are often used on windows systems
+
+ /**
+ * 8-bit red, green, and blue.
+ */
+ RGB8(24),
+
+ /**
+ * 10-bit red, green, and blue.
+ */
+ RGB10(30),
+
+ /**
+ * 16-bit red, green, and blue.
+ */
+ RGB16(48),
+
+ /**
+ * 5-bit red, 6-bit green, and 5-bit blue.
+ */
+ RGB565(16),
+
+ /**
+ * 4-bit alpha, red, green, and blue. Used on Android only.
+ */
+ ARGB4444(16),
+
+ /**
+ * 5-bit red, green, and blue with 1-bit alpha.
+ */
+ RGB5A1(16),
+
+ /**
+ * 8-bit red, green, blue, and alpha.
+ */
+ RGBA8(32),
+
+ /**
+ * 8-bit alpha, blue, green, and red.
+ */
+ ABGR8(32),
+
+ /**
+ * 16-bit red, green, blue and alpha
+ */
+ RGBA16(64),
+
+ /**
+ * S3TC compression DXT1.
+ * Called BC1 in DirectX10.
+ */
+ DXT1(4,false,true, false),
+
+ /**
+ * S3TC compression DXT1 with 1-bit alpha.
+ */
+ DXT1A(4,false,true, false),
+
+ /**
+ * S3TC compression DXT3 with 4-bit alpha.
+ * Called BC2 in DirectX10.
+ */
+ DXT3(8,false,true, false),
+
+ /**
+ * S3TC compression DXT5 with interpolated 8-bit alpha.
+ * Called BC3 in DirectX10.
+ */
+ DXT5(8,false,true, false),
+
+ /**
+ * Luminance-Alpha Texture Compression.
+ * Called BC5 in DirectX10.
+ */
+ LATC(8, false, true, false),
+
+ /**
+ * Arbitrary depth format. The precision is chosen by the video
+ * hardware.
+ */
+ Depth(0,true,false,false),
+
+ /**
+ * 16-bit depth.
+ */
+ Depth16(16,true,false,false),
+
+ /**
+ * 24-bit depth.
+ */
+ Depth24(24,true,false,false),
+
+ /**
+ * 32-bit depth.
+ */
+ Depth32(32,true,false,false),
+
+ /**
+ * single-precision floating point depth.
+ */
+ Depth32F(32,true,false,true),
+
+ /**
+ * Texture data is stored as {@link Format#RGB16F} in system memory,
+ * but will be converted to {@link Format#RGB111110F} when sent
+ * to the video hardware.
+ */
+ RGB16F_to_RGB111110F(48,true),
+
+ /**
+ * unsigned floating-point red, green and blue that uses 32 bits.
+ */
+ RGB111110F(32,true),
+
+ /**
+ * Texture data is stored as {@link Format#RGB16F} in system memory,
+ * but will be converted to {@link Format#RGB9E5} when sent
+ * to the video hardware.
+ */
+ RGB16F_to_RGB9E5(48,true),
+
+ /**
+ * 9-bit red, green and blue with 5-bit exponent.
+ */
+ RGB9E5(32,true),
+
+ /**
+ * half-precision floating point red, green, and blue.
+ */
+ RGB16F(48,true),
+
+ /**
+ * half-precision floating point red, green, blue, and alpha.
+ */
+ RGBA16F(64,true),
+
+ /**
+ * single-precision floating point red, green, and blue.
+ */
+ RGB32F(96,true),
+
+ /**
+ * single-precision floating point red, green, blue and alpha.
+ */
+ RGBA32F(128,true),
+
+ /**
+ * Luminance/grayscale texture compression.
+ * Called BC4 in DirectX10.
+ */
+ LTC(4, false, true, false);
+
+ private int bpp;
+ private boolean isDepth;
+ private boolean isCompressed;
+ private boolean isFloatingPoint;
+
+ private Format(int bpp){
+ this.bpp = bpp;
+ }
+
+ private Format(int bpp, boolean isFP){
+ this(bpp);
+ this.isFloatingPoint = isFP;
+ }
+
+ private Format(int bpp, boolean isDepth, boolean isCompressed, boolean isFP){
+ this(bpp, isFP);
+ this.isDepth = isDepth;
+ this.isCompressed = isCompressed;
+ }
+
+ /**
+ * @return bits per pixel.
+ */
+ public int getBitsPerPixel(){
+ return bpp;
+ }
+
+ /**
+ * @return True if this format is a depth format, false otherwise.
+ */
+ public boolean isDepthFormat(){
+ return isDepth;
+ }
+
+ /**
+ * @return True if this is a compressed image format, false if
+ * uncompressed.
+ */
+ public boolean isCompressed() {
+ return isCompressed;
+ }
+
+ /**
+ * @return True if this image format is in floating point,
+ * false if it is an integer format.
+ */
+ public boolean isFloatingPont(){
+ return isFloatingPoint;
+ }
+
+ }
+
+ // image attributes
+ protected Format format;
+ protected int width, height, depth;
+ protected int[] mipMapSizes;
+ protected ArrayList<ByteBuffer> data;
+ protected transient Object efficientData;
+ protected int multiSamples = 1;
+// protected int mipOffset = 0;
+
+ @Override
+ public void resetObject() {
+ this.id = -1;
+ setUpdateNeeded();
+ }
+
+ @Override
+ public void deleteObject(Object rendererObject) {
+ ((Renderer)rendererObject).deleteImage(this);
+ }
+
+ @Override
+ public NativeObject createDestructableClone() {
+ return new Image(id);
+ }
+
+ /**
+ * @return A shallow clone of this image. The data is not cloned.
+ */
+ @Override
+ public Image clone(){
+ Image clone = (Image) super.clone();
+ clone.mipMapSizes = mipMapSizes != null ? mipMapSizes.clone() : null;
+ clone.data = data != null ? new ArrayList<ByteBuffer>(data) : null;
+ clone.setUpdateNeeded();
+ return clone;
+ }
+
+ /**
+ * Constructor instantiates a new <code>Image</code> object. All values
+ * are undefined.
+ */
+ public Image() {
+ super(Image.class);
+ data = new ArrayList<ByteBuffer>(1);
+ }
+
+ protected Image(int id){
+ super(Image.class, id);
+ }
+
+ /**
+ * 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.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data.
+ * @param mipMapSizes
+ * the array of mipmap sizes, or null for no mipmaps.
+ */
+ public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data,
+ int[] mipMapSizes) {
+
+ this();
+
+ if (mipMapSizes != null && mipMapSizes.length <= 1) {
+ mipMapSizes = null;
+ }
+
+ setFormat(format);
+ this.width = width;
+ this.height = height;
+ this.data = data;
+ this.depth = depth;
+ this.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.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data.
+ * @param mipMapSizes
+ * the array of mipmap sizes, or null for no mipmaps.
+ */
+ public Image(Format format, int width, int height, ByteBuffer data,
+ int[] mipMapSizes) {
+
+ this();
+
+ if (mipMapSizes != null && mipMapSizes.length <= 1) {
+ mipMapSizes = null;
+ }
+
+ setFormat(format);
+ this.width = width;
+ this.height = height;
+ if (data != null){
+ this.data = new ArrayList<ByteBuffer>(1);
+ this.data.add(data);
+ }
+ this.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.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data.
+ */
+ public Image(Format format, int width, int height, int depth, ArrayList<ByteBuffer> data) {
+ this(format, width, height, depth, data, null);
+ }
+
+ /**
+ * 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.
+ * @param width
+ * the width of the image.
+ * @param height
+ * the height of the image.
+ * @param data
+ * the image data.
+ */
+ public Image(Format format, int width, int height, ByteBuffer data) {
+ this(format, width, height, data, null);
+ }
+
+ /**
+ * @return The number of samples (for multisampled textures).
+ * @see Image#setMultiSamples(int)
+ */
+ public int getMultiSamples() {
+ return multiSamples;
+ }
+
+ /**
+ * @param multiSamples Set the number of samples to use for this image,
+ * setting this to a value higher than 1 turns this image/texture
+ * into a multisample texture (on OpenGL3.1 and higher).
+ */
+ public void setMultiSamples(int multiSamples) {
+ if (multiSamples <= 0)
+ throw new IllegalArgumentException("multiSamples must be > 0");
+
+ if (getData(0) != null)
+ throw new IllegalArgumentException("Cannot upload data as multisample texture");
+
+ if (hasMipmaps())
+ throw new IllegalArgumentException("Multisample textures do not support mipmaps");
+
+ this.multiSamples = multiSamples;
+ }
+
+ /**
+ * <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.
+ */
+ public void setData(ArrayList<ByteBuffer> data) {
+ this.data = data;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <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(ByteBuffer data) {
+ this.data = new ArrayList<ByteBuffer>(1);
+ this.data.add(data);
+ setUpdateNeeded();
+ }
+
+ public void addData(ByteBuffer data) {
+ if (this.data == null)
+ this.data = new ArrayList<ByteBuffer>(1);
+ this.data.add(data);
+ setUpdateNeeded();
+ }
+
+ public void setData(int index, ByteBuffer data) {
+ if (index >= 0) {
+ while (this.data.size() <= index) {
+ this.data.add(null);
+ }
+ this.data.set(index, data);
+ setUpdateNeeded();
+ } else {
+ throw new IllegalArgumentException("index must be greater than or equal to 0.");
+ }
+ }
+
+ /**
+ * Set the efficient data representation of this image.
+ * <p>
+ * Some system implementations are more efficient at operating
+ * on data other than ByteBuffers, in that case, this method can be used.
+ *
+ * @param efficientData
+ */
+ public void setEfficentData(Object efficientData){
+ this.efficientData = efficientData;
+ setUpdateNeeded();
+ }
+
+ /**
+ * @return The efficient data representation of this image.
+ * @see Image#setEfficentData(java.lang.Object)
+ */
+ public Object getEfficentData(){
+ return efficientData;
+ }
+
+ /**
+ * Sets the mipmap 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 and this will automatically be expanded
+ * into a single mipmap of the full
+ *
+ * @param mipMapSizes
+ * the mipmap sizes array, or null for a single image map.
+ */
+ public void setMipMapSizes(int[] mipMapSizes) {
+ if (mipMapSizes != null && mipMapSizes.length <= 1)
+ mipMapSizes = null;
+
+ this.mipMapSizes = mipMapSizes;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <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(int height) {
+ this.height = height;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <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(int depth) {
+ this.depth = depth;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <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(int width) {
+ this.width = width;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <code>setFormat</code> sets the image format for this image.
+ *
+ * @param format
+ * the image format.
+ * @throws NullPointerException
+ * if format is null
+ * @see Format
+ */
+ public void setFormat(Format format) {
+ if (format == null) {
+ throw new NullPointerException("format may not be null.");
+ }
+
+ this.format = format;
+ setUpdateNeeded();
+ }
+
+ /**
+ * <code>getFormat</code> returns the image format for this image.
+ *
+ * @return the image format.
+ * @see Format
+ */
+ public Format getFormat() {
+ return format;
+ }
+
+ /**
+ * <code>getWidth</code> returns the width of this image.
+ *
+ * @return the width of this image.
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * <code>getHeight</code> returns the height of this image.
+ *
+ * @return the height of this image.
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * <code>getDepth</code> returns the depth of this image (for 3d images).
+ *
+ * @return the depth of this image.
+ */
+ 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;
+ }
+
+ /**
+ * <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 ByteBuffer getData(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[] getMipMapSizes() {
+ return mipMapSizes;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append("[size=").append(width).append("x").append(height);
+
+ if (depth > 1)
+ sb.append("x").append(depth);
+
+ sb.append(", format=").append(format.name());
+
+ if (hasMipmaps())
+ sb.append(", mips");
+
+ if (getId() >= 0)
+ sb.append(", id=").append(id);
+
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof Image)) {
+ return false;
+ }
+ Image that = (Image) other;
+ if (this.getFormat() != that.getFormat())
+ return false;
+ if (this.getWidth() != that.getWidth())
+ return false;
+ if (this.getHeight() != that.getHeight())
+ return false;
+ if (this.getData() != null && !this.getData().equals(that.getData()))
+ return false;
+ if (this.getData() == null && that.getData() != null)
+ return false;
+ if (this.getMipMapSizes() != null
+ && !Arrays.equals(this.getMipMapSizes(), that.getMipMapSizes()))
+ return false;
+ if (this.getMipMapSizes() == null && that.getMipMapSizes() != null)
+ return false;
+ if (this.getMultiSamples() != that.getMultiSamples())
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 7;
+ hash = 97 * hash + (this.format != null ? this.format.hashCode() : 0);
+ hash = 97 * hash + this.width;
+ hash = 97 * hash + this.height;
+ hash = 97 * hash + this.depth;
+ hash = 97 * hash + Arrays.hashCode(this.mipMapSizes);
+ hash = 97 * hash + (this.data != null ? this.data.hashCode() : 0);
+ hash = 97 * hash + this.multiSamples;
+ return hash;
+ }
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(format, "format", Format.RGBA8);
+ capsule.write(width, "width", 0);
+ capsule.write(height, "height", 0);
+ capsule.write(depth, "depth", 0);
+ capsule.write(mipMapSizes, "mipMapSizes", null);
+ capsule.write(multiSamples, "multiSamples", 1);
+ capsule.writeByteBufferArrayList(data, "data", null);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ format = capsule.readEnum("format", Format.class, Format.RGBA8);
+ width = capsule.readInt("width", 0);
+ height = capsule.readInt("height", 0);
+ depth = capsule.readInt("depth", 0);
+ mipMapSizes = capsule.readIntArray("mipMapSizes", null);
+ multiSamples = capsule.readInt("multiSamples", 1);
+ data = (ArrayList<ByteBuffer>) capsule.readByteBufferArrayList("data", null);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/texture/Texture.java b/engine/src/core/com/jme3/texture/Texture.java
new file mode 100644
index 0000000..1efedec
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.texture;
+
+import com.jme3.asset.Asset;
+import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.export.*;
+import com.jme3.util.PlaceholderAssets;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <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.jme3.texture.Image
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
+ */
+public abstract class Texture implements Asset, Savable, Cloneable {
+
+ public enum Type {
+
+ /**
+ * Two dimensional texture (default). A rectangle.
+ */
+ TwoDimensional,
+
+ /**
+ * An array of two dimensional textures.
+ */
+ TwoDimensionalArray,
+
+ /**
+ * Three dimensional texture. (A cube)
+ */
+ ThreeDimensional,
+
+ /**
+ * A set of 6 TwoDimensional textures arranged as faces of a cube facing
+ * inwards.
+ */
+ CubeMap;
+ }
+
+ public enum MinFilter {
+
+ /**
+ * 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)
+ */
+ NearestNoMipMaps(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)
+ */
+ NearestNearestMipMap(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)
+ */
+ NearestLinearMipMap(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 MinFilter(boolean usesMipMapLevels) {
+ this.usesMipMapLevels = usesMipMapLevels;
+ }
+
+ public boolean usesMipMapLevels() {
+ return usesMipMapLevels;
+ }
+ }
+
+ public enum MagFilter {
+
+ /**
+ * 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)
+ */
+ Nearest,
+
+ /**
+ * 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;
+ }
+
+ /**
+ * If this texture is a depth texture (the format is Depth*) then
+ * this value may be used to compare the texture depth to the R texture
+ * coordinate.
+ */
+ public enum ShadowCompareMode {
+ /**
+ * Shadow comparison mode is disabled.
+ * Texturing is done normally.
+ */
+ Off,
+
+ /**
+ * Compares the 3rd texture coordinate R to the value
+ * in this depth texture. If R <= texture value then result is 1.0,
+ * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
+ * the implementation may sample the texture multiple times to provide
+ * smoother results in the range [0, 1].
+ */
+ LessOrEqual,
+
+ /**
+ * Compares the 3rd texture coordinate R to the value
+ * in this depth texture. If R >= texture value then result is 1.0,
+ * otherwise, result is 0.0. If filtering is set to bilinear or trilinear
+ * the implementation may sample the texture multiple times to provide
+ * smoother results in the range [0, 1].
+ */
+ GreaterOrEqual
+ }
+
+ /**
+ * The name of the texture (if loaded as a resource).
+ */
+ private String name = null;
+
+ /**
+ * The image stored in the texture
+ */
+ private Image image = null;
+
+ /**
+ * The texture key allows to reload a texture from a file
+ * if needed.
+ */
+ private TextureKey key = null;
+
+ private MinFilter minificationFilter = MinFilter.BilinearNoMipMaps;
+ private MagFilter magnificationFilter = MagFilter.Bilinear;
+ private ShadowCompareMode shadowCompareMode = ShadowCompareMode.Off;
+ private int anisotropicFilter;
+
+ /**
+ * @return
+ */
+ @Override
+ public Texture clone(){
+ try {
+ return (Texture) super.clone();
+ } catch (CloneNotSupportedException ex) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Constructor instantiates a new <code>Texture</code> object with default
+ * attributes.
+ */
+ public Texture() {
+ }
+
+ /**
+ * @return the MinificationFilterMode of this texture.
+ */
+ public MinFilter getMinFilter() {
+ return minificationFilter;
+ }
+
+ /**
+ * @param minificationFilter
+ * the new MinificationFilterMode for this texture.
+ * @throws IllegalArgumentException
+ * if minificationFilter is null
+ */
+ public void setMinFilter(MinFilter minificationFilter) {
+ if (minificationFilter == null) {
+ throw new IllegalArgumentException(
+ "minificationFilter can not be null.");
+ }
+ this.minificationFilter = minificationFilter;
+ }
+
+ /**
+ * @return the MagnificationFilterMode of this texture.
+ */
+ public MagFilter getMagFilter() {
+ return magnificationFilter;
+ }
+
+ /**
+ * @param magnificationFilter
+ * the new MagnificationFilter for this texture.
+ * @throws IllegalArgumentException
+ * if magnificationFilter is null
+ */
+ public void setMagFilter(MagFilter magnificationFilter) {
+ if (magnificationFilter == null) {
+ throw new IllegalArgumentException(
+ "magnificationFilter can not be null.");
+ }
+ this.magnificationFilter = magnificationFilter;
+ }
+
+ /**
+ * @return The ShadowCompareMode of this texture.
+ * @see ShadowCompareMode
+ */
+ public ShadowCompareMode getShadowCompareMode(){
+ return shadowCompareMode;
+ }
+
+ /**
+ * @param compareMode
+ * the new ShadowCompareMode for this texture.
+ * @throws IllegalArgumentException
+ * if compareMode is null
+ * @see ShadowCompareMode
+ */
+ public void setShadowCompareMode(ShadowCompareMode compareMode){
+ if (compareMode == null){
+ throw new IllegalArgumentException(
+ "compareMode can not be null.");
+ }
+ this.shadowCompareMode = compareMode;
+ }
+
+ /**
+ * <code>setImage</code> sets the image object that defines the texture.
+ *
+ * @param image
+ * the image that defines the texture.
+ */
+ public void setImage(Image image) {
+ this.image = image;
+ }
+
+ /**
+ * @param key The texture key that was used to load this texture
+ */
+ public void setKey(AssetKey key){
+ this.key = (TextureKey) key;
+ }
+
+ public AssetKey getKey(){
+ return this.key;
+ }
+
+ /**
+ * <code>getImage</code> returns the image data that makes up this
+ * texture. If no image data has been set, this will return null.
+ *
+ * @return the image data that makes up the texture.
+ */
+ public Image getImage() {
+ return image;
+ }
+
+ /**
+ * <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 or invalid for this type of texture
+ */
+ public abstract void setWrap(WrapAxis axis, WrapMode mode);
+
+ /**
+ * <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 or invalid for this type of texture
+ */
+ public abstract void setWrap(WrapMode 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 or invalid for this type of texture
+ */
+ public abstract WrapMode getWrap(WrapAxis axis);
+
+ public abstract Type getType();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the anisotropic filtering level for this texture. Default value
+ * is 1 (no anisotrophy), 2 means x2, 4 is x4, etc.
+ */
+ public int getAnisotropicFilter() {
+ return anisotropicFilter;
+ }
+
+ /**
+ * @param level
+ * the anisotropic filtering level for this texture.
+ */
+ public void setAnisotropicFilter(int level) {
+ if (level < 1)
+ anisotropicFilter = 1;
+ else
+ anisotropicFilter = level;
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append(getClass().getSimpleName());
+ sb.append("[name=").append(name);
+ if (image != null)
+ sb.append(", image=").append(image.toString());
+
+ sb.append("]");
+
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Texture other = (Texture) obj;
+ if (this.image != other.image && (this.image == null || !this.image.equals(other.image))) {
+ return false;
+ }
+ if (this.minificationFilter != other.minificationFilter) {
+ return false;
+ }
+ if (this.magnificationFilter != other.magnificationFilter) {
+ return false;
+ }
+ if (this.shadowCompareMode != other.shadowCompareMode) {
+ return false;
+ }
+ if (this.anisotropicFilter != other.anisotropicFilter) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 5;
+ hash = 67 * hash + (this.image != null ? this.image.hashCode() : 0);
+ hash = 67 * hash + (this.minificationFilter != null ? this.minificationFilter.hashCode() : 0);
+ hash = 67 * hash + (this.magnificationFilter != null ? this.magnificationFilter.hashCode() : 0);
+ hash = 67 * hash + (this.shadowCompareMode != null ? this.shadowCompareMode.hashCode() : 0);
+ hash = 67 * hash + this.anisotropicFilter;
+ return hash;
+ }
+
+
+
+// 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(Texture rVal) {
+ rVal.setMinFilter(minificationFilter);
+ rVal.setMagFilter(magnificationFilter);
+ rVal.setShadowCompareMode(shadowCompareMode);
+// rVal.setHasBorder(hasBorder);
+ rVal.setAnisotropicFilter(anisotropicFilter);
+ rVal.setImage(image); // NOT CLONED.
+// rVal.memReq = memReq;
+ rVal.setKey(key);
+ rVal.setName(name);
+// rVal.setBlendColor(blendColor != null ? blendColor.clone() : null);
+// if (getTextureKey() != null) {
+// rVal.setTextureKey(getTextureKey());
+// }
+ return rVal;
+ }
+
+ public abstract Texture createSimpleClone();
+
+ public void write(JmeExporter e) throws IOException {
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(name, "name", null);
+
+ if (key == null){
+ // no texture key is set, try to save image instead then
+ capsule.write(image, "image", null);
+ }else{
+ capsule.write(key, "key", null);
+ }
+
+ capsule.write(anisotropicFilter, "anisotropicFilter", 1);
+ capsule.write(minificationFilter, "minificationFilter",
+ MinFilter.BilinearNoMipMaps);
+ capsule.write(magnificationFilter, "magnificationFilter",
+ MagFilter.Bilinear);
+ }
+
+ public void read(JmeImporter e) throws IOException {
+ InputCapsule capsule = e.getCapsule(this);
+ name = capsule.readString("name", null);
+ key = (TextureKey) capsule.readSavable("key", null);
+
+ // load texture from key, if available
+ if (key != null) {
+ // key is available, so try the texture from there.
+ try {
+ Texture loadedTex = e.getAssetManager().loadTexture(key);
+ image = loadedTex.getImage();
+ } catch (AssetNotFoundException ex){
+ Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot locate texture {0}", key);
+ image = PlaceholderAssets.getPlaceholderImage();
+ }
+ }else{
+ // no key is set on the texture. Attempt to load an embedded image
+ image = (Image) capsule.readSavable("image", null);
+ if (image == null){
+ // TODO: what to print out here? the texture has no key or data, there's no useful information ..
+ // assume texture.name is set even though the key is null
+ Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot load embedded image {0}", toString() );
+ }
+ }
+
+ anisotropicFilter = capsule.readInt("anisotropicFilter", 1);
+ minificationFilter = capsule.readEnum("minificationFilter",
+ MinFilter.class,
+ MinFilter.BilinearNoMipMaps);
+ magnificationFilter = capsule.readEnum("magnificationFilter",
+ MagFilter.class, MagFilter.Bilinear);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/Texture2D.java b/engine/src/core/com/jme3/texture/Texture2D.java
new file mode 100644
index 0000000..9598413
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture2D.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * @author Joshua Slack
+ */
+public class Texture2D extends Texture {
+
+ private WrapMode wrapS = WrapMode.EdgeClamp;
+ private WrapMode wrapT = WrapMode.EdgeClamp;
+
+ /**
+ * Creates a new two-dimensional texture with default attributes.
+ */
+ public Texture2D(){
+ super();
+ }
+
+ /**
+ * Creates a new two-dimensional texture using the given image.
+ * @param img The image to use.
+ */
+ public Texture2D(Image img){
+ super();
+ setImage(img);
+ if (img.getFormat().isDepthFormat()){
+ setMagFilter(MagFilter.Nearest);
+ setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+
+ /**
+ * Creates a new two-dimensional texture for the purpose of offscreen
+ * rendering.
+ *
+ * @see com.jme3.texture.FrameBuffer
+ *
+ * @param width
+ * @param height
+ * @param format
+ */
+ public Texture2D(int width, int height, Image.Format format){
+ this(new Image(format, width, height, null));
+ }
+
+ /**
+ * Creates a new two-dimensional texture for the purpose of offscreen
+ * rendering.
+ *
+ * @see com.jme3.texture.FrameBuffer
+ *
+ * @param width
+ * @param height
+ * @param format
+ * @param numSamples
+ */
+ public Texture2D(int width, int height, int numSamples, Image.Format format){
+ this(new Image(format, width, height, null));
+ getImage().setMultiSamples(numSamples);
+ }
+
+ @Override
+ public Texture createSimpleClone() {
+ Texture2D clone = new Texture2D();
+ createSimpleClone(clone);
+ return clone;
+ }
+
+ @Override
+ public Texture createSimpleClone(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
+ */
+ public void setWrap(WrapAxis axis, 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:
+ this.wrapS = mode;
+ break;
+ case T:
+ this.wrapT = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Not applicable for 2D textures");
+ }
+ }
+
+ /**
+ * <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
+ */
+ public void setWrap(WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ this.wrapS = mode;
+ this.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
+ */
+ public WrapMode getWrap(WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return wrapS;
+ case T:
+ return wrapT;
+ default:
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TwoDimensional;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Texture2D)) {
+ return false;
+ }
+ Texture2D that = (Texture2D) other;
+ if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S))
+ return false;
+ if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T))
+ return false;
+ return super.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 79 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0);
+ hash = 79 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/texture/Texture3D.java b/engine/src/core/com/jme3/texture/Texture3D.java
new file mode 100644
index 0000000..bded644
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/Texture3D.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * @author Maarten Steur
+ */
+public class Texture3D extends Texture {
+
+ private WrapMode wrapS = WrapMode.EdgeClamp;
+ private WrapMode wrapT = WrapMode.EdgeClamp;
+ private WrapMode wrapR = WrapMode.EdgeClamp;
+
+ /**
+ * Creates a new two-dimensional texture with default attributes.
+ */
+ public Texture3D() {
+ super();
+ }
+
+ /**
+ * Creates a new three-dimensional texture using the given image.
+ * @param img The image to use.
+ */
+ public Texture3D(Image img) {
+ super();
+ setImage(img);
+ if (img.getFormat().isDepthFormat()) {
+ setMagFilter(MagFilter.Nearest);
+ setMinFilter(MinFilter.NearestNoMipMaps);
+ }
+ }
+
+ /**
+ * Creates a new three-dimensional texture for the purpose of offscreen
+ * rendering.
+ *
+ * @see com.jme3.texture.FrameBuffer
+ *
+ * @param width
+ * @param height
+ * @param depth
+ * @param format
+ */
+ public Texture3D(int width, int height, int depth, Image.Format format) {
+ this(new Image(format, width, height, depth, null));
+ }
+
+ /**
+ * Creates a new three-dimensional texture for the purpose of offscreen
+ * rendering.
+ *
+ * @see com.jme3.texture.FrameBuffer
+ *
+ * @param width
+ * @param height
+ * @param format
+ * @param numSamples
+ */
+ public Texture3D(int width, int height, int depth, int numSamples, Image.Format format) {
+ this(new Image(format, width, height, depth, null));
+ getImage().setMultiSamples(numSamples);
+ }
+
+ @Override
+ public Texture createSimpleClone() {
+ Texture3D clone = new Texture3D();
+ createSimpleClone(clone);
+ return clone;
+ }
+
+ @Override
+ public Texture createSimpleClone(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
+ */
+ public void setWrap(WrapAxis axis, 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:
+ this.wrapS = mode;
+ break;
+ case T:
+ this.wrapT = mode;
+ break;
+ case R:
+ this.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
+ */
+ public void setWrap(WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ this.wrapS = mode;
+ this.wrapT = mode;
+ this.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
+ */
+ public WrapMode getWrap(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(Object other) {
+ if (!(other instanceof Texture3D)) {
+ return false;
+ }
+ Texture3D that = (Texture3D) other;
+ if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) {
+ return false;
+ }
+ if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) {
+ return false;
+ }
+ if (this.getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R)) {
+ return false;
+ }
+ return super.equals(other);
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+ capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp);
+ wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp);
+ wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp);
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/TextureArray.java b/engine/src/core/com/jme3/texture/TextureArray.java
new file mode 100644
index 0000000..e39d95f
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/TextureArray.java
@@ -0,0 +1,113 @@
+package com.jme3.texture;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * This class implements a Texture array
+ * warning, this feature is only supported on opengl 3.0 version.
+ * To check if a hardware supports TextureArray check :
+ * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+ * @author phate666
+ */
+public class TextureArray extends Texture {
+
+ private WrapMode wrapS = WrapMode.EdgeClamp;
+ private WrapMode wrapT = WrapMode.EdgeClamp;
+
+ /**
+ * Construct a TextureArray
+ * warning, this feature is only supported on opengl 3.0 version.
+ * To check if a hardware supports TextureArray check :
+ * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+ */
+ public TextureArray() {
+ super();
+ }
+
+ /**
+ * Construct a TextureArray from the given list of images
+ * warning, this feature is only supported on opengl 3.0 version.
+ * To check if a hardware supports TextureArray check :
+ * renderManager.getRenderer().getCaps().contains(Caps.TextureArray)
+ * @param images
+ */
+ public TextureArray(List<Image> images) {
+ super();
+ int width = images.get(0).getWidth();
+ int height = images.get(0).getHeight();
+ Image arrayImage = new Image(images.get(0).getFormat(), width, height,
+ null);
+
+ for (Image img : images) {
+ if (img.getHeight() != height || img.getWidth() != width) {
+ Logger.getLogger(TextureArray.class.getName()).log(
+ Level.WARNING,
+ "all images must have the same width/height");
+ continue;
+ }
+ arrayImage.addData(img.getData(0));
+ }
+ setImage(arrayImage);
+ }
+
+ @Override
+ public Texture createSimpleClone() {
+ TextureArray clone = new TextureArray();
+ createSimpleClone(clone);
+ return clone;
+ }
+
+ @Override
+ public Texture createSimpleClone(Texture rVal) {
+ rVal.setWrap(WrapAxis.S, wrapS);
+ rVal.setWrap(WrapAxis.T, wrapT);
+ return super.createSimpleClone(rVal);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TwoDimensionalArray;
+ }
+
+ @Override
+ public WrapMode getWrap(WrapAxis axis) {
+ switch (axis) {
+ case S:
+ return wrapS;
+ case T:
+ return wrapT;
+ default:
+ throw new IllegalArgumentException("invalid WrapAxis: " + axis);
+ }
+ }
+
+ @Override
+ public void setWrap(WrapAxis axis, 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:
+ this.wrapS = mode;
+ break;
+ case T:
+ this.wrapT = mode;
+ break;
+ default:
+ throw new IllegalArgumentException("Not applicable for 2D textures");
+ }
+ }
+
+ @Override
+ public void setWrap(WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ this.wrapS = mode;
+ this.wrapT = mode;
+ }
+} \ No newline at end of file
diff --git a/engine/src/core/com/jme3/texture/TextureCubeMap.java b/engine/src/core/com/jme3/texture/TextureCubeMap.java
new file mode 100644
index 0000000..9290d8a
--- /dev/null
+++ b/engine/src/core/com/jme3/texture/TextureCubeMap.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.texture;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import java.io.IOException;
+
+/**
+ * Describes a cubemap texture.
+ * The image specified by setImage must contain 6 data units,
+ * each data contains a 2D image representing a cube's face.
+ * The slices are specified in this order:<br/>
+ * <br/>
+ * 0 => Positive X (+x)<br/>
+ * 1 => Negative X (-x)<br/>
+ * 2 => Positive Y (+y)<br/>
+ * 3 => Negative Y (-y)<br/>
+ * 4 => Positive Z (+z)<br/>
+ * 5 => Negative Z (-z)<br/>
+ *
+ * @author Joshua Slack
+ */
+public class TextureCubeMap extends Texture {
+
+ private WrapMode wrapS = WrapMode.EdgeClamp;
+ private WrapMode wrapT = WrapMode.EdgeClamp;
+ private WrapMode wrapR = WrapMode.EdgeClamp;
+
+ /**
+ * Face of the Cubemap as described by its directional offset from the
+ * origin.
+ */
+// public enum Face {
+// PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ;
+// }
+
+ public TextureCubeMap(){
+ super();
+ }
+
+ public TextureCubeMap(Image img){
+ super();
+ setImage(img);
+ }
+
+ public Texture createSimpleClone() {
+ return createSimpleClone(new TextureCubeMap());
+ }
+
+ @Override
+ public Texture createSimpleClone(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
+ */
+ public void setWrap(WrapAxis axis, 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:
+ this.wrapS = mode;
+ break;
+ case T:
+ this.wrapT = mode;
+ break;
+ case R:
+ this.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
+ */
+ public void setWrap(WrapMode mode) {
+ if (mode == null) {
+ throw new IllegalArgumentException("mode can not be null.");
+ }
+ this.wrapS = mode;
+ this.wrapT = mode;
+ this.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
+ */
+ public WrapMode getWrap(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.CubeMap;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof TextureCubeMap)) {
+ return false;
+ }
+ TextureCubeMap that = (TextureCubeMap) other;
+ if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S))
+ return false;
+ if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T))
+ return false;
+ if (this.getWrap(WrapAxis.R) != that.getWrap(WrapAxis.R))
+ return false;
+ return super.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = super.hashCode();
+ hash = 53 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0);
+ hash = 53 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0);
+ hash = 53 * hash + (this.wrapR != null ? this.wrapR.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp);
+ capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp);
+ capsule.write(wrapR, "wrapR", WrapMode.EdgeClamp);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ 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/engine/src/core/com/jme3/ui/Picture.java b/engine/src/core/com/jme3/ui/Picture.java
new file mode 100644
index 0000000..81371f5
--- /dev/null
+++ b/engine/src/core/com/jme3/ui/Picture.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.ui;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.material.Material;
+import com.jme3.material.RenderState.BlendMode;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.shape.Quad;
+import com.jme3.texture.Texture2D;
+
+/**
+ * A <code>Picture</code> represents a 2D image drawn on the screen.
+ * It can be used to represent sprites or other background elements.
+ *
+ * @author Kirill Vainer
+ */
+public class Picture extends Geometry {
+
+ private float width = 1f;
+ private float height = 1f;
+
+ /**
+ * Create a named picture.
+ *
+ * By default a picture's width and height are 1
+ * and its position is 0, 0.
+ *
+ * @param name the name of the picture in the scene graph
+ * @param flipY If true, the Y coordinates of the texture will be flipped.
+ */
+ public Picture(String name, boolean flipY){
+ super(name, new Quad(1, 1, flipY));
+ setQueueBucket(Bucket.Gui);
+ setCullHint(CullHint.Never);
+ }
+
+ /**
+ * Creates a named picture.
+ * By default a picture's width and height are 1
+ * and its position is 0, 0.
+ * The image texture coordinates will not be flipped.
+ *
+ * @param name the name of the picture in the scene graph
+ */
+ public Picture(String name){
+ this(name, false);
+ }
+
+ /*
+ * Serialization only. Do not use.
+ */
+ public Picture(){
+ }
+
+ /**
+ * Set the width in pixels of the picture, if the width
+ * does not match the texture's width, then the texture will
+ * be scaled to fit the picture.
+ *
+ * @param width the width to set.
+ */
+ public void setWidth(float width){
+ this.width = width;
+ setLocalScale(new Vector3f(width, height, 1f));
+ }
+
+ /**
+ * Set the height in pixels of the picture, if the height
+ * does not match the texture's height, then the texture will
+ * be scaled to fit the picture.
+ *
+ * @param height the height to set.
+ */
+ public void setHeight(float height){
+ this.height = height;
+ setLocalScale(new Vector3f(width, height, 1f));
+ }
+
+ /**
+ * Set the position of the picture in pixels.
+ * The origin (0, 0) is at the bottom-left of the screen.
+ *
+ * @param x The x coordinate
+ * @param y The y coordinate
+ */
+ public void setPosition(float x, float y){
+ float z = getLocalTranslation().getZ();
+ setLocalTranslation(x, y, z);
+ }
+
+ /**
+ * Set the image to put on the picture.
+ *
+ * @param assetManager The {@link AssetManager} to use to load the image.
+ * @param imgName The image name.
+ * @param useAlpha If true, the picture will appear transparent and allow
+ * objects behind it to appear through. If false, the transparent
+ * portions will be the image's color at that pixel.
+ */
+ public void setImage(AssetManager assetManager, String imgName, boolean useAlpha){
+ TextureKey key = new TextureKey(imgName, true);
+ Texture2D tex = (Texture2D) assetManager.loadTexture(key);
+ setTexture(assetManager, tex, useAlpha);
+ }
+
+ /**
+ * Set the texture to put on the picture.
+ *
+ * @param assetManager The {@link AssetManager} to use to load the material.
+ * @param tex The texture
+ * @param useAlpha If true, the picture will appear transparent and allow
+ * objects behind it to appear through. If false, the transparent
+ * portions will be the image's color at that pixel.
+ */
+ public void setTexture(AssetManager assetManager, Texture2D tex, boolean useAlpha){
+ if (getMaterial() == null){
+ Material mat = new Material(assetManager, "Common/MatDefs/Gui/Gui.j3md");
+ mat.setColor("Color", ColorRGBA.White);
+ setMaterial(mat);
+ }
+ material.getAdditionalRenderState().setBlendMode(useAlpha ? BlendMode.Alpha : BlendMode.Off);
+ material.setTexture("Texture", tex);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/util/BufferUtils.java b/engine/src/core/com/jme3/util/BufferUtils.java
new file mode 100644
index 0000000..f0cc698
--- /dev/null
+++ b/engine/src/core/com/jme3/util/BufferUtils.java
@@ -0,0 +1,1196 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <code>BufferUtils</code> is a helper class for generating nio buffers from
+ * jME data classes such as Vectors and ColorRGBA.
+ *
+ * @author Joshua Slack
+ * @version $Id: BufferUtils.java,v 1.16 2007/10/29 16:56:18 nca Exp $
+ */
+public final class BufferUtils {
+
+ private static final Map<Buffer, Object> trackingHash = Collections.synchronizedMap(new WeakHashMap<Buffer, Object>());
+ private static final Object ref = new Object();
+
+ // Note: a WeakHashMap is really bad here since the hashCode() and
+ // equals() behavior of buffers will vary based on their contents.
+ // As it stands, put()'ing an empty buffer will wipe out the last
+ // empty buffer with the same size. So any tracked memory calculations
+ // could be lying.
+ // Besides, the hashmap behavior isn't even being used here and
+ // yet the expense is still incurred. For example, a newly allocated
+ // 10,000 byte buffer will iterate through the whole buffer of 0's
+ // to calculate the hashCode and then potentially do it again to
+ // calculate the equals()... which by the way is guaranteed for
+ // every empty buffer of an existing size since they will always produce
+ // the same hashCode().
+ // It would be better to just keep a straight list of weak references
+ // and clean out the dead every time a new buffer is allocated.
+ // WeakHashMap is doing that anyway... so there is no extra expense
+ // incurred.
+ // Recommend a ConcurrentLinkedQueue of WeakReferences since it
+ // supports the threading semantics required with little extra overhead.
+ private static final boolean trackDirectMemory = false;
+
+ /**
+ * Creates a clone of the given buffer. The clone's capacity is
+ * equal to the given buffer's limit.
+ *
+ * @param buf The buffer to clone
+ * @return The cloned buffer
+ */
+ public static Buffer clone(Buffer buf) {
+ if (buf instanceof FloatBuffer) {
+ return clone((FloatBuffer) buf);
+ } else if (buf instanceof ShortBuffer) {
+ return clone((ShortBuffer) buf);
+ } else if (buf instanceof ByteBuffer) {
+ return clone((ByteBuffer) buf);
+ } else if (buf instanceof IntBuffer) {
+ return clone((IntBuffer) buf);
+ } else if (buf instanceof DoubleBuffer) {
+ return clone((DoubleBuffer) buf);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private static void onBufferAllocated(Buffer buffer){
+ /*
+ StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ int initialIndex = 0;
+
+ for (int i = 0; i < stackTrace.length; i++){
+ if (!stackTrace[i].getClassName().equals(BufferUtils.class.getName())){
+ initialIndex = i;
+ break;
+ }
+ }
+
+ int allocated = buffer.capacity();
+ int size = 0;
+
+ if (buffer instanceof FloatBuffer){
+ size = 4;
+ }else if (buffer instanceof ShortBuffer){
+ size = 2;
+ }else if (buffer instanceof ByteBuffer){
+ size = 1;
+ }else if (buffer instanceof IntBuffer){
+ size = 4;
+ }else if (buffer instanceof DoubleBuffer){
+ size = 8;
+ }
+
+ allocated *= size;
+
+ for (int i = initialIndex; i < stackTrace.length; i++){
+ StackTraceElement element = stackTrace[i];
+ if (element.getClassName().startsWith("java")){
+ break;
+ }
+
+ try {
+ Class clazz = Class.forName(element.getClassName());
+ if (i == initialIndex){
+ System.out.println(clazz.getSimpleName()+"."+element.getMethodName()+"():" + element.getLineNumber() + " allocated " + allocated);
+ }else{
+ System.out.println(" at " + clazz.getSimpleName()+"."+element.getMethodName()+"()");
+ }
+ } catch (ClassNotFoundException ex) {
+ }
+ }*/
+
+ if (trackDirectMemory){
+ trackingHash.put(buffer, ref);
+ }
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of Vector3f 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 Vector3f objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(Vector3f... data) {
+ if (data == null) {
+ return null;
+ }
+ FloatBuffer buff = createFloatBuffer(3 * data.length);
+ for (int x = 0; x < data.length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].x).put(data[x].y).put(data[x].z);
+ } else {
+ buff.put(0).put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Generate a new FloatBuffer using the given array of Quaternion objects.
+ * The FloatBuffer will be 4 * data.length long and contain the vector data.
+ *
+ * @param data array of Quaternion objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(Quaternion... data) {
+ if (data == null) {
+ return null;
+ }
+ FloatBuffer buff = createFloatBuffer(4 * data.length);
+ for (int x = 0; x < data.length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].getX()).put(data[x].getY()).put(data[x].getZ()).put(data[x].getW());
+ } else {
+ buff.put(0).put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * 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(float... data) {
+ if (data == null) {
+ return null;
+ }
+ FloatBuffer buff = createFloatBuffer(data.length);
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified
+ * number of Vector3f 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(int vertices) {
+ FloatBuffer vBuff = createFloatBuffer(3 * vertices);
+ return vBuff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified
+ * number of Vector3f 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 createVector3Buffer(FloatBuffer buf, int vertices) {
+ if (buf != null && buf.limit() == 3 * vertices) {
+ buf.rewind();
+ return buf;
+ }
+
+ return createFloatBuffer(3 * vertices);
+ }
+
+ /**
+ * 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 postion to place the data; in terms of colors not floats
+ */
+ public static void setInBuffer(ColorRGBA color, FloatBuffer buf,
+ int index) {
+ buf.position(index * 4);
+ buf.put(color.r);
+ buf.put(color.g);
+ buf.put(color.b);
+ buf.put(color.a);
+ }
+
+ /**
+ * Sets the data contained in the given quaternion into the FloatBuffer at the
+ * specified index.
+ *
+ * @param quat
+ * the {@link Quaternion} to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the postion to place the data; in terms of quaternions not floats
+ */
+ public static void setInBuffer(Quaternion quat, FloatBuffer buf,
+ int index) {
+ buf.position(index * 4);
+ buf.put(quat.getX());
+ buf.put(quat.getY());
+ buf.put(quat.getZ());
+ buf.put(quat.getW());
+ }
+
+ /**
+ * Sets the data contained in the given Vector3F into the FloatBuffer at the
+ * specified index.
+ *
+ * @param vector
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the postion to place the data; in terms of vectors not floats
+ */
+ public static void setInBuffer(Vector3f vector, FloatBuffer buf, 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.x);
+ buf.put((index * 3) + 1, vector.y);
+ buf.put((index * 3) + 2, vector.z);
+ }
+ }
+
+ /**
+ * 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(Vector3f vector, FloatBuffer buf, int index) {
+ vector.x = buf.get(index * 3);
+ vector.y = buf.get(index * 3 + 1);
+ vector.z = buf.get(index * 3 + 2);
+ }
+
+ /**
+ * Generates a Vector3f array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of Vector3f objects
+ */
+ public static Vector3f[] getVector3Array(FloatBuffer buff) {
+ buff.clear();
+ Vector3f[] verts = new Vector3f[buff.limit() / 3];
+ for (int x = 0; x < verts.length; x++) {
+ Vector3f v = new Vector3f(buff.get(), buff.get(), buff.get());
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Copies a Vector3f from one position in the buffer to another. The index
+ * values are in terms of vector number (eg, vector number 0 is postions 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(FloatBuffer buf, int fromPos, int toPos) {
+ copyInternal(buf, fromPos * 3, toPos * 3, 3);
+ }
+
+ /**
+ * Normalize a Vector3f in-buffer.
+ *
+ * @param buf
+ * the buffer to find the Vector3f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to normalize
+ */
+ public static void normalizeVector3(FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector3f tempVec3 = vars.vect1;
+ populateFromBuffer(tempVec3, buf, index);
+ tempVec3.normalizeLocal();
+ setInBuffer(tempVec3, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Add to a Vector3f in-buffer.
+ *
+ * @param toAdd
+ * the vector to add from
+ * @param buf
+ * the buffer to find the Vector3f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to add to
+ */
+ public static void addInBuffer(Vector3f toAdd, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector3f tempVec3 = vars.vect1;
+ populateFromBuffer(tempVec3, buf, index);
+ tempVec3.addLocal(toAdd);
+ setInBuffer(tempVec3, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Multiply and store a Vector3f in-buffer.
+ *
+ * @param toMult
+ * the vector to multiply against
+ * @param buf
+ * the buffer to find the Vector3f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to multiply
+ */
+ public static void multInBuffer(Vector3f toMult, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector3f tempVec3 = vars.vect1;
+ populateFromBuffer(tempVec3, buf, index);
+ tempVec3.multLocal(toMult);
+ setInBuffer(tempVec3, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Checks to see if the given Vector3f 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(Vector3f check, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector3f tempVec3 = vars.vect1;
+ populateFromBuffer(tempVec3, buf, index);
+ boolean eq = tempVec3.equals(check);
+ vars.release();
+ return eq;
+ }
+
+ // // -- VECTOR2F METHODS -- ////
+ /**
+ * Generate a new FloatBuffer using the given array of Vector2f 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 Vector2f objects to place into a new FloatBuffer
+ */
+ public static FloatBuffer createFloatBuffer(Vector2f... data) {
+ if (data == null) {
+ return null;
+ }
+ FloatBuffer buff = createFloatBuffer(2 * data.length);
+ for (int x = 0; x < data.length; x++) {
+ if (data[x] != null) {
+ buff.put(data[x].x).put(data[x].y);
+ } else {
+ buff.put(0).put(0);
+ }
+ }
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified
+ * number of Vector2f 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(int vertices) {
+ FloatBuffer vBuff = createFloatBuffer(2 * vertices);
+ return vBuff;
+ }
+
+ /**
+ * Create a new FloatBuffer of an appropriate size to hold the specified
+ * number of Vector2f 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(FloatBuffer buf, int vertices) {
+ if (buf != null && buf.limit() == 2 * vertices) {
+ buf.rewind();
+ return buf;
+ }
+
+ return createFloatBuffer(2 * vertices);
+ }
+
+ /**
+ * Sets the data contained in the given Vector2F into the FloatBuffer at the
+ * specified index.
+ *
+ * @param vector
+ * the data to insert
+ * @param buf
+ * the buffer to insert into
+ * @param index
+ * the postion to place the data; in terms of vectors not floats
+ */
+ public static void setInBuffer(Vector2f vector, FloatBuffer buf, int index) {
+ buf.put(index * 2, vector.x);
+ buf.put((index * 2) + 1, vector.y);
+ }
+
+ /**
+ * 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(Vector2f vector, FloatBuffer buf, int index) {
+ vector.x = buf.get(index * 2);
+ vector.y = buf.get(index * 2 + 1);
+ }
+
+ /**
+ * Generates a Vector2f array from the given FloatBuffer.
+ *
+ * @param buff
+ * the FloatBuffer to read from
+ * @return a newly generated array of Vector2f objects
+ */
+ public static Vector2f[] getVector2Array(FloatBuffer buff) {
+ buff.clear();
+ Vector2f[] verts = new Vector2f[buff.limit() / 2];
+ for (int x = 0; x < verts.length; x++) {
+ Vector2f v = new Vector2f(buff.get(), buff.get());
+ verts[x] = v;
+ }
+ return verts;
+ }
+
+ /**
+ * Copies a Vector2f from one position in the buffer to another. The index
+ * values are in terms of vector number (eg, vector number 0 is postions 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(FloatBuffer buf, int fromPos, int toPos) {
+ copyInternal(buf, fromPos * 2, toPos * 2, 2);
+ }
+
+ /**
+ * Normalize a Vector2f in-buffer.
+ *
+ * @param buf
+ * the buffer to find the Vector2f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to normalize
+ */
+ public static void normalizeVector2(FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector2f tempVec2 = vars.vect2d;
+ populateFromBuffer(tempVec2, buf, index);
+ tempVec2.normalizeLocal();
+ setInBuffer(tempVec2, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Add to a Vector2f in-buffer.
+ *
+ * @param toAdd
+ * the vector to add from
+ * @param buf
+ * the buffer to find the Vector2f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to add to
+ */
+ public static void addInBuffer(Vector2f toAdd, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector2f tempVec2 = vars.vect2d;
+ populateFromBuffer(tempVec2, buf, index);
+ tempVec2.addLocal(toAdd);
+ setInBuffer(tempVec2, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Multiply and store a Vector2f in-buffer.
+ *
+ * @param toMult
+ * the vector to multiply against
+ * @param buf
+ * the buffer to find the Vector2f within
+ * @param index
+ * the position (in terms of vectors, not floats) of the vector
+ * to multiply
+ */
+ public static void multInBuffer(Vector2f toMult, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector2f tempVec2 = vars.vect2d;
+ populateFromBuffer(tempVec2, buf, index);
+ tempVec2.multLocal(toMult);
+ setInBuffer(tempVec2, buf, index);
+ vars.release();
+ }
+
+ /**
+ * Checks to see if the given Vector2f 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(Vector2f check, FloatBuffer buf, int index) {
+ TempVars vars = TempVars.get();
+ Vector2f tempVec2 = vars.vect2d;
+ populateFromBuffer(tempVec2, buf, index);
+ boolean eq = tempVec2.equals(check);
+ vars.release();
+ return eq;
+ }
+
+ //// -- 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(int... data) {
+ if (data == null) {
+ return null;
+ }
+ 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(IntBuffer buff) {
+ if (buff == null) {
+ return null;
+ }
+ buff.clear();
+ int[] inds = new int[buff.limit()];
+ 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(FloatBuffer buff) {
+ if (buff == null) {
+ return null;
+ }
+ buff.clear();
+ 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 createDoubleBuffer(int size) {
+ DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
+ buf.clear();
+ onBufferAllocated(buf);
+ 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, 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(DoubleBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ DoubleBuffer copy;
+ if (buf.isDirect()) {
+ copy = createDoubleBuffer(buf.limit());
+ } else {
+ copy = DoubleBuffer.allocate(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(int size) {
+ FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ buf.clear();
+ onBufferAllocated(buf);
+ return buf;
+ }
+
+ /**
+ * 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(FloatBuffer buf, int fromPos, int toPos, int length) {
+ 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(FloatBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ FloatBuffer copy;
+ if (buf.isDirect()) {
+ copy = createFloatBuffer(buf.limit());
+ } else {
+ copy = FloatBuffer.allocate(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 createIntBuffer(int size) {
+ IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
+ buf.clear();
+ onBufferAllocated(buf);
+ 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, 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(IntBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ IntBuffer copy;
+ if (buf.isDirect()) {
+ copy = createIntBuffer(buf.limit());
+ } else {
+ copy = IntBuffer.allocate(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(int size) {
+ ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ buf.clear();
+ onBufferAllocated(buf);
+ 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, int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createByteBuffer(size);
+ return buf;
+ }
+
+ public static ByteBuffer createByteBuffer(byte... data) {
+ ByteBuffer bb = createByteBuffer(data.length);
+ bb.put(data);
+ bb.flip();
+ return bb;
+ }
+
+ public static ByteBuffer createByteBuffer(String data) {
+ byte[] bytes = data.getBytes();
+ ByteBuffer bb = createByteBuffer(bytes.length);
+ bb.put(bytes);
+ bb.flip();
+ return bb;
+ }
+
+ /**
+ * 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(ByteBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ ByteBuffer copy;
+ if (buf.isDirect()) {
+ copy = createByteBuffer(buf.limit());
+ } else {
+ copy = ByteBuffer.allocate(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 createShortBuffer(int size) {
+ ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
+ buf.clear();
+ onBufferAllocated(buf);
+ return buf;
+ }
+
+ /**
+ * 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, int size) {
+ if (buf != null && buf.limit() == size) {
+ buf.rewind();
+ return buf;
+ }
+
+ buf = createShortBuffer(size);
+ return buf;
+ }
+
+ public static ShortBuffer createShortBuffer(short... data) {
+ if (data == null) {
+ return null;
+ }
+ ShortBuffer buff = createShortBuffer(data.length);
+ buff.clear();
+ buff.put(data);
+ buff.flip();
+ return buff;
+ }
+
+ /**
+ * 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(ShortBuffer buf) {
+ if (buf == null) {
+ return null;
+ }
+ buf.rewind();
+
+ ShortBuffer copy;
+ if (buf.isDirect()) {
+ copy = createShortBuffer(buf.limit());
+ } else {
+ copy = ShortBuffer.allocate(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, int required) {
+ if (buffer == null || (buffer.remaining() < required)) {
+ int position = (buffer != null ? buffer.position() : 0);
+ FloatBuffer newVerts = createFloatBuffer(position + required);
+ if (buffer != null) {
+ buffer.rewind();
+ newVerts.put(buffer);
+ newVerts.position(position);
+ }
+ buffer = newVerts;
+ }
+ return buffer;
+ }
+
+ public static ShortBuffer ensureLargeEnough(ShortBuffer buffer, int required) {
+ if (buffer == null || (buffer.remaining() < required)) {
+ int position = (buffer != null ? buffer.position() : 0);
+ ShortBuffer newVerts = createShortBuffer(position + required);
+ if (buffer != null) {
+ buffer.rewind();
+ newVerts.put(buffer);
+ newVerts.position(position);
+ }
+ buffer = newVerts;
+ }
+ return buffer;
+ }
+
+ public static ByteBuffer ensureLargeEnough(ByteBuffer buffer, int required) {
+ if (buffer == null || (buffer.remaining() < required)) {
+ int position = (buffer != null ? buffer.position() : 0);
+ ByteBuffer newVerts = createByteBuffer(position + required);
+ if (buffer != null) {
+ buffer.rewind();
+ newVerts.put(buffer);
+ newVerts.position(position);
+ }
+ buffer = newVerts;
+ }
+ return buffer;
+ }
+
+ public static void printCurrentDirectMemory(StringBuilder store) {
+ long totalHeld = 0;
+ // make a new set to hold the keys to prevent concurrency issues.
+ ArrayList<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 (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++;
+ }
+ }
+ long heapMem = Runtime.getRuntime().totalMemory()
+ - Runtime.getRuntime().freeMemory();
+
+ 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 heap memory held: ").append(heapMem / 1024).append("kb\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());
+ }
+ }
+
+ /**
+ * Direct buffers are garbage collected by using a phantom reference and a
+ * reference queue. Every once a while, the JVM checks the reference queue and
+ * cleans the direct buffers. However, as this doesn't happen
+ * immediately after discarding all references to a direct buffer, it's
+ * easy to OutOfMemoryError yourself using direct buffers. This function
+ * explicitly calls the Cleaner method of a direct buffer.
+ *
+ * @param toBeDestroyed
+ * The direct buffer that will be "cleaned". Utilizes reflection.
+ *
+ */
+ public static void destroyDirectBuffer(Buffer toBeDestroyed) {
+
+ if (!toBeDestroyed.isDirect()) {
+ return;
+ }
+ try {
+ Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
+ cleanerMethod.setAccessible(true);
+ Object cleaner = cleanerMethod.invoke(toBeDestroyed);
+ if (cleaner != null) {
+ Method cleanMethod = cleaner.getClass().getMethod("clean");
+ cleanMethod.setAccessible(true);
+ cleanMethod.invoke(cleaner);
+ } else {
+ // Try the alternate approach of getting the viewed buffer
+ Method viewedBufferMethod = toBeDestroyed.getClass().getMethod("viewedBuffer");
+ viewedBufferMethod.setAccessible(true);
+ Object viewedBuffer = viewedBufferMethod.invoke(toBeDestroyed);
+ if (viewedBuffer != null) {
+ destroyDirectBuffer( (Buffer)viewedBuffer );
+ } else {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed);
+ }
+ }
+ } catch (IllegalAccessException ex) {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+ } catch (IllegalArgumentException ex) {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+ } catch (InvocationTargetException ex) {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+ } catch (NoSuchMethodException ex) {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+ } catch (SecurityException ex) {
+ Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex);
+ }
+ }
+
+}
diff --git a/engine/src/core/com/jme3/util/IntMap.java b/engine/src/core/com/jme3/util/IntMap.java
new file mode 100644
index 0000000..edf659b
--- /dev/null
+++ b/engine/src/core/com/jme3/util/IntMap.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+import com.jme3.util.IntMap.Entry;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Similar to a {@link Map} except that ints are used as keys.
+ *
+ * Taken from <a href="http://code.google.com/p/skorpios/">http://code.google.com/p/skorpios/</a>
+ *
+ * @author Nate
+ */
+public final class IntMap<T> implements Iterable<Entry<T>>, Cloneable {
+
+ private final IntMapIterator iterator = new IntMapIterator();
+
+ private Entry[] table;
+ private final float loadFactor;
+ private int size, mask, capacity, threshold;
+
+ public IntMap() {
+ this(16, 0.75f);
+ }
+
+ public IntMap(int initialCapacity) {
+ this(initialCapacity, 0.75f);
+ }
+
+ public IntMap(int initialCapacity, float loadFactor) {
+ if (initialCapacity > 1 << 30){
+ throw new IllegalArgumentException("initialCapacity is too large.");
+ }
+ if (initialCapacity < 0){
+ throw new IllegalArgumentException("initialCapacity must be greater than zero.");
+ }
+ if (loadFactor <= 0){
+ throw new IllegalArgumentException("initialCapacity must be greater than zero.");
+ }
+ capacity = 1;
+ while (capacity < initialCapacity){
+ capacity <<= 1;
+ }
+ this.loadFactor = loadFactor;
+ this.threshold = (int) (capacity * loadFactor);
+ this.table = new Entry[capacity];
+ this.mask = capacity - 1;
+ }
+
+ @Override
+ public IntMap<T> clone(){
+ try{
+ IntMap<T> clone = (IntMap<T>) super.clone();
+ Entry[] newTable = new Entry[table.length];
+ for (int i = table.length - 1; i >= 0; i--){
+ if (table[i] != null)
+ newTable[i] = table[i].clone();
+ }
+ clone.table = newTable;
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ }
+ return null;
+ }
+
+ public boolean containsValue(Object value) {
+ Entry[] table = this.table;
+ for (int i = table.length; i-- > 0;){
+ for (Entry e = table[i]; e != null; e = e.next){
+ if (e.value.equals(value)){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean containsKey(int key) {
+ int index = ((int) key) & mask;
+ for (Entry e = table[index]; e != null; e = e.next){
+ if (e.key == key){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public T get(int key) {
+ int index = key & mask;
+ for (Entry e = table[index]; e != null; e = e.next){
+ if (e.key == key){
+ return (T) e.value;
+ }
+ }
+ return null;
+ }
+
+ public T put(int key, T value) {
+ int index = key & mask;
+ // Check if key already exists.
+ for (Entry e = table[index]; e != null; e = e.next){
+ if (e.key != key){
+ continue;
+ }
+ Object oldValue = e.value;
+ e.value = value;
+ return (T) oldValue;
+ }
+ table[index] = new Entry(key, value, table[index]);
+ if (size++ >= threshold){
+ // Rehash.
+ int newCapacity = 2 * capacity;
+ Entry[] newTable = new Entry[newCapacity];
+ Entry[] src = table;
+ int bucketmask = newCapacity - 1;
+ for (int j = 0; j < src.length; j++){
+ Entry e = src[j];
+ if (e != null){
+ src[j] = null;
+ do{
+ Entry next = e.next;
+ index = e.key & bucketmask;
+ e.next = newTable[index];
+ newTable[index] = e;
+ e = next;
+ }while (e != null);
+ }
+ }
+ table = newTable;
+ capacity = newCapacity;
+ threshold = (int) (newCapacity * loadFactor);
+ mask = capacity - 1;
+ }
+ return null;
+ }
+
+ public T remove(int key) {
+ int index = key & mask;
+ Entry prev = table[index];
+ Entry e = prev;
+ while (e != null){
+ Entry next = e.next;
+ if (e.key == key){
+ size--;
+ if (prev == e){
+ table[index] = next;
+ }else{
+ prev.next = next;
+ }
+ return (T) e.value;
+ }
+ prev = e;
+ e = next;
+ }
+ return null;
+ }
+
+ public int size() {
+ return size;
+ }
+
+ public void clear() {
+ Entry[] table = this.table;
+ for (int index = table.length; --index >= 0;){
+ table[index] = null;
+ }
+ size = 0;
+ }
+
+ public Iterator<Entry<T>> iterator() {
+ iterator.beginUse();
+ return iterator;
+ }
+
+ final class IntMapIterator implements Iterator<Entry<T>> {
+
+ /**
+ * Current entry.
+ */
+ private Entry cur;
+
+ /**
+ * Entry in the table
+ */
+ private int idx = 0;
+
+ /**
+ * Element in the entry
+ */
+ private int el = 0;
+
+ public IntMapIterator() {
+ }
+
+ public void beginUse(){
+ cur = table[0];
+ idx = 0;
+ el = 0;
+ }
+
+ public boolean hasNext() {
+ return el < size;
+ }
+
+ public Entry next() {
+ if (el >= size)
+ throw new IllegalStateException("No more elements!");
+
+ if (cur != null){
+ Entry e = cur;
+ cur = cur.next;
+ el++;
+ return e;
+ }
+// if (cur != null && cur.next != null){
+ // if we have a current entry, continue to the next entry in the list
+// cur = cur.next;
+// el++;
+// return cur;
+// }
+
+ do {
+ // either we exhausted the current entry list, or
+ // the entry was null. find another non-null entry.
+ cur = table[++idx];
+ } while (cur == null);
+
+ Entry e = cur;
+ cur = cur.next;
+ el ++;
+
+ return e;
+ }
+
+ public void remove() {
+ }
+
+ }
+
+ public static final class Entry<T> implements Cloneable {
+
+ final int key;
+ T value;
+ Entry next;
+
+ Entry(int k, T v, Entry n) {
+ key = k;
+ value = v;
+ next = n;
+ }
+
+ public int getKey(){
+ return key;
+ }
+
+ public T getValue(){
+ return value;
+ }
+
+ @Override
+ public String toString(){
+ return key + " => " + value;
+ }
+
+ @Override
+ public Entry<T> clone(){
+ try{
+ Entry<T> clone = (Entry<T>) super.clone();
+ clone.next = next != null ? next.clone() : null;
+ return clone;
+ }catch (CloneNotSupportedException ex){
+ }
+ return null;
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/util/JmeFormatter.java b/engine/src/core/com/jme3/util/JmeFormatter.java
new file mode 100644
index 0000000..998438a
--- /dev/null
+++ b/engine/src/core/com/jme3/util/JmeFormatter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.logging.Formatter;
+import java.util.logging.LogRecord;
+
+/**
+ * More simple formatter than the default one used in Java logging.
+ * Example output: <br/>
+ * INFO Display3D 12:00 PM: Display created.
+ */
+public class JmeFormatter extends Formatter {
+
+ private Date calendar = new Date();
+ private String lineSeperator;
+ private MessageFormat format;
+ private Object args[] = new Object[1];
+ private StringBuffer store = new StringBuffer();
+
+ public JmeFormatter(){
+ lineSeperator = System.getProperty("line.separator");
+ format = new MessageFormat("{0,time}");
+ }
+
+ @Override
+ public String format(LogRecord record) {
+ StringBuffer sb = new StringBuffer();
+
+ calendar.setTime(record.getMillis());
+ args[0] = calendar;
+ store.setLength(0);
+ format.format(args, store, null);
+
+ String clazz = null;
+ try{
+ clazz = Class.forName(record.getSourceClassName()).getSimpleName();
+ } catch (ClassNotFoundException ex){
+ }
+
+ sb.append(record.getLevel().getLocalizedName()).append(" ");
+ sb.append(clazz).append(" ");
+ sb.append(store.toString()).append(" ");
+ sb.append(formatMessage(record)).append(lineSeperator);
+
+ if (record.getThrown() != null) {
+ try {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ record.getThrown().printStackTrace(pw);
+ pw.close();
+ sb.append(sw.toString());
+ } catch (Exception ex) {
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/engine/src/core/com/jme3/util/ListMap.java b/engine/src/core/com/jme3/util/ListMap.java
new file mode 100644
index 0000000..c5b6de4
--- /dev/null
+++ b/engine/src/core/com/jme3/util/ListMap.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Implementation of a Map that favors iteration speed rather than
+ * get/put speed.
+ *
+ * @author Kirill Vainer
+ */
+public final class ListMap<K, V> implements Map<K, V>, Cloneable, Serializable {
+
+ public static void main(String[] args){
+ Map<String, String> map = new ListMap<String, String>();
+ map.put( "bob", "hello");
+ System.out.println(map.get("bob"));
+ map.remove("bob");
+ System.out.println(map.size());
+
+ map.put("abc", "1");
+ map.put("def", "2");
+ map.put("ghi", "3");
+ map.put("jkl", "4");
+ map.put("mno", "5");
+ System.out.println(map.get("ghi"));
+ }
+
+ private final static class ListMapEntry<K, V> implements Map.Entry<K, V>, Cloneable {
+
+ private final K key;
+ private V value;
+
+ public ListMapEntry(K key, V value){
+ this.key = key;
+ this.value = value;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V v) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ListMapEntry<K, V> clone(){
+ return new ListMapEntry<K, V>(key, value);
+ }
+
+ }
+
+ private final HashMap<K, V> backingMap;
+ private ListMapEntry<K, V>[] entries;
+
+// private final ArrayList<ListMapEntry<K,V>> entries;
+
+ public ListMap(){
+ entries = new ListMapEntry[4];
+ backingMap = new HashMap<K, V>(4);
+// entries = new ArrayList<ListMapEntry<K,V>>();
+ }
+
+ public ListMap(int initialCapacity){
+ entries = new ListMapEntry[initialCapacity];
+ backingMap = new HashMap<K, V>(initialCapacity);
+// entries = new ArrayList<ListMapEntry<K, V>>(initialCapacity);
+ }
+
+ public ListMap(Map<? extends K, ? extends V> map){
+ entries = new ListMapEntry[map.size()];
+ backingMap = new HashMap<K, V>(map.size());
+// entries = new ArrayList<ListMapEntry<K, V>>(map.size());
+ putAll(map);
+ }
+
+ public int size() {
+// return entries.size();
+ return backingMap.size();
+ }
+
+ public Entry<K, V> getEntry(int index){
+// return entries.get(index);
+ return entries[index];
+ }
+
+ public V getValue(int index){
+// return entries.get(index).value;
+ return entries[index].value;
+ }
+
+ public K getKey(int index){
+// return entries.get(index).key;
+ return entries[index].key;
+ }
+
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ private static boolean keyEq(Object keyA, Object keyB){
+ return keyA.hashCode() == keyB.hashCode() ? (keyA == keyB) || keyA.equals(keyB) : false;
+ }
+//
+// private static boolean valEq(Object a, Object b){
+// return a == null ? (b == null) : a.equals(b);
+// }
+
+ public boolean containsKey(Object key) {
+ return backingMap.containsKey( (K) key);
+// if (key == null)
+// throw new IllegalArgumentException();
+//
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// if (keyEq(entry.key, key))
+// return true;
+// }
+// return false;
+ }
+
+ public boolean containsValue(Object value) {
+ return backingMap.containsValue( (V) value);
+// for (int i = 0; i < entries.size(); i++){
+// if (valEq(entries.get(i).value, value))
+// return true;
+// }
+// return false;
+ }
+
+ public V get(Object key) {
+ return backingMap.get( (K) key);
+// if (key == null)
+// throw new IllegalArgumentException();
+//
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// if (keyEq(entry.key, key))
+// return entry.value;
+// }
+// return null;
+ }
+
+ public V put(K key, V value) {
+ if (backingMap.containsKey(key)){
+ // set the value on the entry
+ int size = size();
+ for (int i = 0; i < size; i++){
+ ListMapEntry<K, V> entry = entries[i];
+ if (keyEq(entry.key, key)){
+ entry.value = value;
+ break;
+ }
+ }
+ }else{
+ int size = size();
+ // expand list as necessary
+ if (size == entries.length){
+ ListMapEntry<K, V>[] tmpEntries = entries;
+ entries = new ListMapEntry[size * 2];
+ System.arraycopy(tmpEntries, 0, entries, 0, size);
+ }
+ entries[size] = new ListMapEntry<K, V>(key, value);
+ }
+ return backingMap.put(key, value);
+// if (key == null)
+// throw new IllegalArgumentException();
+//
+// // check if entry exists, if yes, overwrite it with new value
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// if (keyEq(entry.key, key)){
+// V prevValue = entry.value;
+// entry.value = value;
+// return prevValue;
+// }
+// }
+//
+// // add a new entry
+// entries.add(new ListMapEntry<K, V>(key, value));
+// return null;
+ }
+
+ public V remove(Object key) {
+ V element = backingMap.remove( (K) key);
+ if (element != null){
+ // find removed element
+ int size = size() + 1; // includes removed element
+ int removedIndex = -1;
+ for (int i = 0; i < size; i++){
+ ListMapEntry<K, V> entry = entries[i];
+ if (keyEq(entry.key, key)){
+ removedIndex = i;
+ break;
+ }
+ }
+ assert removedIndex >= 0;
+
+ size --;
+ for (int i = removedIndex; i < size; i++){
+ entries[i] = entries[i+1];
+ }
+ }
+ return element;
+// if (key == null)
+// throw new IllegalArgumentException();
+//
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// if (keyEq(entry.key, key)){
+// return entries.remove(i).value;
+// }
+// }
+// return null;
+ }
+
+ public void putAll(Map<? extends K, ? extends V> map) {
+ for (Entry<? extends K, ? extends V> entry : map.entrySet()){
+ put(entry.getKey(), entry.getValue());
+ }
+
+
+// if (map instanceof ListMap){
+// ListMap<K, V> listMap = (ListMap<K, V>) map;
+// ArrayList<ListMapEntry<K, V>> otherEntries = listMap.entries;
+// for (int i = 0; i < otherEntries.size(); i++){
+// ListMapEntry<K, V> entry = otherEntries.get(i);
+// put(entry.key, entry.value);
+// }
+// }else{
+// for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()){
+// put(entry.getKey(), entry.getValue());
+// }
+// }
+ }
+
+ public void clear() {
+ backingMap.clear();
+// entries.clear();
+ }
+
+ @Override
+ public ListMap<K, V> clone(){
+ ListMap<K, V> clone = new ListMap<K, V>(size());
+ clone.putAll(this);
+ return clone;
+ }
+
+ public Set<K> keySet() {
+ return backingMap.keySet();
+// HashSet<K> keys = new HashSet<K>();
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// keys.add(entry.key);
+// }
+// return keys;
+ }
+
+ public Collection<V> values() {
+ return backingMap.values();
+// ArrayList<V> values = new ArrayList<V>();
+// for (int i = 0; i < entries.size(); i++){
+// ListMapEntry<K,V> entry = entries.get(i);
+// values.add(entry.value);
+// }
+// return values;
+ }
+
+ public Set<Entry<K, V>> entrySet() {
+ return backingMap.entrySet();
+// HashSet<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
+// entryset.addAll(entries);
+// return entryset;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/util/LittleEndien.java b/engine/src/core/com/jme3/util/LittleEndien.java
new file mode 100644
index 0000000..0f71596
--- /dev/null
+++ b/engine/src/core/com/jme3/util/LittleEndien.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import java.io.*;
+
+/**
+ * <code>LittleEndien</code> is a class to read littleendien stored data
+ * via a InputStream. All functions work as defined in DataInput, but
+ * assume they come from a LittleEndien input stream. Currently used to read .ms3d and .3ds files.
+ * @author Jack Lindamood
+ */
+public class LittleEndien extends InputStream implements DataInput {
+
+ private BufferedInputStream in;
+ private BufferedReader inRead;
+
+ /**
+ * Creates a new LittleEndien reader from the given input stream. The
+ * stream is wrapped in a BufferedReader automatically.
+ * @param in The input stream to read from.
+ */
+ public LittleEndien(InputStream in) {
+ this.in = new BufferedInputStream(in);
+ inRead = new BufferedReader(new InputStreamReader(in));
+ }
+
+ public int read() throws IOException {
+ return in.read();
+ }
+
+ @Override
+ public int read(byte[] buf) throws IOException {
+ return in.read(buf);
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ return in.read(buf, off, len);
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return (in.read() & 0xff) | ((in.read() & 0xff) << 8);
+ }
+
+ /**
+ * read an unsigned int as a long
+ */
+ public long readUInt() throws IOException {
+ return ((in.read() & 0xff)
+ | ((in.read() & 0xff) << 8)
+ | ((in.read() & 0xff) << 16)
+ | (((long) (in.read() & 0xff)) << 24));
+ }
+
+ public boolean readBoolean() throws IOException {
+ return (in.read() != 0);
+ }
+
+ public byte readByte() throws IOException {
+ return (byte) in.read();
+ }
+
+ public int readUnsignedByte() throws IOException {
+ return in.read();
+ }
+
+ public short readShort() throws IOException {
+ return (short) this.readUnsignedShort();
+ }
+
+ public char readChar() throws IOException {
+ return (char) this.readUnsignedShort();
+ }
+
+ public int readInt() throws IOException {
+ return ((in.read() & 0xff)
+ | ((in.read() & 0xff) << 8)
+ | ((in.read() & 0xff) << 16)
+ | ((in.read() & 0xff) << 24));
+ }
+
+ public long readLong() throws IOException {
+ return ((in.read() & 0xff)
+ | ((long) (in.read() & 0xff) << 8)
+ | ((long) (in.read() & 0xff) << 16)
+ | ((long) (in.read() & 0xff) << 24)
+ | ((long) (in.read() & 0xff) << 32)
+ | ((long) (in.read() & 0xff) << 40)
+ | ((long) (in.read() & 0xff) << 48)
+ | ((long) (in.read() & 0xff) << 56));
+ }
+
+ public float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ public void readFully(byte b[]) throws IOException {
+ in.read(b, 0, b.length);
+ }
+
+ public void readFully(byte b[], int off, int len) throws IOException {
+ in.read(b, off, len);
+ }
+
+ public int skipBytes(int n) throws IOException {
+ return (int) in.skip(n);
+ }
+
+ public String readLine() throws IOException {
+ return inRead.readLine();
+ }
+
+ public String readUTF() throws IOException {
+ throw new IOException("Unsupported operation");
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+
+ @Override
+ public int available() throws IOException {
+ return in.available();
+ }
+}
diff --git a/engine/src/core/com/jme3/util/NativeObject.java b/engine/src/core/com/jme3/util/NativeObject.java
new file mode 100644
index 0000000..a59cb05
--- /dev/null
+++ b/engine/src/core/com/jme3/util/NativeObject.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+/**
+ * Describes a native object. An encapsulation of a certain object
+ * on the native side of the graphics or audio library.
+ *
+ * This class is used to track when OpenGL and OpenAL native objects are
+ * collected by the garbage collector, and then invoke the proper destructor
+ * on the OpenGL library to delete it from memory.
+ */
+public abstract class NativeObject implements Cloneable {
+
+ /**
+ * The ID of the object, usually depends on its type.
+ * Typically returned from calls like glGenTextures, glGenBuffers, etc.
+ */
+ protected int id = -1;
+
+ /**
+ * A reference to a "handle". By hard referencing a certain object, it's
+ * possible to find when a certain GLObject is no longer used, and to delete
+ * its instance from the graphics library.
+ */
+ protected Object handleRef = null;
+
+ /**
+ * True if the data represented by this GLObject has been changed
+ * and needs to be updated before used.
+ */
+ protected boolean updateNeeded = true;
+
+ /**
+ * The type of the GLObject, usually specified by a subclass.
+ */
+ protected final Class<?> type;
+
+ /**
+ * Creates a new GLObject with the given type. Should be
+ * called by the subclasses.
+ *
+ * @param type The type that the subclass represents.
+ */
+ public NativeObject(Class<?> type){
+ this.type = type;
+ this.handleRef = new Object();
+ }
+
+ /**
+ * Protected constructor that doesn't allocate handle ref.
+ * This is used in subclasses for the createDestructableClone().
+ */
+ protected NativeObject(Class<?> type, int id){
+ this.type = type;
+ this.id = id;
+ }
+
+ /**
+ * Sets the ID of the GLObject. This method is used in Renderer and must
+ * not be called by the user.
+ * @param id The ID to set
+ */
+ public void setId(int id){
+ if (this.id != -1)
+ throw new IllegalStateException("ID has already been set for this GL object.");
+
+ this.id = id;
+ }
+
+ /**
+ * @return The ID of the object. Should not be used by user code in most
+ * cases.
+ */
+ public int getId(){
+ return id;
+ }
+
+ /**
+ * Internal use only. Indicates that the object has changed
+ * and its state needs to be updated.
+ */
+ public void setUpdateNeeded(){
+ updateNeeded = true;
+ }
+
+ /**
+ * Internal use only. Indicates that the state changes were applied.
+ */
+ public void clearUpdateNeeded(){
+ updateNeeded = false;
+ }
+
+ /**
+ * Internal use only. Check if {@link #setUpdateNeeded()} was called before.
+ */
+ public boolean isUpdateNeeded(){
+ return updateNeeded;
+ }
+
+ @Override
+ public String toString(){
+ return "Native" + type.getSimpleName() + " " + id;
+ }
+
+ /**
+ * This should create a deep clone. For a shallow clone, use
+ * createDestructableClone().
+ */
+ @Override
+ protected NativeObject clone(){
+ try{
+ NativeObject obj = (NativeObject) super.clone();
+ obj.handleRef = new Object();
+ obj.id = -1;
+ obj.updateNeeded = true;
+ return obj;
+ }catch (CloneNotSupportedException ex){
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Called when the GL context is restarted to reset all IDs. Prevents
+ * "white textures" on display restart.
+ */
+ public abstract void resetObject();
+
+ /**
+ * Deletes the GL object from the GPU when it is no longer used. Called
+ * automatically by the GL object manager.
+ *
+ * @param rendererObject The renderer to be used to delete the object
+ */
+ public abstract void deleteObject(Object rendererObject);
+
+ /**
+ * Creates a shallow clone of this GL Object. The deleteObject method
+ * should be functional for this object.
+ */
+ public abstract NativeObject createDestructableClone();
+}
diff --git a/engine/src/core/com/jme3/util/NativeObjectManager.java b/engine/src/core/com/jme3/util/NativeObjectManager.java
new file mode 100644
index 0000000..f8d1d18
--- /dev/null
+++ b/engine/src/core/com/jme3/util/NativeObjectManager.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+import java.lang.ref.PhantomReference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * GLObjectManager tracks all GLObjects used by the Renderer. Using a
+ * <code>ReferenceQueue</code> the <code>GLObjectManager</code> can delete
+ * unused objects from GPU when their counterparts on the CPU are no longer used.
+ *
+ * On restart, the renderer may request the objects to be reset, thus allowing
+ * the GLObjects to re-initialize with the new display context.
+ */
+public class NativeObjectManager {
+
+ private static final Logger logger = Logger.getLogger(NativeObjectManager.class.getName());
+
+ /**
+ * The queue will receive notifications of {@link NativeObject}s which are no longer
+ * referenced.
+ */
+ private ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
+
+ /**
+ * List of currently active GLObjects.
+ */
+ private ArrayList<NativeObjectRef> refList
+ = new ArrayList<NativeObjectRef>();
+
+ private class NativeObjectRef extends PhantomReference<Object>{
+
+ private NativeObject objClone;
+ private WeakReference<NativeObject> realObj;
+
+ public NativeObjectRef(NativeObject obj){
+ super(obj.handleRef, refQueue);
+ assert obj.handleRef != null;
+
+ this.realObj = new WeakReference<NativeObject>(obj);
+ this.objClone = obj.createDestructableClone();
+ }
+ }
+
+ /**
+ * Register a GLObject with the manager.
+ */
+ public void registerForCleanup(NativeObject obj){
+ NativeObjectRef ref = new NativeObjectRef(obj);
+ refList.add(ref);
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST, "Registered: {0}", new String[]{obj.toString()});
+ }
+
+ /**
+ * Deletes unused GLObjects
+ */
+ public void deleteUnused(Object rendererObject){
+ while (true){
+ NativeObjectRef ref = (NativeObjectRef) refQueue.poll();
+ if (ref == null)
+ return;
+
+ refList.remove(ref);
+ ref.objClone.deleteObject(rendererObject);
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST, "Deleted: {0}", ref.objClone);
+ }
+ }
+
+ /**
+ * Deletes all objects. Must only be called when display is destroyed.
+ */
+ public void deleteAllObjects(Object rendererObject){
+ deleteUnused(rendererObject);
+ for (NativeObjectRef ref : refList){
+ ref.objClone.deleteObject(rendererObject);
+ NativeObject realObj = ref.realObj.get();
+ if (realObj != null){
+ // Note: make sure to reset them as well
+ // They may get used in a new renderer in the future
+ realObj.resetObject();
+ }
+ }
+ refList.clear();
+ }
+
+ /**
+ * Resets all {@link NativeObject}s.
+ */
+ public void resetObjects(){
+ for (NativeObjectRef ref : refList){
+ // here we use the actual obj not the clone,
+ // otherwise its useless
+ NativeObject realObj = ref.realObj.get();
+ if (realObj == null)
+ continue;
+
+ realObj.resetObject();
+ if (logger.isLoggable(Level.FINEST))
+ logger.log(Level.FINEST, "Reset: {0}", realObj);
+ }
+ refList.clear();
+ }
+
+// public void printObjects(){
+// System.out.println(" ------------------- ");
+// System.out.println(" GL Object count: "+ objectList.size());
+// for (GLObject obj : objectList){
+// System.out.println(obj);
+// }
+// }
+}
diff --git a/engine/src/core/com/jme3/util/PlaceholderAssets.java b/engine/src/core/com/jme3/util/PlaceholderAssets.java
new file mode 100644
index 0000000..c36abc9
--- /dev/null
+++ b/engine/src/core/com/jme3/util/PlaceholderAssets.java
@@ -0,0 +1,72 @@
+package com.jme3.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.audio.AudioBuffer;
+import com.jme3.audio.AudioData;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import java.nio.ByteBuffer;
+
+public class PlaceholderAssets {
+
+ /**
+ * Checkerboard of white and red squares
+ */
+ private static final byte[] imageData = {
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ (byte)0xFF, (byte)0x00, (byte)0x00,
+ (byte)0xFF, (byte)0xFF, (byte)0xFF,
+ };
+
+ public static Image getPlaceholderImage(){
+ ByteBuffer tempData = BufferUtils.createByteBuffer(3 * 4 * 4);
+ tempData.put(imageData).flip();
+ return new Image(Format.RGB8, 4, 4, tempData);
+ }
+
+ public static Material getPlaceholderMaterial(AssetManager assetManager){
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.setColor("Color", ColorRGBA.Red);
+ return mat;
+ }
+
+ public static Spatial getPlaceholderModel(AssetManager assetManager){
+ // What should be the size? Nobody knows
+ // the user's expected scale...
+ Box box = new Box(1, 1, 1);
+ Geometry geom = new Geometry("placeholder", box);
+ geom.setMaterial(getPlaceholderMaterial(assetManager));
+ return geom;
+ }
+
+ public static AudioData getPlaceholderAudio(){
+ AudioBuffer audioBuf = new AudioBuffer();
+ audioBuf.setupFormat(1, 8, 44100);
+ ByteBuffer bb = BufferUtils.createByteBuffer(1);
+ bb.put((byte)0).flip();
+ audioBuf.updateData(bb);
+ return audioBuf;
+ }
+
+}
diff --git a/engine/src/core/com/jme3/util/SafeArrayList.java b/engine/src/core/com/jme3/util/SafeArrayList.java
new file mode 100644
index 0000000..fcd6971
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SafeArrayList.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2009-2011 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util;
+
+import java.util.*;
+
+/**
+ * <p>Provides a list with similar modification semantics to java.util.concurrent's
+ * CopyOnWriteArrayList except that it is not concurrent and also provides
+ * direct access to the current array. This List allows modification of the
+ * contents while iterating as any iterators will be looking at a snapshot of
+ * the list at the time they were created. Similarly, access the raw internal
+ * array is only presenting a snap shot and so can be safely iterated while
+ * the list is changing.</p>
+ *
+ * <p>All modifications, including set() operations will cause a copy of the
+ * data to be created that replaces the old version. Because this list is
+ * not designed for threading concurrency it further optimizes the "many modifications"
+ * case by buffering them as a normal ArrayList until the next time the contents
+ * are accessed.</p>
+ *
+ * <p>Normal list modification performance should be equal to ArrayList in a
+ * many situations and always better than CopyOnWriteArrayList. Optimum usage
+ * is when modifications are done infrequently or in batches... as is often the
+ * case in a scene graph. Read operations perform superior to all other methods
+ * as the array can be accessed directly.</p>
+ *
+ * <p>Important caveats over normal java.util.Lists:</p>
+ * <ul>
+ * <li>Even though this class supports modifying the list, the subList() method
+ * returns a read-only list. This technically breaks the List contract.</li>
+ * <li>The ListIterators returned by this class only support the remove()
+ * modification method. add() and set() are not supported on the iterator.
+ * Even after ListIterator.remove() or Iterator.remove() is called, this change
+ * is not reflected in the iterator instance as it is still refering to its
+ * original snapshot.
+ * </ul>
+ *
+ * @version $Revision: 8940 $
+ * @author Paul Speed
+ */
+public class SafeArrayList<E> implements List<E> {
+
+ // Implementing List directly to avoid accidentally acquiring
+ // incorrect or non-optimal behavior from AbstractList. For
+ // example, the default iterator() method will not work for
+ // this list.
+
+ // Note: given the particular use-cases this was intended,
+ // it would make sense to nerf the public mutators and
+ // make this publicly act like a read-only list.
+ // SafeArrayList-specific methods could then be exposed
+ // for the classes like Node and Spatial to use to manage
+ // the list. This was the callers couldn't remove a child
+ // without it being detached properly, for example.
+
+ private Class<E> elementType;
+ private List<E> buffer;
+ private E[] backingArray;
+ private int size = 0;
+
+ public SafeArrayList(Class<E> elementType) {
+ this.elementType = elementType;
+ }
+
+ public SafeArrayList(Class<E> elementType, Collection<? extends E> c) {
+ this.elementType = elementType;
+ addAll(c);
+ }
+
+ protected final <T> T[] createArray(Class<T> type, int size) {
+ return (T[])java.lang.reflect.Array.newInstance(type, size);
+ }
+
+ protected final E[] createArray(int size) {
+ return createArray(elementType, size);
+ }
+
+ /**
+ * Returns a current snapshot of this List's backing array that
+ * is guaranteed not to change through further List manipulation.
+ * Changes to this array may or may not be reflected in the list and
+ * should be avoided.
+ */
+ public final E[] getArray() {
+ if( backingArray != null )
+ return backingArray;
+
+ if( buffer == null ) {
+ backingArray = createArray(0);
+ } else {
+ // Only keep the array or the buffer but never both at
+ // the same time. 1) it saves space, 2) it keeps the rest
+ // of the code safer.
+ backingArray = buffer.toArray( createArray(buffer.size()) );
+ buffer = null;
+ }
+ return backingArray;
+ }
+
+ protected final List<E> getBuffer() {
+ if( buffer != null )
+ return buffer;
+
+ if( backingArray == null ) {
+ buffer = new ArrayList();
+ } else {
+ // Only keep the array or the buffer but never both at
+ // the same time. 1) it saves space, 2) it keeps the rest
+ // of the code safer.
+ buffer = new ArrayList( Arrays.asList(backingArray) );
+ backingArray = null;
+ }
+ return buffer;
+ }
+
+ public final int size() {
+ return size;
+ }
+
+ public final boolean isEmpty() {
+ return size == 0;
+ }
+
+ public boolean contains(Object o) {
+ return indexOf(o) >= 0;
+ }
+
+ public Iterator<E> iterator() {
+ return listIterator();
+ }
+
+ public Object[] toArray() {
+ return getArray();
+ }
+
+ public <T> T[] toArray(T[] a) {
+
+ E[] array = getArray();
+ if (a.length < array.length) {
+ return (T[])Arrays.copyOf(array, array.length, a.getClass());
+ }
+
+ System.arraycopy( array, 0, a, 0, array.length );
+
+ if (a.length > array.length) {
+ a[array.length] = null;
+ }
+
+ return a;
+ }
+
+ public boolean add(E e) {
+ boolean result = getBuffer().add(e);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public boolean remove(Object o) {
+ boolean result = getBuffer().remove(o);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public boolean containsAll(Collection<?> c) {
+ return Arrays.asList(getArray()).containsAll(c);
+ }
+
+ public boolean addAll(Collection<? extends E> c) {
+ boolean result = getBuffer().addAll(c);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public boolean addAll(int index, Collection<? extends E> c) {
+ boolean result = getBuffer().addAll(index, c);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public boolean removeAll(Collection<?> c) {
+ boolean result = getBuffer().removeAll(c);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public boolean retainAll(Collection<?> c) {
+ boolean result = getBuffer().retainAll(c);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public void clear() {
+ getBuffer().clear();
+ size = 0;
+ }
+
+ public boolean equals(Object o) {
+ if( o == this )
+ return true;
+ if( !(o instanceof List) ) //covers null too
+ return false;
+ List other = (List)o;
+ Iterator i1 = iterator();
+ Iterator i2 = other.iterator();
+ while( i1.hasNext() && i2.hasNext() ) {
+ Object o1 = i1.next();
+ Object o2 = i2.next();
+ if( o1 == o2 )
+ continue;
+ if( o1 == null || !o1.equals(o2) )
+ return false;
+ }
+ return !(i1.hasNext() || !i2.hasNext());
+ }
+
+ public int hashCode() {
+ // Exactly the hash code described in the List interface, basically
+ E[] array = getArray();
+ int result = 1;
+ for( E e : array ) {
+ result = 31 * result + (e == null ? 0 : e.hashCode());
+ }
+ return result;
+ }
+
+ public final E get(int index) {
+ if( backingArray != null )
+ return backingArray[index];
+ if( buffer != null )
+ return buffer.get(index);
+ throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" );
+ }
+
+ public E set(int index, E element) {
+ return getBuffer().set(index, element);
+ }
+
+ public void add(int index, E element) {
+ getBuffer().add(index, element);
+ size = getBuffer().size();
+ }
+
+ public E remove(int index) {
+ E result = getBuffer().remove(index);
+ size = getBuffer().size();
+ return result;
+ }
+
+ public int indexOf(Object o) {
+ E[] array = getArray();
+ for( int i = 0; i < array.length; i++ ) {
+ E element = array[i];
+ if( element == o ) {
+ return i;
+ }
+ if( element != null && element.equals(o) ) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public int lastIndexOf(Object o) {
+ E[] array = getArray();
+ for( int i = array.length - 1; i >= 0; i-- ) {
+ E element = array[i];
+ if( element == o ) {
+ return i;
+ }
+ if( element != null && element.equals(o) ) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public ListIterator<E> listIterator() {
+ return new ArrayIterator<E>(getArray(), 0);
+ }
+
+ public ListIterator<E> listIterator(int index) {
+ return new ArrayIterator<E>(getArray(), index);
+ }
+
+ public List<E> subList(int fromIndex, int toIndex) {
+
+ // So far JME doesn't use subList that I can see so I'm nerfing it.
+ List<E> raw = Arrays.asList(getArray()).subList(fromIndex, toIndex);
+ return Collections.unmodifiableList(raw);
+ }
+
+ public String toString() {
+
+ E[] array = getArray();
+ if( array.length == 0 ) {
+ return "[]";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for( int i = 0; i < array.length; i++ ) {
+ if( i > 0 )
+ sb.append( ", " );
+ E e = array[i];
+ sb.append( e == this ? "(this Collection)" : e );
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+
+ protected class ArrayIterator<E> implements ListIterator<E> {
+ private E[] array;
+ private int next;
+ private int lastReturned;
+
+ protected ArrayIterator( E[] array, int index ) {
+ this.array = array;
+ this.next = index;
+ this.lastReturned = -1;
+ }
+
+ public boolean hasNext() {
+ return next != array.length;
+ }
+
+ public E next() {
+ if( !hasNext() )
+ throw new NoSuchElementException();
+ lastReturned = next++;
+ return array[lastReturned];
+ }
+
+ public boolean hasPrevious() {
+ return next != 0;
+ }
+
+ public E previous() {
+ if( !hasPrevious() )
+ throw new NoSuchElementException();
+ lastReturned = --next;
+ return array[lastReturned];
+ }
+
+ public int nextIndex() {
+ return next;
+ }
+
+ public int previousIndex() {
+ return next - 1;
+ }
+
+ public void remove() {
+ // This operation is not so easy to do but we will fake it.
+ // The issue is that the backing list could be completely
+ // different than the one this iterator is a snapshot of.
+ // We'll just remove(element) which in most cases will be
+ // correct. If the list had earlier .equals() equivalent
+ // elements then we'll remove one of those instead. Either
+ // way, none of those changes are reflected in this iterator.
+ SafeArrayList.this.remove( array[lastReturned] );
+ }
+
+ public void set(E e) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void add(E e) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/util/SkyFactory.java b/engine/src/core/com/jme3/util/SkyFactory.java
new file mode 100644
index 0000000..2808696
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SkyFactory.java
@@ -0,0 +1,214 @@
+package com.jme3.util;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.TextureKey;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.material.Material;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.texture.Image;
+import com.jme3.texture.Image.Format;
+import com.jme3.texture.Texture;
+import com.jme3.texture.TextureCubeMap;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+/**
+ * <code>SkyFactory</code> is used to create jME {@link Spatial}s that can
+ * be attached to the scene to display a sky image in the background.
+ *
+ * @author Kirill Vainer
+ */
+public class SkyFactory {
+
+ /**
+ * Creates a sky using the given texture (cubemap or spheremap).
+ *
+ * @param assetManager The asset manager to use to load materials
+ * @param texture Texture to use for the sky
+ * @param normalScale The normal scale is multiplied by the 3D normal
+ * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply
+ * and transformation to the normal.
+ * @param sphereMap The way the texture is used
+ * depends on this value:<br>
+ * <ul>
+ * <li>true: Its a Texture2D with the pixels arranged for
+ * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li>
+ * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D
+ * then the image is taken from it and is inserted into a TextureCubeMap</li>
+ * </ul>
+ * @return A spatial representing the sky
+ */
+ public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap) {
+ return createSky(assetManager, texture, normalScale, sphereMap, 10);
+ }
+
+ /**
+ * Creates a sky using the given texture (cubemap or spheremap).
+ *
+ * @param assetManager The asset manager to use to load materials
+ * @param texture Texture to use for the sky
+ * @param normalScale The normal scale is multiplied by the 3D normal
+ * to get a texture coordinate. Use Vector3f.UNIT_XYZ to not apply
+ * and transformation to the normal.
+ * @param sphereMap The way the texture is used
+ * depends on this value:<br>
+ * <ul>
+ * <li>true: Its a Texture2D with the pixels arranged for
+ * <a href="http://en.wikipedia.org/wiki/Sphere_mapping">sphere mapping</a>.</li>
+ * <li>false: Its either a TextureCubeMap or Texture2D. If its a Texture2D
+ * then the image is taken from it and is inserted into a TextureCubeMap</li>
+ * </ul>
+ * @param sphereRadius If specified, this will be the sky sphere's radius.
+ * This should be the camera's near plane for optimal quality.
+ * @return A spatial representing the sky
+ */
+ public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap, int sphereRadius) {
+ if (texture == null) {
+ throw new IllegalArgumentException("texture cannot be null");
+ }
+ final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true);
+
+ Geometry sky = new Geometry("Sky", sphereMesh);
+ sky.setQueueBucket(Bucket.Sky);
+ sky.setCullHint(Spatial.CullHint.Never);
+ sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO));
+
+ Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+
+ skyMat.setVector3("NormalScale", normalScale);
+ if (sphereMap) {
+ skyMat.setBoolean("SphereMap", sphereMap);
+ } else if (!(texture instanceof TextureCubeMap)) {
+ // make sure its a cubemap
+ Image img = texture.getImage();
+ texture = new TextureCubeMap();
+ texture.setImage(img);
+ }
+ skyMat.setTexture("Texture", texture);
+ sky.setMaterial(skyMat);
+
+ return sky;
+ }
+
+ private static void checkImage(Image image) {
+// if (image.getDepth() != 1)
+// throw new IllegalArgumentException("3D/Array images not allowed");
+
+ if (image.getWidth() != image.getHeight()) {
+ throw new IllegalArgumentException("Image width and height must be the same");
+ }
+
+ if (image.getMultiSamples() != 1) {
+ throw new IllegalArgumentException("Multisample textures not allowed");
+ }
+ }
+
+ private static void checkImagesForCubeMap(Image... images) {
+ if (images.length == 1) {
+ return;
+ }
+
+ Format fmt = images[0].getFormat();
+ int width = images[0].getWidth();
+ int height = images[0].getHeight();
+
+ ByteBuffer data = images[0].getData(0);
+ int size = data != null ? data.capacity() : 0;
+
+ checkImage(images[0]);
+
+ for (int i = 1; i < images.length; i++) {
+ Image image = images[i];
+ checkImage(images[i]);
+ if (image.getFormat() != fmt) {
+ throw new IllegalArgumentException("Images must have same format");
+ }
+ if (image.getWidth() != width || image.getHeight() != height) {
+ throw new IllegalArgumentException("Images must have same resolution");
+ }
+ ByteBuffer data2 = image.getData(0);
+ if (data2 != null){
+ if (data2.capacity() != size) {
+ throw new IllegalArgumentException("Images must have same size");
+ }
+ }
+ }
+ }
+
+ public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale) {
+ return createSky(assetManager, west, east, north, south, up, down, normalScale, 10);
+ }
+
+ public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down, Vector3f normalScale, int sphereRadius) {
+ final Sphere sphereMesh = new Sphere(10, 10, sphereRadius, false, true);
+ Geometry sky = new Geometry("Sky", sphereMesh);
+ sky.setQueueBucket(Bucket.Sky);
+ sky.setCullHint(Spatial.CullHint.Never);
+ sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO));
+
+ Image westImg = west.getImage();
+ Image eastImg = east.getImage();
+ Image northImg = north.getImage();
+ Image southImg = south.getImage();
+ Image upImg = up.getImage();
+ Image downImg = down.getImage();
+
+ checkImagesForCubeMap(westImg, eastImg, northImg, southImg, upImg, downImg);
+
+ Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), null);
+
+ cubeImage.addData(westImg.getData(0));
+ cubeImage.addData(eastImg.getData(0));
+
+ cubeImage.addData(downImg.getData(0));
+ cubeImage.addData(upImg.getData(0));
+
+ cubeImage.addData(southImg.getData(0));
+ cubeImage.addData(northImg.getData(0));
+
+ if (westImg.getEfficentData() != null){
+ // also consilidate efficient data
+ ArrayList<Object> efficientData = new ArrayList<Object>(6);
+ efficientData.add(westImg.getEfficentData());
+ efficientData.add(eastImg.getEfficentData());
+ efficientData.add(downImg.getEfficentData());
+ efficientData.add(upImg.getEfficentData());
+ efficientData.add(southImg.getEfficentData());
+ efficientData.add(northImg.getEfficentData());
+ cubeImage.setEfficentData(efficientData);
+ }
+
+ TextureCubeMap cubeMap = new TextureCubeMap(cubeImage);
+ cubeMap.setAnisotropicFilter(0);
+ cubeMap.setMagFilter(Texture.MagFilter.Bilinear);
+ cubeMap.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+ cubeMap.setWrap(Texture.WrapMode.EdgeClamp);
+
+ Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md");
+ skyMat.setTexture("Texture", cubeMap);
+ skyMat.setVector3("NormalScale", normalScale);
+ sky.setMaterial(skyMat);
+
+ return sky;
+ }
+
+ public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) {
+ return createSky(assetManager, west, east, north, south, up, down, Vector3f.UNIT_XYZ);
+ }
+
+ public static Spatial createSky(AssetManager assetManager, Texture texture, boolean sphereMap) {
+ return createSky(assetManager, texture, Vector3f.UNIT_XYZ, sphereMap);
+ }
+
+ public static Spatial createSky(AssetManager assetManager, String textureName, boolean sphereMap) {
+ TextureKey key = new TextureKey(textureName, true);
+ key.setGenerateMips(true);
+ key.setAsCube(!sphereMap);
+ Texture tex = assetManager.loadTexture(key);
+ return createSky(assetManager, tex, sphereMap);
+ }
+}
diff --git a/engine/src/core/com/jme3/util/SortUtil.java b/engine/src/core/com/jme3/util/SortUtil.java
new file mode 100644
index 0000000..fabe3bf
--- /dev/null
+++ b/engine/src/core/com/jme3/util/SortUtil.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Quick and merge sort implementations that create no garbage, unlike {@link
+ * Arrays#sort}. The merge sort is stable, the quick sort is not.
+ */
+public class SortUtil {
+
+ /**
+ * The size at or below which we will use insertion sort because it's
+ * probably faster.
+ */
+ private static final int INSERTION_SORT_THRESHOLD = 7;
+
+
+ /**
+ procedure optimizedGnomeSort(a[])
+ pos := 1
+ last := 0
+ while pos < length(a)
+ if (a[pos] >= a[pos-1])
+ if (last != 0)
+ pos := last
+ last := 0
+ end if
+ pos := pos + 1
+ else
+ swap a[pos] and a[pos-1]
+ if (pos > 1)
+ if (last == 0)
+ last := pos
+ end if
+ pos := pos - 1
+ else
+ pos := pos + 1
+ end if
+ end if
+ end while
+end procedure
+ */
+
+ public static void gsort(Object[] a, Comparator comp) {
+ int pos = 1;
+ int last = 0;
+ int length = a.length;
+
+ while (pos < length){
+ if ( comp.compare(a[pos], a[pos-1]) >= 0 ){
+ if (last != 0){
+ pos = last;
+ last = 0;
+ }
+ pos ++;
+ }else{
+ Object tmp = a[pos];
+ a[pos] = a[pos-1];
+ a[pos-1] = tmp;
+
+ if (pos > 1){
+ if (last == 0){
+ last = pos;
+ }
+ pos --;
+ }else{
+ pos ++;
+ }
+ }
+ }
+
+// int p = 0;
+// int l = a.length;
+// while (p < l) {
+// int pm1 = p - 1;
+// if (p == 0 || comp.compare(a[p], a[pm1]) >= 0) {
+// p++;
+// } else {
+// Object t = a[p];
+// a[p] = a[pm1];
+// a[pm1] = t;
+// p--;
+// }
+// }
+ }
+
+ private static void test(Float[] original, Float[] sorted, Comparator<Float> ic) {
+ long time, dt;
+
+ time = System.nanoTime();
+ for (int i = 0; i < 1000000; i++) {
+ System.arraycopy(original, 0, sorted, 0, original.length);
+ gsort(sorted, ic);
+ }
+ dt = System.nanoTime() - time;
+ System.out.println("GSort " + (dt/1000000.0) + " ms");
+
+ time = System.nanoTime();
+ for (int i = 0; i < 1000000; i++) {
+ System.arraycopy(original, 0, sorted, 0, original.length);
+ qsort(sorted, ic);
+ }
+ dt = System.nanoTime() - time;
+ System.out.println("QSort " + (dt/1000000.0) + " ms");
+
+ time = System.nanoTime();
+ for (int i = 0; i < 1000000; i++) {
+ System.arraycopy(original, 0, sorted, 0, original.length);
+ msort(original, sorted, ic);
+ }
+ dt = System.nanoTime() - time;
+ System.out.println("MSort " + (dt/1000000.0) + " ms");
+
+ time = System.nanoTime();
+ for (int i = 0; i < 1000000; i++) {
+ System.arraycopy(original, 0, sorted, 0, original.length);
+ Arrays.sort(sorted, ic);
+ }
+ dt = System.nanoTime() - time;
+ System.out.println("ASort " + (dt/1000000.0) + " ms");
+ }
+
+ public static void main(String[] args) {
+ Comparator<Float> ic = new Comparator<Float>() {
+
+ public int compare(Float o1, Float o2) {
+ return (int) (o1 - o2);
+ }
+ };
+ Float[] original = new Float[]{2f, 1f, 5f, 3f, 4f, 6f, 8f, 9f,
+ 11f, 10f, 12f, 13f, 14f, 15f, 7f, 19f, 20f, 18f, 16f, 17f,
+ 21f, 23f, 22f, 24f, 25f, 27f, 26f, 29f, 28f, 30f, 31f};
+ Float[] sorted = new Float[original.length];
+
+ while (true) {
+ test(original, sorted, ic);
+ }
+ }
+
+ /**
+ * Quick sorts the supplied array using the specified comparator.
+ */
+ public static void qsort(Object[] a, Comparator comp) {
+ qsort(a, 0, a.length - 1, comp);
+ }
+
+ /**
+ * Quick sorts the supplied array using the specified comparator.
+ *
+ * @param lo0 the index of the lowest element to include in the sort.
+ * @param hi0 the index of the highest element to include in the sort.
+ */
+ @SuppressWarnings("unchecked")
+ public static void qsort(Object[] a, int lo0, int hi0, Comparator comp) {
+ // bail out if we're already done
+ if (hi0 <= lo0) {
+ return;
+ }
+
+ // if this is a two element list, do a simple sort on it
+ Object t;
+ if (hi0 - lo0 == 1) {
+ // if they're not already sorted, swap them
+ if (comp.compare(a[hi0], a[lo0]) < 0) {
+ t = a[lo0];
+ a[lo0] = a[hi0];
+ a[hi0] = t;
+ }
+ return;
+ }
+
+ // the middle element in the array is our partitioning element
+ Object mid = a[(lo0 + hi0) / 2];
+
+ // set up our partitioning boundaries
+ int lo = lo0 - 1, hi = hi0 + 1;
+
+ // loop through the array until indices cross
+ for (;;) {
+ // find the first element that is greater than or equal to
+ // the partition element starting from the left Index.
+ while (comp.compare(a[++lo], mid) < 0);
+
+ // find an element that is smaller than or equal to
+ // the partition element starting from the right Index.
+ while (comp.compare(mid, a[--hi]) < 0);
+
+ // swap the two elements or bail out of the loop
+ if (hi > lo) {
+ t = a[lo];
+ a[lo] = a[hi];
+ a[hi] = t;
+ } else {
+ break;
+ }
+ }
+
+ // if the right index has not reached the left side of array
+ // must now sort the left partition
+ if (lo0 < lo - 1) {
+ qsort(a, lo0, lo - 1, comp);
+ }
+
+ // if the left index has not reached the right side of array
+ // must now sort the right partition
+ if (hi + 1 < hi0) {
+ qsort(a, hi + 1, hi0, comp);
+ }
+ }
+
+ public static void qsort(int[] a, int lo0, int hi0, Comparator comp) {
+ // bail out if we're already done
+ if (hi0 <= lo0) {
+ return;
+ }
+
+ // if this is a two element list, do a simple sort on it
+ int t;
+ if (hi0 - lo0 == 1) {
+ // if they're not already sorted, swap them
+ if (comp.compare(a[hi0], a[lo0]) < 0) {
+ t = a[lo0];
+ a[lo0] = a[hi0];
+ a[hi0] = t;
+ }
+ return;
+ }
+
+ // the middle element in the array is our partitioning element
+ int mid = a[(lo0 + hi0) / 2];
+
+ // set up our partitioning boundaries
+ int lo = lo0 - 1, hi = hi0 + 1;
+
+ // loop through the array until indices cross
+ for (;;) {
+ // find the first element that is greater than or equal to
+ // the partition element starting from the left Index.
+ while (comp.compare(a[++lo], mid) < 0);
+
+ // find an element that is smaller than or equal to
+ // the partition element starting from the right Index.
+ while (comp.compare(mid, a[--hi]) < 0);
+
+ // swap the two elements or bail out of the loop
+ if (hi > lo) {
+ t = a[lo];
+ a[lo] = a[hi];
+ a[hi] = t;
+ } else {
+ break;
+ }
+ }
+
+ // if the right index has not reached the left side of array
+ // must now sort the left partition
+ if (lo0 < lo - 1) {
+ qsort(a, lo0, lo - 1, comp);
+ }
+
+ // if the left index has not reached the right side of array
+ // must now sort the right partition
+ if (hi + 1 < hi0) {
+ qsort(a, hi + 1, hi0, comp);
+ }
+ }
+
+ /**
+ * Merge sort
+ */
+ public static void msort(Object[] src, Object[] dest, Comparator comp){
+ msort(src, dest, 0, src.length - 1, comp);
+ }
+
+ /**
+ * Merge sort
+ *
+ * @param src Source array
+ * @param dest Destination array
+ * @param low Index of beginning element
+ * @param high Index of end element
+ * @param comp Comparator
+ */
+ public static void msort(Object[] src, Object[] dest, int low, int high,
+ Comparator comp) {
+ if(low < high) {
+ int center = (low + high) / 2;
+ msort(src, dest, low, center, comp);
+ msort(src, dest, center + 1, high, comp);
+ merge(src, dest, low, center + 1, high, comp);
+ }
+ }
+
+ private static void merge(Object[] src, Object[] dest,
+ int low, int middle, int high, Comparator comp) {
+ int leftEnd = middle - 1;
+ int pos = low;
+ int numElements = high - low + 1;
+
+ while (low <= leftEnd && middle <= high) {
+ if (comp.compare(src[low], src[middle]) <= 0) {
+ dest[pos++] = src[low++];
+ } else {
+ dest[pos++] = src[middle++];
+ }
+ }
+
+ while (low <= leftEnd) {
+ dest[pos++] = src[low++];
+ }
+
+ while (middle <= high) {
+ dest[pos++] = src[middle++];
+ }
+
+ for (int i = 0; i < numElements; i++, high--) {
+ src[high] = dest[high];
+ }
+ }
+}
diff --git a/engine/src/core/com/jme3/util/TangentBinormalGenerator.java b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
new file mode 100644
index 0000000..88f6822
--- /dev/null
+++ b/engine/src/core/com/jme3/util/TangentBinormalGenerator.java
@@ -0,0 +1,739 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.*;
+import com.jme3.scene.VertexBuffer.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.mesh.IndexBuffer;
+import static com.jme3.util.BufferUtils.*;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Lex (Aleksey Nikiforov)
+ */
+public class TangentBinormalGenerator {
+
+ private static final float ZERO_TOLERANCE = 0.0000001f;
+ private static final Logger log = Logger.getLogger(
+ TangentBinormalGenerator.class.getName());
+ private static float toleranceAngle;
+ private static float toleranceDot;
+
+ static {
+ setToleranceAngle(45);
+ }
+
+
+ private static class VertexInfo {
+ public final Vector3f position;
+ public final Vector3f normal;
+ public final ArrayList<Integer> indices = new ArrayList<Integer>();
+
+ public VertexInfo(Vector3f position, Vector3f normal) {
+ this.position = position;
+ this.normal = normal;
+ }
+ }
+
+ /** Collects all the triangle data for one vertex.
+ */
+ private static class VertexData {
+ public final ArrayList<TriangleData> triangles = new ArrayList<TriangleData>();
+
+ public VertexData() { }
+ }
+
+ /** Keeps track of tangent, binormal, and normal for one triangle.
+ */
+ public static class TriangleData {
+ public final Vector3f tangent;
+ public final Vector3f binormal;
+ public final Vector3f normal;
+
+ public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) {
+ this.tangent = tangent;
+ this.binormal = binormal;
+ this.normal = normal;
+ }
+ }
+
+ private static VertexData[] initVertexData(int size) {
+ VertexData[] vertices = new VertexData[size];
+ for (int i = 0; i < size; i++) {
+ vertices[i] = new VertexData();
+ }
+ return vertices;
+ }
+
+ public static void generate(Mesh mesh) {
+ generate(mesh, true);
+ }
+
+ public static void generate(Spatial scene) {
+ if (scene instanceof Node) {
+ Node node = (Node) scene;
+ for (Spatial child : node.getChildren()) {
+ generate(child);
+ }
+ } else {
+ Geometry geom = (Geometry) scene;
+ generate(geom.getMesh());
+ }
+ }
+
+ public static void generate(Mesh mesh, boolean approxTangents) {
+ int[] index = new int[3];
+ Vector3f[] v = new Vector3f[3];
+ Vector2f[] t = new Vector2f[3];
+ for (int i = 0; i < 3; i++) {
+ v[i] = new Vector3f();
+ t[i] = new Vector2f();
+ }
+
+ if (mesh.getBuffer(Type.Normal) == null) {
+ throw new IllegalArgumentException("The given mesh has no normal data!");
+ }
+
+ VertexData[] vertices;
+ switch (mesh.getMode()) {
+ case Triangles:
+ vertices = processTriangles(mesh, index, v, t);
+ break;
+ case TriangleStrip:
+ vertices = processTriangleStrip(mesh, index, v, t);
+ break;
+ case TriangleFan:
+ vertices = processTriangleFan(mesh, index, v, t);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ mesh.getMode() + " is not supported.");
+ }
+
+ processTriangleData(mesh, vertices, approxTangents);
+
+ //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer
+ if (mesh.getBuffer(Type.BindPosePosition) != null) {
+
+ VertexBuffer tangents = mesh.getBuffer(Type.Tangent);
+ if (tangents != null) {
+ VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent);
+ bindTangents.setupData(Usage.CpuOnly,
+ 4,
+ Format.Float,
+ BufferUtils.clone(tangents.getData()));
+
+ if (mesh.getBuffer(Type.BindPoseTangent) != null) {
+ mesh.clearBuffer(Type.BindPoseTangent);
+ }
+ mesh.setBuffer(bindTangents);
+ tangents.setUsage(Usage.Stream);
+ }
+ }
+ }
+
+ private static VertexData[] processTriangles(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ if (mesh.getBuffer(Type.TexCoord) == null) {
+ throw new IllegalArgumentException("Can only generate tangents for "
+ + "meshes with texture coordinates");
+ }
+
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ for (int i = 0; i < indexBuffer.size() / 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ index[j] = indexBuffer.get(i * 3 + j);
+ populateFromBuffer(v[j], vertexBuffer, index[j]);
+ populateFromBuffer(t[j], textureBuffer, index[j]);
+ }
+
+ TriangleData triData = processTriangle(index, v, t);
+ if (triData != null) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+ }
+
+ return vertices;
+ }
+
+ private static VertexData[] processTriangleStrip(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ index[0] = indexBuffer.get(0);
+ index[1] = indexBuffer.get(1);
+
+ populateFromBuffer(v[0], vertexBuffer, index[0]);
+ populateFromBuffer(v[1], vertexBuffer, index[1]);
+
+ populateFromBuffer(t[0], textureBuffer, index[0]);
+ populateFromBuffer(t[1], textureBuffer, index[1]);
+
+ for (int i = 2; i < indexBuffer.size(); i++) {
+ index[2] = indexBuffer.get(i);
+ BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]);
+ BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]);
+
+ boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]);
+ TriangleData triData = processTriangle(index, v, t);
+
+ if (triData != null && !isDegenerate) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+
+ Vector3f vTemp = v[0];
+ v[0] = v[1];
+ v[1] = v[2];
+ v[2] = vTemp;
+
+ Vector2f tTemp = t[0];
+ t[0] = t[1];
+ t[1] = t[2];
+ t[2] = tTemp;
+
+ index[0] = index[1];
+ index[1] = index[2];
+ }
+
+ return vertices;
+ }
+
+ private static VertexData[] processTriangleFan(Mesh mesh,
+ int[] index, Vector3f[] v, Vector2f[] t) {
+ IndexBuffer indexBuffer = mesh.getIndexBuffer();
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData();
+
+ VertexData[] vertices = initVertexData(vertexBuffer.capacity() / 3);
+
+ index[0] = indexBuffer.get(0);
+ index[1] = indexBuffer.get(1);
+
+ populateFromBuffer(v[0], vertexBuffer, index[0]);
+ populateFromBuffer(v[1], vertexBuffer, index[1]);
+
+ populateFromBuffer(t[0], textureBuffer, index[0]);
+ populateFromBuffer(t[1], textureBuffer, index[1]);
+
+ for (int i = 2; i < vertexBuffer.capacity() / 3; i++) {
+ index[2] = indexBuffer.get(i);
+ populateFromBuffer(v[2], vertexBuffer, index[2]);
+ populateFromBuffer(t[2], textureBuffer, index[2]);
+
+ TriangleData triData = processTriangle(index, v, t);
+ if (triData != null) {
+ vertices[index[0]].triangles.add(triData);
+ vertices[index[1]].triangles.add(triData);
+ vertices[index[2]].triangles.add(triData);
+ }
+
+ Vector3f vTemp = v[1];
+ v[1] = v[2];
+ v[2] = vTemp;
+
+ Vector2f tTemp = t[1];
+ t[1] = t[2];
+ t[2] = tTemp;
+
+ index[1] = index[2];
+ }
+
+ return vertices;
+ }
+
+ // check if the area is greater than zero
+ private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) {
+ return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0;
+ }
+
+ public static TriangleData processTriangle(int[] index,
+ Vector3f[] v, Vector2f[] t) {
+ Vector3f edge1 = new Vector3f();
+ Vector3f edge2 = new Vector3f();
+ Vector2f edge1uv = new Vector2f();
+ Vector2f edge2uv = new Vector2f();
+
+ Vector3f tangent = new Vector3f();
+ Vector3f binormal = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ t[1].subtract(t[0], edge1uv);
+ t[2].subtract(t[0], edge2uv);
+ float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x;
+
+ boolean normalize = false;
+ if (Math.abs(det) < ZERO_TOLERANCE) {
+ log.log(Level.WARNING, "Colinear uv coordinates for triangle "
+ + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], "
+ + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]",
+ new Object[]{index[0], index[1], index[2],
+ t[0].x, t[0].y, t[1].x, t[1].y, t[2].x, t[2].y});
+ det = 1;
+ normalize = true;
+ }
+
+ v[1].subtract(v[0], edge1);
+ v[2].subtract(v[0], edge2);
+
+ tangent.set(edge1);
+ tangent.normalizeLocal();
+ binormal.set(edge2);
+ binormal.normalizeLocal();
+
+ if (Math.abs(Math.abs(tangent.dot(binormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING, "Vertices are on the same line "
+ + "for triangle [{0}, {1}, {2}].",
+ new Object[]{index[0], index[1], index[2]});
+ }
+
+ float factor = 1 / det;
+ tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor;
+ tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor;
+ tangent.z = (edge2uv.y * edge1.z - edge1uv.y * edge2.z) * factor;
+ if (normalize) {
+ tangent.normalizeLocal();
+ }
+
+ binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor;
+ binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor;
+ binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor;
+ if (normalize) {
+ binormal.normalizeLocal();
+ }
+
+ tangent.cross(binormal, normal);
+ normal.normalizeLocal();
+
+ return new TriangleData(
+ tangent,
+ binormal,
+ normal);
+ }
+
+ public static void setToleranceAngle(float angle) {
+ if (angle < 0 || angle > 179) {
+ throw new IllegalArgumentException(
+ "The angle must be between 0 and 179 degrees.");
+ }
+ toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD);
+ toleranceAngle = angle;
+ }
+
+
+ private static boolean approxEqual(Vector3f u, Vector3f v) {
+ float tolerance = 1E-4f;
+ return (FastMath.abs(u.x - v.x) < tolerance) &&
+ (FastMath.abs(u.y - v.y) < tolerance) &&
+ (FastMath.abs(u.z - v.z) < tolerance);
+ }
+
+ private static ArrayList<VertexInfo> linkVertices(Mesh mesh) {
+ ArrayList<VertexInfo> vertexMap = new ArrayList<VertexInfo>();
+
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ Vector3f position = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ final int size = vertexBuffer.capacity() / 3;
+ for (int i = 0; i < size; i++) {
+
+ populateFromBuffer(position, vertexBuffer, i);
+ populateFromBuffer(normal, normalBuffer, i);
+
+ boolean found = false;
+
+ for (int j = 0; j < vertexMap.size(); j++) {
+ VertexInfo vertexInfo = vertexMap.get(j);
+ if (approxEqual(vertexInfo.position, position) &&
+ approxEqual(vertexInfo.normal, normal))
+ {
+ vertexInfo.indices.add(i);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone());
+ vertexInfo.indices.add(i);
+ vertexMap.add(vertexInfo);
+ }
+ }
+
+ return vertexMap;
+ }
+
+ private static void processTriangleData(Mesh mesh, VertexData[] vertices,
+ boolean approxTangent)
+ {
+ ArrayList<VertexInfo> vertexMap = linkVertices(mesh);
+
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.length * 4);
+// FloatBuffer binormals = BufferUtils.createFloatBuffer(vertices.length * 3);
+
+ Vector3f tangent = new Vector3f();
+ Vector3f binormal = new Vector3f();
+ Vector3f normal = new Vector3f();
+ Vector3f givenNormal = new Vector3f();
+
+ Vector3f tangentUnit = new Vector3f();
+ Vector3f binormalUnit = new Vector3f();
+
+ for (int k = 0; k < vertexMap.size(); k++) {
+ float wCoord = -1;
+
+ VertexInfo vertexInfo = vertexMap.get(k);
+
+ givenNormal.set(vertexInfo.normal);
+ givenNormal.normalizeLocal();
+
+ TriangleData firstTriangle = vertices[vertexInfo.indices.get(0)].triangles.get(0);
+
+ // check tangent and binormal consistency
+ tangent.set(firstTriangle.tangent);
+ tangent.normalizeLocal();
+ binormal.set(firstTriangle.binormal);
+ binormal.normalizeLocal();
+
+ for (int i : vertexInfo.indices) {
+ ArrayList<TriangleData> triangles = vertices[i].triangles;
+
+ for (int j = 0; j < triangles.size(); j++) {
+ TriangleData triangleData = triangles.get(j);
+
+ tangentUnit.set(triangleData.tangent);
+ tangentUnit.normalizeLocal();
+ if (tangent.dot(tangentUnit) < toleranceDot) {
+ log.log(Level.WARNING,
+ "Angle between tangents exceeds tolerance "
+ + "for vertex {0}.", i);
+ break;
+ }
+
+ if (!approxTangent) {
+ binormalUnit.set(triangleData.binormal);
+ binormalUnit.normalizeLocal();
+ if (binormal.dot(binormalUnit) < toleranceDot) {
+ log.log(Level.WARNING,
+ "Angle between binormals exceeds tolerance "
+ + "for vertex {0}.", i);
+ break;
+ }
+ }
+ }
+ }
+
+
+ // find average tangent
+ tangent.set(0, 0, 0);
+ binormal.set(0, 0, 0);
+
+ int triangleCount = 0;
+ for (int i : vertexInfo.indices) {
+ ArrayList<TriangleData> triangles = vertices[i].triangles;
+ triangleCount += triangles.size();
+
+ boolean flippedNormal = false;
+ for (int j = 0; j < triangles.size(); j++) {
+ TriangleData triangleData = triangles.get(j);
+ tangent.addLocal(triangleData.tangent);
+ binormal.addLocal(triangleData.binormal);
+
+ if (givenNormal.dot(triangleData.normal) < 0) {
+ flippedNormal = true;
+ }
+ }
+ if (flippedNormal /*&& approxTangent*/) {
+ // Generated normal is flipped for this vertex,
+ // so binormal = normal.cross(tangent) will be flipped in the shader
+ // log.log(Level.WARNING,
+ // "Binormal is flipped for vertex {0}.", i);
+
+ wCoord = 1;
+ }
+ }
+
+
+ int blameVertex = vertexInfo.indices.get(0);
+
+ if (tangent.length() < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Shared tangent is zero for vertex {0}.", blameVertex);
+ // attempt to fix from binormal
+ if (binormal.length() >= ZERO_TOLERANCE) {
+ binormal.cross(givenNormal, tangent);
+ tangent.normalizeLocal();
+ } // if all fails use the tangent from the first triangle
+ else {
+ tangent.set(firstTriangle.tangent);
+ }
+ } else {
+ tangent.divideLocal(triangleCount);
+ }
+
+ tangentUnit.set(tangent);
+ tangentUnit.normalizeLocal();
+ if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Normal and tangent are parallel for vertex {0}.", blameVertex);
+ }
+
+
+ if (!approxTangent) {
+ if (binormal.length() < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Shared binormal is zero for vertex {0}.", blameVertex);
+ // attempt to fix from tangent
+ if (tangent.length() >= ZERO_TOLERANCE) {
+ givenNormal.cross(tangent, binormal);
+ binormal.normalizeLocal();
+ } // if all fails use the binormal from the first triangle
+ else {
+ binormal.set(firstTriangle.binormal);
+ }
+ } else {
+ binormal.divideLocal(triangleCount);
+ }
+
+ binormalUnit.set(binormal);
+ binormalUnit.normalizeLocal();
+ if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Normal and binormal are parallel for vertex {0}.", blameVertex);
+ }
+
+ if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1)
+ < ZERO_TOLERANCE) {
+ log.log(Level.WARNING,
+ "Tangent and binormal are parallel for vertex {0}.", blameVertex);
+ }
+ }
+
+ for (int i : vertexInfo.indices) {
+ if (approxTangent) {
+ // This calculation ensures that normal and tagent have a 90 degree angle.
+ // Removing this will lead to visual artifacts.
+ givenNormal.cross(tangent, binormal);
+ binormal.cross(givenNormal, tangent);
+
+ tangent.normalizeLocal();
+
+ tangents.put((i * 4), tangent.x);
+ tangents.put((i * 4) + 1, tangent.y);
+ tangents.put((i * 4) + 2, tangent.z);
+ tangents.put((i * 4) + 3, wCoord);
+ } else {
+ tangents.put((i * 4), tangent.x);
+ tangents.put((i * 4) + 1, tangent.y);
+ tangents.put((i * 4) + 2, tangent.z);
+ tangents.put((i * 4) + 3, wCoord);
+
+ //setInBuffer(binormal, binormals, i);
+ }
+ }
+ }
+
+ mesh.setBuffer(Type.Tangent, 4, tangents);
+// if (!approxTangent) mesh.setBuffer(Type.Binormal, 3, binormals);
+ }
+
+ public static Mesh genTbnLines(Mesh mesh, float scale) {
+ if (mesh.getBuffer(Type.Tangent) == null) {
+ return genNormalLines(mesh, scale);
+ } else {
+ return genTangentLines(mesh, scale);
+ }
+ }
+
+ public static Mesh genNormalLines(Mesh mesh, float scale) {
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+
+ ColorRGBA originColor = ColorRGBA.White;
+ ColorRGBA normalColor = ColorRGBA.Blue;
+
+ Mesh lineMesh = new Mesh();
+ lineMesh.setMode(Mesh.Mode.Lines);
+
+ Vector3f origin = new Vector3f();
+ Vector3f point = new Vector3f();
+
+ FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 2);
+ FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 2);
+
+ for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+ populateFromBuffer(origin, vertexBuffer, i);
+ populateFromBuffer(point, normalBuffer, i);
+
+ int index = i * 2;
+
+ setInBuffer(origin, lineVertex, index);
+ setInBuffer(originColor, lineColor, index);
+
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 1);
+ setInBuffer(normalColor, lineColor, index + 1);
+ }
+
+ lineMesh.setBuffer(Type.Position, 3, lineVertex);
+ lineMesh.setBuffer(Type.Color, 4, lineColor);
+
+ lineMesh.setStatic();
+ lineMesh.setInterleaved();
+ return lineMesh;
+ }
+
+ private static Mesh genTangentLines(Mesh mesh, float scale) {
+ FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData();
+ FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData();
+ FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData();
+
+ FloatBuffer binormalBuffer = null;
+ if (mesh.getBuffer(Type.Binormal) != null) {
+ binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData();
+ }
+
+ ColorRGBA originColor = ColorRGBA.White;
+ ColorRGBA tangentColor = ColorRGBA.Red;
+ ColorRGBA binormalColor = ColorRGBA.Green;
+ ColorRGBA normalColor = ColorRGBA.Blue;
+
+ Mesh lineMesh = new Mesh();
+ lineMesh.setMode(Mesh.Mode.Lines);
+
+ Vector3f origin = new Vector3f();
+ Vector3f point = new Vector3f();
+ Vector3f tangent = new Vector3f();
+ Vector3f normal = new Vector3f();
+
+ IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.capacity() / 3 * 6);
+ FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.capacity() * 4);
+ FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.capacity() / 3 * 4 * 4);
+
+ boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4;
+ float tangentW = 1;
+
+ for (int i = 0; i < vertexBuffer.capacity() / 3; i++) {
+ populateFromBuffer(origin, vertexBuffer, i);
+ populateFromBuffer(normal, normalBuffer, i);
+
+ if (hasParity) {
+ tangent.x = tangentBuffer.get(i * 4);
+ tangent.y = tangentBuffer.get(i * 4 + 1);
+ tangent.z = tangentBuffer.get(i * 4 + 2);
+ tangentW = tangentBuffer.get(i * 4 + 3);
+ } else {
+ populateFromBuffer(tangent, tangentBuffer, i);
+ }
+
+ int index = i * 4;
+
+ int id = i * 6;
+ lineIndex.put(id, index);
+ lineIndex.put(id + 1, index + 1);
+ lineIndex.put(id + 2, index);
+ lineIndex.put(id + 3, index + 2);
+ lineIndex.put(id + 4, index);
+ lineIndex.put(id + 5, index + 3);
+
+ setInBuffer(origin, lineVertex, index);
+ setInBuffer(originColor, lineColor, index);
+
+ point.set(tangent);
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 1);
+ setInBuffer(tangentColor, lineColor, index + 1);
+
+ // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w
+
+ if (binormalBuffer == null) {
+ normal.cross(tangent, point);
+ point.multLocal(-tangentW);
+ point.normalizeLocal();
+ } else {
+ populateFromBuffer(point, binormalBuffer, i);
+ }
+
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 2);
+ setInBuffer(binormalColor, lineColor, index + 2);
+
+ point.set(normal);
+ point.multLocal(scale);
+ point.addLocal(origin);
+ setInBuffer(point, lineVertex, index + 3);
+ setInBuffer(normalColor, lineColor, index + 3);
+ }
+
+ lineMesh.setBuffer(Type.Index, 1, lineIndex);
+ lineMesh.setBuffer(Type.Position, 3, lineVertex);
+ lineMesh.setBuffer(Type.Color, 4, lineColor);
+
+ lineMesh.setStatic();
+ lineMesh.setInterleaved();
+ return lineMesh;
+ }
+}
diff --git a/engine/src/core/com/jme3/util/TempVars.java b/engine/src/core/com/jme3/util/TempVars.java
new file mode 100644
index 0000000..2fdea36
--- /dev/null
+++ b/engine/src/core/com/jme3/util/TempVars.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.util;
+
+import com.jme3.collision.bih.BIHNode.BIHStackData;
+import com.jme3.math.*;
+import com.jme3.scene.Spatial;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+
+/**
+ * Temporary variables assigned to each thread. Engine classes may access
+ * these temp variables with TempVars.get(), all retrieved TempVars
+ * instances must be returned via TempVars.release().
+ * This returns an available instance of the TempVar class ensuring this
+ * particular instance is never used elsewhere in the mean time.
+ */
+public class TempVars {
+
+ /**
+ * Allow X instances of TempVars in a single thread.
+ */
+ private static final int STACK_SIZE = 5;
+
+ /**
+ * <code>TempVarsStack</code> contains a stack of TempVars.
+ * Every time TempVars.get() is called, a new entry is added to the stack,
+ * and the index incremented.
+ * When TempVars.release() is called, the entry is checked against
+ * the current instance and then the index is decremented.
+ */
+ private static class TempVarsStack {
+
+ int index = 0;
+ TempVars[] tempVars = new TempVars[STACK_SIZE];
+ }
+ /**
+ * ThreadLocal to store a TempVarsStack for each thread.
+ * This ensures each thread has a single TempVarsStack that is
+ * used only in method calls in that thread.
+ */
+ private static final ThreadLocal<TempVarsStack> varsLocal = new ThreadLocal<TempVarsStack>() {
+
+ @Override
+ public TempVarsStack initialValue() {
+ return new TempVarsStack();
+ }
+ };
+ /**
+ * This instance of TempVars has been retrieved but not released yet.
+ */
+ private boolean isUsed = false;
+
+ private TempVars() {
+ }
+
+ /**
+ * Acquire an instance of the TempVar class.
+ * You have to release the instance after use by calling the
+ * release() method.
+ * If more than STACK_SIZE (currently 5) instances are requested
+ * in a single thread then an ArrayIndexOutOfBoundsException will be thrown.
+ *
+ * @return A TempVar instance
+ */
+ public static TempVars get() {
+ TempVarsStack stack = varsLocal.get();
+
+ TempVars instance = stack.tempVars[stack.index];
+
+ if (instance == null) {
+ // Create new
+ instance = new TempVars();
+
+ // Put it in there
+ stack.tempVars[stack.index] = instance;
+ }
+
+ stack.index++;
+
+ instance.isUsed = true;
+
+ return instance;
+ }
+
+ /**
+ * Releases this instance of TempVars.
+ * Once released, the contents of the TempVars are undefined.
+ * The TempVars must be released in the opposite order that they are retrieved,
+ * e.g. Acquiring vars1, then acquiring vars2, vars2 MUST be released
+ * first otherwise an exception will be thrown.
+ */
+ public void release() {
+ if (!isUsed) {
+ throw new IllegalStateException("This instance of TempVars was already released!");
+ }
+
+ isUsed = false;
+
+ TempVarsStack stack = varsLocal.get();
+
+ // Return it to the stack
+ stack.index--;
+
+ // Check if it is actually there
+ if (stack.tempVars[stack.index] != this) {
+ throw new IllegalStateException("An instance of TempVars has not been released in a called method!");
+ }
+ }
+ /**
+ * For interfacing with OpenGL in Renderer.
+ */
+ public final IntBuffer intBuffer1 = BufferUtils.createIntBuffer(1);
+ public final IntBuffer intBuffer16 = BufferUtils.createIntBuffer(16);
+ public final FloatBuffer floatBuffer16 = BufferUtils.createFloatBuffer(16);
+ /**
+ * Skinning buffers
+ */
+ public final float[] skinPositions = new float[512 * 3];
+ public final float[] skinNormals = new float[512 * 3];
+ //tangent buffer as 4 components by elements
+ public final float[] skinTangents = new float[512 * 4];
+ /**
+ * Fetching triangle from mesh
+ */
+ public final Triangle triangle = new Triangle();
+ /**
+ * Color
+ */
+ public final ColorRGBA color = new ColorRGBA();
+ /**
+ * General vectors.
+ */
+ public final Vector3f vect1 = new Vector3f();
+ public final Vector3f vect2 = new Vector3f();
+ public final Vector3f vect3 = new Vector3f();
+ public final Vector3f vect4 = new Vector3f();
+ public final Vector3f vect5 = new Vector3f();
+ public final Vector3f vect6 = new Vector3f();
+ public final Vector3f vect7 = new Vector3f();
+ //seems the maximum number of vector used is 7 in com.jme3.bounding.java
+ public final Vector3f vect8 = new Vector3f();
+ public final Vector3f vect9 = new Vector3f();
+ public final Vector3f vect10 = new Vector3f();
+ public final Vector4f vect4f = new Vector4f();
+ public final Vector3f[] tri = {new Vector3f(),
+ new Vector3f(),
+ new Vector3f()};
+ /**
+ * 2D vector
+ */
+ public final Vector2f vect2d = new Vector2f();
+ public final Vector2f vect2d2 = new Vector2f();
+ /**
+ * General matrices.
+ */
+ public final Matrix3f tempMat3 = new Matrix3f();
+ public final Matrix4f tempMat4 = new Matrix4f();
+ public final Matrix4f tempMat42 = new Matrix4f();
+ /**
+ * General quaternions.
+ */
+ public final Quaternion quat1 = new Quaternion();
+ public final Quaternion quat2 = new Quaternion();
+ /**
+ * Eigen
+ */
+ public final Eigen3f eigen = new Eigen3f();
+ /**
+ * Plane
+ */
+ public final Plane plane = new Plane();
+ /**
+ * BoundingBox ray collision
+ */
+ public final float[] fWdU = new float[3];
+ public final float[] fAWdU = new float[3];
+ public final float[] fDdU = new float[3];
+ public final float[] fADdU = new float[3];
+ public final float[] fAWxDdU = new float[3];
+ /**
+ * Maximum tree depth .. 32 levels??
+ */
+ public final Spatial[] spatialStack = new Spatial[32];
+ public final float[] matrixWrite = new float[16];
+ /**
+ * BIHTree
+ */
+ public final float[] bihSwapTmp = new float[9];
+ public final ArrayList<BIHStackData> bihStack = new ArrayList<BIHStackData>();
+}
diff --git a/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java
new file mode 100644
index 0000000..6dfa6b8
--- /dev/null
+++ b/engine/src/core/com/jme3/util/blockparser/BlockLanguageParser.java
@@ -0,0 +1,92 @@
+package com.jme3.util.blockparser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BlockLanguageParser {
+
+ private Reader reader;
+ private ArrayList<Statement> statementStack = new ArrayList<Statement>();
+ private Statement lastStatement;
+ private int lineNumber = 1;
+
+ private BlockLanguageParser(){
+ }
+
+ private void reset(){
+ statementStack.clear();
+ statementStack.add(new Statement(0, "<root>"));
+ lastStatement = null;
+ lineNumber = 1;
+ }
+
+ private void pushStatement(StringBuilder buffer){
+ String content = buffer.toString().trim();
+ if (content.length() > 0){
+ // push last statement onto the list
+ lastStatement = new Statement(lineNumber, content);
+
+ Statement parent = statementStack.get(statementStack.size()-1);
+ parent.addStatement(lastStatement);
+
+ buffer.setLength(0);
+ }
+ }
+
+ private void load(InputStream in) throws IOException{
+ reset();
+
+ reader = new InputStreamReader(in);
+
+ StringBuilder buffer = new StringBuilder();
+ boolean insideComment = false;
+ char lastChar = '\0';
+
+ while (true){
+ int ci = reader.read();
+ char c = (char) ci;
+ if (c == '\r'){
+ continue;
+ }
+ if (insideComment && c == '\n'){
+ insideComment = false;
+ }else if (c == '/' && lastChar == '/'){
+ buffer.deleteCharAt(buffer.length()-1);
+ insideComment = true;
+ pushStatement(buffer);
+ lastChar = '\0';
+ }else if (!insideComment){
+ if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){
+ pushStatement(buffer);
+ lastChar = '\0';
+ if (c == '{'){
+ // push last statement onto the stack
+ statementStack.add(lastStatement);
+ continue;
+ }else if (c == '}'){
+ // pop statement from stack
+ statementStack.remove(statementStack.size()-1);
+ continue;
+ }else if (c == '\n'){
+ lineNumber++;
+ }else if (ci == -1){
+ break;
+ }
+ }else{
+ buffer.append(c);
+ lastChar = c;
+ }
+ }
+ }
+ }
+
+ public static List<Statement> parse(InputStream in) throws IOException {
+ BlockLanguageParser parser = new BlockLanguageParser();
+ parser.load(in);
+ return parser.statementStack.get(0).getContents();
+ }
+}
diff --git a/engine/src/core/com/jme3/util/blockparser/Statement.java b/engine/src/core/com/jme3/util/blockparser/Statement.java
new file mode 100644
index 0000000..d1309ad
--- /dev/null
+++ b/engine/src/core/com/jme3/util/blockparser/Statement.java
@@ -0,0 +1,61 @@
+package com.jme3.util.blockparser;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Statement {
+
+ private int lineNumber;
+ private String line;
+ private List<Statement> contents = new ArrayList<Statement>();
+
+ Statement(int lineNumber, String line) {
+ this.lineNumber = lineNumber;
+ this.line = line;
+ }
+
+ void addStatement(Statement statement){
+// if (contents == null){
+// contents = new ArrayList<Statement>();
+// }
+ contents.add(statement);
+ }
+
+ public int getLineNumber(){
+ return lineNumber;
+ }
+
+ public String getLine() {
+ return line;
+ }
+
+ public List<Statement> getContents() {
+ return contents;
+ }
+
+ private String getIndent(int indent){
+ return " ".substring(0, indent);
+ }
+
+ private String toString(int indent){
+ StringBuilder sb = new StringBuilder();
+ sb.append(getIndent(indent));
+ sb.append(line);
+ if (contents != null){
+ sb.append(" {\n");
+ for (Statement statement : contents){
+ sb.append(statement.toString(indent+4));
+ sb.append("\n");
+ }
+ sb.append(getIndent(indent));
+ sb.append("}");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString(){
+ return toString(0);
+ }
+
+}
diff --git a/engine/src/core/com/jme3/util/xml/SAXUtil.java b/engine/src/core/com/jme3/util/xml/SAXUtil.java
new file mode 100644
index 0000000..1ac4936
--- /dev/null
+++ b/engine/src/core/com/jme3/util/xml/SAXUtil.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2009-2010 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.util.xml;
+
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * Utility methods for parsing XML data using SAX.
+ */
+public final class SAXUtil {
+
+ /**
+ * Parses an integer from a string, if the string is null returns
+ * def.
+ *
+ * @param i
+ * @param def
+ * @return
+ * @throws SAXException
+ */
+ public static int parseInt(String i, int def) throws SAXException{
+ if (i == null)
+ return def;
+ else{
+ try {
+ return Integer.parseInt(i);
+ } catch (NumberFormatException ex){
+ throw new SAXException("Expected an integer, got '"+i+"'");
+ }
+ }
+ }
+
+ public static int parseInt(String i) throws SAXException{
+ if (i == null)
+ throw new SAXException("Expected an integer");
+ else{
+ try {
+ return Integer.parseInt(i);
+ } catch (NumberFormatException ex){
+ throw new SAXException("Expected an integer, got '"+i+"'");
+ }
+ }
+ }
+
+ public static float parseFloat(String f, float def) throws SAXException{
+ if (f == null)
+ return def;
+ else{
+ try {
+ return Float.parseFloat(f);
+ } catch (NumberFormatException ex){
+ throw new SAXException("Expected a decimal, got '"+f+"'");
+ }
+ }
+ }
+
+ public static float parseFloat(String f) throws SAXException{
+ if (f == null)
+ throw new SAXException("Expected a decimal");
+ else{
+ try {
+ return Float.parseFloat(f);
+ } catch (NumberFormatException ex){
+ throw new SAXException("Expected a decimal, got '"+f+"'");
+ }
+ }
+ }
+
+ public static boolean parseBool(String bool, boolean def) throws SAXException{
+ if (bool == null || bool.equals(""))
+ return def;
+ else
+ return Boolean.valueOf(bool);
+ //else
+ //else
+ // throw new SAXException("Expected a boolean, got'"+bool+"'");
+ }
+
+ public static String parseString(String str, String def){
+ if (str == null)
+ return def;
+ else
+ return str;
+ }
+
+ public static String parseString(String str) throws SAXException{
+ if (str == null)
+ throw new SAXException("Expected a string");
+ else
+ return str;
+ }
+
+ public static Vector3f parseVector3(Attributes attribs) throws SAXException{
+ float x = parseFloat(attribs.getValue("x"));
+ float y = parseFloat(attribs.getValue("y"));
+ float z = parseFloat(attribs.getValue("z"));
+ return new Vector3f(x,y,z);
+ }
+
+ public static ColorRGBA parseColor(Attributes attribs) throws SAXException{
+ float r = parseFloat(attribs.getValue("r"));
+ float g = parseFloat(attribs.getValue("g"));
+ float b = parseFloat(attribs.getValue("b"));
+ return new ColorRGBA(r, g, b, 1f);
+ }
+
+}