From ad6a85fe5fca5562698f4ae308ffbc7db081f71c Mon Sep 17 00:00:00 2001 From: Loki McKay Date: Tue, 28 May 2024 03:18:36 +1000 Subject: [PATCH] Add cross-platform determinism (#2) --- .vscode/settings.json | 3 + CONTRIBUTING.md | 17 +- README.md | 15 +- addons/godot-rapier-3d/Rapier3D.gd | 7 +- addons/godot-rapier-3d/Rapier3DDebugger.gd | 7 +- addons/godot-rapier-3d/debug_info.gd | 5 + .../gdscript/physics_controls.gd | 5 +- .../godot-rapier-3d/gdscript/physics_state.gd | 2 +- .../gdscript/project_settings.gd | 7 +- addons/godot-rapier-3d/rust/Cargo.lock | 233 +++++++++++-- addons/godot-rapier-3d/rust/Cargo.toml | 9 +- addons/godot-rapier-3d/rust/src/collider.rs | 322 ------------------ .../rust/src/editor_plugin/autoloads.rs | 24 +- .../rust/src/editor_plugin/gizmos.rs | 112 +++--- .../rust/src/editor_plugin/mod.rs | 8 +- addons/godot-rapier-3d/rust/src/engine.rs | 141 ++++---- addons/godot-rapier-3d/rust/src/lib.rs | 17 +- addons/godot-rapier-3d/rust/src/log.rs | 142 -------- addons/godot-rapier-3d/rust/src/lookups.rs | 114 +++++++ .../rust/src/lookups/id_bridge.rs | 59 ++++ addons/godot-rapier-3d/rust/src/objects.rs | 192 +++++++++++ .../rust/src/objects/collider.rs | 222 ++++++++++++ .../rust/src/objects/handle.rs | 88 +++++ .../rust/src/objects/object_bridge.rs | 99 ++++++ .../rust/src/objects/rigid_body.rs | 122 +++++++ .../rust/src/physics_pipeline.rs | 209 ------------ addons/godot-rapier-3d/rust/src/pipeline.rs | 165 +++++++++ .../{ => pipeline}/debug_render_pipeline.rs | 31 +- .../{physics_state.rs => pipeline/state.rs} | 30 +- addons/godot-rapier-3d/rust/src/queue.rs | 237 +++++++++++++ .../godot-rapier-3d/rust/src/queue/action.rs | 51 +++ .../rust/src/queue/actionable.rs | 154 +++++++++ .../godot-rapier-3d/rust/src/queue/process.rs | 141 ++++++++ .../rust/src/queue/process/add_or_remove.rs | 122 +++++++ .../rust/src/queue/process/sim.rs | 19 ++ .../rust/src/queue/process/sync.rs | 33 ++ addons/godot-rapier-3d/rust/src/rigid_body.rs | 210 ------------ addons/godot-rapier-3d/rust/src/utils.rs | 69 ++-- addons/godot-rapier-3d/rust/src/utils/id.rs | 32 ++ .../godot-rapier-3d/rust/src/utils/logger.rs | 66 ++++ demos/colliders.tscn | 9 + project.godot | 4 + tests/determinism.tscn | 27 ++ tests/sim_test.gd | 6 +- 44 files changed, 2403 insertions(+), 1184 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 addons/godot-rapier-3d/debug_info.gd delete mode 100644 addons/godot-rapier-3d/rust/src/collider.rs delete mode 100644 addons/godot-rapier-3d/rust/src/log.rs create mode 100644 addons/godot-rapier-3d/rust/src/lookups.rs create mode 100644 addons/godot-rapier-3d/rust/src/lookups/id_bridge.rs create mode 100644 addons/godot-rapier-3d/rust/src/objects.rs create mode 100644 addons/godot-rapier-3d/rust/src/objects/collider.rs create mode 100644 addons/godot-rapier-3d/rust/src/objects/handle.rs create mode 100644 addons/godot-rapier-3d/rust/src/objects/object_bridge.rs create mode 100644 addons/godot-rapier-3d/rust/src/objects/rigid_body.rs delete mode 100644 addons/godot-rapier-3d/rust/src/physics_pipeline.rs create mode 100644 addons/godot-rapier-3d/rust/src/pipeline.rs rename addons/godot-rapier-3d/rust/src/{ => pipeline}/debug_render_pipeline.rs (76%) rename addons/godot-rapier-3d/rust/src/{physics_state.rs => pipeline/state.rs} (55%) create mode 100644 addons/godot-rapier-3d/rust/src/queue.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/action.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/actionable.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/process.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/process/add_or_remove.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/process/sim.rs create mode 100644 addons/godot-rapier-3d/rust/src/queue/process/sync.rs delete mode 100644 addons/godot-rapier-3d/rust/src/rigid_body.rs create mode 100644 addons/godot-rapier-3d/rust/src/utils/id.rs create mode 100644 addons/godot-rapier-3d/rust/src/utils/logger.rs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b3cffb2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": ["./addons/godot-rapier-3d/rust/Cargo.toml"] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5b2716..9d7eb69 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,7 @@ - Follow [semver](https://semver.org/) when releasing new versions - If functionality doesn't need to directly interact with Rapier and doesn't need to be optimized, prefer writing it in GDScript +- Any mutations to the Rapier pipeline need to be fed through the action queue in order to ensure determinism ## Development quickstart @@ -25,22 +26,6 @@ Please raise an issue and provide reproducible steps or a minimal reproduction project, which is a small Godot project which reproduces the issue, with no unnecessary files included. -## Known issues - -- Using any of the logging macros (`crate::error!` etc.) grabs a mutable reference to the engine singleton. If you already have done this in the same function, Godot will crash. You instead need to pass the engine bind: - - ```rust - fn my_func() { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - // ...stuff... - // crate::error!("This will crash Godot"); // INCORRECT - crate::error!(bind; "This is fine"); - } - ``` - - the `get_engine!` macro should be upgraded to prevent such double access cases - ## Roadmap - [x] Visualize colliders diff --git a/README.md b/README.md index 891178d..34ff949 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ It is _not_ a drop-in replacement for the Godot physics engine. Rapier nodes ope ### Features -- Cross platform determinism (TBC - needs testing) +- Cross platform determinism (confirmed via [actions](https://github.com/deltasiege/godot-rapier-3d/actions/workflows/build-and-test.yml)!) - Physics state manual stepping - Physics state saving & loading @@ -54,9 +54,6 @@ Obtain a [hash](https://docs.godotengine.org/en/stable/classes/class_array.html# var initial_snapshot func _ready(): - Rapier3D.physics_ready.connect(_on_physics_ready) - -func _on_physics_ready(): initial_snapshot = Rapier3D.get_state() var hash = Rapier3D.get_hash(initial_snapshot) @@ -64,12 +61,6 @@ func _on_foo(): Rapier3D.set_state(initial_snapshot) ``` -### Why `_on_physics_ready`? - -Colliders need 1 extra frame to attach to physics bodies. Therefore it's recommended that you don't use `Rapier3D.get_state()` within a `_ready()` function because colliders will not be attached yet. - -Instead, you should connect to the `Rapier3D.physics_ready` signal as shown above. - ## Why does this exist? Currently Godot does not support [on-demand physics simulation](https://github.com/godotengine/godot-proposals/issues/2821), does not have [built-in snapshotting](https://github.com/godotengine/godot-proposals/issues/7041), and is also not [deterministic](https://gafferongames.com/post/deterministic_lockstep). @@ -82,10 +73,6 @@ Luckily, Godot 4 provides a great [extension system][gdext-link] and [Rapier][ra - No mobile support ([godot-rust](https://github.com/godot-rust/gdext/issues/24)) -## Known issues - -- `Project -> Reload current project` can cause the engine to not load properly - errors will print to the godot console. These errors can be safely ignored. To eliminate them, close godot entirely and reopen your project from the project manager. - ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/addons/godot-rapier-3d/Rapier3D.gd b/addons/godot-rapier-3d/Rapier3D.gd index 59b6beb..5222af4 100644 --- a/addons/godot-rapier-3d/Rapier3D.gd +++ b/addons/godot-rapier-3d/Rapier3D.gd @@ -1,13 +1,14 @@ +@tool extends Node3D const utils = preload("res://addons/godot-rapier-3d/gdscript/utils.gd") const physics_state = preload("res://addons/godot-rapier-3d/gdscript/physics_state.gd") -signal physics_ready +func _ready(): + Rapier3DEngine._process() func _physics_process(_delta): - if Engine.get_physics_frames() != 1: return # Need to wait a few frames for colliders to properly mount in the tree - physics_ready.emit() + Rapier3DEngine._process() func step() -> void: Rapier3DEngine.step() diff --git a/addons/godot-rapier-3d/Rapier3DDebugger.gd b/addons/godot-rapier-3d/Rapier3DDebugger.gd index 81ed1fe..6bd5d53 100644 --- a/addons/godot-rapier-3d/Rapier3DDebugger.gd +++ b/addons/godot-rapier-3d/Rapier3DDebugger.gd @@ -69,4 +69,9 @@ func _draw_line(a, b, color): func _should_run(): if Engine.is_editor_hint(): return run_in_editor - else: return run_in_game + else: + var current_scene = get_tree().current_scene + var scene_path = current_scene.scene_file_path + var is_test_scene = scene_path.contains("res://tests/") + if is_test_scene: return false + return run_in_game diff --git a/addons/godot-rapier-3d/debug_info.gd b/addons/godot-rapier-3d/debug_info.gd new file mode 100644 index 0000000..694b015 --- /dev/null +++ b/addons/godot-rapier-3d/debug_info.gd @@ -0,0 +1,5 @@ +@tool +extends EditorScript + +func _run(): + Rapier3DEngine.print_debug_info() diff --git a/addons/godot-rapier-3d/gdscript/physics_controls.gd b/addons/godot-rapier-3d/gdscript/physics_controls.gd index bf4fdbc..e064fdb 100644 --- a/addons/godot-rapier-3d/gdscript/physics_controls.gd +++ b/addons/godot-rapier-3d/gdscript/physics_controls.gd @@ -15,14 +15,11 @@ var godot_hash func _ready(): _update_should_show() if !should_show: return - Rapier3D.physics_ready.connect(_on_physics_ready) + initial_snapshot = _save() if !Engine.is_editor_hint(): play_button.set_pressed(true) play = true -func _on_physics_ready(): - initial_snapshot = _save() - func _physics_process(_delta): if !should_show: return if play: Rapier3D.step() diff --git a/addons/godot-rapier-3d/gdscript/physics_state.gd b/addons/godot-rapier-3d/gdscript/physics_state.gd index c51e5e8..420fa76 100644 --- a/addons/godot-rapier-3d/gdscript/physics_state.gd +++ b/addons/godot-rapier-3d/gdscript/physics_state.gd @@ -1,6 +1,6 @@ const utils = preload("res://addons/godot-rapier-3d/gdscript/utils.gd") -static func _get_physics_state(root: Node3D): +static func _get_physics_state(root: Node): var state = PackedByteArray() var physics_objects = _get_all_physics_objects(root) var sorted = _sort_by_iid(physics_objects) diff --git a/addons/godot-rapier-3d/gdscript/project_settings.gd b/addons/godot-rapier-3d/gdscript/project_settings.gd index 38640a8..4bf6de0 100644 --- a/addons/godot-rapier-3d/gdscript/project_settings.gd +++ b/addons/godot-rapier-3d/gdscript/project_settings.gd @@ -3,7 +3,7 @@ extends Object var _property_order: int = 1000 var _project_settings = [ - { "name": "debug/rapier_3d/logging_level", "type": TYPE_INT, "default": 2, "hint": PROPERTY_HINT_ENUM, "hint_string": "Error,Warning,Info,Debug" }, + { "name": "debug/rapier_3d/logging_level", "type": TYPE_STRING, "default": "Info", "hint": PROPERTY_HINT_ENUM, "hint_string": "Off,Error,Warning,Info,Debug,Trace" }, { "name": "debug/rapier_3d/debug_in_game", "type": TYPE_BOOL, "default": true }, { "name": "debug/rapier_3d/debug_in_editor", "type": TYPE_BOOL, "default": true }, { "name": "debug/rapier_3d/show_colliders", "type": TYPE_BOOL, "default": true }, @@ -11,7 +11,7 @@ var _project_settings = [ { "name": "physics/rapier_3d/gravity_vector", "type": TYPE_VECTOR3, "default": Vector3(0, -9.8, 0) }, ] -func _add_project_setting(name: String, type: int, default, hint = null, hint_string = null) -> void: +func _add_project_setting(name: String, type: int, default, hint = null, hint_string = null, restart_if_changed = false) -> void: if not ProjectSettings.has_setting(name): ProjectSettings.set_setting(name, default) ProjectSettings.set_initial_value(name, default) ProjectSettings.set_order(name, _property_order) @@ -20,13 +20,14 @@ func _add_project_setting(name: String, type: int, default, hint = null, hint_st if hint != null: info['hint'] = hint if hint_string != null: info['hint_string'] = hint_string ProjectSettings.add_property_info(info) + ProjectSettings.set_restart_if_changed(name, restart_if_changed) func _remove_project_setting(name: String) -> void: if ProjectSettings.has_setting(name): ProjectSettings.set_setting(name, null) func add_project_settings() -> void: for setting in _project_settings: - _add_project_setting(setting.name, setting.type, setting.default, setting.get("hint", null), setting.get("hint_string", null)) + _add_project_setting(setting.name, setting.type, setting.default, setting.get("hint", null), setting.get("hint_string", null), setting.get("restart_if_changed", false)) func remove_project_settings() -> void: for setting in _project_settings: diff --git a/addons/godot-rapier-3d/rust/Cargo.lock b/addons/godot-rapier-3d/rust/Cargo.lock index f5da112..27a827e 100644 --- a/addons/godot-rapier-3d/rust/Cargo.lock +++ b/addons/godot-rapier-3d/rust/Cargo.lock @@ -74,11 +74,20 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "cfg-if" @@ -86,6 +95,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam" version = "0.8.4" @@ -101,9 +119,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] @@ -138,9 +156,47 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cuid-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea2bfe0336ff1b7ca74819b2df8dfae9afea358aff6b1688baa5c181d8c3713" + +[[package]] +name = "cuid2" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "47d99cacd52fd67db7490ad051c8c1973fb75520174d69aabbae08c534c9d0e8" +dependencies = [ + "cuid-util", + "num", + "rand", + "sha3", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] name = "downcast-rs" @@ -150,9 +206,19 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + +[[package]] +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] [[package]] name = "gensym" @@ -162,7 +228,7 @@ checksum = "913dce4c5f06c2ea40fc178c06f777ac89fc6b1383e90c254fafb1abe4ba3c82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", "uuid", ] @@ -186,7 +252,7 @@ checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" [[package]] name = "godot" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "godot-core", "godot-macros", @@ -195,7 +261,7 @@ dependencies = [ [[package]] name = "godot-bindings" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "godot4-prebuilt", ] @@ -203,12 +269,12 @@ dependencies = [ [[package]] name = "godot-cell" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" [[package]] name = "godot-codegen" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "godot-bindings", "godot-fmt", @@ -222,7 +288,7 @@ dependencies = [ [[package]] name = "godot-core" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "glam", "godot-bindings", @@ -234,7 +300,7 @@ dependencies = [ [[package]] name = "godot-ffi" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "gensym", "godot-bindings", @@ -246,7 +312,7 @@ dependencies = [ [[package]] name = "godot-fmt" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#40179a04709ca05d24ad63a3afce1ac23c893e71" dependencies = [ "proc-macro2", ] @@ -254,7 +320,7 @@ dependencies = [ [[package]] name = "godot-macros" version = "0.1.0" -source = "git+https://github.com/godot-rust/gdext?branch=master#e43fdd391af2bd2433fe04bfc707342382351bdb" +source = "git+https://github.com/godot-rust/gdext?branch=master#1e9ea8022beb4adb591bae71aa5ab67c595090bc" dependencies = [ "godot-bindings", "proc-macro2", @@ -267,7 +333,9 @@ name = "godot-rapier-3d" version = "0.1.0" dependencies = [ "bincode", + "cuid2", "godot", + "log", "rapier3d", "serde", ] @@ -310,11 +378,20 @@ dependencies = [ "serde", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "libc" -version = "0.2.154" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" @@ -322,6 +399,12 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + [[package]] name = "matrixmultiply" version = "0.3.8" @@ -381,6 +464,30 @@ version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e943b2c21337b7e3ec6678500687cdc741b7639ad457f234693352075c082204" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -399,7 +506,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] [[package]] @@ -411,12 +518,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ + "num-bigint", "num-integer", "num-traits", ] @@ -466,11 +585,17 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -484,6 +609,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rapier3d" version = "0.18.0" @@ -563,22 +718,32 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.201" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", ] [[package]] @@ -612,9 +777,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spade" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61addf9117b11d1f5b4bf6fe94242ba25f59d2d4b2080544b771bd647024fd00" +checksum = "5b20a809169ae442497e41a997fc5f14e2eea04e6ac590816a910d5d8068c8c0" dependencies = [ "hashbrown 0.14.5", "num-traits", @@ -635,9 +800,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -689,9 +854,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wide" -version = "0.7.17" +version = "0.7.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0e39d2c603fdc0504b12b458cf1f34e0b937ed2f4f2dc20796e3e86f34e11f" +checksum = "cd8dc749a1b03f3c255a3064a4f5c0ee5ed09b7c6bc6d4525d31f779cd74d7fc" dependencies = [ "bytemuck", "safe_arch", @@ -714,5 +879,5 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.66", ] diff --git a/addons/godot-rapier-3d/rust/Cargo.toml b/addons/godot-rapier-3d/rust/Cargo.toml index 960aa6a..bb49aab 100644 --- a/addons/godot-rapier-3d/rust/Cargo.toml +++ b/addons/godot-rapier-3d/rust/Cargo.toml @@ -8,9 +8,16 @@ crate-type = ["cdylib"] # Compile this crate to a dynamic C library. [dependencies] bincode = "1.3.3" -godot = { git = "https://github.com/godot-rust/gdext", branch = "master" } +cuid2 = "0.1.2" +godot = { git = "https://github.com/godot-rust/gdext", branch = "master" } # godot-fmt needs lock to 40179a04709ca05d24ad63a3afce1ac23c893e71 rev because of GodotClass issue - if build works after updating, can remove https://discord.com/channels/723850269347283004/1242473322374234294/1242473322374234294 +log = { version = "0.4.21", features = ["std"] } rapier3d = { version = "0.18.0", features = [ "enhanced-determinism", "serde-serialize", "debug-render" ] } serde = { version = "1.0.201", features = ["derive"] } +# Rapier optimizations +# https://rapier.rs/docs/user_guides/rust/common_mistakes [profile.dev.package.rapier3d] opt-level = 3 + +[profile.release] +codegen-units = 1 \ No newline at end of file diff --git a/addons/godot-rapier-3d/rust/src/collider.rs b/addons/godot-rapier-3d/rust/src/collider.rs deleted file mode 100644 index 4449043..0000000 --- a/addons/godot-rapier-3d/rust/src/collider.rs +++ /dev/null @@ -1,322 +0,0 @@ -use crate::physics_pipeline::GR3DPhysicsPipeline; -use crate::rigid_body::RapierRigidBody3D; -use godot::engine::notify::Node3DNotification; -use godot::engine::GDExtensionManager; -use godot::engine::INode3D; -use godot::engine::Node3D; -use godot::prelude::*; -use rapier3d::prelude::*; - -#[derive(GodotClass)] -#[class(base = Node3D)] -pub struct RapierCollider3D { - #[var] - pub id: Array, // ColliderHandle::into_raw_parts - pub handle: ColliderHandle, - pub parent: Option, - - #[export] - #[var(get, set = set_shape)] - pub shape: ShapeType, - - #[export] - #[var(get, set = set_restitution)] - pub restitution: f32, - - #[export] - #[var(get, set = set_friction)] - pub friction: f32, - - #[export] - #[var(get, set = set_ball_radius)] - pub ball_radius: f32, - - #[export] - #[var(get, set = set_cuboid_half_extents)] - pub cuboid_half_extents: Vector3, - - notify_parent: bool, - hot_reload_cb: Callable, - base: Base, -} - -#[godot_api] -impl INode3D for RapierCollider3D { - fn init(base: Base) -> Self { - Self { - id: Array::new(), - handle: ColliderHandle::invalid(), - parent: None, - shape: ShapeType::Ball, - restitution: 0.5, - friction: 0.5, - ball_radius: 0.5, - cuboid_half_extents: Vector3::new(0.5, 0.5, 0.5), - notify_parent: true, - hot_reload_cb: Callable::invalid(), - base, - } - } - - fn on_notification(&mut self, what: Node3DNotification) { - match what { - Node3DNotification::EnterTree => self.on_enter_tree(), - Node3DNotification::ExitTree => self.on_exit_tree(), - Node3DNotification::Parented => self.on_parented(), - Node3DNotification::Unparented => self.on_unparented(), - Node3DNotification::TransformChanged => self.on_transform_changed(), - _ => {} - } - } -} - -#[godot_api] -impl RapierCollider3D { - fn register(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - let handle = bind.pipeline.register_collider(self); - self.handle = handle; - self.id = crate::utils::collider_handle_to_id(handle); - let collider = match bind.pipeline.get_collider_mut(self.handle) { - Some(collider) => collider, - None => { - crate::error!(bind; "RapierCollider3D register - could not find collider {:?} in pipeline", self.handle); - return; - } - }; - self.sync_transforms_to_godot(collider); - crate::debug!(bind; "RapierCollider3D registered {:?}", self.handle.clone()); - } - - fn unregister(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - bind.pipeline.unregister_collider(self); - crate::debug!(bind; "RapierCollider3D unregistered {:?}", self.handle.clone()); - self.handle = ColliderHandle::invalid(); - self.id = Array::new(); - } - - fn reregister(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - bind.pipeline.unregister_collider(self); - let handle = bind.pipeline.register_collider(self); - self.handle = handle; - self.id = crate::utils::collider_handle_to_id(handle); - let collider = match bind.pipeline.get_collider_mut(self.handle) { - Some(collider) => collider, - None => { - crate::error!(bind; "RapierCollider3D reregister - could not find collider {:?} in pipeline", self.handle); - return; - } - }; - self.sync_transforms_to_godot(collider); - crate::debug!(bind; "RapierCollider3D reregistered {:?}", handle); - } - - fn attach_extensions_reloaded_signal(&mut self) { - let sig = Signal::from_object_signal( - &GDExtensionManager::singleton(), - StringName::from("extensions_reloaded") - ); - let cb = Callable::from_object_method(&self.to_gd(), StringName::from("_on_hot_reload")); - let already_connected = sig.is_connected(cb.clone()); - if already_connected { - return; - } - GDExtensionManager::singleton().connect( - StringName::from("extensions_reloaded"), - cb.clone() - ); - self.hot_reload_cb = cb; - } - - fn detach_extensions_reloaded_signal(&mut self) { - if !self.hot_reload_cb.is_null() { - GDExtensionManager::singleton().disconnect( - StringName::from("extensions_reloaded"), - self.hot_reload_cb.clone() - ); - } - } - - #[func] - fn _on_hot_reload(&mut self) { - crate::debug!("RapierCollider3D _on_hot_reload {:?}", self.handle.clone()); - self.base_mut().set_notify_transform(false); - self.reregister(); - self.base_mut().set_notify_transform(true); - } - - fn on_enter_tree(&mut self) { - self.register(); - self.attach_extensions_reloaded_signal(); - self.base_mut().set_notify_transform(true); - } - - fn on_exit_tree(&mut self) { - self.base_mut().set_notify_transform(false); - self.unregister(); - self.detach_extensions_reloaded_signal(); - } - - fn on_parented(&mut self) { - if !self.notify_parent { - return; - } - self.notify_parent = false; - let _res = self.base_mut().try_call_deferred(StringName::from("_on_parented"), &[]); // Collider registering needs to be defferred so that RigidBodies are already registered - } - - #[func] - fn _on_parented(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - let parent = match self.base().get_parent_node_3d() { - Some(parent) => parent, - None => { - self.clear_parent(&mut bind.pipeline); - return; - } - }; - let casted = match parent.is_class(GString::from("RapierRigidBody3D")) { - true => parent.cast::(), - false => { - // Parented to something that is not a rigid body - self.clear_parent(&mut bind.pipeline); - self.notify_parent = true; - return; - } - }; - let class = casted.bind(); - - if self.parent == Some(class.handle) { - // Parenting to already attached parent - do nothing - self.notify_parent = true; - return; - } - - if !bind.pipeline.state.rigid_body_set.contains(class.handle) { - // Trying to parent to rigid body that doesn't exist - godot_error!( - "RapierCollider3D _on_parented - could not find rigid body {:?} in pipeline", - class.handle - ); - self.notify_parent = true; - return; - } - - bind.pipeline.unregister_collider(self); - let handle = bind.pipeline.register_collider_with_parent(self, class.handle); - self.parent = Some(class.handle); - self.handle = handle; - self.id = crate::utils::collider_handle_to_id(handle); - self.notify_parent = true; - } - - fn on_unparented(&mut self) { - if !self.notify_parent { - return; - } - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - self.clear_parent(&mut bind.pipeline); - } - - fn clear_parent(&mut self, pipeline: &mut GR3DPhysicsPipeline) { - self.parent = None; - pipeline.set_collider_parent(self.handle, None); - } - - fn on_transform_changed(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - match bind.pipeline.get_collider_mut(self.handle) { - Some(collider) => { - self.sync_transforms_to_godot(collider); - } - None => { - godot_error!( - "RapierCollider3D on_transform_changed - could not find collider {:?} in pipeline", - self.handle - ); - return; - } - } - } - - // Changes rapier transforms to match godot transforms - fn sync_transforms_to_godot(&mut self, collider: &mut Collider) { - if self.base().is_inside_tree() { - let translation = self.base().get_global_position(); - let rotation = self.base().get_global_transform().basis.to_quat(); - let r_pos = crate::utils::pos_godot_to_rapier(translation); - let r_rot = crate::utils::rot_godot_to_rapier(rotation); - collider.set_translation(r_pos); - collider.set_rotation(r_rot); - } - } - - pub fn build(&self) -> Collider { - let shape = match self.shape { - ShapeType::Ball => SharedShape::ball(self.ball_radius), - ShapeType::Cuboid => - SharedShape::cuboid( - self.cuboid_half_extents.x, - self.cuboid_half_extents.y, - self.cuboid_half_extents.z - ), - }; - let collider = ColliderBuilder::new(shape) - .restitution(self.restitution) - .friction(self.friction) - .build(); - collider - } - - // This is gross - don't want a function for every single property - // But don't want to define every single property like they are anyway (prefer Shape3Ds or Resources) - // just wait until https://github.com/godot-rust/gdext/issues/440 is resolved - #[func] - fn set_shape(&mut self, shape: ShapeType) { - self.shape = shape; - self.base_mut().update_gizmos(); - self.reregister(); - } - - #[func] - fn set_ball_radius(&mut self, radius: f32) { - self.ball_radius = radius; - self.base_mut().update_gizmos(); - self.reregister(); - } - - #[func] - fn set_cuboid_half_extents(&mut self, half_extents: Vector3) { - self.cuboid_half_extents = half_extents; - self.base_mut().update_gizmos(); - self.reregister(); - } - - #[func] - fn set_restitution(&mut self, restitution: f32) { - self.restitution = restitution; - self.reregister(); - } - - #[func] - fn set_friction(&mut self, friction: f32) { - self.friction = friction; - self.reregister(); - } -} - -#[derive(Debug, GodotConvert, Var, Export)] -#[godot(via = GString)] -// https://docs.rs/rapier3d/latest/rapier3d/geometry/enum.ShapeType.html -pub enum ShapeType { - Ball, - Cuboid, -} diff --git a/addons/godot-rapier-3d/rust/src/editor_plugin/autoloads.rs b/addons/godot-rapier-3d/rust/src/editor_plugin/autoloads.rs index bee84fd..35c6fef 100644 --- a/addons/godot-rapier-3d/rust/src/editor_plugin/autoloads.rs +++ b/addons/godot-rapier-3d/rust/src/editor_plugin/autoloads.rs @@ -8,8 +8,7 @@ pub const AUTOLOAD_PATHS: &'static [&'static str] = &[ ]; pub fn add_all_autoloads(plugin: &mut GR3DEditorPlugin) { - let singleton_list: PackedStringArray = godot::engine::Engine - ::singleton() + let singleton_list: PackedStringArray = godot::engine::Engine::singleton() .get_singleton_list() .clone(); @@ -31,16 +30,19 @@ pub fn remove_all_autoloads(plugin: &mut GR3DEditorPlugin) { fn add_autoload(plugin: &mut GR3DEditorPlugin, name: &str, path: &str) { // Call deferred so that Godot editor has time to detect Rust singleton first - crate::debug!("Adding autoload: {} -> {}", name, path); - plugin - .base_mut() - .call_deferred( - StringName::from("add_autoload_singleton"), - &[GString::from(name).to_variant(), GString::from(path).to_variant()] - ); + log::debug!("Adding autoload: {} -> {}", name, path); + plugin.base_mut().call_deferred( + StringName::from("add_autoload_singleton"), + &[ + GString::from(name).to_variant(), + GString::from(path).to_variant(), + ], + ); } fn remove_autoload(plugin: &mut GR3DEditorPlugin, name: &str) { - crate::debug!("Removing autoload: {}", name); - plugin.base_mut().remove_autoload_singleton(GString::from(name)); + log::debug!("Removing autoload: {}", name); + plugin + .base_mut() + .remove_autoload_singleton(GString::from(name)); } diff --git a/addons/godot-rapier-3d/rust/src/editor_plugin/gizmos.rs b/addons/godot-rapier-3d/rust/src/editor_plugin/gizmos.rs index 37957e6..ef127b6 100644 --- a/addons/godot-rapier-3d/rust/src/editor_plugin/gizmos.rs +++ b/addons/godot-rapier-3d/rust/src/editor_plugin/gizmos.rs @@ -1,61 +1,63 @@ -use crate::editor_plugin::GR3DEditorPlugin; -use crate::log::LogLevel; -use godot::engine::EditorNode3DGizmoPlugin; -use godot::engine::GDScript; -use godot::prelude::*; +// Not needed for now -pub const GIZMO_PATHS: &'static [&'static str] = &[ - "res://addons/godot-rapier-3d/gdscript/gizmos/collider3D.gd", -]; +// use crate::editor_plugin::GR3DEditorPlugin; +// use crate::log::LogLevel; +// use godot::engine::EditorNode3DGizmoPlugin; +// use godot::engine::GDScript; +// use godot::prelude::*; -pub fn add_all_gizmos(plugin: &mut GR3DEditorPlugin) { - for path in GIZMO_PATHS { - add_gizmo(plugin, path); - } -} +// pub const GIZMO_PATHS: &'static [&'static str] = &[ +// "res://addons/godot-rapier-3d/gdscript/gizmos/collider3D.gd", +// ]; -pub fn remove_all_gizmos(plugin: &mut GR3DEditorPlugin) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - for iid in bind.gizmo_iids.clone() { - remove_gizmo(plugin, iid, bind.log_level); - } - bind.gizmo_iids.clear(); -} +// pub fn add_all_gizmos(plugin: &mut GR3DEditorPlugin) { +// for path in GIZMO_PATHS { +// add_gizmo(plugin, path); +// } +// } -fn add_gizmo(plugin: &mut GR3DEditorPlugin, path: &str) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - let mut gizmo_script = match try_load::(path) { - Ok(script) => script, - _ => { - crate::error!(bind; "Could not load gizmo: {:?}", path); - return; - } - }; +// pub fn remove_all_gizmos(plugin: &mut GR3DEditorPlugin) { +// let mut engine = crate::engine::get_engine(); +// let mut bind = engine.bind_mut(); +// for iid in bind.gizmo_iids.clone() { +// remove_gizmo(plugin, iid, bind.log_level); +// } +// bind.gizmo_iids.clear(); +// } - let script_obj: Variant = gizmo_script.instantiate(&[]); - let gizmo = match Gd::::try_from_variant(&script_obj) { - Ok(gizmo) => gizmo, - Err(error) => { - crate::error!(bind; "Could not process gizmo script {:?}. Error: {:?}", path, error); - return; - } - }; - let iid = gizmo.instance_id().to_i64(); - plugin.base_mut().add_node_3d_gizmo_plugin(gizmo); - bind.gizmo_iids.push(iid); - crate::debug!(bind; "Added gizmo: {:?}", path); -} +// fn add_gizmo(plugin: &mut GR3DEditorPlugin, path: &str) { +// let mut engine = crate::engine::get_engine(); +// let mut bind = engine.bind_mut(); +// let mut gizmo_script = match try_load::(path) { +// Ok(script) => script, +// _ => { +// log::error!("Could not load gizmo: {:?}", path); +// return; +// } +// }; -fn remove_gizmo(plugin: &mut GR3DEditorPlugin, iid: i64, log_level: LogLevel) { - let instance_id = InstanceId::from_i64(iid); - let gizmo: Gd = match Gd::try_from_instance_id(instance_id) { - Ok(gizmo) => gizmo, - _ => { - crate::error!(log_level => "Could not remove gizmo {:?}", instance_id); - return; - } - }; - plugin.base_mut().remove_node_3d_gizmo_plugin(gizmo); -} +// let script_obj: Variant = gizmo_script.instantiate(&[]); +// let gizmo = match Gd::::try_from_variant(&script_obj) { +// Ok(gizmo) => gizmo, +// Err(error) => { +// log::error!("Could not process gizmo script {:?}. Error: {:?}", path, error); +// return; +// } +// }; +// let iid = gizmo.instance_id().to_i64(); +// plugin.base_mut().add_node_3d_gizmo_plugin(gizmo); +// bind.gizmo_iids.push(iid); +// godot_print!("Added gizmo: {:?}", path); +// } + +// fn remove_gizmo(plugin: &mut GR3DEditorPlugin, iid: i64, log_level: LogLevel) { +// let instance_id = InstanceId::from_i64(iid); +// let gizmo: Gd = match Gd::try_from_instance_id(instance_id) { +// Ok(gizmo) => gizmo, +// _ => { +// log::error!("Could not remove gizmo {:?}", instance_id); +// return; +// } +// }; +// plugin.base_mut().remove_node_3d_gizmo_plugin(gizmo); +// } diff --git a/addons/godot-rapier-3d/rust/src/editor_plugin/mod.rs b/addons/godot-rapier-3d/rust/src/editor_plugin/mod.rs index 8deb42c..ec9b55d 100644 --- a/addons/godot-rapier-3d/rust/src/editor_plugin/mod.rs +++ b/addons/godot-rapier-3d/rust/src/editor_plugin/mod.rs @@ -1,5 +1,5 @@ -use crate::editor_plugin::autoloads::{ add_all_autoloads, remove_all_autoloads }; -use crate::editor_plugin::gizmos::{ add_all_gizmos, remove_all_gizmos }; +use crate::editor_plugin::autoloads::{add_all_autoloads, remove_all_autoloads}; +// use crate::editor_plugin::gizmos::{add_all_gizmos, remove_all_gizmos}; use godot::engine::EditorPlugin; use godot::engine::IEditorPlugin; use godot::prelude::*; @@ -27,11 +27,11 @@ impl IEditorPlugin for GR3DEditorPlugin { impl GR3DEditorPlugin { pub fn register(&mut self) { add_all_autoloads(self); - add_all_gizmos(self); + // add_all_gizmos(self); } pub fn unregister(&mut self) { remove_all_autoloads(self); - remove_all_gizmos(self); + // remove_all_gizmos(self); } } diff --git a/addons/godot-rapier-3d/rust/src/engine.rs b/addons/godot-rapier-3d/rust/src/engine.rs index 9857afd..f1cd97f 100644 --- a/addons/godot-rapier-3d/rust/src/engine.rs +++ b/addons/godot-rapier-3d/rust/src/engine.rs @@ -1,17 +1,16 @@ -use crate::log::LogLevel; -use crate::physics_pipeline::GR3DPhysicsPipeline; +use crate::utils::{init_logger, LogLevel}; +use crate::{ActionQueue, GR3DPhysicsPipeline, Lookups}; use godot::builtin::PackedByteArray; -use godot::engine::Engine; -use godot::engine::IObject; -use godot::engine::Object; +use godot::engine::{Engine, IObject, Object}; use godot::prelude::*; pub const ENGINE_SINGLETON_NAME: &str = "Rapier3DEngine"; pub fn register_engine() { + init_logger(); Engine::singleton().register_singleton( StringName::from(ENGINE_SINGLETON_NAME), - GR3DEngineSingleton::new_alloc().upcast() + GR3DEngineSingleton::new_alloc().upcast(), ); } @@ -30,21 +29,21 @@ pub fn unregister_engine() { #[derive(GodotClass)] #[class(base = Object)] pub struct GR3DEngineSingleton { + pub lookups: Lookups, pub pipeline: GR3DPhysicsPipeline, + pub action_queue: ActionQueue, pub gizmo_iids: Vec, // Remembered so that gizmos can be removed - pub log_level: LogLevel, base: Base, } #[godot_api] impl IObject for GR3DEngineSingleton { fn init(base: Base) -> Self { - let log_level = LogLevel::Info; - Self { - pipeline: GR3DPhysicsPipeline::new(log_level), + lookups: Lookups::new(), + pipeline: GR3DPhysicsPipeline::new(), + action_queue: ActionQueue::new(), gizmo_iids: Vec::new(), - log_level, base, } } @@ -53,99 +52,73 @@ impl IObject for GR3DEngineSingleton { #[godot_api] impl GR3DEngineSingleton { #[func] + // Advances the physics simulation 1 step pub fn step(&mut self) { - self.pipeline.step(); + self.action_queue.push_step_action(); } #[func] pub fn get_state(&self) -> PackedByteArray { - let vec = self.pipeline.state.pack(); - let slice = vec.as_slice(); - PackedByteArray::from(slice) + match self.pipeline.state.pack() { + Ok(vec) => { + let slice = vec.as_slice(); + PackedByteArray::from(slice) + } + Err(e) => { + log::error!("Could not get state: {:?}", e); + PackedByteArray::new() + } + } } + // TODO will need orchestration? #[func] pub fn set_state(&mut self, data: PackedByteArray) { let slice = data.as_slice(); - let state = self.pipeline.state.unpack(slice); - self.pipeline.state = state; - self.pipeline.sync_all_body_positions(); + match self.pipeline.state.unpack(slice) { + Ok(state) => { + self.pipeline.state = state; + self.pipeline.sync_all_g2r(&self.lookups); + } + Err(e) => { + log::error!("Could not set state: {:?}", e); + return; + } + }; } #[func] - pub fn set_log_level(&mut self, log_level: i32) { - let level = LogLevel::try_from(log_level).unwrap_or(LogLevel::Debug); - self.log_level = level; - self.pipeline.log_level = level; + // Process physics objects (must be done in determinstic order) + pub fn _process(&mut self) { + self.action_queue + ._process(&mut self.pipeline, &mut self.lookups); } #[func] pub fn print_debug_info(&self) { - godot_print!( - "Rigid body ids: {:?} -Collider ids: {:?}", - self.pipeline.rigid_body_ids, - self.pipeline.collider_ids + log::debug!( + "Lookups: {:#?}\nPipeline: {}", + self.lookups.get_all_cuids(), + self.pipeline + .get_debug_info() + .ok() + .ok_or("Could not get debug info") + .unwrap() ); - - for (handle, rb) in self.pipeline.state.rigid_body_set.iter() { - godot_print!("Rigid body {:?}: {:?}", handle, rb.position()); - } - - for (handle, collider) in self.pipeline.state.collider_set.iter() { - godot_print!("Collider {:?}: {:?}", handle, collider.position()); - } } -} -#[macro_export] -macro_rules! get_engine { - () => { - { - let gd_pointer = match - godot::engine::Engine::singleton().get_singleton(StringName::from("Rapier3DEngine")) - { - Some(gd_pointer) => gd_pointer, - None => { - godot_error!("Could not obtain Rapier3DEngine singleton"); - return; - } - }; - - let singleton = match gd_pointer.try_cast::() { - Ok(singleton) => singleton, - Err(_) => { - godot_error!("Could not cast to Rapier3DEngine singleton"); - return; - } - }; - - singleton - } - }; + #[func] + pub fn set_log_level(&mut self, level: LogLevel) { + crate::utils::set_log_level(level); + } } -#[macro_export] -macro_rules! get_engine_checked { - () => { - { - match - godot::engine::Engine::singleton().get_singleton(StringName::from("Rapier3DEngine")) - { - Some(gd_pointer) => { - match gd_pointer.try_cast::() { - Ok(singleton) => Ok(singleton), - Err(_) => { - godot_error!("Could not cast to Rapier3DEngine singleton"); - Err("Could not cast to Rapier3DEngine singleton") - }, - } - }, - None => { - godot_error!("Could not cast to Rapier3DEngine singleton"); - Err("Could not cast to Rapier3DEngine singleton") - }, - } - } - }; +pub fn get_engine() -> Result, &'static str> { + match Engine::singleton().get_singleton(StringName::from("Rapier3DEngine")) { + Some(gd_pointer) => match gd_pointer.try_cast::() { + Ok(singleton) => Ok(singleton), + Err(_) => Err("Could not cast to Rapier3DEngine singleton"), + }, + None => Err("Could not retrieve Rapier3DEngine singleton"), + } } diff --git a/addons/godot-rapier-3d/rust/src/lib.rs b/addons/godot-rapier-3d/rust/src/lib.rs index 429f7c3..75a87a3 100644 --- a/addons/godot-rapier-3d/rust/src/lib.rs +++ b/addons/godot-rapier-3d/rust/src/lib.rs @@ -1,16 +1,21 @@ use crate::engine::{register_engine, unregister_engine}; use godot::prelude::*; -mod collider; -mod debug_render_pipeline; mod editor_plugin; mod engine; -mod log; -mod physics_pipeline; -mod physics_state; -mod rigid_body; +mod lookups; +mod objects; +mod pipeline; +mod queue; mod utils; +pub use engine::get_engine; +pub use lookups::{IDBridge, LookupIdentifier, Lookups}; +pub use objects::{ObjectKind, PhysicsObject}; +pub use pipeline::{GR3DPhysicsPipeline, GR3DPhysicsState}; +pub use queue::ActionQueue; +pub use utils::{cuid2, handle_error}; + struct GodotRapier3D; #[gdextension] diff --git a/addons/godot-rapier-3d/rust/src/log.rs b/addons/godot-rapier-3d/rust/src/log.rs deleted file mode 100644 index d4dc777..0000000 --- a/addons/godot-rapier-3d/rust/src/log.rs +++ /dev/null @@ -1,142 +0,0 @@ -use godot::builtin::GString; -use godot::log::{ godot_error, godot_print, godot_warn }; -use godot::prelude::{ Export, GodotConvert, Var }; - -pub fn log(engine_level: LogLevel, msg_level: LogLevel, message: &str) { - if msg_level <= engine_level { - match msg_level { - LogLevel::Error => godot_error!("{}", message), - LogLevel::Warning => godot_warn!("{}", message), - LogLevel::Info => godot_print!("{}", message), - LogLevel::Debug => godot_print!("{}", message), - } - } -} - -#[derive(PartialEq, PartialOrd, Debug, GodotConvert, Var, Export, Copy, Clone)] -#[godot(via = GString)] -pub enum LogLevel { - Error, - Warning, - Info, - Debug, -} - -impl TryFrom for LogLevel { - type Error = (); - - fn try_from(v: i32) -> Result { - match v { - x if x == (LogLevel::Error as i32) => Ok(LogLevel::Error), - x if x == (LogLevel::Warning as i32) => Ok(LogLevel::Warning), - x if x == (LogLevel::Info as i32) => Ok(LogLevel::Info), - x if x == (LogLevel::Debug as i32) => Ok(LogLevel::Debug), - _ => Err(()), - } - } -} - -// TODO I'm not sure if double surrounding blocks { { } } in the 3rd arms are required -// to avoid the engine bind leaking out of the macro and causing Godot crashes ? - -// TODO - I wanted to generate these macros using a super macro, but apparently repeating args are not able to be nested -// Is there a cleaner way to do this? -#[macro_export] -macro_rules! error { - ($engine_log_level:expr => $($arg:expr),*) => { - crate::log::log($engine_log_level, crate::log::LogLevel::Error, &format!($($arg),*)); - }; - ($engine_bind:expr; $($arg:expr),*) => { - crate::log::log($engine_bind.log_level, crate::log::LogLevel::Error, &format!($($arg),*)); - }; - ($($arg:expr),*) => { - { - { - let engine_result = crate::get_engine_checked!(); - match engine_result { - Ok(engine) => { - crate::log::log(engine.bind().log_level, crate::log::LogLevel::Error, &format!($($arg),*)); - }, - Err(_) => { - godot_error!("Failed to print error message: {}", &format!($($arg),*)); - }, - }; - } - } - }; -} - -#[macro_export] -macro_rules! warn { - ($engine_log_level:expr => $($arg:expr),*) => { - crate::log::log($engine_log_level, crate::log::LogLevel::Warn, &format!($($arg),*)); - }; - ($engine_bind:expr; $($arg:expr),*) => { - crate::log::log($engine_bind.log_level, crate::log::LogLevel::Warn, &format!($($arg),*)); - }; - ($($arg:expr),*) => { - { - { - let engine_result = crate::get_engine_checked!(); - match engine_result { - Ok(engine) => { - crate::log::log(engine.bind().log_level, crate::log::LogLevel::Warn, &format!($($arg),*)); - }, - Err(_) => { - godot_warn!("Failed to print warn message: {}", &format!($($arg),*)); - }, - }; - } - } - }; -} - -#[macro_export] -macro_rules! info { - ($engine_log_level:expr => $($arg:expr),*) => { - crate::log::log($engine_log_level, crate::log::LogLevel::Info, &format!($($arg),*)); - }; - ($engine_bind:expr; $($arg:expr),*) => { - crate::log::log($engine_bind.log_level, crate::log::LogLevel::Info, &format!($($arg),*)); - }; - ($($arg:expr),*) => { - { - { - let engine_result = crate::get_engine_checked!(); - match engine_result { - Ok(engine) => { - crate::log::log(engine.bind().log_level, crate::log::LogLevel::Info, &format!($($arg),*)); - }, - Err(_) => { - godot_print!("Failed to print info message: {}", &format!($($arg),*)); - }, - }; - } - } - }; -} - -#[macro_export] -macro_rules! debug { - ($engine_log_level:expr => $($arg:expr),*) => { - crate::log::log($engine_log_level, crate::log::LogLevel::Debug, &format!($($arg),*)); - }; - ($engine_bind:expr; $($arg:expr),*) => { - crate::log::log($engine_bind.log_level, crate::log::LogLevel::Debug, &format!($($arg),*)); - }; - ($($arg:expr),*) => { - { - { - let engine_result = crate::get_engine_checked!(); - match engine_result { - Ok(engine) => { - crate::log::log(engine.bind().log_level, crate::log::LogLevel::Debug, &format!($($arg),*)); - }, - Err(_) => { - godot_print!("Failed to print debug message: {}", &format!($($arg),*)); - }, - }; - } - } - }; -} diff --git a/addons/godot-rapier-3d/rust/src/lookups.rs b/addons/godot-rapier-3d/rust/src/lookups.rs new file mode 100644 index 0000000..11a9bae --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/lookups.rs @@ -0,0 +1,114 @@ +use crate::objects::{Handle, ObjectBridge}; +use crate::ObjectKind; +use std::collections::HashMap; +use std::fmt::Debug; + +mod id_bridge; +pub use id_bridge::IDBridge; + +#[derive(Eq, PartialEq, Debug, Hash)] +pub enum LookupIdentifier { + ID, + Handle, + InstanceID, +} + +// Provides means of accessing all currently registered physics objects +pub struct Lookups { + pub inner: HashMap>>, +} + +impl Lookups { + pub fn new() -> Self { + Self { + inner: HashMap::from([ + ( + ObjectKind::RigidBody, + HashMap::from([ + (LookupIdentifier::ID, HashMap::new()), // CUID2 <-> IDBridge + (LookupIdentifier::Handle, HashMap::new()), // Rapier handle raw parts <-> IDBridge + (LookupIdentifier::InstanceID, HashMap::new()), // Godot instance ID <-> IDBridge + ]), + ), + ( + ObjectKind::Collider, + HashMap::from([ + (LookupIdentifier::ID, HashMap::new()), // CUID2 <-> IDBridge + (LookupIdentifier::Handle, HashMap::new()), // Rapier handle raw parts <-> IDBridge + (LookupIdentifier::InstanceID, HashMap::new()), // Godot instance ID <-> IDBridge + ]), + ), + ]), + } + } + + // Returns true if the given CUID2 is unique across all physics objects + pub fn is_cuid2_unique(&self, cuid2: &str) -> bool { + self.inner + .values() + .all(|map| map.values().all(|map| !map.contains_key(cuid2))) + } + + pub fn get( + &self, + object_kind: ObjectKind, + identifier: LookupIdentifier, + key: &str, + ) -> Option<&IDBridge> { + self.inner.get(&object_kind)?.get(&identifier)?.get(key) + } + + pub fn insert(&mut self, object_kind: ObjectKind, id_bridge: IDBridge) -> Result<(), String> { + let object_map = self.inner.get_mut(&object_kind).ok_or(format!( + "Could not find object kind '{}' in lookups", + object_kind + ))?; + + for (ident, map) in object_map.into_iter() { + match ident { + LookupIdentifier::ID => { + map.insert(id_bridge.clone().cuid2, id_bridge.clone()); + } + LookupIdentifier::Handle => { + let object_bridge = ObjectBridge::from(object_kind.clone()); + let handle = Handle { + kind: object_bridge.handle_kind, + raw: id_bridge.clone().handle_raw, + }; + map.insert(handle.to_string(), id_bridge.clone()); + } + LookupIdentifier::InstanceID => { + map.insert(id_bridge.clone().instance_id.to_string(), id_bridge.clone()); + } + } + } + Ok(()) + } + + pub fn remove(&mut self, cuid2: String) -> Result<(), String> { + for (_, object_map) in self.inner.iter_mut() { + for (ident, map) in object_map.iter_mut() { + match ident { + LookupIdentifier::ID => { + map.remove(&cuid2); + } + _ => map.retain(|_, id_bridge| id_bridge.cuid2 != cuid2), + } + } + } + Ok(()) + } + + pub fn get_all_cuids(&self) -> HashMap> { + let mut ret_map = HashMap::new(); + for (object_kind, object_map) in self.inner.iter() { + let mut cuids = Vec::new(); + let cuid_map = object_map.get(&LookupIdentifier::ID); + for map in cuid_map.iter() { + cuids.extend(map.keys().cloned()); + } + ret_map.insert(object_kind.to_string(), cuids); + } + ret_map + } +} diff --git a/addons/godot-rapier-3d/rust/src/lookups/id_bridge.rs b/addons/godot-rapier-3d/rust/src/lookups/id_bridge.rs new file mode 100644 index 0000000..158dda6 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/lookups/id_bridge.rs @@ -0,0 +1,59 @@ +use crate::objects::{Handle, ObjectBridge}; +use crate::{LookupIdentifier, Lookups}; +#[derive(Debug, Clone)] +pub struct IDBridge { + pub cuid2: String, // CUID2 + pub handle_raw: [u32; 2], // Rapier handle raw parts + pub instance_id: i64, // Godot node instance_id +} + +impl IDBridge { + pub fn new(cuid2: String, handle_raw: [u32; 2], instance_id: i64) -> Self { + Self { + cuid2, + handle_raw, + instance_id, + } + } + + pub fn invalid() -> IDBridge { + Self { + cuid2: String::new(), + handle_raw: [u32::MAX, u32::MAX], + instance_id: i64::MAX, + } + } + + pub fn is_valid(&self) -> Result<(), String> { + if self.cuid2.len() == 0 { + return Err("CUID2 is empty".to_string()); + } + if self.handle_raw == [u32::MAX, u32::MAX] { + return Err("Handle is invalid".to_string()); + } + if self.instance_id == i64::MAX { + return Err("Instance ID is invalid".to_string()); + } + Ok(()) + } + + pub fn from_handle(handle: Handle, lookups: &Lookups) -> Self { + let object_bridge = ObjectBridge::from(handle.kind.clone()); + let found = match lookups.get( + object_bridge.object_kind, + LookupIdentifier::Handle, + &handle.to_string(), + ) { + Some(found) => found, + None => { + return Self::invalid(); + } + }; + + Self { + cuid2: found.cuid2.to_string(), + handle_raw: handle.raw, + instance_id: found.instance_id, + } + } +} diff --git a/addons/godot-rapier-3d/rust/src/objects.rs b/addons/godot-rapier-3d/rust/src/objects.rs new file mode 100644 index 0000000..b87d16e --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/objects.rs @@ -0,0 +1,192 @@ +use crate::engine::get_engine; +use crate::queue::{Action, Actionable, QueueName}; +use crate::utils::{isometry_to_transform, transform_to_isometry}; +use crate::LookupIdentifier; +use godot::builtin::Transform3D; +use godot::engine::{GDExtensionManager, Node3D}; +use godot::obj::WithBaseField; +use godot::prelude::*; +use rapier3d::math::{Isometry, Real}; + +mod collider; +mod handle; +mod object_bridge; +mod rigid_body; + +pub use collider::RapierCollider3D; +pub use handle::{Handle, HandleKind}; +pub use object_bridge::{ObjectBridge, ObjectKind}; +pub use rigid_body::RapierRigidBody3D; + +// Implemented by all classes that inherit a Godot node and have an underlying Rapier object +pub trait PhysicsObject: WithBaseField { + // Required + fn get_kind(&self) -> ObjectKind; + fn get_cuid2(&self) -> String; + fn set_cuid2(&mut self, cuid2: String); + fn get_handle(&self) -> Handle; + fn set_handle(&mut self, handle: Handle); + fn get_hot_reload_cb(&self) -> Callable; + fn set_hot_reload_cb(&mut self, cb: Callable); + fn build(&self) -> Actionable; + + // Provided + fn is_registered(&self) -> Result { + let cuid2 = self.get_cuid2(); + let engine = get_engine()?; + let lookups = &engine.bind().lookups; + Ok(lookups + .get(self.get_kind(), LookupIdentifier::ID, &cuid2) + .is_some()) + } + + fn attach_extensions_reloaded_signal(&mut self) { + let sig = Signal::from_object_signal( + &GDExtensionManager::singleton(), + StringName::from("extensions_reloaded"), + ); + let cb = Callable::from_object_method(&self.base(), StringName::from("_on_hot_reload")); + let already_connected = sig.is_connected(cb.clone()); + if already_connected { + return; + } + GDExtensionManager::singleton() + .connect(StringName::from("extensions_reloaded"), cb.clone()); + self.set_hot_reload_cb(cb); + } + + fn detach_extensions_reloaded_signal(&mut self) { + if !self.get_hot_reload_cb().is_null() { + GDExtensionManager::singleton().disconnect( + StringName::from("extensions_reloaded"), + self.get_hot_reload_cb(), + ); + } + } + + fn on_hot_reload(&mut self) { + log::debug!("Hot reloading {:?}", self.get_cuid2()); + self.base_mut().set_notify_transform(false); + self.register().map_err(crate::handle_error).ok(); + self.sync_r2g().map_err(crate::handle_error).ok(); + } + + fn on_enter_tree(&mut self) { + self.register().map_err(crate::handle_error).ok(); + self.attach_extensions_reloaded_signal(); + self.sync_r2g().map_err(crate::handle_error).ok(); + } + + fn on_exit_tree(&mut self) { + self.base_mut().set_notify_transform(false); + self.unregister().map_err(crate::handle_error).ok(); + self.detach_extensions_reloaded_signal(); + } + + fn on_transform_changed(&mut self) { + // Change rapier transform to match godot transform + self.sync_r2g().map_err(crate::handle_error).ok(); + } + + fn get_action(&self, data: Actionable) -> Action { + Action { + inner_cuid: self.get_cuid2(), + inner_iid: self.base().instance_id().to_i64(), + data, + } + } + + fn get_rapier_position(&self) -> Result, String> { + let handle = self.get_handle(); + let engine = get_engine()?; + let bind = engine.bind(); + bind.pipeline.get_object_position(handle) + } + + fn get_node_transform(&self) -> Result { + let node = self.base(); + match node.is_inside_tree() { + true => Ok(node.get_global_transform()), + false => Err(format!("Node '{:?}' is not inside tree", node.get_name())), + } + } + + fn set_node_transform(&mut self, transform: Transform3D) -> Result<(), String> { + let mut node = self.base_mut(); + match node.is_inside_tree() { + true => { + node.set_global_transform(transform); + Ok(()) + } + false => Err(format!("Node '{:?}' is not inside tree", node.get_name())), + } + } + + fn register(&mut self) -> Result<(), String> { + log::debug!("Registering {} {:?}", self.get_kind(), self.get_cuid2()); + let action = self.get_action(self.build()); + let mut engine = get_engine()?; + let mut bind = engine.bind_mut(); + let queue = bind + .action_queue + .queues + .get_mut(&QueueName::Insert) + .expect("QueueName::Insert not found"); + queue.push(action); + Ok(()) + } + + fn unregister(&mut self) -> Result<(), String> { + log::debug!("Unregistering {} {:?}", self.get_kind(), self.get_cuid2()); + let action = self.get_action(Actionable::from(self.get_handle())); + self.set_handle(Handle::invalid()); + let mut engine = get_engine()?; + let mut bind = engine.bind_mut(); + let queue = bind + .action_queue + .queues + .get_mut(&QueueName::Remove) + .expect("QueueName::Remove not found"); + queue.push(action); + Ok(()) + } + + fn reregister(&mut self) -> Result<(), String> { + self.unregister()?; + self.register() + } + + fn sync_r2g(&mut self) -> Result<(), String> { + log::trace!("Syncing r2g for {} {:?}", self.get_kind(), self.get_cuid2()); + let node_pos = self.get_godot_isometry()?; + let action = self.get_action(Actionable::NodePos(self.get_kind(), node_pos)); + let mut engine = get_engine()?; + let mut bind = engine.bind_mut(); + bind.action_queue.add_action(action, &QueueName::Sync); + Ok(()) + } + + // Changes godot transforms to match rapier transforms + fn sync_g2r(&mut self) -> Result<(), String> { + log::debug!( + "Syncing g2r for {:?} {:?}", + self.get_kind(), + self.get_cuid2() + ); + let transform = self.get_rapier_transform()?; + self.set_node_transform(transform)?; + Ok(()) + } + + // Returns the current position and rotation of the godot node as a Rapier Isometry + fn get_godot_isometry(&self) -> Result, String> { + let transform = self.get_node_transform()?; + Ok(transform_to_isometry(transform)) + } + + // Returns the current position and rotation of the internal Rapier object as a Godot Transform + fn get_rapier_transform(&self) -> Result { + let isometry = self.get_rapier_position()?; + Ok(isometry_to_transform(isometry)) + } +} diff --git a/addons/godot-rapier-3d/rust/src/objects/collider.rs b/addons/godot-rapier-3d/rust/src/objects/collider.rs new file mode 100644 index 0000000..5801968 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/objects/collider.rs @@ -0,0 +1,222 @@ +use crate::queue::Actionable; +use crate::queue::QueueName; +use crate::ObjectKind; +use crate::PhysicsObject; +use godot::engine::notify::Node3DNotification; +use godot::engine::INode3D; +use godot::engine::Node3D; +use godot::prelude::*; +use rapier3d::prelude::*; + +use super::Handle; +use super::HandleKind; +use super::RapierRigidBody3D; + +#[derive(GodotClass)] +#[class(base = Node3D)] +pub struct RapierCollider3D { + #[export] + #[var(usage_flags = [EDITOR, STORAGE, READ_ONLY])] + pub id: GString, // https://crates.io/crates/cuid2 + pub handle: Handle, + pub parent: Option, + + #[export] + #[var(get, set = set_shape)] + pub shape: ShapeType, + + #[export] + #[var(get, set = set_restitution)] + pub restitution: f32, + + #[export] + #[var(get, set = set_friction)] + pub friction: f32, + + #[export] + #[var(get, set = set_ball_radius)] + pub ball_radius: f32, + + #[export] + #[var(get, set = set_cuboid_half_extents)] + pub cuboid_half_extents: Vector3, + + hot_reload_cb: Callable, + base: Base, +} + +#[godot_api] +impl INode3D for RapierCollider3D { + fn init(base: Base) -> Self { + Self { + id: GString::from(crate::cuid2()), + handle: Handle::invalid(), + parent: None, + shape: ShapeType::Ball, + restitution: 0.5, + friction: 0.5, + ball_radius: 0.5, + cuboid_half_extents: Vector3::new(0.5, 0.5, 0.5), + hot_reload_cb: Callable::invalid(), + base, + } + } + + fn on_notification(&mut self, what: Node3DNotification) { + match what { + Node3DNotification::EnterTree => self.on_enter_tree(), + Node3DNotification::ExitTree => self.on_exit_tree(), + Node3DNotification::Parented => self.on_parented(), + Node3DNotification::TransformChanged => self.on_transform_changed(), + _ => {} + } + } +} + +#[godot_api] +impl RapierCollider3D { + #[func] + fn _on_hot_reload(&mut self) { + self.on_hot_reload(); + } + + fn on_parented(&mut self) { + self.push_parent_action().map_err(crate::handle_error).ok(); + } + + fn push_parent_action(&mut self) -> Result<(), String> { + let parent_id = match self.get_parent_rigid_body_node() { + Some(rb) => Some(rb.bind().id.to_string()), + None => None, + }; + log::trace!( + "Parenting collider '{}' to rigid body '{:?}'", + self.id.to_string(), + parent_id, + ); + + let action = self.get_action(Actionable::ColliderIDWithParentID( + self.id.to_string(), + parent_id, + )); + + let mut engine = crate::get_engine()?; + let mut bind = engine.bind_mut(); + let queue = bind + .action_queue + .queues + .get_mut(&QueueName::Parent) + .expect("QueueName::Parent not found"); + queue.push(action); + Ok(()) + } + + fn get_parent_rigid_body_node(&self) -> Option> { + match self.base().get_parent_node_3d() { + Some(parent) => match parent.is_class(GString::from("RapierRigidBody3D")) { + true => Some(parent.cast::()), + false => None, + }, + None => None, + } + } + + // TODO This is gross - don't want a function for every single property + // But don't want to define every single property like they are anyway (prefer Shape3Ds or Resources) + // just wait until https://github.com/godot-rust/gdext/issues/440 is resolved + #[func] + fn set_shape(&mut self, shape: ShapeType) { + self.shape = shape; + self.base_mut().update_gizmos(); + // self.reregister().map_err(crate::handle_error).ok(); + } + + #[func] + fn set_ball_radius(&mut self, radius: f32) { + self.ball_radius = radius; + self.base_mut().update_gizmos(); + // self.reregister().map_err(crate::handle_error).ok(); + } + + #[func] + fn set_cuboid_half_extents(&mut self, half_extents: Vector3) { + self.cuboid_half_extents = half_extents; + self.base_mut().update_gizmos(); + // self.reregister().map_err(crate::handle_error).ok(); + } + + #[func] + fn set_restitution(&mut self, restitution: f32) { + self.restitution = restitution; + // self.reregister().map_err(crate::handle_error).ok(); + } + + #[func] + fn set_friction(&mut self, friction: f32) { + self.friction = friction; + // self.reregister().map_err(crate::handle_error).ok(); + } +} + +impl PhysicsObject for RapierCollider3D { + fn get_kind(&self) -> ObjectKind { + ObjectKind::Collider + } + + fn get_cuid2(&self) -> String { + self.id.to_string() + } + + fn set_cuid2(&mut self, cuid2: String) { + self.id = GString::from(cuid2); + } + + fn get_handle(&self) -> Handle { + self.handle.clone() + } + + fn set_handle(&mut self, handle: Handle) { + match handle.kind { + HandleKind::ColliderHandle | HandleKind::Invalid => { + self.handle = handle; + } + _ => log::error!( + "[{}] Invalid handle kind '{:?}'", + self.id.to_string(), + handle.kind + ), + } + } + + fn get_hot_reload_cb(&self) -> Callable { + self.hot_reload_cb.clone() + } + + fn set_hot_reload_cb(&mut self, cb: Callable) { + self.hot_reload_cb = cb; + } + + fn build(&self) -> Actionable { + let shape = match self.shape { + ShapeType::Ball => SharedShape::ball(self.ball_radius), + ShapeType::Cuboid => SharedShape::cuboid( + self.cuboid_half_extents.x, + self.cuboid_half_extents.y, + self.cuboid_half_extents.z, + ), + }; + let collider = ColliderBuilder::new(shape) + .restitution(self.restitution) + .friction(self.friction) + .build(); + Actionable::Collider(collider) + } +} + +#[derive(Debug, GodotConvert, Var, Export)] +#[godot(via = GString)] +// https://docs.rs/rapier3d/latest/rapier3d/geometry/enum.ShapeType.html +pub enum ShapeType { + Ball, + Cuboid, +} diff --git a/addons/godot-rapier-3d/rust/src/objects/handle.rs b/addons/godot-rapier-3d/rust/src/objects/handle.rs new file mode 100644 index 0000000..f1e054d --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/objects/handle.rs @@ -0,0 +1,88 @@ +use rapier3d::dynamics::RigidBodyHandle; +use rapier3d::geometry::ColliderHandle; +use std::fmt; + +use crate::queue::Actionable; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum HandleKind { + RigidBodyHandle, + ColliderHandle, + Invalid, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Handle { + pub kind: HandleKind, + pub raw: [u32; 2], +} + +impl Handle { + pub fn new(kind: HandleKind, raw: [u32; 2]) -> Self { + Handle { kind, raw } + } + + pub fn invalid() -> Self { + Handle { + kind: HandleKind::Invalid, + raw: [u32::MAX, u32::MAX], + } + } +} + +impl fmt::Display for Handle { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.raw) + } +} + +impl From<&RigidBodyHandle> for Handle { + fn from(handle: &RigidBodyHandle) -> Self { + let parts = handle.into_raw_parts(); + Handle { + kind: HandleKind::RigidBodyHandle, + raw: [parts.0, parts.1], + } + } +} + +impl From<&ColliderHandle> for Handle { + fn from(handle: &ColliderHandle) -> Self { + let parts = handle.into_raw_parts(); + Handle { + kind: HandleKind::ColliderHandle, + raw: [parts.0, parts.1], + } + } +} + +impl From for RigidBodyHandle { + fn from(handle: Handle) -> Self { + RigidBodyHandle::from_raw_parts(handle.raw[0], handle.raw[1]) + } +} + +impl From for ColliderHandle { + fn from(handle: Handle) -> Self { + ColliderHandle::from_raw_parts(handle.raw[0], handle.raw[1]) + } +} + +impl From<[u32; 2]> for Handle { + fn from(raw: [u32; 2]) -> Self { + Handle { + kind: HandleKind::Invalid, + raw, + } + } +} + +impl From for Handle { + fn from(actionable: Actionable) -> Self { + match actionable { + Actionable::RigidBodyHandle(handle) => Handle::from(&handle), + Actionable::ColliderHandle(handle) => Handle::from(&handle), + _ => Handle::invalid(), + } + } +} diff --git a/addons/godot-rapier-3d/rust/src/objects/object_bridge.rs b/addons/godot-rapier-3d/rust/src/objects/object_bridge.rs new file mode 100644 index 0000000..0733034 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/objects/object_bridge.rs @@ -0,0 +1,99 @@ +use super::HandleKind; +use crate::queue::Actionable; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct ObjectBridge { + pub class: ObjectClass, + pub handle_kind: HandleKind, + pub object_kind: ObjectKind, +} + +impl ObjectBridge { + pub fn invalid() -> ObjectBridge { + ObjectBridge { + class: ObjectClass::RapierRigidBody3D, + handle_kind: HandleKind::Invalid, + object_kind: ObjectKind::Invalid, + } + } +} + +impl From for ObjectBridge { + fn from(object_kind: ObjectKind) -> Self { + match object_kind { + ObjectKind::RigidBody => ObjectBridge { + class: ObjectClass::RapierRigidBody3D, + handle_kind: HandleKind::RigidBodyHandle, + object_kind, + }, + ObjectKind::Collider => ObjectBridge { + class: ObjectClass::RapierCollider3D, + handle_kind: HandleKind::ColliderHandle, + object_kind, + }, + ObjectKind::Invalid => ObjectBridge::invalid(), + } + } +} + +impl From for ObjectBridge { + fn from(handle_kind: HandleKind) -> Self { + match handle_kind { + HandleKind::RigidBodyHandle => ObjectBridge::from(ObjectKind::RigidBody), + HandleKind::ColliderHandle => ObjectBridge::from(ObjectKind::Collider), + HandleKind::Invalid => ObjectBridge::invalid(), + } + } +} + +impl From<&Actionable> for ObjectBridge { + fn from(actionable: &Actionable) -> Self { + match actionable { + Actionable::RigidBody(_) | Actionable::RigidBodyHandle(_) => { + ObjectBridge::from(ObjectKind::RigidBody) + } + Actionable::Collider(_) + | Actionable::ColliderWithParent(_, _) + | Actionable::ColliderIDWithParentID(_, _) + | Actionable::ColliderHandle(_) => ObjectBridge::from(ObjectKind::Collider), + Actionable::NodePos(kind, _) => ObjectBridge::from(kind.clone()), + Actionable::Invalid => ObjectBridge::invalid(), + Actionable::Step => ObjectBridge::invalid(), + } + } +} + +#[derive(Debug, Clone)] +pub enum ObjectClass { + RapierRigidBody3D, + RapierCollider3D, +} + +#[derive(Eq, PartialEq, Debug, Hash, Clone)] +pub enum ObjectKind { + RigidBody, + Collider, + Invalid, +} + +impl From<&Actionable> for ObjectKind { + fn from(actionable: &Actionable) -> Self { + match actionable { + Actionable::RigidBody(_) | Actionable::RigidBodyHandle(_) => ObjectKind::RigidBody, + Actionable::Collider(_) + | Actionable::ColliderWithParent(_, _) + | Actionable::ColliderIDWithParentID(_, _) + | Actionable::ColliderHandle(_) => ObjectKind::Collider, + Actionable::NodePos(kind, _) => kind.clone(), + Actionable::Invalid => ObjectKind::Invalid, + Actionable::Step => ObjectKind::Invalid, + } + } +} + +impl fmt::Display for ObjectKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} diff --git a/addons/godot-rapier-3d/rust/src/objects/rigid_body.rs b/addons/godot-rapier-3d/rust/src/objects/rigid_body.rs new file mode 100644 index 0000000..f517536 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/objects/rigid_body.rs @@ -0,0 +1,122 @@ +use crate::queue::Actionable; +use crate::{ObjectKind, PhysicsObject}; +use godot::engine::notify::Node3DNotification; +use godot::engine::{INode3D, Node3D}; +use godot::prelude::*; +use rapier3d::dynamics::{RigidBodyBuilder, RigidBodyType}; +use rapier3d::math::Real; + +use super::{Handle, HandleKind}; + +#[derive(GodotClass, Debug)] +#[class(base = Node3D)] +pub struct RapierRigidBody3D { + #[export] + #[var(usage_flags = [EDITOR, STORAGE, READ_ONLY])] + pub id: GString, // https://crates.io/crates/cuid2 + pub handle: Handle, + #[export] + body_type: RBType, + #[export] + pub additional_mass: Real, + hot_reload_cb: Callable, + base: Base, +} + +#[godot_api] +impl INode3D for RapierRigidBody3D { + fn init(base: Base) -> Self { + Self { + id: GString::from(crate::cuid2()), + handle: Handle::invalid(), + body_type: RBType::Dynamic, + additional_mass: 0.0, + hot_reload_cb: Callable::invalid(), + base, + } + } + + fn on_notification(&mut self, what: Node3DNotification) { + match what { + Node3DNotification::EnterTree => self.on_enter_tree(), + Node3DNotification::ExitTree => self.on_exit_tree(), + Node3DNotification::TransformChanged => self.on_transform_changed(), + _ => {} + } + } +} + +#[godot_api] +impl RapierRigidBody3D { + #[func] + fn _on_hot_reload(&mut self) { + self.on_hot_reload(); + } +} + +impl PhysicsObject for RapierRigidBody3D { + fn get_kind(&self) -> ObjectKind { + ObjectKind::RigidBody + } + + fn get_cuid2(&self) -> String { + self.id.to_string() + } + + fn set_cuid2(&mut self, cuid2: String) { + self.id = GString::from(cuid2); + } + + fn get_handle(&self) -> Handle { + self.handle.clone() + } + + fn set_handle(&mut self, handle: Handle) { + match handle.kind { + HandleKind::RigidBodyHandle | HandleKind::Invalid => { + self.handle = handle; + } + _ => log::error!( + "[{}] Invalid handle kind '{:?}'", + self.id.to_string(), + handle.kind + ), + } + } + + fn get_hot_reload_cb(&self) -> Callable { + self.hot_reload_cb.clone() + } + + fn set_hot_reload_cb(&mut self, cb: Callable) { + self.hot_reload_cb = cb; + } + + fn build(&self) -> Actionable { + let rb = RigidBodyBuilder::new(self.body_type.clone().into()) + .additional_mass(self.additional_mass) + .build(); + Actionable::RigidBody(rb) + } +} + +#[derive(GodotConvert, Var, Export, Debug, Clone)] +#[godot(via = GString)] +// Mirror of https://docs.rs/rapier3d/latest/rapier3d/dynamics/enum.RigidBodyType.html +pub enum RBType { + Dynamic, + Fixed, + KinematicPositionBased, + KinematicVelocityBased, +} + +impl Into for RBType { + fn into(self) -> RigidBodyType { + match self { + RBType::Dynamic => RigidBodyType::Dynamic, + RBType::Fixed => RigidBodyType::Fixed, + RBType::KinematicPositionBased => RigidBodyType::KinematicPositionBased, + RBType::KinematicVelocityBased => RigidBodyType::KinematicVelocityBased, + } + } +} diff --git a/addons/godot-rapier-3d/rust/src/physics_pipeline.rs b/addons/godot-rapier-3d/rust/src/physics_pipeline.rs deleted file mode 100644 index e0779cb..0000000 --- a/addons/godot-rapier-3d/rust/src/physics_pipeline.rs +++ /dev/null @@ -1,209 +0,0 @@ -use crate::collider::RapierCollider3D; -use crate::physics_state::GR3DPhysicsState; -use crate::rigid_body::RapierRigidBody3D; -use godot::prelude::*; -use rapier3d::prelude::*; -use crate::log::LogLevel; - -pub struct GR3DPhysicsPipeline { - pub rigid_body_ids: Dictionary, // gd node instance_id <-> rapier rb_handle_to_id() - pub collider_ids: Dictionary, // gd node instance_id <-> rapier collider_handle_to_id() - physics_pipeline: PhysicsPipeline, - pub state: GR3DPhysicsState, - pub log_level: LogLevel, - physics_hooks: (), - event_handler: (), -} - -impl GR3DPhysicsPipeline { - pub fn new(log_level: LogLevel) -> Self { - Self { - rigid_body_ids: Dictionary::new(), - collider_ids: Dictionary::new(), - physics_pipeline: PhysicsPipeline::new(), - state: GR3DPhysicsState::default(), - log_level, - physics_hooks: (), - event_handler: (), - } - } - - pub fn step(&mut self) { - self.physics_pipeline.step( - &self.state.gravity, - &self.state.integration_parameters, - &mut self.state.island_manager, - &mut self.state.broad_phase, - &mut self.state.narrow_phase, - &mut self.state.rigid_body_set, - &mut self.state.collider_set, - &mut self.state.impulse_joint_set, - &mut self.state.multibody_joint_set, - &mut self.state.ccd_solver, - Some(&mut self.state.query_pipeline), - &self.physics_hooks, - &self.event_handler - ); - - self.sync_active_body_positions(); - } - - // Syncs all rigid body Godot positions to match Rapier positions - pub fn sync_all_body_positions(&mut self) { - let dynamic_bodies = self.state.rigid_body_set.iter(); - for (handle, rb) in dynamic_bodies { - self.sync_transform_to_rapier(handle, rb); - } - } - - // Syncs active rigid body Godot positions to match Rapier positions - pub fn sync_active_body_positions(&mut self) { - let active_dynamic_bodies = self.state.island_manager.active_dynamic_bodies(); - for active_body_handle in active_dynamic_bodies { - let rb = match self.state.rigid_body_set.get(*active_body_handle) { - Some(rb) => rb, - None => { - crate::error!(self.log_level => "Pipeline: could not find active body {:?}", active_body_handle); - continue; - } - }; - self.sync_transform_to_rapier(*active_body_handle, rb); - } - } - - // Changes godot transforms to match rapier transforms - pub fn sync_transform_to_rapier( - &self, - rigid_body_handle: RigidBodyHandle, - rigid_body: &RigidBody - ) { - let g_pos = crate::utils::pos_rapier_to_godot(*rigid_body.translation()); - let g_rot = crate::utils::rot_rapier_to_godot(*rigid_body.rotation()); - - let id = crate::utils::rb_handle_to_id(rigid_body_handle); - let instance_id_var: Variant = match self.rigid_body_ids.find_key_by_value(id.clone()) { - Some(instance_id) => instance_id, - None => { - crate::error!(self.log_level => "Pipeline: could not find instance_id for active body {:?}", id.clone()); - return; - } - }; - - let instance_id_int = instance_id_var.to_string().parse::().unwrap(); - let instance_id = InstanceId::from_i64(instance_id_int); - - let mut node: Gd = match Gd::try_from_instance_id(instance_id) { - Ok(node) => node, - _ => { - crate::error!(self.log_level => "Pipeline: could not find node for active body {:?}", instance_id); - return; - } - }; - - node.bind_mut().base_mut().set_notify_transform(false); - node.bind_mut().base_mut().set_global_position(g_pos); - node.bind_mut().base_mut().set_quaternion(g_rot); - node.bind_mut().base_mut().set_notify_transform(true); - } - - pub fn register_rigid_body(&mut self, class: &mut RapierRigidBody3D) -> RigidBodyHandle { - let instance_id = class.base().instance_id().to_i64(); - let mut rigid_body: RigidBody = class.build(); - rigid_body.user_data = u128::try_from(instance_id).unwrap(); - let handle = self.state.rigid_body_set.insert(rigid_body); - let id = crate::utils::rb_handle_to_id(handle); - self.rigid_body_ids.set(instance_id, id); - crate::debug!(self.log_level => "Pipeline: registered rigid body '{:?}'", handle); - handle - } - - pub fn unregister_rigid_body(&mut self, class: &mut RapierRigidBody3D) { - let instance_id = class.base().instance_id().to_i64(); - let handle = class.handle; - self.state.rigid_body_set.remove( - handle, - &mut self.state.island_manager, - &mut self.state.collider_set, - &mut self.state.impulse_joint_set, - &mut self.state.multibody_joint_set, - true // true = also remove colliders - ); - crate::debug!(self.log_level => "Pipeline: unregistered rigid body '{:?}'", handle); - self.rigid_body_ids.remove(instance_id); - } - - pub fn get_rigid_body_mut(&mut self, handle: RigidBodyHandle) -> Option<&mut RigidBody> { - self.state.rigid_body_set.get_mut(handle) - } - - pub fn register_collider(&mut self, class: &mut RapierCollider3D) -> ColliderHandle { - if self.collider_is_registered(class) { - return class.handle; - } - let instance_id = class.base().instance_id().to_i64(); - let mut collider: Collider = class.build(); - collider.user_data = u128::try_from(instance_id).unwrap(); - let handle = self.state.collider_set.insert(collider); - let id = crate::utils::collider_handle_to_id(handle); - self.collider_ids.set(instance_id, id); - crate::debug!(self.log_level => "Pipeline: registered collider '{:?}'", handle); - handle - } - - pub fn collider_is_registered(&self, class: &RapierCollider3D) -> bool { - let instance_id = class.base().instance_id().to_i64(); - let result = self.collider_ids.contains_key(instance_id); - if result { - crate::debug!(self.log_level => "Pipeline: collider '{:?}' already registered", class.handle); - } - result - } - - // Parent rigid_body must already exist in rigid_body_set when calling this - pub fn register_collider_with_parent( - &mut self, - class: &mut RapierCollider3D, - parent_handle: RigidBodyHandle - ) -> ColliderHandle { - if self.collider_is_registered(class) { - return class.handle; - } - let instance_id = class.base().instance_id().to_i64(); - let mut collider: Collider = class.build(); - collider.user_data = u128::try_from(instance_id).unwrap(); - let handle = self.state.collider_set.insert_with_parent( - collider, - parent_handle, - &mut self.state.rigid_body_set - ); - let id = crate::utils::collider_handle_to_id(handle); - self.collider_ids.set(instance_id, id); - crate::debug!(self.log_level => "Pipeline: registered collider '{:?}'", handle); - handle - } - - pub fn set_collider_parent( - &mut self, - collider: ColliderHandle, - parent: Option - ) { - self.state.collider_set.set_parent(collider, parent, &mut self.state.rigid_body_set); - } - - pub fn unregister_collider(&mut self, class: &mut RapierCollider3D) { - let instance_id = class.base().instance_id().to_i64(); - let handle = class.handle; - self.state.collider_set.remove( - handle, - &mut self.state.island_manager, - &mut self.state.rigid_body_set, - false - ); // false = don't wakeup parent rigid_body - self.collider_ids.remove(instance_id); - crate::debug!(self.log_level => "Pipeline: unregistered collider '{:?}'", handle); - } - - pub fn get_collider_mut(&mut self, handle: ColliderHandle) -> Option<&mut Collider> { - self.state.collider_set.get_mut(handle) - } -} diff --git a/addons/godot-rapier-3d/rust/src/pipeline.rs b/addons/godot-rapier-3d/rust/src/pipeline.rs new file mode 100644 index 0000000..454208d --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/pipeline.rs @@ -0,0 +1,165 @@ +use crate::objects::{Handle, HandleKind, ObjectBridge, RapierRigidBody3D}; +use crate::utils::{handle_to_instance_id, isometry_to_transform, node_from_instance_id}; +use crate::{LookupIdentifier, Lookups}; +use godot::obj::WithBaseField; +use rapier3d::math::Vector as RVector; +use rapier3d::prelude::*; +use std::collections::HashMap; +use std::fmt; + +mod debug_render_pipeline; +mod state; +pub use state::GR3DPhysicsState; + +pub struct GR3DPhysicsPipeline { + pub state: GR3DPhysicsState, + pub physics_pipeline: PhysicsPipeline, + pub physics_hooks: (), + pub event_handler: (), +} + +impl GR3DPhysicsPipeline { + pub fn new() -> Self { + Self { + state: GR3DPhysicsState::default(), + physics_pipeline: PhysicsPipeline::new(), + physics_hooks: (), + event_handler: (), + } + } + + // Returns the rapier position of a given object + pub fn get_object_position(&self, handle: Handle) -> Result, String> { + match handle.kind { + HandleKind::RigidBodyHandle => { + let rigid_body = self + .state + .rigid_body_set + .get(RigidBodyHandle::from(handle)) + .ok_or("Could not find rigid body in pipeline")?; + Ok(*rigid_body.position()) + } + HandleKind::ColliderHandle => { + let collider = self + .state + .collider_set + .get(ColliderHandle::from(handle)) + .ok_or("Could not find collider in pipeline")?; + Ok(*collider.position()) + } + _ => Err("Invalid handle type".to_string()), + } + } + + // Changes all RigidBody Godot node transforms to match Rapier transforms + pub fn sync_all_g2r(&self, lookups: &Lookups) { + let dynamic_bodies = self.state.rigid_body_set.iter(); + for (handle, rb) in dynamic_bodies { + self.sync_g2r(handle, rb, lookups) + .map_err(crate::handle_error) + .ok(); + } + } + + // Changes active RigidBody Godot node transforms to match Rapier transforms + pub fn sync_active_g2r(&self, lookups: &Lookups) { + let active_dynamic_bodies = self.state.island_manager.active_dynamic_bodies(); + for active_body_handle in active_dynamic_bodies { + match self.state.rigid_body_set.get(*active_body_handle) { + Some(rb) => self + .sync_g2r(*active_body_handle, rb, lookups) + .map_err(crate::handle_error) + .ok(), + None => { + log::error!( + "Pipeline: could not find active body {:?}", + active_body_handle + ); + continue; + } + }; + } + } + + // Changes specific RigidBody Godot node transform to match Rapier transform + pub fn sync_g2r( + &self, + handle: RigidBodyHandle, + rigid_body: &RigidBody, + lookups: &Lookups, + ) -> Result<(), String> { + let transform = isometry_to_transform(*rigid_body.position()); + let instance_id = handle_to_instance_id(Handle::from(&handle), lookups)?; + let mut node = node_from_instance_id::(instance_id)?; + node.bind_mut().base_mut().set_notify_transform(false); + node.bind_mut().base_mut().set_global_transform(transform); + node.bind_mut().base_mut().set_notify_transform(true); + Ok(()) + } + + pub fn get_debug_info(&self) -> Result { + let engine = crate::engine::get_engine()?; + let mut ret_map = HashMap::new(); + let rb_key = format!("Rigid bodies ({})", self.state.rigid_body_set.len()); + let col_key = format!("Colliders ({})", self.state.collider_set.len()); + + let handles_to_entries = |handle: Handle| { + let object_bridge = ObjectBridge::from(handle.kind.clone()); + let pos = self.get_object_position(handle.clone()); + if pos.is_err() { + return DebugEntry { + id: "Unknown".to_string(), + pos: RVector::zeros(), + rot: (0.0, 0.0, 0.0), + }; + } + match engine.bind().lookups.get( + object_bridge.object_kind, + LookupIdentifier::Handle, + &handle.to_string(), + ) { + Some(result) => DebugEntry { + id: result.cuid2.clone(), + pos: pos.clone().unwrap().translation.vector, + rot: pos.clone().unwrap().rotation.euler_angles(), + }, + None => DebugEntry { + id: "Unknown".to_string(), + pos: RVector::zeros(), + rot: (0.0, 0.0, 0.0), + }, + } + }; + + let rigid_body_entries = self + .state + .rigid_body_set + .iter() + .map(|(handle, _)| handles_to_entries(Handle::from(&handle))) + .collect::>(); + + let collider_entries = self + .state + .collider_set + .iter() + .map(|(handle, _)| handles_to_entries(Handle::from(&handle))) + .collect::>(); + + ret_map.insert(rb_key, rigid_body_entries); + ret_map.insert(col_key, collider_entries); + + Ok(format!("{:#?}", ret_map)) + } +} + +pub struct DebugEntry { + pub id: String, + pub pos: RVector, + pub rot: (f32, f32, f32), +} + +impl fmt::Debug for DebugEntry { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: pos: {:?}, rot: {:?}", self.id, self.pos, self.rot) + } +} diff --git a/addons/godot-rapier-3d/rust/src/debug_render_pipeline.rs b/addons/godot-rapier-3d/rust/src/pipeline/debug_render_pipeline.rs similarity index 76% rename from addons/godot-rapier-3d/rust/src/debug_render_pipeline.rs rename to addons/godot-rapier-3d/rust/src/pipeline/debug_render_pipeline.rs index e90a598..c3234bc 100644 --- a/addons/godot-rapier-3d/rust/src/debug_render_pipeline.rs +++ b/addons/godot-rapier-3d/rust/src/pipeline/debug_render_pipeline.rs @@ -1,4 +1,3 @@ -use crate::physics_state::GR3DPhysicsState; use godot::engine::IRefCounted; use godot::engine::RefCounted; use godot::prelude::*; @@ -19,7 +18,7 @@ impl IRefCounted for RapierDebugRenderPipeline { Self { debug_render_pipeline: DebugRenderPipeline::new( DebugRenderStyle::default(), - DebugRenderMode::COLLIDER_SHAPES + DebugRenderMode::COLLIDER_SHAPES, ), debug_render_backend: RapierDebugRenderBackend::new(), base, @@ -36,17 +35,20 @@ impl RapierDebugRenderPipeline { #[func] pub fn render_colliders(&mut self) { - let engine = crate::get_engine!(); - let bind = engine.bind(); - let state: &GR3DPhysicsState = &bind.pipeline.state; - let rigid_body_set: &RigidBodySet = &state.rigid_body_set; - let collider_set: &ColliderSet = &state.collider_set; + self.try_render_colliders() + .map_err(crate::handle_error) + .ok(); + } + pub fn try_render_colliders(&mut self) -> Result<(), String> { + let engine = crate::get_engine()?; + let bind = engine.bind(); self.debug_render_pipeline.render_colliders( &mut self.debug_render_backend, - rigid_body_set, - collider_set + &bind.pipeline.state.rigid_body_set, + &bind.pipeline.state.collider_set, ); + Ok(()) } } @@ -68,7 +70,7 @@ impl DebugRenderBackend for RapierDebugRenderBackend { _object: DebugRenderObject<'_>, a: Point, b: Point, - color: [f32; 4] + color: [f32; 4], ) { let debugger_node = &mut self.debugger_node; match debugger_node { @@ -77,17 +79,14 @@ impl DebugRenderBackend for RapierDebugRenderBackend { Variant::from(Vector3::new(a.x as f32, a.y as f32, a.z as f32)), Variant::from(Vector3::new(b.x as f32, b.y as f32, b.z as f32)), Variant::from( - Color::from_hsv( - color[0] as f64, - color[1] as f64, - color[2] as f64 - ).with_alpha(color[3]) + Color::from_hsv(color[0] as f64, color[1] as f64, color[2] as f64) + .with_alpha(color[3]), ), ]; node.call(StringName::from("_draw_line"), args); } None => { - godot_error!( + log::error!( "RapierDebugRenderBackend::draw_line - Trying to draw_line but no debugger node registered" ); } diff --git a/addons/godot-rapier-3d/rust/src/physics_state.rs b/addons/godot-rapier-3d/rust/src/pipeline/state.rs similarity index 55% rename from addons/godot-rapier-3d/rust/src/physics_state.rs rename to addons/godot-rapier-3d/rust/src/pipeline/state.rs index 26d6883..8d4a58a 100644 --- a/addons/godot-rapier-3d/rust/src/physics_state.rs +++ b/addons/godot-rapier-3d/rust/src/pipeline/state.rs @@ -17,17 +17,15 @@ pub struct GR3DPhysicsState { } impl GR3DPhysicsState { - pub fn pack(&self) -> Vec { - bincode::serialize(self).unwrap() + pub fn pack(&self) -> Result, bincode::Error> { + bincode::serialize(self) } - pub fn unpack(&self, data: &[u8]) -> Self { - bincode::deserialize(data).unwrap() + pub fn unpack(&self, data: &[u8]) -> Result { + bincode::deserialize(data) } -} -impl Default for GR3DPhysicsState { - fn default() -> Self { + pub fn default() -> Self { Self { rigid_body_set: RigidBodySet::new(), collider_set: ColliderSet::new(), @@ -43,3 +41,21 @@ impl Default for GR3DPhysicsState { } } } + +// impl Default for GR3DPhysicsState { +// fn default() -> Self { +// Self { +// rigid_body_set: RigidBodySet::new(), +// collider_set: ColliderSet::new(), +// gravity: Vector::new(0.0, -9.81, 0.0), +// island_manager: IslandManager::new(), +// integration_parameters: IntegrationParameters::default(), +// broad_phase: BroadPhase::new(), +// narrow_phase: NarrowPhase::new(), +// impulse_joint_set: ImpulseJointSet::new(), +// multibody_joint_set: MultibodyJointSet::new(), +// ccd_solver: CCDSolver::new(), +// query_pipeline: QueryPipeline::new(), +// } +// } +// } diff --git a/addons/godot-rapier-3d/rust/src/queue.rs b/addons/godot-rapier-3d/rust/src/queue.rs new file mode 100644 index 0000000..0d9bf54 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue.rs @@ -0,0 +1,237 @@ +use std::collections::HashMap; + +use crate::lookups::Lookups; +use crate::queue::process::process_sync_action; +use crate::{GR3DPhysicsPipeline, GR3DPhysicsState, ObjectKind}; +use process::{process_insert_action, process_parent_action, process_remove_action}; + +mod action; +mod actionable; +mod process; +pub use action::Action; +pub use actionable::Actionable; + +use self::process::process_sim_action; + +// Purpose of this struct is to store pending actions that need to be processed by the physics pipeline. +// All modifications of the pipeline need to be done in a deterministic order to ensure determinism of the simulation. +// Note: all actions are r2g (change rapier to match godot) since only Rapier modifications need to be queued. + +pub struct ActionQueue { + pub queues: HashMap>, +} + +impl ActionQueue { + pub fn new() -> Self { + Self { + queues: HashMap::from([ + (QueueName::Insert, Vec::new()), + (QueueName::Remove, Vec::new()), + (QueueName::Parent, Vec::new()), + (QueueName::Sync, Vec::new()), + (QueueName::Sim, Vec::new()), + ]), + } + } + + pub fn push_step_action(&mut self) { + self.add_action(Action::step(), &QueueName::Sim); + } + + pub fn add_action(&mut self, action: Action, queue: &QueueName) { + let queue = self.queues.get_mut(queue).expect("Invalid queue name"); + queue.push(action); + } + + pub fn _process(&mut self, pipeline: &mut GR3DPhysicsPipeline, lookups: &mut Lookups) { + self.sort(); + let state = &mut pipeline.state; + self.process_remove_actions(state, lookups).ok(); + self.process_insert_actions(state, lookups).ok(); + self.process_parent_actions(state, lookups).ok(); + self.process_sync_actions(state, lookups).ok(); + self.process_sim_actions(pipeline, lookups).ok(); + } + + fn sort(&mut self) { + for (queue_name, queue) in self.queues.iter_mut() { + if queue.is_empty() { + continue; + } + if queue_name == &QueueName::Sim { + continue; + } + log::trace!( + "Sorting queue '{:?}': {:#?}", + queue_name, + debug_queue(queue) + ); + queue.sort_unstable(); + log::trace!("Sorted queue '{:?}': {:#?}", queue_name, debug_queue(queue)); + } + } + + fn process_insert_actions( + &mut self, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, + ) -> Result<(), String> { + let queue = self + .queues + .get_mut(&QueueName::Insert) + .ok_or("Insert queue is None")?; + if queue.is_empty() { + Ok(()) + } else { + for action in queue.drain(..) { + let cuid = action.inner_cuid.clone(); + let object_kind = ObjectKind::from(&action.data); + log::trace!("[AQ]: Inserting {} {:?}", object_kind, cuid); + match process_insert_action(action, state, lookups) { + Ok(_) => log::debug!("[AQ]: Inserted {} {:?}", object_kind, cuid), + Err(e) => { + log::error!("[AQ]: Error inserting {} {:?}: {}", object_kind, cuid, e); + continue; + } + } + } + Ok(()) + } + } + + fn process_remove_actions( + &mut self, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, + ) -> Result<(), String> { + let queue = self + .queues + .get_mut(&QueueName::Remove) + .ok_or("Remove queue is None")?; + if queue.is_empty() { + Ok(()) + } else { + for action in queue.drain(..) { + let cuid = action.inner_cuid.clone(); + let object_kind = ObjectKind::from(&action.data); + log::trace!("[AQ]: Removing {} {:?}", object_kind, cuid); + match process_remove_action(action, state, lookups) { + Ok(_) => log::debug!("[AQ]: Removed {} {:?}", object_kind, cuid), + Err(e) => { + log::error!("[AQ]: Error removing {} {:?}: {}", object_kind, cuid, e); + continue; + } + } + } + Ok(()) + } + } + + fn process_parent_actions( + &mut self, + state: &mut GR3DPhysicsState, + lookups: &Lookups, + ) -> Result<(), String> { + let queue = self + .queues + .get_mut(&QueueName::Parent) + .ok_or("Parent queue is None")?; + if queue.is_empty() { + Ok(()) + } else { + for action in queue.drain(..) { + let cuid = action.inner_cuid.clone(); + let object_kind = ObjectKind::from(&action.data); + log::trace!("[AQ]: Parenting {} {:?}", object_kind, cuid); + match process_parent_action(action, state, lookups) { + Ok(has_parent) => { + let op = if has_parent { "Parented" } else { "Unparented" }; + log::debug!("[AQ]: {} {} {:?}", op, object_kind, cuid) + } + Err(e) => { + log::error!("[AQ]: Error parenting {} {:?}: {}", object_kind, cuid, e); + continue; + } + } + } + Ok(()) + } + } + + fn process_sync_actions( + &mut self, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, + ) -> Result<(), String> { + let queue = self + .queues + .get_mut(&QueueName::Sync) + .ok_or("Sync queue is None")?; + if queue.is_empty() { + Ok(()) + } else { + for action in queue.drain(..) { + let cuid = action.inner_cuid.clone(); + let object_kind = ObjectKind::from(&action.data); + log::trace!("[AQ]: Syncing {} {:?}", object_kind, cuid); + match process_sync_action(action, state, lookups) { + Ok(_) => log::trace!("[AQ]: Synced {} {:?}", object_kind, cuid), + Err(e) => { + log::error!("[AQ]: Error syncing {} {:?}: {}", object_kind, cuid, e); + continue; + } + } + } + Ok(()) + } + } + + fn process_sim_actions( + &mut self, + pipeline: &mut GR3DPhysicsPipeline, + lookups: &Lookups, + ) -> Result<(), String> { + let queue = self + .queues + .get_mut(&QueueName::Sim) + .ok_or("Sim queue is None")?; + if queue.is_empty() { + Ok(()) + } else { + for action in queue.drain(..) { + log::trace!("[AQ]: Simulating"); + match process_sim_action(action, pipeline, lookups) { + Ok(_) => log::trace!("[AQ]: Simulated"), + Err(e) => { + log::error!("[AQ]: Error while simulating: {}", e); + continue; + } + } + } + Ok(()) + } + } +} + +#[derive(Eq, PartialEq, Debug, Hash)] +pub enum QueueName { + Insert, + Remove, + Parent, + Sync, + Sim, +} + +fn debug_queue(actions: &Vec) -> Vec { + actions + .iter() + .map(|a| { + format!( + "{}, {}, {}", + ObjectKind::from(&a.data), + a.inner_cuid.clone(), + a.data + ) + }) + .collect() +} diff --git a/addons/godot-rapier-3d/rust/src/queue/action.rs b/addons/godot-rapier-3d/rust/src/queue/action.rs new file mode 100644 index 0000000..1f8bb97 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/action.rs @@ -0,0 +1,51 @@ +use core::fmt::Debug; +use std::any::type_name; +use std::cmp::Ordering; +use std::fmt::{self, Display, Formatter}; + +use super::Actionable; + +#[derive(Debug)] +pub struct Action { + pub inner_cuid: String, // cuid2 of inner data + pub inner_iid: i64, // instance_id of inner data + pub data: Actionable, +} + +impl Action { + pub fn step() -> Self { + Self { + inner_cuid: "step".to_string(), + inner_iid: 0, + data: Actionable::Step, + } + } +} + +impl Display for Action { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "Action: {} {}", self.inner_cuid, type_name::()) + } +} +impl Eq for Action {} +impl PartialEq for Action { + fn eq(&self, other: &Self) -> bool { + self.inner_cuid == other.inner_cuid + } +} +impl PartialOrd for Action { + fn partial_cmp(&self, other: &Self) -> Option { + Some( + self.data + .cmp(&other.data) + .then_with(|| self.inner_cuid.cmp(&other.inner_cuid)), + ) + } +} +impl Ord for Action { + fn cmp(&self, other: &Self) -> Ordering { + self.data + .cmp(&other.data) + .then_with(|| self.inner_cuid.cmp(&other.inner_cuid)) + } +} diff --git a/addons/godot-rapier-3d/rust/src/queue/actionable.rs b/addons/godot-rapier-3d/rust/src/queue/actionable.rs new file mode 100644 index 0000000..4799692 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/actionable.rs @@ -0,0 +1,154 @@ +use std::cmp::Ordering; +use std::fmt; + +use crate::objects::{Handle, HandleKind}; +use crate::ObjectKind; +use rapier3d::dynamics::{RigidBody, RigidBodyHandle}; +use rapier3d::geometry::{Collider, ColliderHandle}; +use rapier3d::math::{Isometry, Real}; + +pub enum Actionable { + RigidBody(RigidBody), + RigidBodyHandle(RigidBodyHandle), + Collider(Collider), + ColliderWithParent(Collider, RigidBodyHandle), + ColliderIDWithParentID(String, Option), + ColliderHandle(ColliderHandle), + NodePos(ObjectKind, Isometry), + Step, + Invalid, +} + +impl Eq for Actionable {} +impl PartialEq for Actionable { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::RigidBody(_), Self::RigidBody(_)) => true, + (Self::RigidBodyHandle(_), Self::RigidBodyHandle(_)) => true, + (Self::Collider(_), Self::Collider(_)) => true, + (Self::ColliderWithParent(_, _), Self::ColliderWithParent(_, _)) => true, + (Self::ColliderIDWithParentID(_, _), Self::ColliderIDWithParentID(_, _)) => true, + (Self::ColliderHandle(_), Self::ColliderHandle(_)) => true, + (Self::NodePos(ObjectKind::RigidBody, _), Self::NodePos(ObjectKind::RigidBody, _)) => { + true + } + (Self::NodePos(ObjectKind::Collider, _), Self::NodePos(ObjectKind::Collider, _)) => { + true + } + (Self::NodePos(ObjectKind::Invalid, _), Self::NodePos(ObjectKind::Invalid, _)) => true, + _ => false, + } + } +} + +// Order RigidBodies before Colliders so they are registered first +// (required for parenting colliders to rigid bodies when registering them) +impl PartialOrd for Actionable { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + ( + Self::RigidBody(_), + Self::Collider(_) + | Self::ColliderWithParent(_, _) + | Self::ColliderIDWithParentID(_, _), + ) => Some(Ordering::Less), + ( + Self::Collider(_) + | Self::ColliderWithParent(_, _) + | Self::ColliderIDWithParentID(_, _), + Self::RigidBody(_), + ) => Some(Ordering::Greater), + (Self::NodePos(ObjectKind::RigidBody, _), Self::NodePos(ObjectKind::Collider, _)) => { + Some(Ordering::Less) + } + (Self::NodePos(ObjectKind::Collider, _), Self::NodePos(ObjectKind::RigidBody, _)) => { + Some(Ordering::Greater) + } + _ => None, + } + } +} +impl Ord for Actionable { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + ( + Self::RigidBody(_), + Self::Collider(_) + | Self::ColliderWithParent(_, _) + | Self::ColliderIDWithParentID(_, _), + ) => Ordering::Less, + ( + Self::Collider(_) + | Self::ColliderWithParent(_, _) + | Self::ColliderIDWithParentID(_, _), + Self::RigidBody(_), + ) => Ordering::Greater, + (Self::NodePos(ObjectKind::RigidBody, _), Self::NodePos(ObjectKind::Collider, _)) => { + Ordering::Less + } + (Self::NodePos(ObjectKind::Collider, _), Self::NodePos(ObjectKind::RigidBody, _)) => { + Ordering::Greater + } + _ => Ordering::Equal, + } + } +} + +impl fmt::Debug for Actionable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::RigidBody(rb) => write!(f, "RigidBody: {:?}", rb), + Self::RigidBodyHandle(handle) => write!(f, "RigidBodyHandle: {:?}", handle), + Self::Collider(col) => write!(f, "Collider: {:?}", col.shared_shape()), + Self::ColliderWithParent(c, h) => { + write!(f, "ColliderWithParent: {:?} {:?}", c.shared_shape(), h) + } + Self::ColliderIDWithParentID(handle, parent) => { + write!(f, "ColliderIDWithParentID: {:?} {:?}", handle, parent) + } + Self::ColliderHandle(handle) => write!(f, "ColliderHandle: {:?}", handle), + Self::NodePos(kind, pos) => write!(f, "NodePos: {:?} {:?}", kind, pos), + Self::Step => write!(f, "Step"), + Self::Invalid => write!(f, "Invalid"), + } + } +} + +impl fmt::Display for Actionable { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::RigidBody(_) => write!(f, "Actionable::RigidBody"), + Self::RigidBodyHandle(_) => write!(f, "Actionable::RigidBodyHandle"), + Self::Collider(_) => write!(f, "Actionable::Collider"), + Self::ColliderWithParent(_, _) => write!(f, "Actionable::ColliderWithParent"), + Self::ColliderIDWithParentID(_, _) => { + write!(f, "Actionable::ColliderIDWithParentID") + } + Self::ColliderHandle(_) => write!(f, "Actionable::ColliderHandle"), + Self::NodePos(_, _) => write!(f, "Actionable::NodePos"), + Self::Step => write!(f, "Actionable::Step"), + Self::Invalid => write!(f, "Actionable::Invalid"), + } + } +} + +impl From for Actionable { + fn from(handle: Handle) -> Self { + match handle.kind { + HandleKind::RigidBodyHandle => { + Actionable::RigidBodyHandle(RigidBodyHandle::from(handle)) + } + HandleKind::ColliderHandle => Actionable::ColliderHandle(ColliderHandle::from(handle)), + HandleKind::Invalid => Actionable::Invalid, + } + } +} + +impl From for Collider { + fn from(actionable: Actionable) -> Self { + match actionable { + Actionable::Collider(col) => col, + _ => panic!("Actionable::Collider expected"), + } + } +} diff --git a/addons/godot-rapier-3d/rust/src/queue/process.rs b/addons/godot-rapier-3d/rust/src/queue/process.rs new file mode 100644 index 0000000..8c67bd2 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/process.rs @@ -0,0 +1,141 @@ +use rapier3d::dynamics::RigidBodyHandle; +use rapier3d::geometry::ColliderHandle; + +use self::add_or_remove::*; +use self::sim::*; +use self::sync::*; +use crate::objects::{Handle, ObjectBridge}; +use crate::queue::Action; +use crate::GR3DPhysicsPipeline; +use crate::LookupIdentifier; +use crate::ObjectKind; +use crate::{GR3DPhysicsState, Lookups}; + +use super::Actionable; + +mod add_or_remove; +mod sim; +mod sync; + +pub fn process_insert_action( + action: Action, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, +) -> Result<(), String> { + let object_bridge = ObjectBridge::from(&action.data); + let cuid2 = ensure_unique_cuid2(action.inner_cuid, lookups); + let handle = insert_into_set(action.data, state)?; + let iid = action.inner_iid.clone(); + attach_handle_to_node(object_bridge.object_kind.clone(), handle.clone(), iid)?; + insert_lookup(object_bridge.object_kind, lookups, cuid2, handle.raw, iid)?; + Ok(()) +} + +pub fn process_remove_action( + action: Action, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, +) -> Result<(), String> { + remove_from_set(action.data, state)?; + remove_lookup(action.inner_cuid, lookups)?; + Ok(()) +} + +pub fn process_parent_action( + action: Action, + state: &mut GR3DPhysicsState, + lookups: &Lookups, +) -> Result { + match action.data { + Actionable::ColliderIDWithParentID(col_id, rb_parent_id) => { + let col_handle = match lookups.get(ObjectKind::Collider, LookupIdentifier::ID, &col_id) + { + Some(id_bridge) => ColliderHandle::from(Handle::from(id_bridge.handle_raw)), + None => { + return Err(format!( + "Could not find collider handle for '{}' in lookups", + col_id + )); + } + }; + + let rb_parent_handle = match rb_parent_id { + Some(parent_id) => { + match lookups.get(ObjectKind::RigidBody, LookupIdentifier::ID, &parent_id) { + Some(id_bridge) => { + Some(RigidBodyHandle::from(Handle::from(id_bridge.handle_raw))) + } + None => { + return Err(format!( + "Could not find rigid body handle for '{}' in lookups", + parent_id + )); + } + } + } + None => None, + }; + + log::trace!( + "[AQ]: Setting parent for collider: {:?} {:?}", + col_handle, + rb_parent_handle + ); + state + .collider_set + .set_parent(col_handle, rb_parent_handle, &mut state.rigid_body_set); + Ok(rb_parent_handle.is_some()) + } + _ => Err(format!( + "[AQ]: Invalid Actionable passed when trying to set collider parent: '{}'", + action.data + )), + } +} + +pub fn process_sync_action( + action: Action, + state: &mut GR3DPhysicsState, + lookups: &mut Lookups, +) -> Result<(), String> { + let object_bridge = ObjectBridge::from(&action.data); + match action.data { + Actionable::NodePos(object_kind, position) => { + let id_bridge = lookups + .get( + object_kind.clone(), + crate::LookupIdentifier::ID, + &action.inner_cuid, + ) + .ok_or("Could not find handle in lookups")?; + + let handle = Handle { + kind: object_bridge.handle_kind, + raw: id_bridge.handle_raw, + }; + + set_object_position(handle, position, false, state)?; + } + _ => { + return Err("Invalid Actionable type".to_string()); + } + } + Ok(()) +} + +pub fn process_sim_action( + action: Action, + pipeline: &mut GR3DPhysicsPipeline, + lookups: &Lookups, +) -> Result<(), String> { + match action.data { + Actionable::Step => { + step(pipeline); + pipeline.sync_active_g2r(lookups) + } + _ => { + return Err("Invalid Actionable type".to_string()); + } + } + Ok(()) +} diff --git a/addons/godot-rapier-3d/rust/src/queue/process/add_or_remove.rs b/addons/godot-rapier-3d/rust/src/queue/process/add_or_remove.rs new file mode 100644 index 0000000..475ae6d --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/process/add_or_remove.rs @@ -0,0 +1,122 @@ +use crate::objects::{Handle, RapierCollider3D, RapierRigidBody3D}; +use crate::queue::Actionable; +use crate::utils::node_from_instance_id; +use crate::{GR3DPhysicsState, IDBridge, Lookups, ObjectKind, PhysicsObject}; +use godot::obj::InstanceId; +use godot::prelude::*; + +// Returns input cuid2 if the CUID2 is unique, otherwise generates and returns a new one +pub fn ensure_unique_cuid2(cuid2: String, lookups: &Lookups) -> String { + match lookups.is_cuid2_unique(&cuid2) { + true => cuid2, + false => { + log::warn!("CUID2 collision, generating new one for {:?}", cuid2); + crate::cuid2() + } + } +} + +pub fn insert_into_set(object: Actionable, state: &mut GR3DPhysicsState) -> Result { + match object { + Actionable::RigidBody(rb) => { + let handle = state.rigid_body_set.insert(rb); + Ok(Handle::from(&handle)) + } + Actionable::Collider(col) => { + let handle = state.collider_set.insert(col); + Ok(Handle::from(&handle)) + } + Actionable::ColliderWithParent(col, rb_parent_handle) => { + let handle = state.collider_set.insert_with_parent( + col, + rb_parent_handle, + &mut state.rigid_body_set, + ); + Ok(Handle::from(&handle)) + } + _ => Err(format!("[AQ]: Could not insert object '{:?}'", object)), + } +} + +pub fn remove_from_set(object: Actionable, state: &mut GR3DPhysicsState) -> Result<(), String> { + match object { + Actionable::RigidBodyHandle(handle) => { + state.rigid_body_set.remove( + handle, + &mut state.island_manager, + &mut state.collider_set, + &mut state.impulse_joint_set, + &mut state.multibody_joint_set, + true, + ); + Ok(()) + } + Actionable::ColliderHandle(handle) => { + state.collider_set.remove( + handle, + &mut state.island_manager, + &mut state.rigid_body_set, + false, + ); + Ok(()) + } + Actionable::Invalid => Ok(()), + _ => Err(format!("[AQ]: Could not remove object '{:?}'", object)), + } +} + +pub fn attach_handle_to_node( + object_kind: ObjectKind, + handle: Handle, + instance_id: i64, +) -> Result<(), String> { + let iid = InstanceId::from_i64(instance_id); + match object_kind { + ObjectKind::RigidBody => { + let mut node = node_from_instance_id::(iid)?; + node.bind_mut().set_handle(handle); + node.bind_mut() + .base_mut() + .try_call_deferred( + StringName::from("set_notify_transform"), + &[Variant::from(true)], + ) + .map_err(|e| format!("[AQ]: Could not set notify transform on node: {:?}", e))?; + Ok(()) + } + ObjectKind::Collider => { + let mut node = node_from_instance_id::(iid)?; + node.bind_mut().set_handle(handle); + node.bind_mut() + .base_mut() + .try_call_deferred( + StringName::from("set_notify_transform"), + &[Variant::from(true)], + ) + .map_err(|e| format!("[AQ]: Could not set notify transform on node: {:?}", e))?; + Ok(()) + } + _ => Err(format!( + "[AQ]: Could not attach handle to '{:?}' node", + object_kind + )), + } +} + +pub fn insert_lookup( + object_kind: ObjectKind, + lookups: &mut Lookups, + cuid: String, + handle_raw: [u32; 2], + instance_id: i64, +) -> Result<(), String> { + let id_bridge = IDBridge::new(cuid, handle_raw, instance_id); + id_bridge.is_valid()?; + lookups.insert(object_kind, id_bridge)?; + Ok(()) +} + +pub fn remove_lookup(cuid: String, lookups: &mut Lookups) -> Result<(), String> { + lookups.remove(cuid)?; + Ok(()) +} diff --git a/addons/godot-rapier-3d/rust/src/queue/process/sim.rs b/addons/godot-rapier-3d/rust/src/queue/process/sim.rs new file mode 100644 index 0000000..0b07ab2 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/process/sim.rs @@ -0,0 +1,19 @@ +use crate::GR3DPhysicsPipeline; + +pub fn step(pipeline: &mut GR3DPhysicsPipeline) { + pipeline.physics_pipeline.step( + &pipeline.state.gravity, + &pipeline.state.integration_parameters, + &mut pipeline.state.island_manager, + &mut pipeline.state.broad_phase, + &mut pipeline.state.narrow_phase, + &mut pipeline.state.rigid_body_set, + &mut pipeline.state.collider_set, + &mut pipeline.state.impulse_joint_set, + &mut pipeline.state.multibody_joint_set, + &mut pipeline.state.ccd_solver, + Some(&mut pipeline.state.query_pipeline), + &pipeline.physics_hooks, + &pipeline.event_handler, + ); +} diff --git a/addons/godot-rapier-3d/rust/src/queue/process/sync.rs b/addons/godot-rapier-3d/rust/src/queue/process/sync.rs new file mode 100644 index 0000000..caaf129 --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/queue/process/sync.rs @@ -0,0 +1,33 @@ +use rapier3d::dynamics::RigidBodyHandle; +use rapier3d::geometry::ColliderHandle; +use rapier3d::math::{Isometry, Real}; + +use crate::objects::{Handle, HandleKind}; +use crate::GR3DPhysicsState; + +pub fn set_object_position( + handle: Handle, + position: Isometry, + wake_up: bool, + state: &mut GR3DPhysicsState, +) -> Result<(), String> { + match handle.kind { + HandleKind::RigidBodyHandle => { + let rigid_body = state + .rigid_body_set + .get_mut(RigidBodyHandle::from(handle)) + .ok_or("Could not find rigid body in pipeline")?; + rigid_body.set_position(position, wake_up); + Ok(()) + } + HandleKind::ColliderHandle => { + let collider = state + .collider_set + .get_mut(ColliderHandle::from(handle)) + .ok_or("Could not find collider in pipeline")?; + collider.set_position(position); + Ok(()) + } + _ => Err("Invalid handle type".to_string()), + } +} diff --git a/addons/godot-rapier-3d/rust/src/rigid_body.rs b/addons/godot-rapier-3d/rust/src/rigid_body.rs deleted file mode 100644 index 0d8274a..0000000 --- a/addons/godot-rapier-3d/rust/src/rigid_body.rs +++ /dev/null @@ -1,210 +0,0 @@ -use godot::builtin::Callable; -use godot::engine::notify::Node3DNotification; -use godot::engine::GDExtensionManager; -use godot::engine::INode3D; -use godot::engine::Node3D; -use godot::prelude::*; -use rapier3d::prelude::*; - -#[derive(GodotClass)] -#[class(base = Node3D)] -pub struct RapierRigidBody3D { - #[var] - pub id: Array, // RigidBodyHandle::into_raw_parts - pub handle: RigidBodyHandle, - #[export] - body_type: RigidBodyType, - #[export] - pub additional_mass: Real, - hot_reload_cb: Callable, - base: Base, -} - -#[godot_api] -impl INode3D for RapierRigidBody3D { - fn init(base: Base) -> Self { - Self { - id: Array::new(), - handle: RigidBodyHandle::invalid(), - body_type: RigidBodyType::Dynamic, - additional_mass: 0.0, - hot_reload_cb: Callable::invalid(), - base, - } - } - - fn on_notification(&mut self, what: Node3DNotification) { - match what { - Node3DNotification::EnterTree => self.on_enter_tree(), - Node3DNotification::ExitTree => self.on_exit_tree(), - Node3DNotification::TransformChanged => self.on_transform_changed(), - _ => {} - } - } -} - -// unsafe impl Send for RapierRigidBody3D {} -// unsafe impl Sync for RapierRigidBody3D {} - -#[godot_api] -impl RapierRigidBody3D { - fn register(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - let handle = bind.pipeline.register_rigid_body(self); - self.handle = handle; - self.id = crate::utils::rb_handle_to_id(handle); - let rigid_body = match bind.pipeline.get_rigid_body_mut(self.handle) { - Some(rigid_body) => rigid_body, - None => { - godot_error!( - "RapierRigidBody3D on_enter_tree - could not find rigid body in pipeline after registering" - ); - return; - } - }; - - self.sync_transforms_to_godot(rigid_body, false); - crate::debug!(bind; "RapierRigidBody3D registered {:?}", self.handle.clone()); - } - - fn unregister(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - bind.pipeline.unregister_rigid_body(self); - crate::debug!(bind; "RapierRigidBody3D unregistered {:?}", self.handle.clone()); - self.handle = RigidBodyHandle::invalid(); - self.id = Array::new(); - } - - fn reregister(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - bind.pipeline.unregister_rigid_body(self); - let handle = bind.pipeline.register_rigid_body(self); - self.handle = handle; - self.id = crate::utils::rb_handle_to_id(handle); - let rigid_body = match bind.pipeline.get_rigid_body_mut(self.handle) { - Some(collider) => collider, - None => { - crate::error!(bind; "RapierRigidBody3D reregister - could not find rigid body {:?} in pipeline", self.handle); - return; - } - }; - self.sync_transforms_to_godot(rigid_body, false); - crate::debug!(bind; "RapierRigidBody3D reregistered {:?}", handle); - } - - fn attach_extensions_reloaded_signal(&mut self) { - let sig = Signal::from_object_signal( - &GDExtensionManager::singleton(), - StringName::from("extensions_reloaded") - ); - let cb = Callable::from_object_method(&self.to_gd(), StringName::from("_on_hot_reload")); - let already_connected = sig.is_connected(cb.clone()); - if already_connected { - return; - } - GDExtensionManager::singleton().connect( - StringName::from("extensions_reloaded"), - cb.clone() - ); - self.hot_reload_cb = cb; - } - - fn detach_extensions_reloaded_signal(&mut self) { - if !self.hot_reload_cb.is_null() { - GDExtensionManager::singleton().disconnect( - StringName::from("extensions_reloaded"), - self.hot_reload_cb.clone() - ); - } - } - - #[func] - fn _on_hot_reload(&mut self) { - crate::debug!("RapierRigidBody3D _on_hot_reload {:?}", self.handle.clone()); - self.base_mut().set_notify_transform(false); - self.reregister(); - self.base_mut().set_notify_transform(true); - } - - fn on_enter_tree(&mut self) { - self.register(); - self.attach_extensions_reloaded_signal(); - self.base_mut().set_notify_transform(true); - } - - fn on_exit_tree(&mut self) { - self.base_mut().set_notify_transform(false); - self.unregister(); - self.detach_extensions_reloaded_signal(); - } - - fn on_transform_changed(&mut self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - match bind.pipeline.get_rigid_body_mut(self.handle) { - Some(rigid_body) => { - self.sync_transforms_to_godot(rigid_body, false); - } - None => { - godot_error!( - "RapierRigidBody3D on_transform_changed - could not find rigid body {:?} in pipeline", - self.handle - ); - return; - } - } - } - - // Changes rapier transforms to match godot transforms - fn sync_transforms_to_godot(&mut self, rigid_body: &mut RigidBody, wakeup: bool) { - if self.base().is_inside_tree() { - let translation = self.base().get_global_position(); - let rotation = self.base().get_quaternion(); - let r_pos = crate::utils::pos_godot_to_rapier(translation); - let r_rot = crate::utils::rot_godot_to_rapier(rotation); - rigid_body.set_translation(r_pos, wakeup); - rigid_body.set_rotation(r_rot, wakeup); - } - } - - pub fn build(&self) -> RigidBody { - let rb = match self.body_type { - RigidBodyType::Dynamic => RigidBodyBuilder::dynamic(), - RigidBodyType::Fixed => RigidBodyBuilder::fixed(), - RigidBodyType::KinematicPositionBased => RigidBodyBuilder::kinematic_position_based(), - RigidBodyType::KinematicVelocityBased => RigidBodyBuilder::kinematic_velocity_based(), - }; - rb.additional_mass(self.additional_mass).build() - } - - #[func] - pub fn print_colliders(&self) { - let mut engine = crate::get_engine!(); - let mut bind = engine.bind_mut(); - match bind.pipeline.get_rigid_body_mut(self.handle) { - Some(rigid_body) => { - godot_print!("Colliders: {:?}", rigid_body.colliders()); - } - None => { - godot_error!( - "RapierRigidBody3D print_colliders - could not find rigid body {:?} in pipeline", - self.handle - ); - return; - } - } - } -} - -#[derive(GodotConvert, Var, Export)] -#[godot(via = GString)] -// https://docs.rs/rapier3d/latest/rapier3d/dynamics/enum.RigidBodyType.html -pub enum RigidBodyType { - Dynamic, - Fixed, - KinematicPositionBased, - KinematicVelocityBased, -} diff --git a/addons/godot-rapier-3d/rust/src/utils.rs b/addons/godot-rapier-3d/rust/src/utils.rs index fa4c552..a796eb5 100644 --- a/addons/godot-rapier-3d/rust/src/utils.rs +++ b/addons/godot-rapier-3d/rust/src/utils.rs @@ -1,44 +1,39 @@ -use godot::builtin::Array as GArray; -use godot::builtin::Quaternion as GQuaternion; -use godot::builtin::Vector3 as GVector; -use godot::prelude::*; -use nalgebra::Quaternion as NAQuaternion; -use nalgebra::Vector3 as NAVector; -use rapier3d::math::Rotation; +use godot::builtin::{Basis, Quaternion as GQuaternion, Transform3D, Vector3 as GVector}; +use nalgebra::geometry::Quaternion as NAQuaternion; +use rapier3d::math::{Isometry, Real, Rotation, Translation}; use rapier3d::prelude::*; -pub fn rot_godot_to_rapier(rot: GQuaternion) -> Rotation { - let result = Rotation::from_quaternion( - NAQuaternion::new(-1.0 * rot.z, rot.y, -1.0 * rot.x, rot.w) - ); - result -} - -pub fn rot_rapier_to_godot(rot: Rotation) -> GQuaternion { - let coords = rot.quaternion().coords; - GQuaternion::new(coords.x, coords.y, coords.z, coords.w) -} +mod id; +mod logger; -pub fn pos_godot_to_rapier(pos: GVector) -> NAVector { - NAVector::new(pos.x, pos.y, pos.z) -} - -pub fn pos_rapier_to_godot(pos: NAVector) -> GVector { - GVector::new(pos.x, pos.y, pos.z) -} +pub use id::*; +pub use logger::*; -pub fn rb_handle_to_id(handle: RigidBodyHandle) -> GArray { - let (index, generation) = handle.into_raw_parts(); - let mut id = GArray::new(); - id.push(Variant::from(index)); - id.push(Variant::from(generation)); - id +// Godot transform to Rapier isometry +pub fn transform_to_isometry(transform: Transform3D) -> Isometry { + let pos = transform.origin; + let quat = transform.basis.to_quat(); + let translation = Translation::new(pos.x, pos.y, pos.z); + let rotation = Rotation::from_quaternion(NAQuaternion::new( + -1.0 * quat.z, + quat.y, + -1.0 * quat.x, + quat.w, + )); + Isometry::from_parts(translation, rotation) } -pub fn collider_handle_to_id(handle: ColliderHandle) -> GArray { - let (index, generation) = handle.into_raw_parts(); - let mut id = GArray::new(); - id.push(Variant::from(index)); - id.push(Variant::from(generation)); - id +// Rapier isometry to Godot transform +pub fn isometry_to_transform(isometry: Isometry) -> Transform3D { + let vec = isometry.translation.vector; + let quat = isometry.rotation.quaternion(); + Transform3D { + basis: Basis::from_quat(GQuaternion::new( + quat.coords.x, + quat.coords.y, + quat.coords.z, + quat.coords.w, + )), + origin: GVector::new(vec.x, vec.y, vec.z), + } } diff --git a/addons/godot-rapier-3d/rust/src/utils/id.rs b/addons/godot-rapier-3d/rust/src/utils/id.rs new file mode 100644 index 0000000..a4c002e --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/utils/id.rs @@ -0,0 +1,32 @@ +use crate::objects::Handle; +use crate::{IDBridge, Lookups}; +use cuid2 as _cuid2; +use godot::obj::WithBaseField; +use godot::prelude::*; + +pub fn cuid2() -> String { + _cuid2::create_id() +} + +pub fn handle_to_instance_id(handle: Handle, lookups: &Lookups) -> Result { + let id_bridge = IDBridge::from_handle(handle.clone(), lookups); + if id_bridge.is_valid().is_ok() { + let instance_id = id_bridge.instance_id; + Ok(InstanceId::from_i64(instance_id)) + } else { + Err(format!( + "Could not find matching instance_id for handle {:?}", + handle + )) + } +} + +pub fn node_from_instance_id(instance_id: InstanceId) -> Result, String> +where + T: WithBaseField + GodotClass, +{ + match Gd::try_from_instance_id(instance_id) { + Ok(node) => Ok(node), + Err(e) => Err(e.to_string()), + } +} diff --git a/addons/godot-rapier-3d/rust/src/utils/logger.rs b/addons/godot-rapier-3d/rust/src/utils/logger.rs new file mode 100644 index 0000000..384c50d --- /dev/null +++ b/addons/godot-rapier-3d/rust/src/utils/logger.rs @@ -0,0 +1,66 @@ +use godot::prelude::*; +use log::{Level, LevelFilter, Metadata, Record}; + +pub fn handle_error(error: impl std::fmt::Display) { + log::error!("{}", error); +} + +pub fn init_logger() { + let logger = GR3DLogger {}; + log::set_max_level(LevelFilter::from(LogLevel::default())); + log::set_boxed_logger(Box::new(logger)).unwrap(); +} + +pub fn set_log_level(log_level: LogLevel) { + log::set_max_level(LevelFilter::from(log_level)); +} + +pub struct GR3DLogger {} + +impl log::Log for GR3DLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= log::max_level() + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + match record.level() { + Level::Error => godot_error!("{}", record.args()), + Level::Warn => godot_warn!("{}", record.args()), + _ => godot_print!("[{}]: {}", record.level(), record.args()), + } + } + } + + fn flush(&self) {} +} + +#[derive(Debug, Clone, GodotConvert, Var, Export)] +#[godot(via = GString)] +pub enum LogLevel { + Off, + Error, + Warning, + Info, + Debug, + Trace, +} + +impl Default for LogLevel { + fn default() -> Self { + LogLevel::Info + } +} + +impl From for LevelFilter { + fn from(level: LogLevel) -> Self { + match level { + LogLevel::Off => LevelFilter::Off, + LogLevel::Error => LevelFilter::Error, + LogLevel::Warning => LevelFilter::Warn, + LogLevel::Info => LevelFilter::Info, + LogLevel::Debug => LevelFilter::Debug, + LogLevel::Trace => LevelFilter::Trace, + } + } +} diff --git a/demos/colliders.tscn b/demos/colliders.tscn index 958a1e7..17250c2 100644 --- a/demos/colliders.tscn +++ b/demos/colliders.tscn @@ -23,9 +23,11 @@ transform = Transform3D(1, 0, 0, 0, 0.984808, 0.173648, 0, -0.173648, 0.984808, [node name="World" parent="." instance=ExtResource("4_vx8g6")] [node name="Ball" type="RapierRigidBody3D" parent="."] +id = "juavrifwxk6asz4kp7t5t9qu" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball"] +id = "nphatyucnvkbk5npv096vzxl" restitution = 0.85 ball_radius = 0.51 @@ -34,9 +36,11 @@ material_override = ExtResource("3_cldhj") mesh = SubResource("SphereMesh_yjjeh") [node name="Cube" type="RapierRigidBody3D" parent="."] +id = "vkk7n0pv1mf0cpm1kn13qqdm" transform = Transform3D(0.853553, -0.146447, 0.5, 0.5, 0.5, -0.707107, -0.146447, 0.853553, 0.5, 0, 10, 0) [node name="RapierCollider3D" type="RapierCollider3D" parent="Cube"] +id = "elbk7azvsv1rvrshft4gg00a" shape = "Cuboid" restitution = 0.2 @@ -45,18 +49,22 @@ material_override = ExtResource("3_cldhj") mesh = SubResource("BoxMesh_v2q8u") [node name="BigCube" type="RapierRigidBody3D" parent="."] +id = "emr8mwt8ytrdzxv63csyehau" transform = Transform3D(0.765839, 0.255643, 0.590031, -0.592616, 0.636751, 0.493309, -0.249592, -0.727457, 0.639148, 0, 6, 0) [node name="RapierCollider3D" type="RapierCollider3D" parent="BigCube"] +id = "fzpdgfk8vww6tjlltawr7opy" shape = "Cuboid" restitution = 0.05 cuboid_half_extents = Vector3(1, 1, 1) +transform = Transform3D(1, 2.98023e-08, 1.49012e-08, -1.49012e-08, 1, 2.98023e-08, 1.49012e-08, -2.98023e-08, 1, 0, 0, 0) [node name="MeshInstance3D" type="MeshInstance3D" parent="BigCube"] material_override = ExtResource("3_cldhj") mesh = SubResource("BoxMesh_53fml") [node name="LWall" type="RapierCollider3D" parent="."] +id = "gq37zqn0rnuw045f9x6ia4gk" shape = "Cuboid" cuboid_half_extents = Vector3(4, 2, 0.5) transform = Transform3D(0.939692, 0, 0.34202, 0, 1, 0, -0.34202, 0, 0.939692, -4, 2, -3) @@ -66,6 +74,7 @@ material_override = ExtResource("3_diwkp") mesh = SubResource("BoxMesh_fmibp") [node name="RWall" type="RapierCollider3D" parent="."] +id = "woz3ic9m91zgi2019k7e9vib" shape = "Cuboid" cuboid_half_extents = Vector3(4, 2, 0.5) transform = Transform3D(0.819152, 0, -0.573576, 0, 1, 0, 0.573576, 0, 0.819152, 4.5, 2, -2) diff --git a/project.godot b/project.godot index f4606f2..c6ca567 100644 --- a/project.godot +++ b/project.godot @@ -20,6 +20,10 @@ config/icon="res://assets/gr3d-logo.svg" Rapier3D="*res://addons/godot-rapier-3d/Rapier3D.gd" Rapier3DDebugger="*res://addons/godot-rapier-3d/Rapier3DDebugger.gd" +[debug] + +rapier_3d/logging_level="Debug" + [editor_plugins] enabled=PackedStringArray() diff --git a/tests/determinism.tscn b/tests/determinism.tscn index 8d28bc7..b1a571e 100644 --- a/tests/determinism.tscn +++ b/tests/determinism.tscn @@ -10,82 +10,109 @@ script = ExtResource("1_dyqow") transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 10, 20) [node name="Ground" type="RapierCollider3D" parent="."] +id = "u3wk7pbzyjkhcs6f3ta5nly3" shape = "Cuboid" cuboid_half_extents = Vector3(200, 1, 200) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) [node name="Ball" type="RapierRigidBody3D" parent="."] +id = "o2vfbpwjrrn8b1w7ejg9owst" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball"] +id = "q11juq2g0w9j5blfz9o3yb5y" [node name="Ball2" type="RapierRigidBody3D" parent="."] +id = "bc68z7m2l2m4wsiv0pwnbd5e" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.14202, 2.32379, 0) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball2"] +id = "c3g7i0mzrbsmd85momzi4qv5" ball_radius = 1.0 [node name="Ball3" type="RapierRigidBody3D" parent="."] +id = "vq6zrzf572jzpl6qg9ll6vei" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.284277, 5.6396, 0.755208) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball3"] +id = "p6amftvosmzx7flxjbsguciw" ball_radius = 2.0 [node name="Ball4" type="RapierRigidBody3D" parent="."] +id = "jmhbbg35xcjqbpcc54lkfesm" transform = Transform3D(0.540505, 0, -0.841341, 0, 1, 0, 0.841341, 0, 0.540505, 0.0207715, 1, 1.31979) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball4"] +id = "z5sndw3iwijir4xkse3jay9h" [node name="Ball5" type="RapierRigidBody3D" parent="."] +id = "yrb8k9ft9hobkx185pcl77nv" transform = Transform3D(0.540505, 0, -0.841341, 0, 1, 0, 0.841341, 0, 0.540505, 0.638038, 2.32379, 2.28062) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball5"] +id = "jrw7fvoctadrv6cm6wbs4r25" [node name="Ball6" type="RapierRigidBody3D" parent="."] +id = "zga1p8jg9sfifbby9c76xh82" transform = Transform3D(0.540505, 0, -0.841341, 0, 1, 0, 0.841341, 0, 0.540505, -0.523195, 2.57541, 1.24434) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball6"] +id = "gxdzmrkan5phqz3jwh93r101" [node name="Ball7" type="RapierRigidBody3D" parent="."] +id = "tykksuzc5fm9a1ghcgxkxtul" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 4.44253) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball7"] +id = "ql8qstzglnfz0c4hspdv3rvn" [node name="Ball8" type="RapierRigidBody3D" parent="."] +id = "kvp5rofgg8emxpm5yvdzoz04" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.14202, 2.32379, 4.44253) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball8"] +id = "kltxathzsl0fj0z04vhjluis" ball_radius = 1.0 [node name="Ball9" type="RapierRigidBody3D" parent="."] +id = "ltawfkkeypqlbo1xutu6r8sj" transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.94715, 2.80819, 5.19774) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball9"] +id = "kelg5eezd9y0j36kekhwfee6" ball_radius = 2.0 [node name="Ball10" type="RapierRigidBody3D" parent="."] +id = "slx0j8xd9f2kn1yo4xktu1vk" transform = Transform3D(0.540505, 0, -0.841341, 0, 1, 0, 0.841341, 0, 0.540505, 0.0207715, 1, 5.76232) [node name="RapierCollider3D" type="RapierCollider3D" parent="Ball10"] +id = "sgc6nrunroaj006x1abyvxgi" [node name="Cube" type="RapierRigidBody3D" parent="."] +id = "pkjtzkb4g7ss16sgtn4g25ni" transform = Transform3D(0.451736, 0.459593, -0.764663, -0.713177, 0.700984, 0, 0.536016, 0.54534, 0.644431, -2.32325, 3.08325, -0.339704) [node name="RapierCollider3D" type="RapierCollider3D" parent="Cube"] +id = "duadm0w8x5kw4d3ioltumhhd" shape = "Cuboid" cuboid_half_extents = Vector3(1, 1, 1) [node name="Cube2" type="RapierRigidBody3D" parent="."] +id = "zsf6foetj16bw0ywr35ce8uz" transform = Transform3D(-0.6412, 0.604272, 0.472988, -0.313427, 0.356387, -0.8802, -0.700447, -0.712631, -0.0391203, 0.728005, 10.338, 0.828321) [node name="RapierCollider3D" type="RapierCollider3D" parent="Cube2"] +id = "qctjmub7ie5opbcrh498cutk" shape = "Cuboid" cuboid_half_extents = Vector3(1, 1, 1) [node name="Cube3" type="RapierRigidBody3D" parent="."] +id = "boexujl2wypi1xkasx96s0ds" transform = Transform3D(-0.6412, 0.604272, 0.472988, -0.313427, 0.356387, -0.8802, -0.700447, -0.712631, -0.0391203, -7.31396, 6.47176, 11.4373) [node name="RapierCollider3D" type="RapierCollider3D" parent="Cube3"] +id = "vgz2cr5reo4s19zu79wn3p8s" shape = "Cuboid" cuboid_half_extents = Vector3(2, 2, 2) transform = Transform3D(1, 2.98023e-08, 1.49012e-08, -2.98023e-08, 1, 1.86265e-08, -2.6077e-08, 1.49012e-08, 1, 0, 0, 0) diff --git a/tests/sim_test.gd b/tests/sim_test.gd index 8232fd4..6e7f293 100644 --- a/tests/sim_test.gd +++ b/tests/sim_test.gd @@ -11,9 +11,6 @@ var report_abs_path var report_generated := false func _ready(): - await get_tree().process_frame - Rapier3DDebugger.show_ui = false - Rapier3DDebugger.settings_changed.emit() Engine.set_physics_ticks_per_second(fps) print("Simulating: '", name, "' for ", total_steps, " steps at: " + str(Engine.physics_ticks_per_second) + " physics ticks per second") var dt = Time.get_datetime_string_from_system(true).replace(":", "_") @@ -46,7 +43,8 @@ func get_env_info(): "get_version: " + OS.get_version(), "get_processor_count: " + str(OS.get_processor_count()), "get_processor_name: " + OS.get_processor_name(), - "get_video_adapter_driver_info: " + ", ".join(OS.get_video_adapter_driver_info()) + "get_video_adapter_driver_info: " + ", ".join(OS.get_video_adapter_driver_info()), + "fps: " + str(Engine.physics_ticks_per_second), ] func save_report(entries: Array, path: String):