Compare commits

...

203 Commits

Author SHA1 Message Date
Stephen Gold
6402aad452 InstancedNode: protect the no-arg constructor and javadoc it 2020-06-14 02:06:00 -07:00
Trevor Flynn
e78f303a9d
Add ability to remove morph targets from mesh (#1378)
* Add ability to remove morph targets from mesh

* Add return values to removeMorphTarget functions

* Add null saftey

* Fix typo
2020-06-13 12:40:27 +04:30
kkolyan
dd0c169d62 avoid NegativeArraySizeException if input string is "\\#FFF#" 2020-06-09 08:50:24 -07:00
René Kraneis
32e8b68ea0
Adding getters/setters to AnimComposer (#1376)
* Add getCurrentAction

* Add removeCurrentAction (default layer)

* Add setTime (default layer)

* Add getTime (default layer)

* Improve documentation
2020-06-07 23:44:19 +04:30
kkolyan
eb7aab9704 Camera#contains javadoc updated 2020-06-05 10:30:58 -07:00
pspeed42
d29d8f5100 Fixing the angle ordering in the fromAngles() javadoc. 2020-05-23 17:39:07 -04:00
Stephen Gold
acc5cfa995 AppSettings: correct a typo in the javadoc 2020-05-20 12:11:42 -07:00
Stephen Gold
f34e660f03 AppSettings: change the "VSync" default from false to true 2020-05-17 23:04:48 -07:00
kkolyan
0f06c14c28 OBJLoader enhancement: named groups support 2020-05-16 13:21:19 -07:00
Stephen Gold
6ade1a027e AppSettings: change the gamma-correction default from false to true 2020-05-15 14:28:22 -07:00
Davis Rollman
30bddef8c0 Replaced Netbeans auto license with real license 2020-05-14 12:01:05 +02:00
ItsMike54
f808187142
Add Proper License to com.jme3.anim files (#1359)
* Update Weights.java

* Update SkinningControl.java

* Update SeparateJointModelTransform.java (#2)

* Update SeparateJointModelTransform.java

* Update MorphControl.java

* Update MatrixJointModelTransform.java

* Update Joint.java

* Update ArmatureMask.java

* Update Armature.java

* Update AnimationMask.java

* Update AnimTrack.java

* Update AnimComposer.java

* Update AnimClip.java

* Update Weighted.java

* Update Primitives.java

* Update JointModelTransform.java

* Update HasLocalTransform.java

* Update AnimMigrationUtils.java

* Update ContainsTweens.java

* Update LinearBlendSpace.java

* Update ClipAction.java

* Update BlendableAction.java

* Update BlendSpace.java

* Update BlendAction.java

* Update BaseAction.java

* Update Action.java

* Update AnimTrack.java (#3)

* Update AnimTrack.java

* Update ArmatureMask.java

* Update MatrixJointModelTransform.java

* Update SeparateJointModelTransform.java

* Update Weights.java

* Update Action.java

* Update BaseAction.java

* Update ContainsTweens.java

* Update SkinningControl.java

* Update AnimTrack.java (#6)

* Update AnimTrack.java

* Update BlendAction.java

* Update BlendSpace.java

* Update BlendableAction.java

* Update AnimMigrationUtils.java (#7)

* Update AnimMigrationUtils.java

* Update HasLocalTransform.java

* Update JointModelTransform.java

* Update Primitives.java

* Update Weighted.java

* Update ClipAction.java

* Update AnimInterpolator.java

* Update AnimInterpolators.java

* Update FrameInterpolator.java

* Update LinearBlendSpace.java

* Revert AnimTrack.java to original state.

* Delete AnimTrack.java

* Add files via upload
2020-05-09 06:19:11 -07:00
Stephen Gold
d6b0069407
restore the JOGL/JOAL hooks (#1368)
* restore the "hooks" for using JOGL/JOAL instead of LWJGL as the backend

* clarify the AppSettings javadoc related to JOGL and JOAL

* clarify some JmeDesktopSystem diagnostics related to JOGL and JOAL
2020-05-08 12:01:59 -07:00
Stephen Gold
c0692df207 Revert "Fixes issue #1336: AWTPanels forcing OpenGL 2.0 (Compat Profile)"
This reverts commit fa6295945109743f55602df592593b0f286da287.
2020-05-08 11:39:44 -07:00
Github Actions
036b08a385 [skip ci] update natives snapshot 2020-05-08 18:27:45 +00:00
Stephen Gold
407ebc9d5e gradle.properties: trivial delta to test the recent fix for issue #1365 2020-05-08 11:08:44 -07:00
Github Actions
f7352ffcfe [skip ci] update natives snapshot 2020-05-08 17:01:17 +00:00
Stephen Gold
599a0d5c56 possible fix for issue #1365 2020-05-08 09:42:19 -07:00
Stephen Gold
8aa50e9b48 Fix issue #1283 (collision-group filter not applied to CCD) 2020-04-26 23:11:45 -07:00
Stephen Gold
5ae9285903 add a test for issue #1283 2020-04-26 23:09:42 -07:00
Stephen Gold
bb6ac612ff make TestMorph (in jme3-examples) more user-friendly 2020-04-24 13:27:08 -07:00
Stephen Gold
9751e23cd7 Fix issue 1360: ArmatureDebugAppState causes OpenGLException 2020-04-24 10:36:50 -07:00
Stephen Gold
ceea055cc4 T key had to be pressed 2x before worked in jme3test.terrain (4 files) 2020-04-23 12:42:36 -07:00
Stephen Gold
d636049a6f improve initial camera orientations in jme3test.terrain (9 files) 2020-04-23 12:32:54 -07:00
Stephen Gold
917509a04e remove an unused empty file jme3test/light/DlsfError.java 2020-04-22 12:16:16 -07:00
rvandoosselaer
3fa85bc139 Set the minimum required OpenGL profiles for the geometry and tessellation shader test cases. Resolves #1357 2020-04-22 09:55:32 -07:00
jayfella
f3d81d11a1 Merge remote-tracking branch 'origin/master' 2020-04-22 09:39:32 +01:00
jayfella
37f1e7fabe Add methods to modify each channel (RGBA) that return 'this'. 2020-04-22 09:38:58 +01:00
jayfella
a057a6d74d Add method to convert RGBA255 colors to ColorRGBA 2020-04-22 09:32:55 +01:00
Stephen Gold
15e3acbe4d Fix issue #1353 (TestUseAfterFree causes AssertionError) 2020-04-21 17:40:03 -07:00
Stephen Gold
e3161f4622 remove an unused empty file jme3test/collision/Main.java 2020-04-20 21:45:00 -07:00
Stephen Gold
c9357b4db1 Fixes issue #1350 for getters (MorphTrack documentation) 2020-04-20 17:08:42 -07:00
Stephen Gold
2e56e1f24c Fixes issue #1350 (MorphTrack documentation) 2020-04-20 17:01:49 -07:00
Stephen Gold
32f71397c5 convert tabs to blanks in FixedScale100.frag 2020-04-20 16:51:24 -07:00
Stephen Gold
f6c467b495 improve javadoc in Triangle: "center" -> "centroid", "point" -> "vertex" 2020-04-20 16:44:44 -07:00
Stephen Gold
84c88709b5 correct a typographic error in TestUseAfterFree output 2020-04-20 15:40:47 -07:00
pspeed42
221acaadfe Removing the jme3-blender subproject and related references since it now lives in its
own repository: https://github.com/jMonkeyEngine-Contributions/BlenderLoader
2020-04-19 18:32:07 -04:00
pspeed42
94451c4d35 Moving all of the blender-related stuff under the jme3-blender subdirectory
to make it easier to move to its own repository and keep all of the history.
2020-04-19 17:53:24 -04:00
Stephen Gold
7212f9b2b2 improve format and rm trailing whitespace (10 files in com.jme3.opencl) 2020-04-18 18:08:32 -07:00
Stephen Gold
a5e0213b01 add some more missing javadoc (11 files in com.jme3.math) 2020-04-18 16:42:11 -07:00
Stephen Gold
a1dcbf9b06 correct javadoc errors in Matrix3f and Quaternion 2020-04-18 14:36:39 -07:00
Stephen Gold
a42103651c correct the javadoc of Matrix4f.set(float[][]) -- missing "not"! 2020-04-18 14:30:22 -07:00
Stephen Gold
cd7d497c79 clarify the javadoc of Quaternion.toString() "displaced"? 2020-04-18 14:22:35 -07:00
Stephen Gold
1e7c95cbb8 correct the javadoc of toString() methods (5 files in com.jme3.math) 2020-04-18 14:15:45 -07:00
Stephen Gold
ee910859ac javadoc corrections: Vector4f differs from Vector3f, has 4 components! 2020-04-18 13:52:09 -07:00
Stephen Gold
5a2499ccbe improve formatting & rm trailing whitespace (21 files in com.jme3.math) 2020-04-18 13:46:44 -07:00
Stephen Gold
4c62d5008e add missing javadoc tags to BufferUtils 2020-04-18 13:25:44 -07:00
Stephen Gold
4d81fc6035 add some missing javadoc (2 files in com.jme3.util) 2020-04-18 13:15:08 -07:00
Stephen Gold
fa62959451 Fixes issue #1336: AWTPanels forcing OpenGL 2.0 (Compat Profile) 2020-04-18 11:35:51 -07:00
Stephen Gold
607e068f76 FastMathTest: test the reversibility of coordinate transformations 2020-04-17 13:00:26 -07:00
Stephen Gold
bb7d2cab01 add missing javadoc tags to 10 files in the com.jme3.math package 2020-04-17 12:11:07 -07:00
Stephen Gold
6cbf62a3b5 resolve an unknown HTML tag in the ColorRGBA javadoc 2020-04-17 11:04:02 -07:00
Stephen Gold
03673ac1f7 add some missing javadoc (13 files in com.jme3.math) 2020-04-17 10:44:29 -07:00
Stephen Gold
32ab16e026 javadoc correction: Quaternion.negate() doesn't "invert" anything 2020-04-17 00:07:37 -07:00
Stephen Gold
84836c6260 don't refer to transforms as "matrices" in the Transform javadoc 2020-04-16 23:50:25 -07:00
Stephen Gold
851793cde6 improve formatting & rm trailing whitespace (5 files in com.jme3.scene) 2020-04-16 23:14:35 -07:00
Stephen Gold
bee3d36f16 remove the javadoc reference to the mythical USE_FAST_TRIG setting 2020-04-16 22:12:51 -07:00
Stephen Gold
50dc8349a1 remove all javadoc references to the mythical JmeException 2020-04-16 22:07:21 -07:00
Stephen Gold
e99c8a74a3 improve formatting and rm trailing whitespace (16 files in jme3-core) 2020-04-16 21:46:31 -07:00
Github Actions
f46a6fac11 [skip ci] update natives snapshot 2020-04-16 01:40:48 +00:00
Stephen Gold
64bfe42878 build with -Xlink:unchecked 2020-04-15 18:22:44 -07:00
Stephen Gold
1c3ed5122f suppress still more javac warnings about unchecked casts 2020-04-15 18:22:18 -07:00
Stephen Gold
70ae48af1e avoid using raw types 2020-04-15 18:18:58 -07:00
Stephen Gold
c7a734d590 Merge branch 'master' of https://github.com/jMonkeyEngine/jmonkeyengine 2020-04-15 17:44:22 -07:00
Stephen Gold
822bcd1300 suppress more javac warnings about unchecked casts 2020-04-15 17:33:59 -07:00
Github Actions
ae9854df2b [skip ci] update natives snapshot 2020-04-16 00:13:51 +00:00
Stephen Gold
88c9371a4d avoid unchecked casts due by premature casting of Class objects 2020-04-15 17:04:45 -07:00
Stephen Gold
297443ada4 suppress javac warnings about unchecked casts in read() and write() 2020-04-15 16:51:48 -07:00
Stephen Gold
60967b9f04 IndexBuffer: resolve a javadoc warning 2020-04-15 13:35:55 -07:00
Stephen Gold
a6f2c12700 jme3-terrain: handle 2 more ByteBuffer cases 2020-04-15 13:32:19 -07:00
ItsMike54
6c5611d3e2
Remove jogl (#1339)
* Remove JOGL

* Update build.gradle

* Update AppSettings.java

* Update settings.gradle

* Update TestMultipleApplications.java

* Update build.gradle

* Update JmeDesktopSystem.java

* Update AppSettings.java

* Update package-info.java

* Update TestApplet.java

* Update HelloOpenCL.java

* Update TestContextSwitching.java

* Update TestOpenCLLibraries.java

* Update TestVertexBufferSharing.java

* Update TestWriteToTexture.java

* Update TestMultiPostWater.java

Co-authored-by: Mike Burnett <mikejme4@gmail.com>
2020-04-15 12:55:50 -07:00
matthias plasser
89df7909d3
Fixes #1341: AudioBuffer.updateData only allows direct buffers (#1342)
* only allowing direct buffers now, see: https://hub.jmonkeyengine.org/t/solved-playing-audio-from-audiobuffer-in-audionode-causes-jre-to-die/43091/12

* style correction

Co-authored-by: Stephen Gold <sgold@sonic.net>
2020-04-15 12:33:39 -07:00
Jérôme
b92e7a0abe
Migrate TestSkeletonControlRefresh to new anim system (#1334)
* Migrate TestSkeletonControlRefresh to new anim system

Migrate SkeletonControl to new SkinningControl

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java

* Eleminate inner class

* Update TestSkeletonControlRefresh.java

* Update TestSkeletonControlRefresh.java
2020-04-13 16:04:08 -07:00
rvandoosselaer
26f393d09c Clean up the test case and add some more context. #1340 2020-04-13 16:02:06 -07:00
rvandoosselaer
2e1a8dcb52 Add a check to see if the NB_PROBES identifier is defined. Fixes #1340 2020-04-13 16:02:06 -07:00
rvandoosselaer
76210fe415 Add a check to see if the NB_PROBES identifier is defined. Fixes #1340 2020-04-13 16:02:06 -07:00
Jérôme
b48391399b Using Tweens methods instead of the inner class
Simplification using Tweens methods instead of NoLoopAction inner class
2020-04-13 16:01:27 -07:00
Jérôme
4c8e400a83
Migrate TestOgreAnim to new anim system (#1333)
* Migrate TestOgreAnim to new anim system

* Removing tabs

* Update TestOgreAnim.java
2020-04-10 18:09:36 -07:00
Riccardo Balbo
d109e4739e Fix last query not closed in profiler 2020-04-10 22:52:19 +02:00
Ali-RS
16a3e7630c
Fixed an issue with AndroidNativeImageLoader which was opening stream twice. (#1338) 2020-04-08 13:14:04 +04:30
Stephen Gold
b8287a2d0d correct some typographic errors in Billboard100.frag comments 2020-04-07 16:23:22 -07:00
Stephen Gold
fef5c101d8 elimiate incidental hard tabs from jme3-examples 2020-04-07 11:06:31 -07:00
Stephen Gold
51022fd521 elimiate incidental hard tabs from jme3-core 2020-04-07 10:51:33 -07:00
Jérôme
b613be35fa Removes tabs 2020-04-07 10:15:42 -07:00
Jérôme
2a0fa2554a Migrate TestOgreComplexAnim to new anim system
Lots of code that did nothing in old test (on feet, hand). Only eft hand anim with the bone anim in simpleUpdate works. So I rework the test to do how actionSquence work and manipulate Armature and Joint with ArmatureMask apply.
So I think there is a bug in ArmatureDebugger when apply PointSize in material. In the render view all points on armature debug have same size.
2020-04-07 10:15:42 -07:00
Jérôme
3385cdaa3f Migrate TestBlenderObjectAnim to new anim system 2020-04-06 23:32:33 -07:00
Jérôme
1433f98ad3 Migrate TestBlenderAnim to the new anim system
Migrate to the new animation system, migration of old Spatial object containing animation and adding method to list all animation name in AnimComposer.
2020-04-06 11:29:00 -07:00
Jérôme
11d3bc67f8 Migrate TestCutomAnim to the new Animation system 2020-04-06 11:28:11 -07:00
Jérôme
bd4691e06b Migrate TestSpatialAnim to the new Anim class
Remove all deprecated class and migrate to the new animation system.
2020-04-06 11:27:07 -07:00
Grabli66
fafe8a74fe Fixes error of loading assets 2020-04-06 11:25:39 -07:00
MeFisto94
efc1962092
Merge pull request #1320 from MeFisto94/fix-lwjgl3-double-stop
Fixes #1319 - LWJGL2: start(true) should not freeze when context could not be initialized
2020-04-04 20:15:52 +02:00
MeFisto94
989b2a66cf
Merge pull request #1322 from MeFisto94/fix-mesa-invalid-enum-ssbo
Fixes SSBO causing GL_INVALID_ENUM in Debug Mode
2020-04-04 20:05:23 +02:00
MeFisto94
0115616652 UBO: Re-order the caps detection for a more clean code. 2020-04-04 17:19:10 +02:00
MeFisto94
64797f9bff UBO: GL_INVALID_ENUM on Mesa 2020-04-04 16:19:56 +02:00
MeFisto94
21b7a71cf8 Fixes SSBO causing GL_INVALID_ENUM in Debug Mode 2020-04-04 15:41:36 +02:00
MeFisto94
8f8fc19043
Merge pull request #1321 from MeFisto94/fix-pbr-opengl2-part2
Fixing PBR on <= GLSL 130:
2020-04-04 14:15:57 +02:00
MeFisto94
646e6741b9 GLSLCompat: The Matrix3 Sub-Constructor is called mat3_sub and defers to the builtin mat3 if available. 2020-04-04 14:07:58 +02:00
MeFisto94
2276197a2b Fixing PBR on <= GLSL 130:
- GLSLCompat now implements mat3(mat4) and determinant() and inverse() for mat2 and mat3
 - PBR.glslib uses this functions again instead of one-off workarounds
2020-04-04 13:44:10 +02:00
MeFisto94
2379cfba77
Merge pull request #1276 from MeFisto94/fix-gldebug
Fixes #1272 - Improve GLDebugDesktop by reducing manual overrides
2020-04-04 03:09:19 +02:00
MeFisto94
df39677381 GLDebug: Throw an IllegalArgumentException instead of an IllegalStateException 2020-04-04 02:50:31 +02:00
MeFisto94
c198b1d546
Merge pull request #1318 from MeFisto94/fix-pbr-opengl2
Fixes #1304 - Don't use mat3() constructor to keep compatibility with…
2020-04-03 22:54:24 +02:00
MeFisto94
d19ae582a1 Fixes #1319 - LWJGL2: start(true) should not freeze when context could not be initialized 2020-04-03 12:09:15 +02:00
MeFisto94
461227bdef Fixes #1304 - Don't use mat3() constructor to keep compatibility with GLSL110 2020-04-02 23:07:33 +02:00
Riccardo Balbo
728a05c4f3 lambda 🎉 2020-04-02 14:56:49 +02:00
Ali-RS
0c6f240222
Check culling on the instanced geometries. (#1315) 2020-04-02 10:47:11 +04:30
MeFisto94
4d81d00f45 GLDebug: Throw an unchecked exception instead of returning null to enforce a crash when glGetError is not present. 2020-04-01 19:45:37 +02:00
MeFisto94
312fb5bb57 GLDebug: Use a method handle instead of a string comparison to increase the performance 2020-04-01 14:23:01 +02:00
Riccardo Balbo
7c01019f0c
Don't fail if there is no javadoc to commit. 2020-03-30 11:02:44 +02:00
Riccardo Balbo
f505bdeb21
Update checkout action to v2 2020-03-29 23:05:49 +02:00
Daniel Perano
ff58abca3d
Merge pull request #1306 from jMonkeyEngine/envcam-viewports
Expose ViewPorts
2020-03-22 18:47:49 -07:00
Toni Helenius
59793d4c06
Fix for resizing the window (issue 1191) (#1308)
* Listen for framebuffer size callbacks and set the resolution from that

* Also check the window size on the framebuffer size callback
2020-03-22 15:31:46 -07:00
Daniel Perano
0aa50a0f15 Expose ViewPorts
This commit allows EnvironmentCamera users to access the ViewPorts used
for rendering the snapshots. This allows incorporating screenspace and other
SceneProcessor-based effects into the snapshots as well as the scene geometry.
2020-03-16 00:28:44 -07:00
Stephen Gold
0252c1b623
README.md: add "Jumping Jack Flag" to game list 2020-03-06 09:24:07 -08:00
Stephen Gold
a407fc3224 main.yml: add Gradle-wrapper validation to several jobs 2020-03-04 15:21:15 -08:00
Toni Helenius
b93ea18fa2
Lwjgl3 restart input handle (#1268)
* Reinit inputs on context restart

* Added test issue from issue #1013

* Verify that the inputs are already initialized
2020-02-25 17:20:09 -08:00
Lou Hamersly
c1d359ca59
Fix #1289 Matrix3f/Matrix4f equals() should require an exact class match (#1302) 2020-02-25 15:14:03 -08:00
Stephen Gold
8e334aa756
BufferUtils: resolve issue #1288 (rewrite isDirect()) (#1299) 2020-02-24 08:40:02 -08:00
Github Actions
426969daeb [skip ci] update natives snapshot 2020-02-13 18:40:22 +00:00
Stephen Gold
f652591281
remove unnecessary casts and tests for null, discovered using NetBeans (#1285)
* remove unnecessary tests for null, discovered using NetBeans

* jme3-core: remove unnecessary casts

* jme3-examples: remove unnecessary casts

* jme3-vr: remove unnecessary casts

* jme-plugins: remove unnecessary casts

* jme3-terrain: remove unnecessary casts

* jme3-desktop: remove unnecessary casts

* jme3-android: remove unnecessary casts

* remove unnecessary casts, discovered using NetBeans

* TerrainQuad: back out one casting delta that's not a removal

* TerrainPatch: simplify generateLodEntropies() after removal of the cast

* TerrainPatch: simplify reIndexGeometry() after removal of the cast
2020-02-13 10:21:51 -08:00
MeFisto94
12481c08f6 Use a reflection-based approach to call checkError() after every call to the openGL API to reduce Code Duplication and increase Maintainability, while also fixing the regression caused by GLDebugDesktop extending from GLDebugES and thus making the Renderer think it is on mobile. 2020-02-12 23:56:06 +01:00
MeFisto94
ab96460853
Fix small things (#1287)
* JmeVersion: Don't throw a NullPointerException when version.properties can't be found.

* ImplHandler: Reduce Code-Duplication by using a multi-catch statement
2020-02-12 10:48:46 -08:00
Stephen Gold
f268d00222 Fixes issue #1286 2020-02-10 05:35:59 -08:00
Paul Speed
5584c93d4a
Merge pull request #1282 from stephengold/master
re-assign Simsilica LLC copyrights with Paul's permission (issue #1001)
2020-02-07 17:09:12 -05:00
MeFisto94
8219d7fc02
Add two convenience flags to AppSettings: GraphicsTrace and GraphicsTiming. (#1279) 2020-02-03 17:56:36 -08:00
Stephen Gold
dde0906963 re-assign Simsilica LLC copyrights with Paul's permission (issue #1001) 2020-02-02 23:27:43 -08:00
Github Actions
e3b44db4aa [skip ci] update natives snapshot 2020-02-03 07:22:28 +00:00
Riccardo Balbo
31476679be Fix openal-soft dead link 2020-02-03 08:04:02 +01:00
Riccardo Balbo
0244ab230b
Update link for stb_image.h 2020-02-03 07:54:38 +01:00
Stephen Gold
1a0b6ecac3
jme3-blender: remove TextureGeneratorWood and its dependencies (#1274) 2020-02-02 19:45:23 -08:00
MeFisto94
acbddc2763
Merge pull request #1252 from MeFisto94/add-opengl31-lwjgl
Fixes #1251 - Support OpenGL3.1 on LWJGL2 and LWJGL3
2020-02-01 14:29:51 +01:00
Github Actions
3c5dd5c168 [skip ci] update natives snapshot 2020-01-30 17:35:48 +00:00
Stephen Gold
0fd70b81c9
Add missing @Override annotations (#1270)
* jme3-core: add the missing @Override annotations

* jme3-desktop: add the missing @Override annotations

* jme3-bullet and jme3-jbullet: add the missing @Override annotations

* jme3-effects: add the missing @Override annotations

* jme3-terrain: add the missing @Override annotations

* jme3-examples: add the missing @Override annotations

* jme3-android: add the missing @Override annotations

* jme3-blender: add the missing @Override annotations

* jme3-ios: add the missing @Override annotations

* jme3-jogg and jme3-jogl: add the missing @Override annotations

* jme3-plugind: add the missing @Override annotations

* jme3-lwjgl and jme3-lwjgl3: add the missing @Override annotations

* jme3-networking: add the missing @Override annotations

* jme3-vr: add the missing @Override annotations
2020-01-30 09:16:47 -08:00
Toni Helenius
427ae0a28b
Added Override annotations, finals, some cleanup and better null checks (#1271)
* Added Override annotations, finals, some cleanup and better null checks

* More general null check

* Follow naming conventions
2020-01-29 11:05:12 -08:00
Ali-RS
2023440acf
Removed extra PostShadow Technique from PBRLighting.j3md (#1273) 2020-01-29 01:31:22 -08:00
Toni Helenius
867e46190e Issue 801 (#1269)
* Test case for issue #801

* Reset the context with the initial values
2020-01-28 10:35:17 -08:00
MeFisto94
eee37022f2 Fixes #1241 - Add a convenient Graphics Debug Option for the AppSettings (#1256) 2020-01-27 10:52:36 -08:00
MeFisto94
124ad35677 Fixes #1249 - Don't hang up when destroy is called multiple times (#1250) 2020-01-27 00:03:25 -08:00
Ryan McDonough
233bc6f0da PreShadow & PostShadow Support for PBRLighting.j3md (#1265)
* Update PBRLighting.j3md

* Create PreShadowPBR.frag

* Create PostShadowPBR.frag
2020-01-27 10:12:52 +03:30
Toni Helenius
ffd9cfcf25 Upgrade Gradle 4.10 -> 5.6.4 (#1238)
* Upgrade Gradle 4.10 -> 5.6.4

* Use Gradle wrapper
2020-01-23 23:17:42 -08:00
Paul Speed
81f9b9d92a
Merge pull request #1262 from MeFisto94/fix-terrain-picker
Fixes #1261 - Clone the Terrain Picker, so that loading a terrain from file still works (after cloning, the picker would have the wrong terrain quad instance)
2020-01-18 11:53:26 -05:00
MeFisto94
9b29e05968 Fixes #1261 - Clone the Terrain Picker, so that loading a terrain from file still works (after cloning, the picker would have the wrong terrain quad instance) 2020-01-18 10:53:58 +01:00
MeFisto94
933b0912e6
Merge pull request #1255 from MeFisto94/fix-bullet-natives
Fixes Bullet-Native Artifacts not containing natives when not building from cpp source and not using the build target.
2020-01-13 15:56:48 +01:00
MeFisto94
ec491575be Fixes Bullet-Native Artifacts not containing natives when not building from cpp source and not using the build target. 2020-01-12 16:55:06 +01:00
MeFisto94
04e7bed5e7 Fixes #1251 - Support OpenGL3.1 on LWJGL2 and LWJGL3 2020-01-12 15:22:26 +01:00
Stephen Gold
77c521fefa
README.md: add pixelapp's Leap to list of games 2020-01-11 11:44:00 -08:00
Stephen Gold
bc64238635 TestPBRLighting: fpp.setNumSamples() to facilitate study of issue #1246 2020-01-10 02:38:49 -08:00
Stephen Gold
3f59008566
README.md: add Nine Circles of Hell 2020-01-02 08:43:55 -08:00
Riccardo Balbo
b059c7c0dd
Fix #1236 non-lvalue cannot be out parameter 2019-12-30 18:41:54 +01:00
joliver82
68fb1afe5d GLRenderer.modifyTexture on GLES fix (#1235)
* Update GLImageFormats.java

* First implementation to solve the glTexSubImage issue

* Setting proper format to the image

* Removed the Image.convertToFormat method and its usage from GLRenderer.modifyTexture. Added a warning instead for the potential failing cases
Fixing the issue in JmeBatchRenderBackend.loadImage transforming it to RGBA8 if required
2019-12-22 21:23:31 -08:00
Paul Speed
b2ae269ede Updating the version.gradle to fix an issue with auto-versioning on the
master branch. (On master we want to default to the gradle.properties
version and not the latest tag for non-specific-commit tags.)
Updated gradle.properties to set master to be 3.4.0 for further development.
2019-12-21 21:55:47 -05:00
Paul Speed
5db3ac4fac Refactored how versions are auto-built to provide more normal versions
when building locally.
Normal auto-detected versions will be based on the base version parsed
from the most recent tag on the branch with a -SNAPSHOT appended.  If
the current commit is the tagged commit then it is used directly to
preserve backwards compatibility... but really that should be a CI
only option for most use-cases.
A new includeBranchInVersion option was added to allow the old behavior
of including the branch name in a munged version string for those
wanting to keep their experimental branch builds separate from their
normal master/version-branch builds.
2019-12-21 08:37:21 -05:00
Riccardo Balbo
124ef031d4
Remove "v" from version tag to maintain consistency with old releases 2019-12-17 14:30:42 +01:00
Stephen Gold
cdcf0512d9
com.jme3.scene.shape.Line: protect the no-argument constructor (#1234) 2019-12-14 21:12:00 -08:00
Stephen Gold
6b7dd5b325
AnimControl: correct javadoc for the no-arg constructor (#1233) 2019-12-11 23:43:05 -08:00
Stephen Gold
8d9d091576
Mesh: avoid NPE in getMorphTargets() when there are no targets (#1231) 2019-12-09 07:39:30 -08:00
Stephen Gold
98f6d326e1
re-publicize InstancedGeometry no-arg constructor, correct erroneous javadoc (#1230)
* re-publicize 3 no-arg constructors, correct/remove erroneous javadoc

* re-protect 2 no-arg constructors, restore javadoc
2019-12-04 12:01:40 -08:00
Github Actions
584bb79392 [skip ci] update natives snapshot 2019-12-03 05:23:37 +00:00
Stephen Gold
75001d8abd
protect 52 no-arg constructors in the jme3-bullet/jme3-jbullet libraries (#1229) 2019-12-02 21:01:11 -08:00
Stephen Gold
9d2d393fc3
protect 47 no-arg constructors in the jme3-core library (#1228) 2019-12-01 14:35:52 -08:00
Paul Speed
c73fd99dd6 Fixed the null child check to throw IllegalArgumentException instead of NullPointerException
because user code should never throw NullPointerException.
Also made trying to add a child to itself an error instead of a no-op.  Attempting
to do something like guiNode.attachChild(guiNode) will now throw an IllegalArgumentException.
2019-11-28 15:56:15 -05:00
Paul Speed
c23f28b51c Modified Line to keep its own start/end instances. This is less of a surprise if
a user later chooses to call updatePoints() after having created the line with
JME constants... since updatePoints() would actually call set() instead of just
replacing the references.  The constructor and updatePoints() should match and I chose
to err on the side of caution and make them both operate on local instances.
2019-11-26 04:08:16 -05:00
Paul Speed
45b1908906 Added an app state that will verify some of the JME constants (easy to expand later)
to see if they've changed in the current frame.  SimpleApplication will configure
this by default to use asserts but the application is free to change it to throw
regular exceptions or just log the error as desired.
2019-11-26 03:34:55 -05:00
Paul Speed
37bb494709 Fixing the default versioning to be compatible with continued development.
-SNAPSHOT releases should be based on real version numbers and not partial
prefixes or it makes it harder to continuing development on the branch.
2019-11-26 02:12:13 -05:00
joliver82
30df2f1b87 Android morph fix (#1221)
* Fixed MorphAnim.glsllib to compile on android and avoid enabling GL_VERTEX_PROGRAM_POINT_SIZE in android as it's not supported
2019-11-24 09:20:09 +03:30
Matthew Universe
48f28974f9 Closes #1210 Quaternion.lookAt now has a return value (#1223) 2019-11-23 10:43:00 -08:00
Riccardo Balbo
b07a11c9d4 Improve support for subimage copying. Allow to copy a part of an image to a texture2D. 2019-11-20 11:02:20 +01:00
joliver82
d935347bde Android buffer allocator implementation (#1214)
* Implemented basic android buffer allocator for newer wrapped buffer classes to be used instead of the default ReflectionAllocator
2019-11-19 08:39:43 +03:30
Riccardo Balbo
816ab99ac5 Fix version naming. Use version-branch-SNAPSHOT for local builds, add custom version name property. 2019-11-17 14:15:42 +01:00
Github Actions
e304c5bb6d [skip ci] update natives snapshot 2019-11-17 12:33:52 +00:00
Riccardo Balbo
128e079a22 Update MacOS build: removed 32 bit support (since deprecated by apple). Update bullet library to the current master branch (needed for compatibility patch with recent clang). 2019-11-17 13:13:57 +01:00
joliver82
663c9776e8 Android openGL ES 3 support (#1147)
* Update GLImageFormats.java

* Added basic support for openGL ES 3.0 and GLSL300 for android

Added instancing support for android which is core feature in GLES3.0

Added fix for shadows in android

* Fixed required types of GLES30.glDrawElementsInstanced

* Properly check GLES3 version and setting precision for shaders for all GLSL ES versions

* Added support for Opengl ES 3.1 and 3.2 and their shader versions
Added proper checkings for new OpenGL ES versions
Fixed some issues with shadow rendering in GLES 3.0 or better (strict type checking, OpenGL ES specific extensions and percision definition)
Added GLSL320 and GLSL300 to Unshaded and Lighting materials

* Added depth texture 24bit for OpenGL ES

* Added geometry shaders and tessellation to GLES 3.1 as extension and GLES 3.2 as core
Modified test materials to use GLSL310

* Added FB blit for android GLES30

* Added GlSL300 or better to all effects not able to run in GLSL100
Mods on Shadows.glsllib to try to fix android shadow rendering
Fixed ssaoBlur.frag that was using a reserved word
Fixed precision type missmatch from vertex to fragment shader

* Partial multisample support
Framebuffer MRT support

* Temporarily removed texture multisampling (not being able to compile using GLES31)

* Updated android.jar to api28
Enabled multisampling

* Better checking for GLES3

* Removed insert precision for all shaders. This avoided some random precision missmatch error but created rendering issues

* Removed border check for GLES, caused filtered shadows not to render on GLES2.0 devices

Added texture compare mode for GLES3.0 and better to be able to use sampler2DShadow

Modified base materials (Lighting and Unshaded) to use only GLSL100 for post shadow pass

* Corrected texture comparison function that were incorrectly changed

* Added precision to samplers for GLESSL300 also

* Created a subclass of Glsl150ShaderGenerator (Glsl300ShaderGenerator) to support ShaderNodes in GLES SL30 and included it's usage in DesktopAssetManager. Nowadays just copied and overwrote the version string. Needs a full review
Added image format RGB16F to be used as texture format only in GLES30 (cannot be used as FB format)
Added GLSL300 and/or GLSL310 to different materials

* Cleaned GLSL300 generator
Fixed shadows glsl lib removing use of GL_EXT_gpu_shaders5

* Fixed usag of textureCubeLod in fragment shader of envMapping which is unsupported in GLES2.0
Modified PBRLighting frag to properly compile on GLES3

* Added GLSL300 and 310 to PostShadowFilter material definition

* Make post shadow filter use GLSL100 for GLSL300
Set highp as default precision to fix glitched shadows caused because not enough precision

* Changed water_normalmap.dds to uncompressed RGB8 because DXT1 is mostly unsupported in GLES

* Fixed GLES30 compilation issues in Water shaders and added GLSL300 to LightScattering material. ** Not fully tested

* Added RGB8 dds formatted FullskiesSunset to be used in any platform. Previous file moved to _dtx1.dds

* Set GL_TEXTURE_MAX_LEVEL in GLES30 contexts fixing issue with not fully mipmapped images and PBR rendering

* Fixed compilation of jme3-android-examples and upgraded sdk version to use

* Fixed Geometry Shader and Tessellation Shaders in testdata package to properly compile on GLES320

* Added non-compressed textures to be able to run all examples on android

Enabled texture arrays and 3d textures on opengl es 3.0. Peding to implement wrappers to android gl functions (currently getting NullPointerException)

* More GLES30 functions included to properly support texture arrays, 3d textures and multisampling matching jme core code

* Fixed 3D texture tests for GLES30
Fixed texture 3D and texture array example shader compilation on GLES30
Fixed some random missing precision compilation errors on GLES30

* Fixed TestTextureArray for GLES30

* Some multisample additions and removal of GLES3 multisampling enable caps
Added RGBA16F as valid format for GLES32 and using it by default in env camera instead of RGB16F. kept fall back to RGB8

* added float and half float formats for gles30
added rgb10a2 format for gles30
fixed usage of glTexImage2D
removed EnvironmentCamera fallback image format as it's useless now

* Added more image formats for GLES30
Fixed rgb8 if having Caps.Rgba8

* Enabled RadialBlur for gles30

* Fixed luminance texture formats for gles

* Added more depth image formats for gles3

* Enabled multisampling in gles3

* Added GLES30 functions to GLDebugES
Reverted TestTexture3D and TestTextureArray to use RGB8 format
Updated EnvMapping100.frag to use EXT_shader_texture_lod in gles sl 100 if available

* Removed all aditional not used compressed dds files
Removed also Pond and rock png files previously used in TestTextureArray

* Removed compressed water_normalmap_dxt1.dds

* Changed GLES_30 and AndroidGL implementing GL2 to avoid duplicated code checking GL2 vs GLES30

* Added aditional checking to avoid gles30 calls from gles20 only devices
2019-11-16 08:24:30 +03:30
Ali-RS
7ae1ff23af
Fixed a typo 2019-11-09 15:54:27 +03:30
Riccardo Balbo
d0186664f2 Fix extraction for non-unix paths. 2019-11-05 16:18:56 +01:00
Stephen Gold
92b161f38e fix issue #1202 2019-11-02 10:52:17 -07:00
Riccardo Balbo
3217cdc74c Remove migration code 2019-11-02 11:37:36 +01:00
Thodoris Sotiropoulos
4d734ac297 Separate getNativesZipFile and getPrebuiltNatives. Improve code quality.
* Fix the task 'getPrebuiltNatives'

* Do not replace native if it's newer than zip
2019-11-02 11:32:24 +01:00
Github Actions
d8a42d0817 [skip ci] update natives snapshot 2019-10-31 22:51:55 +00:00
Riccardo Balbo
973d8be894 Hotfix expired certificate for macOS_SDK_headers_for_macOS_10.14 2019-10-31 22:51:43 +01:00
Riccardo Balbo
b571840bd0 Fix missing bullet natives when running tests from within the engine project. 2019-10-31 21:47:15 +01:00
Ali-RS
2c6161ecd8 Added AnimComposer.hasAnimClip() 2019-10-24 10:43:19 +03:30
Ali-RS
d4c9a9ad1e
Added Joint.getInitialTransform() (#1206) 2019-10-24 10:32:42 +03:30
Ali-RS
3b8665fe93
Fixed an issue with BlendableAction.setTransitionLength()... (#1205)
...which was not setting new length to transition tween and causing the new length to have no effect.
2019-10-23 13:43:56 +03:30
Ali-RS
5c75af1d38 AnimComposer: added getTime() and setTime() methods (#1201)
Resolves #1200
2019-10-13 10:52:40 -07:00
Github Actions
96b16884ce [skip ci] update natives snapshot 2019-10-10 10:20:50 +00:00
Riccardo Balbo
a4c694ba36
Trigger rebuild 2019-10-10 12:00:42 +02:00
Riccardo Balbo
69ef8efcce Github Actions 2019-10-10 11:38:21 +02:00
Riccardo Balbo
88e9c8482c Store prebuild libraries outside the source folder, download prebuilt libraries from bintray 2019-10-10 11:11:49 +02:00
Riccardo Balbo
8a6bd1947c Remove fixed structure for version names 2019-10-10 11:11:49 +02:00
Riccardo Balbo
273f65b651 Fix toolchain. Add way to select wich platform you want to build. 2019-10-10 11:11:49 +02:00
MeFisto94
88385ba647 Build Bullet for Linux ARM (Raspberry Pi) 2019-10-10 11:11:49 +02:00
Ali-RS
b1db497a00 Fix issue #1197: ClassCastException in LODGeomap.createMesh() (#1198) 2019-10-08 11:52:52 -07:00
Daniel Perano
f312608725 Fixed ClassCastException in RMI implementation. (#1195)
jME's RMI registry uses an internal SharedObject class to track
metadata about objects that have been shared. The problem is that when
you retrieve the shared object, the RMI implementation mistakenly
attempts to cast the SharedObject holder to the class of the actual
shared object. This PR fixes that bug by casting the actual shared
object instead of the SharedObject holder.
2019-10-06 10:25:31 +03:30
Ali-RS
f506e2a5a5 Updated mockito-core to v3.0.0 2019-09-23 12:01:55 +02:00
Ali-RS
a6a35b370f Fix issue #1176: mergedJavadoc task fails on java 11 2019-09-23 12:01:55 +02:00
appveyor
3b6a602f96 [ci skip] bullet: update windows natives 2019-09-21 21:39:01 +00:00
Stephen Gold
62faa9a873 post-release cleanup 2019-09-21 14:32:59 -07:00
appveyor
84d3e0615d [ci skip] bullet: update windows natives 2019-09-21 19:26:21 +00:00
Stephen Gold
bb32b882a0 gradle.properties: ready for 3.3.0-alpha5 build 2019-09-21 12:21:08 -07:00
Stephen Gold
2010c819cb travis: upload JARs and deploy from UPLOAD builds only 2019-09-21 12:20:40 -07:00
appveyor
4c8cd56c50 [ci skip] bullet: update windows natives 2019-09-21 16:24:26 +00:00
sgold
b57d8239b4 gradle.properties: ready for 3.3.0-alpha4 build 2019-09-21 09:19:17 -07:00
sgold
52fbc17f4f travis: compile Linux natives on openjdk8, upload JARs from oraclejdk8 2019-09-21 09:00:46 -07:00
appveyor
1f02d87793 [ci skip] bullet: update windows natives 2019-09-21 01:11:47 +00:00
1164 changed files with 17068 additions and 44134 deletions

93
.github/actions/tools/bintray.sh vendored Normal file
View File

@ -0,0 +1,93 @@
#!/bin/bash
# bintray_createPackage [REPO] [PACKAGE] [USER] [PASSWORD] [GIT REPO] [LICENSE]
function bintray_createPackage {
repo="$1"
package="$2"
user="$3"
password="$4"
srcrepo="$5"
license="$6"
repoUrl="https://api.bintray.com/packages/$repo"
if [ "`curl -u$user:$password -H Content-Type:application/json -H Accept:application/json \
--write-out %{http_code} --silent --output /dev/null -X GET \"$repoUrl/$package\"`" != "200" ];
then
if [ "$srcrepo" != "" -a "$license" != "" ];
then
echo "Package does not exist... create."
data="{
\"name\": \"${package}\",
\"labels\": [],
\"licenses\": [\"${license}\"],
\"vcs_url\": \"${srcrepo}\"
}"
curl -u$user:$password -H "Content-Type:application/json" -H "Accept:application/json" -X POST \
-d "${data}" "$repoUrl"
else
echo "Package does not exist... you need to specify a repo and license for it to be created."
fi
else
echo "The package already exists. Skip."
fi
}
# uploadFile file destination [REPO] "content" [PACKAGE] [USER] [PASSWORD] [SRCREPO] [LICENSE]
function bintray_uploadFile {
file="$1"
dest="$2"
echo "Upload $file to $dest"
repo="$3"
type="$4"
package="$5"
user="$6"
password="$7"
srcrepo="$8"
license="$9"
publish="${10}"
bintray_createPackage $repo $package $user $password $srcrepo $license
url="https://api.bintray.com/$type/$repo/$package/$dest"
if [ "$publish" = "true" ]; then url="$url;publish=1"; fi
curl -T "$file" -u$user:$password "$url"
}
function bintray_uploadAll {
path="$1"
destpath="$2"
repo="$3"
type="$4"
package="$5"
user="$6"
password="$7"
srcrepo="$8"
license="$9"
publish="${10}"
cdir="$PWD"
cd "$path"
files="`find . -type f -print`"
IFS="
"
set -f
for f in $files; do
destfile="$destpath/${f:2}"
bintray_uploadFile $f $destfile $repo $type $package $user $password $srcrepo $license $publish
done
set +f
unset IFS
cd "$cdir"
}

85
.github/actions/tools/uploadToMaven.sh vendored Normal file
View File

@ -0,0 +1,85 @@
#!/bin/bash
#############################################
#
# Usage
# uploadAllToMaven path/of/dist/maven https://api.bintray.com/maven/riccardo/sandbox-maven/ riccardo $BINTRAY_PASSWORD gitrepo license
# Note: gitrepo and license are needed only when uploading to bintray if you want to create missing packages automatically
# gitrepo must be a valid source repository
# license must be a license supported by bintray eg "BSD 3-Clause"
# or
# uploadAllToMaven path/of/dist/maven $GITHUB_PACKAGE_REPOSITORY user password
#
#############################################
root="`dirname ${BASH_SOURCE[0]}`"
source $root/bintray.sh
set -e
function uploadToMaven {
file="$1"
destfile="$2"
repourl="$3"
user="$4"
password="$5"
srcrepo="$6"
license="$7"
auth=""
if [ "$user" != "token" ];
then
echo "Upload with username $user and password"
auth="-u$user:$password"
else
echo "Upload with token"
auth="-H \"Authorization: token $password\""
fi
if [[ $repourl == https\:\/\/api.bintray.com\/* ]];
then
package="`dirname $destfile`"
version="`basename $package`"
package="`dirname $package`"
package="`basename $package`"
if [ "$user" = "" -o "$password" = "" ];
then
echo "Error! You need username and password to upload to bintray"
exit 1
fi
echo "Detected bintray"
bintrayRepo="${repourl/https\:\/\/api.bintray.com\/maven/}"
echo "Create package on $bintrayRepo"
bintray_createPackage $bintrayRepo $package $user $password $srcrepo $license
repourl="$repourl/$package"
fi
cmd="curl -T \"$file\" $auth \
\"$repourl/$destfile\" \
-vvv"
echo "Run $cmd"
eval "$cmd"
}
export -f uploadToMaven
function uploadAllToMaven {
path="$1"
cdir="$PWD"
cd "$path"
files="`find . \( -name "*.jar" -o -name "*.pom" \) -type f -print`"
IFS="
"
set -f
for art in $files; do
art="${art:2}"
uploadToMaven "$art" "$art" ${@:2}
done
set +f
unset IFS
cd "$cdir"
}

554
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,554 @@
######################################################################################
# JME CI/CD
######################################################################################
# Quick overview of what is going on in this script:
# - Build natives for android
# - Build natives for linux arm
# - Build natives for windows,mac,linux x86_64 and x86
# - Merge the natives, build the engine, create the zip release, maven artifacts, javadoc and native snapshot
# - (only when there is a change in the native code) Deploy the native snapshot to bintray
# - (only when building a release) Deploy everything else to github releases, github packet registry and bintray
# - (only when building a release) Update javadoc.jmonkeyengine.org
# Note:
# All the actions/upload-artifact and actions/download-artifact steps are used to pass
# stuff between jobs, github actions has some sort of storage that is local to the
# running workflow, we use it to store the result of each job since the filesystem
# is not maintained between jobs.
################# CONFIGURATIONS #####################################################
# >> Configure BINTRAY RELEASE & NATIVE SNAPSHOT
# Configure the following secrets/variables (customize the values with your own)
# BINTRAY_GENERIC_REPO=riccardoblsandbox/jmonkeyengine-files
# BINTRAY_MAVEN_REPO=riccardoblsandbox/jmonkeyengine
# BINTRAY_USER=riccardo
# BINTRAY_APIKEY=XXXXXX
# BINTRAY_LICENSE="BSD 3-Clause"
# >> Configure PACKAGE REGISTRY RELEASE
# Nothing to do here, everything is autoconfigured to work with the account/org that
# is running the build.
# >> Configure JAVADOC
# JAVADOC_GHPAGES_REPO="riccardoblsandbox/javadoc.jmonkeyengine.org.git"
# Generate a deloy key
# ssh-keygen -t rsa -b 4096 -C "actions@users.noreply.github.com" -f javadoc_deploy
# Set
# JAVADOC_GHPAGES_DEPLOY_PRIVKEY="......."
# In github repo -> Settings, use javadoc_deploy.pub as Deploy key with write access
######################################################################################
# Resources:
# - Github actions docs: https://help.github.com/en/articles/about-github-actions
# - Package registry docs: https://help.github.com/en/articles/about-github-package-registry
# - Official actions: https://github.com/actions
# - Community actions: https://github.com/sdras/awesome-actions
######################################################################################
# - Riccardo Balbo
######################################################################################
name: Build jMonkeyEngine
on:
push:
branches:
- master
- newbuild
- v3.3.*
- v3.2
- v3.2.*
pull_request:
release:
types: [published]
jobs:
# Builds the natives on linux arm
BuildLinuxArmNatives:
name: Build natives for linux (arm)
runs-on: ubuntu-18.04
container:
image: riccardoblb/buildenv-jme3:linuxArm
steps:
- name: Clone the repo
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Validate the Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build
run: |
# Build
# Note: since this is crossbuild we use the buildForPlatforms filter to tell
# the buildscript wich platforms it should build for.
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildForPlatforms=LinuxArm,LinuxArmHF,LinuxArm64 -PbuildNativeProjects=true \
:jme3-bullet-native:assemble
- name: Upload natives
uses: actions/upload-artifact@master
with:
name: linuxarm-natives
path: build/native
# Build the natives on android
BuildAndroidNatives:
name: Build natives for android
runs-on: ubuntu-18.04
container:
image: riccardoblb/buildenv-jme3:android
steps:
- name: Clone the repo
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Validate the Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build
run: |
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \
:jme3-android-native:assemble \
:jme3-bullet-native-android:assemble
- name: Upload natives
uses: actions/upload-artifact@master
with:
name: android-natives
path: build/native
# Build the natives
BuildNatives:
strategy:
fail-fast: true
matrix:
os: [ubuntu-18.04,windows-2019,macOS-latest]
jdk: [8.x.x]
include:
- os: ubuntu-18.04
osName: linux
- os: windows-2019
osName: windows
- os: macOS-latest
osName: mac
name: Build natives for ${{ matrix.osName }}
runs-on: ${{ matrix.os }}
steps:
- name: Clone the repo
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Prepare java environment
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.jdk }}
architecture: x64
- name: Validate the Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build Natives
shell: bash
env:
OS_NAME: ${{ matrix.osName }}
run: |
# Install dependencies
if [ "$OS_NAME" = "mac" ];
then
echo "Prepare mac"
elif [ "$OS_NAME" = "linux" ];
then
echo "Prepare linux"
sudo apt-get update
sudo apt-get install -y gcc-multilib g++-multilib
else
echo "Prepare windows"
fi
# Build
./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true -Dmaven.repo.local="$PWD/dist/maven" \
build \
:jme3-bullet-native:build
# Upload natives to be used later by the BuildJMonkey job
- name: Upload natives
uses: actions/upload-artifact@master
with:
name: ${{ matrix.osName }}-natives
path: build/native
# Build the engine, we only deploy from ubuntu-18.04 jdk8
BuildJMonkey:
needs: [BuildNatives,BuildAndroidNatives]
name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [ubuntu-18.04,windows-2019,macOS-latest]
jdk: [8.x.x,11.x.x]
include:
- os: ubuntu-18.04
osName: linux
deploy: true
- os: windows-2019
osName: windows
- os: macOS-latest
osName: mac
- jdk: 11.x.x
deploy: false
steps:
- name: Clone the repo
uses: actions/checkout@v2
with:
fetch-depth: 1
- name: Setup the java environment
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.jdk }}
architecture: x64
- name: Download natives for linux
uses: actions/download-artifact@master
with:
name: linux-natives
path: build/native
- name: Download natives for windows
uses: actions/download-artifact@master
with:
name: windows-natives
path: build/native
- name: Download natives for mac
uses: actions/download-artifact@master
with:
name: mac-natives
path: build/native
- name: Download natives for android
uses: actions/download-artifact@master
with:
name: android-natives
path: build/native
- name: Download natives for linux (arm)
uses: actions/download-artifact@master
with:
name: linuxarm-natives
path: build/native
- name: Validate the Gradle wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build Engine
shell: bash
run: |
# Build
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build
if [ "${{ matrix.deploy }}" = "true" ];
then
# We are going to need "zip"
sudo apt-get update
sudo apt-get install -y zip
# Create the zip release and the javadoc
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution
# We prepare the release for deploy
mkdir -p ./dist/release/
mv build/distributions/*.zip dist/release/
# Create the maven artifacts
mkdir -p ./dist/maven/
./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true install -Dmaven.repo.local="$PWD/dist/maven"
# Zip the natives into a single archive (we are going to use this to deploy native snapshots)
echo "Create native zip"
cdir="$PWD"
cd "build/native"
zip -r "$cdir/dist/jme3-natives.zip" *
cd "$cdir"
echo "Done"
fi
# Used later by DeploySnapshot
- name: Upload merged natives
if: matrix.deploy==true
uses: actions/upload-artifact@master
with:
name: natives
path: dist/jme3-natives.zip
# Upload maven artifacts to be used later by the deploy job
- name: Upload maven artifacts
if: matrix.deploy==true
uses: actions/upload-artifact@master
with:
name: maven
path: dist/maven
- name: Upload javadoc
if: matrix.deploy==true
uses: actions/upload-artifact@master
with:
name: javadoc
path: dist/javadoc
# Upload release archive to be used later by the deploy job
- name: Upload release
if: github.event_name == 'release' && matrix.deploy==true
uses: actions/upload-artifact@master
with:
name: release
path: dist/release
# This job deploys the native snapshot.
# The snapshot is downloaded when people build the engine without setting buildNativeProject
# this is useful for people that want to build only the java part and don't have
# all the stuff needed to compile natives.
DeploySnapshot:
needs: [BuildJMonkey]
name: "Deploy snapshot"
runs-on: ubuntu-18.04
if: github.event_name == 'push'
steps:
# We clone the repo manually, since we are going to push back a reference to the snapshot
- name: Clone the repo
run: |
branch="${GITHUB_REF//refs\/heads\//}"
if [ "$branch" != "" ];
then
git clone --single-branch --branch "$branch" https://github.com/${GITHUB_REPOSITORY}.git .
fi
- name: Download merged natives
uses: actions/download-artifact@master
with:
name: natives
path: dist/
- name: Deploy natives snapshot
run: |
source .github/actions/tools/bintray.sh
NATIVE_CHANGES="yes"
branch="${GITHUB_REF//refs\/heads\//}"
if [ "$branch" != "" ];
then
if [ -f "natives-snapshot.properties" ];
then
nativeSnapshot=`cat "natives-snapshot.properties"`
nativeSnapshot="${nativeSnapshot#*=}"
# We deploy ONLY if GITHUB_SHA (the current commit hash) is newer than $nativeSnapshot
if [ "`git rev-list --count $nativeSnapshot..$GITHUB_SHA`" = "0" ];
then
NATIVE_CHANGES=""
else
# We check if the native code changed.
echo "Detect changes"
NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet-native/)"
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-android-native/)"; fi
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet-native-android/)"; fi
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet/)"; fi
# The bulletUrl (in gradle.properties) might have changed.
if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- gradle.properties)"; fi
fi
fi
# We do nothing if there is no change
if [ "$NATIVE_CHANGES" = "" ];
then
echo "No changes, skip."
else
if [ "${{ secrets.BINTRAY_GENERIC_REPO }}" = "" ];
then
echo "Configure the following secrets to enable native snapshot deployment"
echo "BINTRAY_GENERIC_REPO, BINTRAY_USER, BINTRAY_APIKEY"
else
# Deploy snapshot
bintray_uploadFile dist/jme3-natives.zip \
$GITHUB_SHA/$GITHUB_SHA/jme3-natives.zip \
${{ secrets.BINTRAY_GENERIC_REPO }} "content" "natives" \
${{ secrets.BINTRAY_USER }} \
${{ secrets.BINTRAY_APIKEY }} \
"https://github.com/${GITHUB_REPOSITORY}" \
"${{ secrets.BINTRAY_LICENSE }}" "true"
# We reference the snapshot by writing its commit hash in natives-snapshot.properties
echo "natives.snapshot=$GITHUB_SHA" > natives-snapshot.properties
# We commit the updated natives-snapshot.properties
git config --global user.name "Github Actions"
git config --global user.email "actions@users.noreply.github.com"
git add natives-snapshot.properties
git commit -m "[skip ci] update natives snapshot"
# Pull rebase from the remote repo, just in case there was a push in the meantime
git pull -q --rebase
# We need to calculate the header for git authentication
header=$(echo -n "ad-m:${{ secrets.GITHUB_TOKEN }}" | base64)
# Push
(git -c http.extraheader="AUTHORIZATION: basic $header" push origin "$branch" || true)
fi
fi
fi
# This job deploys the release
DeployRelease:
needs: [BuildJMonkey]
name: Deploy Release
runs-on: ubuntu-18.04
if: github.event_name == 'release'
steps:
# We need to clone everything again for uploadToMaven.sh ...
- name: Clone the repo
uses: actions/checkout@v2
with:
fetch-depth: 1
# Download all the stuff...
- name: Download maven artifacts
uses: actions/download-artifact@master
with:
name: maven
path: dist/maven
- name: Download release
uses: actions/download-artifact@master
with:
name: release
path: dist/release
- name: Deploy to github releases
run: |
# We need to get the release id (yeah, it's not the same as the tag)
echo "${GITHUB_EVENT_PATH}"
cat ${GITHUB_EVENT_PATH}
releaseId=$(jq --raw-output '.release.id' ${GITHUB_EVENT_PATH})
# Now that we have the id, we just upload the release zip from before
echo "Upload to release $releaseId"
filename="$(ls dist/release/*.zip)"
url="https://uploads.github.com/repos/${GITHUB_REPOSITORY}/releases/$releaseId/assets?name=$(basename $filename)"
echo "Upload to $url"
curl -L \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/zip" \
--data-binary @"$filename" \
"$url"
- name: Deploy to bintray
run: |
source .github/actions/tools/uploadToMaven.sh
if [ "${{ secrets.BINTRAY_MAVEN_REPO }}" = "" ];
then
echo "Configure the following secrets to enable bintray deployment"
echo "BINTRAY_MAVEN_REPO, BINTRAY_USER, BINTRAY_APIKEY"
else
uploadAllToMaven dist/maven/ https://api.bintray.com/maven/${{ secrets.BINTRAY_MAVEN_REPO }} ${{ secrets.BINTRAY_USER }} ${{ secrets.BINTRAY_APIKEY }} "https://github.com/${GITHUB_REPOSITORY}" "${{ secrets.BINTRAY_LICENSE }}"
fi
# - name: Deploy to github package registry
# run: |
# source .github/actions/tools/uploadToMaven.sh
# registry="https://maven.pkg.github.com/$GITHUB_REPOSITORY"
# echo "Deploy to github package registry $registry"
# uploadAllToMaven dist/maven/ $registry "token" ${{ secrets.GITHUB_TOKEN }}
# Deploy the javadoc
DeployJavaDoc:
needs: [BuildJMonkey]
name: Deploy Javadoc
runs-on: ubuntu-18.04
if: github.event_name == 'release'
steps:
# We are going to need a deploy key for this, since we need
# to push to a different repo
- name: Set ssh key
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.JAVADOC_GHPAGES_DEPLOY_PRIVKEY }}" > $HOME/.ssh/deploy.key
chmod 600 $HOME/.ssh/deploy.key
# We clone the javadoc repo
- name: Clone gh-pages
run: |
branch="gh-pages"
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $HOME/.ssh/deploy.key"
git clone --single-branch --branch "$branch" git@github.com:${{ secrets.JAVADOC_GHPAGES_REPO }} .
# Download the javadoc in the new directory "newdoc"
- name: Download javadoc
uses: actions/download-artifact@master
with:
name: javadoc
path: newdoc
# The actual deploy
- name: Deploy to github pages
run: |
set -f
IFS=$'\n'
# Get the tag for this release
version="`if [[ $GITHUB_REF == refs\/tags* ]]; then echo ${GITHUB_REF//refs\/tags\//}; fi`"
# If there is no tag, then we do nothing.
if [ "$version" != "" ];
then
echo "Deploy as $version"
# Remove any older version of the javadoc for this tag
if [ -d "$version" ];then rm -Rf "$version"; fi
# Rename newdoc with the version name
mv newdoc "$version"
# if there isn't an index.txt we create one (we need this to list the versions)
if [ ! -f "index.txt" ]; then echo "" > index.txt ; fi
index="`cat index.txt`"
# Check if this version is already in index.txt
addNew=true
for v in $index;
do
if [ "$v" = "$version" ];
then
echo "$v" "$version"
addNew=false
break
fi
done
# If not, we add it to the beginning
if [ "$addNew" = "true" ];
then
echo -e "$version\n$index" > index.txt
index="`cat index.txt`"
fi
# Regenerate the pages
chmod +x make.sh
./make.sh
# Configure git to use the deploy key
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $HOME/.ssh/deploy.key"
# Commit the changes
git config --global user.name "Github Actions"
git config --global user.email "actions@users.noreply.github.com"
git add . || true
git commit -m "$version" || true
branch="gh-pages"
git push origin "$branch" --force || true
fi

12
.gitignore vendored
View File

@ -34,12 +34,6 @@
/jme3-android-native/src/native/jme_decode/STBI/
/jme3-android-native/src/native/jme_decode/Tremor/
/jme3-android-native/stb_image.h
!/jme3-bullet-native/libs/native/windows/x86_64/bulletjme.dll
!/jme3-bullet-native/libs/native/windows/x86/bulletjme.dll
!/jme3-bullet-native/libs/native/osx/x86/libbulletjme.dylib
!/jme3-bullet-native/libs/native/osx/x86_64/libbulletjme.dylib
!/jme3-bullet-native/libs/native/linux/x86/libbulletjme.so
!/jme3-bullet-native/libs/native/linux/x86_64/libbulletjme.so
/jme3-examples/private/
!/jme3-vr/src/main/resources/**/*.dylib
!/jme3-vr/src/main/resources/**/*.so
@ -47,4 +41,8 @@
!/jme3-vr/src/main/resources/**/*.dll
!/jme3-vr/src/main/resources/**/*.pdb
/buildMaven.bat
/private
.travis.yml
appveyor.yml
javadoc_deploy
javadoc_deploy.pub

View File

@ -1,99 +0,0 @@
language: java
branches:
only:
- master
- /^v3.3.*$/
- v3.2
- /^v3.2.*$/
matrix:
include:
- os: linux
jdk: openjdk8
dist: xenial
env: UPLOAD=true UPLOAD_NATIVE=true
- os: linux
jdk: openjdk11
dist: xenial
- os: osx
osx_image: xcode9.3
env: UPLOAD_NATIVE=true
- language: android
os: linux
dist: trusty
jdk: openjdk10
install:
- export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'
- echo y | sdkmanager "ndk-bundle" &> /dev/null
before_script:
- export ANDROID_NDK=$ANDROID_HOME/ndk-bundle
script:
- ./gradlew -PbuildNativeProjects=true jme3-android-native:assemble jme3-bullet-native-android:assemble
after_success:
- '[ "$TRAVIS_PULL_REQUEST" == "false" ] && ./private/upload_native.sh || :'
- '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && ./gradlew bintrayUpload || :'
addons:
ssh_known_hosts: github.com
hosts:
- travisci
hostname: travisci
apt:
packages:
- gcc-multilib
- g++-multilib
before_install:
- '[ -n "$UPLOAD" ] && git fetch --unshallow || :'
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
install:
- '[ -n "$UPLOAD_NATIVE" ] && ./gradlew -PbuildNativeProjects=true assemble -xjme3-android-native:assemble -xjme3-bullet-native-android:assemble || ./gradlew assemble -xjme3-android-native:assemble -xjme3-bullet-native-android:assemble'
script:
- ./gradlew check
after_success:
- '[ "$TRAVIS_PULL_REQUEST" == "false" ] && [ -n "$UPLOAD_NATIVE" ] && ./private/upload_native.sh || :'
- '[ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ -n "$UPLOAD" ] && ./gradlew bintrayUpload || :'
notifications:
slack:
on_success: change
on_failure: always
rooms:
secure: "PWEk4+VL986c3gAjWp12nqyifvxCjBqKoESG9d7zWh1uiTLadTHhZJRMdsye36FCpz/c/Jt7zCRO/5y7FaubQptnRrkrRfjp5f99MJRzQVXnUAM+y385qVkXKRKd/PLpM7XPm4AvjvxHCyvzX2wamRvul/TekaXKB9Ti5FCN87s="
before_deploy:
- ./gradlew createZipDistribution
- export RELEASE_DIST=$(ls build/distributions/*.zip)
deploy:
provider: releases
api_key:
secure: PuEsJd6juXBH29ByITW3ntSAyrwWs0IeFvXJ5Y2YlhojhSMtTwkoWeB6YmDJWP4fhzbajk4TQ1HlOX2IxJXSW/8ShOEIUlGXz9fHiST0dkSM+iRAUgC5enCLW5ITPTiem7eY9ZhS9miIam7ngce9jHNMh75PTzZrEJtezoALT9w=
file_glob: true
file: "${RELEASE_DIST}"
skip_cleanup: true
on:
repo: jMonkeyEngine/jmonkeyengine
tags: true
# before_install:
# required libs for android build tools
# sudo apt-get update
# sudo apt-get install -qq p7zip-full
# sudo apt-get install -qq --force-yes libgd2-xpm ia32-libs ia32-libs-multiarch
# newest Android NDK
# wget http://dl.google.com/android/ndk/android-ndk-r10c-linux-x86_64.bin -O ndk.bin
# 7z x ndk.bin -y > /dev/null
# export ANDROID_NDK=`pwd`/android-ndk-r10c

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
Copyright (c) 2009-2020 jMonkeyEngine
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of 'jMonkeyEngine' nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,7 +1,7 @@
jMonkeyEngine
jMonkeyEngine
=============
[![Build Status](https://travis-ci.org/jMonkeyEngine/jmonkeyengine.svg?branch=master)](https://travis-ci.org/jMonkeyEngine/jmonkeyengine)
[![Build Status](https://github.com/jMonkeyEngine/jmonkeyengine/workflows/Build%20jMonkeyEngine/badge.svg)](https://github.com/jMonkeyEngine/jmonkeyengine/actions)
jMonkeyEngine is a 3-D game engine for adventurous Java developers. Its open-source, cross-platform, and cutting-edge. 3.2.4 is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll release 3.2.x updates until the major 3.3 release arrives.
@ -20,6 +20,9 @@ The engine is used by several commercial game studios and computer-science cours
- [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/)
- [Skullstone](http://www.skullstonegame.com/)
- [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/)
- [Nine Circles of Hell (on Steam)](https://store.steampowered.com/app/1200600/Nine_Circles_of_Hell/)
- [Leap](https://gamejolt.com/games/leap/313308)
- [Jumping Jack Flag](http://timealias.bplaced.net/jack/)
## Getting started
@ -34,8 +37,8 @@ Note: The master branch on GitHub is a development version of the engine and is
- NetBeans Platform
- Gradle
Plus a bunch of awesome libraries & tight integrations like Bullet, Blender, NiftyGUI and other goodies.
Plus a bunch of awesome libraries & tight integrations like Bullet, NiftyGUI and other goodies.
### Documentation
Did you miss it? Don't sweat it, [here it is again](https://jmonkeyengine.github.io/wiki).

View File

@ -1,63 +0,0 @@
version: 1.0.{build}.{branch}
branches:
only:
- master
- v3.2
only_commits:
files:
- jme3-bullet-native/
- appveyor.yml
- gradle.properties
skip_tags: true
max_jobs: 1
clone_depth: 1
image: Visual Studio 2013
environment:
encrypted_f0a0b284e2e8_iv:
secure: aImQXs4g7zMXm1nWRvlh2wPK1UQvozS1fOVNthpyoEDFZ2FvBSdXqh5NPbGh44+F
encrypted_f0a0b284e2e8_key:
secure: Ek2lqC2e19qQDRRdlvnYyLFBq3TNj6YwKTAPuJ2VElJsxi9lQg+9ZP+VbP4kbHTx6Zaa++vtmOuxLZL7gdILrEEPa1Jix2BBLBfcxBUxe6w=
install:
- cmd: >-
set GRADLE_LOCK=C:\Users\appveyor\.gradle\caches\modules-2\modules-2.lock
if exist %GRADLE_LOCK% del %GRADLE_LOCK%
build_script:
- cmd: gradlew.bat -PbuildNativeProjects=true :jme3-bullet-native:assemble
cache:
- C:\Users\appveyor\.gradle\caches
- C:\Users\appveyor\.gradle\wrapper
- jme3-bullet-native\bullet3.zip -> gradle.properties
test: off
deploy: off
on_success:
- cmd: >-
if not defined encrypted_f0a0b284e2e8_key appveyor exit
openssl aes-256-cbc -K %encrypted_f0a0b284e2e8_key% -iv %encrypted_f0a0b284e2e8_iv% -in private\key.enc -out c:\users\appveyor\.ssh\id_rsa -d
git config --global user.email "appveyor"
git config --global user.name "appveyor"
git checkout -q %APPVEYOR_REPO_BRANCH%
git add -- jme3-bullet-native/libs/native/windows/
git commit -m "[ci skip] bullet: update windows natives"
git pull -q --rebase
git push git@github.com:jMonkeyEngine/jmonkeyengine.git

View File

@ -1,3 +1,6 @@
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
buildscript {
repositories {
google()
@ -6,12 +9,22 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
classpath 'me.tatarka:gradle-retrolambda:3.7.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'base'
apply from: file('version.gradle')
apply plugin: 'me.tatarka.retrolambda'
// This is applied to all sub projects
subprojects {
if(!project.name.equals('jme3-android-examples')) {
@ -102,8 +115,8 @@ task mergedJavadoc(type: Javadoc, description: 'Creates Javadoc from all the pro
source subprojects.collect {project ->
project.sourceSets*.allJava
}
// classpath = files(subprojects.collect {project ->
// project.sourceSets*.compileClasspath})
classpath = files(subprojects.collect {project ->
project.sourceSets*.compileClasspath})
// source {
// subprojects*.sourceSets*.main*.allSource
// }
@ -146,6 +159,62 @@ task configureAndroidNDK {
}
}
gradle.rootProject.ext.set("usePrebuildNatives", buildNativeProjects!="true");
if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") {
String rootPath = rootProject.projectDir.absolutePath
Properties nativesSnasphotProp = new Properties()
File nativesSnasphotPropF = new File("${rootPath}/natives-snapshot.properties");
if (nativesSnasphotPropF.exists()) {
nativesSnasphotPropF.withInputStream { nativesSnasphotProp.load(it) }
String nativesSnasphot = nativesSnasphotProp.getProperty("natives.snapshot");
String nativesUrl = PREBUILD_NATIVES_URL.replace('${natives.snapshot}', nativesSnasphot)
println "Use natives snapshot: " + nativesUrl
String nativesZipFile = "${rootPath}" + File.separator + "build" + File.separator + nativesSnasphot + "-natives.zip"
String nativesPath = "${rootPath}" + File.separator + "build" + File.separator + "native"
task getNativesZipFile {
outputs.file nativesZipFile
doFirst {
File target = file(nativesZipFile);
println("Download natives from " + nativesUrl + " to " + nativesZipFile);
target.getParentFile().mkdirs();
ant.get(src: nativesUrl, dest: target);
}
}
task extractPrebuiltNatives {
inputs.file nativesZipFile
outputs.dir nativesPath
dependsOn getNativesZipFile
doFirst {
for (File src : zipTree(nativesZipFile)) {
String srcRel = src.getAbsolutePath().substring((int) (nativesZipFile.length() + 1));
srcRel = srcRel.substring(srcRel.indexOf(File.separator) + 1);
File dest = new File(nativesPath + File.separator + srcRel);
boolean doCopy = !(dest.exists() && dest.lastModified() > src.lastModified())
if (doCopy) {
println("Copy " + src + " " + dest);
dest.getParentFile().mkdirs();
Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
}
}
}
assemble.dependsOn extractPrebuiltNatives
}
}
//class IncrementalReverseTask extends DefaultTask {
// @InputDirectory
// def File inputDir
@ -181,3 +250,14 @@ task configureAndroidNDK {
// enableAssertions = true // true by default
// }
//}
wrapper {
gradleVersion = '5.6.4'
}
retrolambda {
javaVersion JavaVersion.VERSION_1_7
incremental true
jvmArgs '-noverify'
}

View File

@ -1,7 +1,7 @@
apply plugin: 'com.android.application'
group = 'org.jmonkeyengine'
version = jmeVersion + '-' + jmeVersionTag
version = jmeFullVersion
sourceCompatibility = '1.6'

View File

@ -7,11 +7,17 @@ apply plugin: 'groovy'
apply plugin: 'maven'
group = 'org.jmonkeyengine'
version = jmePomVersion
version = jmeFullVersion
sourceCompatibility = '1.8'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
gradle.projectsEvaluated {
tasks.withType(JavaCompile) { // compile-time options:
options.compilerArgs << '-Xlint:unchecked'
}
}
repositories {
mavenCentral()
maven {
@ -25,7 +31,7 @@ repositories {
dependencies {
// Adding dependencies here will add the dependencies to each subproject.
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19'
testCompile group: 'org.mockito', name: 'mockito-core', version: '3.0.0'
testCompile group: 'org.easytesting', name: 'fest-assert-core', version: '2.0M10'
testCompile 'org.codehaus.groovy:groovy-all:2.5.8'
}
@ -51,9 +57,9 @@ jar {
javadoc {
failOnError = false
options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED
options.docTitle = "jMonkeyEngine ${jmeMainVersion} ${project.name} Javadoc"
options.windowTitle = "jMonkeyEngine ${jmeMainVersion} ${project.name} Javadoc"
options.header = "<b>jMonkeyEngine ${jmeMainVersion} ${project.name}</b>"
options.docTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc"
options.windowTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc"
options.header = "<b>jMonkeyEngine ${jmeFullVersion} ${project.name}</b>"
options.author = "true"
options.use = "true"
options.charSet = "UTF-8"

View File

@ -1,11 +1,16 @@
# Version number used for plugins, only 3 numbers (e.g. 3.1.3)
jmeVersion = 3.3.0
# Version used for application and settings folder, no spaces!
jmeMainVersion = 3.3
# Version addition: SNAPSHOT, alpha, beta, stable
jmeVersionTag = alpha3
# Increment this each time jmeVersionTag changes but jmeVersion stays the same
jmeVersionTagID = 3
# Version number: Major.Minor.SubMinor (e.g. 3.3.0)
jmeVersion = 3.4.0
# Leave empty to autogenerate
# (use -PjmeVersionName="myVersion" from commandline to specify a custom version name )
jmeVersionName =
# If true, the version name will contain the commit hash
useCommitHashAsVersionName = false
# Set to true if a non-master branch name should be included in the automatically
# generated version.
includeBranchInVersion = false
# specify if JavaDoc should be built
buildJavaDoc = true
@ -14,13 +19,17 @@ buildJavaDoc = true
buildNativeProjects = false
buildAndroidExamples = false
buildForPlatforms = Linux64,Linux32,Windows64,Windows32,Mac64
# Forcefully ignore prebuilt libraries
skipPrebuildLibraries=false
# Path to android NDK for building native libraries
#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7
ndkPath = /opt/android-ndk-r16b
# Path for downloading native Bullet
bulletUrl = https://github.com/bulletphysics/bullet3/archive/2.87.zip
bulletFolder = bullet3-2.87
# 2.89+ (circa 26 April 2020, to avoid jMonkeyEngine issue #1283)
bulletUrl = https://github.com/bulletphysics/bullet3/archive/cd8cf7521cbb8b7808126a6adebd47bb83ea166a.zip
bulletFolder = bullet3-cd8cf7521cbb8b7808126a6adebd47bb83ea166a
bulletZipFile = bullet3.zip
# POM settings
@ -39,3 +48,4 @@ POM_INCEPTION_YEAR=2009
bintray_user=
bintray_api_key=
PREBUILD_NATIVES_URL=https://dl.bintray.com/jmonkeyengine/files/${natives.snapshot}/jme3-natives.zip

Binary file not shown.

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

22
gradlew vendored
View File

@ -1,5 +1,21 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@ -109,8 +125,8 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

18
gradlew.bat vendored
View File

@ -1,3 +1,19 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

View File

@ -1,8 +1,8 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
compileSdkVersion 28
buildToolsVersion "28.0.3"
lintOptions {
// Fix nifty gui referencing "java.awt" package.
@ -13,7 +13,7 @@ android {
defaultConfig {
applicationId "org.jmonkeyengine.jme3androidexamples"
minSdkVersion 15 // Android 4.0.3 ICE CREAM SANDWICH
targetSdkVersion 22 // Android 5.1 LOLLIPOP
targetSdkVersion 28 // Android 9 PIE
versionCode 1
versionName "1.0" // TODO: from settings.gradle
}
@ -25,6 +25,11 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
java {
@ -42,7 +47,7 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:appcompat-v7:28.0.0'
compile project(':jme3-core')
compile project(':jme3-android')

View File

@ -62,15 +62,15 @@ public class JmeFragment extends AndroidHarnessFragment {
public void onCreate(Bundle savedInstanceState) {
Bundle bundle=getArguments();
appClass = bundle.getString(SELECTED_APP_CLASS);
appClass = bundle.getString(MainActivity.SELECTED_APP_CLASS);
// Log.d(this.getClass().getSimpleName(), "AppClass: " + appClass);
joystickEventsEnabled = bundle.getBoolean(ENABLE_JOYSTICK_EVENTS);
joystickEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS);
// Log.d(this.getClass().getSimpleName(), "JoystickEventsEnabled: " + joystickEventsEnabled);
keyEventsEnabled = bundle.getBoolean(ENABLE_KEY_EVENTS);
keyEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_KEY_EVENTS);
// Log.d(this.getClass().getSimpleName(), "KeyEventsEnabled: " + keyEventsEnabled);
mouseEventsEnabled = bundle.getBoolean(ENABLE_MOUSE_EVENTS);
mouseEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_MOUSE_EVENTS);
// Log.d(this.getClass().getSimpleName(), "MouseEventsEnabled: " + mouseEventsEnabled);
boolean verboseLogging = bundle.getBoolean(VERBOSE_LOGGING);
boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING);
// Log.d(this.getClass().getSimpleName(), "VerboseLogging: " + verboseLogging);
if (verboseLogging) {
// Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info)

View File

@ -286,7 +286,7 @@ public class MainActivity extends AppCompatActivity implements OnItemClickListen
private boolean checkClassType(String className) {
boolean include = true;
try {
Class<?> clazz = (Class<?>) Class.forName(className);
Class<?> clazz = Class.forName(className);
if (Application.class.isAssignableFrom(clazz)) {
Log.d(TAG, "Class " + className + " is a jME Application");
} else {

View File

@ -1,5 +1,5 @@
String tremorZipFile = "TremorAndroid.zip"
String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h'
String stbiUrl = 'https://raw.githubusercontent.com/jMonkeyEngine/stb/0224a44a10564a214595797b4c88323f79a5f934/stb_image.h'
// Working directories for the ndk build.
String decodeBuildDir = "${buildDir}" + File.separator + 'decode'
@ -8,7 +8,8 @@ String decodeBuildJniDir = decodeBuildDir + File.separator + 'jni'
String decodeBuildLibsDir = decodeBuildDir + File.separator + 'libs'
// Pre-compiled libs directory
String decodePreCompiledLibsDir = 'libs' + File.separator + 'decode'
def rootPath = rootProject.projectDir.absolutePath
String decodePreCompiledLibsDir = rootPath + File.separator + 'build' + File.separator + 'native' + File.separator + 'android' + File.separator + 'decode'
// jME Android Native source files path
String decodeSourceDir = 'src/native/jme_decode'

View File

@ -1,11 +1,11 @@
// OpenAL Soft r1.16
String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/e5016f814a265ed592a88acea95cf912c4bfdf12.zip'
String openALSoftUrl = 'https://github.com/jMonkeyEngine/openal-soft/archive/e5016f814a265ed592a88acea95cf912c4bfdf12.zip'
String openALSoftZipFile = 'OpenALSoft.zip'
// OpenAL Soft directory the download is extracted into
// Typically, the downloaded OpenAL Soft zip file will extract to a directory
// called "openal-soft"
String openALSoftFolder = 'openal-soft-e5016f8'
String openALSoftFolder = 'openal-soft-e5016f814a265ed592a88acea95cf912c4bfdf12'
//Working directories for the ndk build.
String openalsoftBuildDir = "${buildDir}" + File.separator + 'openalsoft'
@ -14,7 +14,8 @@ String openalsoftBuildJniDir = openalsoftBuildDir + File.separator + 'jni'
String openalsoftBuildLibsDir = openalsoftBuildDir + File.separator + 'libs'
//Pre-compiled libs directory
String openalsoftPreCompiledLibsDir = 'libs' + File.separator + 'openalsoft'
def rootPath = rootProject.projectDir.absolutePath
String openalsoftPreCompiledLibsDir = rootPath + File.separator + 'build' + File.separator + 'native' + File.separator + 'android' + File.separator + 'openalsoft'
// jME Android Native source files path
String openalsoftJmeAndroidPath = 'src/native/jme_openalsoft'

View File

@ -239,9 +239,8 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
// Create application instance
try {
if (app == null) {
@SuppressWarnings("unchecked")
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
app = clazz.newInstance();
Class clazz = Class.forName(appClass);
app = (LegacyApplication)clazz.newInstance();
}
app.setSettings(settings);
@ -362,6 +361,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
* @param dialog
* @param whichButton
*/
@Override
public void onClick(DialogInterface dialog, int whichButton) {
if (whichButton != -2) {
if (app != null) {
@ -473,6 +473,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
handler.setLevel(Level.ALL);
}
@Override
public void initialize() {
app.initialize();
if (handleExitHook) {
@ -488,10 +489,12 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
}
}
@Override
public void reshape(int width, int height) {
app.reshape(width, height);
}
@Override
public void update() {
app.update();
// call to remove the splash screen, if present.
@ -503,10 +506,12 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
}
}
@Override
public void requestClose(boolean esc) {
app.requestClose(esc);
}
@Override
public void destroy() {
if (app != null) {
app.destroy();
@ -516,6 +521,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
}
}
@Override
public void gainFocus() {
logger.fine("gainFocus");
if (view != null) {
@ -547,6 +553,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
}
}
@Override
public void loseFocus() {
logger.fine("loseFocus");
if (app != null) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -257,9 +257,8 @@ public class AndroidHarnessFragment extends Fragment implements
// Create application instance
try {
if (app == null) {
@SuppressWarnings("unchecked")
Class<? extends LegacyApplication> clazz = (Class<? extends LegacyApplication>) Class.forName(appClass);
app = clazz.newInstance();
Class clazz = Class.forName(appClass);
app = (LegacyApplication)clazz.newInstance();
}
app.setSettings(settings);
@ -684,10 +683,10 @@ public class AndroidHarnessFragment extends Fragment implements
if (viewWidth > viewHeight && viewWidth > maxResolutionDimension) {
// landscape
fixedSizeWidth = maxResolutionDimension;
fixedSizeHeight = (int)(maxResolutionDimension * ((float)viewHeight / (float)viewWidth));
fixedSizeHeight = (int)(maxResolutionDimension * (viewHeight / (float)viewWidth));
} else if (viewHeight > viewWidth && viewHeight > maxResolutionDimension) {
// portrait
fixedSizeWidth = (int)(maxResolutionDimension * ((float)viewWidth / (float)viewHeight));
fixedSizeWidth = (int)(maxResolutionDimension * (viewWidth / (float)viewHeight));
fixedSizeHeight = maxResolutionDimension;
} else if (viewWidth == viewHeight && viewWidth > maxResolutionDimension) {
fixedSizeWidth = maxResolutionDimension;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 jMonkeyEngine
* Copyright (c) 2014-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -82,6 +82,7 @@ import com.jme3.renderer.queue.RenderQueue;
public class DefaultAndroidProfiler implements AppProfiler {
private int androidApiLevel = Build.VERSION.SDK_INT;
@Override
public void appStep(AppStep appStep) {
if (androidApiLevel >= 18) {
switch(appStep) {
@ -140,6 +141,7 @@ public class DefaultAndroidProfiler implements AppProfiler {
}
@Override
public void vpStep(VpStep vpStep, ViewPort vp, RenderQueue.Bucket bucket) {
if (androidApiLevel >= 18) {
switch (vpStep) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -478,7 +478,7 @@ public class MjpegFileWriter {
baos.write(fcc);
baos.write(intBytes(swapInt(cb)));
for (int i = 0; i < ind.size(); i++) {
AVIIndex in = (AVIIndex) ind.get(i);
AVIIndex in = ind.get(i);
baos.write(in.toBytes());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -73,6 +73,7 @@ public class VideoRecorderAppState extends AbstractAppState {
private Application app;
private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setName("jME3 Video Processor");
@ -239,6 +240,7 @@ public class VideoRecorderAppState extends AbstractAppState {
renderer.readFrameBufferWithFormat(out, item.buffer, Image.Format.BGRA8);
executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
if (fastMode) {
item.data = item.buffer.array();
@ -260,6 +262,7 @@ public class VideoRecorderAppState extends AbstractAppState {
}
}
@Override
public void initialize(RenderManager rm, ViewPort viewPort) {
logger.log(Level.INFO, "initialize in VideoProcessor");
this.camera = viewPort.getCamera();
@ -275,13 +278,16 @@ public class VideoRecorderAppState extends AbstractAppState {
}
}
@Override
public void reshape(ViewPort vp, int w, int h) {
}
@Override
public boolean isInitialized() {
return this.isInitilized;
}
@Override
public void preFrame(float tpf) {
if (null == writer) {
try {
@ -292,14 +298,17 @@ public class VideoRecorderAppState extends AbstractAppState {
}
}
@Override
public void postQueue(RenderQueue rq) {
}
@Override
public void postFrame(FrameBuffer out) {
numFrames++;
addImage(renderManager.getRenderer(), out);
}
@Override
public void cleanup() {
logger.log(Level.INFO, "cleanup in VideoProcessor");
logger.log(Level.INFO, "VideoProcessor numFrames: {0}", numFrames);
@ -332,22 +341,27 @@ public class VideoRecorderAppState extends AbstractAppState {
this.ticks = 0;
}
@Override
public long getTime() {
return (long) (this.ticks * (1.0f / this.framerate) * 1000f);
}
@Override
public long getResolution() {
return 1000L;
}
@Override
public float getFrameRate() {
return this.framerate;
}
@Override
public float getTimePerFrame() {
return (float) (1.0f / this.framerate);
return 1.0f / this.framerate;
}
@Override
public void update() {
long time = System.currentTimeMillis();
long difference = time - lastTime;
@ -364,6 +378,7 @@ public class VideoRecorderAppState extends AbstractAppState {
this.ticks++;
}
@Override
public void reset() {
this.ticks = 0;
}

View File

@ -17,6 +17,7 @@ public class AndroidLocator implements AssetLocator {
public AndroidLocator() {
}
@Override
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}

View File

@ -10,44 +10,64 @@ public final class AndroidAL implements AL {
public AndroidAL() {
}
@Override
public native String alGetString(int parameter);
@Override
public native int alGenSources();
@Override
public native int alGetError();
@Override
public native void alDeleteSources(int numSources, IntBuffer sources);
@Override
public native void alGenBuffers(int numBuffers, IntBuffer buffers);
@Override
public native void alDeleteBuffers(int numBuffers, IntBuffer buffers);
@Override
public native void alSourceStop(int source);
@Override
public native void alSourcei(int source, int param, int value);
@Override
public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency);
@Override
public native void alSourcePlay(int source);
@Override
public native void alSourcePause(int source);
@Override
public native void alSourcef(int source, int param, float value);
@Override
public native void alSource3f(int source, int param, float value1, float value2, float value3);
@Override
public native int alGetSourcei(int source, int param);
@Override
public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers);
@Override
public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers);
@Override
public native void alListener(int param, FloatBuffer data);
@Override
public native void alListenerf(int param, float value);
@Override
public native void alListener3f(int param, float value1, float value2, float value3);
@Override
public native void alSource3i(int source, int param, int value1, int value2, int value3);
}

View File

@ -12,19 +12,27 @@ public final class AndroidALC implements ALC {
public AndroidALC() {
}
@Override
public native void createALC();
@Override
public native void destroyALC();
@Override
public native boolean isCreated();
@Override
public native String alcGetString(int parameter);
@Override
public native boolean alcIsExtensionPresent(String extension);
@Override
public native void alcGetInteger(int param, IntBuffer buffer, int size);
@Override
public native void alcDevicePauseSOFT();
@Override
public native void alcDeviceResumeSOFT();
}

View File

@ -8,25 +8,36 @@ public class AndroidEFX implements EFX {
public AndroidEFX() {
}
@Override
public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers);
@Override
public native void alGenEffects(int numEffects, IntBuffer buffers);
@Override
public native void alEffecti(int effect, int param, int value);
@Override
public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value);
@Override
public native void alDeleteEffects(int numEffects, IntBuffer buffers);
@Override
public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers);
@Override
public native void alGenFilters(int numFilters, IntBuffer buffers);
@Override
public native void alFilteri(int filter, int param, int value);
@Override
public native void alFilterf(int filter, int param, float value);
@Override
public native void alDeleteFilters(int numFilters, IntBuffer buffers);
@Override
public native void alEffectf(int effect, int param, float value);
}

View File

@ -46,6 +46,7 @@ public class NativeVorbisLoader implements AssetLoader {
throw new IOException("Not supported for audio streams");
}
@Override
public void setTime(float time) {
try {
file.seekTime(time);

View File

@ -113,7 +113,7 @@ public class AndroidJoystickJoyInput14 {
joysticks.clear();
joystickIndex.clear();
ArrayList gameControllerDeviceIds = new ArrayList();
ArrayList<Integer> gameControllerDeviceIds = new ArrayList<>();
int[] deviceIds = InputDevice.getDeviceIds();
for (int deviceId : deviceIds) {
InputDevice dev = InputDevice.getDevice(deviceId);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -561,7 +561,7 @@ public class AndroidSensorJoyInput implements SensorEventListener {
}
}
}
} else if (sensorData != null) {
} else {
if (!sensorData.haveData) {
sensorData.haveData = true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -131,8 +131,8 @@ public class AndroidTouchInput implements TouchInput {
// view width and height are 0 until the view is displayed on the screen
if (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) {
scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth();
scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight();
scaleX = settings.getWidth() / (float)androidInput.getView().getWidth();
scaleY = settings.getHeight() / (float)androidInput.getView().getHeight();
}
logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}",
new Object[]{scaleX, scaleY});

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -87,7 +87,7 @@ public class TouchEventPool {
TouchEvent evt = null;
int curSize = eventPool.size();
while (curSize > 0) {
evt = (TouchEvent)eventPool.pop();
evt = eventPool.pop();
if (evt.isConsumed()) {
break;
} else {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2015 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -34,6 +34,7 @@ package com.jme3.renderer.android;
import android.opengl.*;
import com.jme3.renderer.RendererException;
import com.jme3.renderer.opengl.*;
import com.jme3.util.BufferUtils;
import java.nio.Buffer;
import java.nio.ByteBuffer;
@ -41,8 +42,11 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class AndroidGL implements GL, GLExt, GLFbo {
public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo {
IntBuffer tmpBuff = BufferUtils.createIntBuffer(1);
@Override
public void resetStats() {
}
@ -83,10 +87,12 @@ public class AndroidGL implements GL, GLExt, GLFbo {
}
}
@Override
public void glActiveTexture(int texture) {
GLES20.glActiveTexture(texture);
}
@Override
public void glAttachShader(int program, int shader) {
GLES20.glAttachShader(program, shader);
}
@ -96,144 +102,179 @@ public class AndroidGL implements GL, GLExt, GLFbo {
GLES30.glBeginQuery(target, query);
}
@Override
public void glBindBuffer(int target, int buffer) {
GLES20.glBindBuffer(target, buffer);
}
@Override
public void glBindTexture(int target, int texture) {
GLES20.glBindTexture(target, texture);
}
@Override
public void glBlendFunc(int sfactor, int dfactor) {
GLES20.glBlendFunc(sfactor, dfactor);
}
@Override
public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) {
GLES20.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha);
}
@Override
public void glBufferData(int target, FloatBuffer data, int usage) {
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
}
@Override
public void glBufferData(int target, ShortBuffer data, int usage) {
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
}
@Override
public void glBufferData(int target, ByteBuffer data, int usage) {
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
}
@Override
public void glBufferData(int target, long data_size, int usage) {
GLES20.glBufferData(target, (int) data_size, null, usage);
}
@Override
public void glBufferSubData(int target, long offset, FloatBuffer data) {
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
}
@Override
public void glBufferSubData(int target, long offset, ShortBuffer data) {
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
}
@Override
public void glBufferSubData(int target, long offset, ByteBuffer data) {
GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data);
}
@Override
public void glGetBufferSubData(int target, long offset, ByteBuffer data) {
throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData");
}
@Override
public void glClear(int mask) {
GLES20.glClear(mask);
}
@Override
public void glClearColor(float red, float green, float blue, float alpha) {
GLES20.glClearColor(red, green, blue, alpha);
}
@Override
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
GLES20.glColorMask(red, green, blue, alpha);
}
@Override
public void glCompileShader(int shader) {
GLES20.glCompileShader(shader);
}
@Override
public void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, ByteBuffer data) {
GLES20.glCompressedTexImage2D(target, level, internalformat, width, height, 0, getLimitBytes(data), data);
}
@Override
public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data) {
GLES20.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, getLimitBytes(data), data);
}
@Override
public int glCreateProgram() {
return GLES20.glCreateProgram();
}
@Override
public int glCreateShader(int shaderType) {
return GLES20.glCreateShader(shaderType);
}
@Override
public void glCullFace(int mode) {
GLES20.glCullFace(mode);
}
@Override
public void glDeleteBuffers(IntBuffer buffers) {
checkLimit(buffers);
GLES20.glDeleteBuffers(buffers.limit(), buffers);
}
@Override
public void glDeleteProgram(int program) {
GLES20.glDeleteProgram(program);
}
@Override
public void glDeleteShader(int shader) {
GLES20.glDeleteShader(shader);
}
@Override
public void glDeleteTextures(IntBuffer textures) {
checkLimit(textures);
GLES20.glDeleteTextures(textures.limit(), textures);
}
@Override
public void glDepthFunc(int func) {
GLES20.glDepthFunc(func);
}
@Override
public void glDepthMask(boolean flag) {
GLES20.glDepthMask(flag);
}
@Override
public void glDepthRange(double nearVal, double farVal) {
GLES20.glDepthRangef((float)nearVal, (float)farVal);
}
@Override
public void glDetachShader(int program, int shader) {
GLES20.glDetachShader(program, shader);
}
@Override
public void glDisable(int cap) {
GLES20.glDisable(cap);
}
@Override
public void glDisableVertexAttribArray(int index) {
GLES20.glDisableVertexAttribArray(index);
}
@Override
public void glDrawArrays(int mode, int first, int count) {
GLES20.glDrawArrays(mode, first, count);
}
@Override
public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) {
GLES20.glDrawElements(mode, count, type, (int)indices);
}
@Override
public void glEnable(int cap) {
GLES20.glEnable(cap);
}
@Override
public void glEnableVertexAttribArray(int index) {
GLES20.glEnableVertexAttribArray(index);
}
@ -243,11 +284,13 @@ public class AndroidGL implements GL, GLExt, GLFbo {
GLES30.glEndQuery(target);
}
@Override
public void glGenBuffers(IntBuffer buffers) {
checkLimit(buffers);
GLES20.glGenBuffers(buffers.limit(), buffers);
}
@Override
public void glGenTextures(IntBuffer textures) {
checkLimit(textures);
GLES20.glGenTextures(textures.limit(), textures);
@ -258,29 +301,35 @@ public class AndroidGL implements GL, GLExt, GLFbo {
GLES30.glGenQueries(num, buff);
}
@Override
public int glGetAttribLocation(int program, String name) {
return GLES20.glGetAttribLocation(program, name);
}
@Override
public void glGetBoolean(int pname, ByteBuffer params) {
// GLES20.glGetBoolean(pname, params);
throw new UnsupportedOperationException("Today is not a good day for this");
}
@Override
public int glGetError() {
return GLES20.glGetError();
}
@Override
public void glGetInteger(int pname, IntBuffer params) {
checkLimit(params);
GLES20.glGetIntegerv(pname, params);
}
@Override
public void glGetProgram(int program, int pname, IntBuffer params) {
checkLimit(params);
GLES20.glGetProgramiv(program, pname, params);
}
@Override
public String glGetProgramInfoLog(int program, int maxLength) {
return GLES20.glGetProgramInfoLog(program);
}
@ -300,51 +349,63 @@ public class AndroidGL implements GL, GLExt, GLFbo {
return buff.get(0);
}
@Override
public void glGetShader(int shader, int pname, IntBuffer params) {
checkLimit(params);
GLES20.glGetShaderiv(shader, pname, params);
}
@Override
public String glGetShaderInfoLog(int shader, int maxLength) {
return GLES20.glGetShaderInfoLog(shader);
}
@Override
public String glGetString(int name) {
return GLES20.glGetString(name);
}
@Override
public int glGetUniformLocation(int program, String name) {
return GLES20.glGetUniformLocation(program, name);
}
@Override
public boolean glIsEnabled(int cap) {
return GLES20.glIsEnabled(cap);
}
@Override
public void glLineWidth(float width) {
GLES20.glLineWidth(width);
}
@Override
public void glLinkProgram(int program) {
GLES20.glLinkProgram(program);
}
@Override
public void glPixelStorei(int pname, int param) {
GLES20.glPixelStorei(pname, param);
}
@Override
public void glPolygonOffset(float factor, float units) {
GLES20.glPolygonOffset(factor, units);
}
@Override
public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) {
GLES20.glReadPixels(x, y, width, height, format, type, data);
}
@Override
public void glScissor(int x, int y, int width, int height) {
GLES20.glScissor(x, y, width, height);
}
@Override
public void glShaderSource(int shader, String[] string, IntBuffer length) {
if (string.length != 1) {
throw new UnsupportedOperationException("Today is not a good day");
@ -352,186 +413,231 @@ public class AndroidGL implements GL, GLExt, GLFbo {
GLES20.glShaderSource(shader, string[0]);
}
@Override
public void glStencilFuncSeparate(int face, int func, int ref, int mask) {
GLES20.glStencilFuncSeparate(face, func, ref, mask);
}
@Override
public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
GLES20.glStencilOpSeparate(face, sfail, dpfail, dppass);
}
@Override
public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) {
GLES20.glTexImage2D(target, level, format, width, height, 0, format, type, data);
GLES20.glTexImage2D(target, level, internalFormat, width, height, 0, format, type, data);
}
@Override
public void glTexParameterf(int target, int pname, float param) {
GLES20.glTexParameterf(target, pname, param);
}
@Override
public void glTexParameteri(int target, int pname, int param) {
GLES20.glTexParameteri(target, pname, param);
}
@Override
public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) {
GLES20.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data);
}
@Override
public void glUniform1(int location, FloatBuffer value) {
GLES20.glUniform1fv(location, getLimitCount(value, 1), value);
}
@Override
public void glUniform1(int location, IntBuffer value) {
GLES20.glUniform1iv(location, getLimitCount(value, 1), value);
}
@Override
public void glUniform1f(int location, float v0) {
GLES20.glUniform1f(location, v0);
}
@Override
public void glUniform1i(int location, int v0) {
GLES20.glUniform1i(location, v0);
}
@Override
public void glUniform2(int location, IntBuffer value) {
GLES20.glUniform2iv(location, getLimitCount(value, 2), value);
}
@Override
public void glUniform2(int location, FloatBuffer value) {
GLES20.glUniform2fv(location, getLimitCount(value, 2), value);
}
@Override
public void glUniform2f(int location, float v0, float v1) {
GLES20.glUniform2f(location, v0, v1);
}
@Override
public void glUniform3(int location, IntBuffer value) {
GLES20.glUniform3iv(location, getLimitCount(value, 3), value);
}
@Override
public void glUniform3(int location, FloatBuffer value) {
GLES20.glUniform3fv(location, getLimitCount(value, 3), value);
}
@Override
public void glUniform3f(int location, float v0, float v1, float v2) {
GLES20.glUniform3f(location, v0, v1, v2);
}
@Override
public void glUniform4(int location, FloatBuffer value) {
GLES20.glUniform4fv(location, getLimitCount(value, 4), value);
}
@Override
public void glUniform4(int location, IntBuffer value) {
GLES20.glUniform4iv(location, getLimitCount(value, 4), value);
}
@Override
public void glUniform4f(int location, float v0, float v1, float v2, float v3) {
GLES20.glUniform4f(location, v0, v1, v2, v3);
}
@Override
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GLES20.glUniformMatrix3fv(location, getLimitCount(value, 3 * 3), transpose, value);
}
@Override
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
GLES20.glUniformMatrix4fv(location, getLimitCount(value, 4 * 4), transpose, value);
}
@Override
public void glUseProgram(int program) {
GLES20.glUseProgram(program);
}
@Override
public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GLES20.glVertexAttribPointer(index, size, type, normalized, stride, (int)pointer);
}
@Override
public void glViewport(int x, int y, int width, int height) {
GLES20.glViewport(x, y, width, height);
}
@Override
public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
throw new UnsupportedOperationException("FBO blit not available on Android");
GLES30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
@Override
public void glBufferData(int target, IntBuffer data, int usage) {
GLES20.glBufferData(target, getLimitBytes(data), data, usage);
}
@Override
public void glBufferSubData(int target, long offset, IntBuffer data) {
GLES20.glBufferSubData(target, (int)offset, getLimitBytes(data), data);
}
@Override
public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) {
throw new UnsupportedOperationException("Instancing not available on Android");
GLES30.glDrawArraysInstanced(mode, first, count, primcount);
}
@Override
public void glDrawBuffers(IntBuffer bufs) {
throw new UnsupportedOperationException("MRT not available on Android");
GLES30.glDrawBuffers(bufs.limit(), bufs);
}
@Override
public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) {
throw new UnsupportedOperationException("Instancing not available on Android");
GLES30.glDrawElementsInstanced(mode, indices_count, type, (int)indices_buffer_offset, primcount);
}
@Override
public void glGetMultisample(int pname, int index, FloatBuffer val) {
throw new UnsupportedOperationException("Multisample renderbuffers not available on Android");
GLES31.glGetMultisamplefv(pname, index, val);
}
@Override
public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) {
throw new UnsupportedOperationException("Multisample renderbuffers not available on Android");
GLES30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
}
@Override
public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) {
throw new UnsupportedOperationException("Multisample textures not available on Android");
GLES31.glTexStorage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations);
}
@Override
public void glVertexAttribDivisorARB(int index, int divisor) {
throw new UnsupportedOperationException("Instancing not available on Android");
GLES30.glVertexAttribDivisor(index, divisor);
}
@Override
public void glBindFramebufferEXT(int param1, int param2) {
GLES20.glBindFramebuffer(param1, param2);
}
@Override
public void glBindRenderbufferEXT(int param1, int param2) {
GLES20.glBindRenderbuffer(param1, param2);
}
@Override
public int glCheckFramebufferStatusEXT(int param1) {
return GLES20.glCheckFramebufferStatus(param1);
}
@Override
public void glDeleteFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
GLES20.glDeleteFramebuffers(param1.limit(), param1);
}
@Override
public void glDeleteRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
GLES20.glDeleteRenderbuffers(param1.limit(), param1);
}
@Override
public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) {
GLES20.glFramebufferRenderbuffer(param1, param2, param3, param4);
}
@Override
public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) {
GLES20.glFramebufferTexture2D(param1, param2, param3, param4, param5);
}
@Override
public void glGenFramebuffersEXT(IntBuffer param1) {
checkLimit(param1);
GLES20.glGenFramebuffers(param1.limit(), param1);
}
@Override
public void glGenRenderbuffersEXT(IntBuffer param1) {
checkLimit(param1);
GLES20.glGenRenderbuffers(param1.limit(), param1);
}
@Override
public void glGenerateMipmapEXT(int param1) {
GLES20.glGenerateMipmap(param1);
}
@Override
public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) {
GLES20.glRenderbufferStorage(param1, param2, param3, param4);
}
@ -564,6 +670,58 @@ public class AndroidGL implements GL, GLExt, GLFbo {
@Override
public void glFramebufferTextureLayerEXT(int target, int attachment, int texture, int level, int layer) {
throw new UnsupportedOperationException("OpenGL ES 2 does not support texture arrays");
GLES30.glFramebufferTextureLayer(target, attachment, texture, level, layer);
}
@Override
public void glAlphaFunc(int func, float ref) {
}
@Override
public void glPointSize(float size) {
}
@Override
public void glPolygonMode(int face, int mode) {
}
// Wrapper to DrawBuffers as there's no DrawBuffer method in GLES
@Override
public void glDrawBuffer(int mode) {
tmpBuff.clear();
tmpBuff.put(0, mode);
tmpBuff.rewind();
glDrawBuffers(tmpBuff);
}
@Override
public void glReadBuffer(int mode) {
GLES30.glReadBuffer(mode);
}
@Override
public void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth,
int border, ByteBuffer data) {
GLES30.glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, getLimitBytes(data), data);
}
@Override
public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width,
int height, int depth, int format, ByteBuffer data) {
GLES30.glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, getLimitBytes(data), data);
}
@Override
public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border,
int format, int type, ByteBuffer data) {
GLES30.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data);
}
@Override
public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height,
int depth, int format, int type, ByteBuffer data) {
GLES30.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data);
}
}

View File

@ -19,6 +19,7 @@ public class AndroidConfigChooser implements EGLConfigChooser {
private static final Logger logger = Logger.getLogger(AndroidConfigChooser.class.getName());
protected AppSettings settings;
private final static int EGL_OPENGL_ES2_BIT = 4;
private final static int EGL_OPENGL_ES3_BIT = 0x40;
public AndroidConfigChooser(AppSettings settings) {
this.settings = settings;
@ -140,12 +141,29 @@ public class AndroidConfigChooser implements EGLConfigChooser {
int[] num_config = new int[1];
int[] configSpec = new int[]{
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL10.EGL_NONE};
boolean gles3=true;
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
RendererUtil.checkEGLError(egl);
throw new AssertionError();
// Try openGL ES 3
try {
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
RendererUtil.checkEGLError(egl);
gles3=false;
}
} catch (com.jme3.renderer.RendererException re) {
// it's just the device not supporting GLES3. Fallback to GLES2
gles3=false;
}
if(!gles3)
{
// Get back to openGL ES 2
configSpec[1]=EGL_OPENGL_ES2_BIT;
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
RendererUtil.checkEGLError(egl);
throw new AssertionError();
}
}
int numConfigs = num_config[0];

View File

@ -204,6 +204,7 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
public void showSoftKeyboard(final boolean show) {
view.getHandler().post(new Runnable() {
@Override
public void run() {
InputMethodManager manager =
(InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -52,13 +52,10 @@ import com.jme3.input.controls.SoftTextDialogInputListener;
import com.jme3.input.dummy.DummyKeyInput;
import com.jme3.input.dummy.DummyMouseInput;
import com.jme3.renderer.android.AndroidGL;
import com.jme3.renderer.opengl.GL;
import com.jme3.renderer.opengl.GLDebugES;
import com.jme3.renderer.opengl.GLExt;
import com.jme3.renderer.opengl.GLFbo;
import com.jme3.renderer.opengl.GLRenderer;
import com.jme3.renderer.opengl.GLTracer;
import com.jme3.renderer.opengl.*;
import com.jme3.system.*;
import com.jme3.util.AndroidBufferAllocator;
import com.jme3.util.BufferAllocatorFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -81,6 +78,14 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
protected long minFrameDuration = 0; // No FPS cap
protected long lastUpdateTime = 0;
static {
final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION;
if (System.getProperty(implementation) == null) {
System.setProperty(implementation, AndroidBufferAllocator.class.getName());
}
}
public OGLESContext() {
}
@ -99,13 +104,13 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
* @return GLSurfaceView The newly created view
*/
public GLSurfaceView createView(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
// NOTE: We assume all ICS devices have OpenGL ES 2.0.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// below 4.0, check OpenGL ES 2.0 support.
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo();
if (info.reqGlEsVersion < 0x20000) {
throw new UnsupportedOperationException("OpenGL ES 2.0 is not supported on this device");
throw new UnsupportedOperationException("OpenGL ES 2.0 or better is not supported on this device");
}
} else if (Build.VERSION.SDK_INT < 9){
throw new UnsupportedOperationException("jME3 requires Android 2.3 or later");
@ -126,7 +131,8 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
// setEGLContextClientVersion must be set before calling setRenderer
// this means it cannot be set in AndroidConfigChooser (too late)
view.setEGLContextClientVersion(2);
// use proper openGL ES version
view.setEGLContextClientVersion(info.reqGlEsVersion>>16);
view.setFocusableInTouchMode(true);
view.setFocusable(true);
@ -190,20 +196,21 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
// Setup unhandled Exception Handler
Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable thrown) {
listener.handleError("Exception thrown in " + thread.toString(), thrown);
}
});
timer = new NanoTimer();
Object gl = new AndroidGL();
GL gl = new AndroidGL();
if (settings.getBoolean("GraphicsDebug")) {
gl = new GLDebugES((GL) gl, (GLExt) gl, (GLFbo) gl);
gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GLES_30.class, GLFbo.class, GLExt.class);
}
if (settings.getBoolean("GraphicsTrace")) {
gl = GLTracer.createGlesTracer(gl, GL.class, GLFbo.class, GLExt.class);
gl = (GL)GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class);
}
renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl);
renderer = new GLRenderer(gl, (GLExt)gl, (GLFbo)gl);
renderer.initialize();
JmeSystem.setSoftTextDialogInput(this);
@ -242,7 +249,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
}
if (settings.getFrameRate() > 0) {
minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms
minFrameDuration = (long)(1000d / settings.getFrameRate()); // ms
logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration);
} else {
minFrameDuration = 0;
@ -402,6 +409,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
}
}
@Override
public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) {
logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}",
new Object[]{title, initialValue});
@ -445,6 +453,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
/* User clicked OK, send COMPLETE action
* and text */
@ -452,6 +461,7 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
}
}).setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
/* User clicked CANCEL, send CANCEL action
* and text */

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2009-2014 jMonkeyEngine
* Copyright (c) 2009-2020 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@ -67,6 +67,7 @@ public class AndroidBufferImageLoader implements AssetLoader {
}
}
@Override
public Object load(AssetInfo assetInfo) throws IOException {
Bitmap bitmap = null;
Image.Format format;

View File

@ -25,12 +25,13 @@ public class AndroidNativeImageLoader implements AssetLoader {
private static native Image load(InputStream in, boolean flipY, byte[] tmpArray) throws IOException;
@Override
public Image load(AssetInfo info) throws IOException {
boolean flip = ((TextureKey) info.getKey()).isFlipY();
InputStream in = null;
try {
in = info.openStream();
return load(info.openStream(), flip, tmpArray);
return load(in, flip, tmpArray);
} finally {
if (in != null){
in.close();

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
/**
* @author Jesus Oliver
*/
public class AndroidBufferAllocator implements BufferAllocator {
// We make use of the ReflectionAllocator to remove the inner buffer
private static final ReflectionAllocator reflectionAllocator = new ReflectionAllocator();
private static final String[] wrapperClassNames = {
"java.nio.ByteBufferAsFloatBuffer",
"java.nio.ByteBufferAsIntBuffer",
"java.nio.ByteBufferAsDoubleBuffer",
"java.nio.ByteBufferAsShortBuffer",
"java.nio.ByteBufferAsLongBuffer",
"java.nio.ByteBufferAsCharBuffer",
};
private static final String[] possibleBufferFieldNames = {"bb", "byteBuffer"};
// Keep track of ByteBuffer field by the wrapper class
private static final Map<Class, Field> fieldIndex = new HashMap<>();
static {
for (String className : wrapperClassNames) {
try {
Class clazz = Class.forName(className);
// loop for all possible field names in android
for (String fieldName : possibleBufferFieldNames) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
fieldIndex.put(clazz, field);
break;
} catch (NoSuchFieldException e) {
}
}
} catch (ClassNotFoundException ex) {
}
}
}
@Override
/**
* This function search the inner direct buffer of the android specific wrapped buffer classes
* and destroys it using the reflection allocator method.
*
* @param toBeDestroyed The direct buffer that will be "cleaned".
*
*/
public void destroyDirectBuffer(Buffer toBeDestroyed) {
// If it is a wrapped buffer, get it's inner direct buffer field and destroy it
Field field = fieldIndex.get(toBeDestroyed.getClass());
if (field != null) {
try {
ByteBuffer innerBuffer = (ByteBuffer) field.get(toBeDestroyed);
if (innerBuffer != null) {
// Destroy it using the reflection method
reflectionAllocator.destroyDirectBuffer(innerBuffer);
}
} catch (IllegalAccessException ex) {
}
} else {
// It is not a wrapped buffer, use default reflection allocator to remove it instead.
reflectionAllocator.destroyDirectBuffer(toBeDestroyed);
}
}
@Override
public ByteBuffer allocate(int size) {
return ByteBuffer.allocateDirect(size);
}
}

View File

@ -49,6 +49,7 @@ public class RingBuffer<T> implements Iterable<T> {
return item;
}
@Override
public Iterator<T> iterator() {
return new RingBufferIterator();
}
@ -58,14 +59,17 @@ public class RingBuffer<T> implements Iterable<T> {
private int i = 0;
@Override
public boolean hasNext() {
return i < count;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();

View File

@ -1,12 +0,0 @@
if (!hasProperty('mainClass')) {
ext.mainClass = ''
}
dependencies {
compile project(':jme3-core')
compile project(':jme3-desktop')
compile project(':jme3-effects')
compile ('org.ejml:core:0.27')
compile ('org.ejml:dense64:0.27')
compile ('org.ejml:simple:0.27')
}

View File

@ -1,733 +0,0 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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;
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.material.RenderState.FaceCullMode;
/**
* Blender key. Contains path of the blender file and its loading properties.
* @author Marcin Roguski (Kaelthas)
*/
public class BlenderKey extends ModelKey {
protected static final int DEFAULT_FPS = 25;
/**
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
* between the frames.
*/
protected int fps = DEFAULT_FPS;
/**
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
*/
protected int featuresToLoad = FeaturesToLoad.ALL;
/** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */
protected boolean loadUnlinkedAssets;
/** The root path for all the assets. */
protected String assetRootPath;
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
protected boolean fixUpAxis = true;
/** Generated textures resolution (PPU - Pixels Per Unit). */
protected int generatedTexturePPU = 128;
/**
* The name of world settings that the importer will use. If not set or specified name does not occur in the file
* then the first world settings in the file will be used.
*/
protected String usedWorld;
/**
* User's default material that is set for objects that have no material definition in blender. The default value is
* null. If the value is null the importer will use its own default material (gray color - like in blender).
*/
protected Material defaultMaterial;
/** Face cull mode. By default it is disabled. */
protected FaceCullMode faceCullMode = FaceCullMode.Back;
/**
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
* If set to -1 then the current layer will be loaded.
*/
protected int layersToLoad = -1;
/** A variable that toggles the object custom properties loading. */
protected boolean loadObjectProperties = true;
/**
* Maximum texture size. Might be dependant on the graphic card.
* This value is taken from <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
*/
protected int maxTextureSize = 8192;
/** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
protected boolean loadGeneratedTextures;
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
/**
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
* textures will get their proper size.
*/
protected int skyGeneratedTextureSize = 1000;
/** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */
protected float skyGeneratedTextureRadius = 1;
/** The shape against which the generated texture for the sky will be created. */
protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE;
/**
* This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together
* and textures that in the final result will never be visible - will be discarded.
*/
protected boolean optimiseTextures;
/** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
/** The size of points that are loaded and do not belong to any edge of the mesh. */
protected float pointsSize = 1;
/** The width of edges that are loaded from the mesh and do not belong to any face. */
protected float linesWidth = 1;
/**
* Constructor used by serialization mechanisms.
*/
public BlenderKey() {
}
/**
* Constructor. Creates a key for the given file name.
* @param name
* the name (path) of a file
*/
public BlenderKey(String name) {
super(name);
}
/**
* This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25.
* @return the frames per second amount
*/
public int getFps() {
return fps;
}
/**
* This method sets frames per second amount.
* @param fps
* the frames per second amount
*/
public void setFps(int fps) {
this.fps = fps;
}
/**
* This method returns the face cull mode.
* @return the face cull mode
*/
public FaceCullMode getFaceCullMode() {
return faceCullMode;
}
/**
* This method sets the face cull mode.
* @param faceCullMode
* the face cull mode
*/
public void setFaceCullMode(FaceCullMode faceCullMode) {
this.faceCullMode = faceCullMode;
}
/**
* This method sets layers to be loaded.
* @param layersToLoad
* layers to be loaded
*/
public void setLayersToLoad(int layersToLoad) {
this.layersToLoad = layersToLoad;
}
/**
* This method returns layers to be loaded.
* @return layers to be loaded
*/
public int getLayersToLoad() {
return layersToLoad;
}
/**
* This method sets the properies loading policy.
* By default the value is true.
* @param loadObjectProperties
* true to load properties and false to suspend their loading
*/
public void setLoadObjectProperties(boolean loadObjectProperties) {
this.loadObjectProperties = loadObjectProperties;
}
/**
* @return the current properties loading properties
*/
public boolean isLoadObjectProperties() {
return loadObjectProperties;
}
/**
* The default value for this parameter is the same as defined by: org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE.
* If by any means this is too large for user's hardware configuration use the 'setMaxTextureSize' method to change that.
* @return maximum texture size (width/height)
*/
public int getMaxTextureSize() {
return maxTextureSize;
}
/**
* This method sets the maximum texture size.
* @param maxTextureSize
* the maximum texture size
*/
public void setMaxTextureSize(int maxTextureSize) {
this.maxTextureSize = maxTextureSize;
}
/**
* This method sets the flag that toggles the generated textures loading.
* @param loadGeneratedTextures
* <b>true</b> if generated textures should be loaded and <b>false</b> otherwise
*/
public void setLoadGeneratedTextures(boolean loadGeneratedTextures) {
this.loadGeneratedTextures = loadGeneratedTextures;
}
/**
* @return tells if the generated textures should be loaded (<b>false</b> is the default value)
*/
public boolean isLoadGeneratedTextures() {
return loadGeneratedTextures;
}
/**
* Not used any more.
* This method sets the asset root path.
* @param assetRootPath
* the assets root path
*/
@Deprecated
public void setAssetRootPath(String assetRootPath) {
this.assetRootPath = assetRootPath;
}
/**
* Not used any more.
* This method returns the asset root path.
* @return the asset root path
*/
@Deprecated
public String getAssetRootPath() {
return assetRootPath;
}
/**
* This method adds features to be loaded.
* @param featuresToLoad
* bitwise flag of FeaturesToLoad interface values
*/
@Deprecated
public void includeInLoading(int featuresToLoad) {
this.featuresToLoad |= featuresToLoad;
}
/**
* This method removes features from being loaded.
* @param featuresNotToLoad
* bitwise flag of FeaturesToLoad interface values
*/
@Deprecated
public void excludeFromLoading(int featuresNotToLoad) {
featuresToLoad &= ~featuresNotToLoad;
}
@Deprecated
public boolean shouldLoad(int featureToLoad) {
return (featuresToLoad & featureToLoad) != 0;
}
/**
* This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by
* the blender file loader.
* @return features that will be loaded by the blender file loader
*/
@Deprecated
public int getFeaturesToLoad() {
return featuresToLoad;
}
/**
* This method determines if unlinked assets should be loaded.
* If not then only objects on selected layers will be loaded and their assets if required.
* If yes then all assets will be loaded even if they are on inactive layers or are not linked
* to anything.
* @return <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
*/
public boolean isLoadUnlinkedAssets() {
return loadUnlinkedAssets;
}
/**
* This method sets if unlinked assets should be loaded.
* If not then only objects on selected layers will be loaded and their assets if required.
* If yes then all assets will be loaded even if they are on inactive layers or are not linked
* to anything.
* @param loadUnlinkedAssets
* <b>true</b> if unlinked assets should be loaded and <b>false</b> otherwise
*/
public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) {
this.loadUnlinkedAssets = loadUnlinkedAssets;
}
/**
* This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y
* is up axis.
* @param fixUpAxis
* the up axis state variable
*/
public void setFixUpAxis(boolean fixUpAxis) {
this.fixUpAxis = fixUpAxis;
}
/**
* This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By
* default Y is up axis.
* @return the up axis state variable
*/
public boolean isFixUpAxis() {
return fixUpAxis;
}
/**
* This method sets the generated textures resolution.
* @param generatedTexturePPU
* the generated textures resolution
*/
public void setGeneratedTexturePPU(int generatedTexturePPU) {
this.generatedTexturePPU = generatedTexturePPU;
}
/**
* @return the generated textures resolution
*/
public int getGeneratedTexturePPU() {
return generatedTexturePPU;
}
/**
* @return mipmaps generation method
*/
public MipmapGenerationMethod getMipmapGenerationMethod() {
return mipmapGenerationMethod;
}
/**
* @param mipmapGenerationMethod
* mipmaps generation method
*/
public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) {
this.mipmapGenerationMethod = mipmapGenerationMethod;
}
/**
* @return the size of the generated textures for the sky (used if no flat textures are applied)
*/
public int getSkyGeneratedTextureSize() {
return skyGeneratedTextureSize;
}
/**
* @param skyGeneratedTextureSize
* the size of the generated textures for the sky (used if no flat textures are applied)
*/
public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) {
if (skyGeneratedTextureSize <= 0) {
throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!");
}
this.skyGeneratedTextureSize = skyGeneratedTextureSize;
}
/**
* @return the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen
*/
public float getSkyGeneratedTextureRadius() {
return skyGeneratedTextureRadius;
}
/**
* @param skyGeneratedTextureRadius
* the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen
*/
public void setSkyGeneratedTextureRadius(float skyGeneratedTextureRadius) {
this.skyGeneratedTextureRadius = skyGeneratedTextureRadius;
}
/**
* @return the shape against which the generated texture for the sky will be created (by default it is a sphere).
*/
public SkyGeneratedTextureShape getSkyGeneratedTextureShape() {
return skyGeneratedTextureShape;
}
/**
* @param skyGeneratedTextureShape
* the shape against which the generated texture for the sky will be created
*/
public void setSkyGeneratedTextureShape(SkyGeneratedTextureShape skyGeneratedTextureShape) {
if (skyGeneratedTextureShape == null) {
throw new IllegalArgumentException("The sky generated shape type cannot be null!");
}
this.skyGeneratedTextureShape = skyGeneratedTextureShape;
}
/**
* If set to true, then textures of the same mapping type will be merged together
* and textures that in the final result will never be visible - will be discarded.
* @param optimiseTextures
* the variable that tells if the textures should be optimised or not
*/
public void setOptimiseTextures(boolean optimiseTextures) {
this.optimiseTextures = optimiseTextures;
}
/**
* @return the variable that tells if the textures should be optimised or not (by default the optimisation is disabled)
*/
public boolean isOptimiseTextures() {
return optimiseTextures;
}
/**
* Sets the way the animations will be matched with skeletons.
*
* @param animationMatchMethod
* the way the animations will be matched with skeletons
*/
public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) {
this.animationMatchMethod = animationMatchMethod;
}
/**
* @return the way the animations will be matched with skeletons
*/
public AnimationMatchMethod getAnimationMatchMethod() {
return animationMatchMethod;
}
/**
* @return the size of points that are loaded and do not belong to any edge of the mesh
*/
public float getPointsSize() {
return pointsSize;
}
/**
* Sets the size of points that are loaded and do not belong to any edge of the mesh.
* @param pointsSize
* The size of points that are loaded and do not belong to any edge of the mesh
*/
public void setPointsSize(float pointsSize) {
this.pointsSize = pointsSize;
}
/**
* @return the width of edges that are loaded from the mesh and do not belong to any face
*/
public float getLinesWidth() {
return linesWidth;
}
/**
* Sets the width of edges that are loaded from the mesh and do not belong to any face.
* @param linesWidth
* the width of edges that are loaded from the mesh and do not belong to any face
*/
public void setLinesWidth(float linesWidth) {
this.linesWidth = linesWidth;
}
/**
* This method sets the name of the WORLD data block that should be used during file loading. By default the name is
* not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
* during loading (assuming any exists in the file).
* @param usedWorld
* the name of the WORLD block used during loading
*/
public void setUsedWorld(String usedWorld) {
this.usedWorld = usedWorld;
}
/**
* This method returns the name of the WORLD data block that should be used during file loading.
* @return the name of the WORLD block used during loading
*/
public String getUsedWorld() {
return usedWorld;
}
/**
* This method sets the default material for objects.
* @param defaultMaterial
* the default material
*/
public void setDefaultMaterial(Material defaultMaterial) {
this.defaultMaterial = defaultMaterial;
}
/**
* This method returns the default material.
* @return the default material
*/
public Material getDefaultMaterial() {
return defaultMaterial;
}
@Override
public void write(JmeExporter e) throws IOException {
super.write(e);
OutputCapsule oc = e.getCapsule(this);
oc.write(fps, "fps", DEFAULT_FPS);
oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL);
oc.write(loadUnlinkedAssets, "load-unlinked-assets", false);
oc.write(assetRootPath, "asset-root-path", null);
oc.write(fixUpAxis, "fix-up-axis", true);
oc.write(generatedTexturePPU, "generated-texture-ppu", 128);
oc.write(usedWorld, "used-world", null);
oc.write(defaultMaterial, "default-material", null);
oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
oc.write(layersToLoad, "layers-to-load", -1);
oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED);
oc.write(skyGeneratedTextureSize, "sky-generated-texture-size", 1000);
oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
oc.write(optimiseTextures, "optimise-textures", false);
oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
oc.write(pointsSize, "points-size", 1);
oc.write(linesWidth, "lines-width", 1);
}
@Override
public void read(JmeImporter e) throws IOException {
super.read(e);
InputCapsule ic = e.getCapsule(this);
fps = ic.readInt("fps", DEFAULT_FPS);
featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL);
loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false);
assetRootPath = ic.readString("asset-root-path", null);
fixUpAxis = ic.readBoolean("fix-up-axis", true);
generatedTexturePPU = ic.readInt("generated-texture-ppu", 128);
usedWorld = ic.readString("used-world", null);
defaultMaterial = (Material) ic.readSavable("default-material", null);
faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
layersToLoad = ic.readInt("layers-to=load", -1);
mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED);
skyGeneratedTextureSize = ic.readInt("sky-generated-texture-size", 1000);
skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
optimiseTextures = ic.readBoolean("optimise-textures", false);
animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
pointsSize = ic.readFloat("points-size", 1);
linesWidth = ic.readFloat("lines-width", 1);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode());
result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
result = prime * result + featuresToLoad;
result = prime * result + (fixUpAxis ? 1231 : 1237);
result = prime * result + fps;
result = prime * result + generatedTexturePPU;
result = prime * result + layersToLoad;
result = prime * result + (loadGeneratedTextures ? 1231 : 1237);
result = prime * result + (loadObjectProperties ? 1231 : 1237);
result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
result = prime * result + maxTextureSize;
result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
result = prime * result + (optimiseTextures ? 1231 : 1237);
result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
result = prime * result + skyGeneratedTextureSize;
result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
result = prime * result + (int) pointsSize;
result = prime * result + (int) linesWidth;
return result;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BlenderKey) {
return false;
}
BlenderKey other = (BlenderKey) obj;
if (animationMatchMethod != other.animationMatchMethod) {
return false;
}
if (assetRootPath == null) {
if (other.assetRootPath != null) {
return false;
}
} else if (!assetRootPath.equals(other.assetRootPath)) {
return false;
}
if (defaultMaterial == null) {
if (other.defaultMaterial != null) {
return false;
}
} else if (!defaultMaterial.equals(other.defaultMaterial)) {
return false;
}
if (faceCullMode != other.faceCullMode) {
return false;
}
if (featuresToLoad != other.featuresToLoad) {
return false;
}
if (fixUpAxis != other.fixUpAxis) {
return false;
}
if (fps != other.fps) {
return false;
}
if (generatedTexturePPU != other.generatedTexturePPU) {
return false;
}
if (layersToLoad != other.layersToLoad) {
return false;
}
if (loadGeneratedTextures != other.loadGeneratedTextures) {
return false;
}
if (loadObjectProperties != other.loadObjectProperties) {
return false;
}
if (loadUnlinkedAssets != other.loadUnlinkedAssets) {
return false;
}
if (maxTextureSize != other.maxTextureSize) {
return false;
}
if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
return false;
}
if (optimiseTextures != other.optimiseTextures) {
return false;
}
if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
return false;
}
if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) {
return false;
}
if (skyGeneratedTextureSize != other.skyGeneratedTextureSize) {
return false;
}
if (usedWorld == null) {
if (other.usedWorld != null) {
return false;
}
} else if (!usedWorld.equals(other.usedWorld)) {
return false;
}
if (pointsSize != other.pointsSize) {
return false;
}
if (linesWidth != other.linesWidth) {
return false;
}
return true;
}
/**
* This enum tells the importer if the mipmaps for textures will be generated by jme. <li>NEVER_GENERATE and ALWAYS_GENERATE are quite understandable <li>GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set in blender, mipmaps are generated only when the option is set
* @author Marcin Roguski (Kaelthas)
*/
public static enum MipmapGenerationMethod {
NEVER_GENERATE, ALWAYS_GENERATE, GENERATE_WHEN_NEEDED;
}
/**
* This interface describes the features of the scene that are to be loaded.
* @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency
* everything must be loaded because in blender one feature might depend on another
* @author Marcin Roguski (Kaelthas)
*/
@Deprecated
public static interface FeaturesToLoad {
int SCENES = 0x0000FFFF;
int OBJECTS = 0x0000000B;
int ANIMATIONS = 0x00000004;
int MATERIALS = 0x00000003;
int TEXTURES = 0x00000001;
int CAMERAS = 0x00000020;
int LIGHTS = 0x00000010;
int WORLD = 0x00000040;
int ALL = 0xFFFFFFFF;
}
/**
* The shape againts which the sky generated texture will be created.
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum SkyGeneratedTextureShape {
CUBE, SPHERE;
}
/**
* This enum describes which animations should be attached to which armature.
* Blender does not store the mapping between action and armature. That is why the importer
* will try to match those by comparing bone name of the armature with the channel names
* int the actions.
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum AnimationMatchMethod {
/**
* Animation is matched with skeleton when at leas one bone name matches the name of the action channel.
* All the bones that do not have their corresponding channel in the animation will not get the proper tracks for
* this particulat animation.
* Also the channel will not be used for the animation if it does not find the proper bone name.
*/
AT_LEAST_ONE_NAME_MATCH,
/**
* Animation is matched when all action names are covered by the target names (bone names or the name of the
* animated spatial.
*/
ALL_NAMES_MATCH;
}
}

View File

@ -1,193 +0,0 @@
/*
* 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.plugins.blender;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.BlenderKey;
import com.jme3.export.Savable;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.Properties;
/**
* A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can
* hold the state of the calculations.
* @author Marcin Roguski
*/
public abstract class AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName());
/** The blender context. */
protected BlenderContext blenderContext;
/** The version of the blend file. */
protected final int blenderVersion;
/** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis;
/** Quaternion used to rotate data when Y is up axis. */
protected Quaternion upAxisRotationQuaternion;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender
* versions.
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public AbstractBlenderHelper(String blenderVersion, BlenderContext blenderContext) {
this.blenderVersion = Integer.parseInt(blenderVersion);
this.blenderContext = blenderContext;
fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
if (fixUpAxis) {
upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0);
}
}
/**
* This method loads the properties if they are available and defined for the structure.
* @param structure
* the structure we read the properties from
* @param blenderContext
* the blender context
* @return loaded properties or null if they are not available
* @throws BlenderFileException
* an exception is thrown when the blend file is somehow corrupted
*/
protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
Properties properties = null;
Structure id = (Structure) structure.getFieldValue("ID");
if (id != null) {
Pointer pProperties = (Pointer) id.getFieldValue("properties");
if (pProperties.isNotNull()) {
Structure propertiesStructure = pProperties.fetchData().get(0);
properties = new Properties();
properties.load(propertiesStructure, blenderContext);
}
}
return properties;
}
/**
* The method applies properties to the given spatial. The Properties
* instance cannot be directly applied because the end-user might not have
* the blender plugin jar file and thus receive ClassNotFoundException. The
* values are set by name instead.
*
* @param spatial
* the spatial that is to have properties applied
* @param properties
* the properties to be applied
*/
public void applyProperties(Spatial spatial, Properties properties) {
List<String> propertyNames = properties.getSubPropertiesNames();
if (propertyNames != null && propertyNames.size() > 0) {
for (String propertyName : propertyNames) {
Object value = properties.findValue(propertyName);
if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) {
spatial.setUserData(propertyName, value);
} else if (value instanceof Double) {
spatial.setUserData(propertyName, ((Double) value).floatValue());
} else if (value instanceof int[]) {
spatial.setUserData(propertyName, Arrays.toString((int[]) value));
} else if (value instanceof float[]) {
spatial.setUserData(propertyName, Arrays.toString((float[]) value));
} else if (value instanceof double[]) {
spatial.setUserData(propertyName, Arrays.toString((double[]) value));
}
}
}
}
/**
* The method loads library of a given ID from linked blender file.
* @param id
* the ID of the linked feature (it contains its name and blender path)
* @return loaded feature or null if none was found
* @throws BlenderFileException
* and exception is throw when problems with reading a blend file occur
*/
protected Object loadLibrary(Structure id) throws BlenderFileException {
Pointer pLib = (Pointer) id.getFieldValue("lib");
if (pLib.isNotNull()) {
String fullName = id.getFieldValue("name").toString();// we need full name with the prefix
String nameOfFeatureToLoad = id.getName();
Structure library = pLib.fetchData().get(0);
String path = library.getFieldValue("filepath").toString();
if (!blenderContext.getLinkedFeatures().keySet().contains(path)) {
Spatial loadedAsset = null;
BlenderKey blenderKey = new BlenderKey(path);
blenderKey.setLoadUnlinkedAssets(true);
try {
loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey);
} catch (AssetNotFoundException e) {
LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", path);
}
if (loadedAsset != null) {
Map<String, Map<String, Object>> linkedData = loadedAsset.getUserData("linkedData");
for (Entry<String, Map<String, Object>> entry : linkedData.entrySet()) {
String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey();
blenderContext.getLinkedFeatures().put(linkedDataFilePath, entry.getValue());
}
} else {
LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path);
}
}
Object result = blenderContext.getLinkedFeature(path, fullName);
if (result == null) {
LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path });
} else {
blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id);
blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result);
}
return result;
} else {
LOGGER.warning("Library link points to nothing!");
}
return null;
}
}

View File

@ -1,767 +0,0 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.asset.AssetManager;
import com.jme3.asset.BlenderKey;
import com.jme3.light.Light;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.post.Filter;
import com.jme3.renderer.Camera;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.animations.BlenderAction;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.Constraint;
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DnaBlockData;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.texture.Texture;
/**
* The class that stores temporary data and manages it during loading the belnd
* file. This class is intended to be used in a single loading thread. It holds
* the state of loading operations.
*
* @author Marcin Roguski (Kaelthas)
*/
public class BlenderContext {
/** The blender file version. */
private int blenderVersion;
/** The blender key. */
private BlenderKey blenderKey;
/** The header of the file block. */
private DnaBlockData dnaBlockData;
/** The scene structure. */
private Structure sceneStructure;
/** The input stream of the blend file. */
private BlenderInputStream inputStream;
/** The asset manager. */
private AssetManager assetManager;
/** The blocks read from the file. */
protected List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
/**
* A map containing the file block headers. The key is the old memory address.
*/
private Map<Long, FileBlockHeader> fileBlockHeadersByOma = new HashMap<Long, FileBlockHeader>();
/** A map containing the file block headers. The key is the block code. */
private Map<BlockCode, List<FileBlockHeader>> fileBlockHeadersByCode = new HashMap<BlockCode, List<FileBlockHeader>>();
/**
* This map stores the loaded features by their old memory address. The
* first object in the value table is the loaded structure and the second -
* the structure already converted into proper data.
*/
private Map<Long, Map<LoadedDataType, Object>> loadedFeatures = new HashMap<Long, Map<LoadedDataType, Object>>();
/** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */
private Map<String, Map<String, Object>> linkedFeatures = new HashMap<String, Map<String, Object>>();
/** A stack that hold the parent structure of currently loaded feature. */
private Stack<Structure> parentStack = new Stack<Structure>();
/** A list of constraints for the specified object. */
protected Map<Long, List<Constraint>> constraints = new HashMap<Long, List<Constraint>>();
/** Animations loaded for features. */
private Map<Long, List<Animation>> animations = new HashMap<Long, List<Animation>>();
/** Loaded skeletons. */
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
/** A map between skeleton and node it modifies. */
private Map<Skeleton, Node> nodesWithSkeletons = new HashMap<Skeleton, Node>();
/** A map of bone contexts. */
protected Map<Long, BoneContext> boneContexts = new HashMap<Long, BoneContext>();
/** A map og helpers that perform loading. */
private Map<String, AbstractBlenderHelper> helpers = new HashMap<String, AbstractBlenderHelper>();
/** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
private Map<String, Map<Object, Object>> markers = new HashMap<String, Map<Object, Object>>();
/** A map of blender actions. The key is the action name and the value is the action itself. */
private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
/**
* This method sets the blender file version.
*
* @param blenderVersion
* the blender file version
*/
public void setBlenderVersion(String blenderVersion) {
this.blenderVersion = Integer.parseInt(blenderVersion);
}
/**
* @return the blender file version
*/
public int getBlenderVersion() {
return blenderVersion;
}
/**
* This method sets the blender key.
*
* @param blenderKey
* the blender key
*/
public void setBlenderKey(BlenderKey blenderKey) {
this.blenderKey = blenderKey;
}
/**
* This method returns the blender key.
*
* @return the blender key
*/
public BlenderKey getBlenderKey() {
return blenderKey;
}
/**
* This method sets the dna block data.
*
* @param dnaBlockData
* the dna block data
*/
public void setBlockData(DnaBlockData dnaBlockData) {
this.dnaBlockData = dnaBlockData;
}
/**
* This method returns the dna block data.
*
* @return the dna block data
*/
public DnaBlockData getDnaBlockData() {
return dnaBlockData;
}
/**
* This method sets the scene structure data.
*
* @param sceneStructure
* the scene structure data
*/
public void setSceneStructure(Structure sceneStructure) {
this.sceneStructure = sceneStructure;
}
/**
* This method returns the scene structure data.
*
* @return the scene structure data
*/
public Structure getSceneStructure() {
return sceneStructure;
}
/**
* This method returns the asset manager.
*
* @return the asset manager
*/
public AssetManager getAssetManager() {
return assetManager;
}
/**
* This method sets the asset manager.
*
* @param assetManager
* the asset manager
*/
public void setAssetManager(AssetManager assetManager) {
this.assetManager = assetManager;
}
/**
* This method returns the input stream of the blend file.
*
* @return the input stream of the blend file
*/
public BlenderInputStream getInputStream() {
return inputStream;
}
/**
* This method sets the input stream of the blend file.
*
* @param inputStream
* the input stream of the blend file
*/
public void setInputStream(BlenderInputStream inputStream) {
this.inputStream = inputStream;
}
/**
* This method adds a file block header to the map. Its old memory address
* is the key.
*
* @param oldMemoryAddress
* the address of the block header
* @param fileBlockHeader
* the block header to store
*/
public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) {
blocks.add(fileBlockHeader);
fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader);
List<FileBlockHeader> headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode());
if (headers == null) {
headers = new ArrayList<FileBlockHeader>();
fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers);
}
headers.add(fileBlockHeader);
}
/**
* @return the block headers
*/
public List<FileBlockHeader> getBlocks() {
return blocks;
}
/**
* This method returns the block header of a given memory address. If the
* header is not present then null is returned.
*
* @param oldMemoryAddress
* the address of the block header
* @return loaded header or null if it was not yet loaded
*/
public FileBlockHeader getFileBlock(Long oldMemoryAddress) {
return fileBlockHeadersByOma.get(oldMemoryAddress);
}
/**
* This method returns a list of file blocks' headers of a specified code.
*
* @param code
* the code of file blocks
* @return a list of file blocks' headers of a specified code
*/
public List<FileBlockHeader> getFileBlocks(BlockCode code) {
return fileBlockHeadersByCode.get(code);
}
/**
* This method adds a helper instance to the helpers' map.
*
* @param <T>
* the type of the helper
* @param clazz
* helper's class definition
* @param helper
* the helper instance
*/
public <T> void putHelper(Class<T> clazz, AbstractBlenderHelper helper) {
helpers.put(clazz.getSimpleName(), helper);
}
@SuppressWarnings("unchecked")
public <T> T getHelper(Class<?> clazz) {
return (T) helpers.get(clazz.getSimpleName());
}
/**
* This method adds a loaded feature to the map. The key is its unique old
* memory address.
*
* @param oldMemoryAddress
* the address of the feature
* @param featureDataType
* @param feature
* the feature we want to store
*/
public void addLoadedFeatures(Long oldMemoryAddress, LoadedDataType featureDataType, Object feature) {
if (oldMemoryAddress == null || featureDataType == null || feature == null) {
throw new IllegalArgumentException("One of the given arguments is null!");
}
Map<LoadedDataType, Object> map = loadedFeatures.get(oldMemoryAddress);
if (map == null) {
map = new HashMap<BlenderContext.LoadedDataType, Object>();
loadedFeatures.put(oldMemoryAddress, map);
}
map.put(featureDataType, feature);
}
/**
* This method returns the feature of a given memory address. If the feature
* is not yet loaded then null is returned.
*
* @param oldMemoryAddress
* the address of the feature
* @param loadedFeatureDataType
* the type of data we want to retrieve it can be either filled
* structure or already converted feature
* @return loaded feature or null if it was not yet loaded
*/
public Object getLoadedFeature(Long oldMemoryAddress, LoadedDataType loadedFeatureDataType) {
Map<LoadedDataType, Object> result = loadedFeatures.get(oldMemoryAddress);
if (result != null) {
return result.get(loadedFeatureDataType);
}
return null;
}
/**
* The method adds linked content to the blender context.
* @param blenderFilePath
* the path of linked blender file
* @param featureGroup
* the linked feature group (ie. scenes, materials, meshes, etc.)
* @param feature
* the linked feature
*/
@Deprecated
public void addLinkedFeature(String blenderFilePath, String featureGroup, Object feature) {
// the method is deprecated and empty at the moment
}
/**
* The method returns linked feature of a given name from the specified blender path.
* @param blenderFilePath
* the blender file path
* @param featureName
* the feature name we want to get
* @return linked feature or null if none was found
*/
@SuppressWarnings("unchecked")
public Object getLinkedFeature(String blenderFilePath, String featureName) {
Map<String, Object> linkedFeatures = this.linkedFeatures.get(blenderFilePath);
if(linkedFeatures != null) {
String namePrefix = (featureName.charAt(0) + "" + featureName.charAt(1)).toUpperCase();
featureName = featureName.substring(2);
if("SC".equals(namePrefix)) {
List<Node> scenes = (List<Node>) linkedFeatures.get("scenes");
if(scenes != null) {
for(Node scene : scenes) {
if(featureName.equals(scene.getName())) {
return scene;
}
}
}
} else if("OB".equals(namePrefix)) {
List<Node> features = (List<Node>) linkedFeatures.get("objects");
if(features != null) {
for(Node feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("ME".equals(namePrefix)) {
List<TemporalMesh> temporalMeshes = (List<TemporalMesh>) linkedFeatures.get("meshes");
if(temporalMeshes != null) {
for(TemporalMesh temporalMesh : temporalMeshes) {
if(featureName.equals(temporalMesh.getName())) {
return temporalMesh;
}
}
}
} else if("MA".equals(namePrefix)) {
List<MaterialContext> features = (List<MaterialContext>) linkedFeatures.get("materials");
if(features != null) {
for(MaterialContext feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("TX".equals(namePrefix)) {
List<Texture> features = (List<Texture>) linkedFeatures.get("textures");
if(features != null) {
for(Texture feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("IM".equals(namePrefix)) {
List<Texture> features = (List<Texture>) linkedFeatures.get("images");
if(features != null) {
for(Texture feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("AC".equals(namePrefix)) {
List<Animation> features = (List<Animation>) linkedFeatures.get("animations");
if(features != null) {
for(Animation feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("CA".equals(namePrefix)) {
List<Camera> features = (List<Camera>) linkedFeatures.get("cameras");
if(features != null) {
for(Camera feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("LA".equals(namePrefix)) {
List<Light> features = (List<Light>) linkedFeatures.get("lights");
if(features != null) {
for(Light feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
} else if("FI".equals(featureName)) {
List<Filter> features = (List<Filter>) linkedFeatures.get("lights");
if(features != null) {
for(Filter feature : features) {
if(featureName.equals(feature.getName())) {
return feature;
}
}
}
}
}
return null;
}
/**
* @return all linked features for the current blend file
*/
public Map<String, Map<String, Object>> getLinkedFeatures() {
return linkedFeatures;
}
/**
* This method adds the structure to the parent stack.
*
* @param parent
* the structure to be added to the stack
*/
public void pushParent(Structure parent) {
parentStack.push(parent);
}
/**
* This method removes the structure from the top of the parent's stack.
*
* @return the structure that was removed from the stack
*/
public Structure popParent() {
try {
return parentStack.pop();
} catch (EmptyStackException e) {
return null;
}
}
/**
* This method retrieves the structure at the top of the parent's stack but
* does not remove it.
*
* @return the structure from the top of the stack
*/
public Structure peekParent() {
try {
return parentStack.peek();
} catch (EmptyStackException e) {
return null;
}
}
/**
* This method adds a new modifier to the list.
*
* @param ownerOMA
* the owner's old memory address
* @param constraints
* the object's constraints
*/
public void addConstraints(Long ownerOMA, List<Constraint> constraints) {
List<Constraint> objectConstraints = this.constraints.get(ownerOMA);
if (objectConstraints == null) {
objectConstraints = new ArrayList<Constraint>();
this.constraints.put(ownerOMA, objectConstraints);
}
objectConstraints.addAll(constraints);
}
/**
* Returns constraints applied to the feature of the given OMA.
* @param ownerOMA
* the constraints' owner OMA
* @return a list of constraints or <b>null</b> if no constraints are applied to the feature
*/
public List<Constraint> getConstraints(Long ownerOMA) {
return constraints.get(ownerOMA);
}
/**
* @return all available constraints
*/
public List<Constraint> getAllConstraints() {
List<Constraint> result = new ArrayList<Constraint>();
for (Entry<Long, List<Constraint>> entry : constraints.entrySet()) {
result.addAll(entry.getValue());
}
return result;
}
/**
* This method adds the animation for the specified OMA of its owner.
*
* @param ownerOMA
* the owner's old memory address
* @param animation
* the animation for the feature specified by ownerOMA
*/
public void addAnimation(Long ownerOMA, Animation animation) {
List<Animation> animList = animations.get(ownerOMA);
if (animList == null) {
animList = new ArrayList<Animation>();
animations.put(ownerOMA, animList);
}
animList.add(animation);
}
/**
* This method returns the animation data for the specified owner.
*
* @param ownerOMA
* the old memory address of the animation data owner
* @return the animation or null if none exists
*/
public List<Animation> getAnimations(Long ownerOMA) {
return animations.get(ownerOMA);
}
/**
* This method sets the skeleton for the specified OMA of its owner.
*
* @param skeletonOMA
* the skeleton's old memory address
* @param skeleton
* the skeleton specified by the given OMA
*/
public void setSkeleton(Long skeletonOMA, Skeleton skeleton) {
skeletons.put(skeletonOMA, skeleton);
}
/**
* The method stores a binding between the skeleton and the proper armature
* node.
*
* @param skeleton
* the skeleton
* @param node
* the armature node
*/
public void setNodeForSkeleton(Skeleton skeleton, Node node) {
nodesWithSkeletons.put(skeleton, node);
}
/**
* This method returns the armature node that is defined for the skeleton.
*
* @param skeleton
* the skeleton
* @return the armature node that defines the skeleton in blender
*/
public Node getControlledNode(Skeleton skeleton) {
return nodesWithSkeletons.get(skeleton);
}
/**
* This method returns the skeleton for the specified OMA of its owner.
*
* @param skeletonOMA
* the skeleton's old memory address
* @return the skeleton specified by the given OMA
*/
public Skeleton getSkeleton(Long skeletonOMA) {
return skeletons.get(skeletonOMA);
}
/**
* This method sets the bone context for the given bone old memory address.
* If the context is already set it will be replaced.
*
* @param boneOMA
* the bone's old memory address
* @param boneContext
* the bones's context
*/
public void setBoneContext(Long boneOMA, BoneContext boneContext) {
boneContexts.put(boneOMA, boneContext);
}
/**
* This method returns the bone context for the given bone old memory
* address. If no context exists then <b>null</b> is returned.
*
* @param boneOMA
* the bone's old memory address
* @return bone's context
*/
public BoneContext getBoneContext(Long boneOMA) {
return boneContexts.get(boneOMA);
}
/**
* Returns bone by given name.
*
* @param skeletonOMA
* the OMA of the skeleton where the bone will be searched
* @param name
* the name of the bone
* @return found bone or null if none bone of a given name exists
*/
public BoneContext getBoneByName(Long skeletonOMA, String name) {
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
if (entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
Bone bone = entry.getValue().getBone();
if (bone != null && name.equals(bone.getName())) {
return entry.getValue();
}
}
}
return null;
}
/**
* Returns bone context for the given bone.
*
* @param bone
* the bone
* @return the bone's bone context
*/
public BoneContext getBoneContext(Bone bone) {
for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
if (entry.getValue().getBone().getName().equals(bone.getName())) {
return entry.getValue();
}
}
throw new IllegalStateException("Cannot find context for bone: " + bone);
}
/**
* This metod returns the default material.
*
* @return the default material
*/
public synchronized Material getDefaultMaterial() {
if (blenderKey.getDefaultMaterial() == null) {
Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
defaultMaterial.setColor("Color", ColorRGBA.DarkGray);
blenderKey.setDefaultMaterial(defaultMaterial);
}
return blenderKey.getDefaultMaterial();
}
/**
* Adds a custom marker for scene's feature.
*
* @param marker
* the marker name
* @param feature
* te scene's feature (can be node, material or texture or
* anything else)
* @param markerValue
* the marker value
*/
public void addMarker(String marker, Object feature, Object markerValue) {
if (markerValue == null) {
throw new IllegalArgumentException("The marker's value cannot be null.");
}
Map<Object, Object> markersMap = markers.get(marker);
if (markersMap == null) {
markersMap = new HashMap<Object, Object>();
markers.put(marker, markersMap);
}
markersMap.put(feature, markerValue);
}
/**
* Returns the marker value. The returned value is null if no marker was
* defined for the given feature.
*
* @param marker
* the marker name
* @param feature
* the scene's feature
* @return marker value or null if it was not defined
*/
public Object getMarkerValue(String marker, Object feature) {
Map<Object, Object> markersMap = markers.get(marker);
return markersMap == null ? null : markersMap.get(feature);
}
/**
* Adds blender action to the context.
* @param action
* the action loaded from the blend file
*/
public void addAction(BlenderAction action) {
actions.put(action.getName(), action);
}
/**
* @return a map of blender actions; the key is the action name and the value is action itself
*/
public Map<String, BlenderAction> getActions() {
return actions;
}
/**
* This enum defines what loaded data type user wants to retrieve. It can be
* either filled structure or already converted data.
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum LoadedDataType {
STRUCTURE, FEATURE, TEMPORAL_MESH;
}
@Override
public String toString() {
return blenderKey == null ? "BlenderContext [key = null]" : "BlenderContext [ key = " + blenderKey.toString() + " ]";
}
}

View File

@ -1,419 +0,0 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;
import com.jme3.animation.Animation;
import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetLocator;
import com.jme3.asset.AssetManager;
import com.jme3.asset.BlenderKey;
import com.jme3.asset.ModelKey;
import com.jme3.asset.StreamAssetInfo;
import com.jme3.light.Light;
import com.jme3.math.ColorRGBA;
import com.jme3.post.Filter;
import com.jme3.renderer.Camera;
import com.jme3.scene.CameraNode;
import com.jme3.scene.LightNode;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.cameras.CameraHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.curves.CurvesHelper;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
import com.jme3.scene.plugins.blender.lights.LightHelper;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.scene.plugins.blender.particles.ParticlesHelper;
import com.jme3.scene.plugins.blender.textures.TextureHelper;
import com.jme3.texture.Texture;
/**
* This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures.
* @author Marcin Roguski (Kaelthas)
*/
public class BlenderLoader implements AssetLoader {
private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName());
@Override
public Spatial load(AssetInfo assetInfo) throws IOException {
try {
BlenderContext blenderContext = this.setup(assetInfo);
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
animationHelper.loadAnimations();
BlenderKey blenderKey = blenderContext.getBlenderKey();
LoadedFeatures loadedFeatures = new LoadedFeatures();
for (FileBlockHeader block : blenderContext.getBlocks()) {
switch (block.getCode()) {
case BLOCK_OB00:
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() });
}
if (object.getParent() == null) {
loadedFeatures.objects.add(object);
}
if (object instanceof LightNode && ((LightNode) object).getLight() != null) {
loadedFeatures.lights.add(((LightNode) object).getLight());
} else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) {
loadedFeatures.cameras.add(((CameraNode) object).getCamera());
}
break;
case BLOCK_SC00:// Scene
loadedFeatures.sceneBlocks.add(block);
break;
case BLOCK_MA00:// Material
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext);
loadedFeatures.materials.add(materialContext);
break;
case BLOCK_ME00:// Mesh
MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class);
TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext);
loadedFeatures.meshes.add(temporalMesh);
break;
case BLOCK_IM00:// Image
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext);
if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded
loadedFeatures.images.add(image);
}
break;
case BLOCK_TE00:
Structure textureStructure = block.getStructure(blenderContext);
int type = ((Number) textureStructure.getFieldValue("type")).intValue();
if (type == TextureHelper.TEX_IMAGE) {
TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class);
Texture texture = texHelper.getTexture(textureStructure, null, blenderContext);
if (texture != null) {// null is returned when texture has no image
loadedFeatures.textures.add(texture);
}
} else {
LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object.");
}
break;
case BLOCK_WO00:// World
LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
Structure worldStructure = block.getStructure(blenderContext);
String worldName = worldStructure.getName();
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
Light ambientLight = landscapeHelper.toAmbientLight(worldStructure);
if (ambientLight != null) {
loadedFeatures.objects.add(new LightNode(null, ambientLight));
loadedFeatures.lights.add(ambientLight);
}
loadedFeatures.sky = landscapeHelper.toSky(worldStructure);
loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure);
Filter fogFilter = landscapeHelper.toFog(worldStructure);
if (fogFilter != null) {
loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure));
}
}
break;
case BLOCK_AC00:
LOGGER.fine("Loading unlinked animations is not yet supported!");
break;
default:
LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode());
}
}
LOGGER.fine("Baking constraints after every feature is loaded.");
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.bakeConstraints(blenderContext);
LOGGER.fine("Loading scenes and attaching them to the root object.");
for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) {
loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext), blenderContext));
}
LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it.");
Node modelRoot = new Node(blenderKey.getName());
for (Node scene : loadedFeatures.scenes) {
modelRoot.attachChild(scene);
}
if (blenderKey.isLoadUnlinkedAssets()) {
LOGGER.fine("Setting loaded content as user data in resulting sptaial.");
Map<String, Map<String, Object>> linkedData = new HashMap<String, Map<String, Object>>();
Map<String, Object> thisFileData = new HashMap<String, Object>();
thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList<Object>() : loadedFeatures.scenes);
thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList<Object>() : loadedFeatures.objects);
thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList<Object>() : loadedFeatures.meshes);
thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList<Object>() : loadedFeatures.materials);
thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList<Object>() : loadedFeatures.textures);
thisFileData.put("images", loadedFeatures.images == null ? new ArrayList<Object>() : loadedFeatures.images);
thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList<Object>() : loadedFeatures.animations);
thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList<Object>() : loadedFeatures.cameras);
thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList<Object>() : loadedFeatures.lights);
thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList<Object>() : loadedFeatures.filters);
thisFileData.put("backgroundColor", loadedFeatures.backgroundColor);
thisFileData.put("sky", loadedFeatures.sky);
linkedData.put("this", thisFileData);
linkedData.putAll(blenderContext.getLinkedFeatures());
modelRoot.setUserData("linkedData", linkedData);
}
return modelRoot;
} catch (BlenderFileException e) {
throw new IOException(e.getLocalizedMessage(), e);
} catch (Exception e) {
throw new IOException("Unexpected importer exception occurred: " + e.getLocalizedMessage(), e);
} finally {
this.clear(assetInfo);
}
}
/**
* This method converts the given structure to a scene node.
* @param structure
* structure of a scene
* @param blenderContext the blender context
* @return scene's node
* @throws BlenderFileException
* an exception throw when problems with blender file occur
*/
private Node toScene(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Node result = new Node(structure.getName());
List<Structure> base = ((Structure) structure.getFieldValue("base")).evaluateListBase();
for (Structure b : base) {
Pointer pObject = (Pointer) b.getFieldValue("object");
if (pObject.isNotNull()) {
Structure objectStructure = pObject.fetchData().get(0);
Object object = objectHelper.toObject(objectStructure, blenderContext);
if (object instanceof Node) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() });
}
if (((Node) object).getParent() == null) {
result.attachChild((Spatial) object);
}
if(object instanceof LightNode) {
result.addLight(((LightNode) object).getLight());
}
}
}
}
return result;
}
/**
* This method sets up the loader.
* @param assetInfo
* the asset info
* @throws BlenderFileException
* an exception is throw when something wrong happens with blender file
*/
protected BlenderContext setup(AssetInfo assetInfo) throws BlenderFileException {
// registering loaders
ModelKey modelKey = (ModelKey) assetInfo.getKey();
BlenderKey blenderKey;
if (modelKey instanceof BlenderKey) {
blenderKey = (BlenderKey) modelKey;
} else {
blenderKey = new BlenderKey(modelKey.getName());
}
// opening stream
BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream());
// reading blocks
List<FileBlockHeader> blocks = new ArrayList<FileBlockHeader>();
FileBlockHeader fileBlock;
BlenderContext blenderContext = new BlenderContext();
blenderContext.setBlenderVersion(inputStream.getVersionNumber());
blenderContext.setAssetManager(assetInfo.getManager());
blenderContext.setInputStream(inputStream);
blenderContext.setBlenderKey(blenderKey);
// creating helpers
blenderContext.putHelper(AnimationHelper.class, new AnimationHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
// reading the blocks (dna block is automatically saved in the blender context when found)
FileBlockHeader sceneFileBlock = null;
do {
fileBlock = new FileBlockHeader(inputStream, blenderContext);
if (!fileBlock.isDnaBlock()) {
blocks.add(fileBlock);
// save the scene's file block
if (fileBlock.getCode() == BlockCode.BLOCK_SC00) {
sceneFileBlock = fileBlock;
}
}
} while (!fileBlock.isLastBlock());
if (sceneFileBlock != null) {
blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext));
}
// adding locator for linked content
assetInfo.getManager().registerLocator(assetInfo.getKey().getName(), LinkedContentLocator.class);
return blenderContext;
}
/**
* The internal data is only needed during loading so make it unreachable so that the GC can release
* that memory (which can be quite large amount).
*/
protected void clear(AssetInfo assetInfo) {
assetInfo.getManager().unregisterLocator(assetInfo.getKey().getName(), LinkedContentLocator.class);
}
/**
* This class holds the loading results according to the given loading flag.
* @author Marcin Roguski (Kaelthas)
*/
private static class LoadedFeatures {
private List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
/** The scenes from the file. */
private List<Node> scenes = new ArrayList<Node>();
/** Objects from all scenes. */
private List<Node> objects = new ArrayList<Node>();
/** All meshes. */
private List<TemporalMesh> meshes = new ArrayList<TemporalMesh>();
/** Materials from all objects. */
private List<MaterialContext> materials = new ArrayList<MaterialContext>();
/** Textures from all objects. */
private List<Texture> textures = new ArrayList<Texture>();
/** The images stored in the blender file. */
private List<Texture> images = new ArrayList<Texture>();
/** Animations of all objects. */
private List<Animation> animations = new ArrayList<Animation>();
/** All cameras from the file. */
private List<Camera> cameras = new ArrayList<Camera>();
/** All lights from the file. */
private List<Light> lights = new ArrayList<Light>();
/** Loaded sky. */
private Spatial sky;
/** Scene filters (ie. FOG). */
private List<Filter> filters = new ArrayList<Filter>();
/**
* The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
* is set to default (as in blender editor.
*/
private ColorRGBA backgroundColor = ColorRGBA.Gray;
}
public static class LinkedContentLocator implements AssetLocator {
private File rootFolder;
@Override
public void setRootPath(String rootPath) {
rootFolder = new File(rootPath);
if(rootFolder.isFile()) {
rootFolder = rootFolder.getParentFile();
}
}
@SuppressWarnings("rawtypes")
@Override
public AssetInfo locate(AssetManager manager, AssetKey key) {
if(key instanceof BlenderKey) {
File linkedAbsoluteFile = new File(key.getName());
if(linkedAbsoluteFile.exists() && linkedAbsoluteFile.isFile()) {
try {
return new StreamAssetInfo(manager, key, new FileInputStream(linkedAbsoluteFile));
} catch (FileNotFoundException e) {
return null;
}
}
File linkedFileInCurrentAssetFolder = new File(rootFolder, linkedAbsoluteFile.getName());
if(linkedFileInCurrentAssetFolder.exists() && linkedFileInCurrentAssetFolder.isFile()) {
try {
return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentAssetFolder));
} catch (FileNotFoundException e) {
return null;
}
}
File linkedFileInCurrentFolder = new File(".", linkedAbsoluteFile.getName());
if(linkedFileInCurrentFolder.exists() && linkedFileInCurrentFolder.isFile()) {
try {
return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentFolder));
} catch (FileNotFoundException e) {
return null;
}
}
}
return null;
}
}
}

View File

@ -1,391 +0,0 @@
package com.jme3.scene.plugins.blender.animations;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.animation.SpatialTrack;
import com.jme3.asset.BlenderKey.AnimationMatchMethod;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
import com.jme3.scene.plugins.blender.curves.BezierCurve;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
/**
* The helper class that helps in animations loading.
* @author Marcin Roguski (Kaelthas)
*/
public class AnimationHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName());
public AnimationHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
/**
* Loads all animations that are stored in the blender file. The animations are not yet applied to the scene features.
* This should be called before objects are loaded.
* @throws BlenderFileException
* an exception is thrown when problems with blender file reading occur
*/
public void loadAnimations() throws BlenderFileException {
LOGGER.info("Loading animations that will be later applied to scene features.");
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00);
if (actionHeaders != null) {
for (FileBlockHeader header : actionHeaders) {
Structure actionStructure = header.getStructure(blenderContext);
LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
}
}
}
/**
* The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file.
* @param node
* the node to whom the animations will be applied
* @param animationMatchMethod
* the way animation should be matched with node
*/
public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) {
List<BlenderAction> actions = this.getActions(node, animationMatchMethod);
if (actions.size() > 0) {
List<Animation> animations = new ArrayList<Animation>();
for (BlenderAction action : actions) {
SpatialTrack[] tracks = action.toTracks(node, blenderContext);
if (tracks != null && tracks.length > 0) {
Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime());
spatialAnimation.setTracks(tracks);
animations.add(spatialAnimation);
blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
}
}
if (animations.size() > 0) {
AnimControl control = new AnimControl();
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size());
for (int i = 0; i < animations.size(); ++i) {
Animation animation = animations.get(i);
anims.put(animation.getName(), animation);
}
control.setAnimations(anims);
node.addControl(control);
}
}
}
/**
* The method applies skeleton animations to the given node.
* @param node
* the node where the animations will be applied
* @param skeleton
* the skeleton of the node
* @param animationMatchMethod
* the way animation should be matched with skeleton
*/
public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
node.addControl(new SkeletonControl(skeleton));
blenderContext.setNodeForSkeleton(skeleton, node);
List<BlenderAction> actions = this.getActions(skeleton, animationMatchMethod);
if (actions.size() > 0) {
List<Animation> animations = new ArrayList<Animation>();
for (BlenderAction action : actions) {
BoneTrack[] tracks = action.toTracks(skeleton, blenderContext);
if (tracks != null && tracks.length > 0) {
Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
boneAnimation.setTracks(tracks);
animations.add(boneAnimation);
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
}
}
if (animations.size() > 0) {
AnimControl control = new AnimControl(skeleton);
HashMap<String, Animation> anims = new HashMap<String, Animation>(animations.size());
for (int i = 0; i < animations.size(); ++i) {
Animation animation = animations.get(i);
anims.put(animation.getName(), animation);
}
control.setAnimations(anims);
node.addControl(control);
// make sure that SkeletonControl is added AFTER the AnimControl
SkeletonControl skeletonControl = node.getControl(SkeletonControl.class);
if (skeletonControl != null) {
node.removeControl(SkeletonControl.class);
node.addControl(skeletonControl);
}
}
}
}
/**
* This method creates an ipo object used for interpolation calculations.
*
* @param ipoStructure
* the structure with ipo definition
* @param blenderContext
* the blender context
* @return the ipo object
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure curvebase = (Structure) ipoStructure.getFieldValue("curve");
// preparing bezier curves
Ipo result = null;
List<Structure> curves = curvebase.evaluateListBase();// IpoCurve
if (curves.size() > 0) {
BezierCurve[] bezierCurves = new BezierCurve[curves.size()];
int frame = 0;
for (Structure curve : curves) {
Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData();
int type = ((Number) curve.getFieldValue("adrcode")).intValue();
bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2);
}
curves.clear();
result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
Long ipoOma = ipoStructure.getOldMemoryAddress();
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.STRUCTURE, ipoStructure);
blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.FEATURE, result);
}
return result;
}
/**
* This method creates an ipo with only a single value. No track type is
* specified so do not use it for calculating tracks.
*
* @param constValue
* the value of this ipo
* @return constant ipo
*/
public Ipo fromValue(float constValue) {
return new ConstIpo(constValue);
}
/**
* This method retuns the bone tracks for animation.
*
* @param actionStructure
* the structure containing the tracks
* @param blenderContext
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
private BlenderAction getTracks(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion < 250) {
return this.getTracks249(actionStructure, blenderContext);
} else {
return this.getTracks250(actionStructure, blenderContext);
}
}
/**
* This method retuns the bone tracks for animation for blender version 2.50
* and higher.
*
* @param actionStructure
* the structure containing the tracks
* @param blenderContext
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Getting tracks!");
Structure groups = (Structure) actionStructure.getFieldValue("groups");
List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
int lastFrame = 1;
for (Structure actionGroup : actionGroups) {
String name = actionGroup.getFieldValue("name").toString();
List<Structure> channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase();
BezierCurve[] bezierCurves = new BezierCurve[channels.size()];
int channelCounter = 0;
for (Structure c : channels) {
int type = this.getCurveType(c, blenderContext);
Pointer pBezTriple = (Pointer) c.getFieldValue("bezt");
List<Structure> bezTriples = pBezTriple.fetchData();
bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2);
}
Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion());
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
blenderAction.featuresTracks.put(name, ipo);
}
blenderAction.stopFrame = lastFrame;
return blenderAction;
}
/**
* This method retuns the bone tracks for animation for blender version 2.49
* (and probably several lower versions too).
*
* @param actionStructure
* the structure containing the tracks
* @param blenderContext
* the blender context
* @return a list of tracks for the specified animation
* @throws BlenderFileException
* an exception is thrown when there are problems with the blend
* file
*/
private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.log(Level.FINE, "Getting tracks!");
Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
int lastFrame = 1;
for (Structure bActionChannel : actionChannels) {
String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
Pointer p = (Pointer) bActionChannel.getFieldValue("ipo");
if (!p.isNull()) {
Structure ipoStructure = p.fetchData().get(0);
Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
if (ipo != null) {// this can happen when ipo with no curves appear in blender file
lastFrame = Math.max(lastFrame, ipo.getLastFrame());
blenderAction.featuresTracks.put(animatedFeatureName, ipo);
}
}
}
blenderAction.stopFrame = lastFrame;
return blenderAction;
}
/**
* This method returns the type of the ipo curve.
*
* @param structure
* the structure must contain the 'rna_path' field and
* 'array_index' field (the type is not important here)
* @param blenderContext
* the blender context
* @return the type of the curve
*/
public int getCurveType(Structure structure, BlenderContext blenderContext) {
// reading rna path first
BlenderInputStream bis = blenderContext.getInputStream();
int currentPosition = bis.getPosition();
Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path");
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress());
bis.setPosition(dataFileBlock.getBlockPosition());
String rnaPath = bis.readString();
bis.setPosition(currentPosition);
int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue();
// determining the curve type
if (rnaPath.endsWith("location")) {
return Ipo.AC_LOC_X + arrayIndex;
}
if (rnaPath.endsWith("rotation_quaternion")) {
return Ipo.AC_QUAT_W + arrayIndex;
}
if (rnaPath.endsWith("scale")) {
return Ipo.AC_SIZE_X + arrayIndex;
}
if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
return Ipo.OB_ROT_X + arrayIndex;
}
LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", rnaPath);
return -1;
}
/**
* The method returns the actions for the given skeleton. The actions represent armature animation in blender.
* @param skeleton
* the skeleton we fetch the actions for
* @param animationMatchMethod
* the method of animation matching
* @return a list of animations for the specified skeleton
*/
private List<BlenderAction> getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
List<BlenderAction> result = new ArrayList<BlenderAction>();
// first get a set of bone names
Set<String> boneNames = new HashSet<String>();
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
String boneName = skeleton.getBone(i).getName();
if (boneName != null && boneName.length() > 0) {
boneNames.add(skeleton.getBone(i).getName());
}
}
// finding matches
Set<String> matchingNames = new HashSet<String>();
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
// compute how many action tracks match the skeleton bones' names
for (String boneName : boneNames) {
if (actionEntry.getValue().hasTrackName(boneName)) {
matchingNames.add(boneName);
}
}
BlenderAction action = null;
if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) {
action = actionEntry.getValue();
} else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) {
action = actionEntry.getValue();
}
if (action != null) {
// remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH
if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) {
action = action.clone();
action.removeTracksThatAreNotInTheCollection(matchingNames);
}
result.add(action);
}
matchingNames.clear();
}
return result;
}
/**
* The method returns the actions for the given node. The actions represent object animation in blender.
* @param node
* the node we fetch the actions for
* @param animationMatchMethod
* the method of animation matching
* @return a list of animations for the specified node
*/
private List<BlenderAction> getActions(Node node, AnimationMatchMethod animationMatchMethod) {
List<BlenderAction> result = new ArrayList<BlenderAction>();
for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
if (actionEntry.getValue().hasTrackName(node.getName())) {
result.add(actionEntry.getValue());
}
}
return result;
}
}

View File

@ -1,134 +0,0 @@
package com.jme3.scene.plugins.blender.animations;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SpatialTrack;
import com.jme3.scene.Node;
import com.jme3.scene.plugins.blender.BlenderContext;
/**
* An abstract representation of animation. The data stored here is mainly a
* raw action data loaded from blender. It can later be transformed into
* bone or spatial animation and applied to the specified node.
*
* @author Marcin Roguski (Kaelthas)
*/
public class BlenderAction implements Cloneable {
/** The action name. */
/* package */final String name;
/** Animation speed - frames per second. */
/* package */int fps;
/**
* The last frame of the animation (the last ipo curve node position is
* used as a last frame).
*/
/* package */int stopFrame;
/**
* Tracks of the features. In case of bone animation the keys are the
* names of the bones. In case of spatial animation - the node's name is
* used. A single ipo contains all tracks for location, rotation and
* scales.
*/
/* package */Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
public BlenderAction(String name, int fps) {
this.name = name;
this.fps = fps;
}
public void removeTracksThatAreNotInTheCollection(Collection<String> trackNames) {
Map<String, Ipo> newTracks = new HashMap<String, Ipo>();
for (String trackName : trackNames) {
if (featuresTracks.containsKey(trackName)) {
newTracks.put(trackName, featuresTracks.get(trackName));
}
}
featuresTracks = newTracks;
}
@Override
public BlenderAction clone() {
BlenderAction result = new BlenderAction(name, fps);
result.stopFrame = stopFrame;
result.featuresTracks = new HashMap<String, Ipo>(featuresTracks);
return result;
}
/**
* Converts the action into JME spatial animation tracks.
*
* @param node
* the node that will be animated
* @return the spatial tracks for the node
*/
public SpatialTrack[] toTracks(Node node, BlenderContext blenderContext) {
List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, null, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
}
return tracks.toArray(new SpatialTrack[tracks.size()]);
}
/**
* Converts the action into JME bone animation tracks.
*
* @param skeleton
* the skeleton that will be animated
* @return the bone tracks for the node
*/
public BoneTrack[] toTracks(Skeleton skeleton, BlenderContext blenderContext) {
List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
int boneIndex = skeleton.getBoneIndex(entry.getKey());
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(boneIndex));
tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, boneContext, boneContext.getBone().getBindPosition(), boneContext.getBone().getBindRotation(), boneContext.getBone().getBindScale(), 1, stopFrame, fps, false));
}
return tracks.toArray(new BoneTrack[tracks.size()]);
}
/**
* @return the name of the action
*/
public String getName() {
return name;
}
/**
* @return the time of animations (in seconds)
*/
public float getAnimationTime() {
return (stopFrame - 1) / (float) fps;
}
/**
* Determines if the current action has a track of a given name.
* CAUTION! The names are case sensitive.
*
* @param name
* the name of the track
* @return <B>true</b> if the track of a given name exists for the
* action and <b>false</b> otherwise
*/
public boolean hasTrackName(String name) {
return featuresTracks.containsKey(name);
}
/**
* @return the amount of tracks in current action
*/
public int getTracksCount() {
return featuresTracks.size();
}
@Override
public String toString() {
return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]";
}
}

View File

@ -1,400 +0,0 @@
package com.jme3.scene.plugins.blender.animations;
import java.util.ArrayList;
import java.util.List;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
/**
* This class holds the basic data that describes a bone.
*
* @author Marcin Roguski (Kaelthas)
*/
public class BoneContext {
// the flags of the bone
public static final int SELECTED = 0x000001;
public static final int CONNECTED_TO_PARENT = 0x000010;
public static final int DEFORM = 0x001000;
public static final int NO_LOCAL_LOCATION = 0x400000;
public static final int NO_INHERIT_SCALE = 0x008000;
public static final int NO_INHERIT_ROTATION = 0x000200;
/**
* The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
* So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results.
*/
public static final Matrix4f BONE_ARMATURE_TRANSFORMATION_MATRIX = new Matrix4f(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
private static final int IKFLAG_LOCK_X = 0x01;
private static final int IKFLAG_LOCK_Y = 0x02;
private static final int IKFLAG_LOCK_Z = 0x04;
private static final int IKFLAG_LIMIT_X = 0x08;
private static final int IKFLAG_LIMIT_Y = 0x10;
private static final int IKFLAG_LIMIT_Z = 0x20;
private BlenderContext blenderContext;
/** The OMA of the bone's armature object. */
private Long armatureObjectOMA;
/** The OMA of the model that owns the bone's skeleton. */
private Long skeletonOwnerOma;
/** The structure of the bone. */
private Structure boneStructure;
/** Bone's name. */
private String boneName;
/** The bone's flag. */
private int flag;
/** The bone's matrix in world space. */
private Matrix4f globalBoneMatrix;
/** The bone's matrix in the model space. */
private Matrix4f boneMatrixInModelSpace;
/** The parent context. */
private BoneContext parent;
/** The children of this context. */
private List<BoneContext> children = new ArrayList<BoneContext>();
/** Created bone (available after calling 'buildBone' method). */
private Bone bone;
/** The length of the bone. */
private float length;
/** The bone's deform envelope. */
private BoneEnvelope boneEnvelope;
// The below data is used only for IK constraint computations.
/** The bone's stretch value. */
private float ikStretch;
/** Bone's rotation minimum values. */
private Vector3f limitMin;
/** Bone's rotation maximum values. */
private Vector3f limitMax;
/** The bone's stiffness values (how much it rotates during IK computations. */
private Vector3f stiffness;
/** Values that indicate if any axis' rotation should be limited by some angle. */
private boolean[] limits;
/** Values that indicate if any axis' rotation should be disabled during IK computations. */
private boolean[] locks;
/**
* Constructor. Creates the basic set of bone's data.
*
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param boneStructure
* the bone's structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
public BoneContext(Long armatureObjectOMA, Structure boneStructure, BlenderContext blenderContext) throws BlenderFileException {
this(boneStructure, armatureObjectOMA, null, blenderContext);
}
/**
* Constructor. Creates the basic set of bone's data.
*
* @param boneStructure
* the bone's structure
* @param armatureObjectOMA
* the OMA of the bone's armature object
* @param parent
* bone's parent (null if the bone is the root bone)
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problem with blender data reading
* occurs
*/
@SuppressWarnings("unchecked")
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException {
this.parent = parent;
this.blenderContext = blenderContext;
this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString();
flag = ((Number) boneStructure.getFieldValue("flag")).intValue();
length = ((Number) boneStructure.getFieldValue("length")).floatValue();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
// first get the bone matrix in its armature space
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
if (blenderContext.getBlenderKey().isFixUpAxis()) {
// then make sure it is rotated in a proper way to fit the jme bone transformation conventions
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
}
Structure armatureStructure = blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext);
Spatial armature = (Spatial) objectHelper.toObject(armatureStructure, blenderContext);
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform(), new Matrix4f());
// and now compute the final bone matrix in world space
globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix);
// load the bone deformation envelope if necessary
if ((flag & DEFORM) == 0) {// if the flag is NOT set then the DEFORM is in use
boneEnvelope = new BoneEnvelope(boneStructure, armatureWorldMatrix, blenderContext.getBlenderKey().isFixUpAxis());
}
// load bone's pose channel data
Pointer pPose = (Pointer) armatureStructure.getFieldValue("pose");
if (pPose != null && pPose.isNotNull()) {
List<Structure> poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase();
for (Structure poseChannel : poseChannels) {
Long boneOMA = ((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress();
if (boneOMA.equals(this.boneStructure.getOldMemoryAddress())) {
ikStretch = ((Number) poseChannel.getFieldValue("ikstretch")).floatValue();
DynamicArray<Number> limitMin = (DynamicArray<Number>) poseChannel.getFieldValue("limitmin");
this.limitMin = new Vector3f(limitMin.get(0).floatValue(), limitMin.get(1).floatValue(), limitMin.get(2).floatValue());
DynamicArray<Number> limitMax = (DynamicArray<Number>) poseChannel.getFieldValue("limitmax");
this.limitMax = new Vector3f(limitMax.get(0).floatValue(), limitMax.get(1).floatValue(), limitMax.get(2).floatValue());
DynamicArray<Number> stiffness = (DynamicArray<Number>) poseChannel.getFieldValue("stiffness");
this.stiffness = new Vector3f(stiffness.get(0).floatValue(), stiffness.get(1).floatValue(), stiffness.get(2).floatValue());
int ikFlag = ((Number) poseChannel.getFieldValue("ikflag")).intValue();
locks = new boolean[] { (ikFlag & IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LOCK_Z) != 0 };
// limits are enabled when locks are disabled, so we ween to take that into account here
limits = new boolean[] { (ikFlag & IKFLAG_LIMIT_X & ~IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LIMIT_Y & ~IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LIMIT_Z & ~IKFLAG_LOCK_Z) != 0 };
break;// we have found what we need, no need to search further
}
}
}
// create the children
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase();
for (Structure child : childbase) {
children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
}
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
}
/**
* This method builds the bone. It recursively builds the bone's children.
*
* @param bones
* a list of bones where the newly created bone will be added
* @param skeletonOwnerOma
* the spatial of the object that will own the skeleton
* @param blenderContext
* the blender context
* @return newly created bone
*/
public Bone buildBone(List<Bone> bones, Long skeletonOwnerOma, BlenderContext blenderContext) {
this.skeletonOwnerOma = skeletonOwnerOma;
Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName);
bones.add(bone);
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.STRUCTURE, boneStructure);
blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.FEATURE, bone);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedDataType.STRUCTURE);
// I could load 'imat' here, but apparently in some older blenders there were bugs or unfinished functionalities that stored ZERO matrix in imat field
// loading 'obmat' and inverting it makes us avoid errors in such cases
Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "obmat", blenderContext.getBlenderKey().isFixUpAxis()).invertLocal();
if (objectHelper.isParent(skeletonOwnerOma, armatureObjectOMA)) {
boneMatrixInModelSpace = globalBoneMatrix.mult(invertedObjectOwnerGlobalMatrix);
} else {
boneMatrixInModelSpace = invertedObjectOwnerGlobalMatrix.mult(globalBoneMatrix);
}
Matrix4f boneLocalMatrix = parent == null ? boneMatrixInModelSpace : parent.boneMatrixInModelSpace.invert().multLocal(boneMatrixInModelSpace);
Vector3f poseLocation = parent == null || !this.is(CONNECTED_TO_PARENT) ? boneLocalMatrix.toTranslationVector() : new Vector3f(0, parent.length, 0);
Quaternion rotation = boneLocalMatrix.toRotationQuat().normalizeLocal();
Vector3f scale = boneLocalMatrix.toScaleVector();
bone.setBindTransforms(poseLocation, rotation, scale);
for (BoneContext child : children) {
bone.addChild(child.buildBone(bones, skeletonOwnerOma, blenderContext));
}
return bone;
}
/**
* @return built bone (available after calling 'buildBone' method)
*/
public Bone getBone() {
return bone;
}
/**
* @return the old memory address of the bone
*/
public Long getBoneOma() {
return boneStructure.getOldMemoryAddress();
}
/**
* The method returns the length of the bone.
* If you want to use it for bone debugger take model space scale into account and do
* something like this:
* <b>boneContext.getLength() * boneContext.getBone().getModelSpaceScale().y</b>.
* Otherwise the bones might not look as they should in the bone debugger.
* @return the length of the bone
*/
public float getLength() {
return length;
}
/**
* @return OMA of the bone's armature object
*/
public Long getArmatureObjectOMA() {
return armatureObjectOMA;
}
/**
* @return the OMA of the model that owns the bone's skeleton
*/
public Long getSkeletonOwnerOma() {
return skeletonOwnerOma;
}
/**
* @return the skeleton the bone of this context belongs to
*/
public Skeleton getSkeleton() {
return blenderContext.getSkeleton(armatureObjectOMA);
}
/**
* @return the initial bone's matrix in model space
*/
public Matrix4f getBoneMatrixInModelSpace() {
return boneMatrixInModelSpace;
}
/**
* @return the vertex assigning envelope of the bone
*/
public BoneEnvelope getBoneEnvelope() {
return boneEnvelope;
}
/**
* @return bone's stretch factor
*/
public float getIkStretch() {
return ikStretch;
}
/**
* @return indicates if the X rotation should be limited
*/
public boolean isLimitX() {
return limits != null ? limits[0] : false;
}
/**
* @return indicates if the Y rotation should be limited
*/
public boolean isLimitY() {
return limits != null ? limits[1] : false;
}
/**
* @return indicates if the Z rotation should be limited
*/
public boolean isLimitZ() {
return limits != null ? limits[2] : false;
}
/**
* @return indicates if the X rotation should be disabled
*/
public boolean isLockX() {
return locks != null ? locks[0] : false;
}
/**
* @return indicates if the Y rotation should be disabled
*/
public boolean isLockY() {
return locks != null ? locks[1] : false;
}
/**
* @return indicates if the Z rotation should be disabled
*/
public boolean isLockZ() {
return locks != null ? locks[2] : false;
}
/**
* @return the minimum values in rotation limitation (if limitation is enabled for specific axis).
*/
public Vector3f getLimitMin() {
return limitMin;
}
/**
* @return the maximum values in rotation limitation (if limitation is enabled for specific axis).
*/
public Vector3f getLimitMax() {
return limitMax;
}
/**
* @return the stiffness of the bone
*/
public Vector3f getStiffness() {
return stiffness;
}
/**
* Tells if the bone is of specified property defined by its flag.
* @param flagMask
* the mask of the flag (constants defined in this class)
* @return <b>true</b> if the bone IS of specified proeprty and <b>false</b> otherwise
*/
public boolean is(int flagMask) {
return (flag & flagMask) != 0;
}
/**
* @return the root bone context of this bone context
*/
public BoneContext getRoot() {
BoneContext result = this;
while (result.parent != null) {
result = result.parent;
}
return result;
}
/**
* @return a number of bones from this bone to its root
*/
public int getDistanceFromRoot() {
int result = 0;
BoneContext boneContext = this;
while (boneContext.parent != null) {
boneContext = boneContext.parent;
++result;
}
return result;
}
@Override
public String toString() {
return "BoneContext: " + boneName;
}
}

View File

@ -1,133 +0,0 @@
package com.jme3.scene.plugins.blender.animations;
import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* An implementation of bone envelope. Used when assigning bones to the mesh by envelopes.
*
* @author Marcin Roguski
*/
public class BoneEnvelope {
/** A defined distance that will be included in the envelope space. */
private float distance;
/** The bone's weight. */
private float weight;
/** The radius of the bone's head. */
private float boneHeadRadius;
/** The radius of the bone's tail. */
private float boneTailRadius;
/** Head position in rest pose in world space. */
private Vector3f head;
/** Tail position in rest pose in world space. */
private Vector3f tail;
/**
* The constructor of bone envelope. It reads all the needed data. Take notice that the positions of head and tail
* are computed in the world space and that the points' positions given for computations should be in world space as well.
*
* @param boneStructure
* the blender bone structure
* @param armatureWorldMatrix
* the world matrix of the armature object
* @param fixUpAxis
* a variable that tells if we use the Y-is up axis orientation
*/
@SuppressWarnings("unchecked")
public BoneEnvelope(Structure boneStructure, Matrix4f armatureWorldMatrix, boolean fixUpAxis) {
distance = ((Number) boneStructure.getFieldValue("dist")).floatValue();
weight = ((Number) boneStructure.getFieldValue("weight")).floatValue();
boneHeadRadius = ((Number) boneStructure.getFieldValue("rad_head")).floatValue();
boneTailRadius = ((Number) boneStructure.getFieldValue("rad_tail")).floatValue();
DynamicArray<Number> headArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_head");
head = new Vector3f(headArray.get(0).floatValue(), headArray.get(1).floatValue(), headArray.get(2).floatValue());
if (fixUpAxis) {
float z = head.z;
head.z = -head.y;
head.y = z;
}
armatureWorldMatrix.mult(head, head);// move the head point to global space
DynamicArray<Number> tailArray = (DynamicArray<Number>) boneStructure.getFieldValue("arm_tail");
tail = new Vector3f(tailArray.get(0).floatValue(), tailArray.get(1).floatValue(), tailArray.get(2).floatValue());
if (fixUpAxis) {
float z = tail.z;
tail.z = -tail.y;
tail.y = z;
}
armatureWorldMatrix.mult(tail, tail);// move the tail point to global space
}
/**
* The method verifies if the given point is inside the envelope.
* @param point
* the point in 3D space (MUST be in a world coordinate space)
* @return <b>true</b> if the point is inside the envelope and <b>false</b> otherwise
*/
public boolean isInEnvelope(Vector3f point) {
Vector3f v = tail.subtract(head);
float boneLength = v.length();
v.normalizeLocal();
// computing a plane that contains 'point' and v is its normal vector
// the plane's equation is: Ax + By + Cz + D = 0, where v = [A, B, C]
float D = -v.dot(point);
// computing a point where a line that contains head and tail crosses the plane
float temp = -(v.dot(head) + D) / v.dot(v);
Vector3f p = head.add(v.x * temp, v.y * temp, v.z * temp);
// determining if the point p is on the same or other side of head than the tail point
Vector3f headToPointOnLineVector = p.subtract(head);
float headToPointLength = headToPointOnLineVector.length();
float cosinus = headToPointOnLineVector.dot(v) / headToPointLength;// the length of v is already = 1; cosinus should be either 1, 0 or -1
if (cosinus < 0 && headToPointLength > boneHeadRadius || headToPointLength > boneLength + boneTailRadius) {
return false;// the point is outside the anvelope
}
// now check if the point is inside and envelope
float pointDistanceFromLine = point.subtract(p).length(), maximumDistance = 0;
if (cosinus < 0) {
// checking if the distance from p to point is inside the half sphere defined by head envelope
// compute the distance from the line to the half sphere border
maximumDistance = boneHeadRadius;
} else if (headToPointLength < boneLength) {
// compute the maximum available distance
if (boneTailRadius > boneHeadRadius) {
// compute the distance from head to p
float headToPDistance = p.subtract(head).length();
// from tangens function we have
float x = headToPDistance * ((boneTailRadius - boneHeadRadius) / boneLength);
maximumDistance = x + boneHeadRadius;
} else if (boneTailRadius < boneHeadRadius) {
// compute the distance from head to p
float tailToPDistance = p.subtract(tail).length();
// from tangens function we have
float x = tailToPDistance * ((boneHeadRadius - boneTailRadius) / boneLength);
maximumDistance = x + boneTailRadius;
} else {
maximumDistance = boneTailRadius;
}
} else {
// checking if the distance from p to point is inside the half sphere defined by tail envelope
maximumDistance = boneTailRadius;
}
return pointDistanceFromLine <= maximumDistance + distance;
}
/**
* @return the weight of the bone
*/
public float getWeight() {
return weight;
}
@Override
public String toString() {
return "BoneEnvelope [d=" + distance + ", w=" + weight + ", hr=" + boneHeadRadius + ", tr=" + boneTailRadius + ", (" + head + ") -> (" + tail + ")]";
}
}

View File

@ -1,317 +0,0 @@
package com.jme3.scene.plugins.blender.animations;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.curves.BezierCurve;
/**
* This class is used to calculate bezier curves value for the given frames. The
* Ipo (interpolation object) consists of several b-spline curves (connected 3rd
* degree bezier curves) of a different type.
*
* @author Marcin Roguski
*/
public class Ipo {
private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName());
public static final int AC_LOC_X = 1;
public static final int AC_LOC_Y = 2;
public static final int AC_LOC_Z = 3;
public static final int OB_ROT_X = 7;
public static final int OB_ROT_Y = 8;
public static final int OB_ROT_Z = 9;
public static final int AC_SIZE_X = 13;
public static final int AC_SIZE_Y = 14;
public static final int AC_SIZE_Z = 15;
public static final int AC_QUAT_W = 25;
public static final int AC_QUAT_X = 26;
public static final int AC_QUAT_Y = 27;
public static final int AC_QUAT_Z = 28;
/** A list of bezier curves for this interpolation object. */
private BezierCurve[] bezierCurves;
/** Each ipo contains one bone track. */
private Track calculatedTrack;
/** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis;
/**
* Depending on the blender version rotations are stored in degrees or
* radians so we need to know the version that is used.
*/
protected final int blenderVersion;
/**
* Constructor. Stores the bezier curves.
*
* @param bezierCurves
* a table of bezier curves
* @param fixUpAxis
* indicates if the Y is the up axis or not
* @param blenderVersion
* the blender version that is currently used
*/
public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) {
this.bezierCurves = bezierCurves;
this.fixUpAxis = fixUpAxis;
this.blenderVersion = blenderVersion;
}
/**
* This method calculates the ipo value for the first curve.
*
* @param frame
* the frame for which the value is calculated
* @return calculated ipo value
*/
public double calculateValue(int frame) {
return this.calculateValue(frame, 0);
}
/**
* This method calculates the ipo value for the curve of the specified
* index. Make sure you do not exceed the curves amount. Alway chech the
* amount of curves before calling this method.
*
* @param frame
* the frame for which the value is calculated
* @param curveIndex
* the index of the curve
* @return calculated ipo value
*/
public double calculateValue(int frame, int curveIndex) {
return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE);
}
/**
* This method returns the frame where last bezier triple center point of
* the specified bezier curve is located.
*
* @return the frame number of the last defined bezier triple point for the
* specified ipo
*/
public int getLastFrame() {
int result = 1;
for (int i = 0; i < bezierCurves.length; ++i) {
int tempResult = bezierCurves[i].getLastFrame();
if (tempResult > result) {
result = tempResult;
}
}
return result;
}
/**
* This method calculates the value of the curves as a bone track between
* the specified frames.
*
* @param targetIndex
* the index of the target for which the method calculates the
* tracks IMPORTANT! Aet to -1 (or any negative number) if you
* want to load spatial animation.
* @param localTranslation
* the local translation of the object/bone that will be animated by
* the track
* @param localRotation
* the local rotation of the object/bone that will be animated by
* the track
* @param localScale
* the local scale of the object/bone that will be animated by
* the track
* @param startFrame
* the first frame of tracks (inclusive)
* @param stopFrame
* the last frame of the tracks (inclusive)
* @param fps
* frame rate (frames per second)
* @param spatialTrack
* this flag indicates if the track belongs to a spatial or to a
* bone; the difference is important because it appears that bones
* in blender have the same type of coordinate system (Y as UP)
* as jme while other features have different one (Z is UP)
* @return bone track for the specified bone
*/
public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) {
if (calculatedTrack == null) {
// preparing data for track
int framesAmount = stopFrame - startFrame;
float timeBetweenFrames = 1.0f / fps;
float[] times = new float[framesAmount + 1];
Vector3f[] translations = new Vector3f[framesAmount + 1];
float[] translation = new float[3];
Quaternion[] rotations = new Quaternion[framesAmount + 1];
float[] quaternionRotation = new float[] { localRotation.getX(), localRotation.getY(), localRotation.getZ(), localRotation.getW(), };
float[] eulerRotation = localRotation.toAngles(null);
Vector3f[] scales = new Vector3f[framesAmount + 1];
float[] scale = new float[] { localScale.x, localScale.y, localScale.z };
float degreeToRadiansFactor = 1;
if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees
degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here
}
int yIndex = 1, zIndex = 2;
boolean swapAxes = spatialTrack && fixUpAxis;
if (swapAxes) {
yIndex = 2;
zIndex = 1;
}
boolean eulerRotationUsed = false, queternionRotationUsed = false;
// calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) {
boolean translationSet = false;
translation[0] = translation[1] = translation[2] = 0;
int index = frame - startFrame;
times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) {
// LOCATION
case AC_LOC_X:
translation[0] = (float) value;
translationSet = true;
break;
case AC_LOC_Y:
if (swapAxes && value != 0) {
value = -value;
}
translation[yIndex] = (float) value;
translationSet = true;
break;
case AC_LOC_Z:
translation[zIndex] = (float) value;
translationSet = true;
break;
// EULER ROTATION
case OB_ROT_X:
eulerRotationUsed = true;
eulerRotation[0] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Y:
eulerRotationUsed = true;
if (swapAxes && value != 0) {
value = -value;
}
eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Z:
eulerRotationUsed = true;
eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
break;
// SIZE
case AC_SIZE_X:
scale[0] = (float) value;
break;
case AC_SIZE_Y:
scale[yIndex] = (float) value;
break;
case AC_SIZE_Z:
scale[zIndex] = (float) value;
break;
// QUATERNION ROTATION (used with bone animation)
case AC_QUAT_W:
queternionRotationUsed = true;
quaternionRotation[3] = (float) value;
break;
case AC_QUAT_X:
queternionRotationUsed = true;
quaternionRotation[0] = (float) value;
break;
case AC_QUAT_Y:
queternionRotationUsed = true;
if (swapAxes && value != 0) {
value = -value;
}
quaternionRotation[yIndex] = (float) value;
break;
case AC_QUAT_Z:
quaternionRotation[zIndex] = (float) value;
break;
default:
LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType());
}
}
if(translationSet) {
translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
} else {
translations[index] = new Vector3f();
}
if(boneContext != null) {
if(boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) {
float temp = translations[index].z;
translations[index].z = -translations[index].y;
translations[index].y = temp;
}
}
if (queternionRotationUsed) {
rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
} else {
rotations[index] = new Quaternion().fromAngles(eulerRotation);
}
scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
}
if (spatialTrack) {
calculatedTrack = new SpatialTrack(times, translations, rotations, scales);
} else {
calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
}
if (queternionRotationUsed && eulerRotationUsed) {
LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
}
}
return calculatedTrack;
}
/**
* Ipo constant curve. This is a curve with only one value and no specified
* type. This type of ipo cannot be used to calculate tracks. It should only
* be used to calculate single value for a given frame.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */static class ConstIpo extends Ipo {
/** The constant value of this ipo. */
private float constValue;
/**
* Constructor. Stores the constant value of this ipo.
*
* @param constValue
* the constant value of this ipo
*/
public ConstIpo(float constValue) {
super(null, false, 0);// the version is not important here
this.constValue = constValue;
}
@Override
public double calculateValue(int frame) {
return constValue;
}
@Override
public double calculateValue(int frame, int curveIndex) {
return constValue;
}
@Override
public BoneTrack calculateTrack(int boneIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
}
}
}

View File

@ -1,148 +0,0 @@
package com.jme3.scene.plugins.blender.cameras;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.renderer.Camera;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that is used to load cameras into the scene.
* @author Marcin Roguski
*/
public class CameraHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName());
protected static final int DEFAULT_CAM_WIDTH = 640;
protected static final int DEFAULT_CAM_HEIGHT = 480;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions.
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public CameraHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
/**
* This method converts the given structure to jme camera.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
if (blenderVersion >= 250) {
return this.toCamera250(structure, blenderContext.getSceneStructure());
} else {
return this.toCamera249(structure);
}
}
/**
* This method converts the given structure to jme camera. Should be used form blender 2.5+.
*
* @param structure
* camera structure
* @param sceneStructure
* scene structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException {
int width = DEFAULT_CAM_WIDTH;
int height = DEFAULT_CAM_HEIGHT;
if (sceneStructure != null) {
Structure renderData = (Structure) sceneStructure.getFieldValue("r");
width = ((Number) renderData.getFieldValue("xsch")).shortValue();
height = ((Number) renderData.getFieldValue("ysch")).shortValue();
}
Camera camera = new Camera(width, height);
int type = ((Number) structure.getFieldValue("type")).intValue();
if (type != 0 && type != 1) {
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
type = 0;
}
// type==0 - perspective; type==1 - orthographic; perspective is used as default
camera.setParallelProjection(type == 1);
float aspect = width / (float) height;
float fovY; // Vertical field of view in degrees
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
if (type == 0) {
// Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get()
// Default sensor size prior to 2.60 was 32.
float sensor = 32.0f;
boolean sensorVertical = false;
Number sensorFit = (Number) structure.getFieldValue("sensor_fit");
if (sensorFit != null) {
// If sensor_fit is vert (2), then sensor_y is used
sensorVertical = sensorFit.byteValue() == 2;
String sensorName = "sensor_x";
if (sensorVertical) {
sensorName = "sensor_y";
}
sensor = ((Number) structure.getFieldValue(sensorName)).floatValue();
}
float focalLength = ((Number) structure.getFieldValue("lens")).floatValue();
float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength);
if (sensorVertical) {
fovY = fov * FastMath.RAD_TO_DEG;
} else {
// Convert fov from horizontal to vertical
fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG;
}
} else {
// This probably is not correct.
fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
}
camera.setFrustumPerspective(fovY, aspect, clipsta, clipend);
camera.setName(structure.getName());
return camera;
}
/**
* This method converts the given structure to jme camera. Should be used form blender 2.49.
*
* @param structure
* camera structure
* @return jme camera object
* @throws BlenderFileException
* an exception is thrown when there are problems with the
* blender file
*/
private Camera toCamera249(Structure structure) throws BlenderFileException {
Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT);
int type = ((Number) structure.getFieldValue("type")).intValue();
if (type != 0 && type != 1) {
LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type);
type = 0;
}
// type==0 - perspective; type==1 - orthographic; perspective is used as default
camera.setParallelProjection(type == 1);
float aspect = 0;
float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue();
float clipend = ((Number) structure.getFieldValue("clipend")).floatValue();
if (type == 0) {
aspect = ((Number) structure.getFieldValue("lens")).floatValue();
} else {
aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue();
}
camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend);
camera.setName(structure.getName());
return camera;
}
}

View File

@ -1,88 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
/**
* Constraint applied on the bone.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class BoneConstraint extends Constraint {
private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName());
/**
* The bone constraint constructor.
*
* @param constraintStructure
* the constraint's structure
* @param ownerOMA
* the OMA of the bone that owns the constraint
* @param influenceIpo
* the influence interpolation curve
* @param blenderContext
* the blender context
* @throws BlenderFileException
* exception thrown when problems with blender file occur
*/
public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
public boolean validate() {
if (targetOMA != null) {
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if (nodeTarget == null) {
LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name);
return false;
}
// the second part of the if expression verifies if the found node
// (if any) is an armature node
if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
if (subtargetName.trim().isEmpty()) {
LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name);
return false;
}
// if the target is not an object node then it is an Armature,
// so make sure the bone is in the current skeleton
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) {
LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name);
return false;
}
}
}
return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
}
@Override
public void apply(int frame) {
super.apply(frame);
blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms();
}
@Override
public Long getTargetOMA() {
if(targetOMA != null && subtargetName != null && !subtargetName.trim().isEmpty()) {
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if(nodeTarget != null) {
if(blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
BoneContext boneContext = blenderContext.getBoneByName(targetOMA, subtargetName);
return boneContext != null ? boneContext.getBoneOma() : 0L;
}
return targetOMA;
}
}
return 0L;
}
}

View File

@ -1,186 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition;
import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinitionFactory;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* The implementation of a constraint.
*
* @author Marcin Roguski (Kaelthas)
*/
public abstract class Constraint {
private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName());
/** The name of this constraint. */
protected final String name;
/** Indicates if the constraint is already baked or not. */
protected boolean baked;
protected Space ownerSpace;
protected final ConstraintDefinition constraintDefinition;
protected Long ownerOMA;
protected Long targetOMA;
protected Space targetSpace;
protected String subtargetName;
/** The ipo object defining influence. */
protected final Ipo ipo;
/** The blender context. */
protected final BlenderContext blenderContext;
protected final ConstraintHelper constraintHelper;
/**
* This constructor creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA
* the old memory address of the constraint owner
* @param influenceIpo
* the ipo curve of the influence factor
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
this.blenderContext = blenderContext;
name = constraintStructure.getFieldValue("name").toString();
Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
if (pData.isNotNull()) {
Structure data = pData.fetchData().get(0);
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, name, ownerOMA, blenderContext);
Pointer pTar = (Pointer) data.getFieldValue("tar");
if (pTar != null && pTar.isNotNull()) {
targetOMA = pTar.getOldMemoryAddress();
targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
Object subtargetValue = data.getFieldValue("subtarget");
if (subtargetValue != null) {// not all constraint data have the
// subtarget field
subtargetName = subtargetValue.toString();
}
}
} else {
// Null constraint has no data, so create it here
constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, name, null, blenderContext);
}
ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
ipo = influenceIpo;
this.ownerOMA = ownerOMA;
constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition });
}
/**
* @return <b>true</b> if the constraint is implemented and <b>false</b>
* otherwise
*/
public boolean isImplemented() {
return constraintDefinition == null ? true : constraintDefinition.isImplemented();
}
/**
* @return the name of the constraint type, similar to the constraint name
* used in Blender
*/
public String getConstraintTypeName() {
return constraintDefinition.getConstraintTypeName();
}
/**
* @return the OMAs of the features whose transform had been altered beside the constraint owner
*/
public Set<Long> getAlteredOmas() {
return constraintDefinition.getAlteredOmas();
}
/**
* Performs validation before baking. Checks factors that can prevent
* constraint from baking that could not be checked during constraint
* loading.
*/
public abstract boolean validate();
/**
* @return the OMA of the target or 0 if no target is specified for the constraint
*/
public abstract Long getTargetOMA();
/**
* Applies the constraint to owner (and in some cases can alter other bones of the skeleton).
* @param frame
* the frame of the animation
*/
public void apply(int frame) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Applying constraint: {0} for frame {1}", new Object[] { name, frame });
}
Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float) ipo.calculateValue(frame));
}
/**
* @return determines if the definition of the constraint will change the bone in any way; in most cases
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
* computing to improve the computation speed and lower the computations complexity
*/
public boolean isTrackToBeChanged() {
return constraintDefinition == null ? false : constraintDefinition.isTrackToBeChanged();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (name == null ? 0 : name.hashCode());
result = prime * result + (ownerOMA == null ? 0 : ownerOMA.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Constraint other = (Constraint) obj;
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
if (ownerOMA == null) {
if (other.ownerOMA != null) {
return false;
}
} else if (!ownerOMA.equals(other.ownerOMA)) {
return false;
}
return true;
}
@Override
public String toString() {
return "Constraint(name = " + name + ", def = " + constraintDefinition + ")";
}
}

View File

@ -1,476 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.AnimationHelper;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.TempVars;
/**
* This class should be used for constraint calculations.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
/**
* Helper constructor.
*
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
/**
* This method reads constraints for for the given structure. The
* constraints are loaded only once for object/bone.
*
* @param objectStructure
* the structure we read constraint's for
* @param blenderContext
* the blender context
* @throws BlenderFileException
*/
public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Loading constraints.");
// reading influence ipos for the constraints
AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
Map<String, Map<String, Ipo>> constraintsIpos = new HashMap<String, Map<String, Ipo>>();
Pointer pActions = (Pointer) objectStructure.getFieldValue("action");
if (pActions.isNotNull()) {
List<Structure> actions = pActions.fetchData();
for (Structure action : actions) {
Structure chanbase = (Structure) action.getFieldValue("chanbase");
List<Structure> actionChannels = chanbase.evaluateListBase();
for (Structure actionChannel : actionChannels) {
Map<String, Ipo> ipos = new HashMap<String, Ipo>();
Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels");
List<Structure> constraintChannels = constChannels.evaluateListBase();
for (Structure constraintChannel : constraintChannels) {
Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo");
if (pIpo.isNotNull()) {
String constraintName = constraintChannel.getFieldValue("name").toString();
Ipo ipo = animationHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext);
ipos.put(constraintName, ipo);
}
}
String actionName = actionChannel.getFieldValue("name").toString();
constraintsIpos.put(actionName, ipos);
}
}
}
// loading constraints connected with the object's bones
Pointer pPose = (Pointer) objectStructure.getFieldValue("pose");
if (pPose.isNotNull()) {
List<Structure> poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase();
for (Structure poseChannel : poseChannels) {
List<Constraint> constraintsList = new ArrayList<Constraint>();
Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());
// the name is read directly from structure because bone might
// not yet be loaded
String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase();
for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
Map<String, Ipo> ipoMap = constraintsIpos.get(name);
Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName);
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = animationHelper.fromValue(enforce);
}
constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext));
}
blenderContext.addConstraints(boneOMA, constraintsList);
}
}
// loading constraints connected with the object itself
List<Structure> constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase();
if (constraints != null && constraints.size() > 0) {
Pointer pData = (Pointer) objectStructure.getFieldValue("data");
String dataType = pData.isNotNull() ? pData.fetchData().get(0).getType() : null;
List<Constraint> constraintsList = new ArrayList<Constraint>(constraints.size());
for (Structure constraint : constraints) {
String constraintName = constraint.getFieldValue("name").toString();
String objectName = objectStructure.getName();
Map<String, Ipo> objectConstraintsIpos = constraintsIpos.get(objectName);
Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null;
if (ipo == null) {
float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue();
ipo = animationHelper.fromValue(enforce);
}
constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
}
blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
}
}
/**
* This method creates a proper constraint object depending on the object's
* data type. Supported data types: <li>Mesh <li>Armature <li>Camera <li>
* Lamp Bone constraints are created in a different place.
*
* @param dataType
* the type of the object's data
* @param constraintStructure
* the constraint structure
* @param ownerOMA
* the owner OMA
* @param influenceIpo
* the influence interpolation curve
* @param blenderContext
* the blender context
* @return constraint object for the required type
* @throws BlenderFileException
* thrown when problems with blender file occurred
*/
private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else if ("Armature".equalsIgnoreCase(dataType)) {
return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else {
throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType);
}
}
/**
* The method bakes all available and valid constraints.
*
* @param blenderContext
* the blender context
*/
public void bakeConstraints(BlenderContext blenderContext) {
Set<Long> owners = new HashSet<Long>();
for (Constraint constraint : blenderContext.getAllConstraints()) {
if(constraint instanceof BoneConstraint) {
BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
owners.add(boneContext.getArmatureObjectOMA());
} else {
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
while (spatial.getParent() != null) {
spatial = spatial.getParent();
}
owners.add((Long)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial));
}
}
List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>(owners.size());
for(Long ownerOMA : owners) {
simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext));
}
for (SimulationNode node : simulationRootNodes) {
node.simulate();
}
}
/**
* The method retrieves the transform from a feature in a given space.
*
* @param oma
* the OMA of the feature (spatial or armature node)
* @param subtargetName
* the feature's subtarget (bone in a case of armature's node)
* @param space
* the space the transform is evaluated to
* @return the transform of a feature in a given space
*/
public Transform getTransform(Long oma, String subtargetName, Space space) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
blenderContext.getSkeleton(oma).updateWorldVectors();
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
Bone bone = targetBoneContext.getBone();
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
space = Space.CONSTRAINT_SPACE_POSE;
}
TempVars tempVars = TempVars.get();// use readable names of the matrices so that the code is more clear
Transform result;
switch (space) {
case CONSTRAINT_SPACE_WORLD:
Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE);
Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4);
Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42);
Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix);
result = new Transform(boneMatrixInWorldSpace.toTranslationVector(), boneMatrixInWorldSpace.toRotationQuat(), boneMatrixInWorldSpace.toScaleVector());
break;
case CONSTRAINT_SPACE_LOCAL:
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
result = new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
break;
case CONSTRAINT_SPACE_POSE: {
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
break;
}
case CONSTRAINT_SPACE_PARLOCAL: {
Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4);
Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal();
Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix);
result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector());
Bone parent = bone.getParent();
if(parent != null) {
BoneContext parentContext = blenderContext.getBoneContext(parent);
Vector3f head = parent.getModelSpacePosition();
Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(parentContext.getLength())));
result.getTranslation().subtractLocal(tail);
}
break;
}
default:
throw new IllegalStateException("Unknown space type: " + space);
}
tempVars.release();
return result;
} else {
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
return feature.getLocalTransform();
case CONSTRAINT_SPACE_WORLD:
return feature.getWorldTransform();
case CONSTRAINT_SPACE_PARLOCAL:
case CONSTRAINT_SPACE_POSE:
throw new IllegalStateException("Nodes can have only Local and World spaces applied!");
default:
throw new IllegalStateException("Unknown space type: " + space);
}
}
}
/**
* Applies transform to a feature (bone or spatial). Computations transform
* the given transformation from the given space to the feature's local
* space.
*
* @param oma
* the OMA of the feature we apply transformation to
* @param subtargetName
* the name of the feature's subtarget (bone in case of armature)
* @param space
* the space in which the given transform is to be applied
* @param transform
* the transform we apply
*/
public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE);
boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null;
if (isArmature) {
Skeleton skeleton = blenderContext.getSkeleton(oma);
BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName);
Bone bone = targetBoneContext.getBone();
if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) {
space = Space.CONSTRAINT_SPACE_POSE;
}
TempVars tempVars = TempVars.get();
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!";
bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
break;
case CONSTRAINT_SPACE_WORLD: {
Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform, tempVars.tempMat4);
Matrix4f modelWorldMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42);
Matrix4f boneMatrixInModelSpace = modelWorldMatrix.invertLocal().multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
}
case CONSTRAINT_SPACE_POSE: {
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace);
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
}
case CONSTRAINT_SPACE_PARLOCAL:
Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4);
Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42));
Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal();
Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace);
Bone parent = bone.getParent();
if (parent != null) {
//first add the initial parent matrix to the bone's model matrix
BoneContext parentContext = blenderContext.getBoneContext(parent);
Matrix4f initialParentMatrixInModelSpace = parentContext.getBoneMatrixInModelSpace();
Matrix4f currentParentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4);
//the bone will now move with its parent in model space
//now we need to subtract the difference between current parent's model matrix and its initial model matrix
boneMatrixInModelSpace = initialParentMatrixInModelSpace.mult(boneMatrixInModelSpace);
Matrix4f diffMatrix = initialParentMatrixInModelSpace.mult(currentParentMatrixInModelSpace.invert());
boneMatrixInModelSpace.multLocal(diffMatrix);
//now the bone will have its position in model space with initial parent's model matrix added
}
bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector());
break;
default:
tempVars.release();
throw new IllegalStateException("Invalid space type for target object: " + space.toString());
}
tempVars.release();
skeleton.updateWorldVectors();
} else {
switch (space) {
case CONSTRAINT_SPACE_LOCAL:
feature.getLocalTransform().set(transform);
break;
case CONSTRAINT_SPACE_WORLD:
if (feature.getParent() == null) {
feature.setLocalTransform(transform);
} else {
Transform parentWorldTransform = feature.getParent().getWorldTransform();
TempVars tempVars = TempVars.get();
Matrix4f parentInverseMatrix = this.toMatrix(parentWorldTransform, tempVars.tempMat4).invertLocal();
Matrix4f m = this.toMatrix(transform, tempVars.tempMat42);
m = m.multLocal(parentInverseMatrix);
tempVars.release();
transform.setTranslation(m.toTranslationVector());
transform.setRotation(m.toRotationQuat());
transform.setScale(m.toScaleVector());
feature.setLocalTransform(transform);
}
break;
default:
throw new IllegalStateException("Invalid space type for spatial object: " + space.toString());
}
}
}
/**
* Converts given transform to the matrix.
*
* @param transform
* the transform to be converted
* @param store
* the matrix where the result will be stored
* @return the store matrix
*/
public Matrix4f toMatrix(Transform transform, Matrix4f store) {
if (transform != null) {
return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), store);
}
store.loadIdentity();
return store;
}
/**
* Converts given transformation parameters into the matrix.
*
* @param position
* the position of the feature
* @param rotation
* the rotation of the feature
* @param scale
* the scale of the feature
* @param store
* the matrix where the result will be stored
* @return the store matrix
*/
private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale, Matrix4f store) {
store.loadIdentity();
store.setTranslation(position);
store.setRotationQuaternion(rotation);
store.setScale(scale);
return store;
}
/**
* The space of target or owner transformation.
*
* @author Marcin Roguski (Kaelthas)
*/
public static enum Space {
/** A transformation of the bone or spatial in the world space. */
CONSTRAINT_SPACE_WORLD,
/**
* For spatial it is the transformation in its parent space or in WORLD space if it has no parent.
* For bone it is a transformation in its bone parent space or in armature space if it has no parent.
*/
CONSTRAINT_SPACE_LOCAL,
/**
* This space IS NOT applicable for spatials.
* For bone it is a transformation in the blender's armature object space.
*/
CONSTRAINT_SPACE_POSE,
CONSTRAINT_SPACE_PARLOCAL;
/**
* This method returns the enum instance when given the appropriate
* value from the blend file.
*
* @param c
* the blender's value of the space modifier
* @return the scape enum instance
*/
public static Space valueOf(byte c) {
switch (c) {
case 0:
return CONSTRAINT_SPACE_WORLD;
case 1:
return CONSTRAINT_SPACE_LOCAL;
case 2:
return CONSTRAINT_SPACE_POSE;
case 3:
return CONSTRAINT_SPACE_PARLOCAL;
default:
throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!");
}
}
}
}

View File

@ -1,397 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Logger;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SpatialTrack;
import com.jme3.animation.Track;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.TempVars;
/**
* A node that represents either spatial or bone in constraint simulation. The
* node is applied its translation, rotation and scale for each frame of its
* animation. Then the constraints are applied that will eventually alter it.
* After that the feature's transformation is stored in VirtualTrack which is
* converted to new bone or spatial track at the very end.
*
* @author Marcin Roguski (Kaelthas)
*/
public class SimulationNode {
private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName());
private Long featureOMA;
/** The blender context. */
private BlenderContext blenderContext;
/** The name of the node (for debugging purposes). */
private String name;
/** A list of children for the node (either bones or child spatials). */
private List<SimulationNode> children = new ArrayList<SimulationNode>();
/** A list of node's animations. */
private List<Animation> animations;
/** The nodes spatial (if null then the boneContext should be set). */
private Spatial spatial;
/** The skeleton of the bone (not null if the node simulated the bone). */
private Skeleton skeleton;
/** Animation controller for the node's feature. */
private AnimControl animControl;
/**
* The star transform of a spatial. Needed to properly reset the spatial to
* its start position.
*/
private Transform spatialStartTransform;
/** Star transformations for bones. Needed to properly reset the bones. */
private Map<Bone, Transform> boneStartTransforms;
/**
* Builds the nodes tree for the given feature. The feature (bone or
* spatial) is found by its OMA. The feature must be a root bone or a root
* spatial.
*
* @param featureOMA
* the OMA of either bone or spatial
* @param blenderContext
* the blender context
*/
public SimulationNode(Long featureOMA, BlenderContext blenderContext) {
this(featureOMA, blenderContext, true);
}
/**
* Creates the node for the feature.
*
* @param featureOMA
* the OMA of either bone or spatial
* @param blenderContext
* the blender context
* @param rootNode
* indicates if the feature is a root bone or root spatial or not
*/
private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
this.featureOMA = featureOMA;
this.blenderContext = blenderContext;
Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE);
if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) {
skeleton = blenderContext.getSkeleton(featureOMA);
Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton);
animControl = nodeWithAnimationControl.getControl(AnimControl.class);
boneStartTransforms = new HashMap<Bone, Transform>();
for (int i = 0; i < skeleton.getBoneCount(); ++i) {
Bone bone = skeleton.getBone(i);
boneStartTransforms.put(bone, new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale()));
}
} else {
if (rootNode && spatial.getParent() != null) {
throw new IllegalStateException("Given spatial must be a root node!");
}
this.spatial = spatial;
spatialStartTransform = spatial.getLocalTransform().clone();
}
name = '>' + spatial.getName() + '<';
// add children nodes
if (skeleton != null) {
Node node = blenderContext.getControlledNode(skeleton);
Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
animations = blenderContext.getAnimations(animatedNodeOMA);
} else {
animations = blenderContext.getAnimations(featureOMA);
for (Spatial child : spatial.getChildren()) {
if (child instanceof Node) {
children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false));
}
}
}
}
/**
* Resets the node's feature to its starting transformation.
*/
private void reset() {
if (spatial != null) {
spatial.setLocalTransform(spatialStartTransform);
for (SimulationNode child : children) {
child.reset();
}
} else if (skeleton != null) {
for (Entry<Bone, Transform> entry : boneStartTransforms.entrySet()) {
Transform t = entry.getValue();
entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
entry.getKey().updateModelTransforms();
}
skeleton.reset();
}
}
/**
* Simulates the spatial node.
*/
private void simulateSpatial() {
List<Constraint> constraints = blenderContext.getConstraints(featureOMA);
if (constraints != null && constraints.size() > 0) {
LOGGER.fine("Simulating spatial.");
boolean applyStaticConstraints = true;
if (animations != null) {
for (Animation animation : animations) {
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
int maxFrame = (int) animationTimeBoundaries[0];
float maxTime = animationTimeBoundaries[1];
VirtualTrack vTrack = new VirtualTrack(spatial.getName(), maxFrame, maxTime);
for (Track track : animation.getTracks()) {
for (int frame = 0; frame < maxFrame; ++frame) {
spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]);
spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]);
spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]);
for (Constraint constraint : constraints) {
constraint.apply(frame);
vTrack.setTransform(frame, spatial.getLocalTransform());
}
}
Track newTrack = vTrack.getAsSpatialTrack();
if (newTrack != null) {
animation.removeTrack(track);
animation.addTrack(newTrack);
}
applyStaticConstraints = false;
}
}
}
// if there are no animations then just constraint the static
// object's transformation
if (applyStaticConstraints) {
for (Constraint constraint : constraints) {
constraint.apply(0);
}
}
}
for (SimulationNode child : children) {
child.simulate();
}
}
/**
* Simulates the bone node.
*/
private void simulateSkeleton() {
LOGGER.fine("Simulating skeleton.");
Set<Long> alteredOmas = new HashSet<Long>();
if (animations != null) {
TempVars vars = TempVars.get();
AnimChannel animChannel = animControl.createChannel();
for (Animation animation : animations) {
float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
int maxFrame = (int) animationTimeBoundaries[0];
float maxTime = animationTimeBoundaries[1];
Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
for (int frame = 0; frame < maxFrame; ++frame) {
// this MUST be done here, otherwise setting next frame of animation will
// lead to possible errors
this.reset();
// first set proper time for all bones in all the tracks ...
for (Track track : animation.getTracks()) {
float time = ((BoneTrack) track).getTimes()[frame];
track.setTime(time, 1, animControl, animChannel, vars);
skeleton.updateWorldVectors();
}
// ... and then apply constraints from the root bone to the last child ...
Set<Long> applied = new HashSet<Long>();
for (Bone rootBone : skeleton.getRoots()) {
// ignore the 0-indexed bone
if (skeleton.getBoneIndex(rootBone) > 0) {
this.applyConstraints(rootBone, alteredOmas, applied, frame, new Stack<Bone>());
}
}
// ... add virtual tracks if necessary, for bones that were altered but had no tracks before ...
for (Long boneOMA : alteredOmas) {
BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
if (!tracks.containsKey(boneIndex)) {
tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
}
}
alteredOmas.clear();
// ... and fill in another frame in the result track
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
Bone bone = skeleton.getBone(trackEntry.getKey());
Transform startTransform = boneStartTransforms.get(bone);
// track contains differences between the frame position and bind positions of bones/spatials
Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation());
Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal();
Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale());
trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
}
}
for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey());
if (newTrack != null) {
boolean trackReplaced = false;
for (Track track : animation.getTracks()) {
if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) {
animation.removeTrack(track);
animation.addTrack(newTrack);
trackReplaced = true;
break;
}
}
if (!trackReplaced) {
animation.addTrack(newTrack);
}
}
}
}
vars.release();
animControl.clearChannels();
this.reset();
}
}
/**
* Applies constraints to the given bone and its children.
* The goal is to apply constraint from root bone to the last child.
* @param bone
* the bone whose constraints will be applied
* @param alteredOmas
* the set of OMAS of the altered bones (is populated if necessary)
* @param frame
* the current frame of the animation
* @param bonesStack
* the stack of bones used to avoid infinite loops while applying constraints
*/
private void applyConstraints(Bone bone, Set<Long> alteredOmas, Set<Long> applied, int frame, Stack<Bone> bonesStack) {
if (!bonesStack.contains(bone)) {
bonesStack.push(bone);
BoneContext boneContext = blenderContext.getBoneContext(bone);
if (!applied.contains(boneContext.getBoneOma())) {
List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
if (constraints != null && constraints.size() > 0) {
for (Constraint constraint : constraints) {
if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) {
// first apply constraints of the target bone
BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA());
this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame, bonesStack);
}
constraint.apply(frame);
if (constraint.getAlteredOmas() != null) {
alteredOmas.addAll(constraint.getAlteredOmas());
}
alteredOmas.add(boneContext.getBoneOma());
}
}
applied.add(boneContext.getBoneOma());
}
List<Bone> children = bone.getChildren();
if (children != null && children.size() > 0) {
for (Bone child : bone.getChildren()) {
this.applyConstraints(child, alteredOmas, applied, frame, bonesStack);
}
}
bonesStack.pop();
}
}
/**
* Simulates the node.
*/
public void simulate() {
this.reset();
if (spatial != null) {
this.simulateSpatial();
} else {
this.simulateSkeleton();
}
}
/**
* Computes the maximum frame and time for the animation. Different tracks
* can have different lengths so here the maximum one is being found.
*
* @param animation
* the animation
* @return maximum frame and time of the animation
*/
private float[] computeAnimationTimeBoundaries(Animation animation) {
int maxFrame = Integer.MIN_VALUE;
float maxTime = -Float.MAX_VALUE;
for (Track track : animation.getTracks()) {
if (track instanceof BoneTrack) {
maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);
maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]);
} else if (track instanceof SpatialTrack) {
maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length);
maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]);
} else {
throw new IllegalStateException("Unsupported track type for simuation: " + track);
}
}
return new float[] { maxFrame, maxTime };
}
/**
* Finds constraints for the node's features.
*
* @param ownerOMA
* the feature's OMA
* @param blenderContext
* the blender context
* @return a list of feature's constraints or empty list if none were found
*/
private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
List<Constraint> result = new ArrayList<Constraint>();
List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
if (constraints != null) {
for (Constraint constraint : constraints) {
if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) {
result.add(constraint);
}
// TODO: add proper warnings to some map or set so that they are not logged on every frame
}
}
return result.size() > 0 ? result : null;
}
@Override
public String toString() {
return name;
}
}

View File

@ -1,41 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* Constraint applied on the skeleton. This constraint is here only to make the
* application not crash when loads constraints applied to armature. But
* skeleton movement is not supported by jme so the constraint will never be
* applied.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class SkeletonConstraint extends Constraint {
private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName());
public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
public boolean validate() {
LOGGER.warning("Constraints for skeleton are not supported.");
return false;
}
@Override
public void apply(int frame) {
LOGGER.warning("Applying constraints to skeleton is not supported.");
}
@Override
public Long getTargetOMA() {
LOGGER.warning("Constraints for skeleton are not supported.");
return null;
}
}

View File

@ -1,32 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* Constraint applied on the spatial objects. This includes: nodes, cameras
* nodes and light nodes.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class SpatialConstraint extends Constraint {
public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
}
@Override
public boolean validate() {
if (targetOMA != null) {
return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
}
return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
}
@Override
public Long getTargetOMA() {
return targetOMA;
}
}

View File

@ -1,165 +0,0 @@
package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.SpatialTrack;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
/**
* A virtual track that stores computed frames after constraints are applied.
* Not all the frames need to be inserted. If there are lacks then the class
* will fill the gaps.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class VirtualTrack {
/** The name of the track (for debugging purposes). */
private String name;
/** The last frame for the track. */
public int maxFrame;
/** The max time for the track. */
public float maxTime;
/** Translations of the track. */
public ArrayList<Vector3f> translations;
/** Rotations of the track. */
public ArrayList<Quaternion> rotations;
/** Scales of the track. */
public ArrayList<Vector3f> scales;
/**
* Constructs the object storing the maximum frame and time.
*
* @param maxFrame
* the last frame for the track
* @param maxTime
* the max time for the track
*/
public VirtualTrack(String name, int maxFrame, float maxTime) {
this.name = name;
this.maxFrame = maxFrame;
this.maxTime = maxTime;
}
/**
* Sets the transform for the given frame.
*
* @param frameIndex
* the frame for which the transform will be set
* @param transform
* the transformation to be set
*/
public void setTransform(int frameIndex, Transform transform) {
if (translations == null) {
translations = this.createList(Vector3f.ZERO, frameIndex);
}
this.append(translations, Vector3f.ZERO, frameIndex - translations.size());
translations.add(transform.getTranslation().clone());
if (rotations == null) {
rotations = this.createList(Quaternion.IDENTITY, frameIndex);
}
this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size());
rotations.add(transform.getRotation().clone());
if (scales == null) {
scales = this.createList(Vector3f.UNIT_XYZ, frameIndex);
}
this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size());
scales.add(transform.getScale().clone());
}
/**
* Returns the track as a bone track.
*
* @param targetBoneIndex
* the bone index
* @return the bone track
*/
public BoneTrack getAsBoneTrack(int targetBoneIndex) {
if (translations == null && rotations == null && scales == null) {
return null;
}
return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
}
/**
* Returns the track as a spatial track.
*
* @return the spatial track
*/
public SpatialTrack getAsSpatialTrack() {
if (translations == null && rotations == null && scales == null) {
return null;
}
return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
}
/**
* The method creates times for the track based on the given maximum values.
*
* @return the times for the track
*/
private float[] createTimes() {
float[] times = new float[maxFrame];
float dT = maxTime / maxFrame;
float t = 0;
for (int i = 0; i < maxFrame; ++i) {
times[i] = t;
t += dT;
}
return times;
}
/**
* Helper method that creates a list of a given size filled with given
* elements.
*
* @param element
* the element to be put into the list
* @param count
* the list size
* @return the list
*/
private <T> ArrayList<T> createList(T element, int count) {
ArrayList<T> result = new ArrayList<T>(count);
for (int i = 0; i < count; ++i) {
result.add(element);
}
return result;
}
/**
* Appends the element to the given list.
*
* @param list
* the list where the element will be appended
* @param element
* the element to be appended
* @param count
* how many times the element will be appended
*/
private <T> void append(ArrayList<T> list, T element, int count) {
for (int i = 0; i < count; ++i) {
list.add(element);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(2048);
result.append("TRACK: ").append(name).append('\n');
if (translations != null && translations.size() > 0) {
result.append("TRANSLATIONS: ").append(translations.toString()).append('\n');
}
if (rotations != null && rotations.size() > 0) {
result.append("ROTATIONS: ").append(rotations.toString()).append('\n');
}
if (scales != null && scales.size() > 0) {
result.append("SCALES: ").append(scales.toString()).append('\n');
}
return result.toString();
}
}

View File

@ -1,162 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import java.util.Set;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A base class for all constraint definitions.
*
* @author Marcin Roguski (Kaelthas)
*/
public abstract class ConstraintDefinition {
protected ConstraintHelper constraintHelper;
/** Constraints flag. Used to load user's options applied to the constraint. */
protected int flag;
/** The constraint's owner. Loaded during runtime. */
private Object owner;
/** The blender context. */
protected BlenderContext blenderContext;
/** The constraint's owner OMA. */
protected Long ownerOMA;
/** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */
protected Set<Long> alteredOmas;
/** The variable that determines if the constraint will alter the track in any way. */
protected boolean trackToBeChanged = true;
/** The name of the constraint. */
protected String constraintName;
/**
* Loads a constraint definition based on the constraint definition
* structure.
*
* @param constraintData
* the constraint definition structure
* @param ownerOMA
* the constraint's owner OMA
* @param blenderContext
* the blender context
*/
public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
if (constraintData != null) {// Null constraint has no data
Number flag = (Number) constraintData.getFieldValue("flag");
if (flag != null) {
this.flag = flag.intValue();
}
}
this.blenderContext = blenderContext;
constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class));
this.ownerOMA = ownerOMA;
}
public void setConstraintName(String constraintName) {
this.constraintName = constraintName;
}
/**
* @return determines if the definition of the constraint will change the bone in any way; in most cases
* it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
* computing to improve the computation speed and lower the computations complexity
*/
public boolean isTrackToBeChanged() {
return trackToBeChanged;
}
/**
* @return determines if this constraint definition requires a defined target or not
*/
public abstract boolean isTargetRequired();
/**
* This method is here because we have no guarantee that the owner is loaded
* when constraint is being created. So use it to get the owner when it is
* needed for computations.
*
* @return the owner of the constraint or null if none is set
*/
protected Object getOwner() {
if (ownerOMA != null && owner == null) {
owner = blenderContext.getLoadedFeature(ownerOMA, LoadedDataType.FEATURE);
if (owner == null) {
throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
}
}
return owner;
}
/**
* The method gets the owner's transformation. The owner can be either bone or spatial.
* @param ownerSpace
* the space in which the computed transformation is given
* @return the constraint owner's transformation
*/
protected Transform getOwnerTransform(Space ownerSpace) {
if (this.getOwner() instanceof Bone) {
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
return constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace);
}
return constraintHelper.getTransform(ownerOMA, null, ownerSpace);
}
/**
* The method applies the given transformation to the owner.
* @param ownerTransform
* the transformation to apply to the owner
* @param ownerSpace
* the space that defines which owner's transformation (ie. global, local, etc. will be set)
*/
protected void applyOwnerTransform(Transform ownerTransform, Space ownerSpace) {
if (this.getOwner() instanceof Bone) {
BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform);
} else {
constraintHelper.applyTransform(ownerOMA, null, ownerSpace, ownerTransform);
}
}
/**
* @return <b>true</b> if the definition is implemented and <b>false</b>
* otherwise
*/
public boolean isImplemented() {
return true;
}
/**
* @return a list of all OMAs of the features that the constraint had altered beside its owner
*/
public Set<Long> getAlteredOmas() {
return alteredOmas;
}
/**
* @return the type name of the constraint
*/
public abstract String getConstraintTypeName();
/**
* Bakes the constraint for the current feature (bone or spatial) position.
*
* @param ownerSpace
* the space where owner transform will be evaluated in
* @param targetSpace
* the space where target transform will be evaluated in
* @param targetTransform
* the target transform used by some of the constraints
* @param influence
* the influence of the constraint from range [0; 1]
*/
public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence);
@Override
public String toString() {
return this.getConstraintTypeName();
}
}

View File

@ -1,84 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Dist limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
private static final int LIMITDIST_INSIDE = 0;
private static final int LIMITDIST_OUTSIDE = 1;
private static final int LIMITDIST_ONSURFACE = 2;
protected int mode;
protected float dist;
public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
// distance limit does not work on bones who are connected to their parent
return;
}
if (influence == 0 || targetTransform == null) {
return;// no need to do anything
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
float currentDistance = v.length();
switch (mode) {
case LIMITDIST_INSIDE:
if (currentDistance >= dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
}
break;
case LIMITDIST_ONSURFACE:
if (currentDistance > dist) {
v.normalizeLocal();
v.multLocal(dist + (currentDistance - dist) * (1.0f - influence));
ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation()));
} else if (currentDistance < dist) {
v.normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
case LIMITDIST_OUTSIDE:
if (currentDistance <= dist) {
v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence);
ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v));
}
break;
default:
throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public boolean isTargetRequired() {
return true;
}
@Override
public String getConstraintTypeName() {
return "Limit distance";
}
}

View File

@ -1,126 +0,0 @@
/*
* 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.plugins.blender.constraints.definitions;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
public class ConstraintDefinitionFactory {
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
static {
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class);
CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class);
CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class);
CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class);
CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionIK.class);
CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);// since blender 2.51
CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionMaintainVolume.class);// since blender 2.53
}
private static final Map<String, String> UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>();
static {
UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action");
UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of");
UNSUPPORTED_CONSTRAINTS.put("bClampToConstraint", "Clamp to");
UNSUPPORTED_CONSTRAINTS.put("bFollowPathConstraint", "Follow path");
UNSUPPORTED_CONSTRAINTS.put("bLockTrackConstraint", "Lock track");
UNSUPPORTED_CONSTRAINTS.put("bMinMaxConstraint", "Min max");
UNSUPPORTED_CONSTRAINTS.put("bPythonConstraint", "Python/Script");
UNSUPPORTED_CONSTRAINTS.put("bRigidBodyJointConstraint", "Rigid body joint");
UNSUPPORTED_CONSTRAINTS.put("bShrinkWrapConstraint", "Shrinkwrap");
UNSUPPORTED_CONSTRAINTS.put("bStretchToConstraint", "Stretch to");
UNSUPPORTED_CONSTRAINTS.put("bTransformConstraint", "Transform");
// Blender 2.50+
UNSUPPORTED_CONSTRAINTS.put("bSplineIKConstraint", "Spline inverse kinematics");
UNSUPPORTED_CONSTRAINTS.put("bDampTrackConstraint", "Damp track");
UNSUPPORTED_CONSTRAINTS.put("bPivotConstraint", "Pivot");
// Blender 2.56+
UNSUPPORTED_CONSTRAINTS.put("bTrackToConstraint", "Track to");
// Blender 2.62+
UNSUPPORTED_CONSTRAINTS.put("bCameraSolverConstraint", "Camera solver");
UNSUPPORTED_CONSTRAINTS.put("bObjectSolverConstraint", "Object solver");
UNSUPPORTED_CONSTRAINTS.put("bFollowTrackConstraint", "Follow track");
}
/**
* This method creates the constraint instance.
*
* @param constraintStructure
* the constraint's structure (bConstraint clss in blender 2.49).
* If the value is null the NullConstraint is created.
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the blender file is somehow
* corrupted
*/
public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, String constraintName, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException {
if (constraintStructure == null) {
return new ConstraintDefinitionNull(null, ownerOMA, blenderContext);
}
String constraintClassName = constraintStructure.getType();
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
if (constraintDefinitionClass != null) {
try {
ConstraintDefinition def = (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
def.setConstraintName(constraintName);
return def;
} catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InstantiationException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (IllegalAccessException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (InvocationTargetException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e);
}
} else {
String unsupportedConstraintClassName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
if (unsupportedConstraintClassName != null) {
return new UnsupportedConstraintDefinition(unsupportedConstraintClassName);
} else {
throw new BlenderFileException("Unknown constraint type: " + constraintClassName);
}
}
}
}

View File

@ -1,236 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import org.ejml.simple.SimpleMatrix;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.math.DQuaternion;
import com.jme3.scene.plugins.blender.math.DTransform;
import com.jme3.scene.plugins.blender.math.Matrix;
import com.jme3.scene.plugins.blender.math.Vector3d;
/**
* A definiotion of a Inverse Kinematics constraint. This implementation uses Jacobian pseudoinverse algorithm.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintDefinitionIK extends ConstraintDefinition {
private static final float MIN_DISTANCE = 0.001f;
private static final float MIN_ANGLE_CHANGE = 0.001f;
private static final int FLAG_USE_TAIL = 0x01;
private static final int FLAG_POSITION = 0x20;
private BonesChain bones;
/** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */
private int bonesAffected;
/** Indicates if the tail of the bone should be used or not. */
private boolean useTail;
/** The amount of iterations of the algorithm. */
private int iterations;
/** The count of bones' chain. */
private int bonesCount = -1;
public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue();
iterations = ((Number) constraintData.getFieldValue("iterations")).intValue();
useTail = (flag & FLAG_USE_TAIL) != 0;
if ((flag & FLAG_POSITION) == 0) {
trackToBeChanged = false;
}
if (trackToBeChanged) {
alteredOmas = new HashSet<Long>();
}
}
/**
* Below are the variables that only need to be allocated once for IK constraint instance.
*/
/** Temporal quaternion. */
private DQuaternion tempDQuaternion = new DQuaternion();
/** Temporal matrix column. */
private Vector3d col = new Vector3d();
/** Effector's position change. */
private Matrix deltaP = new Matrix(3, 1);
/** The current target position. */
private Vector3d target = new Vector3d();
/** Rotation vectors for each joint (allocated when we know the size of a bones' chain. */
private Vector3d[] rotationVectors;
/** The Jacobian matrix. Allocated when the bones' chain size is known. */
private Matrix J;
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !trackToBeChanged || targetTransform == null || bonesCount == 0) {
return;// no need to do anything
}
if (bones == null) {
bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, alteredOmas, blenderContext);
}
if (bones.size() == 0) {
bonesCount = 0;
return;// no need to do anything
}
double distanceFromTarget = Double.MAX_VALUE;
target.set(targetTransform.getTranslation().x, targetTransform.getTranslation().y, targetTransform.getTranslation().z);
if (bonesCount < 0) {
bonesCount = bones.size();
rotationVectors = new Vector3d[bonesCount];
for (int i = 0; i < bonesCount; ++i) {
rotationVectors[i] = new Vector3d();
}
J = new Matrix(3, bonesCount);
}
BoneContext topBone = bones.get(0);
for (int i = 0; i < iterations; ++i) {
DTransform topBoneTransform = bones.getWorldTransform(topBone);
Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
distanceFromTarget = e.distance(target);
if (distanceFromTarget <= MIN_DISTANCE) {
break;
}
deltaP.setColumn(0, 0, target.x - e.x, target.y - e.y, target.z - e.z);
int column = 0;
for (BoneContext boneContext : bones) {
DTransform boneWorldTransform = bones.getWorldTransform(boneContext);
Vector3d j = boneWorldTransform.getTranslation(); // current join position
Vector3d vectorFromJointToEffector = e.subtract(j);
vectorFromJointToEffector.cross(target.subtract(j), rotationVectors[column]).normalizeLocal();
rotationVectors[column].cross(vectorFromJointToEffector, col);
J.setColumn(col, column++);
}
Matrix J_1 = J.pseudoinverse();
SimpleMatrix deltaThetas = J_1.mult(deltaP);
if (deltaThetas.elementMaxAbs() < MIN_ANGLE_CHANGE) {
break;
}
for (int j = 0; j < deltaThetas.numRows(); ++j) {
double angle = deltaThetas.get(j, 0);
Vector3d rotationVector = rotationVectors[j];
tempDQuaternion.fromAngleAxis(angle, rotationVector);
BoneContext boneContext = bones.get(j);
Bone bone = boneContext.getBone();
if (bone.equals(this.getOwner())) {
if (boneContext.isLockX()) {
tempDQuaternion.set(0, tempDQuaternion.getY(), tempDQuaternion.getZ(), tempDQuaternion.getW());
}
if (boneContext.isLockY()) {
tempDQuaternion.set(tempDQuaternion.getX(), 0, tempDQuaternion.getZ(), tempDQuaternion.getW());
}
if (boneContext.isLockZ()) {
tempDQuaternion.set(tempDQuaternion.getX(), tempDQuaternion.getY(), 0, tempDQuaternion.getW());
}
}
DTransform boneTransform = bones.getWorldTransform(boneContext);
boneTransform.getRotation().set(tempDQuaternion.mult(boneTransform.getRotation()));
bones.setWorldTransform(boneContext, boneTransform);
}
}
// applying the results
for (int i = bonesCount - 1; i >= 0; --i) {
BoneContext boneContext = bones.get(i);
DTransform transform = bones.getWorldTransform(boneContext);
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform());
}
bones = null;// need to reload them again
}
@Override
public String getConstraintTypeName() {
return "Inverse kinematics";
}
@Override
public boolean isTargetRequired() {
return true;
}
/**
* Loaded bones' chain. This class allows to operate on transform matrices that use double precision in computations.
* Only the final result is being transformed to single precision numbers.
*
* @author Marcin Roguski (Kaelthas)
*/
private static class BonesChain extends ArrayList<BoneContext> {
private static final long serialVersionUID = -1850524345643600718L;
private List<Matrix> localBonesMatrices = new ArrayList<Matrix>();
public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection<Long> alteredOmas, BlenderContext blenderContext) {
if (bone != null) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
if (!useTail) {
bone = bone.getParent();
}
while (bone != null && (bonesAffected <= 0 || this.size() < bonesAffected)) {
BoneContext boneContext = blenderContext.getBoneContext(bone);
this.add(boneContext);
alteredOmas.add(boneContext.getBoneOma());
Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
localBonesMatrices.add(new DTransform(transform).toMatrix());
bone = bone.getParent();
}
if(localBonesMatrices.size() > 0) {
// making the matrices describe the local transformation
Matrix parentWorldMatrix = localBonesMatrices.get(localBonesMatrices.size() - 1);
for(int i=localBonesMatrices.size() - 2;i>=0;--i) {
SimpleMatrix m = parentWorldMatrix.invert().mult(localBonesMatrices.get(i));
parentWorldMatrix = localBonesMatrices.get(i);
localBonesMatrices.set(i, new Matrix(m));
}
}
}
}
public DTransform getWorldTransform(BoneContext bone) {
int index = this.indexOf(bone);
return this.getWorldMatrix(index).toTransform();
}
public void setWorldTransform(BoneContext bone, DTransform transform) {
int index = this.indexOf(bone);
Matrix boneMatrix = transform.toMatrix();
if (index < this.size() - 1) {
// computing the current bone local transform
Matrix parentWorldMatrix = this.getWorldMatrix(index + 1);
SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix);
boneMatrix = new Matrix(m);
}
localBonesMatrices.set(index, boneMatrix);
}
public Matrix getWorldMatrix(int index) {
if (index == this.size() - 1) {
return new Matrix(localBonesMatrices.get(this.size() - 1));
}
SimpleMatrix result = this.getWorldMatrix(index + 1);
result = result.mult(localBonesMatrices.get(index));
return new Matrix(result);
}
}
}

View File

@ -1,107 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Loc like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
private static final int LOCLIKE_X = 0x01;
private static final int LOCLIKE_Y = 0x02;
private static final int LOCLIKE_Z = 0x04;
// protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in
// blender
private static final int LOCLIKE_X_INVERT = 0x10;
private static final int LOCLIKE_Y_INVERT = 0x20;
private static final int LOCLIKE_Z_INVERT = 0x40;
private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
// swapping Y and X limits flag in the bitwise flag
int y = flag & LOCLIKE_Y;
int invY = flag & LOCLIKE_Y_INVERT;
int z = flag & LOCLIKE_Z;
int invZ = flag & LOCLIKE_Z_INVERT;
// clear the other flags to swap them
flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;
flag |= y << 1;
flag |= invY << 1;
flag |= z >> 1;
flag |= invZ >> 1;
trackToBeChanged = (flag & LOCLIKE_X) != 0 || (flag & LOCLIKE_Y) != 0 || (flag & LOCLIKE_Z) != 0;
}
}
@Override
public boolean isTrackToBeChanged() {
// location copy does not work on bones who are connected to their parent
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || targetTransform == null || !this.isTrackToBeChanged()) {
return;
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation();
Vector3f startLocation = ownerTransform.getTranslation().clone();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location
offset = startLocation;
}
if ((flag & LOCLIKE_X) != 0) {
ownerLocation.x = targetLocation.x;
if ((flag & LOCLIKE_X_INVERT) != 0) {
ownerLocation.x = -ownerLocation.x;
}
}
if ((flag & LOCLIKE_Y) != 0) {
ownerLocation.y = targetLocation.y;
if ((flag & LOCLIKE_Y_INVERT) != 0) {
ownerLocation.y = -ownerLocation.y;
}
}
if ((flag & LOCLIKE_Z) != 0) {
ownerLocation.z = targetLocation.z;
if ((flag & LOCLIKE_Z_INVERT) != 0) {
ownerLocation.z = -ownerLocation.z;
}
}
ownerLocation.addLocal(offset);
if (influence < 1.0f) {
startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
ownerLocation.addLocal(startLocation);
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Copy location";
}
@Override
public boolean isTargetRequired() {
return true;
}
}

View File

@ -1,106 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Loc limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20;
protected float[][] limits = new float[3][2];
public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
// swapping Y and X limits flag in the bitwise flag
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
// them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
}
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
}
@Override
public boolean isTrackToBeChanged() {
// location limit does not work on bones who are connected to their parent
return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !this.isTrackToBeChanged()) {
return;// no need to do anything
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
translation.x -= (translation.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) {
translation.x -= (translation.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) {
translation.y -= (translation.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) {
translation.y -= (translation.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) {
translation.z -= (translation.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
translation.z -= (translation.z - limits[2][1]) * influence;
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Limit location";
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,61 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Maintain volume' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
private static final int FLAG_MASK_X = 0;
private static final int FLAG_MASK_Y = 1;
private static final int FLAG_MASK_Z = 2;
private float volume;
public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue());
trackToBeChanged = volume != 1 && (flag & (FLAG_MASK_X | FLAG_MASK_Y | FLAG_MASK_Z)) != 0;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (trackToBeChanged && influence > 0) {
// the maintain volume constraint is applied directly to object's scale, so no need to do it again
// but in case of bones we need to make computations
if (this.getOwner() instanceof Bone) {
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
switch (flag) {
case FLAG_MASK_X:
ownerTransform.getScale().multLocal(1, volume, volume);
break;
case FLAG_MASK_Y:
ownerTransform.getScale().multLocal(volume, 1, volume);
break;
case FLAG_MASK_Z:
ownerTransform.getScale().multLocal(volume, volume, 1);
break;
default:
throw new IllegalStateException("Unknown flag value: " + flag);
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
}
}
@Override
public String getConstraintTypeName() {
return "Maintain volume";
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,34 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Null' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionNull extends ConstraintDefinition {
public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
trackToBeChanged = false;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
// null constraint does nothing so no need to implement this one
}
@Override
public String getConstraintTypeName() {
return "Null";
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,87 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Rot like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition {
private static final int ROTLIKE_X = 0x01;
private static final int ROTLIKE_Y = 0x02;
private static final int ROTLIKE_Z = 0x04;
private static final int ROTLIKE_X_INVERT = 0x10;
private static final int ROTLIKE_Y_INVERT = 0x20;
private static final int ROTLIKE_Z_INVERT = 0x40;
private static final int ROTLIKE_OFFSET = 0x80;
private transient float[] ownerAngles = new float[3];
private transient float[] targetAngles = new float[3];
public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
trackToBeChanged = (flag & (ROTLIKE_X | ROTLIKE_Y | ROTLIKE_Z)) != 0;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || targetTransform == null || !trackToBeChanged) {
return;// no need to do anything
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Quaternion ownerRotation = ownerTransform.getRotation();
ownerAngles = ownerRotation.toAngles(ownerAngles);
targetAngles = targetTransform.getRotation().toAngles(targetAngles);
Quaternion startRotation = ownerRotation.clone();
Quaternion offset = Quaternion.IDENTITY;
if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to
// the copied rotation
offset = startRotation;
}
if ((flag & ROTLIKE_X) != 0) {
ownerAngles[0] = targetAngles[0];
if ((flag & ROTLIKE_X_INVERT) != 0) {
ownerAngles[0] = -ownerAngles[0];
}
}
if ((flag & ROTLIKE_Y) != 0) {
ownerAngles[1] = targetAngles[1];
if ((flag & ROTLIKE_Y_INVERT) != 0) {
ownerAngles[1] = -ownerAngles[1];
}
}
if ((flag & ROTLIKE_Z) != 0) {
ownerAngles[2] = targetAngles[2];
if ((flag & ROTLIKE_Z_INVERT) != 0) {
ownerAngles[2] = -ownerAngles[2];
}
}
ownerRotation.fromAngles(ownerAngles).multLocal(offset);
if (influence < 1.0f) {
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
// ownerLocation.addLocal(startLocation);
// TODO
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Copy rotation";
}
@Override
public boolean isTargetRequired() {
return true;
}
}

View File

@ -1,129 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.FastMath;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Rot limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition {
private static final int LIMIT_XROT = 0x01;
private static final int LIMIT_YROT = 0x02;
private static final int LIMIT_ZROT = 0x04;
private transient float[][] limits = new float[3][2];
private transient float[] angles = new float[3];
public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
// swapping Y and X limits flag in the bitwise flag
int limitY = flag & LIMIT_YROT;
int limitZ = flag & LIMIT_ZROT;
flag &= LIMIT_XROT;// clear the other flags to swap them
flag |= limitY << 1;
flag |= limitZ >> 1;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
}
// until blender 2.49 the rotations values were stored in degrees
if (blenderContext.getBlenderVersion() <= 249) {
for (int i = 0; i < 3; ++i) {
limits[i][0] *= FastMath.DEG_TO_RAD;
limits[i][1] *= FastMath.DEG_TO_RAD;
}
}
// make sure that the limits are always in range [0, 2PI)
// TODO: left it here because it is essential to make sure all cases
// work poperly
// but will do it a little bit later ;)
/*
* for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int
* multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if
* (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor +
* 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make
* sure the lower limit is not greater than the upper one
* if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
* limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
*/
trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !trackToBeChanged) {
return;
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
ownerTransform.getRotation().toAngles(angles);
// make sure that the rotations are always in range [0, 2PI)
// TODO: same comment as in constructor
/*
* for (int i = 0; i < 3; ++i) { int multFactor =
* (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) {
* angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i]
* -= FastMath.TWO_PI * multFactor; } }
*/
if ((flag & LIMIT_XROT) != 0) {
float difference = 0.0f;
if (angles[0] < limits[0][0]) {
difference = (angles[0] - limits[0][0]) * influence;
} else if (angles[0] > limits[0][1]) {
difference = (angles[0] - limits[0][1]) * influence;
}
angles[0] -= difference;
}
if ((flag & LIMIT_YROT) != 0) {
float difference = 0.0f;
if (angles[1] < limits[1][0]) {
difference = (angles[1] - limits[1][0]) * influence;
} else if (angles[1] > limits[1][1]) {
difference = (angles[1] - limits[1][1]) * influence;
}
angles[1] -= difference;
}
if ((flag & LIMIT_ZROT) != 0) {
float difference = 0.0f;
if (angles[2] < limits[2][0]) {
difference = (angles[2] - limits[2][0]) * influence;
} else if (angles[2] > limits[2][1]) {
difference = (angles[2] - limits[2][1]) * influence;
}
angles[2] -= difference;
}
ownerTransform.getRotation().fromAngles(angles);
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Limit rotation";
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,74 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Size like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition {
private static final int SIZELIKE_X = 0x01;
private static final int SIZELIKE_Y = 0x02;
private static final int SIZELIKE_Z = 0x04;
private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
// swapping Y and X limits flag in the bitwise flag
int y = flag & SIZELIKE_Y;
int z = flag & SIZELIKE_Z;
flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap
// them
flag |= y << 1;
flag |= z >> 1;
trackToBeChanged = (flag & (SIZELIKE_X | SIZELIKE_Y | SIZELIKE_Z)) != 0;
}
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || targetTransform == null || !trackToBeChanged) {
return;// no need to do anything
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f ownerScale = ownerTransform.getScale();
Vector3f targetScale = targetTransform.getScale();
Vector3f offset = Vector3f.ZERO;
if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the
// copied scale
offset = ownerScale.clone();
}
if ((flag & SIZELIKE_X) != 0) {
ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x;
}
if ((flag & SIZELIKE_Y) != 0) {
ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y;
}
if ((flag & SIZELIKE_Z) != 0) {
ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;
}
ownerScale.addLocal(offset);
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Copy scale";
}
@Override
public boolean isTargetRequired() {
return true;
}
}

View File

@ -1,96 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* This class represents 'Size limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
private static final int LIMIT_XMIN = 0x01;
private static final int LIMIT_XMAX = 0x02;
private static final int LIMIT_YMIN = 0x04;
private static final int LIMIT_YMAX = 0x08;
private static final int LIMIT_ZMIN = 0x10;
private static final int LIMIT_ZMAX = 0x20;
protected transient float[][] limits = new float[3][2];
public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
// swapping Y and X limits flag in the bitwise flag
int ymin = flag & LIMIT_YMIN;
int ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX;
flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
// them
flag |= ymin << 2;
flag |= ymax << 2;
flag |= zmin >> 2;
flag |= zmax >> 2;
} else {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
}
trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || !trackToBeChanged) {
return;
}
Transform ownerTransform = this.getOwnerTransform(ownerSpace);
Vector3f scale = ownerTransform.getScale();
if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
scale.x -= (scale.x - limits[0][0]) * influence;
}
if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) {
scale.x -= (scale.x - limits[0][1]) * influence;
}
if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) {
scale.y -= (scale.y - limits[1][0]) * influence;
}
if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) {
scale.y -= (scale.y - limits[1][1]) * influence;
}
if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) {
scale.z -= (scale.z - limits[2][0]) * influence;
}
if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
scale.z -= (scale.z - limits[2][1]) * influence;
}
this.applyOwnerTransform(ownerTransform, ownerSpace);
}
@Override
public String getConstraintTypeName() {
return "Limit scale";
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,81 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.TempVars;
/**
* This class represents 'Trans like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintDefinitionTransLike extends ConstraintDefinition {
private Long targetOMA;
private String subtargetName;
public ConstraintDefinitionTransLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext);
Pointer pTarget = (Pointer) constraintData.getFieldValue("tar");
targetOMA = pTarget.getOldMemoryAddress();
Object subtarget = constraintData.getFieldValue("subtarget");
if (subtarget != null) {
subtargetName = subtarget.toString();
}
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (influence == 0 || targetTransform == null) {
return;// no need to do anything
}
Object target = this.getTarget();// Bone or Node
Object owner = this.getOwner();// Bone or Node
if (!target.getClass().equals(owner.getClass())) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
TempVars tempVars = TempVars.get();
Matrix4f m = constraintHelper.toMatrix(targetTransform, tempVars.tempMat4);
tempVars.tempMat42.set(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX);
if (target instanceof Bone) {
tempVars.tempMat42.invertLocal();
}
m = m.multLocal(tempVars.tempMat42);
tempVars.release();
targetTransform = new Transform(m.toTranslationVector(), m.toRotationQuat(), m.toScaleVector());
}
this.applyOwnerTransform(targetTransform, ownerSpace);
}
/**
* @return the target feature; it is either Node or Bone (vertex group subtarger is not yet supported)
*/
private Object getTarget() {
Object target = blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
if (subtargetName != null && blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, target) != null) {
Skeleton skeleton = blenderContext.getSkeleton(targetOMA);
target = skeleton.getBone(subtargetName);
}
return target;
}
@Override
public String getConstraintTypeName() {
return "Copy transforms";
}
@Override
public boolean isTargetRequired() {
return true;
}
}

View File

@ -1,40 +0,0 @@
package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.math.Transform;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
/**
* This class represents a constraint that is defined by blender but not
* supported by either importer ot jme. It only wirtes down a warning when
* baking is called.
*
* @author Marcin Roguski (Kaelthas)
*/
/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition {
private String typeName;
public UnsupportedConstraintDefinition(String typeName) {
super(null, null, null);
this.typeName = typeName;
trackToBeChanged = false;
}
@Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
}
@Override
public boolean isImplemented() {
return false;
}
@Override
public String getConstraintTypeName() {
return typeName;
}
@Override
public boolean isTargetRequired() {
return false;
}
}

View File

@ -1,172 +0,0 @@
package com.jme3.scene.plugins.blender.curves;
import java.util.ArrayList;
import java.util.List;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize
* floating point operations errors.
* @author Marcin Roguski (Kaelthas)
*/
public class BezierCurve {
private static final int IPO_CONSTANT = 0;
private static final int IPO_LINEAR = 1;
private static final int IPO_BEZIER = 2;
public static final int X_VALUE = 0;
public static final int Y_VALUE = 1;
public static final int Z_VALUE = 2;
/**
* The type of the curve. Describes the data it modifies.
* Used in ipos calculations.
*/
private int type;
/** The dimension of the curve. */
private int dimension;
/** A table of the bezier points. */
private double[][][] bezierPoints;
/** Array that stores a radius for each bezier triple. */
private double[] radiuses;
/** Interpolation types of the bezier triples. */
private int[] interpolations;
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension) {
this(type, bezTriples, dimension, false);
}
@SuppressWarnings("unchecked")
public BezierCurve(final int type, final List<Structure> bezTriples, final int dimension, boolean fixUpAxis) {
if (dimension != 2 && dimension != 3) {
throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!");
}
this.type = type;
this.dimension = dimension;
// first index of the bezierPoints table has the length of triples amount
// the second index points to a table od three points of a bezier triple (handle, point, handle)
// the third index specifies the coordinates of the specific point in a bezier triple
bezierPoints = new double[bezTriples.size()][3][dimension];
radiuses = new double[bezTriples.size()];
interpolations = new int[bezTriples.size()];
int i = 0, j, k;
for (Structure bezTriple : bezTriples) {
DynamicArray<Number> vec = (DynamicArray<Number>) bezTriple.getFieldValue("vec");
for (j = 0; j < 3; ++j) {
for (k = 0; k < dimension; ++k) {
bezierPoints[i][j][k] = vec.get(j, k).doubleValue();
}
if (fixUpAxis && dimension == 3) {
double temp = bezierPoints[i][j][2];
bezierPoints[i][j][2] = -bezierPoints[i][j][1];
bezierPoints[i][j][1] = temp;
}
}
radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue();
interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue();
}
}
/**
* This method evaluates the data for the specified frame. The Y value is returned.
* @param frame
* the frame for which the value is being calculated
* @param valuePart
* this param specifies wheather we should return the X, Y or Z part of the result value; it should have
* one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result
* Z_VALUE - the Z factor of the result
* @return the value of the curve
*/
public double evaluate(int frame, int valuePart) {
for (int i = 0; i < bezierPoints.length - 1; ++i) {
if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) {
double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]);
switch (interpolations[i]) {
case IPO_BEZIER:
double oneMinusT = 1.0f - t;
double oneMinusT2 = oneMinusT * oneMinusT;
double t2 = t * t;
return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t;
case IPO_LINEAR:
return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart];
case IPO_CONSTANT:
return bezierPoints[i][1][valuePart];
default:
throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]);
}
}
}
if (frame < bezierPoints[0][1][0]) {
return bezierPoints[0][1][1];
} else { // frame>bezierPoints[bezierPoints.length-1][1][0]
return bezierPoints[bezierPoints.length - 1][1][1];
}
}
/**
* This method returns the frame where last bezier triple center point of the bezier curve is located.
* @return the frame number of the last defined bezier triple point for the curve
*/
public int getLastFrame() {
return (int) bezierPoints[bezierPoints.length - 1][1][0];
}
/**
* This method returns the type of the bezier curve. The type describes the parameter that this curve modifies
* (ie. LocationX or rotationW of the feature).
* @return the type of the bezier curve
*/
public int getType() {
return type;
}
/**
* The method returns the radius for the required bezier triple.
*
* @param bezierTripleIndex
* index of the bezier triple
* @return radius of the required bezier triple
*/
public double getRadius(int bezierTripleIndex) {
return radiuses[bezierTripleIndex];
}
/**
* This method returns a list of control points for this curve.
* @return a list of control points for this curve.
*/
public List<Vector3f> getControlPoints() {
List<Vector3f> controlPoints = new ArrayList<Vector3f>(bezierPoints.length * 3);
for (int i = 0; i < bezierPoints.length; ++i) {
controlPoints.add(new Vector3f((float) bezierPoints[i][0][0], (float) bezierPoints[i][0][1], (float) bezierPoints[i][0][2]));
controlPoints.add(new Vector3f((float) bezierPoints[i][1][0], (float) bezierPoints[i][1][1], (float) bezierPoints[i][1][2]));
controlPoints.add(new Vector3f((float) bezierPoints[i][2][0], (float) bezierPoints[i][2][1], (float) bezierPoints[i][2][2]));
}
return controlPoints;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n');
for (int i = 0; i < bezierPoints.length; ++i) {
sb.append(this.toStringBezTriple(i)).append('\n');
}
return sb.toString();
}
/**
* This method converts the bezier triple of a specified index into text.
* @param tripleIndex
* index of the triple
* @return text representation of the triple
*/
private String toStringBezTriple(int tripleIndex) {
if (dimension == 2) {
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]";
} else {
return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]";
}
}
}

View File

@ -1,159 +0,0 @@
/*
* 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.plugins.blender.curves;
import java.util.logging.Logger;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
/**
* A class that is used in mesh calculations.
*
* @author Marcin Roguski (Kaelthas)
*/
public class CurvesHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName());
/** Minimum basis U function degree for NURBS curves and surfaces. */
protected int minimumBasisUFunctionDegree = 4;
/** Minimum basis V function degree for NURBS curves and surfaces. */
protected int minimumBasisVFunctionDegree = 4;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions.
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public CurvesHelper(String blenderVersion, BlenderContext blenderContext) {
super(blenderVersion, blenderContext);
}
public CurvesTemporalMesh toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {
CurvesTemporalMesh result = new CurvesTemporalMesh(curveStructure, blenderContext);
if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
LOGGER.fine("Reading custom properties.");
result.setProperties(this.loadProperties(curveStructure, blenderContext));
}
return result;
}
/**
* The method transforms the bevel along the curve.
*
* @param bevel
* the bevel to be transformed
* @param prevPos
* previous curve point
* @param currPos
* current curve point (here the center of the new bevel will be
* set)
* @param nextPos
* next curve point
* @return points of transformed bevel
*/
protected Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) {
bevel = bevel.clone();
// currPos and directionVector define the line in 3D space
Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos);
directionVector.normalizeLocal();
// plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz)
Vector3f planeNormal = null;
if (prevPos != null) {
planeNormal = currPos.subtract(prevPos).normalizeLocal();
if (nextPos != null) {
planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal();
}
} else {
planeNormal = nextPos.subtract(currPos).normalizeLocal();
}
float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz)
// now we need to compute paralell cast of each bevel point on the plane, the leading line is already known
// parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t
// where p = currPos and v = directionVector
// using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross
float temp = planeNormal.dot(directionVector);
for (int i = 0; i < bevel.length; ++i) {
float t = -(planeNormal.dot(bevel[i]) + D) / temp;
if (fixUpAxis) {
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t);
} else {
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t);
}
}
return bevel;
}
/**
* This method transforms the first line of the bevel points positioning it
* on the first point of the curve.
*
* @param startingLinePoints
* the vbevel shape points
* @param firstCurvePoint
* the first curve's point
* @param secondCurvePoint
* the second curve's point
* @return points of transformed bevel
*/
protected Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) {
Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal();
float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_X));
Vector3f rotationVector = Vector3f.UNIT_X.cross(planeNormal).normalizeLocal();
Matrix4f m = new Matrix4f();
m.setRotationQuaternion(new Quaternion().fromAngleAxis(angle, rotationVector));
m.setTranslation(firstCurvePoint);
Vector3f temp = new Vector3f();
Vector3f[] verts = new Vector3f[startingLinePoints.length];
for (int i = 0; i < verts.length; ++i) {
verts[i] = m.mult(startingLinePoints[i], temp).clone();
}
return verts;
}
}

View File

@ -1,890 +0,0 @@
package com.jme3.scene.plugins.blender.curves;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Logger;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.FastMath;
import com.jme3.math.Spline;
import com.jme3.math.Spline.SplineType;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.materials.MaterialContext;
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
import com.jme3.scene.plugins.blender.meshes.Edge;
import com.jme3.scene.plugins.blender.meshes.Face;
import com.jme3.scene.plugins.blender.meshes.TemporalMesh;
import com.jme3.scene.shape.Curve;
import com.jme3.scene.shape.Surface;
import com.jme3.util.BufferUtils;
/**
* A temporal mesh for curves and surfaces. It works in similar way as TemporalMesh for meshes.
* It prepares all necessary lines and faces and allows to apply modifiers just like in regular temporal mesh.
*
* @author Marcin Roguski (Kaelthas)
*/
public class CurvesTemporalMesh extends TemporalMesh {
private static final Logger LOGGER = Logger.getLogger(CurvesTemporalMesh.class.getName());
private static final int TYPE_BEZIER = 0x0001;
private static final int TYPE_NURBS = 0x0004;
private static final int FLAG_3D = 0x0001;
private static final int FLAG_FRONT = 0x0002;
private static final int FLAG_BACK = 0x0004;
private static final int FLAG_FILL_CAPS = 0x4000;
private static final int FLAG_SMOOTH = 0x0001;
protected CurvesHelper curvesHelper;
protected boolean is2D;
protected boolean isFront;
protected boolean isBack;
protected boolean fillCaps;
protected float bevelStart;
protected float bevelEnd;
protected List<BezierLine> beziers = new ArrayList<BezierLine>();
protected CurvesTemporalMesh bevelObject;
protected CurvesTemporalMesh taperObject;
/** The scale that is used if the curve is a bevel or taper curve. */
protected Vector3f scale = new Vector3f(1, 1, 1);
/**
* The constructor creates an empty temporal mesh.
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this will never be thrown here
*/
protected CurvesTemporalMesh(BlenderContext blenderContext) throws BlenderFileException {
super(null, blenderContext, false);
}
/**
* Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface.
* @param curveStructure
* the structure that contains the curve/surface data
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
public CurvesTemporalMesh(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {
this(curveStructure, new Vector3f(1, 1, 1), true, blenderContext);
}
/**
* Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface.
* @param curveStructure
* the structure that contains the curve/surface data
* @param scale
* the scale used if the current curve is used as a bevel curve
* @param loadBevelAndTaper indicates if bevel and taper should be loaded (this is not needed for curves that are loaded to be used as bevel and taper)
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
@SuppressWarnings("unchecked")
private CurvesTemporalMesh(Structure curveStructure, Vector3f scale, boolean loadBevelAndTaper, BlenderContext blenderContext) throws BlenderFileException {
super(curveStructure, blenderContext, false);
name = curveStructure.getName();
curvesHelper = blenderContext.getHelper(CurvesHelper.class);
this.scale = scale;
int flag = ((Number) curveStructure.getFieldValue("flag")).intValue();
is2D = (flag & FLAG_3D) == 0;
if (is2D) {
// TODO: add support for 3D flag
LOGGER.warning("2D flag not yet supported for curves!");
}
isFront = (flag & FLAG_FRONT) != 0;
isBack = (flag & FLAG_BACK) != 0;
fillCaps = (flag & FLAG_FILL_CAPS) != 0;
bevelStart = ((Number) curveStructure.getFieldValue("bevfac1", 0)).floatValue();
bevelEnd = ((Number) curveStructure.getFieldValue("bevfac2", 1)).floatValue();
if (bevelStart > bevelEnd) {
float temp = bevelStart;
bevelStart = bevelEnd;
bevelEnd = temp;
}
LOGGER.fine("Reading nurbs (and sorting them by material).");
Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();
List<Structure> nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase();
for (Structure nurb : nurbStructures) {
Number matNumber = (Number) nurb.getFieldValue("mat_nr");
List<Structure> nurbList = nurbs.get(matNumber);
if (nurbList == null) {
nurbList = new ArrayList<Structure>();
nurbs.put(matNumber, nurbList);
}
nurbList.add(nurb);
}
LOGGER.fine("Getting materials.");
MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class);
materials = materialHelper.getMaterials(curveStructure, blenderContext);
if (materials != null) {
for (MaterialContext materialContext : materials) {
materialContext.setFaceCullMode(FaceCullMode.Off);
}
}
LOGGER.fine("Getting or creating bevel object.");
bevelObject = loadBevelAndTaper ? this.loadBevelObject(curveStructure) : null;
LOGGER.fine("Getting taper object.");
Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj");
if (bevelObject != null && pTaperObject.isNotNull()) {
Structure taperObjectStructure = pTaperObject.fetchData().get(0);
DynamicArray<Number> scaleArray = (DynamicArray<Number>) taperObjectStructure.getFieldValue("size");
scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue());
Pointer pTaperStructure = (Pointer) taperObjectStructure.getFieldValue("data");
Structure taperStructure = pTaperStructure.fetchData().get(0);
taperObject = new CurvesTemporalMesh(taperStructure, blenderContext);
}
LOGGER.fine("Creating the result curves.");
for (Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) {
for (Structure nurb : nurbEntry.getValue()) {
int type = ((Number) nurb.getFieldValue("type")).intValue();
if ((type & TYPE_BEZIER) != 0) {
this.loadBezierCurve(nurb, nurbEntry.getKey().intValue());
} else if ((type & TYPE_NURBS) != 0) {
this.loadNurbSurface(nurb, nurbEntry.getKey().intValue());
} else {
throw new BlenderFileException("Unknown curve type: " + type);
}
}
}
if (bevelObject != null && beziers.size() > 0) {
this.append(this.applyBevelAndTaper(this, bevelObject, taperObject, blenderContext));
} else {
for (BezierLine bezierLine : beziers) {
int originalVerticesAmount = vertices.size();
vertices.add(bezierLine.vertices[0]);
Vector3f v = bezierLine.vertices[1].subtract(bezierLine.vertices[0]).normalizeLocal();
float temp = v.x;
v.x = -v.y;
v.y = temp;
v.z = 0;
normals.add(v);// this will be smoothed in the next iteration
for (int i = 1; i < bezierLine.vertices.length; ++i) {
vertices.add(bezierLine.vertices[i]);
edges.add(new Edge(originalVerticesAmount + i - 1, originalVerticesAmount + i, 0, false, this));
// generating normal for vertex at 'i'
v = bezierLine.vertices[i].subtract(bezierLine.vertices[i - 1]).normalizeLocal();
temp = v.x;
v.x = -v.y;
v.y = temp;
v.z = 0;
// make the previous normal smooth
normals.get(i - 1).addLocal(v).multLocal(0.5f).normalizeLocal();
normals.add(v);// this will be smoothed in the next iteration
}
}
}
}
/**
* The method computes the value of a point at the certain relational distance from its beginning.
* @param alongRatio
* the relative distance along the curve; should be a value between 0 and 1 inclusive;
* if the value exceeds the boundaries it is truncated to them
* @return computed value along the curve
*/
private Vector3f getValueAlongCurve(float alongRatio) {
alongRatio = FastMath.clamp(alongRatio, 0, 1);
Vector3f result = new Vector3f();
float probeLength = this.getLength() * alongRatio, length = 0;
for (BezierLine bezier : beziers) {
float edgeLength = bezier.getLength();
if (length + edgeLength >= probeLength) {
float ratioAlongEdge = (probeLength - length) / edgeLength;
return bezier.getValueAlongCurve(ratioAlongEdge);
}
length += edgeLength;
}
return result;
}
/**
* @return the length of the curve
*/
private float getLength() {
float result = 0;
for (BezierLine bezier : beziers) {
result += bezier.getLength();
}
return result;
}
/**
* The methods loads the bezier curve from the given structure.
* @param nurbStructure
* the structure containing a single curve definition
* @param materialIndex
* the index of this segment's material
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
private void loadBezierCurve(Structure nurbStructure, int materialIndex) throws BlenderFileException {
Pointer pBezierTriple = (Pointer) nurbStructure.getFieldValue("bezt");
if (pBezierTriple.isNotNull()) {
int resolution = ((Number) nurbStructure.getFieldValue("resolu")).intValue();
boolean cyclic = (((Number) nurbStructure.getFieldValue("flagu")).intValue() & 0x01) != 0;
boolean smooth = (((Number) nurbStructure.getFieldValue("flag")).intValue() & FLAG_SMOOTH) != 0;
// creating the curve object
BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3, blenderContext.getBlenderKey().isFixUpAxis());
List<Vector3f> controlPoints = bezierCurve.getControlPoints();
if (cyclic) {
// copy the first three points at the end
for (int i = 0; i < 3; ++i) {
controlPoints.add(controlPoints.get(i));
}
}
// removing the first and last handles
controlPoints.remove(0);
controlPoints.remove(controlPoints.size() - 1);
// creating curve
Curve curve = new Curve(new Spline(SplineType.Bezier, controlPoints, 0, false), resolution);
FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData();
beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, cyclic));
}
}
/**
* This method loads the NURBS curve or surface.
* @param nurb
* the NURBS data structure
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
@SuppressWarnings("unchecked")
private void loadNurbSurface(Structure nurb, int materialIndex) throws BlenderFileException {
// loading the knots
List<Float>[] knots = new List[2];
Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") };
for (int i = 0; i < knots.length; ++i) {
if (pKnots[i].isNotNull()) {
FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress());
BlenderInputStream blenderInputStream = blenderContext.getInputStream();
blenderInputStream.setPosition(fileBlockHeader.getBlockPosition());
int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4;
knots[i] = new ArrayList<Float>(knotsAmount);
for (int j = 0; j < knotsAmount; ++j) {
knots[i].add(Float.valueOf(blenderInputStream.readFloat()));
}
}
}
// loading the flags and orders (basis functions degrees)
int flag = ((Number) nurb.getFieldValue("flag")).intValue();
boolean smooth = (flag & FLAG_SMOOTH) != 0;
int flagU = ((Number) nurb.getFieldValue("flagu")).intValue();
int flagV = ((Number) nurb.getFieldValue("flagv")).intValue();
int orderU = ((Number) nurb.getFieldValue("orderu")).intValue();
int orderV = ((Number) nurb.getFieldValue("orderv")).intValue();
// loading control points and their weights
int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue();
int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue();
List<Structure> bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData();
List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);
for (int i = 0; i < pntsV; ++i) {
List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);
for (int j = 0; j < pntsU; ++j) {
DynamicArray<Float> vec = (DynamicArray<Float>) bPoints.get(j + i * pntsU).getFieldValue("vec");
if (blenderContext.getBlenderKey().isFixUpAxis()) {
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue()));
} else {
uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue()));
}
}
if ((flagU & 0x01) != 0) {
for (int k = 0; k < orderU - 1; ++k) {
uControlPoints.add(uControlPoints.get(k));
}
}
controlPoints.add(uControlPoints);
}
if ((flagV & 0x01) != 0) {
for (int k = 0; k < orderV - 1; ++k) {
controlPoints.add(controlPoints.get(k));
}
}
int originalVerticesAmount = vertices.size();
int resolu = ((Number) nurb.getFieldValue("resolu")).intValue();
if (knots[1] == null) {// creating the NURB curve
Curve curve = new Curve(new Spline(controlPoints.get(0), knots[0]), resolu);
FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData();
beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, false));
} else {// creating the NURB surface
int resolv = ((Number) nurb.getFieldValue("resolv")).intValue();
int uSegments = resolu * controlPoints.get(0).size() - 1;
int vSegments = resolv * controlPoints.size() - 1;
Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, uSegments, vSegments, orderU, orderV, smooth);
FloatBuffer vertsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Position).getData();
vertices.addAll(Arrays.asList(BufferUtils.getVector3Array(vertsBuffer)));
FloatBuffer normalsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Normal).getData();
normals.addAll(Arrays.asList(BufferUtils.getVector3Array(normalsBuffer)));
IndexBuffer indexBuffer = nurbSurface.getIndexBuffer();
for (int i = 0; i < indexBuffer.size(); i += 3) {
int index1 = indexBuffer.get(i) + originalVerticesAmount;
int index2 = indexBuffer.get(i + 1) + originalVerticesAmount;
int index3 = indexBuffer.get(i + 2) + originalVerticesAmount;
faces.add(new Face(new Integer[] { index1, index2, index3 }, smooth, materialIndex, null, null, this));
}
}
}
/**
* The method loads the bevel object that should be applied to curve. It can either be another curve or a generated one
* based on the bevel generating parameters in blender.
* @param curveStructure
* the structure with the curve's data (the curve being loaded, NOT the bevel curve)
* @return the curve's bevel object
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
@SuppressWarnings("unchecked")
private CurvesTemporalMesh loadBevelObject(Structure curveStructure) throws BlenderFileException {
CurvesTemporalMesh bevelObject = null;
Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj");
boolean cyclic = false;
if (pBevelObject.isNotNull()) {
Structure bevelObjectStructure = pBevelObject.fetchData().get(0);
DynamicArray<Number> scaleArray = (DynamicArray<Number>) bevelObjectStructure.getFieldValue("size");
Vector3f scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue());
Pointer pBevelStructure = (Pointer) bevelObjectStructure.getFieldValue("data");
Structure bevelStructure = pBevelStructure.fetchData().get(0);
bevelObject = new CurvesTemporalMesh(bevelStructure, scale, false, blenderContext);
// transforming the bezier lines from plane XZ to plane YZ
for (BezierLine bl : bevelObject.beziers) {
for (Vector3f v : bl.vertices) {
// casting the bezier curve orthogonally on the plane XZ (making Y = 0) and then moving the plane XZ to ZY in a way that:
// -Z => +Y and +X => +Z and +Y => +X (but because casting would make Y = 0, then we simply set X = 0)
v.y = -v.z;
v.z = v.x;
v.x = 0;
}
// bevel curves should not have repeated the first vertex at the end when they are cyclic (this is handled differently)
if (bl.isCyclic()) {
bl.removeLastVertex();
}
}
} else {
fillCaps = false;// this option is inactive in blender when there is no bevel object applied
int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue();
float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue();
float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue();
float offset = ((Number) curveStructure.getFieldValue("offset", 0)).floatValue();
if (offset != 0) {
// TODO: add support for offset parameter
LOGGER.warning("Offset parameter not yet supported.");
}
Curve bevelCurve = null;
if (bevelDepth > 0.0f) {
float handlerLength = bevelDepth / 2.0f;
cyclic = !isFront && !isBack;
List<Vector3f> conrtolPoints = new ArrayList<Vector3f>();
// blenders from 2.49 to 2.52 did not pay attention to fron and back faces
// so in order to draw the scene exactly as it is in different blender versions the blender version is checked here
// when neither fron and back face is selected all version behave the same and draw full bevel around the curve
if (cyclic || blenderContext.getBlenderVersion() < 253) {
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth));
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth));
if (extrude > 0) {
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth));
}
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0));
if (cyclic) {
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, handlerLength));
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude, bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, bevelDepth));
if (extrude > 0) {
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, bevelDepth));
conrtolPoints.add(new Vector3f(0, -extrude, bevelDepth));
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, bevelDepth));
}
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, handlerLength));
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
}
} else {
if (extrude > 0) {
if (isBack) {
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0));
conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth));
}
conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth));
conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth));
if (isFront) {
conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0));
}
} else {
if (isFront && isBack) {
conrtolPoints.add(new Vector3f(0, -bevelDepth, 0));
conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, bevelDepth, 0));
} else {
if (isBack) {
conrtolPoints.add(new Vector3f(0, -bevelDepth, 0));
conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
} else {
conrtolPoints.add(new Vector3f(0, 0, -bevelDepth));
conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth));
conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength));
conrtolPoints.add(new Vector3f(0, bevelDepth, 0));
}
}
}
}
bevelCurve = new Curve(new Spline(SplineType.Bezier, conrtolPoints, 0, false), bevResol);
} else if (extrude > 0.0f) {
Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) }, 1, false);
bevelCurve = new Curve(bevelSpline, bevResol);
}
if (bevelCurve != null) {
bevelObject = new CurvesTemporalMesh(blenderContext);
FloatBuffer vertsBuffer = (FloatBuffer) bevelCurve.getBuffer(Type.Position).getData();
Vector3f[] verts = BufferUtils.getVector3Array(vertsBuffer);
if (cyclic) {// get rid of the last vertex which is identical to the first one
verts = Arrays.copyOf(verts, verts.length - 1);
}
bevelObject.beziers.add(new BezierLine(verts, 0, false, cyclic));
}
}
return bevelObject;
}
private List<BezierLine> getScaledBeziers() {
if (scale.equals(Vector3f.UNIT_XYZ)) {
return beziers;
}
List<BezierLine> result = new ArrayList<BezierLine>();
for (BezierLine bezierLine : beziers) {
result.add(bezierLine.scale(scale));
}
return result;
}
/**
* This method applies bevel and taper objects to the curve.
* @param curve
* the curve we apply the objects to
* @param bevelObject
* the bevel object
* @param taperObject
* the taper object
* @param blenderContext
* the blender context
* @return a list of geometries representing the beveled and/or tapered curve
* @throws BlenderFileException
* an exception is thrown when problems with reading occur
*/
private CurvesTemporalMesh applyBevelAndTaper(CurvesTemporalMesh curve, CurvesTemporalMesh bevelObject, CurvesTemporalMesh taperObject, BlenderContext blenderContext) throws BlenderFileException {
List<BezierLine> bevelBezierLines = bevelObject.getScaledBeziers();
List<BezierLine> curveLines = curve.beziers;
if (bevelBezierLines.size() == 0 || curveLines.size() == 0) {
return null;
}
CurvesTemporalMesh result = new CurvesTemporalMesh(blenderContext);
for (BezierLine curveLine : curveLines) {
Vector3f[] curveLineVertices = curveLine.getVertices(bevelStart, bevelEnd);
for (BezierLine bevelBezierLine : bevelBezierLines) {
CurvesTemporalMesh partResult = new CurvesTemporalMesh(blenderContext);
Vector3f[] bevelLineVertices = bevelBezierLine.getVertices();
List<Vector3f[]> bevels = new ArrayList<Vector3f[]>();
Vector3f[] bevelPoints = curvesHelper.transformToFirstLineOfBevelPoints(bevelLineVertices, curveLineVertices[0], curveLineVertices[1]);
bevels.add(bevelPoints);
for (int i = 1; i < curveLineVertices.length - 1; ++i) {
bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[i - 1], curveLineVertices[i], curveLineVertices[i + 1]);
bevels.add(bevelPoints);
}
bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[curveLineVertices.length - 2], curveLineVertices[curveLineVertices.length - 1], null);
bevels.add(bevelPoints);
Vector3f subtractResult = new Vector3f();
if (bevels.size() > 2) {
// changing the first and last bevel so that they are parallel to their neighbours (blender works this way)
// notice this implicates that the distances of every corresponding point in the two bevels must be identical and
// equal to the distance between the points on curve that define the bevel position
// so instead doing complicated rotations on each point we will simply properly translate each of them
int[][] pointIndexes = new int[][] { { 0, 1 }, { curveLineVertices.length - 1, curveLineVertices.length - 2 } };
for (int[] indexes : pointIndexes) {
float distance = curveLineVertices[indexes[1]].subtract(curveLineVertices[indexes[0]], subtractResult).length();
Vector3f[] bevel = bevels.get(indexes[0]);
Vector3f[] nextBevel = bevels.get(indexes[1]);
for (int i = 0; i < bevel.length; ++i) {
float d = bevel[i].subtract(nextBevel[i], subtractResult).length();
subtractResult.normalizeLocal().multLocal(distance - d);
bevel[i].addLocal(subtractResult);
}
}
}
if (taperObject != null) {
float curveLength = curveLine.getLength(), lengthAlongCurve = bevelStart;
for (int i = 0; i < curveLineVertices.length; ++i) {
if (i > 0) {
lengthAlongCurve += curveLineVertices[i].subtract(curveLineVertices[i - 1], subtractResult).length();
}
float taperScale = -taperObject.getValueAlongCurve(lengthAlongCurve / curveLength).z * taperObject.scale.z;
if (taperScale != 1) {
this.applyScale(bevels.get(i), curveLineVertices[i], taperScale);
}
}
}
// adding vertices to the part result
for (Vector3f[] bevel : bevels) {
for (Vector3f d : bevel) {
partResult.getVertices().add(d);
}
}
// preparing faces for the part result (each face is a quad)
int bevelVertCount = bevelPoints.length;
for (int i = 0; i < bevels.size() - 1; ++i) {
for (int j = 0; j < bevelVertCount - 1; ++j) {
Integer[] indexes = new Integer[] { i * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j, i * bevelVertCount + j };
partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult));
partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult));
}
if (bevelBezierLine.isCyclic()) {
int j = bevelVertCount - 1;
Integer[] indexes = new Integer[] { i * bevelVertCount, (i + 1) * bevelVertCount, (i + 1) * bevelVertCount + j, i * bevelVertCount + j };
partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult));
partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult));
partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult));
}
}
partResult.generateNormals();
if (fillCaps) {// caps in blender behave as if they weren't affected by the smooth factor
// START CAP
Vector3f[] cap = bevels.get(0);
List<Integer> capIndexes = new ArrayList<Integer>(cap.length);
Vector3f capNormal = curveLineVertices[0].subtract(curveLineVertices[1]).normalizeLocal();
for (int i = 0; i < cap.length; ++i) {
capIndexes.add(partResult.getVertices().size());
partResult.getVertices().add(cap[i]);
partResult.getNormals().add(capNormal);
}
Collections.reverse(capIndexes);// the indexes ned to be reversed for the face to have fron face outside the beveled line
partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult));
for (int i = 1; i < capIndexes.size(); ++i) {
partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult));
}
// END CAP
cap = bevels.get(bevels.size() - 1);
capIndexes.clear();
capNormal = curveLineVertices[curveLineVertices.length - 1].subtract(curveLineVertices[curveLineVertices.length - 2]).normalizeLocal();
for (int i = 0; i < cap.length; ++i) {
capIndexes.add(partResult.getVertices().size());
partResult.getVertices().add(cap[i]);
partResult.getNormals().add(capNormal);
}
partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult));
for (int i = 1; i < capIndexes.size(); ++i) {
partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult));
}
}
result.append(partResult);
}
}
return result;
}
/**
* The method generates normals for the curve. If any normals were already stored they are discarded.
*/
private void generateNormals() {
Map<Integer, Vector3f> normalMap = new TreeMap<Integer, Vector3f>();
for (Face face : faces) {
// the first 3 verts are enough here (all faces are triangles except for the caps, but those are fully flat anyway)
int index1 = face.getIndexes().get(0);
int index2 = face.getIndexes().get(1);
int index3 = face.getIndexes().get(2);
Vector3f n = FastMath.computeNormal(vertices.get(index1), vertices.get(index2), vertices.get(index3));
for (int index : face.getIndexes()) {
Vector3f normal = normalMap.get(index);
if (normal == null) {
normalMap.put(index, n.clone());
} else {
normal.addLocal(n).normalizeLocal();
}
}
}
normals.clear();
Collections.addAll(normals, new Vector3f[normalMap.size()]);
for (Entry<Integer, Vector3f> entry : normalMap.entrySet()) {
normals.set(entry.getKey(), entry.getValue());
}
}
/**
* the method applies scale for the given bevel points. The points table is
* being modified so expect your result there.
*
* @param points
* the bevel points
* @param centerPoint
* the center point of the bevel
* @param scale
* the scale to be applied
*/
private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) {
Vector3f taperScaleVector = new Vector3f();
for (Vector3f p : points) {
taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale);
p.addLocal(taperScaleVector);
}
}
/**
* A helper class that represents a single bezier line. It consists of Edge's and allows to
* get a subline of a length of the line.
*
* @author Marcin Roguski (Kaelthas)
*/
public static class BezierLine {
/** The edges of the bezier line. */
private Vector3f[] vertices;
/** The material number of the line. */
private int materialNumber;
/** Indicates if the line is smooth of flat. */
private boolean smooth;
/** The length of the line. */
private float length;
/** Indicates if the current line is cyclic or not. */
private boolean cyclic;
public BezierLine(Vector3f[] vertices, int materialNumber, boolean smooth, boolean cyclik) {
this.vertices = vertices;
this.materialNumber = materialNumber;
this.smooth = smooth;
cyclic = cyclik;
this.recomputeLength();
}
public BezierLine scale(Vector3f scale) {
BezierLine result = new BezierLine(vertices, materialNumber, smooth, cyclic);
result.vertices = new Vector3f[vertices.length];
for (int i = 0; i < vertices.length; ++i) {
result.vertices[i] = vertices[i].mult(scale);
}
result.recomputeLength();
return result;
}
public void removeLastVertex() {
Vector3f[] newVertices = new Vector3f[vertices.length - 1];
for (int i = 0; i < vertices.length - 1; ++i) {
newVertices[i] = vertices[i];
}
vertices = newVertices;
this.recomputeLength();
}
private void recomputeLength() {
length = 0;
for (int i = 1; i < vertices.length; ++i) {
length += vertices[i - 1].distance(vertices[i]);
}
if (cyclic) {
// if the first vertex is repeated at the end the distance will be = 0 so it won't affect the result, and if it is not repeated
// then it is necessary to add the length between the last and the first vertex
length += vertices[vertices.length - 1].distance(vertices[0]);
}
}
public Vector3f[] getVertices() {
return this.getVertices(0, 1);
}
public Vector3f[] getVertices(float startSlice, float endSlice) {
if (startSlice == 0 && endSlice == 1) {
return vertices;
}
List<Vector3f> result = new ArrayList<Vector3f>();
float length = this.getLength(), temp = 0;
float startSliceLength = length * startSlice;
float endSliceLength = length * endSlice;
int index = 1;
if (startSlice > 0) {
while (temp < startSliceLength) {
Vector3f v1 = vertices[index - 1];
Vector3f v2 = vertices[index++];
float edgeLength = v1.distance(v2);
temp += edgeLength;
if (temp == startSliceLength) {
result.add(v2);
} else if (temp > startSliceLength) {
result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2));
}
}
}
if (endSlice < 1) {
if (index == vertices.length) {
Vector3f v1 = vertices[vertices.length - 2];
Vector3f v2 = vertices[vertices.length - 1];
result.add(v1.subtract(v2).normalizeLocal().multLocal(length - endSliceLength).addLocal(v2));
} else {
for (int i = index; i < vertices.length && temp < endSliceLength; ++i) {
Vector3f v1 = vertices[index - 1];
Vector3f v2 = vertices[index++];
temp += v1.distance(v2);
if (temp == endSliceLength) {
result.add(v2);
} else if (temp > endSliceLength) {
result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2));
}
}
}
} else {
result.addAll(Arrays.asList(Arrays.copyOfRange(vertices, index, vertices.length)));
}
return result.toArray(new Vector3f[result.size()]);
}
/**
* The method computes the value of a point at the certain relational distance from its beginning.
* @param alongRatio
* the relative distance along the curve; should be a value between 0 and 1 inclusive;
* if the value exceeds the boundaries it is truncated to them
* @return computed value along the curve
*/
public Vector3f getValueAlongCurve(float alongRatio) {
alongRatio = FastMath.clamp(alongRatio, 0, 1);
Vector3f result = new Vector3f();
float probeLength = this.getLength() * alongRatio;
float length = 0;
for (int i = 1; i < vertices.length; ++i) {
float edgeLength = vertices[i].distance(vertices[i - 1]);
if (length + edgeLength > probeLength) {
float ratioAlongEdge = (probeLength - length) / edgeLength;
return FastMath.interpolateLinear(ratioAlongEdge, vertices[i - 1], vertices[i]);
} else if (length + edgeLength == probeLength) {
return vertices[i];
}
length += edgeLength;
}
return result;
}
/**
* @return the material number of this bezier line
*/
public int getMaterialNumber() {
return materialNumber;
}
/**
* @return indicates if the line is smooth of flat
*/
public boolean isSmooth() {
return smooth;
}
/**
* @return the length of this bezier line
*/
public float getLength() {
return length;
}
/**
* @return indicates if the current line is cyclic or not
*/
public boolean isCyclic() {
return cyclic;
}
}
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender.file;
/**
* This exception is thrown when blend file data is somehow invalid.
* @author Marcin Roguski
*/
public class BlenderFileException extends Exception {
private static final long serialVersionUID = 7573482836437866767L;
/**
* Constructor. Creates an exception with no description.
*/
public BlenderFileException() {
// this constructor has no message
}
/**
* Constructor. Creates an exception containing the given message.
* @param message
* the message describing the problem that occurred
*/
public BlenderFileException(String message) {
super(message);
}
/**
* Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then.
* @param throwable
* an exception/error that occurred
*/
public BlenderFileException(Throwable throwable) {
super(throwable);
}
/**
* Constructor. Creates an exception with both a message and stacktrace.
* @param message
* the message describing the problem that occurred
* @param throwable
* an exception/error that occurred
*/
public BlenderFileException(String message, Throwable throwable) {
super(message, throwable);
}
}

View File

@ -1,371 +0,0 @@
/*
* Copyright (c) 2009-2019 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender.file;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
/**
* An input stream with random access to data.
* @author Marcin Roguski
*/
public class BlenderInputStream extends InputStream {
private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName());
/** The default size of the blender buffer. */
private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB
/**
* Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes.
*/
private int pointerSize;
/**
* Type of byte ordering used; 'v' means little endian and 'V' means big endian.
*/
private char endianess;
/** Version of Blender the file was created in; '248' means version 2.48. */
private String versionNumber;
/** The buffer we store the read data to. */
protected byte[] cachedBuffer;
/** The total size of the stored data. */
protected int size;
/** The current position of the read cursor. */
protected int position;
/**
* Constructor. The input stream is stored and used to read data.
* @param inputStream
* the stream we read data from
* @throws BlenderFileException
* this exception is thrown if the file header has some invalid data
*/
public BlenderInputStream(InputStream inputStream) throws BlenderFileException {
// the size value will canche while reading the file; the available() method cannot be counted on
try {
size = inputStream.available();
} catch (IOException e) {
size = 0;
}
if (size <= 0) {
size = BlenderInputStream.DEFAULT_BUFFER_SIZE;
}
// buffered input stream is used here for much faster file reading
BufferedInputStream bufferedInputStream;
if (inputStream instanceof BufferedInputStream) {
bufferedInputStream = (BufferedInputStream) inputStream;
} else {
bufferedInputStream = new BufferedInputStream(inputStream);
}
try {
this.readStreamToCache(bufferedInputStream);
} catch (IOException e) {
throw new BlenderFileException("Problems occurred while caching the file!", e);
} finally {
try {
inputStream.close();
} catch (IOException e) {
LOGGER.warning("Unable to close stream with blender file.");
}
}
try {
this.readFileHeader();
} catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;)
this.decompressFile();
position = 0;
this.readFileHeader();
}
}
/**
* This method reads the whole stream into a buffer.
* @param inputStream
* the stream to read the file data from
* @throws IOException
* an exception is thrown when data read from the stream is invalid or there are problems with i/o
* operations
*/
private void readStreamToCache(InputStream inputStream) throws IOException {
int data = inputStream.read();
cachedBuffer = new byte[size];
size = 0;// this will count the actual size
while (data != -1) {
if (size >= cachedBuffer.length) {// widen the cached array
byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)];
System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length);
cachedBuffer = newBuffer;
}
cachedBuffer[size++] = (byte) data;
data = inputStream.read();
}
}
/**
* This method is used when the blender file is gzipped. It decompresses the data and stores it back into the
* cachedBuffer field.
*/
private void decompressFile() {
GZIPInputStream gis = null;
try {
gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer));
this.readStreamToCache(gis);
} catch (IOException e) {
throw new IllegalStateException("IO errors occurred where they should NOT! " + "The data is already buffered at this point!", e);
} finally {
try {
if (gis != null) {
gis.close();
}
} catch (IOException e) {
LOGGER.warning(e.getMessage());
}
}
}
/**
* This method loads the header from the given stream during instance creation.
* @param inputStream
* the stream we read the header from
* @throws BlenderFileException
* this exception is thrown if the file header has some invalid data
*/
private void readFileHeader() throws BlenderFileException {
byte[] identifier = new byte[7];
int bytesRead = this.readBytes(identifier);
if (bytesRead != 7) {
throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!");
}
String strIdentifier = new String(identifier);
if (!"BLENDER".equals(strIdentifier)) {
throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!");
}
char pointerSizeSign = (char) this.readByte();
if (pointerSizeSign == '-') {
pointerSize = 8;
} else if (pointerSizeSign == '_') {
pointerSize = 4;
} else {
throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign);
}
endianess = (char) this.readByte();
if (endianess != 'v' && endianess != 'V') {
throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess);
}
byte[] versionNumber = new byte[3];
bytesRead = this.readBytes(versionNumber);
if (bytesRead != 3) {
throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!");
}
this.versionNumber = new String(versionNumber);
}
@Override
public int read() throws IOException {
return this.readByte();
}
/**
* This method reads 1 byte from the stream.
* It works just in the way the read method does.
* It just not throw an exception because at this moment the whole file
* is loaded into buffer, so no need for IOException to be thrown.
* @return a byte from the stream (1 bytes read)
*/
public int readByte() {
return cachedBuffer[position++] & 0xFF;
}
/**
* This method reads a bytes number big enough to fill the table.
* It does not throw exceptions so it is for internal use only.
* @param bytes
* an array to be filled with data
* @return number of read bytes (a length of array actually)
*/
private int readBytes(byte[] bytes) {
for (int i = 0; i < bytes.length; ++i) {
bytes[i] = (byte) this.readByte();
}
return bytes.length;
}
/**
* This method reads 2-byte number from the stream.
* @return a number from the stream (2 bytes read)
*/
public int readShort() {
int part1 = this.readByte();
int part2 = this.readByte();
if (endianess == 'v') {
return (part2 << 8) + part1;
} else {
return (part1 << 8) + part2;
}
}
/**
* This method reads 4-byte number from the stream.
* @return a number from the stream (4 bytes read)
*/
public int readInt() {
int part1 = this.readByte();
int part2 = this.readByte();
int part3 = this.readByte();
int part4 = this.readByte();
if (endianess == 'v') {
return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1;
} else {
return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4;
}
}
/**
* This method reads 4-byte floating point number (float) from the stream.
* @return a number from the stream (4 bytes read)
*/
public float readFloat() {
int intValue = this.readInt();
return Float.intBitsToFloat(intValue);
}
/**
* This method reads 8-byte number from the stream.
* @return a number from the stream (8 bytes read)
*/
public long readLong() {
long part1 = this.readInt();
long part2 = this.readInt();
long result = -1;
if (endianess == 'v') {
result = part2 << 32 | part1;
} else {
result = part1 << 32 | part2;
}
return result;
}
/**
* This method reads 8-byte floating point number (double) from the stream.
* @return a number from the stream (8 bytes read)
*/
public double readDouble() {
long longValue = this.readLong();
return Double.longBitsToDouble(longValue);
}
/**
* This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either
* 4 or 8 bytes of data.
* @return the pointer value
*/
public long readPointer() {
if (pointerSize == 4) {
return this.readInt();
}
return this.readLong();
}
/**
* This method reads the string. It assumes the string is terminated with zero in the stream.
* @return the string read from the stream
*/
public String readString() {
StringBuilder stringBuilder = new StringBuilder();
int data = this.readByte();
while (data != 0) {
stringBuilder.append((char) data);
data = this.readByte();
}
return stringBuilder.toString();
}
/**
* This method sets the current position of the read cursor.
* @param position
* the position of the read cursor
*/
public void setPosition(int position) {
this.position = position;
}
/**
* This method returns the position of the read cursor.
* @return the position of the read cursor
*/
public int getPosition() {
return position;
}
/**
* This method returns the blender version number where the file was created.
* @return blender version number
*/
public String getVersionNumber() {
return versionNumber;
}
/**
* This method returns the size of the pointer.
* @return the size of the pointer
*/
public int getPointerSize() {
return pointerSize;
}
/**
* This method aligns cursor position forward to a given amount of bytes.
* @param bytesAmount
* the byte amount to which we aligh the cursor
*/
public void alignPosition(int bytesAmount) {
if (bytesAmount <= 0) {
throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!");
}
long move = position % bytesAmount;
if (move > 0) {
position += bytesAmount - move;
}
}
@Override
public void close() throws IOException {
// this method is unimplemented because some loaders (ie. TGALoader) tend close the stream given from the outside
// because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader
// to read the image file, that is why we do not want it to be closed before the reading is done
// and anyway this stream is only a cached buffer, so it does not hold any open connection to anything
}
}

View File

@ -1,203 +0,0 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender.file;
import com.jme3.scene.plugins.blender.BlenderContext;
import java.util.HashMap;
import java.util.Map;
/**
* The data block containing the description of the file.
* @author Marcin Roguski (Kaelthas)
*/
public class DnaBlockData {
private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA
private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME
private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE
private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN
private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC
/** Structures available inside the file. */
private final Structure[] structures;
/** A map that helps finding a structure by type. */
private final Map<String, Structure> structuresMap;
/**
* Constructor. Loads the block from the given stream during instance creation.
* @param inputStream
* the stream we read the block from
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is throw if the blend file is invalid or somehow corrupted
*/
public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
int identifier;
// reading 'SDNA' identifier
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != SDNA_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
}
// reading names
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != NAME_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));
}
int amount = inputStream.readInt();
if (amount <= 0) {
throw new BlenderFileException("The names amount number should be positive!");
}
String[] names = new String[amount];
for (int i = 0; i < amount; ++i) {
names[i] = inputStream.readString();
}
// reading types
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TYPE_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));
}
amount = inputStream.readInt();
if (amount <= 0) {
throw new BlenderFileException("The types amount number should be positive!");
}
String[] types = new String[amount];
for (int i = 0; i < amount; ++i) {
types[i] = inputStream.readString();
}
// reading lengths
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != TLEN_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));
}
int[] lengths = new int[amount];// theamount is the same as int types
for (int i = 0; i < amount; ++i) {
lengths[i] = inputStream.readShort();
}
// reading structures
inputStream.alignPosition(4);
identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte();
if (identifier != STRC_ID) {
throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));
}
amount = inputStream.readInt();
if (amount <= 0) {
throw new BlenderFileException("The structures amount number should be positive!");
}
structures = new Structure[amount];
structuresMap = new HashMap<String, Structure>(amount);
for (int i = 0; i < amount; ++i) {
structures[i] = new Structure(inputStream, names, types, blenderContext);
if (structuresMap.containsKey(structures[i].getType())) {
throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!");
}
structuresMap.put(structures[i].getType(), structures[i]);
}
}
/**
* This method returns the amount of the structures.
* @return the amount of the structures
*/
public int getStructuresCount() {
return structures.length;
}
/**
* This method returns the structure of the given index.
* @param index
* the index of the structure
* @return the structure of the given index
*/
public Structure getStructure(int index) {
try {
return (Structure) structures[index].clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("Structure should be clonable!!!", e);
}
}
/**
* This method returns a structure of the given name. If the name does not exists then null is returned.
* @param name
* the name of the structure
* @return the required structure or null if the given name is inapropriate
*/
public Structure getStructure(String name) {
try {
return (Structure) structuresMap.get(name).clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
/**
* This method indicates if the structure of the given name exists.
* @param name
* the name of the structure
* @return true if the structure exists and false otherwise
*/
public boolean hasStructure(String name) {
return structuresMap.containsKey(name);
}
/**
* This method converts the given identifier code to string.
* @param code
* the code that is to be converted
* @return the string value of the identifier
*/
private String toString(int code) {
char c1 = (char) ((code & 0xFF000000) >> 24);
char c2 = (char) ((code & 0xFF0000) >> 16);
char c3 = (char) ((code & 0xFF00) >> 8);
char c4 = (char) (code & 0xFF);
return String.valueOf(c1) + c2 + c3 + c4;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n');
for (Structure structure : structures) {
stringBuilder.append(structure.toString()).append('\n');
}
return stringBuilder.append("===============").toString();
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender.file;
/**
* An array that can be dynamically modified
* @author Marcin Roguski
* @param <T>
* the type of stored data in the array
*/
public class DynamicArray<T> implements Cloneable {
/** An array object that holds the required data. */
private T[] array;
/**
* This table holds the sizes of dimensions of the dynamic table. Its length specifies the table dimension or a
* pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths:
* dynTable[a][b][c], where a,b,c are stored in the tableSizes table.
*/
private int[] tableSizes;
/**
* Constructor. Builds an empty array of the specified sizes.
* @param tableSizes
* the sizes of the table
* @throws IllegalArgumentException
* an exception is thrown if one of the sizes is not a positive number
*/
public DynamicArray(int[] tableSizes, T[] data) {
this.tableSizes = tableSizes;
int totalSize = 1;
for (int size : tableSizes) {
if (size <= 0) {
throw new IllegalArgumentException("The size of the table must be positive!");
}
totalSize *= size;
}
if (totalSize != data.length) {
throw new IllegalArgumentException("The size of the table does not match the size of the given data!");
}
this.array = data;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**
* This method returns a value on the specified position. The dimension of the table is not taken into
* consideration.
* @param position
* the position of the data
* @return required data
*/
public T get(int position) {
return array[position];
}
/**
* This method returns a value on the specified position in multidimensional array. Be careful not to exceed the
* table boundaries. Check the table's dimension first.
* @param position
* the position of the data indices of data position
* @return required data required data
*/
public T get(int... position) {
if (position.length != tableSizes.length) {
throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!");
}
int index = 0;
for (int i = 0; i < position.length - 1; ++i) {
index += position[i] * tableSizes[i + 1];
}
index += position[position.length - 1];
return array[index];
}
/**
* This method returns the total amount of data stored in the array.
* @return the total amount of data stored in the array
*/
public int getTotalSize() {
return array.length;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
if (array instanceof Character[]) {// in case of character array we convert it to String
for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0'
result.append(array[i]);
}
} else {
result.append('[');
for (int i = 0; i < array.length; ++i) {
result.append(array[i].toString());
if (i + 1 < array.length) {
result.append(',');
}
}
result.append(']');
}
return result.toString();
}
}

View File

@ -1,327 +0,0 @@
package com.jme3.scene.plugins.blender.file;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.Structure.DataType;
import java.util.ArrayList;
import java.util.List;
/**
* This class represents a single field in the structure. It can be either a primitive type or a table or a reference to
* another structure.
* @author Marcin Roguski
*/
/* package */
class Field implements Cloneable {
private static final int NAME_LENGTH = 24;
private static final int TYPE_LENGTH = 16;
/** The blender context. */
public BlenderContext blenderContext;
/** The type of the field. */
public String type;
/** The name of the field. */
public String name;
/** The value of the field. Filled during data reading. */
public Object value;
/** This variable indicates the level of the pointer. */
public int pointerLevel;
/**
* This variable determines the sizes of the array. If the value is null then the field is not an array.
*/
public int[] tableSizes;
/** This variable indicates if the field is a function pointer. */
public boolean function;
/**
* Constructor. Saves the field data and parses its name.
* @param name
* the name of the field
* @param type
* the type of the field
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown if the names contain errors
*/
public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException {
this.type = type;
this.blenderContext = blenderContext;
this.parseField(new StringBuilder(name));
}
/**
* Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we
* have a clean empty copy of the field to fill with data.
* @param field
* the object that we copy
*/
private Field(Field field) {
type = field.type;
name = field.name;
blenderContext = field.blenderContext;
pointerLevel = field.pointerLevel;
if (field.tableSizes != null) {
tableSizes = field.tableSizes.clone();
}
function = field.function;
}
@Override
public Object clone() throws CloneNotSupportedException {
return new Field(this);
}
/**
* This method fills the field with data read from the input stream.
* @param blenderInputStream
* the stream we read data from
* @throws BlenderFileException
* an exception is thrown when the blend file is somehow invalid or corrupted
*/
public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException {
int dataToRead = 1;
if (tableSizes != null && tableSizes.length > 0) {
for (int size : tableSizes) {
if (size <= 0) {
throw new BlenderFileException("The field " + name + " has invalid table size: " + size);
}
dataToRead *= size;
}
}
DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER;
switch (dataType) {
case POINTER:
if (dataToRead == 1) {
Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
pointer.fill(blenderInputStream);
value = pointer;
} else {
Pointer[] data = new Pointer[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
Pointer pointer = new Pointer(pointerLevel, function, blenderContext);
pointer.fill(blenderInputStream);
data[i] = pointer;
}
value = new DynamicArray<Pointer>(tableSizes, data);
}
break;
case CHARACTER:
// character is also stored as a number, because sometimes the new blender version uses
// other number type instead of character as a field type
// and characters are very often used as byte number stores instead of real chars
if (dataToRead == 1) {
value = Byte.valueOf((byte) blenderInputStream.readByte());
} else {
Character[] data = new Character[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Character.valueOf((char) blenderInputStream.readByte());
}
value = new DynamicArray<Character>(tableSizes, data);
}
break;
case SHORT:
if (dataToRead == 1) {
value = Integer.valueOf(blenderInputStream.readShort());
} else {
Number[] data = new Number[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Integer.valueOf(blenderInputStream.readShort());
}
value = new DynamicArray<Number>(tableSizes, data);
}
break;
case INTEGER:
if (dataToRead == 1) {
value = Integer.valueOf(blenderInputStream.readInt());
} else {
Number[] data = new Number[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Integer.valueOf(blenderInputStream.readInt());
}
value = new DynamicArray<Number>(tableSizes, data);
}
break;
case LONG:
if (dataToRead == 1) {
value = Long.valueOf(blenderInputStream.readLong());
} else {
Number[] data = new Number[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Long.valueOf(blenderInputStream.readLong());
}
value = new DynamicArray<Number>(tableSizes, data);
}
break;
case FLOAT:
if (dataToRead == 1) {
value = Float.valueOf(blenderInputStream.readFloat());
} else {
Number[] data = new Number[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Float.valueOf(blenderInputStream.readFloat());
}
value = new DynamicArray<Number>(tableSizes, data);
}
break;
case DOUBLE:
if (dataToRead == 1) {
value = Double.valueOf(blenderInputStream.readDouble());
} else {
Number[] data = new Number[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
data[i] = Double.valueOf(blenderInputStream.readDouble());
}
value = new DynamicArray<Number>(tableSizes, data);
}
break;
case VOID:
break;
case STRUCTURE:
if (dataToRead == 1) {
Structure structure = blenderContext.getDnaBlockData().getStructure(type);
structure.fill(blenderContext.getInputStream());
value = structure;
} else {
Structure[] data = new Structure[dataToRead];
for (int i = 0; i < dataToRead; ++i) {
Structure structure = blenderContext.getDnaBlockData().getStructure(type);
structure.fill(blenderContext.getInputStream());
data[i] = structure;
}
value = new DynamicArray<Structure>(tableSizes, data);
}
break;
default:
throw new IllegalStateException("Unimplemented filling of type: " + type);
}
}
/**
* This method parses the field name to determine how the field should be used.
* @param nameBuilder
* the name of the field (given as StringBuilder)
* @throws BlenderFileException
* this exception is thrown if the names contain errors
*/
private void parseField(StringBuilder nameBuilder) throws BlenderFileException {
this.removeWhitespaces(nameBuilder);
// veryfying if the name is a pointer
int pointerIndex = nameBuilder.indexOf("*");
while (pointerIndex >= 0) {
++pointerLevel;
nameBuilder.deleteCharAt(pointerIndex);
pointerIndex = nameBuilder.indexOf("*");
}
// veryfying if the name is a function pointer
if (nameBuilder.indexOf("(") >= 0) {
function = true;
this.removeCharacter(nameBuilder, '(');
this.removeCharacter(nameBuilder, ')');
} else {
// veryfying if the name is a table
int tableStartIndex = 0;
List<Integer> lengths = new ArrayList<Integer>(3);// 3 dimensions will be enough in most cases
do {
tableStartIndex = nameBuilder.indexOf("[");
if (tableStartIndex > 0) {
int tableStopIndex = nameBuilder.indexOf("]");
if (tableStopIndex < 0) {
throw new BlenderFileException("Invalid structure name: " + name);
}
try {
lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex)));
} catch (NumberFormatException e) {
throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e);
}
nameBuilder.delete(tableStartIndex, tableStopIndex + 1);
}
} while (tableStartIndex > 0);
if (!lengths.isEmpty()) {
tableSizes = new int[lengths.size()];
for (int i = 0; i < tableSizes.length; ++i) {
tableSizes[i] = lengths.get(i).intValue();
}
}
}
name = nameBuilder.toString();
}
/**
* This method removes the required character from the text.
* @param text
* the text we remove characters from
* @param toRemove
* the character to be removed
*/
private void removeCharacter(StringBuilder text, char toRemove) {
for (int i = 0; i < text.length(); ++i) {
if (text.charAt(i) == toRemove) {
text.deleteCharAt(i);
--i;
}
}
}
/**
* This method removes all whitespace from the text.
* @param text
* the text we remove whitespace from
*/
private void removeWhitespaces(StringBuilder text) {
for (int i = 0; i < text.length(); ++i) {
if (Character.isWhitespace(text.charAt(i))) {
text.deleteCharAt(i);
--i;
}
}
}
/**
* This method builds the full name of the field (with function, pointer and table indications).
* @return the full name of the field
*/
/*package*/ String getFullName() {
StringBuilder result = new StringBuilder();
if (function) {
result.append('(');
}
for (int i = 0; i < pointerLevel; ++i) {
result.append('*');
}
result.append(name);
if (tableSizes != null) {
for (int i = 0; i < tableSizes.length; ++i) {
result.append('[').append(tableSizes[i]).append(']');
}
}
if (function) {
result.append(")()");
}
return result.toString();
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(this.getFullName());
// insert appropriate number of spaces to format the output corrently
int nameLength = result.length();
result.append(' ');// at least one space is a must
for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added
result.append(' ');
}
result.append(type);
nameLength = result.length();
for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) {
result.append(' ');
}
if (value instanceof Character) {
result.append(" = ").append((int) ((Character) value).charValue());
} else {
result.append(" = ").append(value != null ? value.toString() : "null");
}
return result.toString();
}
}

View File

@ -1,213 +0,0 @@
/*
* 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.plugins.blender.file;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.scene.plugins.blender.BlenderContext;
/**
* A class that holds the header data of a file block. The file block itself is not implemented. This class holds its
* start position in the stream and using this the structure can fill itself with the proper data.
* @author Marcin Roguski
*/
public class FileBlockHeader {
private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName());
/** Identifier of the file-block [4 bytes]. */
private BlockCode code;
/** Total length of the data after the file-block-header [4 bytes]. */
private int size;
/**
* Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
* size)].
*/
private long oldMemoryAddress;
/** Index of the SDNA structure [4 bytes]. */
private int sdnaIndex;
/** Number of structure located in this file-block [4 bytes]. */
private int count;
/** Start position of the block's data in the stream. */
private int blockPosition;
/**
* Constructor. Loads the block header from the given stream during instance creation.
* @param inputStream
* the stream we read the block header from
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception is thrown when the pointer size is neither 4 nor 8
*/
public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
inputStream.alignPosition(4);
code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte());
size = inputStream.readInt();
oldMemoryAddress = inputStream.readPointer();
sdnaIndex = inputStream.readInt();
count = inputStream.readInt();
blockPosition = inputStream.getPosition();
if (BlockCode.BLOCK_DNA1 == code) {
blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));
} else {
inputStream.setPosition(blockPosition + size);
blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this);
}
}
/**
* This method returns the structure described by the header filled with appropriate data.
* @param blenderContext
* the blender context
* @return structure filled with data
* @throws BlenderFileException
*/
public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException {
blenderContext.getInputStream().setPosition(blockPosition);
Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex);
structure.fill(blenderContext.getInputStream());
return structure;
}
/**
* This method returns the code of this data block.
* @return the code of this data block
*/
public BlockCode getCode() {
return code;
}
/**
* This method returns the size of the data stored in this block.
* @return the size of the data stored in this block
*/
public int getSize() {
return size;
}
/**
* This method returns the sdna index.
* @return the sdna index
*/
public int getSdnaIndex() {
return sdnaIndex;
}
/**
* This data returns the number of structure stored in the data block after this header.
* @return the number of structure stored in the data block after this header
*/
public int getCount() {
return count;
}
/**
* This method returns the start position of the data block in the blend file stream.
* @return the start position of the data block
*/
public int getBlockPosition() {
return blockPosition;
}
/**
* This method indicates if the block is the last block in the file.
* @return true if this block is the last one in the file nad false otherwise
*/
public boolean isLastBlock() {
return BlockCode.BLOCK_ENDB == code;
}
/**
* This method indicates if the block is the SDNA block.
* @return true if this block is the SDNA block and false otherwise
*/
public boolean isDnaBlock() {
return BlockCode.BLOCK_DNA1 == code;
}
@Override
public String toString() {
return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
}
public static enum BlockCode {
BLOCK_ME00('M' << 24 | 'E' << 16), // mesh
BLOCK_CA00('C' << 24 | 'A' << 16), // camera
BLOCK_LA00('L' << 24 | 'A' << 16), // lamp
BLOCK_OB00('O' << 24 | 'B' << 16), // object
BLOCK_MA00('M' << 24 | 'A' << 16), // material
BLOCK_SC00('S' << 24 | 'C' << 16), // scene
BLOCK_WO00('W' << 24 | 'O' << 16), // world
BLOCK_TX00('T' << 24 | 'X' << 16), // texture
BLOCK_IP00('I' << 24 | 'P' << 16), // ipo
BLOCK_AC00('A' << 24 | 'C' << 16), // action
BLOCK_IM00('I' << 24 | 'M' << 16), // image
BLOCK_TE00('T' << 24 | 'E' << 16),
BLOCK_WM00('W' << 24 | 'M' << 16),
BLOCK_SR00('S' << 24 | 'R' << 16),
BLOCK_SN00('S' << 24 | 'N' << 16),
BLOCK_BR00('B' << 24 | 'R' << 16),
BLOCK_LS00('L' << 24 | 'S' << 16),
BLOCK_GR00('G' << 24 | 'R' << 16),
BLOCK_AR00('A' << 24 | 'R' << 16),
BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'),
BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'),
BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'),
BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'),
BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'),
BLOCK_TEST('T' << 24 | 'E' << 16 | 'S' << 8 | 'T'),
BLOCK_UNKN(0);
private int code;
private BlockCode(int code) {
this.code = code;
}
public static BlockCode valueOf(int code) {
for (BlockCode blockCode : BlockCode.values()) {
if (blockCode.code == code) {
return blockCode;
}
}
byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) };
for (int i = 0; i < codeBytes.length; ++i) {
if (codeBytes[i] == 0) {
codeBytes[i] = '0';
}
}
LOGGER.log(Level.WARNING, "Unknown block header: {0}", new String(codeBytes));
return BLOCK_UNKN;
}
}
}

View File

@ -1,189 +0,0 @@
/*
* 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.plugins.blender.file;
import java.util.ArrayList;
import java.util.List;
import com.jme3.scene.plugins.blender.BlenderContext;
/**
* A class that represents a pointer of any level that can be stored in the file.
* @author Marcin Roguski
*/
public class Pointer {
/** The blender context. */
private BlenderContext blenderContext;
/** The level of the pointer. */
private int pointerLevel;
/** The address in file it points to. */
private long oldMemoryAddress;
/** This variable indicates if the field is a function pointer. */
public boolean function;
/**
* Constructr. Stores the basic data about the pointer.
* @param pointerLevel
* the level of the pointer
* @param function
* this variable indicates if the field is a function pointer
* @param blenderContext
* the repository f data; used in fetching the value that the pointer points
*/
public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) {
this.pointerLevel = pointerLevel;
this.function = function;
this.blenderContext = blenderContext;
}
/**
* This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method
* for this.
* @param inputStream
* the stream we read the pointer value from
*/
public void fill(BlenderInputStream inputStream) {
oldMemoryAddress = inputStream.readPointer();
}
/**
* This method fetches the data stored under the given address.
* @return the data read from the file
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
*/
public List<Structure> fetchData() throws BlenderFileException {
if (oldMemoryAddress == 0) {
throw new NullPointerException("The pointer points to nothing!");
}
List<Structure> structures = null;
FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress);
if (dataFileBlock == null) {
throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Make sure you did not open the newer blender file with older blender version.");
}
BlenderInputStream inputStream = blenderContext.getInputStream();
if (pointerLevel > 1) {
int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount();
for (int i = 0; i < pointersAmount; ++i) {
inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i);
long oldMemoryAddress = inputStream.readPointer();
if (oldMemoryAddress != 0L) {
Pointer p = new Pointer(pointerLevel - 1, function, blenderContext);
p.oldMemoryAddress = oldMemoryAddress;
if (structures == null) {
structures = p.fetchData();
} else {
structures.addAll(p.fetchData());
}
} else {
// it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index
// of the material is important, that is why we need null's to indicate that some materials' slots are empty
if (structures == null) {
structures = new ArrayList<Structure>();
}
structures.add(null);
}
}
} else {
inputStream.setPosition(dataFileBlock.getBlockPosition());
structures = new ArrayList<Structure>(dataFileBlock.getCount());
for (int i = 0; i < dataFileBlock.getCount(); ++i) {
Structure structure = blenderContext.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex());
structure.fill(blenderContext.getInputStream());
structures.add(structure);
}
return structures;
}
return structures;
}
/**
* This method indicates if this pointer points to a function.
* @return <b>true</b> if this is a function pointer and <b>false</b> otherwise
*/
public boolean isFunction() {
return function;
}
/**
* This method indicates if this is a null-pointer or not.
* @return <b>true</b> if the pointer is null and <b>false</b> otherwise
*/
public boolean isNull() {
return oldMemoryAddress == 0;
}
/**
* This method indicates if this is a null-pointer or not.
* @return <b>true</b> if the pointer is not null and <b>false</b> otherwise
*/
public boolean isNotNull() {
return oldMemoryAddress != 0;
}
/**
* This method returns the old memory address of the structure pointed by the pointer.
* @return the old memory address of the structure pointed by the pointer
*/
public long getOldMemoryAddress() {
return oldMemoryAddress;
}
@Override
public String toString() {
return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}";
}
@Override
public int hashCode() {
return 31 + (int) (oldMemoryAddress ^ oldMemoryAddress >>> 32);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
Pointer other = (Pointer) obj;
if (oldMemoryAddress != other.oldMemoryAddress) {
return false;
}
return true;
}
}

View File

@ -1,328 +0,0 @@
/*
* Copyright (c) 2009-2018 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR 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.plugins.blender.file;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.jme3.scene.plugins.blender.BlenderContext;
/**
* A class representing a single structure in the file.
* @author Marcin Roguski
*/
public class Structure implements Cloneable {
/** The address of the block that fills the structure. */
private transient Long oldMemoryAddress;
/** The type of the structure. */
private String type;
/**
* The fields of the structure. Each field consists of a pair: name-type.
*/
private Field[] fields;
/**
* Constructor that copies the data of the structure.
* @param structure
* the structure to copy.
* @throws CloneNotSupportedException
* this exception should never be thrown
*/
private Structure(Structure structure) throws CloneNotSupportedException {
type = structure.type;
fields = new Field[structure.fields.length];
for (int i = 0; i < fields.length; ++i) {
fields[i] = (Field) structure.fields[i].clone();
}
oldMemoryAddress = structure.oldMemoryAddress;
}
/**
* Constructor. Loads the structure from the given stream during instance creation.
* @param inputStream
* the stream we read the structure from
* @param names
* the names from which the name of structure and its fields will be taken
* @param types
* the names of types for the structure
* @param blenderContext
* the blender context
* @throws BlenderFileException
* this exception occurs if the amount of fields, defined in the file, is negative
*/
public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException {
int nameIndex = inputStream.readShort();
type = types[nameIndex];
int fieldsAmount = inputStream.readShort();
if (fieldsAmount < 0) {
throw new BlenderFileException("The amount of fields of " + type + " structure cannot be negative!");
}
if (fieldsAmount > 0) {
fields = new Field[fieldsAmount];
for (int i = 0; i < fieldsAmount; ++i) {
int typeIndex = inputStream.readShort();
nameIndex = inputStream.readShort();
fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext);
}
}
oldMemoryAddress = Long.valueOf(-1L);
}
/**
* This method fills the structure with data.
* @param inputStream
* the stream we read data from, its read cursor should be placed at the start position of the data for the
* structure
* @throws BlenderFileException
* an exception is thrown when the blend file is somehow invalid or corrupted
*/
public void fill(BlenderInputStream inputStream) throws BlenderFileException {
int position = inputStream.getPosition();
inputStream.setPosition(position - 8 - inputStream.getPointerSize());
oldMemoryAddress = Long.valueOf(inputStream.readPointer());
inputStream.setPosition(position);
for (Field field : fields) {
field.fill(inputStream);
}
}
/**
* This method returns the value of the filed with a given name.
* @param fieldName
* the name of the field
* @return the value of the field or null if no field with a given name is found
*/
public Object getFieldValue(String fieldName) {
return this.getFieldValue(fieldName, null);
}
/**
* This method returns the value of the filed with a given name.
* @param fieldName
* the name of the field
* @param defaultValue
* the value that is being returned when no field of a given name is found
* @return the value of the field or the given default value if no field with a given name is found
*/
public Object getFieldValue(String fieldName, Object defaultValue) {
for (Field field : fields) {
if (field.name.equalsIgnoreCase(fieldName)) {
return field.value;
}
}
return defaultValue;
}
/**
* This method returns the value of the filed with a given name. The structure is considered to have flat fields
* only (no substructures).
* @param fieldName
* the name of the field
* @return the value of the field or null if no field with a given name is found
*/
public Object getFlatFieldValue(String fieldName) {
for (Field field : fields) {
Object value = field.value;
if (field.name.equalsIgnoreCase(fieldName)) {
return value;
} else if (value instanceof Structure) {
value = ((Structure) value).getFlatFieldValue(fieldName);
if (value != null) {// we can compare references here, since we use one static object as a NULL field value
return value;
}
}
}
return null;
}
/**
* This method should be used on structures that are of a 'ListBase' type. It creates a List of structures that are
* held by this structure within the blend file.
* @return a list of filled structures
* @throws BlenderFileException
* this exception is thrown when the blend file structure is somehow invalid or corrupted
* @throws IllegalArgumentException
* this exception is thrown if the type of the structure is not 'ListBase'
*/
public List<Structure> evaluateListBase() throws BlenderFileException {
if (!"ListBase".equals(type)) {
throw new IllegalStateException("This structure is not of type: 'ListBase'");
}
Pointer first = (Pointer) this.getFieldValue("first");
Pointer last = (Pointer) this.getFieldValue("last");
long currentAddress = 0;
long lastAddress = last.getOldMemoryAddress();
List<Structure> result = new LinkedList<Structure>();
while (currentAddress != lastAddress) {
currentAddress = first.getOldMemoryAddress();
Structure structure = first.fetchData().get(0);
result.add(structure);
first = (Pointer) structure.getFlatFieldValue("next");
}
return result;
}
/**
* This method returns the type of the structure.
* @return the type of the structure
*/
public String getType() {
return type;
}
/**
* This method returns the amount of fields for the current structure.
* @return the amount of fields for the current structure
*/
public int getFieldsAmount() {
return fields.length;
}
/**
* This method returns the full field name of the given index.
* @param fieldIndex
* the index of the field
* @return the full field name of the given index
*/
public String getFieldFullName(int fieldIndex) {
return fields[fieldIndex].getFullName();
}
/**
* This method returns the field type of the given index.
* @param fieldIndex
* the index of the field
* @return the field type of the given index
*/
public String getFieldType(int fieldIndex) {
return fields[fieldIndex].type;
}
/**
* This method returns the address of the structure. The structure should be filled with data otherwise an exception
* is thrown.
* @return the address of the feature stored in this structure
*/
public Long getOldMemoryAddress() {
if (oldMemoryAddress.longValue() == -1L) {
throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!");
}
return oldMemoryAddress;
}
/**
* This method returns the name of the structure. If the structure has an ID field then the name is returned.
* Otherwise the name does not exists and the method returns null.
* @return the name of the structure read from the ID field or null
*/
public String getName() {
Object fieldValue = this.getFieldValue("ID");
if (fieldValue instanceof Structure) {
Structure id = (Structure) fieldValue;
return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix
}
Object name = this.getFieldValue("name", null);
return name == null ? null : name.toString().substring(2);
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n");
for (int i = 0; i < fields.length; ++i) {
result.append(fields[i].toString()).append('\n');
}
return result.append('}').toString();
}
@Override
public Object clone() throws CloneNotSupportedException {
return new Structure(this);
}
/**
* This enum enumerates all known data types that can be found in the blend file.
* @author Marcin Roguski (Kaelthas)
*/
/* package */static enum DataType {
CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER;
/** The map containing the known primary types. */
private static final Map<String, DataType> PRIMARY_TYPES = new HashMap<String, DataType>(10);
static {
PRIMARY_TYPES.put("char", CHARACTER);
PRIMARY_TYPES.put("uchar", CHARACTER);
PRIMARY_TYPES.put("short", SHORT);
PRIMARY_TYPES.put("ushort", SHORT);
PRIMARY_TYPES.put("int", INTEGER);
PRIMARY_TYPES.put("long", LONG);
PRIMARY_TYPES.put("ulong", LONG);
PRIMARY_TYPES.put("uint64_t", LONG);
PRIMARY_TYPES.put("float", FLOAT);
PRIMARY_TYPES.put("double", DOUBLE);
PRIMARY_TYPES.put("void", VOID);
}
/**
* This method returns the data type that is appropriate to the given type name. WARNING! The type recognition
* is case sensitive!
* @param type
* the type name of the data
* @param blenderContext
* the blender context
* @return appropriate enum value to the given type name
* @throws BlenderFileException
* this exception is thrown if the given type name does not exist in the blend file
*/
public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException {
DataType result = PRIMARY_TYPES.get(type);
if (result != null) {
return result;
}
if (blenderContext.getDnaBlockData().hasStructure(type)) {
return STRUCTURE;
}
throw new BlenderFileException("Unknown data type: " + type);
}
/**
* @return a collection of known primary types names
*/
/* package */static Collection<String> getKnownPrimaryTypesNames() {
return PRIMARY_TYPES.keySet();
}
}
}

Some files were not shown because too many files have changed in this diff Show More