Compare commits
540 Commits
in-pass-sh
...
master
Author | SHA1 | Date |
---|---|---|
Stephen Gold | 6402aad452 | 5 years ago |
Trevor Flynn | e78f303a9d | 5 years ago |
kkolyan | dd0c169d62 | 5 years ago |
René Kraneis | 32e8b68ea0 | 5 years ago |
kkolyan | eb7aab9704 | 5 years ago |
pspeed42 | d29d8f5100 | 5 years ago |
Stephen Gold | acc5cfa995 | 5 years ago |
Stephen Gold | f34e660f03 | 5 years ago |
kkolyan | 0f06c14c28 | 5 years ago |
Stephen Gold | 6ade1a027e | 5 years ago |
Davis Rollman | 30bddef8c0 | 5 years ago |
ItsMike54 | f808187142 | 5 years ago |
Stephen Gold | d6b0069407 | 5 years ago |
Stephen Gold | c0692df207 | 5 years ago |
Github Actions | 036b08a385 | 5 years ago |
Stephen Gold | 407ebc9d5e | 5 years ago |
Github Actions | f7352ffcfe | 5 years ago |
Stephen Gold | 599a0d5c56 | 5 years ago |
Stephen Gold | 8aa50e9b48 | 5 years ago |
Stephen Gold | 5ae9285903 | 5 years ago |
Stephen Gold | bb6ac612ff | 5 years ago |
Stephen Gold | 9751e23cd7 | 5 years ago |
Stephen Gold | ceea055cc4 | 5 years ago |
Stephen Gold | d636049a6f | 5 years ago |
Stephen Gold | 917509a04e | 5 years ago |
rvandoosselaer | 3fa85bc139 | 5 years ago |
jayfella | f3d81d11a1 | 5 years ago |
jayfella | 37f1e7fabe | 5 years ago |
jayfella | a057a6d74d | 5 years ago |
Stephen Gold | 15e3acbe4d | 5 years ago |
Stephen Gold | e3161f4622 | 5 years ago |
Stephen Gold | c9357b4db1 | 5 years ago |
Stephen Gold | 2e56e1f24c | 5 years ago |
Stephen Gold | 32f71397c5 | 5 years ago |
Stephen Gold | f6c467b495 | 5 years ago |
Stephen Gold | 84c88709b5 | 5 years ago |
pspeed42 | 221acaadfe | 5 years ago |
pspeed42 | 94451c4d35 | 5 years ago |
Stephen Gold | 7212f9b2b2 | 5 years ago |
Stephen Gold | a5e0213b01 | 5 years ago |
Stephen Gold | a1dcbf9b06 | 5 years ago |
Stephen Gold | a42103651c | 5 years ago |
Stephen Gold | cd7d497c79 | 5 years ago |
Stephen Gold | 1e7c95cbb8 | 5 years ago |
Stephen Gold | ee910859ac | 5 years ago |
Stephen Gold | 5a2499ccbe | 5 years ago |
Stephen Gold | 4c62d5008e | 5 years ago |
Stephen Gold | 4d81fc6035 | 5 years ago |
Stephen Gold | fa62959451 | 5 years ago |
Stephen Gold | 607e068f76 | 5 years ago |
Stephen Gold | bb7d2cab01 | 5 years ago |
Stephen Gold | 6cbf62a3b5 | 5 years ago |
Stephen Gold | 03673ac1f7 | 5 years ago |
Stephen Gold | 32ab16e026 | 5 years ago |
Stephen Gold | 84836c6260 | 5 years ago |
Stephen Gold | 851793cde6 | 5 years ago |
Stephen Gold | bee3d36f16 | 5 years ago |
Stephen Gold | 50dc8349a1 | 5 years ago |
Stephen Gold | e99c8a74a3 | 5 years ago |
Github Actions | f46a6fac11 | 5 years ago |
Stephen Gold | 64bfe42878 | 5 years ago |
Stephen Gold | 1c3ed5122f | 5 years ago |
Stephen Gold | 70ae48af1e | 5 years ago |
Stephen Gold | c7a734d590 | 5 years ago |
Stephen Gold | 822bcd1300 | 5 years ago |
Github Actions | ae9854df2b | 5 years ago |
Stephen Gold | 88c9371a4d | 5 years ago |
Stephen Gold | 297443ada4 | 5 years ago |
Stephen Gold | 60967b9f04 | 5 years ago |
Stephen Gold | a6f2c12700 | 5 years ago |
ItsMike54 | 6c5611d3e2 | 5 years ago |
matthias plasser | 89df7909d3 | 5 years ago |
Jérôme | b92e7a0abe | 5 years ago |
rvandoosselaer | 26f393d09c | 5 years ago |
rvandoosselaer | 2e1a8dcb52 | 5 years ago |
rvandoosselaer | 76210fe415 | 5 years ago |
Jérôme | b48391399b | 5 years ago |
Jérôme | 4c8e400a83 | 5 years ago |
Riccardo Balbo | d109e4739e | 5 years ago |
Ali-RS | 16a3e7630c | 5 years ago |
Stephen Gold | b8287a2d0d | 5 years ago |
Stephen Gold | fef5c101d8 | 5 years ago |
Stephen Gold | 51022fd521 | 5 years ago |
Jérôme | b613be35fa | 5 years ago |
Jérôme | 2a0fa2554a | 5 years ago |
Jérôme | 3385cdaa3f | 5 years ago |
Jérôme | 1433f98ad3 | 5 years ago |
Jérôme | 11d3bc67f8 | 5 years ago |
Jérôme | bd4691e06b | 5 years ago |
Grabli66 | fafe8a74fe | 5 years ago |
MeFisto94 | efc1962092 | 5 years ago |
MeFisto94 | 989b2a66cf | 5 years ago |
MeFisto94 | 0115616652 | 5 years ago |
MeFisto94 | 64797f9bff | 5 years ago |
MeFisto94 | 21b7a71cf8 | 5 years ago |
MeFisto94 | 8f8fc19043 | 5 years ago |
MeFisto94 | 646e6741b9 | 5 years ago |
MeFisto94 | 2276197a2b | 5 years ago |
MeFisto94 | 2379cfba77 | 5 years ago |
MeFisto94 | df39677381 | 5 years ago |
MeFisto94 | c198b1d546 | 5 years ago |
MeFisto94 | d19ae582a1 | 5 years ago |
MeFisto94 | 461227bdef | 5 years ago |
Riccardo Balbo | 728a05c4f3 | 5 years ago |
Ali-RS | 0c6f240222 | 5 years ago |
MeFisto94 | 4d81d00f45 | 5 years ago |
MeFisto94 | 312fb5bb57 | 5 years ago |
Riccardo Balbo | 7c01019f0c | 5 years ago |
Riccardo Balbo | f505bdeb21 | 5 years ago |
Daniel Perano | ff58abca3d | 5 years ago |
Toni Helenius | 59793d4c06 | 5 years ago |
Daniel Perano | 0aa50a0f15 | 5 years ago |
Stephen Gold | 0252c1b623 | 5 years ago |
Stephen Gold | a407fc3224 | 5 years ago |
Toni Helenius | b93ea18fa2 | 5 years ago |
Lou Hamersly | c1d359ca59 | 5 years ago |
Stephen Gold | 8e334aa756 | 5 years ago |
Github Actions | 426969daeb | 5 years ago |
Stephen Gold | f652591281 | 5 years ago |
MeFisto94 | 12481c08f6 | 5 years ago |
MeFisto94 | ab96460853 | 5 years ago |
Stephen Gold | f268d00222 | 5 years ago |
Paul Speed | 5584c93d4a | 5 years ago |
MeFisto94 | 8219d7fc02 | 5 years ago |
Stephen Gold | dde0906963 | 5 years ago |
Github Actions | e3b44db4aa | 5 years ago |
Riccardo Balbo | 31476679be | 5 years ago |
Riccardo Balbo | 0244ab230b | 5 years ago |
Stephen Gold | 1a0b6ecac3 | 5 years ago |
MeFisto94 | acbddc2763 | 5 years ago |
Github Actions | 3c5dd5c168 | 5 years ago |
Stephen Gold | 0fd70b81c9 | 5 years ago |
Toni Helenius | 427ae0a28b | 5 years ago |
Ali-RS | 2023440acf | 5 years ago |
Toni Helenius | 867e46190e | 5 years ago |
MeFisto94 | eee37022f2 | 5 years ago |
MeFisto94 | 124ad35677 | 5 years ago |
Ryan McDonough | 233bc6f0da | 5 years ago |
Toni Helenius | ffd9cfcf25 | 5 years ago |
Paul Speed | 81f9b9d92a | 5 years ago |
MeFisto94 | 9b29e05968 | 5 years ago |
MeFisto94 | 933b0912e6 | 5 years ago |
MeFisto94 | ec491575be | 5 years ago |
MeFisto94 | 04e7bed5e7 | 5 years ago |
Stephen Gold | 77c521fefa | 5 years ago |
Stephen Gold | bc64238635 | 5 years ago |
Stephen Gold | 3f59008566 | 5 years ago |
Riccardo Balbo | b059c7c0dd | 5 years ago |
joliver82 | 68fb1afe5d | 5 years ago |
Paul Speed | b2ae269ede | 5 years ago |
Paul Speed | 5db3ac4fac | 5 years ago |
Riccardo Balbo | 124ef031d4 | 5 years ago |
Stephen Gold | cdcf0512d9 | 5 years ago |
Stephen Gold | 6b7dd5b325 | 5 years ago |
Stephen Gold | 8d9d091576 | 5 years ago |
Stephen Gold | 98f6d326e1 | 5 years ago |
Github Actions | 584bb79392 | 5 years ago |
Stephen Gold | 75001d8abd | 5 years ago |
Stephen Gold | 9d2d393fc3 | 5 years ago |
Paul Speed | c73fd99dd6 | 5 years ago |
Paul Speed | c23f28b51c | 5 years ago |
Paul Speed | 45b1908906 | 5 years ago |
Paul Speed | 37bb494709 | 5 years ago |
joliver82 | 30df2f1b87 | 5 years ago |
Matthew Universe | 48f28974f9 | 5 years ago |
Riccardo Balbo | b07a11c9d4 | 5 years ago |
joliver82 | d935347bde | 5 years ago |
Riccardo Balbo | 816ab99ac5 | 5 years ago |
Github Actions | e304c5bb6d | 5 years ago |
Riccardo Balbo | 128e079a22 | 5 years ago |
joliver82 | 663c9776e8 | 5 years ago |
Ali-RS | 7ae1ff23af | 5 years ago |
Riccardo Balbo | d0186664f2 | 5 years ago |
Stephen Gold | 92b161f38e | 5 years ago |
Riccardo Balbo | 3217cdc74c | 5 years ago |
Thodoris Sotiropoulos | 4d734ac297 | 5 years ago |
Github Actions | d8a42d0817 | 5 years ago |
Riccardo Balbo | 973d8be894 | 5 years ago |
Riccardo Balbo | b571840bd0 | 5 years ago |
Ali-RS | 2c6161ecd8 | 5 years ago |
Ali-RS | d4c9a9ad1e | 5 years ago |
Ali-RS | 3b8665fe93 | 5 years ago |
Ali-RS | 5c75af1d38 | 5 years ago |
Github Actions | 96b16884ce | 5 years ago |
Riccardo Balbo | a4c694ba36 | 5 years ago |
Riccardo Balbo | 69ef8efcce | 5 years ago |
Riccardo Balbo | 88e9c8482c | 5 years ago |
Riccardo Balbo | 8a6bd1947c | 5 years ago |
Riccardo Balbo | 273f65b651 | 5 years ago |
MeFisto94 | 88385ba647 | 5 years ago |
Ali-RS | b1db497a00 | 5 years ago |
Daniel Perano | f312608725 | 5 years ago |
Ali-RS | f506e2a5a5 | 5 years ago |
Ali-RS | a6a35b370f | 5 years ago |
appveyor | 3b6a602f96 | 5 years ago |
Stephen Gold | 62faa9a873 | 5 years ago |
appveyor | 84d3e0615d | 5 years ago |
Stephen Gold | bb32b882a0 | 5 years ago |
Stephen Gold | 2010c819cb | 5 years ago |
appveyor | 4c8cd56c50 | 5 years ago |
sgold | b57d8239b4 | 5 years ago |
sgold | 52fbc17f4f | 5 years ago |
appveyor | 1f02d87793 | 5 years ago |
Stephen Gold | 001b59344d | 5 years ago |
Stephen Gold | 0425c61dd4 | 5 years ago |
Stephen Gold | dcb66977ba | 5 years ago |
Ali-RS | 621a4ab6a9 | 5 years ago |
Ali-RS | 981aa1d181 | 5 years ago |
Stephen Gold | 46a52af2f5 | 5 years ago |
travis-ci | bc38b9b7ef | 5 years ago |
Stephen Gold | 89e753bf47 | 5 years ago |
Stephen Gold | 3e89cd1377 | 5 years ago |
sgold | 126874873c | 5 years ago |
travis-ci | 6640436b03 | 5 years ago |
travis-ci | cc9fcd7173 | 5 years ago |
appveyor | 8f6c88413f | 5 years ago |
sgold | 3482904c76 | 5 years ago |
travis-ci | 4ccdd7aae0 | 5 years ago |
sgold | 814ab1b106 | 5 years ago |
travis-ci | 4e56a6e24a | 5 years ago |
appveyor | 06c07fe0df | 5 years ago |
Stephen Gold | 42530de219 | 5 years ago |
Stephen Gold | 2c2f76cca1 | 5 years ago |
travis-ci | 91f304272a | 5 years ago |
appveyor | 1484107c92 | 5 years ago |
Stephen Gold | 392a3c5cff | 5 years ago |
Stephen Gold | 0f3bc6cdcf | 5 years ago |
Stephen Gold | 5eaf653de9 | 5 years ago |
travis-ci | 909ea30216 | 5 years ago |
appveyor | 637ccf91db | 5 years ago |
Stephen Gold | 30d1ecaec2 | 5 years ago |
Stephen Gold | 3c74fe539c | 5 years ago |
MeFisto94 | 6aac1a21bd | 5 years ago |
MeFisto94 | ad64b7e4b6 | 5 years ago |
MeFisto94 | 21d0854b61 | 5 years ago |
MeFisto94 | c461a1b43d | 5 years ago |
Stephen Gold | 9aa25ad3ef | 5 years ago |
Stephen Gold | 04953341d4 | 5 years ago |
mitm001 | 7a4e881bc6 | 5 years ago |
appveyor | 970eb7fa15 | 5 years ago |
MeFisto94 | 1bde90d1a1 | 5 years ago |
Stephen Gold | 194ae883c5 | 5 years ago |
MeFisto94 | 3ec89ce499 | 5 years ago |
Paul Speed | 1ffd11fae4 | 5 years ago |
Paul Speed | 36afe829c6 | 5 years ago |
Paul Speed | 3dd2755c20 | 5 years ago |
Paul Speed | 3b8314a36f | 5 years ago |
MeFisto94 | 90d3b69bd1 | 5 years ago |
Paul Speed | 0d8fe2ac22 | 5 years ago |
Paul Speed | 1c37d5a92d | 5 years ago |
Riccardo Balbo | a9afcecc41 | 5 years ago |
grizeldi | 484d192467 | 5 years ago |
Ali-RS | 3d7a5ee01b | 5 years ago |
Ali-RS | 740832d699 | 5 years ago |
Ali-RS | 16ccd36d56 | 5 years ago |
Ali-RS | 53839ca528 | 5 years ago |
Dima Myroniuk | ae67b9c6f8 | 5 years ago |
Ali-RS | 467d7b2f0f | 5 years ago |
sgold | 3fe896300d | 5 years ago |
Lou Hamersly | b864372256 | 5 years ago |
Stephen Gold | acdeef54fe | 5 years ago |
empirephoenix | 80aed88f82 | 5 years ago |
Lou Hamersly | de092b92bb | 5 years ago |
travis-ci | e31a047746 | 5 years ago |
travis-ci | 9f51de98a5 | 5 years ago |
appveyor | e7bf627e9d | 5 years ago |
sgold | 23ee02071e | 5 years ago |
Ali-RS | 02ccce08ec | 5 years ago |
Trevor Flynn | 57db8f618f | 5 years ago |
Riccardo Balbo | 7058439e7d | 5 years ago |
Sebastian Teumert | 0ffc612bfb | 5 years ago |
Lou H | 827d4ebd52 | 5 years ago |
travis-ci | 1d5fe496e0 | 5 years ago |
appveyor | 69319738e6 | 5 years ago |
travis-ci | 918d90a637 | 5 years ago |
Stephen Gold | f831301a2a | 5 years ago |
sgold | 2ab0319202 | 5 years ago |
Lou H | 8856ba7d25 | 5 years ago |
sgold | cff4dec57d | 5 years ago |
Riccardo Balbo | e603845fa5 | 6 years ago |
Stephen Gold | fca8bcf470 | 6 years ago |
Remy | adb88dcaeb | 6 years ago |
sgold | 23759820e5 | 6 years ago |
travis-ci | 12e513c8ee | 6 years ago |
travis-ci | ad6232c297 | 6 years ago |
appveyor | 5b9693f000 | 6 years ago |
sgold | 56d37cb866 | 6 years ago |
Stephen Gold | 2cd3dac543 | 6 years ago |
Stephen Gold | df02333cd2 | 6 years ago |
Stephen Gold | 5586c28c95 | 6 years ago |
Lou H | 1687d34eb7 | 6 years ago |
Lou H | 1f3245b237 | 6 years ago |
Stephen Gold | a91bcfd678 | 6 years ago |
Stephen Gold | a4b22e4b37 | 6 years ago |
richardTingle | 04f771f7f9 | 6 years ago |
Lou H | f8dae05a52 | 6 years ago |
Karan Nehra | 9dff704a82 | 6 years ago |
Lou Hamersly | cde108c2c0 | 6 years ago |
Riccardo Balbo | 97049630eb | 6 years ago |
Karan Nehra | ddeb4549a2 | 6 years ago |
Lou H | 930090dfa0 | 6 years ago |
Lou Hamersly | cf45e66d11 | 6 years ago |
James Khan | b145268004 | 6 years ago |
James Khan | 82faaca87c | 6 years ago |
James Khan | a8f5ac0589 | 6 years ago |
James Khan | 1116df31c9 | 6 years ago |
Riccardo Balbo | a68d8b50e3 | 6 years ago |
lifeinwild | 7e11a6c0fe | 6 years ago |
James Khan | 4f47e5602b | 6 years ago |
appveyor | a43ad8ecfe | 6 years ago |
Stephen Gold | 5451e3eaf0 | 6 years ago |
Stephen Gold | 3f1f2e8356 | 6 years ago |
Paul Speed | 33ade6b874 | 6 years ago |
Stephen Gold | 6a71a1c273 | 6 years ago |
Stephen Gold | ac01203c80 | 6 years ago |
James Khan | b9b4a2d75b | 6 years ago |
James Khan | 2f6185b5cf | 6 years ago |
James Khan | d3be2f332f | 6 years ago |
James Khan | 76fcc2c497 | 6 years ago |
James Khan | 682d8c9fd8 | 6 years ago |
James Khan | 80adca6dce | 6 years ago |
James Khan | 318d6d0e89 | 6 years ago |
Ali-RS | c4d2de1656 | 6 years ago |
Stephen Gold | 50aed6f59e | 6 years ago |
Stephen Gold | fff16c2328 | 6 years ago |
Ali-RS | 36ddb5b0ce | 6 years ago |
Stephen Gold | 40be1b42b8 | 6 years ago |
Stephen Gold | 432085c191 | 6 years ago |
Stephen Gold | 234bd476f3 | 6 years ago |
Remy | 8d3980bbe9 | 6 years ago |
Stephen Gold | 0bf931bf79 | 6 years ago |
Greg Hoffman | 0bc060d474 | 6 years ago |
Remy | ffa1df2aff | 6 years ago |
Trevor Flynn | 9c1452b63b | 6 years ago |
appveyor | 41f88cd5eb | 6 years ago |
Trevor Flynn | ad2ba95e99 | 6 years ago |
remy | 74588d96f3 | 6 years ago |
Remy Van Doosselaer | 988537987a | 6 years ago |
travis-ci | 35b9d9df4e | 6 years ago |
travis-ci | 4bd8cfb218 | 6 years ago |
appveyor | 28cba18cbf | 6 years ago |
Stephen Gold | 9e532009da | 6 years ago |
Stephen Gold | ba340cf13a | 6 years ago |
travis-ci | 3696074c9a | 6 years ago |
travis-ci | 18cc15254b | 6 years ago |
appveyor | 73c055c37e | 6 years ago |
Stephen Gold | 8a4e5e1177 | 6 years ago |
Paul Speed | 657c5841aa | 6 years ago |
Remy Van Doosselaer | 9a80f013fc | 6 years ago |
Rickard Edén | 1a1348983b | 6 years ago |
FennelFetish | 88a9069662 | 6 years ago |
Riccardo Balbo | 3b24067a3c | 6 years ago |
Paul Speed | 7c6e12ed70 | 6 years ago |
Ali-RS | cd90be13ec | 6 years ago |
Stephen Gold | d55776d081 | 6 years ago |
Stephen Gold | 235b9db2ca | 6 years ago |
Paul Speed | 59af265398 | 6 years ago |
Paul Speed | 54b812ca71 | 6 years ago |
Paul Speed | 5901a95363 | 6 years ago |
quazi-irfan | 5bc64800e0 | 6 years ago |
Paul Speed | ac60a134d2 | 6 years ago |
Stephen Gold | 27bd16979c | 6 years ago |
Stephen Gold | f0d28a9501 | 6 years ago |
Stephen Gold | a2df09b2fd | 6 years ago |
Riccardo Balbo | 51cadb260c | 6 years ago |
Riccardo Balbo | cf7b15bc23 | 6 years ago |
Stephen Gold | 1ad324aa57 | 6 years ago |
James Adam | 7c2a401c28 | 6 years ago |
Riccardo Balbo | f60ff58ef0 | 6 years ago |
Paul Speed | 9a6032a087 | 6 years ago |
Ali-RS | c43eafc319 | 6 years ago |
appveyor | 9f9b443dee | 6 years ago |
Stephen Gold | dc99bc0731 | 6 years ago |
Stephen Gold | a6622d4613 | 6 years ago |
Stephen Gold | 9d9132d5b6 | 6 years ago |
appveyor | 31574f1c65 | 6 years ago |
Stephen Gold | a03f1fe49c | 6 years ago |
Quazi Irfan | 76049c76f3 | 6 years ago |
Stephen Gold | 2124e3e86b | 6 years ago |
Stephen Gold | b9c13a209a | 6 years ago |
Stephen Gold | 7e929aacab | 6 years ago |
James Adam | fe03a9b6a3 | 6 years ago |
James Adam | eba9ef6d58 | 6 years ago |
Ali-RS | 6dd737d378 | 6 years ago |
Stephen Gold | ffa58be3d7 | 6 years ago |
Stephen Gold | 75112201db | 6 years ago |
Stephen Gold | 2356320d30 | 6 years ago |
Riccardo Balbo | 3851c35f08 | 6 years ago |
Stephen Gold | 784106e0c6 | 6 years ago |
travis-ci | ff5124bc93 | 6 years ago |
travis-ci | d60aec411b | 6 years ago |
appveyor | f1d9eb37c4 | 6 years ago |
Riccardo Balbo | 2bbf784c96 | 6 years ago |
Stephen Gold | 7e6fa03009 | 6 years ago |
Stephen Gold | f33252f4d0 | 6 years ago |
Stephen Gold | 72bed74fa1 | 6 years ago |
Stephen Gold | 15f9f69611 | 6 years ago |
Stephen Gold | 4581b6cb36 | 6 years ago |
Stephen Gold | c7c203853c | 6 years ago |
Stephen Gold | 8d6900a32a | 6 years ago |
Stephen Gold | 5bf619cca3 | 6 years ago |
Stephen Gold | b2381e8aed | 6 years ago |
Stephen Gold | 7ad91651f1 | 6 years ago |
Stephen Gold | c11fbe4dd8 | 6 years ago |
Stephen Gold | 8ecddf4fa2 | 6 years ago |
Stephen Gold | ce730e5656 | 6 years ago |
Stephen Gold | 2c25d77e64 | 6 years ago |
Stephen Gold | 512b096cb4 | 6 years ago |
Stephen Gold | 6ab7c0d91f | 6 years ago |
Ali-RS | dae85e1598 | 6 years ago |
Ali-RS | 6679873c9b | 6 years ago |
Ali-RS | 9ded31cf1c | 6 years ago |
Riccardo Balbo | 626af3123a | 6 years ago |
Julien Seinturier | 835fbd7957 | 6 years ago |
Ali-RS | 7363662f21 | 6 years ago |
Ali-RS | 637162e484 | 6 years ago |
FennelFetish | 254a5839c1 | 6 years ago |
FennelFetish | 0f9cf090e7 | 6 years ago |
NemesisMate | 304550a0f4 | 6 years ago |
Paul Speed | 7b0471c43a | 6 years ago |
Paul Speed | d415cbb66b | 6 years ago |
Patrick Werner | c7f6aa9ae6 | 6 years ago |
Ali-RS | 7b346d2760 | 6 years ago |
Stephen Gold | 9d6d31d685 | 6 years ago |
Stephen Gold | 34b32bf29b | 6 years ago |
Stephen Gold | d17376e713 | 6 years ago |
Stephen Gold | 51b12e1b9e | 6 years ago |
Davis Rollman | c6158bba68 | 6 years ago |
Stephen Gold | 13d00e0df3 | 6 years ago |
Stephen Gold | b9ea661391 | 6 years ago |
Stephen Gold | fceee58546 | 6 years ago |
Stephen Gold | 000ac95fd4 | 6 years ago |
Stephen Gold | e5fb5bfefe | 6 years ago |
Stephen Gold | 0e9ff83996 | 6 years ago |
Stephen Gold | 7c6d828cb5 | 6 years ago |
Stephen Gold | 8cd5894f55 | 6 years ago |
Stephen Gold | 3c3e3f4af2 | 6 years ago |
Stephen Gold | 64d0e00a49 | 6 years ago |
Stephen Gold | bbf1f73a89 | 6 years ago |
Stephen Gold | 2a904034c2 | 6 years ago |
Toni Helenius | 55d5cd7381 | 6 years ago |
Stephen Gold | 1bb388d793 | 6 years ago |
Stephen Gold | 44801da706 | 6 years ago |
Paul Speed | 1f02567739 | 6 years ago |
grizeldi | a88fdf99f0 | 6 years ago |
Francivan Bezerra | 467af4fff5 | 6 years ago |
Paul Speed | f73d10bb9c | 6 years ago |
mitm | 416b866c78 | 6 years ago |
mitm001 | d785caa5ca | 6 years ago |
Stephen Gold | 06d9194ddf | 6 years ago |
Stephen Gold | 627f4726e5 | 6 years ago |
mitm001 | 8a10738097 | 6 years ago |
appveyor | c22fac2dce | 6 years ago |
Stephen Gold | 2d621ce24e | 6 years ago |
Stephen Gold | b525d6c00d | 6 years ago |
Stephen Gold | 017800251b | 6 years ago |
Stephen Gold | 552f4a069c | 6 years ago |
Stephen Gold | c2337e823e | 6 years ago |
Stephen Gold | c4e1e7f1f8 | 6 years ago |
Stephen Gold | cf7725a56e | 6 years ago |
Stephen Gold | 53d7052301 | 6 years ago |
Ali-RS | a47b4a46ba | 6 years ago |
Stephen Gold | d8acd30a27 | 6 years ago |
travis-ci | cde3c39c4f | 6 years ago |
travis-ci | 3d1dec7d77 | 6 years ago |
appveyor | 347191f6bd | 6 years ago |
Stephen Gold | cf33c3c2dd | 6 years ago |
Paul Speed | f44fdb35e6 | 6 years ago |
Ali-RS | a61813ebd8 | 6 years ago |
Ali-RS | c0823bb903 | 6 years ago |
appveyor | d36deaf8ea | 6 years ago |
Stephen Gold | 938c28fd2b | 6 years ago |
travis-ci | 2ffebcf044 | 6 years ago |
travis-ci | a95cf8963f | 6 years ago |
appveyor | a66cb558c3 | 6 years ago |
Stephen Gold | e9069a0276 | 6 years ago |
Stephen Gold | 772af5ac60 | 6 years ago |
Stephen Gold | 7da493d1b1 | 6 years ago |
Stephen Gold | d3e7d20374 | 6 years ago |
MeFisto94 | 3d63433b28 | 6 years ago |
Stephen Gold | c707785a34 | 6 years ago |
Paul Speed | 53c788c031 | 6 years ago |
Stephen Gold | 68ea05eaa6 | 6 years ago |
Stephen Gold | c29a4bb1c6 | 6 years ago |
Paul Speed | d5bfe1e813 | 6 years ago |
Paul Speed | c2fe803f53 | 6 years ago |
joliver82 | 2b1c3d22e8 | 6 years ago |
joliver82 | f19ad06820 | 6 years ago |
Eike Foede | 07c0df7f1d | 6 years ago |
JESTERRRRRR | 7095ad10e0 | 6 years ago |
Stephen Gold | 21454f2ab2 | 6 years ago |
Rickard Edén | a8b2ad0869 | 6 years ago |
Stephen Gold | 8f21a7eb55 | 6 years ago |
jseinturier | 9661d8c747 | 6 years ago |
JESTERRRRRR | 45f7c3a230 | 6 years ago |
travis-ci | 03a5a4470a | 6 years ago |
travis-ci | 3f4d47559c | 6 years ago |
appveyor | 58eb2029c1 | 6 years ago |
Stephen Gold | 976d45a230 | 6 years ago |
David Berrios | 95ba65c8c5 | 6 years ago |
Toni Helenius | 989fc0a9f0 | 6 years ago |
Toni Helenius | ca81975988 | 6 years ago |
Toni Helenius | 87023eb3f7 | 6 years ago |
Nebloksiam | e8cb3124f2 | 6 years ago |
Stephen Gold | 02ec12e454 | 6 years ago |
travis-ci | c1c08f3a74 | 6 years ago |
travis-ci | d096f1be20 | 6 years ago |
appveyor | a492b746c2 | 6 years ago |
Stephen Gold | 8f26a3cc3f | 6 years ago |
Stephen Gold | 697b4a675e | 6 years ago |
Stephen Gold | b2be9ce3f2 | 6 years ago |
Stephen Gold | ac088e4554 | 6 years ago |
travis-ci | a31ee80798 | 6 years ago |
appveyor | b764f1346c | 6 years ago |
Stephen Gold | 618337a81d | 6 years ago |
travis-ci | 499714507b | 6 years ago |
appveyor | 0fde1b86f8 | 6 years ago |
Stephen Gold | dc4189afc3 | 6 years ago |
Stephen Gold | ce871712a8 | 6 years ago |
Stephen Gold | f16974fcb6 | 6 years ago |
Stephen Gold | 84a103fbe4 | 6 years ago |
Stephen Gold | 1c5c5e85c0 | 6 years ago |
Stephen Gold | 6e96ee5bf2 | 6 years ago |
Stephen Gold | 025b27c96d | 6 years ago |
Stephen Gold | 4323c4101a | 6 years ago |
Stephen Gold | e80302728f | 6 years ago |
Stephen Gold | 0b782b9fb4 | 6 years ago |
Stephen Gold | 10eef65e73 | 6 years ago |
Stephen Gold | a4ca3cbd40 | 6 years ago |
Stephen Gold | a28d5ccd16 | 6 years ago |
Riccardo Balbo | f12d7e4e60 | 6 years ago |
travis-ci | 9c949891b0 | 6 years ago |
appveyor | 34126f25e4 | 6 years ago |
Stephen Gold | 570e4850ea | 6 years ago |
Stephen Gold | 694232e269 | 6 years ago |
Stephen Gold | 40f6a7cc7a | 6 years ago |
Stephen Gold | ac182e38d5 | 6 years ago |
Riccardo Balbo | 087197aff4 | 6 years ago |
Stephen Gold | 2d6aa15526 | 6 years ago |
Toni Helenius | bbcb8c5701 | 6 years ago |
@ -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" |
||||||
|
} |
@ -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" |
||||||
|
} |
@ -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 |
@ -1,86 +0,0 @@ |
|||||||
language: java |
|
||||||
sudo: false |
|
||||||
dist: precise |
|
||||||
|
|
||||||
branches: |
|
||||||
only: |
|
||||||
- master |
|
||||||
- v3.1 |
|
||||||
- /^v3.2.0-.*$/ |
|
||||||
|
|
||||||
matrix: |
|
||||||
include: |
|
||||||
- os: linux |
|
||||||
jdk: oraclejdk8 |
|
||||||
env: UPLOAD=true UPLOAD_NATIVE=true |
|
||||||
- os: linux |
|
||||||
jdk: openjdk7 |
|
||||||
- os: osx |
|
||||||
env: UPLOAD_NATIVE=true |
|
||||||
|
|
||||||
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 || ./gradlew 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 |
|
||||||
|
|
||||||
|
|
@ -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. |
@ -1,58 +0,0 @@ |
|||||||
version: 1.0.{build}.{branch} |
|
||||||
|
|
||||||
branches: |
|
||||||
only: |
|
||||||
- master |
|
||||||
|
|
||||||
only_commits: |
|
||||||
files: |
|
||||||
- jme3-bullet-native/ |
|
||||||
|
|
||||||
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 |
|
||||||
|
|
||||||
test: off |
|
||||||
deploy: off |
|
||||||
|
|
||||||
on_success: |
|
||||||
- cmd: >- |
|
||||||
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 |
|
Binary file not shown.
@ -0,0 +1,2 @@ |
|||||||
|
# The headers are autogenerated and nobody should try to commit them... |
||||||
|
src/native/headers |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,173 +0,0 @@ |
|||||||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
|
||||||
#include <jni.h> |
|
||||||
/* Header for class com_jme3_audio_android_AndroidAL */ |
|
||||||
|
|
||||||
#ifndef _Included_com_jme3_audio_android_AndroidAL |
|
||||||
#define _Included_com_jme3_audio_android_AndroidAL |
|
||||||
#ifdef __cplusplus |
|
||||||
extern "C" { |
|
||||||
#endif |
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alGetString |
|
||||||
* Signature: (I)Ljava/lang/String; |
|
||||||
*/ |
|
||||||
JNIEXPORT jstring JNICALL Java_com_jme3_audio_android_AndroidAL_alGetString |
|
||||||
(JNIEnv *, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alGenSources |
|
||||||
* Signature: ()I |
|
||||||
*/ |
|
||||||
JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGenSources |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alGetError |
|
||||||
* Signature: ()I |
|
||||||
*/ |
|
||||||
JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGetError |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alDeleteSources |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alDeleteSources |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alGenBuffers |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alGenBuffers |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alDeleteBuffers |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alDeleteBuffers |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourceStop |
|
||||||
* Signature: (I)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceStop |
|
||||||
(JNIEnv *, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourcei |
|
||||||
* Signature: (III)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcei |
|
||||||
(JNIEnv *, jobject, jint, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alBufferData |
|
||||||
* Signature: (IILjava/nio/ByteBuffer;II)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alBufferData |
|
||||||
(JNIEnv *, jobject, jint, jint, jobject, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourcePlay |
|
||||||
* Signature: (I)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcePlay |
|
||||||
(JNIEnv *, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourcePause |
|
||||||
* Signature: (I)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcePause |
|
||||||
(JNIEnv *, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourcef |
|
||||||
* Signature: (IIF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcef |
|
||||||
(JNIEnv *, jobject, jint, jint, jfloat); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSource3f |
|
||||||
* Signature: (IIFFF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSource3f |
|
||||||
(JNIEnv *, jobject, jint, jint, jfloat, jfloat, jfloat); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alGetSourcei |
|
||||||
* Signature: (II)I |
|
||||||
*/ |
|
||||||
JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGetSourcei |
|
||||||
(JNIEnv *, jobject, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourceUnqueueBuffers |
|
||||||
* Signature: (IILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceUnqueueBuffers |
|
||||||
(JNIEnv *, jobject, jint, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSourceQueueBuffers |
|
||||||
* Signature: (IILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceQueueBuffers |
|
||||||
(JNIEnv *, jobject, jint, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alListener |
|
||||||
* Signature: (ILjava/nio/FloatBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListener |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alListenerf |
|
||||||
* Signature: (IF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListenerf |
|
||||||
(JNIEnv *, jobject, jint, jfloat); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alListener3f |
|
||||||
* Signature: (IFFF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListener3f |
|
||||||
(JNIEnv *, jobject, jint, jfloat, jfloat, jfloat); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidAL |
|
||||||
* Method: alSource3i |
|
||||||
* Signature: (IIIII)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSource3i |
|
||||||
(JNIEnv *, jobject, jint, jint, jint, jint, jint); |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
} |
|
||||||
#endif |
|
||||||
#endif |
|
@ -1,77 +0,0 @@ |
|||||||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
|
||||||
#include <jni.h> |
|
||||||
/* Header for class com_jme3_audio_android_AndroidALC */ |
|
||||||
|
|
||||||
#ifndef _Included_com_jme3_audio_android_AndroidALC |
|
||||||
#define _Included_com_jme3_audio_android_AndroidALC |
|
||||||
#ifdef __cplusplus |
|
||||||
extern "C" { |
|
||||||
#endif |
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: createALC |
|
||||||
* Signature: ()V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_createALC |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: destroyALC |
|
||||||
* Signature: ()V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_destroyALC |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: isCreated |
|
||||||
* Signature: ()Z |
|
||||||
*/ |
|
||||||
JNIEXPORT jboolean JNICALL Java_com_jme3_audio_android_AndroidALC_isCreated |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: alcGetString |
|
||||||
* Signature: (I)Ljava/lang/String; |
|
||||||
*/ |
|
||||||
JNIEXPORT jstring JNICALL Java_com_jme3_audio_android_AndroidALC_alcGetString |
|
||||||
(JNIEnv *, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: alcIsExtensionPresent |
|
||||||
* Signature: (Ljava/lang/String;)Z |
|
||||||
*/ |
|
||||||
JNIEXPORT jboolean JNICALL Java_com_jme3_audio_android_AndroidALC_alcIsExtensionPresent |
|
||||||
(JNIEnv *, jobject, jstring); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: alcGetInteger |
|
||||||
* Signature: (ILjava/nio/IntBuffer;I)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcGetInteger |
|
||||||
(JNIEnv *, jobject, jint, jobject, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: alcDevicePauseSOFT |
|
||||||
* Signature: ()V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcDevicePauseSOFT |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidALC |
|
||||||
* Method: alcDeviceResumeSOFT |
|
||||||
* Signature: ()V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcDeviceResumeSOFT |
|
||||||
(JNIEnv *, jobject); |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
} |
|
||||||
#endif |
|
||||||
#endif |
|
@ -1,101 +0,0 @@ |
|||||||
/* DO NOT EDIT THIS FILE - it is machine generated */ |
|
||||||
#include <jni.h> |
|
||||||
/* Header for class com_jme3_audio_android_AndroidEFX */ |
|
||||||
|
|
||||||
#ifndef _Included_com_jme3_audio_android_AndroidEFX |
|
||||||
#define _Included_com_jme3_audio_android_AndroidEFX |
|
||||||
#ifdef __cplusplus |
|
||||||
extern "C" { |
|
||||||
#endif |
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alGenAuxiliaryEffectSlots |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenAuxiliaryEffectSlots |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alGenEffects |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenEffects |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alEffecti |
|
||||||
* Signature: (III)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alEffecti |
|
||||||
(JNIEnv *, jobject, jint, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alAuxiliaryEffectSloti |
|
||||||
* Signature: (III)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alAuxiliaryEffectSloti |
|
||||||
(JNIEnv *, jobject, jint, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alDeleteEffects |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteEffects |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alDeleteAuxiliaryEffectSlots |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteAuxiliaryEffectSlots |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alGenFilters |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenFilters |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alFilteri |
|
||||||
* Signature: (III)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alFilteri |
|
||||||
(JNIEnv *, jobject, jint, jint, jint); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alFilterf |
|
||||||
* Signature: (IIF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alFilterf |
|
||||||
(JNIEnv *, jobject, jint, jint, jfloat); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alDeleteFilters |
|
||||||
* Signature: (ILjava/nio/IntBuffer;)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteFilters |
|
||||||
(JNIEnv *, jobject, jint, jobject); |
|
||||||
|
|
||||||
/*
|
|
||||||
* Class: com_jme3_audio_android_AndroidEFX |
|
||||||
* Method: alEffectf |
|
||||||
* Signature: (IIF)V |
|
||||||
*/ |
|
||||||
JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alEffectf |
|
||||||
(JNIEnv *, jobject, jint, jint, jfloat); |
|
||||||
|
|
||||||
#ifdef __cplusplus |
|
||||||
} |
|
||||||
#endif |
|
||||||
#endif |
|
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -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') |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -1,770 +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.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 featureName |
|
||||||
* the name of the feature |
|
||||||
* @param structure |
|
||||||
* the filled structure of the feature |
|
||||||
* @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 retreive 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 retreives 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 retreive. 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() + " ]"; |
|
||||||
} |
|
||||||
} |
|
@ -1,417 +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.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 occured: " + 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 LightNode) { |
|
||||||
result.addLight(((LightNode) object).getLight());// FIXME: check if this is needed !!!
|
|
||||||
result.attachChild((LightNode) object); |
|
||||||
} else 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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
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; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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() + "]]"; |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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 + ")]"; |
|
||||||
} |
|
||||||
} |
|
@ -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!"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -1,188 +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 ownerType |
|
||||||
* the type 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 + ")"; |
|
||||||
} |
|
||||||
} |
|
@ -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 occured |
|
||||||
*/ |
|
||||||
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 retreives 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 thensform 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!"); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
@ -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(); |
|
||||||
} |
|
||||||
} |
|
@ -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(); |
|
||||||
} |
|
||||||
} |
|
@ -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"; |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
||||||
} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue