diff --git a/.bazelrc b/.bazelrc index e14400fe..9b9e7584 100644 --- a/.bazelrc +++ b/.bazelrc @@ -2,3 +2,23 @@ common --java_runtime_version=remotejdk_21 common --tool_java_runtime_version=remotejdk_21 common --tool_java_language_version=21 common --java_language_version=21 + +# For minimum binary size +build --copt -flto --copt -Os +build --linkopt -flto + +# For Zig CC +build:linux --sandbox_add_mount_pair=/tmp +build:macos --sandbox_add_mount_pair=/var/tmp + +# For Android Rules +common --android_platforms=@//:android_arm32,@//:android_arm64,@//:android_x86,@//:android_x86_64 +common:linux --cxxopt=-std=c++17 +common:linux --host_cxxopt=-std=c++17 +common:macos --cxxopt=-std=c++17 +common:macos --host_cxxopt=-std=c++17 +common:windows --cxxopt=/std:c++17 +common:windows --host_cxxopt=/std:c++17 +build:windows --define=protobuf_allow_msvc=true + +common --enable_platform_specific_config diff --git a/BUILD.bazel b/BUILD.bazel index 007df4b4..5e3d818e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -42,6 +42,108 @@ extract_update_log( ], ) +platform( + name = "android_arm32", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:android", + "@platforms//cpu:armv7", + ], +) + +platform( + name = "android_arm64", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:android", + "@platforms//cpu:aarch64", + ], +) + +platform( + name = "android_x86", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:android", + "@platforms//cpu:x86_32", + ], +) + +platform( + name = "android_x86_64", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:android", + "@platforms//cpu:x86_64", + ], +) + +platform( + name = "linux_x86", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_32", + ] +) + +platform( + name = "linux_x86_64_glibc", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + "@zig_sdk//libc:gnu.2.24", + ], +) + +platform( + name = "linux_arm32_glibc", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:armv7", + "@zig_sdk//libc:gnu.2.24", + ], +) + +platform( + name = "linux_arm64_glibc", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:aarch64", + "@zig_sdk//libc:gnu.2.24", + ], +) + +platform( + name = "windows_x86", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_32", + ] +) + +platform( + name = "windows_x86_64", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +) + +platform( + name = "windows_arm64", + visibility = ["//visibility:public"], + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:aarch64", + ] +) + alias( name = "license", visibility = ["//visibility:public"], diff --git a/MODULE.bazel b/MODULE.bazel index 08f41db1..42bc759c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,6 +14,32 @@ bazel_dep(name = "rules_java", version = "8.12.0") bazel_dep(name = "rules_kotlin", version = "2.1.4") bazel_dep(name = "rules_jvm_external", version = "6.7") bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "platforms", version = "1.0.0") +bazel_dep(name = "rules_android_ndk", version = "0.1.3") +bazel_dep(name = "rules_jni", version = "0.11.0") +bazel_dep(name = "hermetic_cc_toolchain", version = "4.0.1") + +git_override( + module_name = "rules_jni", + remote = "https://github.com/fmeum/rules_jni.git", + commit = "22b643d23414379b760fced29f36e766dde5fd2a", + patch_strip = 1, + patches = [ + "@//third_party/rules_jni/patches:armv7-cpu.patch", + ], +) + +androidndk = use_extension("@rules_android_ndk//:extension.bzl", "android_ndk_repository_extension") +use_repo(androidndk, "androidndk") +register_toolchains("@androidndk//:all") + +zig_cc = use_extension("@hermetic_cc_toolchain//toolchain:ext.bzl", "toolchains") +use_repo(zig_cc, "zig_sdk") + +register_toolchains( + "@zig_sdk//toolchain/...", + "@zig_sdk//libc_aware/toolchain/...", +) maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( @@ -88,6 +114,21 @@ maven.install( ) use_repo(maven, "maven") +git_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +git_repository( + name = "libbulletjme", + commit = "ea117c17501001bd96db05e0fc479420efb3d79e", + build_file = "@//third_party/libbulletjme:BUILD.libbulletjme.bazel", + remote = "https://github.com/stephengold/Libbulletjme.git", + patch_strip = 1, + patches = [ + "@//third_party/libbulletjme/patches:vhacd-mutex.patch", + "@//third_party/libbulletjme/patches:winsock2-case.patch", + "@//third_party/libbulletjme/patches:remove-collision-group-one-bit-assert.patch", + "@//third_party/libbulletjme/patches:add-ground-object.patch", + ], +) + http_jar = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_jar") http_jar( name = "sodium", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 8d7d6afd..15c833d2 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -20,6 +20,7 @@ "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", "https://bcr.bazel.build/modules/bazel_features/1.25.0/MODULE.bazel": "e2e60a10a6da64bbf533f15ca652bf61a033e41c2ed734d79a9a08ba87f68c1a", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", @@ -55,17 +56,20 @@ "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.0.1/MODULE.bazel": "0809d28e562d804e478c683b06a9f3adeedccfdb42a426c2cc69e39cbc7e3bf3", + "https://bcr.bazel.build/modules/hermetic_cc_toolchain/4.0.1/source.json": "527d73a9964cd34ceeb73a1d5e5d04d9e6238401363c783c1f3021d5b25b8a63", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", @@ -85,12 +89,15 @@ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.6.1/MODULE.bazel": "e6e60f22b5fe385352718325c5f3356bec9478087ec649777e3cfe25a4ac412e", "https://bcr.bazel.build/modules/rules_android/0.6.1/source.json": "04bbb5a232621c797f8a545cb24b96a1c797c16a4246b296388f8aba3e30a786", + "https://bcr.bazel.build/modules/rules_android_ndk/0.1.3/MODULE.bazel": "3ca2a6bf93e33c696e0393ef86698d155f1b8c6f72e7418b782690a75bc1ab99", + "https://bcr.bazel.build/modules/rules_android_ndk/0.1.3/source.json": "2cc34cc412da45b81c82168252b8d347e108a62607b98a80afdf5f3787bc7cc0", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", @@ -5482,6 +5489,22 @@ "recordedRepoMappingEntries": [] } }, + "@@rules_android_ndk+//:extension.bzl%android_ndk_repository_extension": { + "general": { + "bzlTransitiveDigest": "oV/TPZWCXAAcpJG/DBV/lNiWCISUTk5ba0KRjUb7adg=", + "usagesDigest": "x+bWwMukfyRIAQNowCpHSkjXxbp4uhFxarfMWJB4qEI=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "androidndk": { + "repoRuleId": "@@rules_android_ndk+//:rules.bzl%android_ndk_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + }, "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { "general": { "bzlTransitiveDigest": "lxvzPQyluk241QRYY81nZHOcv5Id/5U2y6dp42qibis=", diff --git a/README.md b/README.md index c2cbc487..b210ff9e 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,15 @@ mod 采取同步模型哈希值的方式,因此你可以重命名模型,甚 - 游戏内模型渲染 - glTF 和 VMD 动画支持 -烈焰棒由 Kotlin 和 Java 编写,不使用原生库,不依赖特定平台,因此可以运行在各种环境上。 +烈焰棒核心部分由 Kotlin 和 Java 编写,不使用原生库,不依赖特定平台,因此可以运行在各种环境上。 + +物理引擎由 [libbulletjme](https://github.com/stephengold/Libbulletjme) 提供支持,其调用了 C++ 的 [bullet](https://pybullet.org/) 物理引擎,目前捆绑了以下平台的原生库: + +- Android (x86、x86_64、armv7、armv8) +- Linux (x86_64、armv8, 仅 glibc) +- Windows (x86_64、armv8) + +没有原生库的平台会自动禁用物理。 烈焰棒目前作为一个 Jar-in-Jar mod 打包在盔甲架中,但可以作为一个单独的库来使用。由于目前没有详细的文档,并且 API 随时可能发生变化,目前不建议在其他项目使用烈焰棒。 @@ -59,6 +67,8 @@ Apache 2.0 许可证授权。 使用了来自 [Cherished Worlds](https://github.com/illusivesoulworks/cherishedworlds)(LGPL 3.0 及以上版本) 的贴图作为收藏图标,在此感谢。 +捆绑了 [bullet](https://pybullet.org/wordpress/)(zlib) 物理引擎及其 Java 绑定 [libbulletjme](https://github.com/stephengold/Libbulletjme)(BSD、zlib、MIT),在此感谢。 + ## 许可证 本 mod 以 LGPL 3.0 及以上版本授权,在发布和修改时请遵守许可证要求。 @@ -80,23 +90,31 @@ Currently, the mod is in early development, and supports the following features: - Support multiplayer (can synchronize model display, but won't support synchronizing model files) - Shader support (Change renderer in mod config to use shader) -If you encounter any problems or have any ideas, please feel free to open an issue or discussion in the [issue area](https://github.com/fifth-light/ArmorStand/issues) and [discussion area](https://github.com/fifth-light/ArmorStand/discussions). +If you encounter any problems or have any ideas, please feel free to open an issue or discussion in the +[issue area](https://github.com/fifth-light/ArmorStand/issues) and [discussion area](https://github.com/fifth-light/ArmorStand/discussions). ## Usage -First, you need to prepare a model. Before using any model, please check the copyright of the model to avoid copyright issues. Once you have prepared the model file, place it in the `models` directory to load it. +First, you need to prepare a model. Before using any model, please check the copyright of the model to avoid copyright +issues. Once you have prepared the model file, place it in the `models` directory to load it. In game, press `I` to open the mod settings, and choose the model you prepared. -In game, press `K` to open the animation control interface, where you can load animation files (you also need to place them in the `models` directory), and control the animation speed and playback progress. +In game, press `K` to open the animation control interface, where you can load animation files (you also need to place +them in the `models` directory), and control the animation speed and playback progress. ## Multiplayer -The mod supports multiplayer model synchronization. Before using it, please ensure that your server also has the mod installed (if the server does not have the mod, other players will not be able to see your model, but your own model can still be displayed). +The mod supports multiplayer model synchronization. Before using it, please ensure that your server also has the mod +installed (if the server does not have the mod, other players will not be able to see your model, but your own model can +still be displayed). -Considering distributing models causes copyright issues, the mod can not and will not support synchronizing model files. You need to ensure that other players will also place your model in the `models` directory, so that they can see your model. +Considering distributing models causes copyright issues, the mod can not and will not support synchronizing model files. +You need to ensure that other players will also place your model in the `models` directory, so that they can see your +model. -The mod uses the model's hash value to synchronize models, so you can rename the model, even put it in a subdirectory. As long as the model content does not change, you can see other players' models. +The mod uses the model's hash value to synchronize models, so you can rename the model, even put it in a subdirectory. +As long as the model content does not change, you can see other players' models. ## BlazeRod @@ -106,8 +124,17 @@ BlazeRod is the library providing underlying render abilities for ArmorStand, an - In-game model rendering - glTF and VMD animation support -BlazeRod is written in Kotlin and Java. It doesn't use native library, and don't depend on particular platform, so -BlazeRod can run in many environment. +Core part of BlazeRod is written in Kotlin and Java. It doesn't use native library, and don't depend on particular +platform, so BlazeRod can run in many environment. + +Physics engine is powered by [libbulletjme](https://github.com/stephengold/Libbulletjme), it calls +[bullet](https://pybullet.org/) physic engine written in C++. Currently, native libraries of these platforms is bundled: + +- Android (x86, x86_64, armv7, armv8) +- Linux (x86_64, armv8, only glibc) +- Windows (x86_64, armv8) + +On platforms without native library, physics will be disabled. BlazeRod is currently packed as a Jar-in-Jar mod in ArmorStand, but it can also be used as a standalone library. Due to lack of documentation, and it's API can be changed in any time, it is not encouraged to use BlazeRod in other project @@ -128,6 +155,9 @@ to redistribute) as default animation, thanks to them. The favorite icon is from [Cherished Worlds](https://github.com/illusivesoulworks/cherishedworlds)(LGPL 3.0 or later), thanks to them. +Bundled [bullet](https://pybullet.org/wordpress/) (zlib) physics engine and its Java binding +[libbulletjme](https://github.com/stephengold/Libbulletjme)(BSD, zlib, MIT), thanks to them. + ## License The mod is licensed under the LGPL 3.0 or later. Please comply with the license requirements when distributing and modifying. diff --git a/blazerod/BUILD.bazel b/blazerod/BUILD.bazel index 05e7329f..b6750e93 100644 --- a/blazerod/BUILD.bazel +++ b/blazerod/BUILD.bazel @@ -2,7 +2,6 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") load("@rules_java//java:defs.bzl", "java_library") load("//:properties.bzl", "blazerod_version") -load("//rule:merge_jar.bzl", "merge_jar") load("//rule:merge_jij.bzl", "merge_jij") java_library( @@ -53,5 +52,7 @@ merge_jij( "//blazerod/model/model-vmd": "blazerod-model-formats-vmd:=", "//blazerod/model/model-bedrock": "blazerod-model-formats-bedrock:=", "//blazerod/render": "blazerod-render:=", + "@libbulletjme//:libbulletjme": "libbulletjme_blazerod:22.0.1+patch1", + "@libbulletjme//:libbulletjme_natives": "libbulletjme_natives_blazerod:22.0.1+patch1", }, ) diff --git a/blazerod/example/ball_block/src/main/java/top/fifthlight/blazerod/example/ballblock/BallBlockMod.java b/blazerod/example/ball_block/src/main/java/top/fifthlight/blazerod/example/ballblock/BallBlockMod.java index ef66720a..fd8326ba 100644 --- a/blazerod/example/ball_block/src/main/java/top/fifthlight/blazerod/example/ballblock/BallBlockMod.java +++ b/blazerod/example/ball_block/src/main/java/top/fifthlight/blazerod/example/ballblock/BallBlockMod.java @@ -70,7 +70,8 @@ private void loadModel() { matrix.getScale().mul(0.5f); matrix.getTranslation().add(0.5f, 0.5f, 0.5f); }); - BALL_INSTANCE.updateRenderData(); + // No physics applied + BALL_INSTANCE.updateRenderData(0f); }); } diff --git a/blazerod/model/model-base/BUILD.bazel b/blazerod/model/model-base/BUILD.bazel index 8495ffcf..a065bb40 100644 --- a/blazerod/model/model-base/BUILD.bazel +++ b/blazerod/model/model-base/BUILD.bazel @@ -1,6 +1,5 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") load("//:properties.bzl", "blazerod_version") fabric_mod_json_jar( diff --git a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/Model.kt b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/Model.kt index 6550fb23..21293cd4 100644 --- a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/Model.kt +++ b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/Model.kt @@ -4,6 +4,7 @@ data class Model( val scenes: List, val defaultScene: Scene? = null, val skins: List, + val physicalJoints: List = listOf(), val expressions: List = listOf(), ) { init { diff --git a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/NodeComponent.kt b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/NodeComponent.kt index 22456532..69559082 100644 --- a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/NodeComponent.kt +++ b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/NodeComponent.kt @@ -24,6 +24,10 @@ sealed class NodeComponent { INFLUENCE_SOURCE( requireMesh = false, singleInstanceOnly = false, + ), + RIGID_BODY( + requireMesh = false, + singleInstanceOnly = false, ); companion object { @@ -67,4 +71,12 @@ sealed class NodeComponent { override val type: Type get() = Type.INFLUENCE_SOURCE } + + data class RigidBodyComponent( + val rigidBodyId: RigidBodyId, + val rigidBody: RigidBody, + ) : NodeComponent() { + override val type: Type + get() = Type.RIGID_BODY + } } \ No newline at end of file diff --git a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/PhysicalJoint.kt b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/PhysicalJoint.kt new file mode 100644 index 00000000..a7dd867e --- /dev/null +++ b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/PhysicalJoint.kt @@ -0,0 +1,22 @@ +package top.fifthlight.blazerod.model + +import org.joml.Vector3fc + +data class PhysicalJoint( + val name: String? = null, + val type: JointType, + val rigidBodyA: RigidBodyId, + val rigidBodyB: RigidBodyId, + val position: Vector3fc, + val rotation: Vector3fc, + val positionMin: Vector3fc, + val positionMax: Vector3fc, + val rotationMin: Vector3fc, + val rotationMax: Vector3fc, + val positionSpring: Vector3fc, + val rotationSpring: Vector3fc, +) { + enum class JointType { + SPRING_6DOF, + } +} diff --git a/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/RigidBody.kt b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/RigidBody.kt new file mode 100644 index 00000000..80861fe9 --- /dev/null +++ b/blazerod/model/model-base/src/main/kotlin/top/fifthlight/blazerod/model/RigidBody.kt @@ -0,0 +1,37 @@ +package top.fifthlight.blazerod.model + +import org.joml.Vector3fc +import java.util.* + +data class RigidBody( + val name: String? = null, + val collisionGroup: Int, + val collisionMask: Int, + val shape: ShapeType, + val shapeSize: Vector3fc, + val shapePosition: Vector3fc, + val shapeRotation: Vector3fc, + val mass: Float, + val moveAttenuation: Float, + val rotationDamping: Float, + val repulsion: Float, + val frictionForce: Float, + val physicsMode: PhysicsMode, +) { + enum class ShapeType { + SPHERE, + BOX, + CAPSULE, + } + + enum class PhysicsMode { + FOLLOW_BONE, + PHYSICS, + PHYSICS_PLUS_BONE, + } +} + +data class RigidBodyId( + val modelId: UUID, + val index: Int, +) \ No newline at end of file diff --git a/blazerod/model/model-formats/BUILD.bazel b/blazerod/model/model-formats/BUILD.bazel index 26475881..c260f1c7 100644 --- a/blazerod/model/model-formats/BUILD.bazel +++ b/blazerod/model/model-formats/BUILD.bazel @@ -1,7 +1,6 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library") load("//:properties.bzl", "blazerod_version") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") formats = [ "gltf", diff --git a/blazerod/model/model-gltf/BUILD.bazel b/blazerod/model/model-gltf/BUILD.bazel index f0460207..19b04819 100644 --- a/blazerod/model/model-gltf/BUILD.bazel +++ b/blazerod/model/model-gltf/BUILD.bazel @@ -2,7 +2,6 @@ load("@rules_java//java:defs.bzl", "java_library") load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") load("//:properties.bzl", "blazerod_version") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") fabric_mod_json_jar( name = "fabric_mod_json", diff --git a/blazerod/model/model-pmd/BUILD.bazel b/blazerod/model/model-pmd/BUILD.bazel index 881d5457..089a5c70 100644 --- a/blazerod/model/model-pmd/BUILD.bazel +++ b/blazerod/model/model-pmd/BUILD.bazel @@ -1,6 +1,5 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") load("//:properties.bzl", "blazerod_version") fabric_mod_json_jar( diff --git a/blazerod/model/model-pmx/BUILD.bazel b/blazerod/model/model-pmx/BUILD.bazel index 479490b9..7b899663 100644 --- a/blazerod/model/model-pmx/BUILD.bazel +++ b/blazerod/model/model-pmx/BUILD.bazel @@ -1,6 +1,5 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") load("//:properties.bzl", "blazerod_version") fabric_mod_json_jar( diff --git a/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/PmxLoader.kt b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/PmxLoader.kt index 226439c8..c8af5e0b 100644 --- a/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/PmxLoader.kt +++ b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/PmxLoader.kt @@ -60,6 +60,7 @@ class PmxLoader : ModelFileLoader { private class Context( private val basePath: Path, ) { + private var version: Float = 0f private lateinit var globals: PmxGlobals private val decoder by lazy { globals.textEncoding.charset.newDecoder() @@ -84,6 +85,9 @@ class PmxLoader : ModelFileLoader { private lateinit var morphTargetGroups: List private val childBoneMap = mutableMapOf>() private val rootBones = mutableListOf() + private lateinit var rigidBodies: List + private var boneToRigidBodyMap = mutableMapOf>() + private lateinit var joints: List private fun loadRgbColor(buffer: ByteBuffer): RgbColor { if (buffer.remaining() < 3 * 4) { @@ -178,21 +182,28 @@ class PmxLoader : ModelFileLoader { 1 -> buffer.get().toInt() 2 -> buffer.getShort().toInt() 4 -> buffer.getInt() - else -> throw PmxLoadException("Bad texture index size: ${globals.boneIndexSize}") + else -> throw PmxLoadException("Bad texture index size: ${globals.textureIndexSize}") } private fun loadMorphIndex(buffer: ByteBuffer) = when (globals.morphIndexSize) { 1 -> buffer.get().toUByte().toInt() 2 -> buffer.getShort().toUShort().toInt() 4 -> buffer.getInt() - else -> throw PmxLoadException("Bad morph index size: ${globals.boneIndexSize}") + else -> throw PmxLoadException("Bad morph index size: ${globals.morphIndexSize}") } private fun loadVertexIndex(buffer: ByteBuffer): Int = when (globals.vertexIndexSize) { 1 -> buffer.get().toUByte().toInt() 2 -> buffer.getShort().toUShort().toInt() 4 -> buffer.getInt() - else -> throw PmxLoadException("Bad vertex index size: ${globals.boneIndexSize}") + else -> throw PmxLoadException("Bad vertex index size: ${globals.vertexIndexSize}") + } + + private fun loadRigidBodyIndex(buffer: ByteBuffer): Int = when (globals.rigidBodyIndexSize) { + 1 -> buffer.get().toUByte().toInt() + 2 -> buffer.getShort().toUShort().toInt() + 4 -> buffer.getInt() + else -> throw PmxLoadException("Bad rigid body index size: ${globals.rigidBodyIndexSize}") } private fun loadString(buffer: ByteBuffer): String { @@ -221,6 +232,7 @@ class PmxLoader : ModelFileLoader { if (version < 2.0f) { throw PmxLoadException("Bad PMX version: at least 2.0, but get $version") } + this.version = version val globalsCount = buffer.get().toUByte().toInt() if (globalsCount < 8) { throw PmxLoadException("Bad global count: $globalsCount, at least 8") @@ -941,6 +953,141 @@ class PmxLoader : ModelFileLoader { morphTargetGroups = morphGroups } + private fun loadDisplayFrames(buffer: ByteBuffer) { + val displayFrameCount = buffer.getInt() + if (displayFrameCount < 0) { + throw PmxLoadException("Bad PMX model: display frames count less than zero") + } + val displayFrames = mutableListOf() + repeat(displayFrameCount) { + val nameLocal = loadString(buffer) + val nameUniversal = loadString(buffer) + val isSpecial = buffer.get() != 0.toByte() + val frameCount = buffer.getInt() + val frames = (0 until frameCount).map { + when (val type = buffer.get()) { + 0.toByte() -> PmxDisplayFrame.FrameData.Bone( + boneIndex = loadBoneIndex(buffer), + ) + + 1.toByte() -> PmxDisplayFrame.FrameData.Morph( + morphIndex = loadMorphIndex(buffer), + ) + + else -> throw PmxLoadException("Unknown frame type: $type") + } + } + displayFrames.add( + PmxDisplayFrame( + nameLocal = nameLocal, + nameUniversal = nameUniversal, + isSpecial = isSpecial, + frames = frames, + ) + ) + } + } + + private fun loadRigidBodies(buffer: ByteBuffer) { + val rigidBodyCount = buffer.getInt() + if (rigidBodyCount < 0) { + throw PmxLoadException("Bad PMX model: rigid bodies count less than zero") + } + + fun loadShapeType(byte: Byte): PmxRigidBody.ShapeType = when (byte.toInt()) { + 0 -> PmxRigidBody.ShapeType.SPHERE + 1 -> PmxRigidBody.ShapeType.BOX + 2 -> PmxRigidBody.ShapeType.CAPSULE + else -> throw PmxLoadException("Unsupported rigid body shape type: $byte") + } + + fun loadPhysicsMode(byte: Byte): PmxRigidBody.PhysicsMode = when (byte.toInt()) { + 0 -> PmxRigidBody.PhysicsMode.FOLLOW_BONE + 1 -> PmxRigidBody.PhysicsMode.PHYSICS + 2 -> PmxRigidBody.PhysicsMode.PHYSICS_PLUS_BONE + else -> throw PmxLoadException("Unsupported rigid body physics mode: $byte") + } + + rigidBodies = (0 until rigidBodyCount).map { index -> + PmxRigidBody( + nameLocal = loadString(buffer), + nameUniversal = loadString(buffer), + relatedBoneIndex = loadBoneIndex(buffer), + groupId = buffer.get().toUByte().toInt(), + nonCollisionGroup = buffer.getShort().toUShort().toInt(), + shape = loadShapeType(buffer.get()), + shapeSize = loadVector3f(buffer).mul(MMD_SCALE), + shapePosition = loadVector3f(buffer).transformPosition(), + shapeRotation = loadVector3f(buffer).also { it.x *= -1 }, + mass = buffer.getFloat(), + moveAttenuation = buffer.getFloat(), + rotationDamping = buffer.getFloat(), + repulsion = buffer.getFloat(), + frictionForce = buffer.getFloat(), + physicsMode = loadPhysicsMode(buffer.get()) + ).also { + if (it.relatedBoneIndex in bones.indices) { + boneToRigidBodyMap.getOrPut(it.relatedBoneIndex, ::mutableListOf).add(index) + } else if (bones.isNotEmpty()) { + // Allocate to first bone + // https://github.com/benikabocha/saba/blob/29b8efa8b31c8e746f9a88020fb0ad9dcdcf3332/src/Saba/Model/MMD/MMDPhysics.cpp#L434 + boneToRigidBodyMap.getOrPut(0, ::mutableListOf).add(index) + } else { + // No bone? Ignore + } + } + } + } + + private fun loadJoints(buffer: ByteBuffer) { + val jointCount = buffer.getInt() + if (jointCount < 0) { + throw PmxLoadException("Bad PMX model: joints count less than zero") + } + + fun loadJointType(byte: Byte): PmxJoint.JointType = PmxJoint.JointType.entries.firstOrNull { + byte.toInt() == it.value + } ?: throw PmxLoadException("Unsupported joint type: $byte") + + joints = (0 until jointCount).map { + val nameLocal = loadString(buffer) + val nameUniversal = loadString(buffer) + val type = loadJointType(buffer.get()) + val rigidBodyIndexA = loadRigidBodyIndex(buffer) + val rigidBodyIndexB = loadRigidBodyIndex(buffer) + val position = loadVector3f(buffer).transformPosition() + val rotation = loadVector3f(buffer).also { it.x *= -1; it.y *= -1 } + + val positionMinimumOrig = loadVector3f(buffer).transformPosition() + val positionMaximumOrig = loadVector3f(buffer).transformPosition() + val positionMinimum = Vector3f(positionMaximumOrig.x, positionMinimumOrig.y, positionMinimumOrig.z) + val positionMaximum = Vector3f(positionMinimumOrig.x, positionMaximumOrig.y, positionMaximumOrig.z) + + val rotationMinimumOrig = loadVector3f(buffer) + val rotationMaximumOrig = loadVector3f(buffer) + val rotationMinimum = Vector3f(-rotationMaximumOrig.x, rotationMinimumOrig.y, rotationMinimumOrig.z) + val rotationMaximum = Vector3f(-rotationMinimumOrig.x, rotationMaximumOrig.y, rotationMaximumOrig.z) + val positionSpring = loadVector3f(buffer).transformPosition() + val rotationSpring = loadVector3f(buffer).also { it.x *= -1 } + + PmxJoint( + nameLocal = nameLocal, + nameUniversal = nameUniversal, + type = type, + rigidBodyIndexA = rigidBodyIndexA, + rigidBodyIndexB = rigidBodyIndexB, + position = position, + rotation = rotation, + positionMinimum = positionMinimum, + positionMaximum = positionMaximum, + rotationMinimum = rotationMinimum, + rotationMaximum = rotationMaximum, + positionSpring = positionSpring, + rotationSpring = rotationSpring, + ) + } + } + private data class MaterialMorphData( val materialIndex: Int, val morphIndex: Int, @@ -954,6 +1101,9 @@ class PmxLoader : ModelFileLoader { loadMaterials(buffer) loadBones(buffer) loadMorphTargets(buffer) + loadDisplayFrames(buffer) + loadRigidBodies(buffer) + loadJoints(buffer) val modelId = UUID.randomUUID() val rootNodes = mutableListOf() @@ -1012,6 +1162,38 @@ class PmxLoader : ModelFileLoader { ) ) } + boneToRigidBodyMap[index]?.forEach { index -> + add( + NodeComponent.RigidBodyComponent( + rigidBodyId = RigidBodyId(modelId, index), + rigidBody = rigidBodies[index].let { rigidBody -> + RigidBody( + name = rigidBody.nameLocal.takeIf(String::isNotBlank), + collisionGroup = rigidBody.groupId, + collisionMask = rigidBody.nonCollisionGroup, + shape = when (rigidBody.shape) { + PmxRigidBody.ShapeType.SPHERE -> RigidBody.ShapeType.SPHERE + PmxRigidBody.ShapeType.BOX -> RigidBody.ShapeType.BOX + PmxRigidBody.ShapeType.CAPSULE -> RigidBody.ShapeType.CAPSULE + }, + shapeSize = rigidBody.shapeSize, + shapePosition = rigidBody.shapePosition, + shapeRotation = rigidBody.shapeRotation, + mass = rigidBody.mass, + moveAttenuation = rigidBody.moveAttenuation, + rotationDamping = rigidBody.rotationDamping, + repulsion = rigidBody.repulsion, + frictionForce = rigidBody.frictionForce, + physicsMode = when (rigidBody.physicsMode) { + PmxRigidBody.PhysicsMode.FOLLOW_BONE -> RigidBody.PhysicsMode.FOLLOW_BONE + PmxRigidBody.PhysicsMode.PHYSICS -> RigidBody.PhysicsMode.PHYSICS + PmxRigidBody.PhysicsMode.PHYSICS_PLUS_BONE -> RigidBody.PhysicsMode.PHYSICS_PLUS_BONE + }, + ) + }, + ) + ) + } } return Node( @@ -1161,6 +1343,30 @@ class PmxLoader : ModelFileLoader { model = Model( scenes = listOf(scene), skins = listOf(skin), + physicalJoints = this.joints.mapNotNull { joint -> + if (joint.rigidBodyIndexA !in rigidBodies.indices) { + return@mapNotNull null + } + if (joint.rigidBodyIndexB !in rigidBodies.indices) { + return@mapNotNull null + } + PhysicalJoint( + name = joint.nameLocal.takeIf(String::isNotBlank), + type = when (joint.type) { + PmxJoint.JointType.SPRING_6DOF -> PhysicalJoint.JointType.SPRING_6DOF + }, + rigidBodyA = RigidBodyId(modelId, joint.rigidBodyIndexA), + rigidBodyB = RigidBodyId(modelId, joint.rigidBodyIndexB), + position = joint.position, + rotation = joint.rotation, + positionMin = joint.positionMinimum, + positionMax = joint.positionMaximum, + rotationMin = joint.rotationMinimum, + rotationMax = joint.rotationMaximum, + positionSpring = joint.positionSpring, + rotationSpring = joint.rotationSpring, + ) + }, expressions = buildList { for ((index, target) in morphTargets.withIndex()) { val expression = Expression.Target( diff --git a/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxDisplayFrame.kt b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxDisplayFrame.kt new file mode 100644 index 00000000..2c753ac1 --- /dev/null +++ b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxDisplayFrame.kt @@ -0,0 +1,14 @@ +package top.fifthlight.blazerod.model.pmx.format + +// Actually useless for us +data class PmxDisplayFrame( + val nameLocal: String, + val nameUniversal: String, + val isSpecial: Boolean, + val frames: List, +) { + sealed class FrameData { + data class Bone(val boneIndex: Int) : FrameData() + data class Morph(val morphIndex: Int) : FrameData() + } +} diff --git a/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxJoint.kt b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxJoint.kt new file mode 100644 index 00000000..97d78dfa --- /dev/null +++ b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxJoint.kt @@ -0,0 +1,23 @@ +package top.fifthlight.blazerod.model.pmx.format + +import org.joml.Vector3f + +data class PmxJoint( + val nameLocal: String, + val nameUniversal: String, + val type: JointType, + val rigidBodyIndexA: Int, + val rigidBodyIndexB: Int, + val position: Vector3f, + val rotation: Vector3f, + val positionMinimum: Vector3f, + val positionMaximum: Vector3f, + val rotationMinimum: Vector3f, + val rotationMaximum: Vector3f, + val positionSpring: Vector3f, + val rotationSpring: Vector3f, +) { + enum class JointType(val value: Int) { + SPRING_6DOF(0), + } +} \ No newline at end of file diff --git a/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxRigidBody.kt b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxRigidBody.kt new file mode 100644 index 00000000..1a73f571 --- /dev/null +++ b/blazerod/model/model-pmx/src/main/kotlin/top/fifthlight/blazerod/model/pmx/format/PmxRigidBody.kt @@ -0,0 +1,33 @@ +package top.fifthlight.blazerod.model.pmx.format + +import org.joml.Vector3f + +data class PmxRigidBody( + val nameLocal: String, + val nameUniversal: String, + val relatedBoneIndex: Int, + val groupId: Int, + val nonCollisionGroup: Int, + val shape: ShapeType, + val shapeSize: Vector3f, + val shapePosition: Vector3f, + val shapeRotation: Vector3f, + val mass: Float, + val moveAttenuation: Float, + val rotationDamping: Float, + val repulsion: Float, + val frictionForce: Float, + val physicsMode: PhysicsMode, +) { + enum class ShapeType(val value: Int) { + SPHERE(0), + BOX(1), + CAPSULE(2) + } + + enum class PhysicsMode(val value: Int) { + FOLLOW_BONE(0), + PHYSICS(1), + PHYSICS_PLUS_BONE(2) + } +} \ No newline at end of file diff --git a/blazerod/model/model-vmd/BUILD.bazel b/blazerod/model/model-vmd/BUILD.bazel index ac1829d5..f8ff8cb0 100644 --- a/blazerod/model/model-vmd/BUILD.bazel +++ b/blazerod/model/model-vmd/BUILD.bazel @@ -1,6 +1,5 @@ load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library", "kt_jvm_test") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") load("//:properties.bzl", "blazerod_version") fabric_mod_json_jar( diff --git a/blazerod/render/BUILD.bazel b/blazerod/render/BUILD.bazel index f1e5f67b..8ac50f8a 100644 --- a/blazerod/render/BUILD.bazel +++ b/blazerod/render/BUILD.bazel @@ -5,7 +5,6 @@ load("//rule:apply_access_widener.bzl", "apply_access_widener") load("//rule:decompile_jar.bzl", "decompile_jar") load("//rule:extract_access_widener.bzl", "extract_access_widener") load("//rule:fabric_mod_json_jar.bzl", "fabric_mod_json_jar") -load("//rule:merge_jar.bzl", "merge_jar") load("//rule:remap_jar.bzl", "remap_jar") extract_access_widener( @@ -86,6 +85,7 @@ kt_jvm_library( "@maven//:net_fabricmc_fabric_loader", "@maven//:net_fabricmc_sponge_mixin", "@minecraft//:%s_client_libraries" % game_version, + "@libbulletjme//:libbulletjme", ], ) diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/BlazeRod.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/BlazeRod.kt index 1008c66f..75a0a926 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/BlazeRod.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/BlazeRod.kt @@ -11,6 +11,7 @@ import top.fifthlight.blazerod.debug.* import top.fifthlight.blazerod.event.RenderEvents import top.fifthlight.blazerod.model.resource.RenderTexture import top.fifthlight.blazerod.model.uniform.UniformBuffer +import top.fifthlight.blazerod.physics.PhysicsLibrary import top.fifthlight.blazerod.util.ThreadExecutorDispatcher import top.fifthlight.blazerod.util.cleanupObjectPools import javax.swing.SwingUtilities @@ -48,6 +49,10 @@ object BlazeRod: ClientModInitializer { } } + if (System.getProperty("blazerod.no_physics") != "true") { + PhysicsLibrary.init() + } + RenderEvents.INITIALIZE_DEVICE.register { // Trigger its loading in render thread RenderTexture.WHITE_RGBA_TEXTURE diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/ModelInstance.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/ModelInstance.kt index c0a6bebd..27334150 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/ModelInstance.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/ModelInstance.kt @@ -1,9 +1,15 @@ package top.fifthlight.blazerod.model +import com.jme3.bullet.PhysicsSpace +import com.jme3.bullet.collision.shapes.PlaneCollisionShape +import com.jme3.bullet.joints.SixDofSpringJoint +import com.jme3.bullet.objects.PhysicsRigidBody +import com.jme3.math.Plane import net.minecraft.client.render.VertexConsumerProvider import net.minecraft.util.Identifier import org.joml.Matrix4f import org.joml.Matrix4fc +import org.joml.Quaternionf import top.fifthlight.blazerod.model.data.LocalMatricesBuffer import top.fifthlight.blazerod.model.data.MorphTargetBuffer import top.fifthlight.blazerod.model.data.RenderSkinBuffer @@ -12,24 +18,125 @@ import top.fifthlight.blazerod.model.node.TransformMap import top.fifthlight.blazerod.model.node.UpdatePhase import top.fifthlight.blazerod.model.node.markNodeTransformDirty import top.fifthlight.blazerod.model.resource.CameraTransform -import top.fifthlight.blazerod.util.AbstractRefCount -import top.fifthlight.blazerod.util.CowBuffer -import top.fifthlight.blazerod.util.copy -import top.fifthlight.blazerod.util.mapToArray +import top.fifthlight.blazerod.physics.PhysicsLibrary +import top.fifthlight.blazerod.util.* import java.util.function.Consumer class ModelInstance(val scene: RenderScene) : AbstractRefCount() { companion object { private val TYPE_ID = Identifier.of("blazerod", "model_instance") + private const val PHYSICS_FPS = 120f } override val typeId: Identifier get() = TYPE_ID val modelData = ModelData(scene) + internal val physicsData = if (PhysicsLibrary.isPhysicsAvailable && scene.hasPhysics) { + PhysicsData(scene) + } else { + null + } init { scene.increaseReferenceCount() + scene.attachToInstance(this) + physicsData?.initJoints() + } + + internal class PhysicsData(scene: RenderScene) : AutoCloseable { + private val physicsJoints = scene.physicsJoints + + val world = PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT).apply { + accuracy = 1f / PHYSICS_FPS + + setGravity(JmeVector3f(0f, -9.8f, 0f)) + + val groundRigidBody = PhysicsRigidBody(PlaneCollisionShape(Plane(JmeVector3f.UNIT_Y, 0f)), 0f) + addCollisionObject(groundRigidBody) + setGroundObject(groundRigidBody) + } + var lastPhysicsTime = -1f + val rigidBodies = Array(scene.rigidBodyComponents.size) { null } + + fun getRigidBody(index: Int) = rigidBodies[index] ?: error("Rigid body not initialized") + + fun initJoints() = physicsJoints.forEach { jointData -> + val jointPositionWorld = jointData.position.toJme() + val jointRotationWorld = Quaternionf() + .rotationZYX(jointData.rotation) + .toJme() + + val rigidBodyA = getRigidBody(jointData.rigidBodyAIndex) + val rigidBodyPositionWorldA = rigidBodyA.getPhysicsLocation(null) + val rigidBodyRotationWorldA = rigidBodyA.getPhysicsRotation(null) + + val rigidBodyB = getRigidBody(jointData.rigidBodyBIndex) + val rigidBodyPositionWorldB = rigidBodyB.getPhysicsLocation(null) + val rigidBodyRotationWorldB = rigidBodyB.getPhysicsRotation(null) + + val pivotInA = rigidBodyRotationWorldA.inverse() + .toRotationMatrix() + .mult(jointPositionWorld.subtract(rigidBodyPositionWorldA), null) + val rotInA = rigidBodyRotationWorldA.inverse() + .mult(jointRotationWorld) + .toRotationMatrix() + + val pivotInB = rigidBodyRotationWorldB.inverse() + .toRotationMatrix() + .mult(jointPositionWorld.subtract(rigidBodyPositionWorldB), null) + val rotInB = rigidBodyRotationWorldB.inverse() + .mult(jointRotationWorld) + .toRotationMatrix() + + val joint = when (jointData.type) { + PhysicalJoint.JointType.SPRING_6DOF -> { + SixDofSpringJoint( + rigidBodyA, + rigidBodyB, + pivotInA, + pivotInB, + rotInA, + rotInB, + true, + ) + } + } + joint.setLinearLowerLimit(jointData.positionMin.toJme()) + joint.setLinearUpperLimit(jointData.positionMax.toJme()) + joint.setAngularLowerLimit(jointData.rotationMin.toJme()) + joint.setAngularUpperLimit(jointData.rotationMax.toJme()) + if (jointData.positionSpring.x() != 0f) { + joint.enableSpring(0, true) + joint.setStiffness(0, jointData.positionSpring.x()) + } + if (jointData.positionSpring.y() != 0f) { + joint.enableSpring(1, true) + joint.setStiffness(1, jointData.positionSpring.y()) + } + if (jointData.positionSpring.z() != 0f) { + joint.enableSpring(2, true) + joint.setStiffness(2, jointData.positionSpring.z()) + } + if (jointData.rotationSpring.x() != 0f) { + joint.enableSpring(3, true) + joint.setStiffness(3, jointData.rotationSpring.x()) + } + if (jointData.rotationSpring.y() != 0f) { + joint.enableSpring(4, true) + joint.setStiffness(4, jointData.rotationSpring.y()) + } + if (jointData.rotationSpring.z() != 0f) { + joint.enableSpring(5, true) + joint.setStiffness(5, jointData.rotationSpring.z()) + } + + world.addJoint(joint) + } + + override fun close() { + world.destroy() + } } class ModelData(scene: RenderScene) : AutoCloseable { @@ -96,6 +203,15 @@ class ModelInstance(val scene: RenderScene) : AbstractRefCount() { transform.setMatrix(transformId, matrix) } + fun setTransformMatrix(nodeIndex: Int, transformId: TransformId, updater: Consumer) = + setTransformMatrix(nodeIndex, transformId) { updater.accept(this) } + + fun setTransformMatrix(nodeIndex: Int, transformId: TransformId, updater: NodeTransform.Matrix.() -> Unit) { + markNodeTransformDirty(scene.nodes[nodeIndex]) + val transform = modelData.transformMaps[nodeIndex] + transform.updateMatrix(transformId, updater) + } + fun setTransformDecomposed(nodeIndex: Int, transformId: TransformId, decomposed: NodeTransformView.Decomposed) { markNodeTransformDirty(scene.nodes[nodeIndex]) val transform = modelData.transformMaps[nodeIndex] @@ -142,12 +258,12 @@ class ModelInstance(val scene: RenderScene) : AbstractRefCount() { scene.updateCamera(this) } - fun debugRender(viewProjectionMatrix: Matrix4fc, consumers: VertexConsumerProvider) { - scene.debugRender(this, viewProjectionMatrix, consumers) + fun debugRender(viewProjectionMatrix: Matrix4fc, consumers: VertexConsumerProvider, time: Float) { + scene.debugRender(this, viewProjectionMatrix, consumers, time) } - fun updateRenderData() { - scene.updateRenderData(this) + fun updateRenderData(time: Float) { + scene.updateRenderData(this, time) } internal fun updateNodeTransform(nodeIndex: Int) { @@ -159,7 +275,7 @@ class ModelInstance(val scene: RenderScene) : AbstractRefCount() { if (modelData.undirtyNodeCount == scene.nodes.size) { return } - node.update(UpdatePhase.GlobalTransformPropagation, node, this) + node.update(UpdatePhase.GlobalTransformPropagation, this) for (child in node.children) { updateNodeTransform(child) } @@ -190,5 +306,6 @@ class ModelInstance(val scene: RenderScene) : AbstractRefCount() { override fun onClosed() { scene.decreaseReferenceCount() modelData.close() + physicsData?.close() } } diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/RenderScene.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/RenderScene.kt index af468647..b408d9a4 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/RenderScene.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/RenderScene.kt @@ -6,14 +6,12 @@ import net.minecraft.util.Identifier import org.joml.Matrix4fc import top.fifthlight.blazerod.model.node.RenderNode import top.fifthlight.blazerod.model.node.UpdatePhase -import top.fifthlight.blazerod.model.node.component.IkTarget -import top.fifthlight.blazerod.model.node.component.Primitive +import top.fifthlight.blazerod.model.node.component.IkTargetNodeComponent +import top.fifthlight.blazerod.model.node.component.PrimitiveNodeComponent import top.fifthlight.blazerod.model.node.component.RenderNodeComponent +import top.fifthlight.blazerod.model.node.component.RigidBodyNodeComponent import top.fifthlight.blazerod.model.node.forEach -import top.fifthlight.blazerod.model.resource.RenderCamera -import top.fifthlight.blazerod.model.resource.RenderExpression -import top.fifthlight.blazerod.model.resource.RenderExpressionGroup -import top.fifthlight.blazerod.model.resource.RenderSkin +import top.fifthlight.blazerod.model.resource.* import top.fifthlight.blazerod.util.AbstractRefCount class RenderScene( @@ -23,9 +21,11 @@ class RenderScene( val expressions: List, val expressionGroups: List, val cameras: List, + val physicsJoints: List, ) : AbstractRefCount() { companion object { private val TYPE_ID = Identifier.of("blazerod", "scene") + private const val PHYSICS_MAX_SUB_STEP_COUNT = 10 } override val typeId: Identifier @@ -33,22 +33,26 @@ class RenderScene( private val sortedNodes: List private val debugRenderNodes: List - val primitiveComponents: List - val morphedPrimitiveComponents: List - val ikTargetComponents: List + val primitiveComponents: List + val morphedPrimitiveComponents: List + val ikTargetComponents: List + val rigidBodyComponents: List val nodeIdMap: Map val nodeNameMap: Map val humanoidTagMap: Map + val hasPhysics: Boolean init { rootNode.increaseReferenceCount() val nodes = mutableListOf() val debugRenderNodes = mutableListOf() - val primitiveComponents = mutableListOf() - val morphedPrimitives = Int2ReferenceOpenHashMap() - val ikTargets = Int2ReferenceOpenHashMap() + val primitiveComponents = mutableListOf() + val morphedPrimitives = Int2ReferenceOpenHashMap() + val ikTargets = Int2ReferenceOpenHashMap() + val rigidBodyComponents = Int2ReferenceOpenHashMap() val nodeIdMap = mutableMapOf() val nodeNameMap = mutableMapOf() val humanoidTagMap = mutableMapOf() + var hasPhysics = false rootNode.forEach { node -> nodes.add(node) node.nodeId?.let { nodeIdMap.put(it, node) } @@ -71,6 +75,10 @@ class RenderScene( node.getComponentsOfType(RenderNodeComponent.Type.IkTarget).forEach { component -> ikTargets.put(component.ikIndex, component) } + node.getComponentsOfType(RenderNodeComponent.Type.RigidBody).forEach { component -> + hasPhysics = true + rigidBodyComponents.put(component.rigidBodyIndex, component) + } } this.sortedNodes = nodes this.debugRenderNodes = debugRenderNodes @@ -81,14 +89,18 @@ class RenderScene( this.ikTargetComponents = (0 until ikTargets.size).map { ikTargets.get(it) ?: error("Ik target index not found: $it") } + this.rigidBodyComponents = (0 until rigidBodyComponents.size).map { + rigidBodyComponents.get(it) ?: error("Rigid body index not found: $it") + } this.nodeIdMap = nodeIdMap this.nodeNameMap = nodeNameMap this.humanoidTagMap = humanoidTagMap + this.hasPhysics = hasPhysics } private fun executePhase(instance: ModelInstance, phase: UpdatePhase) { for (node in sortedNodes) { - node.update(phase, node, instance) + node.update(phase, instance) } } @@ -106,7 +118,12 @@ class RenderScene( executePhase(instance, UpdatePhase.CameraUpdate) } - fun debugRender(instance: ModelInstance, viewProjectionMatrix: Matrix4fc, consumers: VertexConsumerProvider) { + fun debugRender( + instance: ModelInstance, + viewProjectionMatrix: Matrix4fc, + consumers: VertexConsumerProvider, + time: Float, // For physics, in seconds + ) { if (debugRenderNodes.isEmpty()) { return } @@ -116,12 +133,27 @@ class RenderScene( executePhase(instance, UpdatePhase.InfluenceTransformUpdate) executePhase(instance, UpdatePhase.GlobalTransformPropagation) } + instance.physicsData?.let { data -> + if (data.lastPhysicsTime < 0) { + data.lastPhysicsTime = time + return@let + } + val timeStep = time - data.lastPhysicsTime + data.lastPhysicsTime = time + executePhase(instance, UpdatePhase.PhysicsUpdatePre) + data.world.update(timeStep, PHYSICS_MAX_SUB_STEP_COUNT) + executePhase(instance, UpdatePhase.PhysicsUpdatePost) + executePhase(instance, UpdatePhase.GlobalTransformPropagation) + } UpdatePhase.DebugRender.acquire(viewProjectionMatrix, consumers).use { executePhase(instance, it) } } - fun updateRenderData(instance: ModelInstance) { + fun updateRenderData( + instance: ModelInstance, + time: Float, // For physics, in seconds + ) { if (instance.modelData.undirtyNodeCount == nodes.size) { return } @@ -129,9 +161,33 @@ class RenderScene( executePhase(instance, UpdatePhase.IkUpdate) executePhase(instance, UpdatePhase.InfluenceTransformUpdate) executePhase(instance, UpdatePhase.GlobalTransformPropagation) + instance.physicsData?.let { data -> + if (data.lastPhysicsTime < 0) { + data.lastPhysicsTime = time + return@let + } + val timeStep = time - data.lastPhysicsTime + data.lastPhysicsTime = time + executePhase(instance, UpdatePhase.PhysicsUpdatePre) + data.world.update(timeStep, PHYSICS_MAX_SUB_STEP_COUNT) + executePhase(instance, UpdatePhase.PhysicsUpdatePost) + executePhase(instance, UpdatePhase.GlobalTransformPropagation) + } executePhase(instance, UpdatePhase.RenderDataUpdate) } + internal fun attachToInstance(instance: ModelInstance) { + executePhase(instance, UpdatePhase.GlobalTransformPropagation) + executePhase(instance, UpdatePhase.IkUpdate) + executePhase(instance, UpdatePhase.InfluenceTransformUpdate) + executePhase(instance, UpdatePhase.GlobalTransformPropagation) + for (node in nodes) { + for (component in node.components) { + component.onAttached(instance, node) + } + } + } + override fun onClosed() { rootNode.decreaseReferenceCount() } diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelLoadInfo.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelLoadInfo.kt index f22ae4b7..70677c02 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelLoadInfo.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelLoadInfo.kt @@ -11,6 +11,7 @@ import top.fifthlight.blazerod.render.RefCountedGpuBuffer import java.nio.ByteBuffer import top.fifthlight.blazerod.model.Camera as ModelCamera import top.fifthlight.blazerod.model.IkTarget as ModelIkTarget +import top.fifthlight.blazerod.model.RigidBody as ModelRigidBody data class TextureLoadData( val name: String?, @@ -147,6 +148,11 @@ data class NodeLoadInfo( val influence: Influence, val transformId: TransformId, ) : Component() + + data class RigidBody( + val rigidBodyIndex: Int, + val rigidBody: ModelRigidBody, + ) : Component() } } @@ -166,6 +172,7 @@ data class ModelLoadInfo val skins: List, val expressions: List, val expressionGroups: List, + val physicalJoints: List, ) typealias PreProcessModelLoadInfo = ModelLoadInfo> diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelPreprocessor.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelPreprocessor.kt index 43e0de27..ccf8665c 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelPreprocessor.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelPreprocessor.kt @@ -7,10 +7,7 @@ import kotlinx.coroutines.* import top.fifthlight.blazerod.extension.NativeImageExt import top.fifthlight.blazerod.extension.TextureFormatExt import top.fifthlight.blazerod.model.* -import top.fifthlight.blazerod.model.resource.MorphTargetGroup -import top.fifthlight.blazerod.model.resource.RenderExpression -import top.fifthlight.blazerod.model.resource.RenderExpressionGroup -import top.fifthlight.blazerod.model.resource.RenderSkin +import top.fifthlight.blazerod.model.resource.* import top.fifthlight.blazerod.render.BlazerodVertexFormatElements import top.fifthlight.blazerod.render.BlazerodVertexFormats import java.nio.ByteBuffer @@ -488,6 +485,8 @@ class ModelPreprocessor private constructor( } private var ikCount = 0 + private var rigidBodyCount = 0 + private val rigidBodyIdToIndexMap = mutableMapOf() private val nodes = mutableListOf() private fun loadNode(node: Node): Int { val skinJointData = skinJointsData[node.id] @@ -546,6 +545,16 @@ class ModelPreprocessor private constructor( ) } + is NodeComponent.RigidBodyComponent -> { + rigidBodyIdToIndexMap[component.rigidBodyId] = rigidBodyCount + add( + NodeLoadInfo.Component.RigidBody( + rigidBodyIndex = rigidBodyCount++, + rigidBody = component.rigidBody, + ) + ) + } + else -> {} } } @@ -619,6 +628,23 @@ class ModelPreprocessor private constructor( return Pair(expressions, expressionGroups) } + private fun loadPhysicalJoints(modelPhysicalJoints: List) = modelPhysicalJoints.mapNotNull { + RenderPhysicsJoint( + name = it.name, + type = it.type, + rigidBodyAIndex = rigidBodyIdToIndexMap[it.rigidBodyA] ?: return@mapNotNull null, + rigidBodyBIndex = rigidBodyIdToIndexMap[it.rigidBodyB] ?: return@mapNotNull null, + position = it.position, + rotation = it.rotation, + positionMin = it.positionMin, + positionMax = it.positionMax, + rotationMin = it.rotationMin, + rotationMax = it.rotationMax, + positionSpring = it.positionSpring, + rotationSpring = it.rotationSpring, + ) + } + private fun loadScene(scene: Scene, expressions: List): PreProcessModelLoadInfo { val rootNode = NodeLoadInfo( nodeId = null, @@ -631,6 +657,7 @@ class ModelPreprocessor private constructor( val rootNodeIndex = nodes.size nodes.add(rootNode) val (expressions, expressionGroups) = loadExpressions(expressions) + val physicalJoints = loadPhysicalJoints(model.physicalJoints) return PreProcessModelLoadInfo( textures = textures, indexBuffers = indexBuffers, @@ -642,6 +669,7 @@ class ModelPreprocessor private constructor( morphTargetInfos = morphTargetInfos, expressions = expressions, expressionGroups = expressionGroups, + physicalJoints = physicalJoints, ) } diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelResourceLoader.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelResourceLoader.kt index 60a144bb..84b53180 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelResourceLoader.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/ModelResourceLoader.kt @@ -141,6 +141,7 @@ object ModelResourceLoader { skins = info.skins, expressions = info.expressions, expressionGroups = info.expressionGroups, + physicalJoints = info.physicalJoints, ) } } \ No newline at end of file diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/SceneReconstructor.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/SceneReconstructor.kt index 6b446697..893411f5 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/SceneReconstructor.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/load/SceneReconstructor.kt @@ -89,7 +89,7 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI } ?: RenderMaterial.defaultMaterial val targets = primitiveInfo.morphedPrimitiveIndex?.let { index -> info.morphTargetInfos[index].await() } - Primitive( + PrimitiveNodeComponent( primitiveIndex = component.infoIndex, primitive = RenderPrimitive( vertices = primitiveInfo.vertices, @@ -113,7 +113,7 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI } is NodeLoadInfo.Component.Joint -> { - Joint( + JointNodeComponent( skinIndex = component.skinIndex, jointIndex = component.jointIndex, ) @@ -127,12 +127,12 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI camera = component.camera, ) ) - Camera(cameraIndex) + CameraNodeComponent(cameraIndex) } is NodeLoadInfo.Component.InfluenceSource -> { val influence = component.influence - InfluenceSource( + InfluenceSourceNodeComponent( targetNodeIndex = nodeIdToIndexMap[component.influence.target] ?: return@mapNotNull null, influence = influence.influence, influenceRotation = influence.influenceRotation, @@ -143,7 +143,7 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI } is NodeLoadInfo.Component.IkTarget -> { - IkTarget( + IkTargetNodeComponent( ikIndex = component.ikIndex, limitRadian = component.ikTarget.limitRadian, loopCount = component.ikTarget.loopCount, @@ -151,13 +151,20 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI effectorNodeIndex = nodeIdToIndexMap[component.ikTarget.effectorNodeId] ?: return@mapNotNull null, chains = component.ikTarget.joints.map { - IkTarget.Chain( + IkTargetNodeComponent.Chain( nodeIndex = nodeIdToIndexMap[it.nodeId] ?: return@mapNotNull null, limit = it.limit, ) } ) } + + is NodeLoadInfo.Component.RigidBody -> { + RigidBodyNodeComponent( + rigidBodyIndex = component.rigidBodyIndex, + rigidBodyData = component.rigidBody, + ) + } } }, ) @@ -175,6 +182,7 @@ class SceneReconstructor private constructor(private val info: GpuLoadModelLoadI expressions = info.expressions, expressionGroups = info.expressionGroups, cameras = cameras, + physicsJoints = info.physicalJoints, ) } diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/RenderNode.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/RenderNode.kt index 2c7cf3d7..ed866400 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/RenderNode.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/RenderNode.kt @@ -1,6 +1,7 @@ package top.fifthlight.blazerod.model.node import net.minecraft.util.Identifier +import org.joml.Matrix4fc import top.fifthlight.blazerod.model.* import top.fifthlight.blazerod.model.node.component.RenderNodeComponent import top.fifthlight.blazerod.util.AbstractRefCount @@ -65,24 +66,24 @@ class RenderNode( typeComponents[type] as? List ?: listOf() fun hasComponentOfType(type: RenderNodeComponent.Type<*>): Boolean = type in typeComponents.keys - fun update(phase: UpdatePhase, node: RenderNode, instance: ModelInstance) { + fun update(phase: UpdatePhase, instance: ModelInstance) { if (phase == UpdatePhase.GlobalTransformPropagation) { - if (!instance.isNodeTransformDirty(node)) { + if (!instance.isNodeTransformDirty(this)) { return } val parent = parent val transformMap = instance.getTransformMap(this) - val worldTransform = instance.getWorldTransform(this) + val worldTransform = instance.modelData.worldTransforms[nodeIndex] val currentLocalTransform = transformMap.getSum(TransformId.LAST) if (parent != null) { instance.getWorldTransform(parent).mul(currentLocalTransform, worldTransform) } else { worldTransform.set(currentLocalTransform) } - instance.cleanNodeTransformDirty(node) + instance.cleanNodeTransformDirty(this) } else { phaseComponents[phase.type]?.forEach { component -> - component.update(phase, node, instance) + component.update(phase, this, instance) } } } @@ -100,9 +101,9 @@ fun RenderNode.forEach(action: (RenderNode) -> Unit) { } fun ModelInstance.getTransformMap(node: RenderNode) = modelData.transformMaps[node.nodeIndex] -fun ModelInstance.getWorldTransform(node: RenderNode) = modelData.worldTransforms[node.nodeIndex] +fun ModelInstance.getWorldTransform(node: RenderNode): Matrix4fc = modelData.worldTransforms[node.nodeIndex] fun ModelInstance.getTransformMap(nodeIndex: Int) = modelData.transformMaps[nodeIndex] -fun ModelInstance.getWorldTransform(nodeIndex: Int) = modelData.worldTransforms[nodeIndex] +fun ModelInstance.getWorldTransform(nodeIndex: Int): Matrix4fc = modelData.worldTransforms[nodeIndex] private fun ModelInstance.isNodeTransformDirty(node: RenderNode) = modelData.transformDirty[node.nodeIndex] fun ModelInstance.markNodeTransformDirty(node: RenderNode) { if (!modelData.transformDirty[node.nodeIndex]) { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/UpdatePhase.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/UpdatePhase.kt index bf8f9cb7..bdf59a68 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/UpdatePhase.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/UpdatePhase.kt @@ -12,6 +12,8 @@ sealed class UpdatePhase( enum class Type { IK_UPDATE, INFLUENCE_TRANSFORM_UPDATE, + PHYSICS_UPDATE_PRE, + PHYSICS_UPDATE_POST, GLOBAL_TRANSFORM_PROPAGATION, RENDER_DATA_UPDATE, CAMERA_UPDATE, @@ -22,6 +24,10 @@ sealed class UpdatePhase( data object InfluenceTransformUpdate : UpdatePhase(Type.INFLUENCE_TRANSFORM_UPDATE) + data object PhysicsUpdatePre : UpdatePhase(Type.PHYSICS_UPDATE_PRE) + + data object PhysicsUpdatePost : UpdatePhase(Type.PHYSICS_UPDATE_POST) + data object GlobalTransformPropagation : UpdatePhase(Type.GLOBAL_TRANSFORM_PROPAGATION) data object RenderDataUpdate : UpdatePhase(Type.RENDER_DATA_UPDATE) diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Camera.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/CameraNodeComponent.kt similarity index 88% rename from blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Camera.kt rename to blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/CameraNodeComponent.kt index 1b575c10..c86b7da6 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Camera.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/CameraNodeComponent.kt @@ -5,7 +5,7 @@ import top.fifthlight.blazerod.model.node.RenderNode import top.fifthlight.blazerod.model.node.UpdatePhase import top.fifthlight.blazerod.model.node.getWorldTransform -class Camera : RenderNodeComponent { +class CameraNodeComponent : RenderNodeComponent { val cameraIndex: Int constructor(cameraIndex: Int) : super() { @@ -14,7 +14,7 @@ class Camera : RenderNodeComponent { override fun onClosed() {} - override val type: Type + override val type: Type get() = Type.Camera companion object { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTarget.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTargetNodeComponent.kt similarity index 99% rename from blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTarget.kt rename to blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTargetNodeComponent.kt index 7affed62..15204d50 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTarget.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/IkTargetNodeComponent.kt @@ -12,17 +12,17 @@ import top.fifthlight.blazerod.model.node.getWorldTransform import top.fifthlight.blazerod.util.drawBox import kotlin.math.* -class IkTarget( +class IkTargetNodeComponent( val ikIndex: Int, val limitRadian: Float, val loopCount: Int, val chains: List, val effectorNodeIndex: Int, val transformId: TransformId, -) : RenderNodeComponent() { +) : RenderNodeComponent() { override fun onClosed() {} - override val type: Type + override val type: Type get() = Type.IkTarget companion object { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSource.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSourceNodeComponent.kt similarity index 94% rename from blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSource.kt rename to blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSourceNodeComponent.kt index 74bf45f6..ac89ce6a 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSource.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/InfluenceSourceNodeComponent.kt @@ -7,17 +7,17 @@ import top.fifthlight.blazerod.model.TransformId import top.fifthlight.blazerod.model.node.RenderNode import top.fifthlight.blazerod.model.node.UpdatePhase -class InfluenceSource( +class InfluenceSourceNodeComponent( val target: TransformId, val targetNodeIndex: Int, val influence: Float, val influenceRotation: Boolean = false, val influenceTranslation: Boolean = false, val appendLocal: Boolean = false, -) : RenderNodeComponent() { +) : RenderNodeComponent() { override fun onClosed() {} - override val type: Type + override val type: Type get() = Type.InfluenceSource companion object { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Joint.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/JointNodeComponent.kt similarity index 94% rename from blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Joint.kt rename to blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/JointNodeComponent.kt index f85fc51b..83994246 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Joint.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/JointNodeComponent.kt @@ -7,13 +7,13 @@ import top.fifthlight.blazerod.model.node.RenderNode import top.fifthlight.blazerod.model.node.UpdatePhase import top.fifthlight.blazerod.model.node.getWorldTransform -class Joint( +class JointNodeComponent( val skinIndex: Int, val jointIndex: Int, -) : RenderNodeComponent() { +) : RenderNodeComponent() { override fun onClosed() {} - override val type: Type + override val type: Type get() = Type.Joint companion object { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Primitive.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/PrimitiveNodeComponent.kt similarity index 90% rename from blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Primitive.kt rename to blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/PrimitiveNodeComponent.kt index 3b4f3da5..606dd529 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/Primitive.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/PrimitiveNodeComponent.kt @@ -7,13 +7,13 @@ import top.fifthlight.blazerod.model.node.UpdatePhase import top.fifthlight.blazerod.model.node.getWorldTransform import top.fifthlight.blazerod.model.resource.RenderPrimitive -class Primitive( +class PrimitiveNodeComponent( val primitiveIndex: Int, val primitive: RenderPrimitive, val skinIndex: Int?, val morphedPrimitiveIndex: Int?, val firstPersonFlag: Mesh.FirstPersonFlag = Mesh.FirstPersonFlag.BOTH, -) : RenderNodeComponent() { +) : RenderNodeComponent() { init { primitive.increaseReferenceCount() } @@ -22,7 +22,7 @@ class Primitive( primitive.decreaseReferenceCount() } - override val type: Type + override val type: Type get() = Type.Primitive companion object { diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RenderNodeComponent.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RenderNodeComponent.kt index 8424c76b..ab410a8a 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RenderNodeComponent.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RenderNodeComponent.kt @@ -30,15 +30,18 @@ sealed class RenderNodeComponent> : AbstractRefCount( get() = TYPE_ID sealed class Type> { - object Primitive : Type() - object Joint : Type() - object InfluenceSource : Type() - object Camera : Type() - object IkTarget : Type() + object Primitive : Type() + object Joint : Type() + object InfluenceSource : Type() + object Camera : Type() + object IkTarget : Type() + object RigidBody : Type() } abstract val type: Type + open fun onAttached(instance: ModelInstance, node: RenderNode) {} + abstract val updatePhases: List abstract fun update(phase: UpdatePhase, node: RenderNode, instance: ModelInstance) diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RigidBodyNodeComponent.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RigidBodyNodeComponent.kt new file mode 100644 index 00000000..14895415 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/node/component/RigidBodyNodeComponent.kt @@ -0,0 +1,230 @@ +package top.fifthlight.blazerod.model.node.component + +import com.jme3.bullet.collision.shapes.BoxCollisionShape +import com.jme3.bullet.collision.shapes.CapsuleCollisionShape +import com.jme3.bullet.collision.shapes.SphereCollisionShape +import com.jme3.bullet.objects.PhysicsRigidBody +import com.jme3.math.Transform +import net.minecraft.util.Colors +import org.joml.Matrix4f +import org.joml.Vector3f +import top.fifthlight.blazerod.model.ModelInstance +import top.fifthlight.blazerod.model.RigidBody +import top.fifthlight.blazerod.model.TransformId +import top.fifthlight.blazerod.model.node.RenderNode +import top.fifthlight.blazerod.model.node.UpdatePhase +import top.fifthlight.blazerod.model.node.getWorldTransform +import top.fifthlight.blazerod.model.util.toRadian +import top.fifthlight.blazerod.util.* + +class RigidBodyNodeComponent( + val rigidBodyIndex: Int, + val rigidBodyData: RigidBody, +) : RenderNodeComponent() { + override val type: Type + get() = Type.RigidBody + + private val shape by lazy { + when (rigidBodyData.shape) { + RigidBody.ShapeType.SPHERE -> SphereCollisionShape(rigidBodyData.shapeSize.x()) + RigidBody.ShapeType.BOX -> BoxCollisionShape(rigidBodyData.shapeSize.toJme()) + RigidBody.ShapeType.CAPSULE -> CapsuleCollisionShape( + rigidBodyData.shapeSize.x(), + rigidBodyData.shapeSize.y() + ) + } + } + private val scaleMustBeUniform = when (rigidBodyData.shape) { + RigidBody.ShapeType.SPHERE -> true + RigidBody.ShapeType.BOX -> false + RigidBody.ShapeType.CAPSULE -> true + } + + private fun Transform.normalizeScale() = if (scaleMustBeUniform) { + // Sometimes we have a small error on the scale + setScale(jmeNodeTransform.scale.x) + } else { + this + } + + private val nodeTransformMatrix = Matrix4f() + private val jmeNodeTransformMatrix = JmeMatrix4f() + private val jmeNodeTransform = Transform() + + // node world transform -> rigid body world transform + private val offsetMatrix = Matrix4f() + + // rigid body world transform -> node world transform + private val inverseOffsetMatrix = Matrix4f() + + override fun onAttached(instance: ModelInstance, node: RenderNode) { + instance.physicsData?.let { data -> + val rigidBody = PhysicsRigidBody( + shape, when (rigidBodyData.physicsMode) { + RigidBody.PhysicsMode.FOLLOW_BONE -> 0f + else -> rigidBodyData.mass + } + ) + instance.getWorldTransform(node) + .invert(offsetMatrix) + .translate(rigidBodyData.shapePosition) + .rotateYXZ(rigidBodyData.shapeRotation) + .invert(inverseOffsetMatrix) + val jmeNodeTransformMatrix = nodeTransformMatrix + .translation(rigidBodyData.shapePosition) + .rotateYXZ(rigidBodyData.shapeRotation) + .get(jmeNodeTransformMatrix) + jmeNodeTransform.apply { + fromTransformMatrix(jmeNodeTransformMatrix) + normalizeScale() + } + + rigidBody.setPhysicsTransform(jmeNodeTransform) + + rigidBody.collisionGroup = rigidBodyData.collisionGroup + rigidBody.collideWithGroups = rigidBodyData.collisionMask + + rigidBody.linearDamping = rigidBodyData.moveAttenuation + rigidBody.angularDamping = rigidBodyData.rotationDamping + rigidBody.restitution = rigidBodyData.repulsion + rigidBody.friction = rigidBodyData.frictionForce + // m_additionalDamping + + rigidBody.setSleepingThresholds(0.01f, 0.01f.toRadian()) + rigidBody.setEnableSleep(false) + if (rigidBodyData.physicsMode == RigidBody.PhysicsMode.FOLLOW_BONE) { + rigidBody.isKinematic = true + } + + data.world.addCollisionObject(rigidBody) + data.rigidBodies[rigidBodyIndex] = rigidBody + } + } + + companion object { + private val updatePhase = listOf( + UpdatePhase.Type.PHYSICS_UPDATE_PRE, + UpdatePhase.Type.PHYSICS_UPDATE_POST, + UpdatePhase.Type.DEBUG_RENDER, + ) + } + + override val updatePhases: List + get() = updatePhase + + private val inverseNodeWorldMatrix = Matrix4f() + private val translation = Vector3f() + private val jmeTranslation = JmeVector3f() + override fun update( + phase: UpdatePhase, + node: RenderNode, + instance: ModelInstance, + ) { + val physicsData = instance.physicsData ?: return + val rigidBody = physicsData.getRigidBody(rigidBodyIndex) + when (phase) { + is UpdatePhase.PhysicsUpdatePre -> { + when (rigidBodyData.physicsMode) { + RigidBody.PhysicsMode.FOLLOW_BONE -> { + val jmeNodeTransformMatrix = instance.getWorldTransform(node) + .mul(offsetMatrix, nodeTransformMatrix) + .get(jmeNodeTransformMatrix) + jmeNodeTransform.apply { + fromTransformMatrix(jmeNodeTransformMatrix) + normalizeScale() + } + rigidBody.setPhysicsTransform(jmeNodeTransform) + } + + RigidBody.PhysicsMode.PHYSICS -> { + // no-op + } + + RigidBody.PhysicsMode.PHYSICS_PLUS_BONE -> { + // only apply position + instance.getWorldTransform(node) + .getTranslation(translation) + .get(jmeTranslation) + rigidBody.setPhysicsLocation(jmeTranslation) + } + } + } + + is UpdatePhase.PhysicsUpdatePost -> { + when (rigidBodyData.physicsMode) { + RigidBody.PhysicsMode.FOLLOW_BONE -> { + // no-op + } + + RigidBody.PhysicsMode.PHYSICS -> { + val nodeTransformMatrix = rigidBody.getTransform(jmeNodeTransform) + .toTransformMatrix(jmeNodeTransformMatrix) + .get(nodeTransformMatrix) + .mul(inverseOffsetMatrix) + val inverseNodeWorldMatrix = instance.getWorldTransform(node) + .invert(inverseNodeWorldMatrix) + val deltaTransformMatrix = nodeTransformMatrix.mulLocal(inverseNodeWorldMatrix) + instance.setTransformMatrix(node.nodeIndex, TransformId.PHYSICS) { + matrix.mul(deltaTransformMatrix) + } + } + + RigidBody.PhysicsMode.PHYSICS_PLUS_BONE -> { + // only apply rotation + } + } + } + + is UpdatePhase.DebugRender -> { + val consumers = phase.vertexConsumerProvider + val vertexBuffer = consumers.getBuffer(DEBUG_RENDER_LAYER) + + rigidBody.getTransform(jmeNodeTransform) + nodeTransformMatrix.set(jmeNodeTransform) + + val matrix = phase.viewProjectionMatrix.mul(nodeTransformMatrix, phase.cacheMatrix) + + val color = when (rigidBodyData.physicsMode) { + RigidBody.PhysicsMode.FOLLOW_BONE -> Colors.PURPLE + RigidBody.PhysicsMode.PHYSICS -> Colors.RED + RigidBody.PhysicsMode.PHYSICS_PLUS_BONE -> Colors.GREEN + } + + when (rigidBodyData.shape) { + RigidBody.ShapeType.SPHERE -> { + vertexBuffer.drawSphereWireframe( + matrix = matrix, + radius = rigidBodyData.shapeSize.x(), + segments = 16, + color = color, + ) + } + + RigidBody.ShapeType.BOX -> { + vertexBuffer.drawBoxWireframe( + matrix = matrix, + width = rigidBodyData.shapeSize.x(), + height = rigidBodyData.shapeSize.y(), + length = rigidBodyData.shapeSize.z(), + color = color, + ) + } + + RigidBody.ShapeType.CAPSULE -> { + vertexBuffer.drawCapsuleWireframe( + matrix = matrix, + radius = rigidBodyData.shapeSize.x(), + height = rigidBodyData.shapeSize.y(), + segments = 16, + color = color, + ) + } + } + } + + else -> {} + } + } + + override fun onClosed() {} +} \ No newline at end of file diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/ComputeShaderTransformRenderer.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/ComputeShaderTransformRenderer.kt index c88dd520..e11c3716 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/ComputeShaderTransformRenderer.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/ComputeShaderTransformRenderer.kt @@ -22,7 +22,7 @@ import top.fifthlight.blazerod.model.RenderScene import top.fifthlight.blazerod.model.RenderTask import top.fifthlight.blazerod.model.data.MorphTargetBuffer import top.fifthlight.blazerod.model.data.RenderSkinBuffer -import top.fifthlight.blazerod.model.node.component.Primitive +import top.fifthlight.blazerod.model.node.component.PrimitiveNodeComponent import top.fifthlight.blazerod.model.resource.RenderMaterial import top.fifthlight.blazerod.model.resource.RenderPrimitive import top.fifthlight.blazerod.model.toVector4f @@ -292,7 +292,7 @@ class ComputeShaderTransformRenderer private constructor() : private class ComputeItem private constructor() { private var released = true - private var _primitiveComponent: Primitive? = null + private var _primitiveComponent: PrimitiveNodeComponent? = null private var _renderTask: RenderTask? = null private var _vertexFormat: VertexFormat? = null private var _vertexBuffer: GpuBufferSlice? = null @@ -331,7 +331,7 @@ class ComputeShaderTransformRenderer private constructor() : ) fun acquire( - primitiveComponent: Primitive, + primitiveComponent: PrimitiveNodeComponent, renderTask: RenderTask, vertexFormat: VertexFormat, vertexBuffer: GpuBufferSlice, diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/Renderer.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/Renderer.kt index 8087b1e9..00058200 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/Renderer.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/Renderer.kt @@ -6,7 +6,7 @@ import top.fifthlight.blazerod.model.RenderTask import top.fifthlight.blazerod.model.TaskMap import top.fifthlight.blazerod.model.data.MorphTargetBuffer import top.fifthlight.blazerod.model.data.RenderSkinBuffer -import top.fifthlight.blazerod.model.node.component.Primitive +import top.fifthlight.blazerod.model.node.component.PrimitiveNodeComponent import top.fifthlight.blazerod.model.resource.RenderPrimitive sealed class Renderer, T : Renderer.Type> : AutoCloseable { @@ -102,6 +102,6 @@ abstract class TaskMapScheduledRenderer, T : Rendere depthFrameBuffer: GpuTextureView?, tasks: List, scene: RenderScene, - component: Primitive, + component: PrimitiveNodeComponent, ) } diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/VertexShaderTransformRenderer.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/VertexShaderTransformRenderer.kt index 33565b25..4ce04856 100644 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/VertexShaderTransformRenderer.kt +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/renderer/VertexShaderTransformRenderer.kt @@ -21,7 +21,7 @@ import top.fifthlight.blazerod.model.RenderScene import top.fifthlight.blazerod.model.RenderTask import top.fifthlight.blazerod.model.data.MorphTargetBuffer import top.fifthlight.blazerod.model.data.RenderSkinBuffer -import top.fifthlight.blazerod.model.node.component.Primitive +import top.fifthlight.blazerod.model.node.component.PrimitiveNodeComponent import top.fifthlight.blazerod.model.resource.RenderMaterial import top.fifthlight.blazerod.model.resource.RenderPrimitive import top.fifthlight.blazerod.model.uniform.* @@ -418,7 +418,7 @@ class VertexShaderTransformRenderer private constructor() : depthFrameBuffer: GpuTextureView?, tasks: List, scene: RenderScene, - component: Primitive, + component: PrimitiveNodeComponent, ) { val primitive = component.primitive val material = primitive.material diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/resource/RenderPhysicsJoint.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/resource/RenderPhysicsJoint.kt new file mode 100644 index 00000000..9550db66 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/model/resource/RenderPhysicsJoint.kt @@ -0,0 +1,19 @@ +package top.fifthlight.blazerod.model.resource + +import org.joml.Vector3fc +import top.fifthlight.blazerod.model.PhysicalJoint + +data class RenderPhysicsJoint( + val name: String? = null, + val type: PhysicalJoint.JointType, + val rigidBodyAIndex: Int, + val rigidBodyBIndex: Int, + val position: Vector3fc, + val rotation: Vector3fc, + val positionMin: Vector3fc, + val positionMax: Vector3fc, + val rotationMin: Vector3fc, + val rotationMax: Vector3fc, + val positionSpring: Vector3fc, + val rotationSpring: Vector3fc, +) diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/physics/PhysicsLibrary.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/physics/PhysicsLibrary.kt new file mode 100644 index 00000000..8e5f8ca6 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/physics/PhysicsLibrary.kt @@ -0,0 +1,147 @@ +package top.fifthlight.blazerod.physics + +import net.fabricmc.loader.api.FabricLoader +import org.slf4j.LoggerFactory +import top.fifthlight.blazerod.BlazeRod +import java.io.IOException +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.DosFileAttributeView +import java.nio.file.attribute.PosixFileAttributeView +import java.nio.file.attribute.PosixFilePermission +import kotlin.io.path.* +import kotlin.jvm.optionals.getOrNull + +object PhysicsLibrary { + private val logger = LoggerFactory.getLogger(PhysicsLibrary::class.java) + var isPhysicsAvailable = false + private set + + private val androidPaths = listOf( + Path.of("/", "system", "build.prop"), + Path.of("/", "system", "bin", "app_process"), + Path.of("/", "system", "framework", "framework.jar") + ) + private val isAndroid: Boolean by lazy { + androidPaths.any { path -> + try { + path.exists() + } catch (ex: SecurityException) { + logger.info("Failed to access $path, may running on Android", ex) + true + } catch (ex: IOException) { + logger.info("Failed to access $path, may running on Android", ex) + true + } + } + } + + fun init() { + if (isPhysicsAvailable) { + return + } + + logger.info("Loading libbulletjme natives...") + + val systemName = System.getProperty("os.name") + val systemArch = System.getProperty("os.arch") + logger.info("System name: $systemName, system arch: $systemArch") + + val (system, extension) = when { + systemName.startsWith("Linux", ignoreCase = true) -> if (isAndroid) { + "android" to "so" + } else { + "linux" to "so" + } + + // Most OpenJDK on Android declare themselves as Linux, but just in case + systemName.contains("Android", ignoreCase = true) -> "android" to "so" + systemName.startsWith("Windows", ignoreCase = true) -> "windows" to "dll" + else -> { + logger.warn("Unknown system: $systemName, no physics available") + return + } + } + val fileName = "liblibbulletjme_natives.$extension" + + val arch = when (systemArch) { + "x86_32", "x86", "i386", "i486", "i586", "i686" -> "x86_32" + "amd64", "x86_64" -> "x86_64" + "armeabi", "armeabi-v7a", "armhf", "arm", "armel" -> "armv7" + "arm64", "aarch64" -> "aarch64" + else -> null + } ?: run { + logger.warn("Unsupported arch: $systemArch, no physics available") + return + } + val resourcePath = "libbulletjme_natives_${system}_${arch}/$fileName" + + val container = FabricLoader.getInstance().getModContainer("libbulletjme_natives_blazerod") + val path = try { + if (container.isEmpty) { + if (!BlazeRod.debug) { + logger.warn("Failed to find natives JAR, no physics available") + return + } + val uri = javaClass.classLoader.getResource(resourcePath)?.toURI() + if (uri?.scheme == "jar") { + runCatching { + FileSystems.newFileSystem(uri, mapOf("create" to "true")) + } + } + val path = uri?.toPath() + if (path == null) { + logger.warn("Failed to find $resourcePath in resources, no physics available") + return + } + path + } else { + val file = container.get().findPath(resourcePath).getOrNull() + if (file == null) { + logger.warn("Failed to find $resourcePath in natives JAR, no physics available") + return + } + file + } + } catch (ex: Exception) { + logger.warn("Failed to get natives in JAR, no physics available", ex) + return + } + + val outputPath = Files.createTempFile("bulletjme", ".$extension") + logger.info("Extracting $resourcePath to $outputPath") + + try { + path.copyTo(outputPath, true) + runCatching { + // Set file to read only after extracting + if (system == "windows") { + val attributeView = outputPath.fileAttributesView() + attributeView.setReadOnly(true) + } else { + val attributeView = outputPath.fileAttributesView() + // 500 + attributeView.setPermissions( + setOf( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_EXECUTE + ) + ) + } + } + System.load(outputPath.toAbsolutePath().toString()) + isPhysicsAvailable = true + logger.info("Loaded libbulletjme natives") + } catch (ex: Throwable) { + logger.warn("Failed to load libbulletjme natives", ex) + } finally { + try { + outputPath.deleteIfExists() + } catch (ex: Exception) { + outputPath.toFile().deleteOnExit() + // Can't delete on Windows + } + } + } +} \ No newline at end of file diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/BoxUtil.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/BoxUtil.kt deleted file mode 100644 index 38abdc66..00000000 --- a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/BoxUtil.kt +++ /dev/null @@ -1,36 +0,0 @@ -package top.fifthlight.blazerod.util - -import net.minecraft.client.render.VertexConsumer -import org.joml.Matrix4f - -fun VertexConsumer.drawBox(matrix: Matrix4f, halfSize: Float, color: Int) { - vertex(matrix, -halfSize, -halfSize, halfSize).color(color) - vertex(matrix, halfSize, -halfSize, halfSize).color(color) - vertex(matrix, halfSize, halfSize, halfSize).color(color) - vertex(matrix, -halfSize, halfSize, halfSize).color(color) - - vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) - vertex(matrix, -halfSize, halfSize, -halfSize).color(color) - vertex(matrix, halfSize, halfSize, -halfSize).color(color) - vertex(matrix, halfSize, -halfSize, -halfSize).color(color) - - vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) - vertex(matrix, -halfSize, -halfSize, halfSize).color(color) - vertex(matrix, -halfSize, halfSize, halfSize).color(color) - vertex(matrix, -halfSize, halfSize, -halfSize).color(color) - - vertex(matrix, halfSize, -halfSize, -halfSize).color(color) - vertex(matrix, halfSize, halfSize, -halfSize).color(color) - vertex(matrix, halfSize, halfSize, halfSize).color(color) - vertex(matrix, halfSize, -halfSize, halfSize).color(color) - - vertex(matrix, -halfSize, halfSize, -halfSize).color(color) - vertex(matrix, -halfSize, halfSize, halfSize).color(color) - vertex(matrix, halfSize, halfSize, halfSize).color(color) - vertex(matrix, halfSize, halfSize, -halfSize).color(color) - - vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) - vertex(matrix, halfSize, -halfSize, -halfSize).color(color) - vertex(matrix, halfSize, -halfSize, halfSize).color(color) - vertex(matrix, -halfSize, -halfSize, halfSize).color(color) -} \ No newline at end of file diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/DrawUtil.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/DrawUtil.kt new file mode 100644 index 00000000..07f96e26 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/DrawUtil.kt @@ -0,0 +1,243 @@ +package top.fifthlight.blazerod.util + +import net.minecraft.client.render.VertexConsumer +import org.joml.Matrix4f +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +fun VertexConsumer.drawBox(matrix: Matrix4f, halfSize: Float, color: Int) { + vertex(matrix, -halfSize, -halfSize, halfSize).color(color) + vertex(matrix, halfSize, -halfSize, halfSize).color(color) + vertex(matrix, halfSize, halfSize, halfSize).color(color) + vertex(matrix, -halfSize, halfSize, halfSize).color(color) + + vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) + vertex(matrix, -halfSize, halfSize, -halfSize).color(color) + vertex(matrix, halfSize, halfSize, -halfSize).color(color) + vertex(matrix, halfSize, -halfSize, -halfSize).color(color) + + vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) + vertex(matrix, -halfSize, -halfSize, halfSize).color(color) + vertex(matrix, -halfSize, halfSize, halfSize).color(color) + vertex(matrix, -halfSize, halfSize, -halfSize).color(color) + + vertex(matrix, halfSize, -halfSize, -halfSize).color(color) + vertex(matrix, halfSize, halfSize, -halfSize).color(color) + vertex(matrix, halfSize, halfSize, halfSize).color(color) + vertex(matrix, halfSize, -halfSize, halfSize).color(color) + + vertex(matrix, -halfSize, halfSize, -halfSize).color(color) + vertex(matrix, -halfSize, halfSize, halfSize).color(color) + vertex(matrix, halfSize, halfSize, halfSize).color(color) + vertex(matrix, halfSize, halfSize, -halfSize).color(color) + + vertex(matrix, -halfSize, -halfSize, -halfSize).color(color) + vertex(matrix, halfSize, -halfSize, -halfSize).color(color) + vertex(matrix, halfSize, -halfSize, halfSize).color(color) + vertex(matrix, -halfSize, -halfSize, halfSize).color(color) +} + +fun VertexConsumer.drawBoxWireframe(matrix: Matrix4f, width: Float, height: Float, length: Float, color: Int) { + val halfWidth = width / 2.0f + val halfHeight = height / 2.0f + val halfLength = length / 2.0f + + vertex(matrix, -halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + + vertex(matrix, -halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + + vertex(matrix, -halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, -halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, halfLength).color(color).normal(0f, 1f, 0f) + vertex(matrix, -halfWidth, halfHeight, -halfLength).color(color).normal(0f, 1f, 0f) +} + +fun VertexConsumer.drawSphereWireframe( + matrix: Matrix4f, + radius: Float, + segments: Int = 16, + color: Int, +) { + val angleStep = (2 * PI / segments).toFloat() + + for (i in 0..segments) { + val latAngle = PI.toFloat() / 2 - i * angleStep + val y = radius * sin(latAngle) + val r = radius * cos(latAngle) + + var prevX = r * cos(0f) + var prevZ = r * sin(0f) + + for (j in 1..segments) { + val lonAngle = j * angleStep + val x = r * cos(lonAngle) + val z = r * sin(lonAngle) + + vertex(matrix, prevX, y, prevZ).color(color).normal(0f, 1f, 0f) + vertex(matrix, x, y, z).color(color).normal(0f, 1f, 0f) + + prevX = x + prevZ = z + } + } + + for (i in 0 until segments) { + val lonAngle = i * angleStep + + var prevX = radius * cos(PI.toFloat() / 2) * cos(lonAngle) + var prevY = radius * sin(PI.toFloat() / 2) + var prevZ = radius * cos(PI.toFloat() / 2) * sin(lonAngle) + + for (j in 1..segments) { + val latAngle = PI.toFloat() / 2 - j * angleStep + val x = radius * cos(latAngle) * cos(lonAngle) + val y = radius * sin(latAngle) + val z = radius * cos(latAngle) * sin(lonAngle) + + vertex(matrix, prevX, prevY, prevZ).color(color).normal(0f, 1f, 0f) + vertex(matrix, x, y, z).color(color).normal(0f, 1f, 0f) + + prevX = x + prevY = y + prevZ = z + } + } +} + +fun VertexConsumer.drawCapsuleWireframe( + matrix: Matrix4f, + radius: Float, + height: Float, + segments: Int = 16, + color: Int, +) { + val halfHeight = height / 2.0f + val angleStep = (2 * PI / segments).toFloat() + + for (i in 0..segments) { + val angle = i * angleStep + val nextAngle = (i + 1) * angleStep + + val x1 = radius * cos(angle) + val z1 = radius * sin(angle) + val x2 = radius * cos(nextAngle) + val z2 = radius * sin(nextAngle) + + vertex(matrix, x1, halfHeight, z1).color(color).normal(0f, 1f, 0f) + vertex(matrix, x2, halfHeight, z2).color(color).normal(0f, 1f, 0f) + + vertex(matrix, x1, -halfHeight, z1).color(color).normal(0f, 1f, 0f) + vertex(matrix, x2, -halfHeight, z2).color(color).normal(0f, 1f, 0f) + + vertex(matrix, x1, halfHeight, z1).color(color).normal(0f, 1f, 0f) + vertex(matrix, x1, -halfHeight, z1).color(color).normal(0f, 1f, 0f) + } + + val topCenterY = halfHeight + for (i in 0 until segments / 2) { + val latAngleCurrent = PI.toFloat() / 2 - i * angleStep + val latAngleNext = PI.toFloat() / 2 - (i + 1) * angleStep + + for (j in 0 until segments) { + val lonAngleCurrent = j * angleStep + val lonAngleNext = (j + 1) * angleStep + + val xCurrentLat = radius * cos(latAngleCurrent) * cos(lonAngleCurrent) + val yCurrentLat = topCenterY + radius * sin(latAngleCurrent) + val zCurrentLat = radius * cos(latAngleCurrent) * sin(lonAngleCurrent) + + val xNextLat = radius * cos(latAngleCurrent) * cos(lonAngleNext) + val yNextLat = topCenterY + radius * sin(latAngleCurrent) + val zNextLat = radius * cos(latAngleCurrent) * sin(lonAngleNext) + + vertex(matrix, xCurrentLat, yCurrentLat, zCurrentLat).color(color).normal(0f, 1f, 0f) + vertex(matrix, xNextLat, yNextLat, zNextLat).color(color).normal(0f, 1f, 0f) + + val xCurrentLon = radius * cos(latAngleCurrent) * cos(lonAngleCurrent) + val yCurrentLon = topCenterY + radius * sin(latAngleCurrent) + val zCurrentLon = radius * cos(latAngleCurrent) * sin(lonAngleCurrent) + + val xNextLon = radius * cos(latAngleNext) * cos(lonAngleCurrent) + val yNextLon = topCenterY + radius * sin(latAngleNext) + val zNextLon = radius * cos(latAngleNext) * sin(lonAngleCurrent) + + vertex(matrix, xCurrentLon, yCurrentLon, zCurrentLon).color(color).normal(0f, 1f, 0f) + vertex(matrix, xNextLon, yNextLon, zNextLon).color(color).normal(0f, 1f, 0f) + } + } + val topPointY = topCenterY + radius + for (j in 0 until segments) { + val lonAngle = j * angleStep + val firstLatAngle = PI.toFloat() / 2 - angleStep + val xFirstLat = radius * cos(firstLatAngle) * cos(lonAngle) + val yFirstLat = topCenterY + radius * sin(firstLatAngle) + val zFirstLat = radius * cos(firstLatAngle) * sin(lonAngle) + + vertex(matrix, 0f, topPointY, 0f).color(color).normal(0f, 1f, 0f) + vertex(matrix, xFirstLat, yFirstLat, zFirstLat).color(color).normal(0f, 1f, 0f) + } + + val bottomCenterY = -halfHeight + for (i in 0 until segments / 2) { + val latAngleCurrent = -PI.toFloat() / 2 + i * angleStep + val latAngleNext = -PI.toFloat() / 2 + (i + 1) * angleStep + + for (j in 0 until segments) { + val lonAngleCurrent = j * angleStep + val lonAngleNext = (j + 1) * angleStep + + val xCurrentLat = radius * cos(latAngleCurrent) * cos(lonAngleCurrent) + val yCurrentLat = bottomCenterY + radius * sin(latAngleCurrent) + val zCurrentLat = radius * cos(latAngleCurrent) * sin(lonAngleCurrent) + + val xNextLat = radius * cos(latAngleCurrent) * cos(lonAngleNext) + val yNextLat = bottomCenterY + radius * sin(latAngleCurrent) + val zNextLat = radius * cos(latAngleCurrent) * sin(lonAngleNext) + + vertex(matrix, xCurrentLat, yCurrentLat, zCurrentLat).color(color).normal(0f, 1f, 0f) + vertex(matrix, xNextLat, yNextLat, zNextLat).color(color).normal(0f, 1f, 0f) + + val xCurrentLon = radius * cos(latAngleCurrent) * cos(lonAngleCurrent) + val yCurrentLon = bottomCenterY + radius * sin(latAngleCurrent) + val zCurrentLon = radius * cos(latAngleNext) * sin(lonAngleCurrent) + + val xNextLon = radius * cos(latAngleNext) * cos(lonAngleCurrent) + val yNextLon = bottomCenterY + radius * sin(latAngleNext) + val zNextLon = radius * cos(latAngleNext) * sin(lonAngleCurrent) + + vertex(matrix, xCurrentLon, yCurrentLon, zCurrentLon).color(color).normal(0f, 1f, 0f) + vertex(matrix, xNextLon, yNextLon, zNextLon).color(color).normal(0f, 1f, 0f) + } + } + + val bottomPointY = bottomCenterY - radius + for (j in 0 until segments) { + val lonAngle = j * angleStep + val firstLatAngle = -PI.toFloat() / 2 + angleStep + val xFirstLat = radius * cos(firstLatAngle) * cos(lonAngle) + val yFirstLat = bottomCenterY + radius * sin(firstLatAngle) + val zFirstLat = radius * cos(firstLatAngle) * sin(lonAngle) + + vertex(matrix, 0f, bottomPointY, 0f).color(color).normal(0f, 1f, 0f) + vertex(matrix, xFirstLat, yFirstLat, zFirstLat).color(color).normal(0f, 1f, 0f) + } +} \ No newline at end of file diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JmeDataUtil.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JmeDataUtil.kt new file mode 100644 index 00000000..f9923ac6 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JmeDataUtil.kt @@ -0,0 +1,84 @@ +package top.fifthlight.blazerod.util + +import com.jme3.math.Transform +import org.joml.* + +typealias JmeVector3f = com.jme3.math.Vector3f + +fun Vector3fc.get(jmeVector3f: JmeVector3f): JmeVector3f = jmeVector3f.set(x(), y(), z()) +fun Vector3f.set(jmeVector3f: JmeVector3f): Vector3f = set(jmeVector3f.x, jmeVector3f.y, jmeVector3f.z) +fun JmeVector3f.get(vector3f: Vector3f): Vector3f = vector3f.set(x, y, z) +fun JmeVector3f.set(vector3f: Vector3fc): JmeVector3f = this.set(vector3f.x(), vector3f.y(), vector3f.z()) +fun Vector3fc.toJme() = JmeVector3f(x(), y(), z()) + +typealias JmeMatrix4f = com.jme3.math.Matrix4f + +fun Matrix4fc.get(jmeMatrix4f: JmeMatrix4f) = jmeMatrix4f.apply { + m00 = m00() + m01 = m10() + m02 = m20() + m03 = m30() + m10 = m01() + m11 = m11() + m12 = m21() + m13 = m31() + m20 = m02() + m21 = m12() + m22 = m22() + m23 = m32() + m30 = m03() + m31 = m13() + m32 = m23() + m33 = m33() +} + +fun Matrix4f.set(jmeMatrix4f: JmeMatrix4f) = apply { + m00(jmeMatrix4f.m00) + m01(jmeMatrix4f.m10) + m02(jmeMatrix4f.m20) + m03(jmeMatrix4f.m30) + m10(jmeMatrix4f.m01) + m11(jmeMatrix4f.m11) + m12(jmeMatrix4f.m21) + m13(jmeMatrix4f.m31) + m20(jmeMatrix4f.m02) + m21(jmeMatrix4f.m12) + m22(jmeMatrix4f.m22) + m23(jmeMatrix4f.m32) + m30(jmeMatrix4f.m03) + m31(jmeMatrix4f.m13) + m32(jmeMatrix4f.m23) + m33(jmeMatrix4f.m33) +} + +fun JmeMatrix4f.get(matrix4f: Matrix4f) = matrix4f.set(this) +fun JmeMatrix4f.set(matrix4fc: Matrix4fc) = matrix4fc.get(this) +fun Matrix4fc.toJme() = JmeMatrix4f().set(this) + +typealias JmeMatrix3f = com.jme3.math.Matrix3f + +typealias JmeQuaternion = com.jme3.math.Quaternion + +fun Quaternionfc.get(jmeQuaternion: JmeQuaternion): JmeQuaternion = jmeQuaternion.set(x(), y(), z(), w()) +fun Quaternionf.set(jmeQuaternion: JmeQuaternion): Quaternionf = + set(jmeQuaternion.x, jmeQuaternion.y, jmeQuaternion.z, jmeQuaternion.w) + +fun JmeQuaternion.get(quaternionf: Quaternionf): Quaternionf = quaternionf.set(x, y, z, w) +fun JmeQuaternion.set(quaternionf: Quaternionfc): JmeQuaternion = + this.set(quaternionf.x(), quaternionf.y(), quaternionf.z(), quaternionf.w()) + +fun Quaternionfc.toJme() = JmeQuaternion().set(this) + +fun Matrix4f.set(transform: Transform) = transform.get(this) +fun Transform.get(matrix4f: Matrix4f) = matrix4f.translationRotateScale( + translation.x, + translation.y, + translation.z, + rotation.x, + rotation.y, + rotation.z, + rotation.w, + scale.x, + scale.y, + scale.z +) diff --git a/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JomlUtil.kt b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JomlUtil.kt new file mode 100644 index 00000000..b91ba2d8 --- /dev/null +++ b/blazerod/render/src/main/kotlin/top/fifthlight/blazerod/util/JomlUtil.kt @@ -0,0 +1,13 @@ +package top.fifthlight.blazerod.util + +import org.joml.Matrix4f +import org.joml.Matrix4fc +import org.joml.Quaternionf +import org.joml.Vector3fc + +internal fun Matrix4fc.rotateYXZ(angles: Vector3fc, dst: Matrix4f): Matrix4f = + rotateYXZ(angles.y(), angles.x(), angles.z(), dst) + +internal fun Matrix4f.rotateYXZ(angles: Vector3fc) = rotateYXZ(angles, this) + +internal fun Quaternionf.rotationZYX(angles: Vector3fc) = rotationZYX(angles.z(), angles.y(), angles.x()) diff --git a/blazerod/src/main/resources/LICENSE b/blazerod/src/main/resources/LICENSE new file mode 100644 index 00000000..6abf7d77 --- /dev/null +++ b/blazerod/src/main/resources/LICENSE @@ -0,0 +1,238 @@ +BlazeRod is licensed as GNU Lesser General Public License 3.0 or later. + +It bundles these libraries: + +- bullet physics (zlib) +- libbulletjme (BSD, zlib and MIT) + +Below are licenses for these libraries: + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +BSD 3-Clause License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Bullet Continuous Collision Detection and Physics Library +http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated + but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/mod/BUILD.bazel b/mod/BUILD.bazel index a28993e9..2bdbc96a 100644 --- a/mod/BUILD.bazel +++ b/mod/BUILD.bazel @@ -192,6 +192,8 @@ merge_jij( "//blazerod/model/model-vmd": "blazerod-model-formats-vmd:=", "//blazerod/model/model-bedrock": "blazerod-model-formats-bedrock:=", "//blazerod/render": "blazerod-render:=", + "@libbulletjme//:libbulletjme": "libbulletjme_blazerod:22.0.1+patch1", + "@libbulletjme//:libbulletjme_natives": "libbulletjme_natives_blazerod:22.0.1+patch1", }, ) @@ -233,8 +235,9 @@ java_binary( "-Ddev.launch.expandRunfileProperties=fabric.remapClasspathFile", "-Dblazerod.debug=true", "-Dblazerod.debug.gui=true", + "-Dblazerod.no_physics=false", "-Darmorstand.debug=true", - "-Darmorstand.debug.bone=false", + "-Darmorstand.debug.bone=true", "-Darmorstand.debug.gui=false", "-Xmx4G", ], @@ -258,6 +261,7 @@ java_binary( "@maven//:net_fabricmc_yarn_v2", "@maven//:org_anarres_jcpp", "@maven//:org_antlr_antlr4_runtime", + "@libbulletjme//:libbulletjme_natives", ], ) diff --git a/mod/src/client/kotlin/top/fifthlight/armorstand/PlayerRenderer.kt b/mod/src/client/kotlin/top/fifthlight/armorstand/PlayerRenderer.kt index c35a470f..a2084625 100644 --- a/mod/src/client/kotlin/top/fifthlight/armorstand/PlayerRenderer.kt +++ b/mod/src/client/kotlin/top/fifthlight/armorstand/PlayerRenderer.kt @@ -14,6 +14,7 @@ import top.fifthlight.armorstand.util.RendererManager import top.fifthlight.blazerod.model.renderer.ScheduledRenderer import top.fifthlight.blazerod.model.resource.CameraTransform import top.fifthlight.blazerod.model.resource.RenderCamera +import top.fifthlight.blazerod.util.TimeUtil import java.lang.ref.WeakReference import java.util.* @@ -68,6 +69,7 @@ object PlayerRenderer { light: Int, overlay: Int, ): Boolean { + val time = System.nanoTime().toFloat() / TimeUtil.NANOSECONDS_PER_SECOND.toFloat() val entry = ModelInstanceManager.get(uuid, System.nanoTime()) if (entry !is ModelInstanceManager.ModelInstanceItem.Model) { return false @@ -78,14 +80,14 @@ object PlayerRenderer { controller.update(uuid, vanillaState) controller.apply(instance) - instance.updateRenderData() + instance.updateRenderData(time) val backupItem = matrixStack.peek().copy() matrixStack.pop() matrixStack.push() if (ArmorStandClient.debugBone) { - instance.debugRender(matrixStack.peek().positionMatrix, consumers) + instance.debugRender(matrixStack.peek().positionMatrix, consumers, time) } else { matrix.set(matrixStack.peek().positionMatrix) matrix.scale(ConfigHolder.config.value.modelScale) diff --git a/mod/src/client/resources/assets/armorstand/lang/ru_ru.json b/mod/src/client/resources/assets/armorstand/lang/ru_ru.json index 0f55b377..4c92ca44 100644 --- a/mod/src/client/resources/assets/armorstand/lang/ru_ru.json +++ b/mod/src/client/resources/assets/armorstand/lang/ru_ru.json @@ -47,7 +47,7 @@ "armorstand.animation.unnamed_ik_node": "Без названия", "armorstand.metadata.title": "Название: %s", "armorstand.metadata.title.unknown": "Название: Неизвестно", - "armorstand.metadata.version": "Версия: %s", + "armorstand.metadata.version": "Версия: %s", "armorstand.metadata.authors": "Авторы: %s", "armorstand.metadata.copyright_information": "Авторские права: %s", diff --git a/mod/src/main/resources/LICENSE b/mod/src/main/resources/LICENSE new file mode 100644 index 00000000..1a5b7164 --- /dev/null +++ b/mod/src/main/resources/LICENSE @@ -0,0 +1,563 @@ +ArmorStand is licensed as GNU Lesser General Public License 3.0 or later. + +It bundles these libraries: + +- H2 database (MPL 2.0 or EPL 1.0, ArmorStand use MPL 2.0) +- Animation from KaiMyEntity-C (Informal license) +- Icons from Cherished Worlds (LGPL 3.0 or later) +- bullet physics (zlib) +- libbulletjme (BSD, zlib and MIT) + +Below are licenses for these libraries: + +KaiMyEntity-C uses an informal license: + +KAIMyEntity.zipに入っているファイルに関してはご自由にお使いください。 +(クレジット表記不要、改変可、再配布可) + +Mozilla Public License, version 2.0 + +1. Definitions + + 1.1. “Contributor” means each individual or legal entity that + creates, contributes to the creation of, or owns Covered Software. + + 1.2. “Contributor Version” means the combination of the + Contributions of others (if any) used by a Contributor and that + particular Contributor’s Contribution. + + 1.3. “Contribution” means Covered Software of a particular + Contributor. + + 1.4. “Covered Software” means Source Code Form to which the initial + Contributor has attached the notice in Exhibit A, the Executable + Form of such Source Code Form, and Modifications of such Source Code + Form, in each case including portions thereof. + + 1.5. “Incompatible With Secondary Licenses” means + + that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms + of a Secondary License. + + 1.6. “Executable Form” means any form of the work other than Source + Code Form. + + 1.7. “Larger Work” means a work that combines Covered Software with + other material, in a separate file or files, that is not Covered + Software. + + 1.8. “License” means this document. + + 1.9. “Licensable” means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently, any and all of the rights conveyed by this License. + + 1.10. “Modifications” means any of the following: + + any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; + or + + any new file in Source Code Form that contains any Covered Software. + + 1.11. “Patent Claims” of a Contributor means any patent claim(s), + including without limitation, method, process, and apparatus claims, + in any patent Licensable by such Contributor that would be + infringed, but for the grant of the License, by the making, using, + selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + + 1.12. “Secondary License” means either the GNU General Public + License, Version 2.0, the GNU Lesser General Public License, Version + 2.1, the GNU Affero General Public License, Version 3.0, or any + later versions of those licenses. + + 1.13. “Source Code Form” means the form of the work preferred for + making modifications. + + 1.14. “You” (or “Your”) means an individual or a legal entity + exercising rights under this License. For legal entities, “You” + includes any entity that controls, is controlled by, or is under + common control with You. For purposes of this definition, “control” + means (a) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, or (b) + ownership of more than fifty percent (50%) of the outstanding shares + or beneficial ownership of such entity. + + +2. License Grants and Conditions + + 2.1. Grants Each Contributor hereby grants You a world-wide, + royalty-free, non-exclusive license: + + under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + + 2.2. Effective Date The licenses granted in Section 2.1 with respect + to any Contribution become effective for each Contribution on the + date the Contributor first distributes such Contribution. + + 2.3. Limitations on Grant Scope The licenses granted in this Section + 2 are the only rights granted under this License. No additional + rights or licenses will be implied from the distribution or + licensing of Covered Software under this License. Notwithstanding + Section 2.1(b) above, no patent license is granted by a Contributor: + + for any code that a Contributor has removed from Covered Software; + or + + for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service + marks, or logos of any Contributor (except as may be necessary to + comply with the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses No Contributor makes additional grants as a + result of Your choice to distribute the Covered Software under a + subsequent version of this License (see Section 10.2) or under the + terms of a Secondary License (if permitted under the terms of + Section 3.3). + + 2.5. Representation Each Contributor represents that the Contributor + believes its Contributions are its original creation(s) or it has + sufficient rights to grant the rights to its Contributions conveyed + by this License. + + 2.6. Fair Use This License is not intended to limit any rights You + have under applicable copyright doctrines of fair use, fair dealing, + or other equivalents. + + 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of + the licenses granted in Section 2.1. + + +3. Responsibilities + + 3.1. Distribution of Source Form All distribution of Covered + Software in Source Code Form, including any Modifications that You + create or to which You contribute, must be under the terms of this + License. You must inform recipients that the Source Code Form of the + Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter + or restrict the recipients’ rights in the Source Code Form. + + 3.2. Distribution of Executable Form If You distribute Covered + Software in Executable Form then: + + such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + + You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients’ rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work You may create and distribute a + Larger Work under terms of Your choice, provided that You also + comply with the requirements of this License for the Covered + Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient + of the Larger Work may, at their option, further distribute the + Covered Software under the terms of either this License or such + Secondary License(s). + + 3.4. Notices You may not remove or alter the substance of any + license notices (including copyright notices, patent notices, + disclaimers of warranty, or limitations of liability) contained + within the Source Code Form of the Covered Software, except that You + may alter any license notices to the extent required to remedy known + factual inaccuracies. + + 3.5. Application of Additional Terms You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Software. However, + You may do so only on Your own behalf, and not on behalf of any + Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for + any liability incurred by such Contributor as a result of warranty, + support, indemnity or liability terms You offer. You may include + additional disclaimers of warranty and limitations of liability + specific to any jurisdiction. + + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply + with the terms of this License to the maximum extent possible; and + (b) describe the limitations and the code they affect. Such + description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to + the extent prohibited by statute or regulation, such description + must be sufficiently detailed for a recipient of ordinary skill to + be able to understand it. + + +5. Termination + + 5.1. The rights granted under this License will terminate + automatically if You fail to comply with any of its terms. However, + if You become compliant, then the rights granted under this License + from a particular Contributor are reinstated (a) provisionally, + unless and until such Contributor explicitly and finally terminates + Your grants, and (b) on an ongoing basis, if such Contributor fails + to notify You of the non-compliance by some reasonable means prior + to 60 days after You have come back into compliance. Moreover, Your + grants from a particular Contributor are reinstated on an ongoing + basis if such Contributor notifies You of the non-compliance by some + reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You + become compliant prior to 30 days after Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a + patent infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor + Version directly or indirectly infringes any patent, then the rights + granted to You by any and all Contributors for the Covered Software + under Section 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, + all end user license agreements (excluding distributors and + resellers) which have been validly granted by You or Your + distributors under this License prior to termination shall survive + termination. + +6. Disclaimer of Warranty Covered Software is provided under this +License on an “as is” basis, without warranty of any kind, either +expressed, implied, or statutory, including, without limitation, +warranties that the Covered Software is free of defects, merchantable, +fit for a particular purpose or non-infringing. The entire risk as to +the quality and performance of the Covered Software is with You. +Should any Covered Software prove defective in any respect, You (not +any Contributor) assume the cost of any necessary servicing, repair, +or correction. This disclaimer of warranty constitutes an essential +part of this License. No use of any Covered Software is authorized +under this License except under this disclaimer. + +7. Limitation of Liability Under no circumstances and under no legal +theory, whether tort (including negligence), contract, or otherwise, +shall any Contributor, or anyone who distributes Covered Software as +permitted above, be liable to You for any direct, indirect, special, +incidental, or consequential damages of any character including, +without limitation, damages for lost profits, loss of goodwill, work +stoppage, computer failure or malfunction, or any and all other +commercial damages or losses, even if such party shall have been +informed of the possibility of such damages. This limitation of +liability shall not apply to liability for death or personal injury +resulting from such party’s negligence to the extent applicable law +prohibits such limitation. Some jurisdictions do not allow the +exclusion or limitation of incidental or consequential damages, so +this exclusion and limitation may not apply to You. + +8. Litigation Any litigation relating to this License may be brought +only in the courts of a jurisdiction where the defendant maintains its +principal place of business and such litigation shall be governed by +laws of that jurisdiction, without reference to its conflict-of-law +provisions. Nothing in this Section shall prevent a party’s ability to +bring cross-claims or counter-claims. + +9. Miscellaneous This License represents the complete agreement +concerning the subject matter hereof. If any provision of this License +is held to be unenforceable, such provision shall be reformed only to +the extent necessary to make it enforceable. Any law or regulation +which provides that the language of a contract shall be construed +against the drafter shall not be used to construe this License against +a Contributor. + +10. Versions of the License + + 10.1. New Versions Mozilla Foundation is the license steward. Except + as provided in Section 10.3, no one other than the license steward + has the right to modify or publish new versions of this License. + Each version will be given a distinguishing version number. + + 10.2. Effect of New Versions You may distribute the Covered Software + under the terms of the version of the License under which You + originally received the Covered Software, or under the terms of any + subsequent version published by the license steward. + + 10.3. Modified Versions If you create software not governed by this + License, and you want to create a new license for such software, you + may create and use a modified version of this License if you rename + the license and remove any references to the name of the license + steward (except to note that such modified license differs from this + License). + + 10.4. Distributing Source Code Form that is Incompatible With + Secondary Licenses If You choose to distribute Source Code Form that + is Incompatible With Secondary Licenses under the terms of this + version of the License, the notice described in Exhibit B of this + License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a + LICENSE file in a relevant directory) where a recipient would be + likely to look for such a notice. + + You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible With Secondary Licenses”, as + defined by the Mozilla Public License, v. 2.0. + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + +BSD 3-Clause License + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Bullet Continuous Collision Detection and Physics Library +http://bulletphysics.org + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not claim + that you wrote the original software. If you use this software in a product, + an acknowledgment in the product documentation would be appreciated + but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/rule/extract_jar.bzl b/rule/extract_jar.bzl index eb92cf09..53103183 100644 --- a/rule/extract_jar.bzl +++ b/rule/extract_jar.bzl @@ -22,7 +22,7 @@ extract_jar = rule( implementation = _extract_jar_impl, attrs = { "input": attr.label( - allow_single_file = [".jar", ".zip"], + allow_single_file = [".jar", ".zip", ".aar"], mandatory = True, doc = "Input JAR file", ), diff --git a/third_party/libbulletjme/BUILD.bazel b/third_party/libbulletjme/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/third_party/libbulletjme/BUILD.libbulletjme.bazel b/third_party/libbulletjme/BUILD.libbulletjme.bazel new file mode 100644 index 00000000..4f789af1 --- /dev/null +++ b/third_party/libbulletjme/BUILD.libbulletjme.bazel @@ -0,0 +1,38 @@ +load("@rules_java//java:defs.bzl", "java_library") +load("@rules_jni//jni:defs.bzl", "cc_jni_library") + +java_library( + name = "libbulletjme", + visibility = ["//visibility:public"], + srcs = glob(["src/main/java/**/*.java"]), +) + +_workspace_root = package_relative_label("@libbulletjme//:__pkg__").workspace_root + +cc_jni_library( + name = "libbulletjme_natives", + visibility = ["//visibility:public"], + srcs = glob(["src/main/native/**/*.cpp", "src/main/native/**/*.h", "src/main/native/**/*.inl"]), + platforms = [ + "@//:android_arm32", + "@//:android_arm64", + "@//:android_x86", + "@//:android_x86_64", + "@//:linux_arm64_glibc", + "@//:linux_x86_64_glibc", + "@//:windows_arm64", + "@//:windows_x86_64", + ], + copts = [ + "-w", + "-I" + _workspace_root + "/src/main/native/bullet3", + "-I" + _workspace_root + "/src/main/native/bullet3/BulletDynamics/Featherstone", + "-I" + _workspace_root + "/src/main/native/bullet3/LinearMath", + "-I" + _workspace_root + "/src/main/native/v-hacd/inc", + "-I" + _workspace_root + "/src/main/native/v-hacd/public", + ], + linkopts = select({ + "@platforms//os:windows": ["-lws2_32"], + "//conditions:default": [], + }), +) diff --git a/third_party/libbulletjme/patches/BUILD.bazel b/third_party/libbulletjme/patches/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/third_party/libbulletjme/patches/add-ground-object.patch b/third_party/libbulletjme/patches/add-ground-object.patch new file mode 100644 index 00000000..ce43c809 --- /dev/null +++ b/third_party/libbulletjme/patches/add-ground-object.patch @@ -0,0 +1,162 @@ +--- a/src/main/native/glue/jmeCollisionSpace.h ++++ b/src/main/native/glue/jmeCollisionSpace.h +@@ -42,7 +42,10 @@ + #include "BulletCollision/Gimpact/btGImpactCollisionAlgorithm.h" + #include "LinearMath/btThreads.h" + ++class jmeCollisionSpace; ++ + struct jmeFilterCallback : public btOverlapFilterCallback { ++ jmeCollisionSpace *m_pCollisionSpace; + bool needBroadphaseCollision(btBroadphaseProxy*, btBroadphaseProxy*) const; + }; + +@@ -62,6 +65,10 @@ + * exclusive access to the JavaVM and JNIEnv during parallel for loops: + */ + btSpinMutex m_mutex; ++ /* ++ * A object that will collide with any object: ++ */ ++ const btCollisionObject *m_groundObject; + + void attachThread(); + btBroadphaseInterface * createBroadphase(const btVector3 & min, +@@ -109,6 +116,16 @@ + getJavaPhysicsSpace() { + return m_javaSpace; + } ++ ++ void ++ setGroundObject(const btCollisionObject * groundObject) { ++ m_groundObject = groundObject; ++ } ++ ++ const btCollisionObject * ++ getGroundObject() { ++ return m_groundObject; ++ } + }; + + #endif + +--- a/src/main/native/glue/jmeCollisionSpace.cpp ++++ b/src/main/native/glue/jmeCollisionSpace.cpp +@@ -43,6 +43,17 @@ + */ + bool jmeFilterCallback::needBroadphaseCollision(btBroadphaseProxy *pProxy0, + btBroadphaseProxy *pProxy1) const { ++ if (m_pCollisionSpace != NULL) { ++ const btCollisionObject *groundObject = m_pCollisionSpace->getGroundObject(); ++ if (groundObject != NULL) { ++ if (pProxy0->m_clientObject == groundObject) { ++ return true; ++ } ++ if (pProxy1->m_clientObject == groundObject) { ++ return true; ++ } ++ } ++ } + /* + * Test the Bullet collision-filter groups. + */ +@@ -107,6 +118,8 @@ + + m_javaSpace = pEnv->NewWeakGlobalRef(javaSpace); // TODO leak + EXCEPTION_CHK(pEnv,); ++ ++ m_groundObject = NULL; + } + + void jmeCollisionSpace::attachThread() { +@@ -144,7 +157,9 @@ + btOverlappingPairCache * const + pPairCache = pBroadphase->getOverlappingPairCache(); + pPairCache->setInternalGhostPairCallback(new btGhostPairCallback()); //dance036 +- pPairCache->setOverlapFilterCallback(new jmeFilterCallback()); //dance011 ++ jmeFilterCallback* pCallback = new jmeFilterCallback(); ++ pCallback->m_pCollisionSpace = this; ++ pPairCache->setOverlapFilterCallback(pCallback); //dance011 + + return pBroadphase; + } + +--- a/src/main/java/com/jme3/bullet/CollisionSpace.java ++++ b/src/main/java/com/jme3/bullet/CollisionSpace.java +@@ -226,6 +226,23 @@ + } + + /** ++ * Set the specified collision object as the ground object. ++ * Ground object will collide with any object. ++ * You can only have one ground object in the same time. If you already ++ * has a ground object, calling this method will override the ground ++ * object set previously. ++ * ++ * @param pco the ground object to set ++ */ ++ public void setGroundObject(PhysicsCollisionObject pco) { ++ Validate.nonNull(pco, "collision object"); ++ ++ long spaceId = nativeId(); ++ long nativeId = pco.nativeId(); ++ setGroundObject(spaceId, nativeId); ++ } ++ ++ /** + * Perform a contact test. This will not detect contacts with soft bodies. + * + * @param pco the collision object to test (not null, unaffected) +@@ -833,6 +850,8 @@ + // native private methods + + native private static void addCollisionObject(long spaceId, long pcoId); ++ ++ native private static void setGroundObject(long spaceId, long pcoId); + + native private static int contactTest( + long spaceId, long pcoId, PhysicsCollisionListener listener); + +--- a/src/main/native/glue/com_jme3_bullet_CollisionSpace.cpp ++++ b/src/main/native/glue/com_jme3_bullet_CollisionSpace.cpp +@@ -66,6 +66,22 @@ + } + + /* ++ * Class: com_jme3_bullet_CollisionSpace ++ * Method: setGroundObject ++ * Signature: (JJ)V ++ */ ++JNIEXPORT void JNICALL Java_com_jme3_bullet_CollisionSpace_setGroundObject ++(JNIEnv *pEnv, jclass, jlong spaceId, jlong pcoId) { ++ jmeCollisionSpace * const ++ pSpace = reinterpret_cast (spaceId); ++ NULL_CHK(pEnv, pSpace, "The collision space does not exist.",) ++ btCollisionObject * const ++ pCollisionObject = reinterpret_cast (pcoId); ++ NULL_CHK(pEnv, pCollisionObject, "The collision object does not exist.",); ++ pSpace->setGroundObject(pCollisionObject); ++} ++ ++/* + * Callback used in contact tests. + */ + struct JmeContactResultCallback + +--- a/src/main/native/glue/com_jme3_bullet_CollisionSpace.h ++++ b/src/main/native/glue/com_jme3_bullet_CollisionSpace.h +@@ -17,6 +17,14 @@ + + /* + * Class: com_jme3_bullet_CollisionSpace ++ * Method: setGroundObject ++ * Signature: (JJ)V ++ */ ++JNIEXPORT void JNICALL Java_com_jme3_bullet_CollisionSpace_setGroundObject ++ (JNIEnv *, jclass, jlong, jlong); ++ ++/* ++ * Class: com_jme3_bullet_CollisionSpace + * Method: contactTest + * Signature: (JJLcom/jme3/bullet/collision/PhysicsCollisionListener;)I + */ diff --git a/third_party/libbulletjme/patches/remove-collision-group-one-bit-assert.patch b/third_party/libbulletjme/patches/remove-collision-group-one-bit-assert.patch new file mode 100644 index 00000000..96a93786 --- /dev/null +++ b/third_party/libbulletjme/patches/remove-collision-group-one-bit-assert.patch @@ -0,0 +1,17 @@ +--- a/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java ++++ b/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java +@@ -452,5 +452,4 @@ + public int getCollisionGroup() { + assert collisionGroup == getCollisionGroup(nativeId()); +- assert Integer.bitCount(collisionGroup) == 1 : collisionGroup; + return collisionGroup; + } +@@ -1023,8 +1023,6 @@ + public void setCollisionGroup(int collisionGroup) { +- Validate.require( +- Integer.bitCount(collisionGroup) == 1, "exactly one bit set"); + + this.collisionGroup = collisionGroup; + long objectId = nativeId(); + setCollisionGroup(objectId, collisionGroup); + } diff --git a/third_party/libbulletjme/patches/vhacd-mutex.patch b/third_party/libbulletjme/patches/vhacd-mutex.patch new file mode 100644 index 00000000..399de28a --- /dev/null +++ b/third_party/libbulletjme/patches/vhacd-mutex.patch @@ -0,0 +1,13 @@ +# patch from https://github.com/godotengine/godot/issues/31555 +--- a/src/main/native/v-hacd/inc/vhacdMutex.h ++++ b/src/main/native/v-hacd/inc/vhacdMutex.h +@@ -71,7 +71,8 @@ + #include + #endif + +-#if defined(__APPLE__) ++// PTHREAD_MUTEX_RECURSIVE_NP isn't available in musl ++#if defined(__APPLE__) || !defined(__GLIBC__) + #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE + #endif + diff --git a/third_party/libbulletjme/patches/winsock2-case.patch b/third_party/libbulletjme/patches/winsock2-case.patch new file mode 100644 index 00000000..a6d1b32d --- /dev/null +++ b/third_party/libbulletjme/patches/winsock2-case.patch @@ -0,0 +1,6 @@ +# To fix compiling on Linux +--- a/src/main/native/glue/com_jme3_bullet_collision_shapes_infos_BoundingValueHierarchy.cpp ++++ b/src/main/native/glue/com_jme3_bullet_collision_shapes_infos_BoundingValueHierarchy.cpp +@@ -38,1 +38,1 @@ +-#include // for htons() ++#include // for htons() \ No newline at end of file diff --git a/third_party/rules_jni/patches/BUILD.bazel b/third_party/rules_jni/patches/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/third_party/rules_jni/patches/armv7-cpu.patch b/third_party/rules_jni/patches/armv7-cpu.patch new file mode 100644 index 00000000..27dd7c2b --- /dev/null +++ b/third_party/rules_jni/patches/armv7-cpu.patch @@ -0,0 +1,5 @@ +--- a/jni/private/os_cpu_utils.bzl ++++ b/jni/private/os_cpu_utils.bzl +@@ -19,1 +19,2 @@ + "arm", ++ "armv7",