├── patch ├── .gdignore └── 0001-Implement-Engine-get_physics_step_time_usec.patch ├── rapier3d ├── .gdignore ├── rustfmt.toml ├── Cargo.toml ├── src │ ├── lib.rs │ ├── util.rs │ ├── server │ │ ├── index.rs │ │ ├── call.rs │ │ ├── joint.rs │ │ ├── ffi.rs │ │ ├── mod.rs │ │ ├── space.rs │ │ ├── area.rs │ │ ├── shape.rs │ │ └── body.rs │ └── indices.rs └── build.rs ├── rapier3d_standalone ├── .gdignore ├── src │ └── lib.rs └── Cargo.toml ├── .gitattributes ├── addons ├── rapier3d │ ├── rustfmt.toml │ └── rapier3d.gdnlib └── 3d_batcher │ ├── lib │ ├── gd_3d_batcher.dll │ ├── libgd_3d_batcher.so │ └── libgd_3d_batcher.dylib │ ├── plugin.cfg │ ├── batched_mesh_instance.gdns │ ├── plugin.gd │ ├── batched_mesh_manager.gdns │ └── gd_3d_batcher.gdnlib ├── module ├── config.pyc ├── config.py ├── SCsub ├── index.h ├── register_types.h ├── typedef.h ├── register_types.cpp ├── space_state.h ├── body_state.h ├── space_state.cpp ├── gdnative.h ├── body_state.cpp └── server.cpp ├── benchmark ├── bodies │ ├── material.tres │ ├── ball.tscn │ ├── box.tscn │ └── atom.tscn ├── heightmap │ ├── editor_ray.gd │ ├── editor_ray.tscn │ ├── boxes.gd │ ├── map.tscn │ └── balls.tscn ├── benchmark.tscn ├── joint │ ├── many_chains.gd │ ├── hinge.tscn │ ├── chain.tscn │ ├── chain.gd │ └── slider.tscn ├── raycast │ ├── single_ray.gd │ ├── raycast_nodes.gd │ ├── raycast_nodes.tscn │ └── single_ray.tscn ├── area │ ├── color_enter.gd │ ├── gravity_ball.tscn │ ├── big_area.tscn │ ├── anti_gravity.gd │ ├── atom_cluster.tscn │ ├── gravity_well.tscn │ ├── anti_gravity.tscn │ ├── viscosity.tscn │ └── priority.tscn ├── exclusion │ ├── exclude_all.tscn │ └── exclude_all.gd ├── stats.tscn ├── ccd │ ├── balls.gd │ └── balls.tscn ├── environment.tscn ├── boxes │ ├── boxes.gd │ └── boxes.tscn ├── stats.gd └── benchmark.gd ├── .gitignore ├── Cargo.toml ├── default_env.tres ├── test ├── empty │ ├── empty.gd │ └── empty.tscn ├── scene_switch │ ├── b.tscn │ ├── a.tscn │ └── body.tscn ├── call │ ├── call.gd │ └── call.tscn ├── step │ ├── manual.gd │ └── manual.tscn ├── space_state │ ├── intersect.gd │ └── intersect.tscn ├── convex │ ├── tetrahedron.obj │ ├── tetrahedron.obj.import │ └── tetrahedron.tscn ├── contact │ ├── color_contact.gd │ ├── ray_pickable.tscn │ ├── area_raycast.tscn │ └── box_on_box.tscn └── lock │ └── lock.tscn ├── export_presets.cfg ├── Makefile ├── LICENSE ├── README.md └── project.godot /patch/.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rapier3d/.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rapier3d_standalone/.gdignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rapier3d/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | godot-headers/* linguist-vendored 2 | -------------------------------------------------------------------------------- /addons/rapier3d/rustfmt.toml: -------------------------------------------------------------------------------- 1 | hard_tabs = true 2 | -------------------------------------------------------------------------------- /module/config.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Demindiro/godot_rapier3d/HEAD/module/config.pyc -------------------------------------------------------------------------------- /module/config.py: -------------------------------------------------------------------------------- 1 | def can_build(env, platform): 2 | return True 3 | 4 | 5 | def configure(env): 6 | pass 7 | -------------------------------------------------------------------------------- /addons/3d_batcher/lib/gd_3d_batcher.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Demindiro/godot_rapier3d/HEAD/addons/3d_batcher/lib/gd_3d_batcher.dll -------------------------------------------------------------------------------- /benchmark/bodies/material.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="SpatialMaterial" format=2] 2 | 3 | [resource] 4 | vertex_color_use_as_albedo = true 5 | -------------------------------------------------------------------------------- /addons/3d_batcher/lib/libgd_3d_batcher.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Demindiro/godot_rapier3d/HEAD/addons/3d_batcher/lib/libgd_3d_batcher.so -------------------------------------------------------------------------------- /addons/3d_batcher/lib/libgd_3d_batcher.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Demindiro/godot_rapier3d/HEAD/addons/3d_batcher/lib/libgd_3d_batcher.dylib -------------------------------------------------------------------------------- /module/SCsub: -------------------------------------------------------------------------------- 1 | Import('env') 2 | 3 | env.add_source_files(env.modules_sources, "*.cpp") 4 | #env.Append(CPPPATH=["#modules", "#modules/gdnative", "#modules/gdnative/include"]) 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .import/ 2 | bin/ 3 | target/ 4 | lib/ 5 | logs/ 6 | rapier3d/api.json 7 | module/api.json 8 | module/api.gen.h 9 | module/server.gen.cpp 10 | __pycache__/ 11 | *.o 12 | *.pyc 13 | -------------------------------------------------------------------------------- /addons/3d_batcher/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="3D Batcher" 4 | description="Automatically batch nodes with the same mesh and material." 5 | author="David Hoppenbrouwers" 6 | version="0.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = ["rapier3d", "rapier3d_standalone"] 4 | 5 | [profile.dev.package."*"] 6 | opt-level = 3 7 | 8 | [profile.release] 9 | opt-level = 3 10 | lto = true 11 | codegen-units = 1 12 | -------------------------------------------------------------------------------- /module/index.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGGABLE_PHYSICS_SERVER_INDEX_H 2 | #define PLUGGABLE_PHYSICS_SERVER_INDEX_H 3 | 4 | #include 5 | 6 | typedef uint64_t maybe_index_t; 7 | typedef uint64_t index_t; 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /module/register_types.h: -------------------------------------------------------------------------------- 1 | #ifndef REGISTER_PLUGGABLE_PHYSICS_TYPES_H 2 | #define REGISTER_PLUGGABLE_PHYSICS_TYPES_H 3 | 4 | void register_pluggable_physics_types(); 5 | void unregister_pluggable_physics_types(); 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | ambient_light_energy = 0.3 9 | -------------------------------------------------------------------------------- /test/empty/empty.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | func _ready(): 5 | yield(get_tree().create_timer(1.5), "timeout") 6 | var cs = CollisionShape.new() 7 | var bs = BoxShape.new() 8 | cs.shape = bs 9 | $RigidBody.add_child(cs) 10 | -------------------------------------------------------------------------------- /benchmark/heightmap/editor_ray.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends RayCast 3 | 4 | 5 | onready var marker: Spatial = get_child(0) 6 | 7 | 8 | func _process(_delta: float) -> void: 9 | force_raycast_update() 10 | marker.global_transform.origin = get_collision_point() 11 | -------------------------------------------------------------------------------- /test/scene_switch/b.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://test/scene_switch/body.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Switch scene B" type="Node"] 6 | 7 | [node name="RigidBody" parent="." instance=ExtResource( 1 )] 8 | -------------------------------------------------------------------------------- /test/call/call.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | var acc = 0.0 5 | var si = -1.0 6 | func _physics_process(delta): 7 | acc += delta 8 | if acc >= 0.5: 9 | var rid = $RigidBody.get_rid() 10 | PhysicsServer.body_set_local_com(rid, Vector3(si * 10, 0, 0)) 11 | si = -si 12 | acc = 0.0 13 | -------------------------------------------------------------------------------- /benchmark/benchmark.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://benchmark/benchmark.gd" type="Script" id=1] 4 | [ext_resource path="res://benchmark/boxes/boxes.tscn" type="PackedScene" id=2] 5 | 6 | [node name="Node" type="Node"] 7 | script = ExtResource( 1 ) 8 | boxes_scene = ExtResource( 2 ) 9 | -------------------------------------------------------------------------------- /benchmark/joint/many_chains.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | export var chain: PackedScene 4 | export var chain_count := 1000 5 | export var chain_offset := 10.0 6 | 7 | func _ready(): 8 | for i in chain_count: 9 | var c: Spatial = chain.instance() 10 | c.translation = Vector3(0, 0, i * chain_offset) 11 | add_child(c) 12 | -------------------------------------------------------------------------------- /benchmark/raycast/single_ray.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | export var from := Vector3(0, 0.5, 0) 4 | export var to := Vector3(0, -0.5, 0) 5 | 6 | 7 | func _ready(): 8 | #yield(get_tree(), "idle_frame") 9 | var state = get_world().direct_space_state 10 | var result = state.intersect_ray(from, to) 11 | print(result) 12 | -------------------------------------------------------------------------------- /addons/3d_batcher/batched_mesh_instance.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/gd_3d_batcher.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | class_name = "BatchedMeshInstance" 7 | library = ExtResource( 1 ) 8 | script_class_name = "BatchedMeshInstance" 9 | -------------------------------------------------------------------------------- /rapier3d_standalone/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gdnative::{godot_gdnative_init, godot_gdnative_terminate, godot_nativescript_init}; 2 | use godot_rapier3d::init; 3 | 4 | godot_gdnative_init!(_ as gd_rapier3d_gdnative_init); 5 | godot_nativescript_init!(init as gd_rapier3d_nativescript_init); 6 | godot_gdnative_terminate!(_ as gd_rapier3d_gdnative_terminate); 7 | -------------------------------------------------------------------------------- /addons/rapier3d/rapier3d.gdnlib: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | singleton=false 4 | load_once=true 5 | symbol_prefix="gd_rapier3d_" 6 | reloadable=false 7 | 8 | [entry] 9 | 10 | X11.64="res://addons/rapier3d/lib/librapier3d.so" 11 | Windows.64="res://addons/rapier3d/lib/rapier3d.dll" 12 | 13 | [dependencies] 14 | 15 | X11.64=[ ] 16 | Windows.64=[ ] 17 | -------------------------------------------------------------------------------- /benchmark/raycast/raycast_nodes.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | export var count_x := 100 4 | export var count_z := 100 5 | export var height := 0.5 6 | 7 | 8 | func _ready(): 9 | for x in count_x: 10 | for z in count_z: 11 | var r := RayCast.new() 12 | r.translation = Vector3(x, height, z) 13 | r.enabled = true 14 | add_child(r) 15 | -------------------------------------------------------------------------------- /rapier3d_standalone/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "godot_rapier3d_standalone" 3 | version = "0.1.0" 4 | authors = ["David Hoppenbrouwers "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "rapier3d" 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | godot_rapier3d = { path = "../rapier3d" } 13 | gdnative = "*" 14 | -------------------------------------------------------------------------------- /test/step/manual.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | func _ready(): 5 | PhysicsServer.set_active(false) 6 | print("Press 'Space' to advance the simulation") 7 | 8 | 9 | func _input(event): 10 | if event.is_action_pressed("advance_simulation"): 11 | PhysicsServer.set_active(true) 12 | PhysicsServer.step(1 / 30.0) 13 | PhysicsServer.set_active(false) 14 | -------------------------------------------------------------------------------- /addons/3d_batcher/plugin.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | 5 | const AUTOLOAD_SCRIPT := "batched_mesh_manager.gdns" 6 | 7 | var autoload = null 8 | 9 | 10 | func _enter_tree() -> void: 11 | autoload = preload(AUTOLOAD_SCRIPT).new() 12 | add_child(autoload) 13 | 14 | 15 | func _exit_tree() -> void: 16 | autoload.queue_free() 17 | autoload = null 18 | -------------------------------------------------------------------------------- /benchmark/area/color_enter.gd: -------------------------------------------------------------------------------- 1 | extends Area 2 | 3 | export var color_enter := Color(1.0, 0.0, 0.0, 1.0) 4 | export var color_exit := Color(0.0, 1.0, 0.0, 1.0) 5 | 6 | 7 | func enter(n): 8 | n = n.get_node_or_null("Mesh") 9 | if n: 10 | n.color = color_enter 11 | 12 | 13 | func exit(n): 14 | n = n.get_node_or_null("Mesh") 15 | if n: 16 | n.color = color_exit 17 | -------------------------------------------------------------------------------- /addons/3d_batcher/batched_mesh_manager.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/gd_3d_batcher.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "BatchedMeshManager" 7 | class_name = "BatchedMeshManager" 8 | library = ExtResource( 1 ) 9 | script_class_name = "BatchedMeshManager" 10 | -------------------------------------------------------------------------------- /benchmark/area/gravity_ball.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [sub_resource type="SphereShape" id=1] 4 | radius = 73.0 5 | 6 | [node name="Area" type="Area"] 7 | space_override = 3 8 | gravity_point = true 9 | gravity_vec = Vector3( 0, 0, 0 ) 10 | gravity = 98.0 11 | 12 | [node name="CollisionShape" type="CollisionShape" parent="."] 13 | shape = SubResource( 1 ) 14 | -------------------------------------------------------------------------------- /benchmark/exclusion/exclude_all.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/exclusion/exclude_all.gd" type="Script" id=2] 5 | 6 | [node name="Exclude all" type="Node"] 7 | script = ExtResource( 2 ) 8 | 9 | [node name="Environment" parent="." instance=ExtResource( 1 )] 10 | -------------------------------------------------------------------------------- /benchmark/exclusion/exclude_all.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | export var box: PackedScene 5 | export var count := 1000 6 | export var spawn := Vector3(0, 1, 0) 7 | 8 | 9 | func _ready(): 10 | var list := [] 11 | for _i in 1000: 12 | var b: RigidBody = box; 13 | for e in list: 14 | b.add_collision_exception_with(e) 15 | b.translation = spawn 16 | add_child(b) 17 | list.push(b) 18 | -------------------------------------------------------------------------------- /module/typedef.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGGABLE_PHYSICS_TYPEDEF_H 2 | #define PLUGGABLE_PHYSICS_TYPEDEF_H 3 | 4 | #include "core/variant.h" 5 | 6 | typedef Basis godot_basis; 7 | typedef Transform godot_transform; 8 | typedef Vector3 godot_vector3; 9 | typedef Variant godot_variant; 10 | typedef StringName godot_string_name; 11 | typedef Object godot_object; 12 | typedef RID godot_rid; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /test/space_state/intersect.gd: -------------------------------------------------------------------------------- 1 | extends Spatial 2 | 3 | func _physics_process(_d): 4 | for _i in 1000 * 1000 * 1000: 5 | var param := PhysicsShapeQueryParameters.new() 6 | var shape := SphereShape.new() 7 | shape.radius = 10.0 8 | param.set_shape(shape) 9 | param.transform.origin = Vector3() 10 | param.collision_mask = 0xffffffff 11 | get_world().direct_space_state.intersect_shape(param) 12 | -------------------------------------------------------------------------------- /benchmark/stats.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://benchmark/stats.gd" type="Script" id=1] 4 | 5 | [node name="Stats" type="Panel"] 6 | margin_right = 265.0 7 | margin_bottom = 104.0 8 | __meta__ = { 9 | "_edit_use_anchors_": false 10 | } 11 | 12 | [node name="Stats" type="Label" parent="."] 13 | process_priority = 10 14 | margin_right = 120.0 15 | margin_bottom = 35.0 16 | script = ExtResource( 1 ) 17 | __meta__ = { 18 | "_edit_use_anchors_": false 19 | } 20 | -------------------------------------------------------------------------------- /rapier3d/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "godot_rapier3d" 3 | version = "0.1.0" 4 | authors = ["David Hoppenbrouwers "] 5 | edition = "2018" 6 | 7 | [lib] 8 | name = "godot_rapier3d" 9 | 10 | [dependencies] 11 | rapier3d = { git = "https://github.com/Demindiro/rapier", branch = "godot-0.10", version = "*", features = ["simd-nightly"] } 12 | gdnative = "*" 13 | lazy_static = "*" 14 | wchar = "*" 15 | 16 | [build-dependencies] 17 | json = "*" 18 | quote = "*" 19 | proc-macro2 = "*" 20 | -------------------------------------------------------------------------------- /benchmark/ccd/balls.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | export var ball: PackedScene 5 | export var count := 1000 6 | export var enable_ccd := true 7 | export var spawn := NodePath() 8 | export var initial_velocity := Vector3() 9 | 10 | 11 | func _ready(): 12 | var pos: Vector3 = get_node(spawn).translation 13 | for _i in count: 14 | var b: Spatial = ball.instance() 15 | b.continuous_cd = enable_ccd 16 | b.collision_layer = 2 17 | b.translation = pos 18 | b.linear_velocity = initial_velocity 19 | add_child(b) 20 | -------------------------------------------------------------------------------- /benchmark/environment.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [sub_resource type="CubeMesh" id=1] 4 | size = Vector3( 100, 1, 100 ) 5 | 6 | [sub_resource type="BoxShape" id=2] 7 | extents = Vector3( 50, 0.5, 50 ) 8 | 9 | [node name="Environment" type="Node"] 10 | 11 | [node name="Floor" type="StaticBody" parent="."] 12 | 13 | [node name="MeshInstance" type="MeshInstance" parent="Floor"] 14 | mesh = SubResource( 1 ) 15 | material/0 = null 16 | 17 | [node name="CollisionShape" type="CollisionShape" parent="Floor"] 18 | shape = SubResource( 2 ) 19 | -------------------------------------------------------------------------------- /addons/3d_batcher/gd_3d_batcher.gdnlib: -------------------------------------------------------------------------------- 1 | [resource] 2 | 3 | entry/X11.64="res://addons/3d_batcher/lib/libgd_3d_batcher.so" 4 | dependency/X11.64=[ ] 5 | 6 | [general] 7 | 8 | singleton=false 9 | load_once=true 10 | symbol_prefix="godot_" 11 | reloadable=true 12 | 13 | [entry] 14 | 15 | OSX.64="res://addons/3d_batcher/lib/libgd_3d_batcher.dylib" 16 | Windows.64="res://addons/3d_batcher/lib/gd_3d_batcher.dll" 17 | X11.64="res://addons/3d_batcher/lib/libgd_3d_batcher.so" 18 | 19 | [dependencies] 20 | 21 | OSX.64=[ ] 22 | Windows.64=[ ] 23 | X11.64=[ ] 24 | -------------------------------------------------------------------------------- /test/empty/empty.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://test/empty/empty.gd" type="Script" id=1] 4 | 5 | [sub_resource type="CubeMesh" id=1] 6 | 7 | [node name="Node" type="Node"] 8 | script = ExtResource( 1 ) 9 | 10 | [node name="RigidBody" type="RigidBody" parent="."] 11 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 0 ) 12 | 13 | [node name="MeshInstance" type="MeshInstance" parent="RigidBody"] 14 | mesh = SubResource( 1 ) 15 | material/0 = null 16 | 17 | [node name="Camera" type="Camera" parent="."] 18 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 15 ) 19 | -------------------------------------------------------------------------------- /test/convex/tetrahedron.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.90.0 OBJ File: '' 2 | # www.blender.org 3 | o Cone 4 | v 0.000000 -1.000000 -1.000000 5 | v 0.866025 -1.000000 0.500000 6 | v -0.866025 -1.000000 0.500000 7 | v 0.000000 1.000000 0.000000 8 | vt 0.250000 0.490000 9 | vt 0.250000 0.250000 10 | vt 0.457846 0.130000 11 | vt 0.750000 0.490000 12 | vt 0.957846 0.130000 13 | vt 0.542154 0.130000 14 | vt 0.042154 0.130000 15 | vn 0.8402 0.2425 -0.4851 16 | vn 0.0000 -1.0000 0.0000 17 | vn -0.0000 0.2425 0.9701 18 | vn -0.8402 0.2425 -0.4851 19 | s off 20 | f 1/1/1 4/2/1 2/3/1 21 | f 1/4/2 2/5/2 3/6/2 22 | f 2/3/3 4/2/3 3/7/3 23 | f 3/7/4 4/2/4 1/1/4 24 | -------------------------------------------------------------------------------- /test/convex/tetrahedron.obj.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wavefront_obj" 4 | type="Mesh" 5 | path="res://.import/tetrahedron.obj-bdbdc1666e94e58f3661486eea8da567.mesh" 6 | 7 | [deps] 8 | 9 | files=[ "res://.import/tetrahedron.obj-bdbdc1666e94e58f3661486eea8da567.mesh" ] 10 | 11 | source_file="res://test/convex/tetrahedron.obj" 12 | dest_files=[ "res://.import/tetrahedron.obj-bdbdc1666e94e58f3661486eea8da567.mesh", "res://.import/tetrahedron.obj-bdbdc1666e94e58f3661486eea8da567.mesh" ] 13 | 14 | [params] 15 | 16 | generate_tangents=true 17 | scale_mesh=Vector3( 1, 1, 1 ) 18 | offset_mesh=Vector3( 0, 0, 0 ) 19 | optimize_mesh=true 20 | -------------------------------------------------------------------------------- /benchmark/heightmap/editor_ray.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://benchmark/heightmap/editor_ray.gd" type="Script" id=1] 4 | 5 | [sub_resource type="SpatialMaterial" id=1] 6 | albedo_color = Color( 0.988235, 0.00784314, 0.00784314, 1 ) 7 | 8 | [sub_resource type="CubeMesh" id=2] 9 | size = Vector3( 0.2, 0.2, 0.2 ) 10 | 11 | [node name="RayCast" type="RayCast"] 12 | enabled = true 13 | cast_to = Vector3( 0, -10000, 0 ) 14 | collision_mask = 1048575 15 | script = ExtResource( 1 ) 16 | 17 | [node name="Marker" type="MeshInstance" parent="."] 18 | material_override = SubResource( 1 ) 19 | mesh = SubResource( 2 ) 20 | material/0 = null 21 | -------------------------------------------------------------------------------- /benchmark/joint/hinge.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://benchmark/joint/many_chains.gd" type="Script" id=1] 4 | [ext_resource path="res://benchmark/joint/chain.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=3] 6 | 7 | [node name="Node" type="Node"] 8 | script = ExtResource( 1 ) 9 | chain = ExtResource( 2 ) 10 | 11 | [node name="Stats" parent="." instance=ExtResource( 3 )] 12 | 13 | [node name="Camera" type="Camera" parent="."] 14 | transform = Transform( -1, -2.31402e-08, 8.63605e-08, 0, 0.965926, 0.258819, -8.9407e-08, 0.258819, -0.965926, 0, 8, -20 ) 15 | size = 12.8541 16 | far = 10000.0 17 | -------------------------------------------------------------------------------- /benchmark/joint/chain.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://benchmark/bodies/box.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/joint/chain.gd" type="Script" id=2] 5 | 6 | 7 | 8 | [sub_resource type="BoxShape" id=1] 9 | 10 | [node name="Chain" type="Spatial"] 11 | script = ExtResource( 2 ) 12 | box_scene = ExtResource( 1 ) 13 | length = 5 14 | offset = 0.5 15 | 16 | [node name="Anchor" type="StaticBody" parent="."] 17 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -6 ) 18 | __meta__ = { 19 | "_edit_group_": true 20 | } 21 | 22 | [node name="CollisionShape" type="CollisionShape" parent="Anchor"] 23 | shape = SubResource( 1 ) 24 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Linux/X11" 4 | platform="Linux/X11" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | export_path="bin/rapier_test.x86_64" 11 | patch_list=PoolStringArray( ) 12 | script_export_mode=1 13 | script_encryption_key="" 14 | 15 | [preset.0.options] 16 | 17 | texture_format/bptc=false 18 | texture_format/s3tc=true 19 | texture_format/etc=false 20 | texture_format/etc2=false 21 | texture_format/no_bptc_fallbacks=true 22 | binary_format/64_bits=true 23 | binary_format/embed_pck=false 24 | custom_template/release="/home/david/Documents/godot/godot-stable/bin/godot.x11.opt.64" 25 | custom_template/debug="" 26 | -------------------------------------------------------------------------------- /rapier3d/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(destructuring_assignment)] 2 | #![feature(option_result_unwrap_unchecked)] 3 | 4 | mod area; 5 | mod body; 6 | mod indices; 7 | mod server; 8 | mod space; 9 | mod util; 10 | 11 | // TODO find a proper workaround for E0446 12 | pub use server::Index; 13 | 14 | use gdnative::prelude::InitHandle; 15 | 16 | extern "C" { 17 | fn feenableexcept(flags: i32); 18 | } 19 | 20 | /// Call this if you are getting NaN errors *somewhere* 21 | #[allow(dead_code)] 22 | fn enable_sigfpe() { 23 | unsafe { 24 | feenableexcept(9); 25 | } 26 | } 27 | 28 | /// Bogus method just so not everything gets optimized away 29 | /// Use this with `godot_nativescript_init` 30 | pub fn init(_: InitHandle) {} 31 | -------------------------------------------------------------------------------- /benchmark/area/big_area.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | 5 | [sub_resource type="BoxShape" id=1] 6 | extents = Vector3( 26, 1, 27 ) 7 | 8 | [node name="Node" type="Node"] 9 | 10 | [node name="Environment" parent="." instance=ExtResource( 1 )] 11 | 12 | [node name="Area" type="Area" parent="."] 13 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 17, 0 ) 14 | 15 | [node name="CollisionShape" type="CollisionShape" parent="Area"] 16 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 17 | shape = SubResource( 1 ) 18 | 19 | [node name="Camera" type="Camera" parent="."] 20 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 7, 11, 46 ) 21 | -------------------------------------------------------------------------------- /benchmark/boxes/boxes.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | export var x := 10 5 | export var y := 10 6 | export var z := 10 7 | export var origin := Vector3.ONE 8 | export var offset := Vector3.ONE 9 | export var box_godot: PackedScene 10 | export var colors := PoolColorArray([Color.white, Color.red, Color.green, Color.blue, Color.yellow, Color.magenta]) 11 | 12 | 13 | func _ready(): 14 | BatchedMeshManager.enable_culling = false # Should reduce rendering impact a little more 15 | var i := 0 16 | for a in x: 17 | for b in y: 18 | for c in z: 19 | var n: Spatial = box_godot.instance() 20 | n.get_node("Mesh").color = colors[i % len(colors)] 21 | i += 1 22 | n.translation = Vector3(a, b, c) * offset + origin 23 | add_child(n) 24 | -------------------------------------------------------------------------------- /benchmark/heightmap/boxes.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | export var box: PackedScene 5 | export var count_x := 50 6 | export var count_y := 50 7 | export var start := Vector3(-45, 10, -45) 8 | export var end := Vector3(45, 10, 45) 9 | export var colors := [ 10 | Color.white, 11 | Color.red, 12 | Color.green, 13 | Color.blue, 14 | Color.cyan, 15 | Color.pink, 16 | ] 17 | 18 | func _ready(): 19 | var d := (end - start) / Vector3(count_x, 1.0, count_y) 20 | var i := 0 21 | for x in count_x: 22 | for y in count_y: 23 | var p := start + d * Vector3(x, 0.0, y) 24 | var b: Spatial = box.instance() 25 | b.translation = p 26 | b.can_sleep = false 27 | b.get_node("Mesh").color = colors[i % len(colors)] 28 | add_child(b) 29 | i += 1 30 | -------------------------------------------------------------------------------- /benchmark/bodies/ball.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/batched_mesh_instance.gdns" type="Script" id=1] 4 | [ext_resource path="res://benchmark/bodies/material.tres" type="Material" id=2] 5 | 6 | [sub_resource type="SphereShape" id=1] 7 | radius = 0.5 8 | 9 | [sub_resource type="SphereMesh" id=2] 10 | material = ExtResource( 2 ) 11 | radius = 0.5 12 | height = 1.0 13 | 14 | [node name="Box" type="RigidBody"] 15 | input_ray_pickable = false 16 | 17 | [node name="Shape" type="CollisionShape" parent="."] 18 | shape = SubResource( 1 ) 19 | 20 | [node name="Mesh" type="Spatial" parent="."] 21 | script = ExtResource( 1 ) 22 | mesh = SubResource( 2 ) 23 | use_color = true 24 | color = Color( 1, 1, 1, 1 ) 25 | -------------------------------------------------------------------------------- /benchmark/area/anti_gravity.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | export var ball: PackedScene 4 | 5 | export var start := Vector3() 6 | export var distance := Vector3() 7 | export var count_x := 100 8 | export var count_y := 100 9 | 10 | export var colors := PoolColorArray([ 11 | Color.white, 12 | Color.yellow, 13 | Color.green, 14 | Color.blue, 15 | Color.red, 16 | Color.cyan, 17 | Color.orange, 18 | Color.purple, 19 | Color.mistyrose, 20 | ]) 21 | 22 | 23 | func _ready() -> void: 24 | var i := 0 25 | for x in count_x: 26 | for y in count_y: 27 | var pos := start + distance * Vector3(x, 0, y) 28 | var node: Spatial = ball.instance() 29 | node.translation = pos 30 | node.get_node("Mesh").color = colors[i % len(colors)] 31 | add_child(node) 32 | i += 1 33 | -------------------------------------------------------------------------------- /benchmark/bodies/box.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/batched_mesh_instance.gdns" type="Script" id=1] 4 | 5 | [sub_resource type="BoxShape" id=1] 6 | extents = Vector3( 0.5, 0.5, 0.5 ) 7 | 8 | [sub_resource type="SpatialMaterial" id=2] 9 | vertex_color_use_as_albedo = true 10 | 11 | [sub_resource type="CubeMesh" id=3] 12 | material = SubResource( 2 ) 13 | size = Vector3( 1, 1, 1 ) 14 | 15 | [node name="Box" type="RigidBody"] 16 | input_ray_pickable = false 17 | 18 | [node name="Shape" type="CollisionShape" parent="."] 19 | shape = SubResource( 1 ) 20 | 21 | [node name="Mesh" type="Spatial" parent="."] 22 | script = ExtResource( 1 ) 23 | mesh = SubResource( 3 ) 24 | use_color = true 25 | color = Color( 1, 1, 1, 1 ) 26 | -------------------------------------------------------------------------------- /benchmark/joint/chain.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | export var box_scene: PackedScene 4 | export var length := 10 5 | export var colors := PoolColorArray([Color.white, Color.green, Color.blue, Color.red, Color.cyan, Color.yellow]) 6 | export var offset := 0.0 7 | 8 | func _ready(): 9 | var anchor_path := "Anchor" 10 | for i in length: 11 | var joint := HingeJoint.new() 12 | joint.set("nodes/node_a", "../%s" % anchor_path) 13 | joint.set("nodes/node_b", "../Box %d" % i) 14 | joint.translation = Vector3((i + 0.5) * (1 + offset), 0, 0) 15 | joint.rotate(Vector3.RIGHT, PI / 2) 16 | var box := box_scene.instance() 17 | box.get_node("Mesh").color = colors[i % len(colors)] 18 | box.name = "Box %d" % i 19 | box.translation = Vector3((i + 1) * (1 + offset), 0, 0) 20 | add_child(box) 21 | add_child(joint) 22 | anchor_path = box.name 23 | -------------------------------------------------------------------------------- /test/scene_switch/a.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://test/scene_switch/body.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Switch scene A" type="Node"] 6 | 7 | [node name="RigidBody" parent="." instance=ExtResource( 1 )] 8 | transform = Transform( 0.707107, 0, 0.707107, 0, 1, 0, -0.707107, 0, 0.707107, -13, 0, 1 ) 9 | 10 | [node name="RigidBody2" parent="." instance=ExtResource( 1 )] 11 | transform = Transform( 0, 0, 1, 0, 1, 0, -1, 0, 0, 0, 0, 7 ) 12 | 13 | [node name="RigidBody3" parent="." instance=ExtResource( 1 )] 14 | transform = Transform( 0.0500842, 0.976356, -0.210288, -0.977322, 0.0045355, -0.211711, -0.205751, 0.216122, 0.954441, 0, 0, -8 ) 15 | 16 | [node name="RigidBody4" parent="." instance=ExtResource( 1 )] 17 | transform = Transform( -0.965926, 0, 0.258819, 0, 1, 0, -0.258819, 0, -0.965926, 10, 0, 7 ) 18 | -------------------------------------------------------------------------------- /test/scene_switch/body.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://test/convex/tetrahedron.obj" type="ArrayMesh" id=1] 4 | 5 | [sub_resource type="ConvexPolygonShape" id=1] 6 | points = PoolVector3Array( 0, 1, 0, 0, -1, -1, 0.866025, -1, 0.5, 0.866025, -1, 0.5, 0, -1, -1, -0.866025, -1, 0.5, 0, 1, 0, 0.866025, -1, 0.5, -0.866025, -1, 0.5, 0, 1, 0, -0.866025, -1, 0.5, 0, -1, -1 ) 7 | 8 | [node name="RigidBody" type="RigidBody"] 9 | 10 | [node name="MeshInstance" type="MeshInstance" parent="."] 11 | transform = Transform( 0.612372, 0.612372, -0.5, -0.707107, 0.707107, 0, 0.353553, 0.353553, 0.866025, -4, 5, 0 ) 12 | mesh = ExtResource( 1 ) 13 | material/0 = null 14 | 15 | [node name="CollisionShape" type="CollisionShape" parent="."] 16 | transform = Transform( 0.612372, 0.612372, -0.5, -0.707107, 0.707107, 0, 0.353553, 0.353553, 0.866025, -4, 5, 0 ) 17 | shape = SubResource( 1 ) 18 | -------------------------------------------------------------------------------- /test/space_state/intersect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://test/space_state/intersect.gd" type="Script" id=1] 4 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://benchmark/bodies/box.tscn" type="PackedScene" id=3] 6 | 7 | [node name="Node" type="Spatial"] 8 | script = ExtResource( 1 ) 9 | 10 | [node name="Stats" parent="." instance=ExtResource( 2 )] 11 | 12 | [node name="Camera" type="Camera" parent="."] 13 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 6, 17 ) 14 | 15 | [node name="Box" parent="." instance=ExtResource( 3 )] 16 | 17 | [node name="Box2" parent="." instance=ExtResource( 3 )] 18 | 19 | [node name="Box3" parent="." instance=ExtResource( 3 )] 20 | 21 | [node name="Box4" parent="." instance=ExtResource( 3 )] 22 | 23 | [node name="Box5" parent="." instance=ExtResource( 3 )] 24 | -------------------------------------------------------------------------------- /test/call/call.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://test/call/call.gd" type="Script" id=1] 4 | 5 | [sub_resource type="BoxShape" id=1] 6 | extents = Vector3( 3.68115, 0.441422, 3.04398 ) 7 | 8 | [node name="Node" type="Node"] 9 | script = ExtResource( 1 ) 10 | 11 | [node name="StaticBody" type="StaticBody" parent="."] 12 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.98989, 0 ) 13 | 14 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 15 | shape = SubResource( 1 ) 16 | 17 | [node name="RigidBody" type="RigidBody" parent="."] 18 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.472478, 0 ) 19 | 20 | [node name="CollisionShape" type="CollisionShape" parent="RigidBody"] 21 | shape = SubResource( 1 ) 22 | 23 | [node name="Camera" type="Camera" parent="."] 24 | transform = Transform( 1, 0, 0, 0, 0.899649, 0.436613, 0, -0.436613, 0.899649, 0, 5.89409, 15.18 ) 25 | -------------------------------------------------------------------------------- /benchmark/area/atom_cluster.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://benchmark/bodies/atom.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/area/anti_gravity.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://benchmark/area/gravity_ball.tscn" type="PackedScene" id=4] 7 | 8 | [node name="Node" type="Node"] 9 | script = ExtResource( 2 ) 10 | ball = ExtResource( 1 ) 11 | start = Vector3( -25, 20, -25 ) 12 | distance = Vector3( 1.25, 0, 1.25 ) 13 | count_x = 14 14 | count_y = 14 15 | 16 | [node name="Camera" type="Camera" parent="."] 17 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -18, 39, 29 ) 18 | 19 | [node name="Stats" parent="." instance=ExtResource( 3 )] 20 | 21 | [node name="Area" parent="." instance=ExtResource( 4 )] 22 | space_override = 4 23 | gravity = 0.0 24 | priority = 1.0 25 | -------------------------------------------------------------------------------- /test/contact/color_contact.gd: -------------------------------------------------------------------------------- 1 | extends RigidBody 2 | 3 | var node_count := 0 4 | 5 | func enter(n): 6 | node_count += 1 7 | get_node("Mesh").color = Color.red 8 | 9 | 10 | func exit(n): 11 | get_node("Mesh").color = Color.green 12 | node_count -= 1 13 | 14 | 15 | func _integrate_forces(state): 16 | print("Node count: ", node_count) 17 | print("Contact count: ", state.get_contact_count()) 18 | for i in state.get_contact_count(): 19 | var pos = state.get_contact_collider_position(i) 20 | var pos_l = state.get_contact_local_position(i) 21 | var norm_l = state.get_contact_local_normal(i) 22 | var shape_self = state.get_contact_local_shape(i) 23 | var shape_other = state.get_contact_collider_shape(i) 24 | var other = state.get_contact_collider_object(i) 25 | print("GLOBAL POS ", pos, " | LOCAL POS ", pos_l, " | LOCAL NORMAL ", norm_l, " | SHAPES ", shape_self, " ", shape_other, " | OBJECT ", other) 26 | print() 27 | -------------------------------------------------------------------------------- /test/contact/ray_pickable.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://benchmark/bodies/box.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Node" type="Node"] 6 | 7 | [node name="Pickable ray" type="RayCast" parent="."] 8 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 ) 9 | enabled = true 10 | 11 | [node name="Unpickable ray" type="RayCast" parent="."] 12 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 ) 13 | enabled = true 14 | 15 | [node name="Camera" type="Camera" parent="."] 16 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4 ) 17 | projection = 1 18 | size = 3.0 19 | 20 | [node name="Pickable box" parent="." instance=ExtResource( 1 )] 21 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 ) 22 | 23 | [node name="Unpickable box" parent="." instance=ExtResource( 1 )] 24 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 ) 25 | input_ray_pickable = true 26 | -------------------------------------------------------------------------------- /benchmark/bodies/atom.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/batched_mesh_instance.gdns" type="Script" id=1] 4 | [ext_resource path="res://benchmark/bodies/material.tres" type="Material" id=2] 5 | [ext_resource path="res://benchmark/area/gravity_ball.tscn" type="PackedScene" id=3] 6 | 7 | [sub_resource type="SphereShape" id=1] 8 | radius = 0.5 9 | 10 | [sub_resource type="SphereMesh" id=2] 11 | material = ExtResource( 2 ) 12 | radius = 0.5 13 | height = 1.0 14 | 15 | [node name="Box" type="RigidBody"] 16 | input_ray_pickable = false 17 | 18 | [node name="Shape" type="CollisionShape" parent="."] 19 | shape = SubResource( 1 ) 20 | 21 | [node name="Mesh" type="Spatial" parent="."] 22 | script = ExtResource( 1 ) 23 | mesh = SubResource( 2 ) 24 | use_color = true 25 | color = Color( 1, 1, 1, 1 ) 26 | 27 | [node name="Area" parent="." instance=ExtResource( 3 )] 28 | space_override = 1 29 | gravity = 9.81 30 | monitorable = false 31 | -------------------------------------------------------------------------------- /benchmark/area/gravity_well.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://benchmark/bodies/ball.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/area/anti_gravity.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=3] 6 | 7 | [sub_resource type="SphereShape" id=1] 8 | radius = 73.0 9 | 10 | [node name="Node" type="Node"] 11 | script = ExtResource( 2 ) 12 | ball = ExtResource( 1 ) 13 | start = Vector3( -25, 20, -25 ) 14 | distance = Vector3( 1.25, 0, 1.25 ) 15 | count_x = 40 16 | count_y = 40 17 | 18 | [node name="Area" type="Area" parent="."] 19 | space_override = 3 20 | gravity_point = true 21 | gravity_vec = Vector3( 0, 0, 0 ) 22 | gravity = 98.0 23 | 24 | [node name="CollisionShape" type="CollisionShape" parent="Area"] 25 | shape = SubResource( 1 ) 26 | 27 | [node name="Camera" type="Camera" parent="."] 28 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 32, 46 ) 29 | 30 | [node name="Stats" parent="." instance=ExtResource( 3 )] 31 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET_LINUX?=x86_64-unknown-linux-gnu 2 | TARGET_OSX?=x86_64-apple-darwin 3 | TARGET_WINDOWS?=x86_64-pc-windows-gnu 4 | OUTPUT_DIR?=addons/rapier3d/lib 5 | GODOT?=godot 6 | 7 | 8 | default: release 9 | 10 | release: linux 11 | 12 | linux: rapier3d/api.json addons/rapier3d/lib/ 13 | GODOT_PATH=$(GODOT) cargo build --quiet --target $(TARGET_LINUX) --release 14 | cp target/$(TARGET_LINUX)/release/librapier3d.so $(OUTPUT_DIR)/librapier3d.so 15 | strip $(OUTPUT_DIR)/librapier3d.so 16 | 17 | debug: rapier3d/api.json 18 | GODOT_PATH=$(GODOT) cargo build 19 | cp target/debug/librapier3d.so $(OUTPUT_DIR)/librapier3d.so 20 | 21 | export-linux: linux 22 | $(GODOT) --export "Linux/X11" bin/rapier_test.x86_64 23 | 24 | clean: 25 | cargo clean 26 | rm rapier3d/api.json || true 27 | rm module/api.json || true 28 | rm module/api.gen.h || true 29 | 30 | addons/rapier3d/lib/: 31 | mkdir $@ 32 | 33 | rapier3d/api.json: module/api.json 34 | cp module/api.json rapier3d/api.json 35 | 36 | module/api.json: module/generate.py 37 | cd module && ./generate.py 38 | -------------------------------------------------------------------------------- /benchmark/boxes/boxes.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://benchmark/boxes/boxes.gd" type="Script" id=5] 6 | [ext_resource path="res://benchmark/bodies/box.tscn" type="PackedScene" id=6] 7 | 8 | 9 | 10 | [node name="Node" type="Node"] 11 | script = ExtResource( 5 ) 12 | x = 16 13 | y = 16 14 | z = 16 15 | offset = Vector3( 1.1, 1.1, 1.1 ) 16 | box_godot = ExtResource( 6 ) 17 | 18 | [node name="Environment" parent="." instance=ExtResource( 2 )] 19 | 20 | [node name="Camera" type="Camera" parent="."] 21 | transform = Transform( 0, -0.5, 0.866025, 0, 0.866025, 0.5, -1, 0, 0, 41, 25, 3 ) 22 | 23 | [node name="DirectionalLight" type="DirectionalLight" parent="."] 24 | transform = Transform( -0.965926, -0.12941, 0.224144, 0, 0.866025, 0.5, -0.258819, 0.482963, -0.836516, 0, 7, -13 ) 25 | shadow_enabled = true 26 | 27 | [node name="Stats" parent="." instance=ExtResource( 1 )] 28 | -------------------------------------------------------------------------------- /module/register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | 3 | #include "core/class_db.h" 4 | #include "core/project_settings.h" 5 | #include "servers/physics_server.h" 6 | 7 | #include "server.h" 8 | 9 | #ifndef _3D_DISABLED 10 | PhysicsServer *_createPluggablePhysicsCallback() { 11 | return memnew(PluggablePhysicsServer); 12 | } 13 | #endif 14 | 15 | void register_pluggable_physics_types() { 16 | #ifndef _3D_DISABLED 17 | ClassDB::register_class(); 18 | ClassDB::register_virtual_class(); 19 | ClassDB::register_virtual_class(); 20 | PhysicsServerManager::register_server("Custom", &_createPluggablePhysicsCallback); 21 | 22 | GLOBAL_DEF("physics/3d/custom_library_path", ""); 23 | String lib_path_prop = "physics/3d/custom_library_path"; 24 | PropertyInfo prop_info(Variant::STRING, lib_path_prop, PROPERTY_HINT_FILE, "*.gdnlib"); 25 | ProjectSettings::get_singleton()->set_custom_property_info(lib_path_prop, prop_info); 26 | #endif 27 | } 28 | 29 | void unregister_pluggable_physics_types() { 30 | } 31 | -------------------------------------------------------------------------------- /test/contact/area_raycast.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [sub_resource type="BoxShape" id=1] 4 | extents = Vector3( 0.5, 0.5, 0.5 ) 5 | 6 | [node name="Node" type="Node"] 7 | 8 | [node name="Pickable ray" type="RayCast" parent="."] 9 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 1, 0 ) 10 | enabled = true 11 | collide_with_areas = true 12 | 13 | [node name="Unpickable ray" type="RayCast" parent="."] 14 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0 ) 15 | enabled = true 16 | 17 | [node name="Camera" type="Camera" parent="."] 18 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4 ) 19 | projection = 1 20 | size = 3.0 21 | 22 | [node name="Area" type="Area" parent="."] 23 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1, 0, 0 ) 24 | 25 | [node name="CollisionShape" type="CollisionShape" parent="Area"] 26 | shape = SubResource( 1 ) 27 | 28 | [node name="Area2" type="Area" parent="."] 29 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 ) 30 | 31 | [node name="CollisionShape" type="CollisionShape" parent="Area2"] 32 | shape = SubResource( 1 ) 33 | -------------------------------------------------------------------------------- /benchmark/raycast/raycast_nodes.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://benchmark/raycast/raycast_nodes.gd" type="Script" id=1] 4 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=2] 5 | 6 | [sub_resource type="CubeMesh" id=1] 7 | size = Vector3( 1000, 1, 1000 ) 8 | 9 | [sub_resource type="BoxShape" id=2] 10 | extents = Vector3( 500, 0.5, 5000 ) 11 | 12 | [node name="Raycast nodes" type="Node"] 13 | script = ExtResource( 1 ) 14 | count_x = 1000 15 | 16 | [node name="StaticBody" type="StaticBody" parent="."] 17 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0 ) 18 | 19 | [node name="MeshInstance" type="MeshInstance" parent="StaticBody"] 20 | mesh = SubResource( 1 ) 21 | material/0 = null 22 | 23 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 24 | shape = SubResource( 2 ) 25 | 26 | [node name="Camera" type="Camera" parent="."] 27 | transform = Transform( -0.707107, 0.183013, -0.683013, 0, 0.965926, 0.258819, 0.707107, 0.183013, -0.683013, -6, 14, -7 ) 28 | 29 | [node name="Stats" parent="." instance=ExtResource( 2 )] 30 | -------------------------------------------------------------------------------- /benchmark/stats.gd: -------------------------------------------------------------------------------- 1 | extends Label 2 | 3 | 4 | var sample_time := OS.get_ticks_usec() 5 | var samples := [] 6 | 7 | const SAMPLE_COUNT := 60 8 | 9 | 10 | func _physics_process(_delta: float) -> void: 11 | var draw_calls = get_tree() \ 12 | .root \ 13 | .get_render_info(Viewport.RENDER_INFO_DRAW_CALLS_IN_FRAME) 14 | 15 | var delta: float 16 | if Engine.has_method("get_physics_step_time_usec"): 17 | delta = Engine.get_physics_step_time_usec() 18 | else: 19 | var time := OS.get_ticks_usec() 20 | delta = time - sample_time 21 | sample_time = time 22 | 23 | samples.push_back(delta) 24 | if len(samples) > SAMPLE_COUNT: 25 | samples.pop_front() 26 | 27 | var d := 0 28 | for s in samples: 29 | d += s 30 | d /= len(samples) 31 | if d == 0: 32 | d = 1 33 | 34 | text = "Engine: %s (%s)" % [ProjectSettings.get("physics/3d/physics_engine"), "Debug" if OS.is_debug_build() else "Release"] 35 | text += "\nReciprocal step time: %f" % (1000000.0 / d) 36 | text += "\nStep time: %f" % (d / 1000000.0) 37 | text += "\nFrame: %d" % Engine.get_physics_frames() 38 | text += "\nDraw calls: %d" % draw_calls 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 David Hoppenbrouwers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /test/lock/lock.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://benchmark/bodies/box.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Node" type="Node"] 6 | 7 | [node name="Camera" type="Camera" parent="."] 8 | transform = Transform( 1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 5, 14 ) 9 | 10 | [node name="Lock translation" parent="." instance=ExtResource( 1 )] 11 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0, 0 ) 12 | axis_lock_linear_x = true 13 | axis_lock_linear_y = true 14 | axis_lock_linear_z = true 15 | 16 | [node name="Lock translation + rotation" parent="." instance=ExtResource( 1 )] 17 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 3, 0, 0 ) 18 | axis_lock_linear_x = true 19 | axis_lock_linear_y = true 20 | axis_lock_linear_z = true 21 | axis_lock_angular_x = true 22 | axis_lock_angular_y = true 23 | axis_lock_angular_z = true 24 | 25 | [node name="Lock none" parent="." instance=ExtResource( 1 )] 26 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 2.18644, 1.24856, 0 ) 27 | 28 | [node name="Lock rotation Z" parent="." instance=ExtResource( 1 )] 29 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -2.20791, 1.27471, 0 ) 30 | axis_lock_angular_z = true 31 | -------------------------------------------------------------------------------- /benchmark/joint/slider.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://benchmark/boxes/box_godot.tscn" type="PackedScene" id=1] 4 | 5 | [sub_resource type="BoxShape" id=1] 6 | 7 | [node name="Slider" type="Node"] 8 | 9 | [node name="Camera" type="Camera" parent="."] 10 | transform = Transform( 1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 2, 8 ) 11 | 12 | [node name="StaticBody" type="RigidBody" parent="."] 13 | axis_lock_linear_x = true 14 | axis_lock_linear_y = true 15 | axis_lock_linear_z = true 16 | axis_lock_angular_x = true 17 | axis_lock_angular_y = true 18 | axis_lock_angular_z = true 19 | 20 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 21 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -2, 0, 0 ) 22 | shape = SubResource( 1 ) 23 | 24 | [node name="SliderJoint" type="SliderJoint" parent="."] 25 | transform = Transform( 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 0, 0, 1, 0, 0, 0 ) 26 | nodes/node_a = NodePath("../StaticBody") 27 | nodes/node_b = NodePath("../Box") 28 | linear_limit/lower_distance = -6.0 29 | 30 | [node name="Box" parent="." instance=ExtResource( 1 )] 31 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 ) 32 | mass = 1000.0 33 | -------------------------------------------------------------------------------- /test/step/manual.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://test/step/manual.gd" type="Script" id=1] 4 | 5 | [sub_resource type="PhysicsMaterial" id=1] 6 | bounce = 1.0 7 | 8 | [sub_resource type="BoxShape" id=2] 9 | 10 | [sub_resource type="CubeMesh" id=3] 11 | 12 | [sub_resource type="PhysicsMaterial" id=4] 13 | bounce = 1.0 14 | 15 | [sub_resource type="BoxShape" id=5] 16 | extents = Vector3( 10.8484, 1, 7.00351 ) 17 | 18 | [node name="Manual step" type="Node"] 19 | script = ExtResource( 1 ) 20 | 21 | [node name="RigidBody" type="RigidBody" parent="."] 22 | physics_material_override = SubResource( 1 ) 23 | 24 | [node name="CollisionShape" type="CollisionShape" parent="RigidBody"] 25 | shape = SubResource( 2 ) 26 | 27 | [node name="MeshInstance" type="MeshInstance" parent="RigidBody"] 28 | mesh = SubResource( 3 ) 29 | material/0 = null 30 | 31 | [node name="Camera" type="Camera" parent="."] 32 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 8, 18 ) 33 | 34 | [node name="StaticBody" type="StaticBody" parent="."] 35 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -4, 0 ) 36 | physics_material_override = SubResource( 4 ) 37 | 38 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 39 | shape = SubResource( 5 ) 40 | -------------------------------------------------------------------------------- /benchmark/area/anti_gravity.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/area/anti_gravity.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/bodies/ball.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=4] 7 | 8 | [sub_resource type="BoxShape" id=1] 9 | extents = Vector3( 25, 11, 25 ) 10 | 11 | [node name="Node" type="Node"] 12 | script = ExtResource( 2 ) 13 | ball = ExtResource( 3 ) 14 | start = Vector3( -25, 20, -25 ) 15 | distance = Vector3( 1.25, 0, 1.25 ) 16 | count_x = 40 17 | count_y = 40 18 | 19 | [node name="Environment" parent="." instance=ExtResource( 1 )] 20 | 21 | [node name="Area" type="Area" parent="."] 22 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 17, 0 ) 23 | space_override = 3 24 | gravity_vec = Vector3( 0, 1, 0 ) 25 | 26 | [node name="CollisionShape" type="CollisionShape" parent="Area"] 27 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 28 | shape = SubResource( 1 ) 29 | 30 | [node name="Camera" type="Camera" parent="."] 31 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 32, 46 ) 32 | 33 | [node name="Stats" parent="." instance=ExtResource( 4 )] 34 | -------------------------------------------------------------------------------- /benchmark/heightmap/map.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://benchmark/heightmap/editor_ray.tscn" type="PackedScene" id=1] 4 | 5 | [sub_resource type="HeightMapShape" id=1] 6 | map_width = 10 7 | map_depth = 10 8 | map_data = PoolRealArray( 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) 9 | 10 | [sub_resource type="PlaneMesh" id=2] 11 | size = Vector2( 9, 9 ) 12 | subdivide_width = 8 13 | subdivide_depth = 8 14 | 15 | [node name="StaticBody" type="StaticBody"] 16 | transform = Transform( 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0 ) 17 | __meta__ = { 18 | "_edit_group_": true, 19 | "_edit_lock_": true 20 | } 21 | 22 | [node name="CollisionShape" type="CollisionShape" parent="."] 23 | shape = SubResource( 1 ) 24 | __meta__ = { 25 | "_edit_lock_": true 26 | } 27 | 28 | [node name="MeshInstance" type="MeshInstance" parent="."] 29 | mesh = SubResource( 2 ) 30 | material/0 = null 31 | __meta__ = { 32 | "_edit_lock_": true 33 | } 34 | 35 | [node name="RayCast" parent="." instance=ExtResource( 1 )] 36 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -1.63249, 1.03491, -0.539847 ) 37 | exclude_parent = false 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot bindings for the Rapier3D physics library 2 | 3 | ## How to use 4 | There are two parts: 5 | 6 | * A Godot module which must be compiled with the engine. This is necessary to 7 | communicate with the library through the PhysicsServer 8 | * A library with the actual bindings 9 | 10 | ## Linux: 11 | 12 | - Run `module/generate.py` first 13 | - Then, either make a symlink or copy the contents of `module` to the engine source `modules/pluggable_physics` 14 | - Build the engine 15 | - Build the library with the provided `Makefile` using `make` 16 | - Set `3d/physics_engine` to `Custom` 17 | 18 | ## Windows: 19 | - Run `python module/generate.py` 20 | - Copy `module/api.json` to `rapier3d/api.json` 21 | - Then, either make a symlink or copy the contents of `module` to the engine source `modules/pluggable_physics` 22 | - Build the engine 23 | - Build the library following these steps: 24 | - ensure `LIBCLANG_PATH` environment variable is set to llvm's bin folder containing `libclang.dll` 25 | - `rustup default nightly-msvc` 26 | - If you get an error try: `cargo update` 27 | - run `build_win.bat` 28 | - Set `3d/physics_engine` to `Custom` 29 | 30 | If you have difficulties, please contact me or open an issue. 31 | 32 | # The editor may crash if you keep `3d/physics_engine` set to `Custom`. If it does, uncomment it in `project.godot` 33 | -------------------------------------------------------------------------------- /benchmark/raycast/single_ray.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://benchmark/raycast/single_ray.gd" type="Script" id=1] 4 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=2] 5 | 6 | [sub_resource type="CubeMesh" id=1] 7 | size = Vector3( 1000, 1, 1000 ) 8 | 9 | [sub_resource type="BoxShape" id=2] 10 | extents = Vector3( 500, 0.5, 500 ) 11 | 12 | [node name="Raycast nodes" type="Spatial"] 13 | script = ExtResource( 1 ) 14 | __meta__ = { 15 | "_editor_description_": "This is not really a benchmark, just a test to see if raycast output is correct (and consistent)" 16 | } 17 | from = Vector3( 0, 100, 0 ) 18 | to = Vector3( 0, -100, 0 ) 19 | 20 | [node name="StaticBody" type="StaticBody" parent="."] 21 | __meta__ = { 22 | "_edit_group_": true 23 | } 24 | 25 | [node name="MeshInstance" type="MeshInstance" parent="StaticBody"] 26 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0 ) 27 | mesh = SubResource( 1 ) 28 | material/0 = null 29 | 30 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 31 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0 ) 32 | shape = SubResource( 2 ) 33 | 34 | [node name="Camera" type="Camera" parent="."] 35 | transform = Transform( -0.707107, 0.183013, -0.683013, 0, 0.965926, 0.258819, 0.707107, 0.183013, -0.683013, -6, 14, -7 ) 36 | 37 | [node name="Stats" parent="." instance=ExtResource( 2 )] 38 | -------------------------------------------------------------------------------- /test/convex/tetrahedron.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://test/convex/tetrahedron.obj" type="ArrayMesh" id=1] 4 | 5 | [sub_resource type="BoxShape" id=1] 6 | extents = Vector3( 7, 1, 7 ) 7 | 8 | [sub_resource type="ConvexPolygonShape" id=2] 9 | points = PoolVector3Array( 0, 1, 0, 0, -1, -1, 0.866025, -1, 0.5, 0.866025, -1, 0.5, 0, -1, -1, -0.866025, -1, 0.5, 0, 1, 0, 0.866025, -1, 0.5, -0.866025, -1, 0.5, 0, 1, 0, -0.866025, -1, 0.5, 0, -1, -1 ) 10 | 11 | [node name="Node" type="Node"] 12 | 13 | [node name="StaticBody" type="StaticBody" parent="."] 14 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 7, 0 ) 15 | 16 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 17 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -3, 0 ) 18 | shape = SubResource( 1 ) 19 | 20 | [node name="RigidBody" type="RigidBody" parent="."] 21 | transform = Transform( 0.707107, -0.5, 0.5, 0.707107, 0.5, -0.5, 0, 0.707107, 0.707107, 0, 8.78455, 0 ) 22 | __meta__ = { 23 | "_edit_group_": true 24 | } 25 | 26 | [node name="MeshInstance" type="MeshInstance" parent="RigidBody"] 27 | mesh = ExtResource( 1 ) 28 | material/0 = null 29 | 30 | [node name="CollisionShape" type="CollisionShape" parent="RigidBody"] 31 | shape = SubResource( 2 ) 32 | 33 | [node name="Camera" type="Camera" parent="."] 34 | transform = Transform( 1, 0, 0, 0, 0.969123, 0.246576, 0, -0.246576, 0.969123, 0, 8.78645, 7.7032 ) 35 | -------------------------------------------------------------------------------- /benchmark/area/viscosity.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/area/anti_gravity.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/bodies/ball.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=4] 7 | [ext_resource path="res://benchmark/area/color_enter.gd" type="Script" id=5] 8 | 9 | [sub_resource type="BoxShape" id=1] 10 | extents = Vector3( 25, 2, 25 ) 11 | 12 | [node name="Node" type="Node"] 13 | script = ExtResource( 2 ) 14 | ball = ExtResource( 3 ) 15 | start = Vector3( -25, 20, -25 ) 16 | distance = Vector3( 1.25, 0, 1.25 ) 17 | count_x = 40 18 | count_y = 40 19 | 20 | [node name="Environment" parent="." instance=ExtResource( 1 )] 21 | 22 | [node name="Area" type="Area" parent="."] 23 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 30, 0 ) 24 | space_override = 3 25 | linear_damp = 30.0 26 | script = ExtResource( 5 ) 27 | 28 | [node name="CollisionShape" type="CollisionShape" parent="Area"] 29 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 30 | shape = SubResource( 1 ) 31 | 32 | [node name="Camera" type="Camera" parent="."] 33 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 32, 46 ) 34 | 35 | [node name="Stats" parent="." instance=ExtResource( 4 )] 36 | [connection signal="body_entered" from="Area" to="Area" method="enter"] 37 | [connection signal="body_exited" from="Area" to="Area" method="exit"] 38 | -------------------------------------------------------------------------------- /rapier3d/src/util.rs: -------------------------------------------------------------------------------- 1 | use gdnative::core_types::*; 2 | use rapier3d::math::*; 3 | use rapier3d::na; 4 | 5 | pub fn vec_gd_to_na(from: Vector3) -> na::Vector3 { 6 | na::Vector3::new(from.x, from.y, from.z) 7 | } 8 | 9 | pub fn vec_na_to_gd(from: na::Vector3) -> Vector3 { 10 | Vector3::new(from.x, from.y, from.z) 11 | } 12 | 13 | pub fn transform_to_isometry_and_scale(transform: &Transform) -> (Isometry, Vector3) { 14 | let origin = Translation::new(transform.origin.x, transform.origin.y, transform.origin.z); 15 | let rotation = transform.basis.to_quat(); 16 | let scale = transform.basis.to_scale(); 17 | let rotation = na::Unit::from_quaternion(na::Quaternion::new( 18 | rotation.r, rotation.i, rotation.j, rotation.k, 19 | )); 20 | (Isometry::from_parts(origin, rotation), scale) 21 | } 22 | 23 | pub fn transform_to_isometry(transform: Transform) -> Isometry { 24 | transform_to_isometry_and_scale(&transform).0 25 | } 26 | 27 | pub fn isometry_to_transform(isometry: &Isometry) -> Transform { 28 | let origin = Vector3::new( 29 | isometry.translation.x, 30 | isometry.translation.y, 31 | isometry.translation.z, 32 | ); 33 | let rot = isometry.rotation.to_rotation_matrix(); 34 | let basis = mat3_to_basis(&rot.matrix()); 35 | Transform { basis, origin } 36 | } 37 | 38 | pub fn mat3_to_basis(mat: &na::Matrix3) -> Basis { 39 | Basis { 40 | elements: [ 41 | Vector3::new(mat[(0, 0)], mat[(0, 1)], mat[(0, 2)]), 42 | Vector3::new(mat[(1, 0)], mat[(1, 1)], mat[(1, 2)]), 43 | Vector3::new(mat[(2, 0)], mat[(2, 1)], mat[(2, 2)]), 44 | ], 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | _global_script_classes=[ { 12 | "base": "Spatial", 13 | "class": "BatchedMeshInstance", 14 | "language": "NativeScript", 15 | "path": "res://addons/3d_batcher/batched_mesh_instance.gdns" 16 | }, { 17 | "base": "Node", 18 | "class": "BatchedMeshManager", 19 | "language": "NativeScript", 20 | "path": "res://addons/3d_batcher/batched_mesh_manager.gdns" 21 | } ] 22 | _global_script_class_icons={ 23 | "BatchedMeshInstance": "", 24 | "BatchedMeshManager": "" 25 | } 26 | 27 | [application] 28 | 29 | config/name="Rapier3D" 30 | config/description="Rapier3D physics for Godot" 31 | run/main_scene="res://benchmark/benchmark.tscn" 32 | 33 | [autoload] 34 | 35 | BatchedMeshManager="*res://addons/3d_batcher/batched_mesh_manager.gdns" 36 | 37 | [editor_plugins] 38 | 39 | enabled=PoolStringArray( "3d_batcher" ) 40 | 41 | [gdnative] 42 | 43 | singletons=[ ] 44 | 45 | [input] 46 | 47 | advance_simulation={ 48 | "deadzone": 0.5, 49 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null) 50 | ] 51 | } 52 | 53 | [physics] 54 | 55 | 3d/physics_engine="Custom" 56 | 3d/active_soft_world=false 57 | 3d/custom_library_path="res://addons/rapier3d/rapier3d.gdnlib" 58 | common/enable_object_picking=false 59 | 60 | [rendering] 61 | 62 | environment/default_environment="res://default_env.tres" 63 | -------------------------------------------------------------------------------- /benchmark/ccd/balls.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://addons/3d_batcher/batched_mesh_instance.gdns" type="Script" id=2] 5 | [ext_resource path="res://benchmark/bodies/material.tres" type="Material" id=3] 6 | [ext_resource path="res://benchmark/ccd/balls.gd" type="Script" id=4] 7 | [ext_resource path="res://benchmark/bodies/ball.tscn" type="PackedScene" id=5] 8 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=6] 9 | 10 | [sub_resource type="BoxShape" id=1] 11 | extents = Vector3( 0.005, 10, 10 ) 12 | 13 | [sub_resource type="CubeMesh" id=2] 14 | material = ExtResource( 3 ) 15 | size = Vector3( 0.01, 20, 20 ) 16 | 17 | [node name="CCD Balls" type="Node"] 18 | script = ExtResource( 4 ) 19 | ball = ExtResource( 5 ) 20 | spawn = NodePath("Spawn") 21 | initial_velocity = Vector3( 0, -50, 0 ) 22 | count = 100 23 | 24 | [node name="Environment" parent="." instance=ExtResource( 1 )] 25 | 26 | [node name="Camera" type="Camera" parent="."] 27 | transform = Transform( 1, 0, 0, 0, 0.965926, 0.258819, 0, -0.258819, 0.965926, 0, 29, 20 ) 28 | 29 | [node name="Floor" type="StaticBody" parent="."] 30 | transform = Transform( -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 0, 0, 1, -9, 18, 0 ) 31 | 32 | [node name="0" type="CollisionShape" parent="Floor"] 33 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -7, 10, 0 ) 34 | shape = SubResource( 1 ) 35 | __meta__ = { 36 | "_edit_group_": true 37 | } 38 | 39 | [node name="Spatial" type="Spatial" parent="Floor/0"] 40 | script = ExtResource( 2 ) 41 | mesh = SubResource( 2 ) 42 | use_color = true 43 | color = Color( 1, 0.792157, 0, 1 ) 44 | 45 | [node name="Spawn" type="Position3D" parent="."] 46 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 30, 0 ) 47 | 48 | [node name="Stats" parent="." instance=ExtResource( 6 )] 49 | -------------------------------------------------------------------------------- /benchmark/benchmark.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | export var boxes_scene: PackedScene 5 | export var spheres_scene: PackedScene 6 | 7 | export var frames_per_test := 1000 8 | 9 | 10 | func _enter_tree(): 11 | var file := File.new() 12 | 13 | if not Engine.has_method("get_physics_step_time_usec"): 14 | print("Engine::get_physics_step_time_usec not found, this may affect measurement accuracy") 15 | print("Use a Godot build with the provided patch to ensure only the time spent inside") 16 | print("PhysicServer::step is measured") 17 | 18 | var eng: String = ProjectSettings.get("physics/3d/physics_engine").to_lower() 19 | for m in get_method_list(): 20 | m = m["name"] 21 | if not m.begins_with("test_"): 22 | continue 23 | 24 | printt(eng, m) 25 | var node: Node = funcref(self, m).call_func() 26 | var e := file.open("user://%s_%s.txt" % [eng, m], File.WRITE) 27 | assert(e == OK, "Failed to open stats file: %d" % e) 28 | 29 | var time: float 30 | for _i in frames_per_test: 31 | if Engine.has_method("get_physics_step_time_usec"): 32 | yield(get_tree(), "physics_frame") 33 | file.store_line(str(Engine.get_physics_step_time_usec())) 34 | else: 35 | time = OS.get_ticks_usec() 36 | yield(get_tree(), "physics_frame") 37 | var t := OS.get_ticks_usec() 38 | file.store_line(str(t - time)) 39 | time = t 40 | 41 | file.close() 42 | node.queue_free() 43 | # Remove the node too to ensure the removal of boxes doesn't stall the next benchmark 44 | # (This should be fixed internally eventually, I guess...) 45 | remove_child(node) 46 | 47 | get_tree().quit() 48 | 49 | 50 | func test_boxes_dense(): 51 | var n := boxes_scene.instance() 52 | n.offset = Vector3.ONE 53 | add_child(n) 54 | return n 55 | 56 | 57 | func test_boxes_spaced(): 58 | var n := boxes_scene.instance() 59 | n.offset = Vector3.ONE * 1.1 60 | add_child(n) 61 | return n 62 | -------------------------------------------------------------------------------- /benchmark/heightmap/balls.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [sub_resource type="HeightMapShape" id=1] 4 | map_width = 10 5 | map_depth = 10 6 | map_data = PoolRealArray( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) 7 | 8 | [sub_resource type="PlaneMesh" id=2] 9 | size = Vector2( 9, 9 ) 10 | subdivide_width = 8 11 | subdivide_depth = 8 12 | 13 | [sub_resource type="SphereShape" id=3] 14 | 15 | [sub_resource type="SphereMesh" id=4] 16 | 17 | [sub_resource type="SpatialMaterial" id=5] 18 | albedo_color = Color( 0.94902, 0.0588235, 0.0588235, 1 ) 19 | 20 | [node name="Heightmap balls" type="Node"] 21 | 22 | [node name="StaticBody" type="StaticBody" parent="."] 23 | __meta__ = { 24 | "_edit_group_": true 25 | } 26 | 27 | [node name="CollisionShape" type="CollisionShape" parent="StaticBody"] 28 | shape = SubResource( 1 ) 29 | 30 | [node name="MeshInstance" type="MeshInstance" parent="StaticBody"] 31 | mesh = SubResource( 2 ) 32 | material/0 = null 33 | 34 | [node name="Camera" type="Camera" parent="."] 35 | transform = Transform( 1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 10, 0 ) 36 | 37 | [node name="Camera2" type="Camera" parent="."] 38 | transform = Transform( 1, 0, 0, 0, 1, 1.06581e-14, 0, -1.06581e-14, 1, 0, 1, 11 ) 39 | current = true 40 | 41 | [node name="DirectionalLight" type="DirectionalLight" parent="."] 42 | transform = Transform( 0.5, -0.433013, 0.75, 0, 0.866025, 0.5, -0.866025, -0.25, 0.433013, 0, 6, 0 ) 43 | shadow_enabled = true 44 | 45 | [node name="Balls" type="Node" parent="."] 46 | 47 | [node name="Ball" type="RigidBody" parent="Balls"] 48 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0 ) 49 | __meta__ = { 50 | "_edit_group_": true 51 | } 52 | 53 | [node name="CollisionShape" type="CollisionShape" parent="Balls/Ball"] 54 | shape = SubResource( 3 ) 55 | 56 | [node name="MeshInstance" type="MeshInstance" parent="Balls/Ball"] 57 | mesh = SubResource( 4 ) 58 | material/0 = SubResource( 5 ) 59 | -------------------------------------------------------------------------------- /test/contact/box_on_box.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://addons/3d_batcher/batched_mesh_instance.gdns" type="Script" id=1] 4 | [ext_resource path="res://test/contact/color_contact.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/bodies/material.tres" type="Material" id=3] 6 | 7 | [sub_resource type="BoxShape" id=1] 8 | extents = Vector3( 4, 1, 4 ) 9 | 10 | [sub_resource type="CubeMesh" id=2] 11 | material = ExtResource( 3 ) 12 | size = Vector3( 8, 2, 8 ) 13 | 14 | [sub_resource type="BoxShape" id=3] 15 | 16 | [sub_resource type="CubeMesh" id=4] 17 | material = ExtResource( 3 ) 18 | 19 | [node name="Box on box" type="Node"] 20 | 21 | [node name="StaticBody" type="StaticBody" parent="."] 22 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0 ) 23 | __meta__ = { 24 | "_edit_group_": true 25 | } 26 | 27 | [node name="0" type="CollisionShape" parent="StaticBody"] 28 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -5, 0 ) 29 | shape = SubResource( 1 ) 30 | 31 | [node name="1" type="CollisionShape" parent="StaticBody"] 32 | shape = SubResource( 1 ) 33 | 34 | [node name="Mesh" type="Spatial" parent="StaticBody"] 35 | script = ExtResource( 1 ) 36 | mesh = SubResource( 2 ) 37 | use_color = true 38 | color = Color( 1, 1, 1, 1 ) 39 | 40 | [node name="RigidBody" type="RigidBody" parent="."] 41 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0 ) 42 | contacts_reported = 8 43 | contact_monitor = true 44 | can_sleep = false 45 | script = ExtResource( 2 ) 46 | __meta__ = { 47 | "_edit_group_": true 48 | } 49 | 50 | [node name="0" type="CollisionShape" parent="RigidBody"] 51 | shape = SubResource( 3 ) 52 | 53 | [node name="Mesh" type="Spatial" parent="RigidBody"] 54 | script = ExtResource( 1 ) 55 | mesh = SubResource( 4 ) 56 | use_color = true 57 | color = Color( 1, 1, 1, 1 ) 58 | 59 | [node name="Timer" type="Timer" parent="RigidBody"] 60 | process_mode = 0 61 | wait_time = 2.5 62 | autostart = true 63 | 64 | [node name="Camera" type="Camera" parent="."] 65 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 12 ) 66 | [connection signal="body_entered" from="RigidBody" to="RigidBody" method="enter"] 67 | [connection signal="body_exited" from="RigidBody" to="RigidBody" method="exit"] 68 | [connection signal="timeout" from="RigidBody/Timer" to="RigidBody" method="set_translation" binds= [ Vector3( 0, 3, 0 ) ]] 69 | -------------------------------------------------------------------------------- /module/space_state.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGGABLE_PHYSICS_DIRECT_SPACE_STATE 2 | #define PLUGGABLE_PHYSICS_DIRECT_SPACE_STATE 3 | 4 | #include "index.h" 5 | #include "api.gen.h" 6 | #include "typedef.h" 7 | #include "servers/physics_server.h" 8 | 9 | class PluggablePhysicsServer; 10 | 11 | class PluggablePhysicsDirectSpaceState : public PhysicsDirectSpaceState { 12 | GDCLASS(PluggablePhysicsDirectSpaceState, PhysicsDirectSpaceState); 13 | 14 | friend class PluggablePhysicsServer; 15 | friend class PluggablePhysicsDirectBodyState; 16 | 17 | PluggablePhysicsServer *server; 18 | index_t space; 19 | 20 | _FORCE_INLINE_ PluggablePhysicsDirectSpaceState(PluggablePhysicsServer *p_server) : PhysicsDirectSpaceState() { 21 | this->server = p_server; 22 | this->space = 0; 23 | } 24 | 25 | protected: 26 | //static void _bind_methods(); 27 | 28 | public: 29 | 30 | virtual int intersect_point(const Vector3 &p_point, ShapeResult *r_results, int p_result_max, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); 31 | 32 | virtual bool intersect_ray(const Vector3 &p_from, const Vector3 &p_to, RayResult &r_result, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, bool p_pick_ray = false); 33 | 34 | virtual int intersect_shape(const RID &p_shape, const Transform &p_xform, float p_margin, ShapeResult *r_results, int p_result_max, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); 35 | 36 | virtual bool cast_motion(const RID &p_shape, const Transform &p_xform, const Vector3 &p_motion, float p_margin, float &p_closest_safe, float &p_closest_unsafe, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false, ShapeRestInfo *r_info = NULL); 37 | 38 | virtual bool collide_shape(RID p_shape, const Transform &p_shape_xform, float p_margin, Vector3 *r_results, int p_result_max, int &r_result_count, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); 39 | 40 | virtual bool rest_info(RID p_shape, const Transform &p_shape_xform, float p_margin, ShapeRestInfo *r_info, const Set &p_exclude = Set(), uint32_t p_collision_mask = 0xFFFFFFFF, bool p_collide_with_bodies = true, bool p_collide_with_areas = false); 41 | 42 | virtual Vector3 get_closest_point_to_object_volume(RID p_object, const Vector3 p_point) const; 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /benchmark/area/priority.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://benchmark/environment.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://benchmark/area/anti_gravity.gd" type="Script" id=2] 5 | [ext_resource path="res://benchmark/bodies/ball.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://benchmark/stats.tscn" type="PackedScene" id=4] 7 | [ext_resource path="res://benchmark/area/color_enter.gd" type="Script" id=5] 8 | 9 | [sub_resource type="BoxShape" id=1] 10 | extents = Vector3( 25, 11, 25 ) 11 | 12 | [node name="Node" type="Node"] 13 | script = ExtResource( 2 ) 14 | ball = ExtResource( 3 ) 15 | start = Vector3( -25, 20, -25 ) 16 | distance = Vector3( 1.25, 0, 1.25 ) 17 | count_x = 40 18 | count_y = 40 19 | colors = PoolColorArray( 0, 1, 0, 1 ) 20 | 21 | [node name="Environment" parent="." instance=ExtResource( 1 )] 22 | 23 | [node name="1" type="Area" parent="."] 24 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 17, 0 ) 25 | space_override = 1 26 | linear_damp = 20.0 27 | priority = 1.0 28 | monitorable = false 29 | script = ExtResource( 5 ) 30 | 31 | [node name="CollisionShape" type="CollisionShape" parent="1"] 32 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 33 | shape = SubResource( 1 ) 34 | 35 | [node name="2" type="Area" parent="."] 36 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -15, 17, 0 ) 37 | space_override = 4 38 | gravity_vec = Vector3( 0, 1, 0 ) 39 | gravity = 100.0 40 | priority = 2.0 41 | monitorable = false 42 | script = ExtResource( 5 ) 43 | 44 | [node name="CollisionShape" type="CollisionShape" parent="2"] 45 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 46 | shape = SubResource( 1 ) 47 | 48 | [node name="3" type="Area" parent="."] 49 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -31, 17, 0 ) 50 | space_override = 3 51 | gravity_vec = Vector3( 0, 1, 0 ) 52 | gravity = 9.81 53 | priority = 3.0 54 | monitorable = false 55 | script = ExtResource( 5 ) 56 | 57 | [node name="CollisionShape" type="CollisionShape" parent="3"] 58 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -16, 0 ) 59 | shape = SubResource( 1 ) 60 | 61 | [node name="Camera" type="Camera" parent="."] 62 | transform = Transform( 1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 32, 46 ) 63 | 64 | [node name="Stats" parent="." instance=ExtResource( 4 )] 65 | [connection signal="body_entered" from="1" to="1" method="enter"] 66 | [connection signal="body_exited" from="1" to="1" method="exit"] 67 | [connection signal="body_entered" from="2" to="2" method="enter"] 68 | [connection signal="body_exited" from="2" to="2" method="exit"] 69 | [connection signal="body_entered" from="3" to="3" method="enter"] 70 | [connection signal="body_exited" from="3" to="3" method="exit"] 71 | -------------------------------------------------------------------------------- /module/body_state.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGGABLE_PHYSICS_BODY_STATE 2 | #define PLUGGABLE_PHYSICS_BODY_STATE 3 | 4 | #include "servers/physics_server.h" 5 | #include "typedef.h" 6 | #include "index.h" 7 | #include "api.gen.h" 8 | 9 | class PluggablePhysicsServer; 10 | class PluggablePhysicsDirectSpaceState; 11 | 12 | class PluggablePhysicsDirectBodyState : public PhysicsDirectBodyState { 13 | GDCLASS(PluggablePhysicsDirectBodyState, PhysicsDirectBodyState); 14 | 15 | friend class PluggablePhysicsServer; 16 | _FORCE_INLINE_ PluggablePhysicsDirectBodyState(PluggablePhysicsServer *p_server) { 17 | this->server = p_server; 18 | } 19 | 20 | struct physics_body_state state; 21 | mutable struct physics_body_contact contact; 22 | PluggablePhysicsServer *server; 23 | PluggablePhysicsDirectSpaceState *space_state_singleton; 24 | index_t body; 25 | real_t delta; 26 | mutable uint32_t contact_index; 27 | 28 | _FORCE_INLINE_ const struct physics_body_contact *_select_contact(int id) const; 29 | 30 | public: 31 | virtual Vector3 get_total_gravity() const; 32 | virtual float get_total_angular_damp() const; 33 | virtual float get_total_linear_damp() const; 34 | 35 | virtual Vector3 get_center_of_mass() const; 36 | virtual Basis get_principal_inertia_axes() const; 37 | virtual float get_inverse_mass() const; // get the mass 38 | virtual Vector3 get_inverse_inertia() const; // get density of this body space 39 | virtual Basis get_inverse_inertia_tensor() const; // get density of this body space 40 | 41 | virtual void set_linear_velocity(const Vector3 &p_velocity); 42 | virtual Vector3 get_linear_velocity() const; 43 | 44 | virtual void set_angular_velocity(const Vector3 &p_velocity); 45 | virtual Vector3 get_angular_velocity() const; 46 | 47 | virtual void set_transform(const Transform &p_transform); 48 | virtual Transform get_transform() const; 49 | 50 | virtual void add_central_force(const Vector3 &p_force); 51 | virtual void add_force(const Vector3 &p_force, const Vector3 &p_pos); 52 | virtual void add_torque(const Vector3 &p_torque); 53 | virtual void apply_central_impulse(const Vector3 &p_j); 54 | virtual void apply_impulse(const Vector3 &p_pos, const Vector3 &p_j); 55 | virtual void apply_torque_impulse(const Vector3 &p_j); 56 | 57 | virtual void set_sleep_state(bool p_enable); 58 | virtual bool is_sleeping() const; 59 | 60 | virtual int get_contact_count() const; 61 | 62 | virtual Vector3 get_contact_local_position(int p_contact_idx) const; 63 | virtual Vector3 get_contact_local_normal(int p_contact_idx) const; 64 | virtual float get_contact_impulse(int p_contact_idx) const; 65 | virtual int get_contact_local_shape(int p_contact_idx) const; 66 | 67 | virtual RID get_contact_collider(int p_contact_idx) const; 68 | virtual Vector3 get_contact_collider_position(int p_contact_idx) const; 69 | virtual ObjectID get_contact_collider_id(int p_contact_idx) const; 70 | virtual Object *get_contact_collider_object(int p_contact_idx) const; 71 | virtual int get_contact_collider_shape(int p_contact_idx) const; 72 | virtual Vector3 get_contact_collider_velocity_at_position(int p_contact_idx) const; 73 | 74 | virtual real_t get_step() const; 75 | virtual void integrate_forces(); 76 | 77 | virtual PhysicsDirectSpaceState *get_space_state(); 78 | }; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /rapier3d/src/server/index.rs: -------------------------------------------------------------------------------- 1 | use crate::indices; 2 | 3 | pub const AREA_ID: u8 = 1; 4 | pub const BODY_ID: u8 = 2; 5 | pub const JOINT_ID: u8 = 3; 6 | pub const SHAPE_ID: u8 = 4; 7 | pub const SPACE_ID: u8 = 5; 8 | 9 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 10 | pub enum Index { 11 | Area(AreaIndex), 12 | Body(BodyIndex), 13 | Joint(JointIndex), 14 | Shape(ShapeIndex), 15 | Space(SpaceIndex), 16 | } 17 | 18 | #[derive(Debug)] 19 | pub struct InvalidIndex; 20 | 21 | impl Index { 22 | /// A value that represents an invalid index as a u64. 23 | #[allow(unused)] 24 | pub const INVALID_RAW: u64 = 0; 25 | 26 | /// Converts an Index into an u64 27 | pub fn raw(self) -> u64 { 28 | let f = |a, i, gen| ((a as u64) << 48) | ((gen as u64) << 32) | i as u64; 29 | match self { 30 | // Reserve 0 for invalid indices 31 | Self::Area(i) => f(AREA_ID, i.index(), i.generation()), 32 | Self::Body(i) => f(BODY_ID, i.index(), i.generation()), 33 | Self::Joint(i) => f(JOINT_ID, i.index(), i.generation()), 34 | Self::Shape(i) => f(SHAPE_ID, i.index(), i.generation()), 35 | Self::Space(i) => f(SPACE_ID, i.index(), i.generation()), 36 | } 37 | } 38 | 39 | /// Converts an u64 to an Index. Returns an error if the index is not valid 40 | pub fn from_raw(from: u64) -> Result { 41 | let i = from as u32; 42 | let gen = (from >> 32) as u16; 43 | Ok(match (from >> 48) as u8 { 44 | // Reserve 0 for invalid indices 45 | AREA_ID => Self::Area(AreaIndex::new(i, gen)), 46 | BODY_ID => Self::Body(BodyIndex::new(i, gen)), 47 | JOINT_ID => Self::Joint(JointIndex::new(i, gen)), 48 | SHAPE_ID => Self::Shape(ShapeIndex::new(i, gen)), 49 | SPACE_ID => Self::Space(SpaceIndex::new(i, gen)), 50 | _ => return Err(InvalidIndex), 51 | }) 52 | } 53 | } 54 | 55 | macro_rules! generate { 56 | ($name:ident, $index:ident, $as:ident) => { 57 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 58 | pub struct $index(u32, u16); 59 | 60 | impl $index { 61 | /// Creates a new $index with the given Index 62 | pub fn new(i: u32, generation: u16) -> Self { 63 | Self(i, generation) 64 | } 65 | 66 | /// Returns the inner index value 67 | pub fn index(&self) -> u32 { 68 | self.0 69 | } 70 | 71 | /// Returns the generation of this $index 72 | pub fn generation(&self) -> u16 { 73 | self.1 74 | } 75 | } 76 | 77 | impl Index { 78 | /// Returns the given index as a [`$index`] if it is one 79 | pub fn $as(self) -> Option<$index> { 80 | if let Self::$name(i) = self { 81 | Some(i) 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | 88 | impl From<$index> for indices::Index { 89 | /// Converts a [`$index`] into a [`Index`](crate::indices::Index) 90 | fn from(index: $index) -> Self { 91 | Self::new(index.0, index.1) 92 | } 93 | } 94 | 95 | impl From<&$index> for indices::Index { 96 | fn from(index: &$index) -> Self { 97 | (*index).into() 98 | } 99 | } 100 | 101 | impl From for $index { 102 | /// Converts a [`$index`] into a [`Index`](crate::indices::Index) 103 | fn from(index: indices::Index) -> Self { 104 | Self(index.index(), index.generation()) 105 | } 106 | } 107 | }; 108 | } 109 | 110 | generate!(Area, AreaIndex, as_area); 111 | generate!(Body, BodyIndex, as_body); 112 | generate!(Joint, JointIndex, as_joint); 113 | generate!(Shape, ShapeIndex, as_shape); 114 | generate!(Space, SpaceIndex, as_space); 115 | -------------------------------------------------------------------------------- /patch/0001-Implement-Engine-get_physics_step_time_usec.patch: -------------------------------------------------------------------------------- 1 | From cc7ece1011622b8b662042b93fb002806b2e8321 Mon Sep 17 00:00:00 2001 2 | From: David Hoppenbrouwers 3 | Date: Thu, 25 Mar 2021 00:20:46 +0100 4 | Subject: [PATCH] Implement Engine::get_physics_step_time_usec 5 | 6 | --- 7 | core/bind/core_bind.cpp | 6 ++++++ 8 | core/bind/core_bind.h | 1 + 9 | core/engine.cpp | 1 + 10 | core/engine.h | 2 ++ 11 | main/main.cpp | 5 +++++ 12 | 5 files changed, 15 insertions(+) 13 | 14 | diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp 15 | index 1d2c39ed6f..3a3b4cfb0e 100644 16 | --- a/core/bind/core_bind.cpp 17 | +++ b/core/bind/core_bind.cpp 18 | @@ -3094,6 +3094,11 @@ uint64_t _Engine::get_physics_frames() const { 19 | return Engine::get_singleton()->get_physics_frames(); 20 | } 21 | 22 | +uint64_t _Engine::get_physics_step_time_usec() const { 23 | + 24 | + return Engine::get_singleton()->get_physics_step_time_usec(); 25 | +} 26 | + 27 | uint64_t _Engine::get_idle_frames() const { 28 | 29 | return Engine::get_singleton()->get_idle_frames(); 30 | @@ -3175,6 +3180,7 @@ void _Engine::_bind_methods() { 31 | ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &_Engine::set_physics_jitter_fix); 32 | ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &_Engine::get_physics_jitter_fix); 33 | ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &_Engine::get_physics_interpolation_fraction); 34 | + ClassDB::bind_method(D_METHOD("get_physics_step_time_usec"), &_Engine::get_physics_step_time_usec); 35 | ClassDB::bind_method(D_METHOD("set_target_fps", "target_fps"), &_Engine::set_target_fps); 36 | ClassDB::bind_method(D_METHOD("get_target_fps"), &_Engine::get_target_fps); 37 | 38 | diff --git a/core/bind/core_bind.h b/core/bind/core_bind.h 39 | index d880ac145a..80ff997dfe 100644 40 | --- a/core/bind/core_bind.h 41 | +++ b/core/bind/core_bind.h 42 | @@ -754,6 +754,7 @@ public: 43 | void set_physics_jitter_fix(float p_threshold); 44 | float get_physics_jitter_fix() const; 45 | float get_physics_interpolation_fraction() const; 46 | + uint64_t get_physics_step_time_usec() const; 47 | 48 | void set_target_fps(int p_fps); 49 | int get_target_fps() const; 50 | diff --git a/core/engine.cpp b/core/engine.cpp 51 | index a1e67bde5e..65a060e554 100644 52 | --- a/core/engine.cpp 53 | +++ b/core/engine.cpp 54 | @@ -233,5 +233,6 @@ Engine::Engine() { 55 | _in_physics = false; 56 | _frame_ticks = 0; 57 | _frame_step = 0; 58 | + _physics_step_time_usec = 0; 59 | editor_hint = false; 60 | } 61 | diff --git a/core/engine.h b/core/engine.h 62 | index b13fba676b..9f4506c201 100644 63 | --- a/core/engine.h 64 | +++ b/core/engine.h 65 | @@ -64,6 +64,7 @@ private: 66 | bool _pixel_snap; 67 | uint64_t _physics_frames; 68 | float _physics_interpolation_fraction; 69 | + uint64_t _physics_step_time_usec; 70 | 71 | uint64_t _idle_frames; 72 | bool _in_physics; 73 | @@ -97,6 +98,7 @@ public: 74 | uint64_t get_idle_frame_ticks() const { return _frame_ticks; } 75 | float get_idle_frame_step() const { return _frame_step; } 76 | float get_physics_interpolation_fraction() const { return _physics_interpolation_fraction; } 77 | + float get_physics_step_time_usec() const { return _physics_step_time_usec; } 78 | 79 | void set_time_scale(float p_scale); 80 | float get_time_scale() const; 81 | diff --git a/main/main.cpp b/main/main.cpp 82 | index 551b5ccf5d..d477957ca9 100644 83 | --- a/main/main.cpp 84 | +++ b/main/main.cpp 85 | @@ -2088,8 +2088,13 @@ bool Main::iteration() { 86 | 87 | message_queue->flush(); 88 | 89 | + OS *os = OS::get_singleton(); 90 | + uint64_t start_3d = os->get_ticks_usec(); 91 | + 92 | PhysicsServer::get_singleton()->step(frame_slice * time_scale); 93 | 94 | + Engine::get_singleton()->_physics_step_time_usec = os->get_ticks_usec() - start_3d; 95 | + 96 | Physics2DServer::get_singleton()->end_sync(); 97 | Physics2DServer::get_singleton()->step(frame_slice * time_scale); 98 | 99 | -- 100 | 2.20.1 101 | 102 | -------------------------------------------------------------------------------- /rapier3d/src/server/call.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use gdnative::prelude::*; 3 | 4 | pub(super) type Result = core::result::Result; 5 | 6 | macro_rules! call_get_arg { 7 | (@INTERNAL $args:expr, $index:literal, $ty_fn:ident, $ty:ident) => { 8 | $args[$index] 9 | .$ty_fn() 10 | .ok_or(PhysicsCallError::invalid_argument($index, VariantType::$ty)) 11 | }; 12 | (@INTERNAL @maybe $args:expr, $index:literal, $ty_fn:ident, $ty:ident, $default:expr) => { 13 | if let Some(v) = $args.get($index) { 14 | v 15 | .$ty_fn() 16 | .ok_or(PhysicsCallError::invalid_argument($index, VariantType::$ty)) 17 | } else { 18 | Ok((|| { $default })()) 19 | } 20 | }; 21 | ($args:ident[$index:literal] => bool) => { 22 | call_get_arg!(@INTERNAL $args, $index, try_to_bool, Bool) 23 | }; 24 | ($args:ident[$index:literal] => bool || $default:expr) => { 25 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_bool, Bool, $default) 26 | }; 27 | ($args:ident[$index:literal] => i64) => { 28 | call_get_arg!(@INTERNAL $args, $index, try_to_i64, Int) 29 | }; 30 | ($args:ident[$index:literal] => i64 || $default:expr) => { 31 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_i64, Int, $default) 32 | }; 33 | ($args:ident[$index:literal] => i32) => { 34 | call_get_arg!(@INTERNAL $args, $index, try_to_i64, Int).map(|v| v as i32) 35 | }; 36 | ($args:ident[$index:literal] => i32 || $default:expr) => { 37 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_i64, Int, $default).map(|v| v as i32) 38 | }; 39 | ($args:ident[$index:literal] => u32) => { 40 | call_get_arg!(@INTERNAL $args, $index, try_to_i64, Int).map(|v| v as u32) 41 | }; 42 | ($args:ident[$index:literal] => u32 || $default:expr) => { 43 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_i64, Int, $default).map(|v| v as u32) 44 | }; 45 | ($args:ident[$index:literal] => Vector3) => { 46 | call_get_arg!(@INTERNAL $args, $index, try_to_vector3, Vector3) 47 | }; 48 | ($args:ident[$index:literal] => Vector3 || $default:expr) => { 49 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_vector3, Vector3, $default) 50 | }; 51 | ($args:ident[$index:literal] => VariantArray) => { 52 | call_get_arg!(@INTERNAL $args, $index, try_to_array, Array) 53 | }; 54 | ($args:ident[$index:literal] => VariantArray || $default:expr) => { 55 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_array, Array, $default) 56 | }; 57 | ($args:ident[$index:literal] => Rid) => { 58 | call_get_arg!(@INTERNAL $args, $index, try_to_rid, Rid) 59 | }; 60 | ($args:ident[$index:literal] => Rid || $default:expr) => { 61 | call_get_arg!(@INTERNAL @maybe $args, $index, try_to_rid, Rid, $default) 62 | }; 63 | } 64 | 65 | macro_rules! call_check_arg_count { 66 | ($args:ident in $min:literal..$max:literal) => {{ 67 | const _MIN_MAX_CHECK: usize = $max - $min; 68 | if $args.len() < $min { 69 | Err(PhysicsCallError::TooFewArguments { min: $min }) 70 | } else if $args.len() > $max { 71 | Err(PhysicsCallError::TooManyArguments { max: $max }) 72 | } else { 73 | Ok(()) 74 | } 75 | }}; 76 | } 77 | 78 | /// Method used to access Rapier-specific functionality. 79 | pub(super) fn call( 80 | method: *const wchar::wchar_t, 81 | arguments: *const &Variant, 82 | arguments_count: usize, 83 | ) -> ffi::PhysicsCallResult { 84 | // Godot's String includes a null char, so we can determine the length from that 85 | let method = unsafe { 86 | let mut size = 0; 87 | let mut m = method; 88 | while *m != 0 { 89 | m = m.add(1); 90 | size += 1; 91 | } 92 | core::slice::from_raw_parts(method, size) 93 | }; 94 | // We'll have to trust Godot on this one (just like with 'method', really). 95 | let arguments = unsafe { core::slice::from_raw_parts(arguments, arguments_count) }; 96 | 97 | use wchar::wch; 98 | ffi::PhysicsCallResult::new(match method { 99 | wch!("body_get_local_com") => body::get_local_com(arguments), 100 | wch!("body_set_local_com") => body::set_local_com(arguments), 101 | wch!("body_add_local_force") => body::add_local_force(arguments), 102 | wch!("body_add_local_impulse") => body::add_local_impulse(arguments), 103 | wch!("space_intersections_with_ray") => space::intersections_with_ray(arguments), 104 | _ => Err(ffi::PhysicsCallError::InvalidMethod), 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /module/space_state.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "core/error_macros.h" 3 | #include "space_state.h" 4 | #include "server.h" 5 | #include "core/os/memory.h" 6 | 7 | 8 | /* 9 | void PluggablePhysicsDirectSpaceState::_bind_methods() { 10 | 11 | } 12 | */ 13 | 14 | int PluggablePhysicsDirectSpaceState::intersect_point(const Vector3 &point, ShapeResult *r_results, int result_max, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas) { 15 | return 0; 16 | ERR_FAIL_V_MSG(0, "TODO"); 17 | } 18 | 19 | bool PluggablePhysicsDirectSpaceState::intersect_ray(const Vector3 &from, const Vector3 &to, PhysicsDirectSpaceState::RayResult &result, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas, bool pick_ray) { 20 | ERR_FAIL_COND_V_MSG(this->server->fn_table.space_intersect_ray == nullptr, false, "Not implemented"); 21 | 22 | index_t *e_list = memnew_arr_template(exclude.size()); 23 | size_t i = 0; 24 | for (typename Set::Element *rid = exclude.front(); rid != nullptr; rid = rid->next()) { 25 | e_list[i++] = this->server->get_index(rid->get()); 26 | } 27 | 28 | struct physics_ray_info info = { 29 | from, 30 | to, 31 | e_list, 32 | (size_t)exclude.size(), 33 | collision_mask, 34 | collide_with_bodies, 35 | collide_with_areas, 36 | pick_ray, 37 | }; 38 | struct physics_ray_result prr = {}; 39 | bool collided = (*this->server->fn_table.space_intersect_ray)(this->space, &info, &prr); 40 | 41 | // Apparently this function doesn't like it if e_list is null (which can happen if exclude.size() == 0) 42 | if (e_list != nullptr) { 43 | memdelete_arr(e_list); 44 | } 45 | 46 | if (collided) { 47 | result.position = prr.position; 48 | result.normal = prr.normal; 49 | result.rid = this->server->get_rid(prr.id); 50 | result.collider_id = prr.object_id; 51 | result.collider = prr.object_id == 0 ? nullptr : ObjectDB::get_instance(prr.object_id); 52 | } 53 | return collided; 54 | } 55 | 56 | int PluggablePhysicsDirectSpaceState::intersect_shape(const RID &shape, const Transform &xform, float margin, PhysicsDirectSpaceState::ShapeResult *r_results, int result_max, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas) { 57 | ERR_FAIL_COND_V_MSG(this->server->fn_table.space_intersect_shape == nullptr, false, "Not implemented"); 58 | 59 | index_t *e_list = memnew_arr_template(exclude.size()); 60 | size_t i = 0; 61 | for (typename Set::Element *rid = exclude.front(); rid != nullptr; rid = rid->next()) { 62 | e_list[i++] = this->server->get_index(rid->get()); 63 | } 64 | 65 | index_t shape_id = this->server->get_index(shape); 66 | 67 | struct physics_shape_info info = { 68 | shape_id, 69 | &xform, 70 | e_list, 71 | (size_t)exclude.size(), 72 | (size_t)result_max, 73 | collision_mask, 74 | collide_with_bodies, 75 | collide_with_areas, 76 | }; 77 | struct physics_shape_result *psr_arr = memnew_arr_template(result_max); 78 | size_t result_count = (*this->server->fn_table.space_intersect_shape)(this->space, &info, psr_arr, (size_t)result_max); 79 | 80 | // Apparently this function doesn't like it if e_list is null (which can happen if exclude.size() == 0) 81 | if (e_list != nullptr) { 82 | memdelete_arr(e_list); 83 | } 84 | 85 | for (i = 0; i < result_count; i++) { 86 | struct physics_shape_result *psr = &psr_arr[i]; 87 | r_results[i].rid = this->server->get_rid(psr->id); 88 | r_results[i].collider_id = psr->object_id; 89 | r_results[i].collider = psr->object_id == 0 ? nullptr : ObjectDB::get_instance(psr->object_id); 90 | r_results[i].shape = psr->shape; 91 | } 92 | 93 | if (psr_arr != nullptr) { 94 | memdelete_arr(psr_arr); 95 | } 96 | 97 | return (int)result_count; 98 | } 99 | 100 | bool PluggablePhysicsDirectSpaceState::cast_motion(const RID &shape, const Transform &xform, const Vector3 &motion, float margin, float &closest_safe, float &closest_unsafe, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas, PhysicsDirectSpaceState::ShapeRestInfo *r_info) { 101 | ERR_FAIL_V_MSG(false, "TODO"); 102 | } 103 | 104 | bool PluggablePhysicsDirectSpaceState::collide_shape(RID shape, const Transform &shape_xform, float margin, Vector3 *r_results, int result_max, int &r_result_count, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas) { 105 | ERR_FAIL_V_MSG(false, "TODO"); 106 | } 107 | 108 | bool PluggablePhysicsDirectSpaceState::rest_info(RID shape, const Transform &shape_xform, float margin, PhysicsDirectSpaceState::ShapeRestInfo *r_info, const Set &exclude, uint32_t collision_mask, bool collide_with_bodies, bool collide_with_areas) { 109 | ERR_FAIL_V_MSG(false, "TODO"); 110 | } 111 | 112 | Vector3 PluggablePhysicsDirectSpaceState::get_closest_point_to_object_volume(RID object, const Vector3 point) const { 113 | ERR_FAIL_V_MSG(Vector3(), "TODO"); 114 | } 115 | -------------------------------------------------------------------------------- /rapier3d/src/indices.rs: -------------------------------------------------------------------------------- 1 | //! Based on https://github.com/fitzgen/generational-arena/blob/master/src/lib.rs but optimized for 2 | //! smaller indices (u32 for index, u16 for generation) so that it's suitable for FFI. 3 | 4 | use core::mem; 5 | 6 | pub struct Indices { 7 | elements: Vec>, 8 | free_slot: Option, 9 | generation: u16, 10 | } 11 | 12 | pub struct Index { 13 | index: u32, 14 | generation: u16, 15 | } 16 | 17 | enum Entry { 18 | Free { next: Option }, 19 | Occupied { item: T, generation: u16 }, 20 | } 21 | 22 | impl Indices { 23 | pub fn new() -> Self { 24 | Self { 25 | elements: Vec::new(), 26 | free_slot: None, 27 | generation: 0, 28 | } 29 | } 30 | 31 | pub fn add(&mut self, item: T) -> Index { 32 | let generation = self.generation; 33 | let entry = Entry::Occupied { item, generation }; 34 | self.generation = self.generation.wrapping_add(1); 35 | if let Some(index) = self.free_slot { 36 | let e = mem::replace(&mut self.elements[index as usize], entry); 37 | if let Entry::Free { next } = e { 38 | self.free_slot = next; 39 | } else { 40 | panic!("Entry was occupied"); 41 | } 42 | Index::new(index, generation) 43 | } else { 44 | let index = self.elements.len() as u32; 45 | self.elements.push(entry); 46 | Index::new(index, generation) 47 | } 48 | } 49 | 50 | pub fn remove(&mut self, index: Index) -> Option { 51 | if let Some(e) = self.elements.get_mut(index.index as usize) { 52 | if let Entry::Occupied { generation, .. } = e { 53 | if *generation == index.generation { 54 | let entry = Entry::Free { 55 | next: self.free_slot, 56 | }; 57 | self.free_slot = Some(index.index); 58 | if let Entry::Occupied { item, .. } = mem::replace(e, entry) { 59 | return Some(item); 60 | } else { 61 | unreachable!(); 62 | } 63 | } 64 | } 65 | } 66 | None 67 | } 68 | 69 | pub fn get(&self, index: Index) -> Option<&T> { 70 | let (i, g) = index.split(); 71 | if let Some(Entry::Occupied { item, generation }) = self.elements.get(i as usize) { 72 | if *generation == g { 73 | return Some(item); 74 | } 75 | } 76 | None 77 | } 78 | 79 | pub fn get_mut(&mut self, index: Index) -> Option<&mut T> { 80 | let (i, g) = index.split(); 81 | if let Some(Entry::Occupied { item, generation }) = self.elements.get_mut(i as usize) { 82 | if *generation == g { 83 | return Some(item); 84 | } 85 | } 86 | None 87 | } 88 | 89 | pub fn get2_mut(&mut self, index_a: Index, index_b: Index) -> (Option<&mut T>, Option<&mut T>) { 90 | let (a, b) = (index_a.split(), index_b.split()); 91 | let (a, b, swapped) = if a.0 < b.0 { 92 | (a, b, false) 93 | } else { 94 | (b, a, true) 95 | }; 96 | let ((ai, ag), (bi, bg)) = ((a.0 as usize, a.1), (b.0 as usize, b.1)); 97 | if ai < self.elements.len() { 98 | let (l, r) = self.elements.split_at_mut(ai + 1); 99 | let a = if let Entry::Occupied { item, generation } = &mut l[ai] { 100 | if *generation == ag { 101 | Some(item) 102 | } else { 103 | None 104 | } 105 | } else { 106 | None 107 | }; 108 | let b = if let Some(Entry::Occupied { item, generation }) = r.get_mut(bi - ai - 1) { 109 | if *generation == bg { 110 | Some(item) 111 | } else { 112 | None 113 | } 114 | } else { 115 | None 116 | }; 117 | if swapped { 118 | (b, a) 119 | } else { 120 | (a, b) 121 | } 122 | } else { 123 | (None, None) 124 | } 125 | } 126 | 127 | pub fn iter(&self) -> impl Iterator { 128 | self.elements.iter().enumerate().filter_map(|(i, e)| { 129 | if let Entry::Occupied { item, generation } = e { 130 | Some((Index::new(i as u32, *generation), item)) 131 | } else { 132 | None 133 | } 134 | }) 135 | } 136 | 137 | pub fn iter_mut(&mut self) -> impl Iterator { 138 | self.elements.iter_mut().enumerate().filter_map(|(i, e)| { 139 | if let Entry::Occupied { item, generation } = e { 140 | Some((Index::new(i as u32, *generation), item)) 141 | } else { 142 | None 143 | } 144 | }) 145 | } 146 | } 147 | 148 | impl Index { 149 | pub fn new(index: u32, generation: u16) -> Self { 150 | Self { index, generation } 151 | } 152 | 153 | pub fn index(&self) -> u32 { 154 | self.index 155 | } 156 | 157 | pub fn generation(&self) -> u16 { 158 | self.generation 159 | } 160 | 161 | fn split(self) -> (u32, u16) { 162 | (self.index, self.generation) 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod test { 168 | use super::*; 169 | 170 | #[test] 171 | fn get2_mut() { 172 | let mut inds = Indices::new(); 173 | let a = inds.add("foo"); 174 | let b = inds.add("bar"); 175 | let (a, b) = inds.get2_mut(a, b); 176 | assert_eq!(a, Some(&mut "foo")); 177 | assert_eq!(b, Some(&mut "bar")); 178 | 179 | let mut inds = Indices::new(); 180 | let a = inds.add("bar"); 181 | let b = inds.add("foo"); 182 | let (a, b) = inds.get2_mut(a, b); 183 | assert_eq!(a, Some(&mut "bar")); 184 | assert_eq!(b, Some(&mut "foo")); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /module/gdnative.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************/ 2 | /* gdnative.h */ 3 | /*************************************************************************/ 4 | /* This file is part of: */ 5 | /* GODOT ENGINE */ 6 | /* https://godotengine.org */ 7 | /*************************************************************************/ 8 | /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ 9 | /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ 10 | /* */ 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ 12 | /* a copy of this software and associated documentation files (the */ 13 | /* "Software"), to deal in the Software without restriction, including */ 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ 16 | /* permit persons to whom the Software is furnished to do so, subject to */ 17 | /* the following conditions: */ 18 | /* */ 19 | /* The above copyright notice and this permission notice shall be */ 20 | /* included in all copies or substantial portions of the Software. */ 21 | /* */ 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 29 | /*************************************************************************/ 30 | 31 | #ifndef GDNATIVE_H 32 | #define GDNATIVE_H 33 | 34 | #include "core/io/resource_loader.h" 35 | #include "core/io/resource_saver.h" 36 | #include "core/os/thread_safe.h" 37 | #include "core/resource.h" 38 | #include "core/io/config_file.h" 39 | 40 | class GDNativeLibraryResourceLoader; 41 | class GDNative; 42 | 43 | class GDNativeLibrary : public Resource { 44 | GDCLASS(GDNativeLibrary, Resource); 45 | 46 | static Map > > loaded_libraries; 47 | 48 | friend class GDNativeLibraryResourceLoader; 49 | friend class GDNative; 50 | 51 | Ref config_file; 52 | 53 | String current_library_path; 54 | Vector current_dependencies; 55 | 56 | bool singleton; 57 | bool load_once; 58 | String symbol_prefix; 59 | bool reloadable; 60 | 61 | public: 62 | GDNativeLibrary(); 63 | ~GDNativeLibrary(); 64 | 65 | virtual bool _set(const StringName &p_name, const Variant &p_property); 66 | virtual bool _get(const StringName &p_name, Variant &r_property) const; 67 | virtual void _get_property_list(List *p_list) const; 68 | 69 | _FORCE_INLINE_ Ref get_config_file() { return config_file; } 70 | 71 | void set_config_file(Ref p_config_file); 72 | 73 | // things that change per-platform 74 | // so there are no setters for this 75 | _FORCE_INLINE_ String get_current_library_path() const { 76 | return current_library_path; 77 | } 78 | _FORCE_INLINE_ Vector get_current_dependencies() const { 79 | return current_dependencies; 80 | } 81 | 82 | // things that are a property of the library itself, not platform specific 83 | _FORCE_INLINE_ bool should_load_once() const { 84 | return load_once; 85 | } 86 | _FORCE_INLINE_ bool is_singleton() const { 87 | return singleton; 88 | } 89 | _FORCE_INLINE_ String get_symbol_prefix() const { 90 | return symbol_prefix; 91 | } 92 | 93 | _FORCE_INLINE_ bool is_reloadable() const { 94 | return reloadable; 95 | } 96 | 97 | _FORCE_INLINE_ void set_load_once(bool p_load_once) { 98 | config_file->set_value("general", "load_once", p_load_once); 99 | load_once = p_load_once; 100 | } 101 | _FORCE_INLINE_ void set_singleton(bool p_singleton) { 102 | config_file->set_value("general", "singleton", p_singleton); 103 | singleton = p_singleton; 104 | } 105 | _FORCE_INLINE_ void set_symbol_prefix(String p_symbol_prefix) { 106 | config_file->set_value("general", "symbol_prefix", p_symbol_prefix); 107 | symbol_prefix = p_symbol_prefix; 108 | } 109 | 110 | _FORCE_INLINE_ void set_reloadable(bool p_reloadable) { 111 | config_file->set_value("general", "reloadable", p_reloadable); 112 | reloadable = p_reloadable; 113 | } 114 | 115 | static void _bind_methods(); 116 | }; 117 | 118 | class GDNative : public Reference { 119 | GDCLASS(GDNative, Reference); 120 | 121 | Ref library; 122 | 123 | void *native_handle; 124 | 125 | bool initialized; 126 | 127 | public: 128 | GDNative(); 129 | ~GDNative(); 130 | 131 | static void _bind_methods(); 132 | 133 | void set_library(Ref p_library); 134 | Ref get_library() const; 135 | 136 | bool is_initialized() const; 137 | 138 | bool initialize(); 139 | bool terminate(); 140 | 141 | Variant call_native(StringName p_native_call_type, StringName p_procedure_name, Array p_arguments = Array()); 142 | 143 | Error get_symbol(StringName p_procedure_name, void *&r_handle, bool p_optional = true) const; 144 | }; 145 | 146 | #endif // GDNATIVE_H 147 | -------------------------------------------------------------------------------- /module/body_state.cpp: -------------------------------------------------------------------------------- 1 | #include "body_state.h" 2 | #include "space_state.h" 3 | #include "server.h" 4 | 5 | 6 | Vector3 PluggablePhysicsDirectBodyState::get_total_gravity() const { 7 | return this->state.gravity; 8 | } 9 | 10 | float PluggablePhysicsDirectBodyState::get_total_angular_damp() const { 11 | return this->state.angular_damp; 12 | } 13 | 14 | float PluggablePhysicsDirectBodyState::get_total_linear_damp() const { 15 | return this->state.linear_damp; 16 | } 17 | 18 | Vector3 PluggablePhysicsDirectBodyState::get_center_of_mass() const { 19 | return this->state.center_of_mass; 20 | } 21 | 22 | Basis PluggablePhysicsDirectBodyState::get_principal_inertia_axes() const { 23 | return Basis(); 24 | } 25 | 26 | float PluggablePhysicsDirectBodyState::get_inverse_mass() const { 27 | return this->state.inv_mass; 28 | } 29 | 30 | Vector3 PluggablePhysicsDirectBodyState::get_inverse_inertia() const { 31 | return this->state.inv_inertia; 32 | } 33 | 34 | Basis PluggablePhysicsDirectBodyState::get_inverse_inertia_tensor() const { 35 | return this->state.inv_inertia_tensor; 36 | } 37 | 38 | void PluggablePhysicsDirectBodyState::set_linear_velocity(const Vector3 &velocity) { 39 | Variant v(velocity); 40 | EXEC_FFI_FN(this->server, body_set_state, this->body, PhysicsServer::BODY_STATE_LINEAR_VELOCITY, &v); 41 | this->state.linear_velocity = velocity; 42 | } 43 | 44 | Vector3 PluggablePhysicsDirectBodyState::get_linear_velocity() const { 45 | return this->state.linear_velocity; 46 | } 47 | 48 | void PluggablePhysicsDirectBodyState::set_angular_velocity(const Vector3 &velocity) { 49 | Variant v(velocity); 50 | EXEC_FFI_FN(this->server, body_set_state, this->body, PhysicsServer::BODY_STATE_ANGULAR_VELOCITY, &v); 51 | this->state.angular_velocity = velocity; 52 | } 53 | 54 | Vector3 PluggablePhysicsDirectBodyState::get_angular_velocity() const { 55 | return this->state.angular_velocity; 56 | } 57 | 58 | void PluggablePhysicsDirectBodyState::set_transform(const Transform &transform) { 59 | Variant t(transform); 60 | EXEC_FFI_FN(this->server, body_set_state, this->body, PhysicsServer::BODY_STATE_TRANSFORM, &t); 61 | this->state.transform = transform; 62 | } 63 | 64 | Transform PluggablePhysicsDirectBodyState::get_transform() const { 65 | return this->state.transform; 66 | } 67 | 68 | void PluggablePhysicsDirectBodyState::add_central_force(const Vector3 &force) { 69 | EXEC_FFI_FN(this->server, body_add_central_force, this->body, &force); 70 | } 71 | 72 | void PluggablePhysicsDirectBodyState::add_force(const Vector3 &force, const Vector3 &pos) { 73 | EXEC_FFI_FN(this->server, body_add_force, this->body, &force, &pos); 74 | }; 75 | 76 | void PluggablePhysicsDirectBodyState::add_torque(const Vector3 &torque) { 77 | EXEC_FFI_FN(this->server, body_add_torque, this->body, &torque); 78 | }; 79 | 80 | void PluggablePhysicsDirectBodyState::apply_central_impulse(const Vector3 &impulse) { 81 | EXEC_FFI_FN(this->server, body_apply_central_impulse, this->body, &impulse); 82 | }; 83 | 84 | void PluggablePhysicsDirectBodyState::apply_impulse(const Vector3 &position, const Vector3 &impulse) { 85 | EXEC_FFI_FN(this->server, body_apply_impulse, this->body, &position, &impulse); 86 | }; 87 | 88 | void PluggablePhysicsDirectBodyState::apply_torque_impulse(const Vector3 &impulse) { 89 | EXEC_FFI_FN(this->server, body_apply_torque_impulse, this->body, &impulse); 90 | }; 91 | 92 | void PluggablePhysicsDirectBodyState::set_sleep_state(bool enable) { 93 | Variant e(enable); 94 | EXEC_FFI_FN(this->server, body_set_state, this->body, PhysicsServer::BODY_STATE_SLEEPING, &e); 95 | }; 96 | 97 | bool PluggablePhysicsDirectBodyState::is_sleeping() const { 98 | return this->state.sleeping; 99 | }; 100 | 101 | int PluggablePhysicsDirectBodyState::get_contact_count() const { 102 | return this->state.contact_count; 103 | } 104 | 105 | Vector3 PluggablePhysicsDirectBodyState::get_contact_local_position(int id) const { 106 | return this->_select_contact(id)->local_position; 107 | } 108 | 109 | Vector3 PluggablePhysicsDirectBodyState::get_contact_local_normal(int id) const { 110 | return this->_select_contact(id)->local_normal; 111 | } 112 | 113 | float PluggablePhysicsDirectBodyState::get_contact_impulse(int id) const { 114 | return this->_select_contact(id)->impulse; 115 | } 116 | 117 | int PluggablePhysicsDirectBodyState::get_contact_local_shape(int id) const { 118 | return this->_select_contact(id)->local_shape; 119 | }; 120 | 121 | RID PluggablePhysicsDirectBodyState::get_contact_collider(int id) const { 122 | return this->server->get_rid(this->_select_contact(id)->index); 123 | }; 124 | 125 | Vector3 PluggablePhysicsDirectBodyState::get_contact_collider_position(int id) const { 126 | return this->_select_contact(id)->position; 127 | } 128 | 129 | ObjectID PluggablePhysicsDirectBodyState::get_contact_collider_id(int id) const { 130 | return this->_select_contact(id)->object_id; 131 | } 132 | 133 | Object *PluggablePhysicsDirectBodyState::get_contact_collider_object(int id) const { 134 | int oid = this->_select_contact(id)->object_id; 135 | return oid != 0 ? ObjectDB::get_instance(oid) : nullptr; 136 | } 137 | 138 | int PluggablePhysicsDirectBodyState::get_contact_collider_shape(int id) const { 139 | return this->_select_contact(id)->shape; 140 | }; 141 | 142 | Vector3 PluggablePhysicsDirectBodyState::get_contact_collider_velocity_at_position(int id) const { 143 | return this->_select_contact(id)->velocity; 144 | }; 145 | 146 | real_t PluggablePhysicsDirectBodyState::get_step() const { 147 | return this->delta; 148 | }; 149 | 150 | void PluggablePhysicsDirectBodyState::integrate_forces() { 151 | // Don't need to do anything here 152 | }; 153 | 154 | PhysicsDirectSpaceState *PluggablePhysicsDirectBodyState::get_space_state() { 155 | this->space_state_singleton->space = this->state.space; 156 | return this->space_state_singleton; 157 | }; 158 | 159 | _FORCE_INLINE_ const struct physics_body_contact *PluggablePhysicsDirectBodyState::_select_contact(int id) const { 160 | uint32_t uid = (uint32_t)id; 161 | if (this->contact_index != uid) { 162 | if (uid < this->state.contact_count) { 163 | EXEC_V_FFI_FN(&this->contact, this->server, body_get_contact, this->body, uid, &this->contact); 164 | this->contact_index = uid; 165 | } else { 166 | this->contact = {}; 167 | this->contact_index = (uint32_t)-1; 168 | } 169 | } 170 | return &this->contact; 171 | } 172 | 173 | -------------------------------------------------------------------------------- /rapier3d/src/server/joint.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::body; 3 | use crate::util::*; 4 | use core::convert::TryFrom; 5 | use gdnative::core_types::*; 6 | use gdnative::godot_error; 7 | use rapier3d::dynamics::{JointHandle, JointParams, RevoluteJoint, SpringModel}; 8 | use rapier3d::math::Point; 9 | use rapier3d::na::Unit; 10 | 11 | pub struct Joint { 12 | joint: Instance, 13 | exclude_bodies: bool, 14 | } 15 | 16 | struct LooseJoint { 17 | params: JointParams, 18 | #[allow(dead_code)] 19 | body_a: BodyIndex, 20 | #[allow(dead_code)] 21 | body_b: BodyIndex, 22 | } 23 | 24 | #[derive(Debug)] 25 | enum HingeParam { 26 | Bias(f32), 27 | LimitUpper(f32), 28 | LimitLower(f32), 29 | LimitBias(f32), 30 | LimitSoftness(f32), 31 | LimitRelaxation(f32), 32 | MotorTargetVelocity(f32), 33 | MotorMaxImpulse(f32), 34 | } 35 | 36 | enum HingeFlag { 37 | UseLimit(bool), 38 | EnableMotor(bool), 39 | } 40 | 41 | #[derive(Debug)] 42 | enum ParamError { 43 | InvalidParam(i32), 44 | } 45 | 46 | impl Joint { 47 | fn new(joint: T, body_a: BodyIndex, body_b: BodyIndex) -> Self 48 | where 49 | T: Into + Copy, 50 | { 51 | let params = joint.into(); 52 | let bodies = BodyIndex::read_all(); 53 | let result = bodies.get(body_a.into()).and_then(|body_a| { 54 | bodies.get(body_b.into()).and_then(|body_b| { 55 | if let Some((body_a, space_a)) = body_a.as_attached() { 56 | if let Some((body_b, space_b)) = body_b.as_attached() { 57 | if space_a == space_b { 58 | return Some(( 59 | space_a 60 | .map_mut(|space| space.add_joint(params, body_a, body_b)) 61 | .expect("Invalid space"), 62 | space_a, 63 | )); 64 | } else { 65 | godot_error!("Bodies are in different spaces"); 66 | } 67 | } 68 | } 69 | None 70 | }) 71 | }); 72 | if let Some((joint, space)) = result { 73 | Self { 74 | joint: Instance::Attached(joint, space), 75 | exclude_bodies: false, 76 | } 77 | } else { 78 | Self { 79 | joint: Instance::loose(LooseJoint { 80 | params, 81 | body_a, 82 | body_b, 83 | }), 84 | exclude_bodies: false, 85 | } 86 | } 87 | } 88 | 89 | /// Frees this hinge, removing it from it's attached bodies (if any) 90 | fn free(mut self) { 91 | match &mut self.joint { 92 | Instance::Attached(jh, space) => { 93 | space 94 | .map_mut(|space| space.remove_joint(*jh)) 95 | .expect("Invalid space"); 96 | } 97 | Instance::Loose(_) => {} 98 | } 99 | } 100 | } 101 | 102 | impl HingeParam { 103 | fn new(n: i32, value: f32) -> Result { 104 | Ok(match n { 105 | 0 => Self::Bias(value), 106 | 1 => Self::LimitUpper(value), 107 | 2 => Self::LimitLower(value), 108 | 3 => Self::LimitBias(value), 109 | 4 => Self::LimitSoftness(value), 110 | 5 => Self::LimitRelaxation(value), 111 | 6 => Self::MotorTargetVelocity(value), 112 | 7 => Self::MotorMaxImpulse(value), 113 | _ => return Err(ParamError::InvalidParam(n)), 114 | }) 115 | } 116 | } 117 | 118 | impl HingeFlag { 119 | fn new(n: i32, value: bool) -> Result { 120 | Ok(match n { 121 | 0 => Self::UseLimit(value), 122 | 1 => Self::EnableMotor(value), 123 | _ => return Err(ParamError::InvalidParam(n)), 124 | }) 125 | } 126 | } 127 | 128 | pub fn init(ffi: &mut ffi::FFI) { 129 | ffi!(ffi, joint_create_hinge, create_hinge); 130 | ffi!( 131 | ffi, 132 | joint_disable_collisions_between_bodies, 133 | disable_collisions_between_bodies 134 | ); 135 | ffi!(ffi, joint_get_solver_priority, |_| 0); 136 | ffi!(ffi, joint_set_solver_priority, |_, _| {}); 137 | ffi!(ffi, hinge_joint_set_flag, set_hinge_flag); 138 | ffi!(ffi, hinge_joint_set_param, set_hinge_param); 139 | } 140 | 141 | /// Frees the given hinge, removing it from it's attached bodies (if any) 142 | pub fn free(joint: Joint) { 143 | joint.free(); 144 | } 145 | 146 | fn create_hinge( 147 | body_a: Index, 148 | transform_a: &Transform, 149 | body_b: Index, 150 | transform_b: &Transform, 151 | ) -> Option { 152 | let body_a = if let Index::Body(index) = body_a { 153 | index 154 | } else { 155 | godot_error!("ID A does not point to a body"); 156 | return None; 157 | }; 158 | let body_b = if let Index::Body(index) = body_b { 159 | index 160 | } else { 161 | godot_error!("ID B does not point to a body"); 162 | return None; 163 | }; 164 | 165 | let origin_a = transform_a.origin; 166 | let origin_b = transform_b.origin; 167 | let origin_a = Point::new(origin_a.x, origin_a.y, origin_a.z); 168 | let origin_b = Point::new(origin_b.x, origin_b.y, origin_b.z); 169 | 170 | let basis_a = transform_a.basis.transposed(); 171 | let basis_b = transform_b.basis.transposed(); 172 | let axis_a = basis_a.elements[2]; 173 | let axis_b = basis_b.elements[2]; 174 | let axis_a = Unit::new_normalize(vec_gd_to_na(axis_a)); 175 | let axis_b = Unit::new_normalize(vec_gd_to_na(axis_b)); 176 | let basis_a = [ 177 | vec_gd_to_na(basis_a.elements[0]), 178 | vec_gd_to_na(basis_a.elements[1]), 179 | ]; 180 | let basis_b = [ 181 | vec_gd_to_na(basis_b.elements[0]), 182 | vec_gd_to_na(basis_b.elements[1]), 183 | ]; 184 | 185 | let mut joint = RevoluteJoint::new(origin_a, axis_a, origin_b, axis_b); 186 | joint.basis1 = basis_a; 187 | joint.basis2 = basis_b; 188 | 189 | Some(Index::Joint(JointIndex::add(Joint::new( 190 | joint, body_a, body_b, 191 | )))) 192 | } 193 | 194 | fn disable_collisions_between_bodies(joint: Index, disable: bool) { 195 | // *disable* collisions = *enable* exclusion 196 | let enable = disable; 197 | map_or_err!(joint, map_joint_mut, |joint, _| { 198 | if joint.exclude_bodies != enable { 199 | joint.exclude_bodies = enable; 200 | if let Instance::Attached(joint, space) = joint.joint { 201 | map_or_err!(space, map_mut, |space| { 202 | let joint = space.joints().get(joint).expect("Invalid joint"); 203 | // FIXME this can (and will) panic if a body is freed. Check again later 204 | // for the cleanest way to handle this (should the joint be freed?) 205 | let a = space.bodies().get(joint.body1).expect("Invalid body A"); 206 | let b = space.bodies().get(joint.body2).expect("Invalid body B"); 207 | let a = body::RigidBodyUserdata::try_from(a).unwrap().index(); 208 | let b = body::RigidBodyUserdata::try_from(b).unwrap().index(); 209 | // Adding an exclusion multiple times is valid. 210 | if enable { 211 | let _ = space.add_body_exclusion(a, b); 212 | } else { 213 | let _ = space.remove_body_exclusion(a, b); 214 | } 215 | }); 216 | } 217 | } 218 | }); 219 | } 220 | 221 | fn set_hinge_flag(joint: Index, flag: i32, value: bool) { 222 | let flag = match HingeFlag::new(flag, value) { 223 | Ok(f) => f, 224 | Err(e) => { 225 | godot_error!("Failed to apply hinge joint parameter: {:?}", e); 226 | return; 227 | } 228 | }; 229 | let apply = |joint: &mut JointParams| { 230 | if let JointParams::RevoluteJoint(j) = joint { 231 | match flag { 232 | HingeFlag::UseLimit(_) => godot_error!("TODO"), 233 | HingeFlag::EnableMotor(v) => { 234 | j.motor_model = if v { 235 | SpringModel::default() 236 | } else { 237 | SpringModel::Disabled 238 | } 239 | } 240 | } 241 | } else { 242 | godot_error!("Joint is not a hinge joint"); 243 | } 244 | }; 245 | map_or_err!(joint, map_joint_mut, |joint, _| { 246 | match &mut joint.joint { 247 | Instance::Attached(jh, space) => { 248 | space 249 | .map_mut(|space| { 250 | apply( 251 | &mut space 252 | .joints_mut() 253 | .get_mut(*jh) 254 | .expect("Invalid joint handle") 255 | .params, 256 | ); 257 | }) 258 | .expect("Invalid space"); 259 | } 260 | Instance::Loose(j) => apply(&mut j.params), 261 | } 262 | }); 263 | } 264 | 265 | fn set_hinge_param(joint: Index, param: i32, value: f32) { 266 | let param = match HingeParam::new(param, value) { 267 | Ok(p) => p, 268 | Err(e) => { 269 | godot_error!("Failed to apply hinge joint parameter: {:?}", e); 270 | return; 271 | } 272 | }; 273 | let apply = |joint: &mut JointParams| { 274 | if let JointParams::RevoluteJoint(j) = joint { 275 | let e = || godot_error!("TODO"); 276 | match param { 277 | // FIXME it seems this parameter doesn't exist in nphysics3d either. How should 278 | // we handle it? 279 | HingeParam::Bias(_) => e(), 280 | HingeParam::LimitBias(_) => e(), 281 | // FIXME it seems there are no configurable limits on hinge joints? 282 | // Judging by the nphysics3d documentation and the fact Rapier is a successor, 283 | // it will hopefully be implemented soon. 284 | HingeParam::LimitUpper(_) => e(), 285 | HingeParam::LimitLower(_) => e(), 286 | HingeParam::LimitSoftness(_) => e(), 287 | HingeParam::LimitRelaxation(_) => e(), 288 | HingeParam::MotorTargetVelocity(v) => j.configure_motor_velocity(-v, 1.0), 289 | HingeParam::MotorMaxImpulse(v) => j.motor_max_impulse = v, 290 | } 291 | } else { 292 | godot_error!("Joint is not a hinge joint"); 293 | } 294 | }; 295 | map_or_err!(joint, map_joint_mut, |joint, _| { 296 | match &mut joint.joint { 297 | Instance::Attached(jh, space) => { 298 | space 299 | .map_mut(|space| { 300 | apply( 301 | &mut space 302 | .joints_mut() 303 | .get_mut(*jh) 304 | .expect("Invalid joint handle") 305 | .params, 306 | ); 307 | }) 308 | .expect("Invalid space"); 309 | } 310 | Instance::Loose(j) => apply(&mut j.params), 311 | } 312 | }); 313 | } 314 | -------------------------------------------------------------------------------- /rapier3d/build.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{format_ident, quote}; 3 | use std::env; 4 | use std::fs::File; 5 | use std::io::{Read, Write}; 6 | use std::path::PathBuf; 7 | 8 | fn main() { 9 | println!("cargo:rerun-if-changed=build.rs"); 10 | println!("cargo:rerun-if-changed=Cargo.lock"); 11 | println!("cargo:rerun-if-changed=api.json"); 12 | 13 | let mut file = File::open("api.json").expect("Failed to open API"); 14 | let mut api = String::new(); 15 | file.read_to_string(&mut api).expect("Failed to read API"); 16 | drop(file); 17 | let api = json::parse(&api).expect("Failed to parse API"); 18 | 19 | let mut bindings = generate_struct(&api); 20 | bindings.extend(generate_impl(&api)); 21 | bindings.extend(generate_structs(&api)); 22 | 23 | let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("ffi.rs"); 24 | let mut out = File::create(out).expect("Failed to create bindings"); 25 | out.write_all(bindings.to_string().as_bytes()) 26 | .expect("Failed to write bindings!"); 27 | } 28 | 29 | fn generate_struct(api: &json::JsonValue) -> TokenStream { 30 | let mut methods_unsafe = TokenStream::new(); 31 | for info in api["methods"].members() { 32 | let method = info["name"].as_str().unwrap(); 33 | let ret_type = info["return_type"].as_str().unwrap(); 34 | let mut args_unsafe = TokenStream::new(); 35 | for a in info["arguments"].members() { 36 | let name = a["name"].as_str().unwrap(); 37 | assert_eq!(name.find('*'), None, "Method name: \"{}\"", name); 38 | let name: TokenStream = name.parse().unwrap(); 39 | args_unsafe.extend(name.clone()); 40 | args_unsafe.extend(quote!(:)); 41 | args_unsafe.extend(map_type(a["type"].as_str().unwrap())); 42 | args_unsafe.extend(quote!(,)); 43 | } 44 | 45 | let method = format_ident!("{}", method); 46 | let ret = map_type(ret_type); 47 | methods_unsafe.extend(quote! { 48 | pub #method: Option #ret>, 49 | }); 50 | } 51 | quote! { 52 | #[repr(C)] 53 | pub struct UnsafeApi { 54 | #methods_unsafe 55 | } 56 | } 57 | } 58 | 59 | fn generate_impl(api: &json::JsonValue) -> TokenStream { 60 | let mut methods = TokenStream::new(); 61 | let mut methods_none = TokenStream::new(); 62 | for info in api["methods"].members() { 63 | let method = info["name"].as_str().unwrap(); 64 | let ret_type = info["return_type"].as_str().unwrap(); 65 | let mut args = TokenStream::new(); 66 | let mut params = TokenStream::new(); 67 | let mut params_safe = TokenStream::new(); 68 | let mut convert_from_sys = TokenStream::new(); 69 | let mut forget = TokenStream::new(); 70 | for a in info["arguments"].members() { 71 | let name = a["name"].as_str().unwrap(); 72 | let typ = a["type"].as_str().unwrap(); 73 | assert_eq!(name.find('*'), None, "Method name: \"{}\"", name); 74 | let name: TokenStream = name.parse().unwrap(); 75 | params.extend(name.clone()); 76 | params.extend(quote!(:)); 77 | params.extend(map_type(typ)); 78 | params.extend(quote!(,)); 79 | params_safe.extend(get_type_from_sys(typ)); 80 | params_safe.extend(quote!(,)); 81 | let (tk, do_forget) = convert_type_from_sys(&name, typ); 82 | if do_forget { 83 | args.extend(quote!(&)); 84 | forget.extend(quote!(#name.forget();)); 85 | } 86 | args.extend(name.clone()); 87 | args.extend(quote!(,)); 88 | convert_from_sys.extend(tk); 89 | convert_from_sys.extend(quote!(;)); 90 | } 91 | let method_wrapped = format_ident!("ffi_{}", method); 92 | let method = format_ident!("{}", method); 93 | let ret = map_type(ret_type); 94 | let (ret_wrap_pre, ret_wrap_post) = if ret_type == "maybe_index_t" { 95 | (quote!(if let Some(r) = ), quote!({ r.raw() } else { 0 })) 96 | } else if ret_type == "index_t" { 97 | (quote!(), quote!(.raw())) 98 | } else { 99 | (quote!(), quote!()) 100 | }; 101 | methods.extend(quote! { 102 | ($ffi:ident, #method, $fn:expr) => { 103 | #[allow(unused_imports)] 104 | #[allow(non_snake_case)] 105 | { 106 | use gdnative::sys; 107 | use crate::server::ffi::*; 108 | unsafe extern "C" fn #method_wrapped(#params) -> #ret { 109 | #convert_from_sys 110 | let r = $fn(#args); 111 | #forget 112 | #ret_wrap_pre r #ret_wrap_post 113 | } 114 | // SAFETY: self.table is a valid pointer 115 | unsafe { 116 | (*$ffi.table).#method = Some(#method_wrapped); 117 | } 118 | } 119 | }; 120 | }); 121 | methods_none.extend(quote!(#method: None,)); 122 | } 123 | quote! { 124 | macro_rules! ffi { 125 | #methods 126 | } 127 | } 128 | } 129 | 130 | fn generate_structs(api: &json::JsonValue) -> TokenStream { 131 | let mut structs = quote!(); 132 | for (name, members) in api["structs"].entries() { 133 | let name = format_ident!("{}", name); 134 | let mut fields = quote!(); 135 | for m in members.members() { 136 | let name: TokenStream = m["name"].as_str().unwrap().parse().unwrap(); 137 | let typ = m["type"].as_str().unwrap(); 138 | let typ = map_type(typ); 139 | fields.extend(quote!(#name: #typ,)); 140 | } 141 | let struc = quote! { 142 | #[repr(C)] 143 | pub struct #name { 144 | #fields 145 | } 146 | }; 147 | structs.extend(struc); 148 | } 149 | structs 150 | } 151 | 152 | fn map_type(t: &str) -> TokenStream { 153 | match t { 154 | "index_t" | "maybe_index_t" => quote!(u64), 155 | "uint8_t" => quote!(u8), 156 | "uint32_t" => quote!(u32), 157 | "int" => quote!(i32), 158 | "bool" => quote!(bool), 159 | "float" | "real_t" => quote!(f32), 160 | "size_t" => quote!(usize), 161 | "void" => quote!(()), 162 | "void *" => quote!(*const ()), 163 | "const wchar_t *" => quote!(*const wchar::wchar_t), 164 | "const index_t *" => quote!(*mut u64), 165 | "struct physics_call_result" => quote!(physics_call_result), 166 | "const godot_variant **" => quote!(*const *const sys::godot_variant), 167 | _ if t.starts_with("struct ") && t.ends_with(" *") => { 168 | format!("*mut {}", &t["struct ".len()..t.len() - " *".len()]) 169 | .parse() 170 | .unwrap() 171 | } 172 | _ if t.starts_with("const struct ") && t.ends_with(" *") => { 173 | format!("*const {}", &t["const struct ".len()..t.len() - " *".len()]) 174 | .parse() 175 | .unwrap() 176 | } 177 | _ if t.starts_with("godot_") => { 178 | if let Some(t) = t.strip_suffix(" *") { 179 | format!("*mut sys::{}", t).parse().unwrap() 180 | } else { 181 | format!("sys::{}", t).parse().unwrap() 182 | } 183 | } 184 | _ if t.starts_with("const godot_") && t.ends_with(" *") => { 185 | format!("*const sys::{}", &t["const ".len()..t.len() - " *".len()]) 186 | .parse() 187 | .unwrap() 188 | } 189 | _ => panic!("Unhandled type: {}", t), 190 | } 191 | } 192 | 193 | fn convert_type_from_sys(name: &TokenStream, t: &str) -> (TokenStream, bool) { 194 | let mut r = quote!(let #name =); 195 | let t = match t { 196 | "index_t" => ( 197 | quote!(Index::from_raw(#name).expect("Invalid index")), 198 | false, 199 | ), 200 | "maybe_index_t" => ( 201 | quote!(if #name == 0 { None } else { Some(Index::from_raw(#name).expect("Invalid index")) }), 202 | false, 203 | ), 204 | "void" => (quote!(()), false), 205 | "size_t" | "uint32_t" | "int" | "bool" | "float" | "real_t" | "void *" => { 206 | (quote!(#name), false) 207 | } 208 | "const wchar_t *" => (quote!(#name), false), 209 | "struct physics_ray_info" => (quote!(&*#name), false), 210 | "godot_rid" => (quote!(RID), false), 211 | "godot_vector3 *" => ( 212 | quote!(&mut *(#name as *mut sys::godot_vector3 as *mut Vector3)), 213 | false, 214 | ), 215 | //"godot_variant" => (quote!(#name), false), 216 | "const godot_variant *" => ( 217 | quote!(&*(#name as *const sys::godot_variant as *const Variant)), 218 | false, 219 | ), 220 | "const godot_variant **" => (quote!(#name as *const &Variant), false), 221 | "const godot_transform *" => ( 222 | quote!(&*(#name as *const sys::godot_transform as *const Transform)), 223 | false, 224 | ), 225 | "const godot_vector3 *" => ( 226 | quote!(&*(#name as *const sys::godot_vector3 as *const Vector3)), 227 | false, 228 | ), 229 | "godot_pool_vector3_array" => (quote!(TypedArray::::from_sys(#name)), false), 230 | _ if t.starts_with("const struct ") && t.ends_with(" *") => (quote!(&*#name), false), 231 | _ if t.starts_with("struct ") && t.ends_with(" *") => (quote!(&mut *#name), false), 232 | _ => panic!("Unhandled type: {}", t), 233 | }; 234 | r.extend(t.0); 235 | (r, t.1) 236 | } 237 | 238 | fn get_type_from_sys(t: &str) -> TokenStream { 239 | match t { 240 | "index_t" => quote!(Index), 241 | "maybe_index_t" => quote!(Option), 242 | "void *" => quote!(*const ()), 243 | "uint8_t" => quote!(u8), 244 | "uint32_t" => quote!(u32), 245 | "int" => quote!(i32), 246 | "bool" => quote!(bool), 247 | "float" | "real_t" => quote!(f32), 248 | "size_t" => quote!(usize), 249 | "const wchar_t *" => quote!(*const wchar::wchar_t), 250 | "godot_rid" => quote!(RID), 251 | "godot_vector3 *" => quote!(&mut Vector3), 252 | "godot_pool_vector3_array" => quote!(TypedArray), 253 | //"godot_variant" => quote!(Variant), 254 | "const godot_variant *" => quote!(&Variant), 255 | "const godot_variant **" => quote!(*const *const sys::godot_variant), 256 | "const godot_transform *" => quote!(&Transform), 257 | "const godot_vector3 *" => quote!(&Vector3), 258 | _ if t.starts_with("struct ") && t.ends_with(" *") => { 259 | format!("&mut {}", &t["struct ".len()..t.len() - " *".len()]) 260 | .parse() 261 | .unwrap() 262 | } 263 | _ if t.starts_with("const struct ") && t.ends_with(" *") => { 264 | format!("&{}", &t["const struct ".len()..t.len() - " *".len()]) 265 | .parse() 266 | .unwrap() 267 | } 268 | _ => panic!("Unhandled type: {}", t), 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /rapier3d/src/server/ffi.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(improper_ctypes)] 5 | #![allow(unused_parens)] 6 | #![allow(dead_code)] 7 | #![allow(clippy::all)] 8 | 9 | //include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 10 | include!(concat!(env!("OUT_DIR"), "/ffi.rs")); 11 | 12 | use super::ObjectID; 13 | use gdnative::core_types::*; 14 | use gdnative::sys; 15 | use std::slice; 16 | 17 | pub type PhysicsCallResult = physics_call_result; 18 | pub type PhysicsBodyState = physics_body_state; 19 | pub type PhysicsBodyContact = physics_body_contact; 20 | pub type PhysicsAreaMonitorEvent = physics_area_monitor_event; 21 | pub type PhysicsRayResult = physics_ray_result; 22 | pub type PhysicsRayInfo = physics_ray_info; 23 | pub type PhysicsShapeResult = physics_shape_result; 24 | pub type PhysicsShapeInfo = physics_shape_info; 25 | pub type PhysicsServer = physics_server; 26 | 27 | #[macro_export] 28 | macro_rules! gdphysics_init { 29 | ($fn:ident) => { 30 | #[no_mangle] 31 | extern "C" fn gdphysics_init(server: *mut ffi::PhysicsServer, table: *mut ffi::UnsafeApi) { 32 | if table.is_null() { 33 | println!("Function table pointer is null"); 34 | } else { 35 | let mut ffi = ffi::FFI { table, server }; 36 | $fn(&mut ffi); 37 | } 38 | } 39 | }; 40 | } 41 | 42 | pub struct FFI { 43 | pub table: *mut UnsafeApi, 44 | pub server: *const PhysicsServer, 45 | } 46 | 47 | #[allow(dead_code)] 48 | pub enum VariantType { 49 | Nil, 50 | Bool, 51 | Int, 52 | Real, 53 | String, 54 | Vector2, // 5 55 | Rect2, 56 | Vector3, 57 | Transform2d, 58 | Plane, 59 | Quat, // 10, 60 | Aabb, 61 | Basis, 62 | Transform, 63 | Color, 64 | NodePath, // 15 65 | Rid, 66 | Object, 67 | Dictionary, 68 | Array, 69 | PoolByteArray, // 20 70 | PoolIntArray, 71 | PoolRealArray, 72 | PoolStringArray, 73 | PoolVector2Array, 74 | PoolVector3Array, // 25 75 | PoolColorArray, 76 | } 77 | 78 | /// Error that may be returned for `ffi::call´ 79 | pub enum PhysicsCallError { 80 | InvalidMethod, 81 | InvalidArgument { 82 | argument: u8, 83 | expected_type: VariantType, 84 | }, 85 | TooManyArguments { 86 | max: u8, 87 | }, 88 | TooFewArguments { 89 | min: u8, 90 | }, 91 | InstanceIsNull, 92 | } 93 | 94 | use super::Index; 95 | 96 | impl From for u8 { 97 | fn from(vt: VariantType) -> u8 { 98 | match vt { 99 | VariantType::Nil => 0, 100 | VariantType::Bool => 1, 101 | VariantType::Int => 2, 102 | VariantType::Real => 3, 103 | VariantType::String => 4, 104 | VariantType::Vector2 => 5, 105 | VariantType::Rect2 => 6, 106 | VariantType::Vector3 => 7, 107 | VariantType::Transform2d => 8, 108 | VariantType::Plane => 9, 109 | VariantType::Quat => 10, 110 | VariantType::Aabb => 11, 111 | VariantType::Basis => 12, 112 | VariantType::Transform => 13, 113 | VariantType::Color => 14, 114 | VariantType::NodePath => 15, 115 | VariantType::Rid => 16, 116 | VariantType::Object => 17, 117 | VariantType::Dictionary => 18, 118 | VariantType::Array => 19, 119 | VariantType::PoolByteArray => 20, 120 | VariantType::PoolIntArray => 21, 121 | VariantType::PoolRealArray => 22, 122 | VariantType::PoolStringArray => 23, 123 | VariantType::PoolVector2Array => 24, 124 | VariantType::PoolVector3Array => 25, 125 | VariantType::PoolColorArray => 26, 126 | } 127 | } 128 | } 129 | 130 | impl PhysicsCallError { 131 | pub fn invalid_argument(argument: u8, expected_type: VariantType) -> Self { 132 | Self::InvalidArgument { 133 | argument, 134 | expected_type, 135 | } 136 | } 137 | } 138 | 139 | impl PhysicsCallResult { 140 | pub fn new(from: Result) -> Self { 141 | match from { 142 | Ok(v) => Self { 143 | value: v.forget(), 144 | status: 0, 145 | argument: 0, 146 | expected_type: 0, 147 | }, 148 | Err(e) => Self { 149 | value: Variant::new().forget(), 150 | status: match &e { 151 | PhysicsCallError::InvalidMethod => 1, 152 | PhysicsCallError::InvalidArgument { .. } => 2, 153 | PhysicsCallError::TooManyArguments { .. } => 3, 154 | PhysicsCallError::TooFewArguments { .. } => 4, 155 | PhysicsCallError::InstanceIsNull => 5, 156 | }, 157 | argument: match &e { 158 | PhysicsCallError::InvalidArgument { argument: v, .. } 159 | | PhysicsCallError::TooFewArguments { min: v } 160 | | PhysicsCallError::TooManyArguments { max: v } => *v, 161 | _ => 0, 162 | }, 163 | expected_type: match e { 164 | PhysicsCallError::InvalidArgument { expected_type, .. } => expected_type.into(), 165 | _ => 0, 166 | }, 167 | }, 168 | } 169 | } 170 | } 171 | 172 | impl PhysicsBodyState { 173 | pub fn set_space(&mut self, index: Option) { 174 | self.space = index.map(Index::raw).unwrap_or(Index::INVALID_RAW); 175 | } 176 | 177 | pub fn set_transform(&mut self, transform: &Transform) { 178 | // SAFETY: transform is guaranteed to be valid 179 | unsafe { 180 | self.transform = *transform.sys(); 181 | } 182 | } 183 | 184 | pub fn set_linear_velocity(&mut self, velocity: Vector3) { 185 | self.linear_velocity = velocity.to_sys(); 186 | } 187 | 188 | pub fn set_angular_velocity(&mut self, velocity: Vector3) { 189 | self.angular_velocity = velocity.to_sys(); 190 | } 191 | 192 | pub fn set_inv_inertia(&mut self, inv_inertia: Vector3) { 193 | self.inv_inertia = inv_inertia.to_sys(); 194 | } 195 | 196 | pub fn set_inv_inertia_tensor(&mut self, inv_inertia_tensor: &Basis) { 197 | // SAFETY: inv_inertia_tensor is guaranteed to be valid 198 | unsafe { 199 | self.inv_inertia_tensor = *inv_inertia_tensor.sys(); 200 | } 201 | } 202 | 203 | pub fn set_gravity(&mut self, gravity: Vector3) { 204 | self.gravity = gravity.to_sys(); 205 | } 206 | 207 | pub fn set_linear_damp(&mut self, damp: f32) { 208 | self.linear_damp = damp; 209 | } 210 | 211 | pub fn set_angular_damp(&mut self, damp: f32) { 212 | self.angular_damp = damp; 213 | } 214 | 215 | pub fn set_sleeping(&mut self, sleeping: bool) { 216 | self.sleeping = sleeping; 217 | } 218 | 219 | pub fn set_mass(&mut self, mass: f32) { 220 | self.inv_mass = 1.0 / mass; 221 | } 222 | 223 | pub fn set_inv_mass(&mut self, inv_mass: f32) { 224 | self.inv_mass = inv_mass; 225 | } 226 | 227 | pub fn set_center_of_mass(&mut self, center: Vector3) { 228 | self.center_of_mass = center.to_sys(); 229 | } 230 | 231 | pub fn set_contact_count(&mut self, count: u32) { 232 | self.contact_count = count; 233 | } 234 | } 235 | 236 | impl PhysicsBodyContact { 237 | pub fn set_index(&mut self, index: Index) { 238 | self.index = index.raw(); 239 | } 240 | 241 | pub fn set_position(&mut self, position: Vector3) { 242 | self.position = position.to_sys(); 243 | } 244 | 245 | pub fn set_object_id(&mut self, id: Option) { 246 | self.object_id = id.map(ObjectID::get).unwrap_or(0) as i32; 247 | } 248 | 249 | pub fn set_shape(&mut self, shape: u32) { 250 | self.shape = shape; 251 | } 252 | 253 | pub fn set_local_position(&mut self, position: Vector3) { 254 | self.local_position = position.to_sys(); 255 | } 256 | 257 | pub fn set_local_normal(&mut self, normal: Vector3) { 258 | self.local_normal = normal.to_sys(); 259 | } 260 | 261 | pub fn set_local_shape(&mut self, shape: u32) { 262 | self.local_shape = shape; 263 | } 264 | 265 | pub fn set_impulse(&mut self, impulse: f32) { 266 | self.impulse = impulse; 267 | } 268 | } 269 | 270 | impl PhysicsRayResult { 271 | pub fn set_position(&mut self, position: Vector3) { 272 | self.position = Vector3::to_sys(position) 273 | } 274 | 275 | pub fn set_normal(&mut self, normal: Vector3) { 276 | self.normal = Vector3::to_sys(normal) 277 | } 278 | 279 | pub fn set_index(&mut self, index: Index) { 280 | self.id = index.raw(); 281 | } 282 | 283 | pub fn set_object_id(&mut self, id: Option) { 284 | self.object_id = id.map(ObjectID::get).unwrap_or(0) as i32; 285 | } 286 | 287 | pub fn set_shape(&mut self, index: u32) { 288 | self.shape = index as i32; 289 | } 290 | } 291 | 292 | impl PhysicsRayInfo { 293 | pub fn from(&self) -> Vector3 { 294 | Vector3::from_sys(self.from) 295 | } 296 | 297 | pub fn to(&self) -> Vector3 { 298 | Vector3::from_sys(self.to) 299 | } 300 | 301 | pub fn exclude_raw<'a>(&'a self) -> &'a [u64] { 302 | // SAFETY: exclude has at least as many elements as specified in exclude_count 303 | unsafe { slice::from_raw_parts(self.exclude, self.exclude_count) } 304 | } 305 | 306 | pub fn collision_mask(&self) -> u32 { 307 | self.collision_mask 308 | } 309 | 310 | pub fn collide_with_bodies(&self) -> bool { 311 | self.collide_with_bodies 312 | } 313 | 314 | pub fn collide_with_areas(&self) -> bool { 315 | self.collide_with_areas 316 | } 317 | 318 | pub fn pick_ray(&self) -> bool { 319 | self.pick_ray 320 | } 321 | } 322 | 323 | impl PhysicsShapeResult { 324 | pub fn set_index(&mut self, index: Index) { 325 | self.id = index.raw(); 326 | } 327 | 328 | pub fn set_object_id(&mut self, id: Option) { 329 | self.object_id = id.map(ObjectID::get).unwrap_or(0) as i32; 330 | } 331 | 332 | pub fn set_shape(&mut self, index: u32) { 333 | self.shape = index as i32; 334 | } 335 | } 336 | 337 | #[derive(Debug)] 338 | pub enum InvalidShapeIndex { 339 | NotAShape, 340 | InvalidIndex(super::InvalidIndex), 341 | } 342 | 343 | impl PhysicsShapeInfo { 344 | pub fn shape(&self) -> Result { 345 | Index::from_raw(self.shape) 346 | .map_err(InvalidShapeIndex::InvalidIndex) 347 | .and_then(|i| i.as_shape().ok_or(InvalidShapeIndex::NotAShape)) 348 | } 349 | 350 | pub fn transform(&self) -> &Transform { 351 | // SAFETY: the two types are ABI-compatible. 352 | unsafe { &*(self.transform as *const _ as *const _) } 353 | } 354 | 355 | pub fn exclude_raw<'a>(&'a self) -> &'a [u64] { 356 | // SAFETY: exclude has at least as many elements as specified in exclude_count 357 | unsafe { slice::from_raw_parts(self.exclude, self.exclude_count) } 358 | } 359 | 360 | pub fn collision_mask(&self) -> u32 { 361 | self.collision_mask 362 | } 363 | 364 | pub fn collide_with_bodies(&self) -> bool { 365 | self.collide_with_bodies 366 | } 367 | 368 | pub fn collide_with_areas(&self) -> bool { 369 | self.collide_with_areas 370 | } 371 | } 372 | 373 | impl PhysicsAreaMonitorEvent { 374 | pub fn set_object_id(&mut self, id: Option) { 375 | self.object_id = id.map(ObjectID::get).unwrap_or(0) as i32; 376 | } 377 | 378 | pub fn set_index(&mut self, index: Index) { 379 | self.id = index.raw(); 380 | } 381 | 382 | pub fn set_added(&mut self, added: bool) { 383 | self.added = added; 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /rapier3d/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod ffi; 3 | #[macro_use] 4 | mod call; 5 | mod area; 6 | mod body; 7 | mod index; 8 | mod joint; 9 | mod shape; 10 | mod space; 11 | 12 | use crate::area::Area; 13 | use crate::body::Body; 14 | use crate::indices::Indices; 15 | use crate::space::Space; 16 | use crate::*; 17 | use core::convert::TryInto; 18 | use core::num::NonZeroU32; 19 | use gdnative::core_types::{Rid, Variant}; 20 | use gdnative::{godot_error, sys}; 21 | pub use index::*; 22 | use joint::Joint; 23 | use rapier3d::na; 24 | pub use shape::Shape; 25 | use std::sync::{Mutex, MutexGuard}; 26 | 27 | lazy_static::lazy_static! { 28 | static ref ACTIVE: Mutex = Mutex::new(true); 29 | static ref AREA_INDICES: Mutex> = Mutex::new(Indices::new()); 30 | static ref BODY_INDICES: Mutex> = Mutex::new(Indices::new()); 31 | static ref JOINT_INDICES: Mutex> = Mutex::new(Indices::new()); 32 | static ref SHAPE_INDICES: Mutex> = Mutex::new(Indices::new()); 33 | static ref SPACE_INDICES: Mutex> = Mutex::new(Indices::new()); 34 | } 35 | 36 | static mut PHYSICS_SERVER: Option<*const ffi::PhysicsServer> = None; 37 | static mut GET_RID: Option sys::godot_rid> = 38 | None; 39 | static mut GET_INDEX: Option< 40 | unsafe extern "C" fn(*const ffi::PhysicsServer, sys::godot_rid) -> u64, 41 | > = None; 42 | 43 | pub enum Instance { 44 | Attached(A, SpaceIndex), 45 | Loose(Box), 46 | } 47 | 48 | impl Instance { 49 | /// Create a new loose instance 50 | pub fn loose(item: L) -> Self { 51 | Self::Loose(Box::new(item)) 52 | } 53 | } 54 | 55 | #[derive(Debug)] 56 | pub enum IndexError { 57 | WrongType, 58 | NoElement, 59 | } 60 | 61 | #[derive(Clone, Copy)] 62 | pub struct ObjectID(NonZeroU32); 63 | 64 | pub trait MapIndex { 65 | fn map(&self, f: F) -> Result 66 | where 67 | F: FnOnce(&T) -> R; 68 | 69 | fn map_mut(&self, f: F) -> Result 70 | where 71 | F: FnOnce(&mut T) -> R; 72 | 73 | fn add(element: T) -> Self; 74 | 75 | fn remove(self) -> Result; 76 | 77 | fn read_all() -> MutexGuard<'static, Indices>; 78 | 79 | fn write_all() -> MutexGuard<'static, Indices>; 80 | } 81 | 82 | trait MapEnumIndex { 83 | fn map_area(&self, f: F) -> Result 84 | where 85 | F: FnOnce(&Area, AreaIndex) -> R; 86 | 87 | fn map_area_mut(&self, f: F) -> Result 88 | where 89 | F: FnOnce(&mut Area, AreaIndex) -> R; 90 | 91 | fn map_body(&self, f: F) -> Result 92 | where 93 | F: FnOnce(&Body, BodyIndex) -> R; 94 | 95 | fn map_body_mut(&self, f: F) -> Result 96 | where 97 | F: FnOnce(&mut Body, BodyIndex) -> R; 98 | 99 | fn map_joint(&self, f: F) -> Result 100 | where 101 | F: FnOnce(&Joint, JointIndex) -> R; 102 | 103 | fn map_joint_mut(&self, f: F) -> Result 104 | where 105 | F: FnOnce(&mut Joint, JointIndex) -> R; 106 | 107 | fn map_shape(&self, f: F) -> Result 108 | where 109 | F: FnOnce(&Shape, ShapeIndex) -> R; 110 | 111 | fn map_shape_mut(&self, f: F) -> Result 112 | where 113 | F: FnOnce(&mut Shape, ShapeIndex) -> R; 114 | 115 | fn map_space(&self, f: F) -> Result 116 | where 117 | F: FnOnce(&Space, SpaceIndex) -> R; 118 | 119 | fn map_space_mut(&self, f: F) -> Result 120 | where 121 | F: FnOnce(&mut Space, SpaceIndex) -> R; 122 | } 123 | 124 | macro_rules! map_index { 125 | ($array:ident, $variant:ident, $index:ident,) => { 126 | impl MapIndex<$variant> for $index { 127 | fn map(&self, f: F) -> Result 128 | where 129 | F: FnOnce(&$variant) -> R, 130 | { 131 | let w = Self::read_all(); 132 | if let Some(element) = w.get(self.into()) { 133 | Ok(f(element)) 134 | } else { 135 | Err(IndexError::NoElement) 136 | } 137 | } 138 | 139 | fn map_mut(&self, f: F) -> Result 140 | where 141 | F: FnOnce(&mut $variant) -> R, 142 | { 143 | let mut w = Self::write_all(); 144 | if let Some(element) = w.get_mut(self.into()) { 145 | Ok(f(element)) 146 | } else { 147 | Err(IndexError::NoElement) 148 | } 149 | } 150 | 151 | fn add(element: $variant) -> Self { 152 | let mut w = Self::write_all(); 153 | w.add(element).into() 154 | } 155 | 156 | fn remove(self) -> Result<$variant, IndexError> { 157 | let mut w = Self::write_all(); 158 | if let Some(element) = w.remove(self.into()) { 159 | Ok(element) 160 | } else { 161 | Err(IndexError::NoElement) 162 | } 163 | } 164 | 165 | fn read_all() -> MutexGuard<'static, Indices<$variant>> { 166 | const MSG: &str = concat!("Failed to read-lock ", stringify!($variant), " array"); 167 | $array.lock().expect(MSG) 168 | } 169 | 170 | fn write_all() -> MutexGuard<'static, Indices<$variant>> { 171 | const MSG: &str = concat!("Failed to write-lock ", stringify!($variant), " array"); 172 | $array.lock().expect(MSG) 173 | } 174 | } 175 | }; 176 | } 177 | 178 | macro_rules! map_enum_index { 179 | ($fn_map:ident, $fn_map_mut:ident, $array:ident, $variant:ident, $index:ident,) => { 180 | #[allow(dead_code)] 181 | fn $fn_map(&self, f: F) -> Result 182 | where 183 | F: FnOnce(&$variant, $index) -> R, 184 | { 185 | if let Index::$variant(index) = self { 186 | index.map(|e| f(e, *index)) 187 | } else { 188 | Err(IndexError::WrongType) 189 | } 190 | } 191 | 192 | #[allow(dead_code)] 193 | fn $fn_map_mut(&self, f: F) -> Result 194 | where 195 | F: FnOnce(&mut $variant, $index) -> R, 196 | { 197 | if let Index::$variant(index) = self { 198 | index.map_mut(|e| f(e, *index)) 199 | } else { 200 | Err(IndexError::WrongType) 201 | } 202 | } 203 | }; 204 | } 205 | 206 | impl Instance { 207 | pub fn as_attached(&self) -> Option<(&A, SpaceIndex)> { 208 | if let Instance::Attached(a, i) = self { 209 | Some((a, *i)) 210 | } else { 211 | None 212 | } 213 | } 214 | 215 | pub fn as_attached_mut(&mut self) -> Option<(&mut A, SpaceIndex)> { 216 | if let Instance::Attached(a, i) = self { 217 | Some((a, *i)) 218 | } else { 219 | None 220 | } 221 | } 222 | } 223 | 224 | map_index!(AREA_INDICES, Area, AreaIndex,); 225 | map_index!(BODY_INDICES, Body, BodyIndex,); 226 | map_index!(JOINT_INDICES, Joint, JointIndex,); 227 | map_index!(SHAPE_INDICES, Shape, ShapeIndex,); 228 | map_index!(SPACE_INDICES, Space, SpaceIndex,); 229 | 230 | impl MapEnumIndex for Index { 231 | map_enum_index!(map_area, map_area_mut, AREA_INDICES, Area, AreaIndex,); 232 | map_enum_index!(map_body, map_body_mut, BODY_INDICES, Body, BodyIndex,); 233 | map_enum_index!(map_joint, map_joint_mut, JOINT_INDICES, Joint, JointIndex,); 234 | map_enum_index!(map_shape, map_shape_mut, SHAPE_INDICES, Shape, ShapeIndex,); 235 | map_enum_index!(map_space, map_space_mut, SPACE_INDICES, Space, SpaceIndex,); 236 | } 237 | 238 | impl ObjectID { 239 | fn new(n: u32) -> Option { 240 | NonZeroU32::new(n).map(Self) 241 | } 242 | 243 | fn get(self) -> u32 { 244 | self.0.get() 245 | } 246 | } 247 | 248 | #[macro_export] 249 | macro_rules! map_or_err { 250 | ($index:expr, $func:ident, $closure:expr) => { 251 | match $index.$func($closure) { 252 | Ok(v) => Some(v), 253 | Err(e) => { 254 | use gdnative::prelude::godot_error; 255 | match e { 256 | IndexError::WrongType => godot_error!("ID is of wrong type {:?}", $index), 257 | IndexError::NoElement => godot_error!("No element at ID {:?}", $index), 258 | } 259 | None 260 | } 261 | } 262 | }; 263 | } 264 | 265 | fn init(ffi: &mut ffi::FFI) { 266 | unsafe { 267 | PHYSICS_SERVER = Some(ffi.server); 268 | GET_INDEX = (*ffi.table).server_get_index; 269 | GET_RID = (*ffi.table).server_get_rid; 270 | } 271 | ffi!(ffi, call, call::call); 272 | ffi!(ffi, set_active, set_active); 273 | ffi!(ffi, init, server_init); 274 | ffi!(ffi, flush_queries, flush_queries); 275 | ffi!(ffi, step, step); 276 | ffi!(ffi, sync, sync); 277 | ffi!(ffi, free, free); 278 | ffi!(ffi, get_process_info, get_process_info); 279 | area::init(ffi); 280 | body::init(ffi); 281 | joint::init(ffi); 282 | space::init(ffi); 283 | shape::init(ffi); 284 | } 285 | 286 | gdphysics_init!(init); 287 | 288 | fn server_init() {} 289 | 290 | fn step(delta: f32) { 291 | if *ACTIVE.lock().expect("Failed to check ACTIVE") { 292 | for (_, area) in AreaIndex::write_all().iter_mut() { 293 | area.clear_events(); 294 | } 295 | for (_, space) in SpaceIndex::write_all().iter_mut() { 296 | space.step(delta); 297 | } 298 | } 299 | } 300 | 301 | fn sync() {} 302 | 303 | fn flush_queries() {} 304 | 305 | fn free(index: Index) { 306 | let rem = || -> Result<(), IndexError> { 307 | match index { 308 | Index::Area(index) => area::free(index.remove()?), 309 | Index::Body(index) => body::free(index.remove()?), 310 | Index::Joint(index) => joint::free(index.remove()?), 311 | Index::Shape(index) => shape::free(index.remove()?), 312 | Index::Space(index) => space::free(index.remove()?), 313 | } 314 | Ok(()) 315 | }; 316 | if let Err(e) = rem() { 317 | godot_error!("Failed to remove index: {:?}", e); 318 | } 319 | } 320 | 321 | fn get_process_info(info: i32) -> i32 { 322 | const INFO_ACTIVE_OBJECTS: i32 = 0; 323 | const INFO_COLLISION_PAIRS: i32 = 1; 324 | const INFO_ISLAND_COUNT: i32 = 2; 325 | match info { 326 | INFO_ACTIVE_OBJECTS => { 327 | let mut total = 0; 328 | for (_, space) in SpaceIndex::read_all().iter() { 329 | total += space 330 | .bodies() 331 | .iter() 332 | .filter(|(_, b)| !b.is_sleeping()) 333 | .count(); 334 | } 335 | total.try_into().unwrap_or(i32::MAX) 336 | } 337 | INFO_COLLISION_PAIRS => { 338 | let mut total = 0; 339 | for (_, space) in SpaceIndex::read_all().iter() { 340 | total += space.collision_pair_count(); 341 | } 342 | total.try_into().unwrap_or(i32::MAX) 343 | } 344 | INFO_ISLAND_COUNT => { 345 | // TODO figure out where the amount of active islands can be extracted. 346 | // For now, just return 0 so Godot at least shuts up. 347 | 0 348 | } 349 | _ => { 350 | godot_error!("Unknown info property {}", info); 351 | -1 352 | } 353 | } 354 | } 355 | 356 | fn set_active(active: bool) { 357 | *ACTIVE.lock().expect("Failed to modify ACTIVE") = active; 358 | } 359 | 360 | fn get_index(rid: Rid) -> Result { 361 | unsafe { 362 | debug_assert!(PHYSICS_SERVER.is_some()); 363 | debug_assert!(GET_INDEX.is_some()); 364 | let index = GET_INDEX.unwrap_unchecked()(PHYSICS_SERVER.unwrap_unchecked(), *rid.sys()); 365 | Index::from_raw(index) 366 | } 367 | } 368 | 369 | fn get_rid(index: Index) -> Rid { 370 | unsafe { 371 | debug_assert!(PHYSICS_SERVER.is_some()); 372 | debug_assert!(GET_RID.is_some()); 373 | let rid = GET_RID.unwrap_unchecked()(PHYSICS_SERVER.unwrap_unchecked(), index.raw()); 374 | Rid::from_sys(rid) 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /module/server.cpp: -------------------------------------------------------------------------------- 1 | #include "core/error_macros.h" 2 | #include "server.h" 3 | #include "api.gen.h" 4 | #include "body_state.h" 5 | #include "core/project_settings.h" 6 | #include "core/os/os.h" 7 | #include "core/io/resource_loader.h" 8 | // TODO figure out how to include the gdnative header properly 9 | //#include "modules/gdnative/gdnative.h" 10 | #include "gdnative.h" 11 | 12 | 13 | PluggablePhysicsServer::PluggablePhysicsServer() { 14 | memset(&this->fn_table, 0, sizeof(this->fn_table)); 15 | this->body_state_singleton = memnew(PluggablePhysicsDirectBodyState(this)); 16 | this->space_state_singleton = memnew(PluggablePhysicsDirectSpaceState(this)); 17 | this->body_state_singleton->space_state_singleton = this->space_state_singleton; 18 | } 19 | 20 | PluggablePhysicsServer::~PluggablePhysicsServer() { 21 | this->library->terminate(); 22 | memdelete(this->body_state_singleton); 23 | memdelete(this->space_state_singleton); 24 | } 25 | 26 | void PluggablePhysicsServer::_bind_methods() { 27 | ClassDB::bind_method(D_METHOD("step", "delta"), &PluggablePhysicsServer::step); 28 | ClassDB::bind_method(D_METHOD("get_rid", "index"), &PluggablePhysicsServer::get_rid); 29 | ClassDB::bind_method(D_METHOD("get_index", "rid"), &PluggablePhysicsServer::get_index); 30 | } 31 | 32 | void PluggablePhysicsServer::area_set_monitor_callback(RID area, Object *receiver, const StringName &method) { 33 | index_t id = this->get_index(area); 34 | ERR_FAIL_COND_MSG(id == 0, "Invalid RID"); 35 | AreaCallback callback(receiver, method); 36 | this->area_body_monitor_callbacks.set(id, callback); 37 | } 38 | 39 | void PluggablePhysicsServer::area_set_area_monitor_callback(RID area, Object *receiver, const StringName &method) { 40 | index_t id = this->get_index(area); 41 | ERR_FAIL_COND_MSG(id == 0, "Invalid RID"); 42 | AreaCallback callback(receiver, method); 43 | this->area_area_monitor_callbacks.set(id, callback); 44 | } 45 | 46 | void PluggablePhysicsServer::body_get_collision_exceptions(RID body, List *list) { 47 | ERR_FAIL_MSG("TODO"); 48 | } 49 | 50 | void PluggablePhysicsServer::init() { 51 | Variant lib_path_variant = ProjectSettings::get_singleton()->get_setting("physics/3d/custom_library_path"); 52 | String lib_path = String(lib_path_variant); 53 | 54 | if (lib_path != "") { 55 | Error err; 56 | RES lib = ResourceLoader::load(lib_path, "", false, &err); 57 | ERR_FAIL_COND_MSG(err, "Failed to load physics server library"); 58 | 59 | void *handle; 60 | String init_symbol = "gdphysics_init"; 61 | this->library.instance(); 62 | this->library->set_library(lib); 63 | this->library->initialize(); 64 | err = this->library->get_symbol(init_symbol, handle); 65 | ERR_FAIL_COND_MSG(err, "Failed to get init handle"); 66 | 67 | // SAFETY: the callee must have the exact same signature 68 | void (*init_func)(const struct physics_server *ps, struct fn_table *) = reinterpret_cast(handle); 69 | // SAFETY: it's just a pointer 70 | const struct physics_server *ps = reinterpret_cast(this); 71 | // SAFETY: the two functions are ABI compatible. 72 | this->fn_table.server_get_index = _get_index; 73 | // SAFETY: Ditto 74 | this->fn_table.server_get_rid = _get_rid; 75 | init_func(ps, &this->fn_table); 76 | } 77 | } 78 | 79 | Variant PluggablePhysicsServer::call(const StringName &method, const Variant **args, int argcount, Variant::CallError &error) { 80 | if (this->fn_table.call != nullptr) { 81 | // TODO should we call the Godot or the Rapier method first? 82 | // I've decided to go with Rapier method first since it uses a match statement internally, 83 | // which is very likely to be much faster than a hashmap lookup (which is what Godot will do). 84 | 85 | // TODO find a way that avoids potential redundant malloc calls. 86 | // Because malloc may return NULL the compiler isn't always able to optimize it out if it 87 | // would have side effects (e.g. error message). 88 | String m(method); 89 | struct physics_call_result result = (*this->fn_table.call)(m.ptr(), args, (size_t)argcount); 90 | error.error = (Variant::CallError::Error)result.status; 91 | if (error.error != Variant::CallError::Error::CALL_ERROR_INVALID_METHOD) { 92 | error.argument = result.argument; 93 | error.expected = (Variant::Type)result.expected_type; 94 | return result.value; 95 | } 96 | } 97 | 98 | return PhysicsServer::call(method, args, argcount, error); 99 | } 100 | 101 | void PluggablePhysicsServer::step(float delta) { 102 | EXEC_FFI_FN(this, step, delta); 103 | 104 | const index_t *id = nullptr; 105 | Vector invalid_callbacks; 106 | 107 | // Execute force integration callbacks 108 | ERR_FAIL_COND_MSG(this->fn_table.body_get_direct_state == nullptr, "Not implemented"); 109 | while ((id = this->body_force_integration_callbacks.next(id)) != nullptr) { 110 | (*this->fn_table.body_get_direct_state)(*id, &this->body_state_singleton->state); 111 | 112 | this->body_state_singleton->delta = delta; 113 | this->body_state_singleton->body = *id; 114 | 115 | Callback *callback = this->body_force_integration_callbacks.getptr(*id); 116 | Object *object = ObjectDB::get_instance(callback->object_id); 117 | 118 | if (object == nullptr) { 119 | invalid_callbacks.push_back(*id); 120 | } else { 121 | Variant variant_body_direct = this->body_state_singleton; 122 | const Variant *argv[2] = { &variant_body_direct, &callback->userdata }; 123 | int argc = (callback->userdata.get_type() == Variant::NIL) ? 1 : 2; 124 | 125 | Variant::CallError error = {}; 126 | object->call(callback->method, argv, argc, error); 127 | } 128 | } 129 | 130 | // Remove any invalid callbacks 131 | for (int i = 0; i < invalid_callbacks.size(); i++) { 132 | this->body_force_integration_callbacks.erase(invalid_callbacks[i]); 133 | } 134 | invalid_callbacks.resize(0); 135 | 136 | // Execute area <-> body monitor callbacks 137 | ERR_FAIL_COND_MSG(this->fn_table.area_get_body_event == nullptr, "Not implemented"); 138 | while ((id = this->area_body_monitor_callbacks.next(id)) != nullptr) { 139 | struct physics_area_monitor_event event; 140 | 141 | AreaCallback *callback = this->area_body_monitor_callbacks.getptr(*id); 142 | Object *object = ObjectDB::get_instance(callback->object_id); 143 | 144 | if (object == nullptr) { 145 | invalid_callbacks.push_back(*id); 146 | } else { 147 | while ((*this->fn_table.area_get_body_event)(*id, &event)) { 148 | Variant event_type = event.added ? 0 : 1; 149 | Variant rid = this->reverse_rids.get(event.id); 150 | Variant object_id = event.object_id; 151 | // Bullets skip this, so shall we I suppose (less work for me) 152 | Variant body_shape = 0; 153 | Variant area_shape = 0; 154 | 155 | const Variant *argv[5] = { &event_type, &rid, &object_id, &body_shape, &area_shape }; 156 | 157 | Variant::CallError error = {}; 158 | object->call(callback->method, argv, 5, error); 159 | } 160 | } 161 | } 162 | 163 | // Remove any invalid callbacks 164 | for (int i = 0; i < invalid_callbacks.size(); i++) { 165 | this->area_body_monitor_callbacks.erase(invalid_callbacks[i]); 166 | } 167 | invalid_callbacks.resize(0); 168 | 169 | // Execute area <-> area monitor callbacks 170 | ERR_FAIL_COND_MSG(this->fn_table.area_get_area_event == nullptr, "Not implemented"); 171 | while ((id = this->area_area_monitor_callbacks.next(id)) != nullptr) { 172 | struct physics_area_monitor_event event; 173 | 174 | AreaCallback *callback = this->area_area_monitor_callbacks.getptr(*id); 175 | Object *object = ObjectDB::get_instance(callback->object_id); 176 | 177 | if (object == nullptr) { 178 | invalid_callbacks.push_back(*id); 179 | } else { 180 | while ((*this->fn_table.area_get_area_event)(*id, &event)) { 181 | Variant event_type = event.added ? 0 : 1; 182 | Variant rid = this->reverse_rids.get(event.id); 183 | Variant object_id = event.object_id; 184 | // Bullets skips this, so shall we I suppose (less work for me) 185 | Variant body_shape = 0; 186 | Variant area_shape = 0; 187 | 188 | const Variant *argv[5] = { &event_type, &rid, &object_id, &body_shape, &area_shape }; 189 | 190 | Variant::CallError error = {}; 191 | object->call(callback->method, argv, 5, error); 192 | } 193 | } 194 | } 195 | 196 | // Remove any invalid callbacks 197 | for (int i = 0; i < invalid_callbacks.size(); i++) { 198 | this->area_body_monitor_callbacks.erase(invalid_callbacks[i]); 199 | } 200 | } 201 | 202 | PhysicsDirectBodyState *PluggablePhysicsServer::body_get_direct_state(RID rid) { 203 | ERR_FAIL_COND_V_MSG(this->fn_table.body_get_direct_state == nullptr, nullptr, "Not implemented"); 204 | index_t id = this->get_index(rid); 205 | ERR_FAIL_COND_V_MSG(id == 0, nullptr, "Invalid RID"); 206 | (*this->fn_table.body_get_direct_state)(id, &this->body_state_singleton->state); 207 | return this->body_state_singleton; 208 | } 209 | 210 | void PluggablePhysicsServer::body_set_force_integration_callback(RID body, Object *receiver, const StringName &method, const Variant &userdata) { 211 | index_t id = this->get_index(body); 212 | Callback callback(receiver, method, userdata); 213 | this->body_force_integration_callbacks.set(id, callback); 214 | } 215 | 216 | void PluggablePhysicsServer::free(RID rid) { 217 | ERR_FAIL_COND_MSG(this->fn_table.free == nullptr, "Not implemented"); 218 | index_t id = this->get_index(rid); 219 | ERR_FAIL_COND_MSG(id == 0, "Invalid RID"); 220 | this->rids.free(rid); 221 | this->reverse_rids.erase(id); 222 | this->body_force_integration_callbacks.erase(id); 223 | this->area_body_monitor_callbacks.erase(id); 224 | this->area_area_monitor_callbacks.erase(id); 225 | (*this->fn_table.free)(id); 226 | } 227 | 228 | PhysicsDirectSpaceState *PluggablePhysicsServer::space_get_direct_state(RID space) { 229 | index_t id = this->get_index(space); 230 | ERR_FAIL_COND_V_MSG(id == 0, nullptr, "Space doesn't exist"); 231 | this->space_state_singleton->space = id; 232 | return this->space_state_singleton; 233 | } 234 | 235 | Vector PluggablePhysicsServer::space_get_contacts(RID space) const { 236 | ERR_FAIL_COND_V_MSG(this->fn_table.space_get_contact_count == nullptr, Vector(), "Not implemented"); 237 | ERR_FAIL_COND_V_MSG(this->fn_table.space_get_contact == nullptr, Vector(), "Not implemented"); 238 | 239 | index_t id = this->get_index(space); 240 | ERR_FAIL_COND_V_MSG(id == 0, Vector(), "Invalid RID"); 241 | 242 | size_t count = (*this->fn_table.space_get_contact_count)(id); 243 | Vector contacts; 244 | contacts.resize(count); 245 | Vector3 *contacts_ptr = contacts.ptrw(); 246 | for (size_t i = 0; i < count; i++) { 247 | (*this->fn_table.space_get_contact)(id, i, &contacts_ptr[i]); 248 | } 249 | return contacts; 250 | } 251 | 252 | void PluggablePhysicsServer::soft_body_update_visual_server(RID soft_body, SoftBodyVisualServerHandler *handler) { 253 | ERR_FAIL_MSG("TODO"); 254 | } 255 | 256 | void PluggablePhysicsServer::soft_body_get_collision_exceptions(RID soft_body, List *list) { 257 | ERR_FAIL_MSG("TODO"); 258 | } 259 | 260 | void PluggablePhysicsServer::soft_body_set_mesh(RID soft_body, const REF &mesh) { 261 | ERR_FAIL_MSG("TODO"); 262 | } 263 | -------------------------------------------------------------------------------- /rapier3d/src/server/space.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::space::{BodyOrAreaIndex, Space}; 3 | use gdnative::core_types::Vector3; 4 | 5 | pub fn init(ffi: &mut ffi::FFI) { 6 | ffi!(ffi, space_create, create); 7 | ffi!(ffi, space_get_contact, get_contact); 8 | ffi!(ffi, space_get_contact_count, get_contact_count); 9 | ffi!(ffi, space_intersect_ray, intersect_ray); 10 | ffi!(ffi, space_intersect_shape, intersect_shape); 11 | ffi!(ffi, space_is_active, is_active); 12 | ffi!(ffi, space_set_active, set_active); 13 | ffi!(ffi, space_set_debug_contacts, set_debug_contacts); 14 | } 15 | 16 | pub fn free(_space: Space) {} 17 | 18 | fn create() -> Option { 19 | let index = SpaceIndex::add(Space::new()); 20 | index.map_mut(|space| space.set_index(index)).unwrap(); 21 | Some(Index::Space(index)) 22 | } 23 | 24 | fn intersect_ray( 25 | space: Index, 26 | info: &ffi::PhysicsRayInfo, 27 | result: &mut ffi::PhysicsRayResult, 28 | ) -> bool { 29 | space 30 | .map_space_mut(|space, _| { 31 | let exclude_raw = info.exclude_raw(); 32 | let mut exclude_bodies = if info.collide_with_bodies() { 33 | Some(Vec::with_capacity(exclude_raw.len())) 34 | } else { 35 | None 36 | }; 37 | let mut exclude_areas = if info.collide_with_areas() { 38 | Some(Vec::with_capacity(exclude_raw.len())) 39 | } else { 40 | None 41 | }; 42 | for &e in exclude_raw { 43 | if let Ok(i) = Index::from_raw(e) { 44 | match i { 45 | Index::Body(i) => { 46 | exclude_bodies.as_mut().map(|v| v.push(i)); 47 | } 48 | Index::Area(i) => { 49 | exclude_areas.as_mut().map(|v| v.push(i)); 50 | } 51 | _ => godot_error!("One of the indices does not point to a body"), 52 | } 53 | } else { 54 | godot_error!("One of the indices is invalid"); 55 | } 56 | } 57 | space 58 | .cast_ray( 59 | info.from(), 60 | info.to(), 61 | info.collision_mask(), 62 | exclude_bodies.as_deref(), 63 | exclude_areas.as_deref(), 64 | info.pick_ray(), 65 | ) 66 | .map(|res| { 67 | let (object_id, index) = match res.index { 68 | BodyOrAreaIndex::Body(body) => ( 69 | body.map(|body| body.object_id()).expect("Invalid body"), 70 | Index::Body(body), 71 | ), 72 | BodyOrAreaIndex::Area(area) => ( 73 | area.map(|area| area.object_id()).expect("Invalid area"), 74 | Index::Area(area), 75 | ), 76 | }; 77 | result.set_position(res.position); 78 | result.set_normal(res.normal); 79 | result.set_object_id(object_id); 80 | result.set_index(index); 81 | result.set_shape(res.shape); 82 | }) 83 | .is_some() 84 | }) 85 | .unwrap_or_else(|_| { 86 | godot_error!("Invalid space index"); 87 | false 88 | }) 89 | } 90 | 91 | fn intersect_shape( 92 | space: Index, 93 | info: &ffi::PhysicsShapeInfo, 94 | results: *mut ffi::PhysicsShapeResult, 95 | max_results: usize, 96 | ) -> usize { 97 | // SAFETY: We'll have to trust the Godot side that the results array is large enough. 98 | let results = unsafe { core::slice::from_raw_parts_mut(results, max_results) }; 99 | map_or_err!(space, map_space_mut, |space, _| { 100 | let exclude_raw = info.exclude_raw(); 101 | let mut exclude_bodies = if info.collide_with_bodies() { 102 | Some(Vec::with_capacity(exclude_raw.len())) 103 | } else { 104 | None 105 | }; 106 | let mut exclude_areas = if info.collide_with_areas() { 107 | Some(Vec::with_capacity(exclude_raw.len())) 108 | } else { 109 | None 110 | }; 111 | for &e in exclude_raw { 112 | if let Ok(i) = Index::from_raw(e) { 113 | match i { 114 | Index::Body(i) => { 115 | exclude_bodies.as_mut().map(|v| v.push(i)); 116 | } 117 | Index::Area(i) => { 118 | exclude_areas.as_mut().map(|v| v.push(i)); 119 | } 120 | _ => godot_error!("One of the indices does not point to a body"), 121 | } 122 | } else { 123 | godot_error!("One of the indices is invalid"); 124 | } 125 | } 126 | 127 | info.shape() 128 | .map_err(|e| godot_error!("Invalid shape index: {:?}", e)) 129 | .map(|shape| { 130 | shape 131 | .map(|shape| { 132 | let mut total = 0; 133 | for r in space.intersect_shape( 134 | shape.shape().as_ref(), 135 | info.transform(), 136 | info.collision_mask(), 137 | exclude_bodies.as_deref(), 138 | exclude_areas.as_deref(), 139 | max_results, 140 | ) { 141 | let (object_id, index) = match r.index { 142 | BodyOrAreaIndex::Body(body) => ( 143 | body.map(|body| body.object_id()).expect("Invalid body"), 144 | Index::Body(body), 145 | ), 146 | BodyOrAreaIndex::Area(area) => ( 147 | area.map(|area| area.object_id()).expect("Invalid area"), 148 | Index::Area(area), 149 | ), 150 | }; 151 | results[total].set_index(index); 152 | results[total].set_object_id(object_id); 153 | results[total].set_shape(r.shape); 154 | total += 1; 155 | } 156 | total 157 | }) 158 | .map_err(|_| godot_error!("Invalid shape")) 159 | .unwrap_or(0) 160 | }) 161 | .unwrap_or(0) 162 | }) 163 | .unwrap_or(0) 164 | } 165 | 166 | fn set_active(space: Index, active: bool) { 167 | map_or_err!(space, map_space_mut, |space, _| space.enabled = active); 168 | } 169 | 170 | fn is_active(space: Index) -> bool { 171 | let result = space.map_space(|space, _| space.enabled); 172 | if let Ok(active) = result { 173 | active 174 | } else { 175 | godot_error!("RID does not point to a space"); 176 | false 177 | } 178 | } 179 | 180 | fn set_debug_contacts(space: Index, contacts: i32) { 181 | map_or_err!(space, map_space_mut, |space, _| space 182 | .set_debug_contact_count(contacts as usize)); 183 | } 184 | 185 | fn get_contact_count(space: Index) -> i32 { 186 | map_or_err!(space, map_space_mut, |space, _| space 187 | .debug_contacts() 188 | .len()) 189 | .unwrap_or(0) as i32 190 | } 191 | 192 | fn get_contact(space: Index, contact: i32, out: &mut Vector3) { 193 | *out = map_or_err!(space, map_space_mut, |space, _| { 194 | if let Some(contact) = space.debug_contacts().get(contact as usize) { 195 | *contact 196 | } else { 197 | godot_error!("Invalid contact index"); 198 | Vector3::zero() 199 | } 200 | }) 201 | .unwrap_or(Vector3::zero()); 202 | } 203 | 204 | /// Extra methods exposed through the "call" function. 205 | mod call { 206 | use super::super::call; 207 | use super::*; 208 | use crate::util::*; 209 | use ffi::{PhysicsCallError, VariantType}; 210 | use gdnative::prelude::*; 211 | 212 | /// Return *all* colliders that intersect with a ray. A `VariantArray` will be returned with a 213 | /// number of `Dictionary` entries. Each entry has the following fields: 214 | /// 215 | /// * `position`: The location where the ray hit. 216 | /// 217 | /// * `normal`: The normal at the intersection point. 218 | /// 219 | /// * `rid`: The RID of the parent object. 220 | /// 221 | /// * `object_id`: The `ObjectID` of the parent, which is a Body or an `Area´. 222 | /// 223 | /// * `shape`: The index of the shape in the body/area. 224 | /// 225 | /// * `time_of_impact`: The time of impact, where 226 | /// `position = from + (to - from) * time_of_impact` 227 | /// 228 | /// There is no order guarantee. 229 | /// 230 | /// `solid` indicates whether it should detect colliders that encapsulate the origin of the 231 | /// ray. 232 | /// 233 | /// `exclude` is a `VariantArray` of `Rid`s pointing to bodies that should be ignored. 234 | pub fn intersections_with_ray(args: &[&Variant]) -> call::Result { 235 | call_check_arg_count!(args in 2..9)?; 236 | let space = call_get_arg!(args[0] => Rid)?; 237 | let from = call_get_arg!(args[1] => Vector3)?; 238 | let to = call_get_arg!(args[2] => Vector3)?; 239 | let solid = call_get_arg!(args[3] => bool || false)?; 240 | let mask = call_get_arg!(args[4] => u32 || 0xffff_ffff)?; 241 | let max_results = call_get_arg!(args[5] => i32 || 32)?; 242 | let exclude_bodies = !call_get_arg!(args[6] => bool || false)?; 243 | let exclude_areas = !call_get_arg!(args[7] => bool || false)?; 244 | let exclude = call_get_arg!(args[8] => VariantArray || VariantArray::new().into_shared())?; 245 | if let Ok(space) = super::get_index(space) { 246 | map_or_err!(space, map_space_mut, |space, _| { 247 | let array = VariantArray::new(); 248 | let mut exclude_bodies = 249 | exclude_bodies.then(|| Vec::with_capacity(exclude.len() as usize)); 250 | let mut exclude_areas = 251 | exclude_areas.then(|| Vec::with_capacity(exclude.len() as usize)); 252 | for (i, e) in exclude.iter().enumerate() { 253 | if let Some(e) = e.try_to_rid() { 254 | if let Ok(e) = super::get_index(e) { 255 | if let Some(e) = e.as_body() { 256 | exclude_bodies.as_mut().map(|v| v.push(e)); 257 | } else if let Some(e) = e.as_area() { 258 | exclude_areas.as_mut().map(|v| v.push(e)); 259 | } else { 260 | godot_error!("RID {:?} is not a body", e); 261 | } 262 | } else { 263 | godot_error!("RID {:?} is invalid", e); 264 | } 265 | } else { 266 | godot_error!("Element {} is not a RID", i); 267 | } 268 | } 269 | // Creating the Godot strings before hand should be slightly more efficient 270 | // (less allocations, more ref counting). Should probably be done as a global 271 | // lazy static or similar but w/e. 272 | let position_key = "position".to_variant(); 273 | let normal_key = "normal".to_variant(); 274 | let rid_key = "rid".to_variant(); 275 | let object_id_key = "object_id".to_variant(); 276 | let shape_key = "shape".to_variant(); 277 | let toi_key = "time_of_impact".to_variant(); 278 | 279 | let bodies = BodyIndex::read_all(); 280 | let areas = AreaIndex::read_all(); 281 | space.intersections_with_ray( 282 | from, 283 | to - from, 284 | solid, 285 | mask, 286 | exclude_bodies.as_ref().map(|v| &v[..]), 287 | exclude_areas.as_ref().map(|v| &v[..]), 288 | |index, shape_index, ri| { 289 | let dict = Dictionary::new(); 290 | let pos = (to - from).normalize() * ri.toi + from; 291 | dict.insert(position_key.clone(), pos); 292 | dict.insert(normal_key.clone(), vec_na_to_gd(ri.normal)); 293 | let (object_id, index) = match index { 294 | BodyOrAreaIndex::Body(body) => ( 295 | bodies 296 | .get(body.into()) 297 | .map(|b| b.object_id()) 298 | .expect("Invalid body"), 299 | Index::Body(body), 300 | ), 301 | BodyOrAreaIndex::Area(area) => ( 302 | areas 303 | .get(area.into()) 304 | .map(|a| a.object_id()) 305 | .expect("Invalid area"), 306 | Index::Area(area), 307 | ), 308 | }; 309 | let rid = super::get_rid(index.into()); 310 | dict.insert( 311 | object_id_key.clone(), 312 | object_id.map(ObjectID::get).unwrap_or(0), 313 | ); 314 | dict.insert(shape_key.clone(), shape_index); 315 | dict.insert(rid_key.clone(), rid); 316 | dict.insert(toi_key.clone(), ri.toi); 317 | array.push(dict); 318 | array.len() < max_results 319 | }, 320 | ); 321 | Ok(array.owned_to_variant()) 322 | }) 323 | .unwrap_or(Ok(Variant::new())) 324 | } else { 325 | godot_error!("Invalid index"); 326 | Ok(Variant::new()) 327 | } 328 | } 329 | } 330 | 331 | pub(super) use call::*; 332 | -------------------------------------------------------------------------------- /rapier3d/src/server/area.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::area::*; 3 | use gdnative::core_types::*; 4 | use gdnative::godot_error; 5 | 6 | #[derive(Debug)] 7 | enum ParamError { 8 | InvalidParam, 9 | InvalidData, 10 | } 11 | 12 | #[derive(Debug)] 13 | enum Param { 14 | Gravity(f32), 15 | GravityVector(Vector3), 16 | GravityIsPoint(bool), 17 | GravityDistanceScale(f32), 18 | LinearDamp(f32), 19 | AngularDamp(f32), 20 | Priority(i32), 21 | } 22 | 23 | impl Param { 24 | fn new(n: i32, data: &Variant) -> Result { 25 | match n { 26 | 0 => data.try_to_f64().map(|g| Self::Gravity(g as f32)), 27 | 1 => data.try_to_vector3().map(Self::GravityVector), 28 | 2 => data.try_to_bool().map(Self::GravityIsPoint), 29 | 3 => data 30 | .try_to_f64() 31 | .map(|s| Self::GravityDistanceScale(s as f32)), 32 | 4 => None, // Superseded by GravityDistanceScale, 33 | 5 => data.try_to_f64().map(|d| Self::LinearDamp(d as f32)), 34 | 6 => data.try_to_f64().map(|d| Self::AngularDamp(d as f32)), 35 | 7 => data 36 | .try_to_i64() 37 | .map(|p| Self::Priority(p as i32)) 38 | .or_else(|| data.try_to_f64().map(|p| Self::Priority(p as i32))), 39 | _ => return Err(ParamError::InvalidParam), 40 | } 41 | .ok_or(ParamError::InvalidData) 42 | } 43 | } 44 | 45 | pub fn init(ffi: &mut ffi::FFI) { 46 | ffi!(ffi, area_add_shape, add_shape); 47 | ffi!( 48 | ffi, 49 | area_attach_object_instance_id, 50 | attach_object_instance_id 51 | ); 52 | ffi!(ffi, area_clear_shapes, clear_shapes); 53 | ffi!(ffi, area_create, create); 54 | ffi!(ffi, area_get_area_event, get_area_event); 55 | ffi!(ffi, area_get_body_event, get_body_event); 56 | ffi!(ffi, area_get_object_instance_id, get_object_instance_id); 57 | ffi!(ffi, area_get_shape, get_shape); 58 | ffi!(ffi, area_get_shape_transform, get_shape_transform); 59 | ffi!(ffi, area_get_space, get_space); 60 | ffi!(ffi, area_get_space_override_mode, get_space_override_mode); 61 | ffi!(ffi, area_get_transform, get_transform); 62 | ffi!(ffi, area_is_ray_pickable, is_ray_pickable); 63 | ffi!(ffi, area_remove_shape, remove_shape); 64 | ffi!(ffi, area_set_collision_layer, set_collision_layer); 65 | ffi!(ffi, area_set_collision_mask, set_collision_mask); 66 | ffi!(ffi, area_set_monitorable, set_monitorable); 67 | ffi!(ffi, area_set_param, set_param); 68 | ffi!(ffi, area_set_ray_pickable, set_ray_pickable); 69 | ffi!(ffi, area_set_shape, set_shape); 70 | ffi!(ffi, area_set_shape_disabled, set_shape_disabled); 71 | ffi!(ffi, area_set_shape_transform, set_shape_transform); 72 | ffi!(ffi, area_set_space, set_space); 73 | ffi!(ffi, area_set_space_override_mode, set_space_override_mode); 74 | ffi!(ffi, area_set_transform, set_transform); 75 | } 76 | 77 | pub fn free(_area: Area) { 78 | godot_error!("TODO"); 79 | } 80 | 81 | fn create() -> Option { 82 | let index = AreaIndex::add(Area::new()); 83 | index.map_mut(|area| area.set_index(index)).unwrap(); 84 | Some(Index::Area(index)) 85 | } 86 | 87 | fn add_shape(area: Index, shape: Index, transform: &Transform, disable: bool) { 88 | if let Some(shape) = shape.as_shape() { 89 | map_or_err!(area, map_area_mut, |area, _| area 90 | .add_shape(shape, transform, !disable)); 91 | } else { 92 | godot_error!("Index does not point to a shape"); 93 | } 94 | } 95 | 96 | fn remove_shape(area: Index, shape: i32) { 97 | map_or_err!(area, map_area_mut, |area, _| { 98 | if area.remove_shape(shape as u32).is_none() { 99 | godot_error!("Invalid shape index"); 100 | } 101 | }); 102 | } 103 | 104 | fn get_shape(area: Index, shape: i32) -> Option { 105 | map_or_err!(area, map_area, |area, _| { 106 | if let Some(shape) = area.get_shape_index(shape as u32) { 107 | Some(Index::Shape(shape)) 108 | } else { 109 | godot_error!("Invalid shape index"); 110 | None 111 | } 112 | }) 113 | .unwrap_or(None) 114 | } 115 | 116 | fn set_shape(area: Index, shape: i32, index: Index) { 117 | if let Index::Shape(index) = index { 118 | map_or_err!(area, map_area_mut, |area, _| { 119 | if !area.set_shape_index(shape as u32, index) { 120 | godot_error!("Invalid shape index"); 121 | } 122 | }); 123 | } else { 124 | godot_error!("Index does not point to a shape"); 125 | } 126 | } 127 | 128 | fn clear_shapes(area: Index) { 129 | map_or_err!(area, map_area_mut, |area, _| area.remove_all_shapes()); 130 | } 131 | 132 | // FIXME handle to_sys() stuff in the generated ffi wrapper 133 | fn get_shape_transform(area: Index, shape: i32) -> gdnative::sys::godot_transform { 134 | let transform = map_or_err!(area, map_area, |area, _| { 135 | if let Some(trf) = area.get_shape_transform(shape as u32) { 136 | trf 137 | } else { 138 | godot_error!("Invalid shape index"); 139 | Transform { 140 | basis: Basis::identity(), 141 | origin: Vector3::zero(), 142 | } 143 | } 144 | }); 145 | let transform = transform.unwrap_or(Transform { 146 | basis: Basis::identity(), 147 | origin: Vector3::zero(), 148 | }); 149 | // SAFETY: transform is guaranteed valid 150 | unsafe { *transform.sys() } 151 | } 152 | 153 | fn set_shape_transform(area: Index, shape: i32, transform: &Transform) { 154 | map_or_err!(area, map_area_mut, |area, _| { 155 | if !area.set_shape_transform(shape as u32, transform) { 156 | godot_error!("Invalid shape index"); 157 | } 158 | }); 159 | } 160 | 161 | fn set_shape_disabled(area: Index, shape: i32, disabled: bool) { 162 | map_or_err!(area, map_area_mut, |area, _| { 163 | if !area.set_shape_enabled(shape as u32, !disabled) { 164 | godot_error!("Invalid shape index"); 165 | } 166 | }); 167 | } 168 | 169 | fn get_space(area: Index) -> Option { 170 | map_or_err!(area, map_area, |area, _| { area.space().map(Index::Space) }).unwrap_or(None) 171 | } 172 | 173 | fn set_space(area: Index, space: Option) { 174 | let space = if let Some(Some(space)) = space.map(|i| i.as_space()) { 175 | Some(space) 176 | } else if space.is_some() { 177 | godot_error!("Index does not point to a space"); 178 | return; 179 | } else { 180 | None 181 | }; 182 | map_or_err!(area, map_area_mut, |area, _| { 183 | area.set_space(space); 184 | }); 185 | } 186 | 187 | fn set_param(area: Index, param: i32, data: &Variant) { 188 | let param = match Param::new(param, data) { 189 | Ok(p) => p, 190 | Err(e) => { 191 | godot_error!("Failed to set area param: {:?}", e); 192 | return; 193 | } 194 | }; 195 | 196 | let result = area.map_area_mut(|area, _| match param { 197 | Param::Priority(p) => area.set_priority(p), 198 | Param::LinearDamp(d) => area.set_linear_damp(d), 199 | Param::AngularDamp(d) => area.set_angular_damp(d), 200 | Param::GravityIsPoint(is) => area.set_point_gravity(is), 201 | Param::Gravity(g) => area.set_gravity_force(g), 202 | Param::GravityVector(g) => area.set_gravity_direction(g), 203 | Param::GravityDistanceScale(s) => area.set_gravity_distance_scale(s), 204 | }); 205 | 206 | match result { 207 | Err(IndexError::WrongType) => { 208 | // Each Space has an imaginary Area 209 | let fix_g = |g| { 210 | if g == Vector3::zero() { 211 | Vector3::new(0.0, -1.0, 0.0) 212 | } else { 213 | g 214 | } 215 | }; 216 | let result = area.map_space_mut(|space, _| match param { 217 | Param::LinearDamp(d) => space.set_default_linear_damp(d), 218 | Param::AngularDamp(d) => space.set_default_angular_damp(d), 219 | Param::Gravity(g) => space.set_gravity(fix_g(space.gravity()).normalize() * g), 220 | Param::GravityVector(g) => space.set_gravity(g * fix_g(space.gravity()).length()), 221 | _ => godot_error!("Parameter {:?} is not supported for spaces", param), 222 | }); 223 | if let Err(e) = result { 224 | match e { 225 | IndexError::WrongType => { 226 | godot_error!("Index does not point to an area or space") 227 | } 228 | IndexError::NoElement => godot_error!("No space at index"), 229 | } 230 | } 231 | } 232 | Err(IndexError::NoElement) => godot_error!("No area at index {:?}", area), 233 | Ok(()) => (), 234 | } 235 | } 236 | 237 | fn attach_object_instance_id(area: Index, id: i32) { 238 | map_or_err!(area, map_area_mut, |area, _| area 239 | .set_object_id(ObjectID::new(id as u32))); 240 | } 241 | 242 | fn get_object_instance_id(area: Index) -> i32 { 243 | map_or_err!(area, map_area_mut, |area, _| area 244 | .object_id() 245 | .map(|id| id.get())) 246 | .flatten() 247 | .unwrap_or(0) as i32 248 | } 249 | 250 | fn set_monitorable(area: Index, monitorable: bool) { 251 | map_or_err!(area, map_area_mut, |area, _| area 252 | .set_monitorable(monitorable)); 253 | } 254 | 255 | fn get_space_override_mode(area: Index) -> i32 { 256 | map_or_err!(area, map_area, |area, _| match area.space_override_mode() { 257 | SpaceOverrideMode::Disabled => 0, 258 | SpaceOverrideMode::Combine => 1, 259 | SpaceOverrideMode::CombineReplace => 2, 260 | SpaceOverrideMode::Replace => 3, 261 | SpaceOverrideMode::ReplaceCombine => 4, 262 | }) 263 | .unwrap_or(0) 264 | } 265 | 266 | fn set_space_override_mode(area: Index, mode: i32) { 267 | let mode = match mode { 268 | 0 => SpaceOverrideMode::Disabled, 269 | 1 => SpaceOverrideMode::Combine, 270 | 2 => SpaceOverrideMode::CombineReplace, 271 | 3 => SpaceOverrideMode::Replace, 272 | 4 => SpaceOverrideMode::ReplaceCombine, 273 | _ => { 274 | godot_error!("Invalid mode"); 275 | return; 276 | } 277 | }; 278 | map_or_err!(area, map_area_mut, |area, _| area 279 | .set_space_override_mode(mode)); 280 | } 281 | 282 | // FIXME fix build script to generate proper return types 283 | fn get_transform(area: Index) -> gdnative::sys::godot_transform { 284 | let t = map_or_err!(area, map_area, |area, _| area.transform()).unwrap_or(Transform { 285 | basis: Basis::identity(), 286 | origin: Vector3::zero(), 287 | }); 288 | // SAFETY: t is guaranteed valid 289 | unsafe { *t.sys() } 290 | } 291 | 292 | fn set_transform(area: Index, transform: &Transform) { 293 | map_or_err!(area, map_area_mut, |area, _| area.set_transform(transform)); 294 | } 295 | 296 | fn is_ray_pickable(area: Index) -> bool { 297 | map_or_err!(area, map_area, |area, _| area.ray_pickable()).unwrap_or(false) 298 | } 299 | 300 | fn set_ray_pickable(area: Index, enable: bool) { 301 | map_or_err!(area, map_area_mut, |area, _| area.set_ray_pickable(enable)); 302 | } 303 | 304 | fn set_collision_mask(area: Index, mask: u32) { 305 | map_or_err!(area, map_area_mut, |area, _| area.set_mask(mask)); 306 | } 307 | 308 | fn set_collision_layer(area: Index, layer: u32) { 309 | map_or_err!(area, map_area_mut, |area, _| area.set_layer(layer)); 310 | } 311 | 312 | fn get_area_event(area: Index, event: &mut ffi::PhysicsAreaMonitorEvent) -> bool { 313 | map_or_err!(area, map_area_mut, |area, _| { 314 | if let Some((body, intersect)) = area.pop_area_event() { 315 | body.map(|body| event.set_object_id(body.object_id())) 316 | .expect("Invalid body index"); 317 | event.set_index(Index::Area(body)); 318 | event.set_added(intersect); 319 | true 320 | } else { 321 | false 322 | } 323 | }) 324 | .unwrap_or(false) 325 | } 326 | 327 | fn get_body_event(area: Index, event: &mut ffi::PhysicsAreaMonitorEvent) -> bool { 328 | map_or_err!(area, map_area_mut, |area, _| { 329 | if let Some((body, intersect)) = area.pop_body_event() { 330 | body.map(|body| event.set_object_id(body.object_id())) 331 | .expect("Invalid body index"); 332 | event.set_index(Index::Body(body)); 333 | event.set_added(intersect); 334 | true 335 | } else { 336 | false 337 | } 338 | }) 339 | .unwrap_or(false) 340 | } 341 | -------------------------------------------------------------------------------- /rapier3d/src/server/shape.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::util::*; 3 | use core::convert::TryFrom; 4 | use core::mem; 5 | use gdnative::core_types::*; 6 | use rapier3d::geometry::{Collider, ColliderBuilder, SharedShape}; 7 | use rapier3d::math::Point; 8 | use rapier3d::na::{DMatrix, Dynamic, Isometry3, Matrix, Matrix3x1, Point3}; 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | enum Type { 12 | Plane, 13 | Ray, 14 | Sphere, 15 | Box, 16 | Capsule, 17 | Cylinder, 18 | Convex, 19 | Concave, 20 | Heightmap, 21 | // Add custom types below 22 | } 23 | 24 | #[derive(Debug)] 25 | enum TypeError { 26 | InvalidType, 27 | } 28 | 29 | impl Type { 30 | fn new(shape: i32) -> Result { 31 | Ok(match shape { 32 | 0 => Type::Plane, 33 | 1 => Type::Ray, 34 | 2 => Type::Sphere, 35 | 3 => Type::Box, 36 | 4 => Type::Capsule, 37 | 5 => Type::Cylinder, 38 | 6 => Type::Convex, 39 | 7 => Type::Concave, 40 | 8 => Type::Heightmap, 41 | _ => return Err(TypeError::InvalidType), 42 | }) 43 | } 44 | } 45 | 46 | pub struct Shape { 47 | r#type: Type, 48 | shape: SharedShape, 49 | index: Option, 50 | } 51 | 52 | #[derive(Debug)] 53 | enum ShapeError { 54 | InvalidData, 55 | IncompleteTriangle, 56 | ConvexNotManifold, 57 | } 58 | 59 | impl Shape { 60 | fn new(r#type: Type) -> Self { 61 | let p = |x, y, z| Point::new(x, y, z); 62 | // We need something for triangle shapes I suppose, so have a pyramid 63 | let pyramid = ( 64 | [ 65 | p(1.0, 0.0, 1.0), 66 | p(1.0, 0.0, -1.0), 67 | p(-1.0, 0.0, -1.0), 68 | p(-1.0, 0.0, 1.0), 69 | p(0.0, 1.0, 0.0), 70 | ], 71 | [ 72 | [0, 1, 2], 73 | [2, 3, 0], 74 | [0, 1, 4], 75 | [1, 2, 4], 76 | [2, 3, 4], 77 | [3, 0, 4], 78 | ], 79 | ); 80 | let shape = match r#type { 81 | // TODO will this work as expected? 82 | Type::Plane => SharedShape::cuboid(1.0, 0.0, 1.0), 83 | Type::Ray => { 84 | // TODO ditto? 85 | SharedShape::capsule(p(0.0, 1.0, 0.0), p(0.0, 0.0, 0.0), 0.0) 86 | } 87 | Type::Sphere => SharedShape::ball(1.0), 88 | Type::Box => SharedShape::cuboid(1.0, 1.0, 1.0), 89 | Type::Capsule => SharedShape::capsule(p(0.0, 1.0, 0.0), p(0.0, -1.0, 0.0), 1.0), 90 | Type::Cylinder => SharedShape::cylinder(1.0, 1.0), 91 | Type::Convex => SharedShape::convex_mesh(Vec::from(pyramid.0), &pyramid.1) 92 | .expect("Failed to create convex mesh"), 93 | // TODO check if this is the correct equivalent 94 | Type::Concave => SharedShape::trimesh(Vec::from(pyramid.0), Vec::from(pyramid.1)), 95 | Type::Heightmap => SharedShape::heightfield( 96 | Matrix::<_, Dynamic, Dynamic, _>::zeros(2, 2), 97 | Matrix3x1::new(1.0, 1.0, 1.0), 98 | ), 99 | }; 100 | let index = None; 101 | Self { 102 | r#type, 103 | shape, 104 | index, 105 | } 106 | } 107 | 108 | fn apply_data(&mut self, data: &Variant) -> Result<(), ShapeError> { 109 | let p = |x, y, z| Point::new(x, y, z); 110 | fn e(r: Option) -> Result { 111 | r.ok_or(ShapeError::InvalidData) 112 | } 113 | let dict = || e(data.try_to_dictionary()); 114 | let get_f = |d: &Dictionary, k| Ok(e(d.get(k).try_to_f64())? as f32); 115 | let get_i = |d: &Dictionary, k| e(d.get(k).try_to_i64()); 116 | self.shape = match self.r#type { 117 | Type::Box => { 118 | let extents = e(data.try_to_vector3())?; 119 | SharedShape::cuboid(extents.x, extents.y, extents.z) 120 | } 121 | Type::Ray => { 122 | let data = dict()?; 123 | let length = get_f(&data, "length")?; 124 | // TODO Ditto 125 | // TODO There is a second paramater in the data that is currently unused 126 | SharedShape::capsule(p(0.0, length, 0.0), p(0.0, 0.0, 0.0), 0.0) 127 | } 128 | Type::Capsule => { 129 | let data = dict()?; 130 | let height = get_f(&data, "height")?; 131 | let radius = get_f(&data, "radius")?; 132 | // TODO Ditto 133 | SharedShape::capsule(p(0.0, height, 0.0), p(0.0, 0.0, 0.0), radius) 134 | } 135 | Type::Cylinder => { 136 | let data = dict()?; 137 | let height = get_f(&data, "height")?; 138 | let radius = get_f(&data, "radius")?; 139 | SharedShape::cylinder(height, radius) 140 | } 141 | Type::Heightmap => { 142 | let data = dict()?; 143 | let depth = get_i(&data, "depth")? as usize; 144 | let width = get_i(&data, "width")? as usize; 145 | let heights = e(data.get("heights").try_to_float32_array())?; 146 | let heights = heights.read(); 147 | // TODO there are max_height and min_height, what are they for? 148 | let mut map = DMatrix::zeros(width, depth); 149 | for x in 0..width { 150 | for z in 0..depth { 151 | map[(x, z)] = heights[x * depth + z]; 152 | } 153 | } 154 | SharedShape::heightfield( 155 | map, 156 | na::Vector3::new(depth as f32 - 1.0, 1.0, width as f32 - 1.0), 157 | ) 158 | } 159 | Type::Plane => { 160 | // TODO figure out a way to implement planes 161 | let plane = e(data.try_to_plane())?; 162 | let _ = plane; 163 | godot_error!("Planes are not implemented yet"); 164 | SharedShape::cuboid(1.0, 0.0, 1.0) 165 | } 166 | Type::Sphere => { 167 | let radius = e(data.try_to_f64())? as f32; 168 | SharedShape::ball(radius) 169 | } 170 | Type::Concave | Type::Convex => { 171 | let array = e(data.try_to_vector3_array())?; 172 | let array = array.read(); 173 | let mut verts = Vec::with_capacity(array.len()); 174 | for v in array.iter() { 175 | verts.push(p(v.x, v.y, v.z)); 176 | } 177 | if let Type::Concave = self.r#type { 178 | if array.len() % 3 != 0 { 179 | return Err(ShapeError::IncompleteTriangle); 180 | } 181 | let mut indices = Vec::with_capacity(array.len()); 182 | // It may be worth to perform some sort of deduplication to reduce the 183 | // amount of vertices. For now, the simple, dumb way will do. 184 | for i in 0..array.len() / 3 { 185 | let i = i * 3; 186 | indices.push([i as u32, (i + 1) as u32, (i + 2) as u32]); 187 | } 188 | SharedShape::trimesh(verts, indices) 189 | } else { 190 | SharedShape::convex_hull(&verts).ok_or(ShapeError::ConvexNotManifold)? 191 | } 192 | } 193 | }; 194 | Ok(()) 195 | } 196 | 197 | fn data(&self) -> Variant { 198 | match self.r#type { 199 | Type::Box => { 200 | let shape = self.shape.as_cuboid().unwrap(); 201 | vec_na_to_gd(shape.half_extents).owned_to_variant() 202 | } 203 | Type::Ray => { 204 | let shape = self.shape.as_capsule().unwrap(); 205 | let dict = Dictionary::new(); 206 | dict.insert("length", shape.segment.a.y.owned_to_variant()); 207 | dict.owned_to_variant() 208 | } 209 | Type::Capsule => { 210 | let shape = self.shape.as_capsule().unwrap(); 211 | let dict = Dictionary::new(); 212 | dict.insert("length", shape.segment.a.y.owned_to_variant()); 213 | dict.insert("radius", shape.radius.owned_to_variant()); 214 | dict.owned_to_variant() 215 | } 216 | Type::Cylinder => { 217 | let shape = self.shape.as_cylinder().unwrap(); 218 | let dict = Dictionary::new(); 219 | dict.insert("height", shape.half_height); 220 | dict.insert("radius", shape.radius); 221 | dict.owned_to_variant() 222 | } 223 | Type::Heightmap => { 224 | let shape = self.shape.as_heightfield().unwrap(); 225 | let depth = shape.nrows(); 226 | let width = shape.ncols(); 227 | let mut heights = TypedArray::::new(); 228 | heights.resize((depth * width).try_into().unwrap()); 229 | let mut wr_heights = heights.write(); 230 | for x in 0..width { 231 | for z in 0..depth { 232 | wr_heights[x * depth + z] = shape.heights()[(x, z)]; 233 | } 234 | } 235 | mem::drop(wr_heights); 236 | let dict = Dictionary::new(); 237 | dict.insert("depth", depth.owned_to_variant()); 238 | dict.insert("width", width.owned_to_variant()); 239 | dict.insert("heights", heights.owned_to_variant()); 240 | dict.owned_to_variant() 241 | } 242 | Type::Plane => { 243 | let _shape = self.shape.as_cuboid().unwrap(); 244 | let plane = Plane::new(Vector3::new(0.0, 0.0, 0.0), 1.0); 245 | plane.owned_to_variant() 246 | } 247 | Type::Sphere => { 248 | let shape = self.shape.as_ball().unwrap(); 249 | shape.radius.owned_to_variant() 250 | } 251 | Type::Convex => { 252 | let shape = self.shape.as_convex_polyhedron().unwrap(); 253 | let mut array = TypedArray::::new(); 254 | array.resize(shape.points().len().try_into().unwrap()); 255 | let mut wr_array = array.write(); 256 | for (s, d) in shape.points().iter().zip(wr_array.iter_mut()) { 257 | *d = vec_na_to_gd(s.coords); 258 | } 259 | mem::drop(wr_array); 260 | array.owned_to_variant() 261 | } 262 | Type::Concave => { 263 | let shape = self.shape.as_trimesh().unwrap(); 264 | let mut array = TypedArray::::new(); 265 | array.resize(shape.flat_indices().len().try_into().unwrap()); 266 | let mut wr_array = array.write(); 267 | for (s, d) in shape.flat_indices().iter().zip(wr_array.iter_mut()) { 268 | *d = vec_na_to_gd(shape.vertices()[usize::try_from(*s).unwrap()].coords); 269 | } 270 | mem::drop(wr_array); 271 | array.owned_to_variant() 272 | } 273 | } 274 | } 275 | 276 | pub fn shape(&self) -> &SharedShape { 277 | &self.shape 278 | } 279 | 280 | /// Do a best effort to scale a collider appropriately 281 | pub fn scaled(&self, scale: Vector3) -> SharedShape { 282 | let scale = vec_gd_to_na(scale); 283 | // TODO figure out the exact way each collider is scaled in Godot for consistency 284 | // The colliders where the scale is not certain are left empty for now 285 | match self.r#type { 286 | Type::Heightmap => { 287 | let hf = self.shape.as_heightfield().unwrap(); 288 | SharedShape::heightfield(hf.heights().clone(), hf.scale().component_mul(&scale)) 289 | } 290 | Type::Convex => { 291 | let cp = self.shape.as_convex_polyhedron().unwrap(); 292 | let cp_points = cp.points(); 293 | let mut verts = Vec::with_capacity(cp_points.len()); 294 | for v in cp_points.iter() { 295 | verts.push(Point3::new(v.x * scale.x, v.y * scale.y, v.z * scale.z)); 296 | } 297 | SharedShape::convex_hull(&verts[..]).expect("Failed to scale convex hull") 298 | } 299 | Type::Concave => { 300 | let tm = self.shape.as_trimesh().unwrap(); 301 | let tm_verts = tm.vertices(); 302 | let mut verts = Vec::with_capacity(tm_verts.len()); 303 | for v in tm_verts.iter() { 304 | verts.push(Point3::new(v.x * scale.x, v.y * scale.y, v.z * scale.z)); 305 | } 306 | SharedShape::trimesh(verts, tm.indices().iter().copied().collect()) 307 | } 308 | _ => self.shape.clone(), 309 | } 310 | } 311 | 312 | /// Frees this shape, removing it from any attached rigidbodies 313 | pub fn free(self) { 314 | // FIXME we need to track attached bodies to remove the corresponding shapes 315 | godot_error!("TODO free shape"); 316 | } 317 | 318 | /// Creates a new shape based on the given position and scale 319 | pub fn build_shape(&self, position: Isometry3, scale: Vector3) -> SharedShape { 320 | let shape_scale = position.rotation * vec_gd_to_na(scale); 321 | let shape_scale = vec_gd_to_na(scale).component_mul(&shape_scale); 322 | let shape_scale = vec_na_to_gd(shape_scale); 323 | self.scaled(shape_scale) 324 | } 325 | 326 | /// Creates a new collider based on the given position and scale 327 | pub fn build(&self, position: Isometry3, scale: Vector3, sensor: bool) -> Collider { 328 | ColliderBuilder::new(self.build_shape(position, scale)) 329 | .position(position) 330 | .sensor(sensor) 331 | .build() 332 | } 333 | 334 | /// Sets the index of this shape 335 | /// 336 | /// # Panics 337 | /// 338 | /// Panics if the index is already set 339 | pub fn set_index(&mut self, index: ShapeIndex) { 340 | assert_eq!(self.index, None, "Index is already set"); 341 | self.index = Some(index); 342 | } 343 | 344 | /// Returns the index of this shape 345 | /// 346 | /// # Panics 347 | /// 348 | /// Panics if the index isn't set 349 | pub fn index(&self) -> ShapeIndex { 350 | self.index.expect("Index isn't set") 351 | } 352 | } 353 | 354 | pub fn init(ffi: &mut ffi::FFI) { 355 | ffi!(ffi, shape_create, create); 356 | ffi!(ffi, shape_get_margin, |_| 0.0); 357 | ffi!(ffi, shape_set_margin, |_, _| ()); 358 | ffi!(ffi, shape_get_data, get_data); 359 | ffi!(ffi, shape_set_data, set_data); 360 | } 361 | 362 | /// Frees the given shape, removing it from any attached rigidbodies 363 | pub fn free(shape: Shape) { 364 | shape.free(); 365 | } 366 | 367 | fn create(shape: i32) -> Option { 368 | match Type::new(shape) { 369 | Ok(shape) => { 370 | let shape = Shape::new(shape); 371 | let index = ShapeIndex::add(shape); 372 | index.map_mut(|s| s.set_index(index)).unwrap(); 373 | Some(Index::Shape(index)) 374 | } 375 | Err(e) => { 376 | godot_error!("Invalid shape: {:?}", e); 377 | None 378 | } 379 | } 380 | } 381 | 382 | fn set_data(shape: Index, data: &Variant) { 383 | map_or_err!(shape, map_shape_mut, |shape, _| { 384 | if let Err(e) = shape.apply_data(&data) { 385 | godot_error!("Failed to apply data: {:?}", e); 386 | } 387 | }); 388 | } 389 | 390 | fn get_data(shape: Index) -> gdnative::sys::godot_variant { 391 | map_or_err!(shape, map_shape, |shape, _| { shape.data() }) 392 | .unwrap_or(Variant::new()) 393 | .forget() 394 | } 395 | -------------------------------------------------------------------------------- /rapier3d/src/server/body.rs: -------------------------------------------------------------------------------- 1 | use super::index::BodyIndex; 2 | use super::*; 3 | use crate::body::Body; 4 | use crate::util::*; 5 | use gdnative::core_types::*; 6 | use gdnative::godot_error; 7 | use rapier3d::dynamics::{RigidBody, RigidBodyActivation, RigidBodyBuilder, RigidBodyType}; 8 | 9 | #[derive(Debug)] 10 | enum Type { 11 | Static, 12 | Kinematic, 13 | Rigid, 14 | Character, 15 | } 16 | 17 | #[derive(Debug)] 18 | enum Param { 19 | Bounce(f32), 20 | Friction(f32), 21 | Mass(f32), 22 | GravityScale(f32), 23 | LinearDamp(f32), 24 | AngularDamp(f32), 25 | } 26 | 27 | #[derive(Debug)] 28 | enum State { 29 | Transform(Transform), 30 | LinearVelocity(Vector3), 31 | AngularVelocity(Vector3), 32 | Sleeping(bool), 33 | CanSleep(bool), 34 | } 35 | 36 | enum Mode { 37 | Static, 38 | Kinematic, 39 | Rigid, 40 | Character, 41 | } 42 | 43 | enum BodyAxis { 44 | Linear(Axis), 45 | Angular(Axis), 46 | } 47 | 48 | #[derive(Debug)] 49 | enum StateError { 50 | InvalidType, 51 | InvalidValue, 52 | } 53 | 54 | #[derive(Debug)] 55 | struct InvalidMode; 56 | 57 | #[derive(Debug)] 58 | struct InvalidAxis; 59 | 60 | impl Type { 61 | fn new(n: i32) -> Result { 62 | Ok(match n { 63 | 0 => Type::Static, 64 | 1 => Type::Kinematic, 65 | 2 => Type::Rigid, 66 | 3 => Type::Character, 67 | _ => return Err(()), 68 | }) 69 | } 70 | 71 | fn create_body(&self, sleep: bool) -> RigidBody { 72 | match self { 73 | Type::Static => RigidBodyBuilder::new_static(), 74 | Type::Kinematic => RigidBodyBuilder::new_kinematic_position_based(), 75 | Type::Rigid => RigidBodyBuilder::new_dynamic(), 76 | Type::Character => RigidBodyBuilder::new_dynamic(), 77 | } 78 | .sleeping(sleep) 79 | .additional_mass(1.0) 80 | .build() 81 | } 82 | } 83 | 84 | impl Param { 85 | fn new(n: i32, value: f32) -> Result { 86 | Ok(match n { 87 | 0 => Self::Bounce(value), 88 | 1 => Self::Friction(value), 89 | 2 => Self::Mass(value), 90 | 3 => Self::GravityScale(value), 91 | 4 => Self::LinearDamp(value), 92 | 5 => Self::AngularDamp(value), 93 | _ => return Err(()), 94 | }) 95 | } 96 | } 97 | 98 | impl State { 99 | fn new(r#type: i32, value: &Variant) -> Result { 100 | Ok(match r#type { 101 | 0 => State::Transform(value.try_to_transform().ok_or(StateError::InvalidValue)?), 102 | 1 => State::LinearVelocity(value.try_to_vector3().ok_or(StateError::InvalidValue)?), 103 | 2 => State::AngularVelocity(value.try_to_vector3().ok_or(StateError::InvalidValue)?), 104 | 3 => State::Sleeping(value.try_to_bool().ok_or(StateError::InvalidValue)?), 105 | 4 => State::CanSleep(value.try_to_bool().ok_or(StateError::InvalidValue)?), 106 | _ => return Err(StateError::InvalidType), 107 | }) 108 | } 109 | } 110 | 111 | impl Mode { 112 | fn new(n: i32) -> Result { 113 | Ok(match n { 114 | 0 => Self::Static, 115 | 1 => Self::Kinematic, 116 | 2 => Self::Rigid, 117 | 3 => Self::Character, 118 | _ => return Err(InvalidMode), 119 | }) 120 | } 121 | } 122 | 123 | impl BodyAxis { 124 | fn new(n: i32) -> Result { 125 | // TODO is it possible that multiplpe axises can be specified at once? 126 | Ok(match n { 127 | 1 => Self::Linear(Axis::X), 128 | 2 => Self::Linear(Axis::Y), 129 | 4 => Self::Linear(Axis::Z), 130 | 8 => Self::Angular(Axis::X), 131 | 16 => Self::Angular(Axis::Y), 132 | 32 => Self::Angular(Axis::Z), 133 | _ => return Err(InvalidAxis), 134 | }) 135 | } 136 | } 137 | 138 | pub fn init(ffi: &mut ffi::FFI) { 139 | ffi!(ffi, body_add_central_force, add_central_force); 140 | ffi!(ffi, body_add_force, add_force); 141 | ffi!(ffi, body_add_shape, add_shape); 142 | ffi!(ffi, body_add_collision_exception, add_collision_exception); 143 | ffi!(ffi, body_apply_central_impulse, apply_central_impulse); 144 | ffi!(ffi, body_apply_impulse, apply_impulse); 145 | ffi!( 146 | ffi, 147 | body_attach_object_instance_id, 148 | attach_object_instance_id 149 | ); 150 | ffi!(ffi, body_create, create); 151 | ffi!( 152 | ffi, 153 | body_is_continuous_collision_detection_enabled, 154 | is_continuous_collision_detection_enabled 155 | ); 156 | ffi!(ffi, body_get_contact, get_contact); 157 | ffi!(ffi, body_get_direct_state, get_direct_state); 158 | ffi!(ffi, body_get_kinematic_safe_margin, |_| 0.0); 159 | ffi!(ffi, body_is_axis_locked, is_axis_locked); 160 | ffi!(ffi, body_remove_shape, remove_shape); 161 | ffi!(ffi, body_set_axis_lock, set_axis_lock); 162 | ffi!(ffi, body_set_collision_layer, set_collision_layer); 163 | ffi!(ffi, body_set_collision_mask, set_collision_mask); 164 | ffi!( 165 | ffi, 166 | body_set_enable_continuous_collision_detection, 167 | set_enable_continuous_collision_detection 168 | ); 169 | ffi!(ffi, body_set_kinematic_safe_margin, |_, _| ()); 170 | ffi!( 171 | ffi, 172 | body_set_max_contacts_reported, 173 | set_max_contacts_reported 174 | ); 175 | ffi!(ffi, body_set_mode, set_mode); 176 | ffi!( 177 | ffi, 178 | body_set_omit_force_integration, 179 | set_omit_force_integration 180 | ); 181 | ffi!(ffi, body_set_param, set_param); 182 | ffi!(ffi, body_set_shape_transform, set_shape_transform); 183 | ffi!(ffi, body_set_shape_disabled, set_shape_disabled); 184 | ffi!(ffi, body_set_space, set_space); 185 | ffi!(ffi, body_set_state, set_state); 186 | ffi!(ffi, body_set_ray_pickable, set_ray_pickable); 187 | } 188 | 189 | /// Frees the given body, removing it from it's attached space (if any) 190 | pub fn free(body: Body) { 191 | body.free() 192 | } 193 | 194 | fn create(typ: i32, sleep: bool) -> Option { 195 | if let Ok(typ) = Type::new(typ) { 196 | let index = BodyIndex::add(Body::new(typ.create_body(sleep))); 197 | index.map_mut(|b| b.set_index(index)).unwrap(); 198 | Some(Index::Body(index)) 199 | } else { 200 | godot_error!("Invalid body type"); 201 | None 202 | } 203 | } 204 | 205 | fn add_collision_exception(body_a: Index, body_b: Index) { 206 | if body_a == body_b { 207 | return; // There is no point excluding the body from itself 208 | } 209 | if let Some(body_a) = body_a.as_body() { 210 | if let Some(body_b) = body_b.as_body() { 211 | let mut bodies = BodyIndex::write_all(); 212 | let (body_a, body_b) = bodies.get2_mut(body_a.into(), body_b.into()); 213 | if let Some(body_a) = body_a { 214 | if let Some(body_b) = body_b { 215 | // "Adding" the same exclusion multiple times is valid 216 | let _ = body_a.add_exclusion(body_b); 217 | } else { 218 | godot_error!("No body at index B"); 219 | } 220 | } else { 221 | godot_error!("No body at index A"); 222 | } 223 | } else { 224 | godot_error!("Index B does not point to a body"); 225 | } 226 | } else { 227 | godot_error!("Index A does not point to a body"); 228 | } 229 | } 230 | 231 | fn add_central_force(body: Index, force: &Vector3) { 232 | map_or_err!(body, map_body_mut, |body, _| { 233 | body.add_central_force(*force); 234 | }); 235 | } 236 | 237 | fn add_force(body: Index, force: &Vector3, position: &Vector3) { 238 | map_or_err!(body, map_body_mut, |body, _| { 239 | let (position, force) = transform_force_arguments(body, position, force); 240 | body.add_force_at_position(force, position); 241 | }); 242 | } 243 | 244 | fn add_shape(body: Index, shape: Index, transform: &Transform, disabled: bool) { 245 | map_or_err!(body, map_body_mut, |body, _| { 246 | map_or_err!(shape, map_shape_mut, |shape, _| { 247 | body.add_shape(shape, transform, !disabled); 248 | }); 249 | }); 250 | } 251 | 252 | fn apply_central_impulse(body: Index, impulse: &Vector3) { 253 | map_or_err!(body, map_body_mut, |body, _| { 254 | body.add_central_impulse(*impulse); 255 | }); 256 | } 257 | 258 | fn apply_impulse(body: Index, position: &Vector3, impulse: &Vector3) { 259 | map_or_err!(body, map_body_mut, |body, _| { 260 | let (position, impulse) = transform_force_arguments(body, position, impulse); 261 | body.add_impulse_at_position(impulse, position); 262 | }); 263 | } 264 | 265 | fn attach_object_instance_id(body: Index, id: u32) { 266 | map_or_err!(body, map_body_mut, |b, _| b 267 | .set_object_id(ObjectID::new(id))); 268 | } 269 | 270 | fn get_direct_state(body: Index, state: &mut ffi::PhysicsBodyState) { 271 | map_or_err!(body, map_body, |body, _| { 272 | body.read_body(|rb, space| { 273 | state.set_transform(&isometry_to_transform(rb.position())); 274 | state.set_linear_velocity(vec_na_to_gd(*rb.linvel())); 275 | state.set_angular_velocity(vec_na_to_gd(*rb.angvel())); 276 | state.set_sleeping(rb.is_sleeping()); 277 | state.set_linear_damp(rb.linear_damping()); 278 | state.set_angular_damp(rb.angular_damping()); 279 | let mp = rb.mass_properties(); 280 | state.set_inv_mass(mp.inv_mass); 281 | let inv_inertia_sqrt = vec_na_to_gd(mp.inv_principal_inertia_sqrt); 282 | state.set_inv_inertia(inv_inertia_sqrt.component_mul(inv_inertia_sqrt)); 283 | let inv_inertia_tensor = mp.reconstruct_inverse_inertia_matrix(); 284 | state.set_inv_inertia_tensor(&mat3_to_basis(&inv_inertia_tensor)); 285 | state.set_contact_count(body.contact_count()); 286 | state.set_space(space.map(Index::Space)); 287 | }); 288 | }); 289 | } 290 | 291 | fn get_contact(body: Index, id: u32, contact: &mut ffi::PhysicsBodyContact) { 292 | map_or_err!(body, map_body, |body, _| { 293 | if let Some(c) = body.get_contact(id) { 294 | body.read_body(|rb, _| { 295 | contact.set_index(Index::Body(c.index())); 296 | contact.set_local_position(c.local_position(rb)); 297 | contact.set_local_normal(c.local_normal(rb)); 298 | contact.set_position(c.position()); 299 | contact.set_object_id(c.object_id()); 300 | contact.set_shape(c.other_shape()); 301 | contact.set_local_shape(c.self_shape()); 302 | }); 303 | } else { 304 | godot_error!("Invalid contact"); 305 | } 306 | }); 307 | } 308 | 309 | fn remove_shape(body: Index, shape: i32) { 310 | map_or_err!(body, map_body_mut, |body, _| body 311 | .remove_shape(shape as u32)); 312 | } 313 | 314 | fn set_param(body: Index, param: i32, value: f32) { 315 | if let Ok(param) = Param::new(param, value) { 316 | map_or_err!(body, map_body_mut, |body, _| { 317 | match param { 318 | Param::Mass(mass) => body.set_mass(mass), 319 | Param::LinearDamp(damp) => body.set_linear_damp(damp), 320 | Param::AngularDamp(damp) => body.set_angular_damp(damp), 321 | Param::GravityScale(scale) => body.set_gravity_scale(scale), 322 | Param::Bounce(bounce) => body.set_restitution(bounce), 323 | Param::Friction(friction) => body.set_friction(friction), 324 | } 325 | }); 326 | } else { 327 | godot_error!("Invalid param"); 328 | }; 329 | } 330 | 331 | fn set_collision_layer(body: Index, layer: u32) { 332 | map_or_err!(body, map_body_mut, |body, _| body.set_groups(layer)); 333 | } 334 | 335 | fn set_collision_mask(body: Index, mask: u32) { 336 | map_or_err!(body, map_body_mut, |body, _| body.set_mask(mask)); 337 | } 338 | 339 | fn set_mode(body: Index, mode: i32) { 340 | match Mode::new(mode) { 341 | Ok(mode) => { 342 | let mode = match mode { 343 | Mode::Static => RigidBodyType::Static, 344 | Mode::Kinematic => RigidBodyType::KinematicPositionBased, 345 | Mode::Rigid => RigidBodyType::Dynamic, 346 | Mode::Character => { 347 | godot_error!("Character mode is not supported"); 348 | return; 349 | } 350 | }; 351 | map_or_err!(body, map_body_mut, |body, _| body.set_body_type(mode)); 352 | } 353 | Err(_) => godot_error!("Invalid mode"), 354 | } 355 | } 356 | 357 | fn set_omit_force_integration(body: Index, enable: bool) { 358 | map_or_err!(body, map_body_mut, |body, _| body 359 | .set_omit_force_integration(enable)); 360 | } 361 | 362 | fn set_shape_transform(body: Index, shape: i32, transform: &Transform) { 363 | let shape = shape as u32; 364 | map_or_err!(body, map_body_mut, |body, _| body 365 | .set_shape_transform(shape, transform)); 366 | } 367 | 368 | fn set_shape_disabled(body: Index, shape: i32, disable: bool) { 369 | map_or_err!(body, map_body_mut, |body, _| body 370 | .set_shape_enable(shape as u32, !disable)); 371 | } 372 | 373 | fn set_space(body: Index, space: Option) { 374 | map_or_err!(body, map_body_mut, |body, _| { 375 | if let Some(space) = space { 376 | map_or_err!(space, map_space_mut, |space, _| body.set_space(space)); 377 | } else { 378 | body.remove_from_space(); 379 | } 380 | }); 381 | } 382 | 383 | fn set_ray_pickable(body: Index, enable: bool) { 384 | map_or_err!(body, map_body_mut, |body, _| body.set_ray_pickable(enable)); 385 | } 386 | 387 | fn set_state(body: Index, state: i32, value: &Variant) { 388 | map_or_err!(body, map_body_mut, |body, _| { 389 | match State::new(state, value) { 390 | Ok(state) => match state { 391 | State::Transform(trf) => body.set_transform(&trf), 392 | State::LinearVelocity(vel) => body.set_linear_velocity(vel), 393 | State::AngularVelocity(vel) => body.set_angular_velocity(vel), 394 | State::Sleeping(sleep) => body.set_sleeping(sleep), 395 | State::CanSleep(sleep) => body.set_sleep_threshold(if sleep { 396 | RigidBodyActivation::default_threshold() 397 | } else { 398 | -1.0 399 | }), 400 | }, 401 | Err(e) => godot_error!("Invalid state: {:?}", e), 402 | } 403 | }); 404 | } 405 | 406 | fn set_max_contacts_reported(body: Index, count: i32) { 407 | let count = count as u32; 408 | map_or_err!(body, map_body_mut, |body, _| body.set_max_contacts(count)); 409 | } 410 | 411 | fn is_continuous_collision_detection_enabled(body: Index) -> bool { 412 | map_or_err!(body, map_body, |body, _| body.is_ccd_enabled()).unwrap_or(false) 413 | } 414 | 415 | fn set_enable_continuous_collision_detection(body: Index, enable: bool) { 416 | map_or_err!(body, map_body_mut, |body, _| body.enable_ccd(enable)); 417 | } 418 | 419 | fn set_axis_lock(body: Index, axis: i32, lock: bool) { 420 | if let Ok(axis) = BodyAxis::new(axis) { 421 | map_or_err!(body, map_body_mut, |body, _| { 422 | match axis { 423 | BodyAxis::Linear(_) => body.set_translation_lock(lock), 424 | BodyAxis::Angular(axis) => body.set_rotation_lock(axis, lock), 425 | } 426 | }); 427 | } else { 428 | godot_error!("Invalid axis"); 429 | } 430 | } 431 | 432 | fn is_axis_locked(body: Index, axis: i32) -> bool { 433 | if let Ok(axis) = BodyAxis::new(axis) { 434 | map_or_err!(body, map_body, |body, _| { 435 | match axis { 436 | BodyAxis::Linear(_) => body.is_translation_locked(), 437 | BodyAxis::Angular(axis) => body.is_rotation_locked(axis), 438 | } 439 | }) 440 | .unwrap_or(false) 441 | } else { 442 | godot_error!("Invalid axis"); 443 | false 444 | } 445 | } 446 | 447 | /// Extra methods exposed through the "call" function. 448 | mod call { 449 | use super::super::call; 450 | use super::*; 451 | use ffi::{PhysicsCallError, VariantType}; 452 | 453 | /// Return the local center of mass. 454 | pub fn get_local_com(arguments: &[&Variant]) -> call::Result { 455 | call_check_arg_count!(arguments in 1..1)?; 456 | let body = call_get_arg!(arguments[0] => Rid)?; 457 | if let Ok(body) = super::get_index(body) { 458 | Ok(map_or_err!(body, map_body_mut, |body, _| { 459 | body.local_com().owned_to_variant() 460 | }) 461 | .unwrap_or(Variant::new())) 462 | } else { 463 | godot_error!("Invalid index"); 464 | Ok(Variant::new()) 465 | } 466 | } 467 | 468 | /// Set the local center of mass. 469 | pub fn set_local_com(arguments: &[&Variant]) -> call::Result { 470 | call_check_arg_count!(arguments in 2..3)?; 471 | let body = call_get_arg!(arguments[0] => Rid)?; 472 | let com = call_get_arg!(arguments[1] => Vector3)?; 473 | let wake = call_get_arg!(arguments[2] => bool || true)?; 474 | if let Ok(body) = super::get_index(body) { 475 | map_or_err!(body, map_body_mut, |body, _| { 476 | body.set_local_com(com, wake); 477 | }); 478 | } else { 479 | godot_error!("Invalid index"); 480 | } 481 | Ok(Variant::new()) 482 | } 483 | 484 | /// Apply a force to a body. The origin and direction are in the local space of the body. 485 | /// 486 | /// The origin is relative to the center of mass. 487 | pub fn add_local_force(arguments: &[&Variant]) -> call::Result { 488 | call_check_arg_count!(arguments in 3..3)?; 489 | let body = call_get_arg!(arguments[0] => Rid)?; 490 | let force = call_get_arg!(arguments[1] => Vector3)?; 491 | let origin = call_get_arg!(arguments[2] => Vector3)?; 492 | if let Ok(body) = super::get_index(body) { 493 | map_or_err!(body, map_body_mut, |body, _| { 494 | body.add_local_force_at_position(force, origin); 495 | }); 496 | } else { 497 | godot_error!("Invalid index"); 498 | } 499 | Ok(Variant::new()) 500 | } 501 | 502 | /// Apply an impulse to a body. The origin and direction are in the local space of the body. 503 | pub fn add_local_impulse(arguments: &[&Variant]) -> call::Result { 504 | call_check_arg_count!(arguments in 3..3)?; 505 | let body = call_get_arg!(arguments[0] => Rid)?; 506 | let origin = call_get_arg!(arguments[1] => Vector3)?; 507 | let direction = call_get_arg!(arguments[2] => Vector3)?; 508 | if let Ok(body) = super::get_index(body) { 509 | map_or_err!(body, map_body_mut, |body, _| { 510 | body.add_local_impulse_at_position(origin, direction); 511 | }); 512 | } else { 513 | godot_error!("Invalid index"); 514 | } 515 | Ok(Variant::new()) 516 | } 517 | } 518 | 519 | pub(super) use call::*; 520 | 521 | /// Godot's "It's local position but global rotation" is such a mindfuck that this function exists 522 | /// to help out 523 | fn transform_force_arguments( 524 | body: &Body, 525 | position: &Vector3, 526 | direction: &Vector3, 527 | ) -> (Vector3, Vector3) { 528 | (*position + body.translation(), *direction) 529 | } 530 | --------------------------------------------------------------------------------