@bornengine/engine 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- package/src/world/version.ts +114 -0
|
@@ -0,0 +1,1908 @@
|
|
|
1
|
+
//! Jolt-backed physics world for Bloom Engine.
|
|
2
|
+
//!
|
|
3
|
+
//! Wraps `jolt_sys` behind f64 handle registries so Perry FFI can speak to
|
|
4
|
+
//! Jolt using only scalar values.
|
|
5
|
+
//!
|
|
6
|
+
//! Handle conventions:
|
|
7
|
+
//! - bloom handles are 1-based f64 indices into HandleRegistry
|
|
8
|
+
//! - bj_world, bj_shape, bj_body, bj_constraint are native Jolt handles
|
|
9
|
+
//! - Body/constraint handles are scoped to a single world internally
|
|
10
|
+
//!
|
|
11
|
+
//! Thread-safety: Jolt's contact listener runs on job threads; the queue it
|
|
12
|
+
//! fills is already mutex-protected inside `bloom_jolt.cpp`. Everything in
|
|
13
|
+
//! this file is single-threaded (matches Bloom's engine-main-thread model).
|
|
14
|
+
|
|
15
|
+
use crate::handles::HandleRegistry;
|
|
16
|
+
use crate::jolt_sys::*;
|
|
17
|
+
|
|
18
|
+
use std::sync::Once;
|
|
19
|
+
|
|
20
|
+
static GLOBAL_INIT: Once = Once::new();
|
|
21
|
+
|
|
22
|
+
/// Ensures `bj_global_init` is called exactly once per process.
|
|
23
|
+
pub fn ensure_jolt_initialised() {
|
|
24
|
+
GLOBAL_INIT.call_once(|| {
|
|
25
|
+
// SAFETY: called exactly once; bj_global_init is refcounted and idempotent.
|
|
26
|
+
let r = unsafe { bj_global_init() };
|
|
27
|
+
assert_eq!(r, BjResult::Ok, "Jolt global init failed");
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================
|
|
32
|
+
// Cached query results (multi-call read-back pattern)
|
|
33
|
+
// ============================================================
|
|
34
|
+
|
|
35
|
+
#[derive(Default)]
|
|
36
|
+
struct RayHitCache {
|
|
37
|
+
hits: Vec<BjRayHit>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[derive(Default)]
|
|
41
|
+
struct OverlapCache {
|
|
42
|
+
bodies: Vec<bj_body>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[derive(Default)]
|
|
46
|
+
struct ContactCache {
|
|
47
|
+
/// Drained from Jolt's queue at the start of each read cycle.
|
|
48
|
+
events: Vec<BjContact>,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================
|
|
52
|
+
// JoltPhysics — owned by EngineState, indexed by f64 handles
|
|
53
|
+
// ============================================================
|
|
54
|
+
|
|
55
|
+
pub struct JoltPhysics {
|
|
56
|
+
/// Usually a single world; supporting a registry keeps the API symmetric.
|
|
57
|
+
worlds: HandleRegistry<bj_world>,
|
|
58
|
+
/// Shapes live across worlds (refcounted in Jolt). Stored as raw pointer values.
|
|
59
|
+
shapes: HandleRegistry<bj_shape>,
|
|
60
|
+
/// Bodies are world-scoped. We remember which world they belong to.
|
|
61
|
+
bodies: HandleRegistry<(bj_world, bj_body)>,
|
|
62
|
+
/// Constraints similarly scoped.
|
|
63
|
+
constraints: HandleRegistry<(bj_world, bj_constraint)>,
|
|
64
|
+
/// Character controllers — world-scoped like bodies/constraints.
|
|
65
|
+
characters: HandleRegistry<(bj_world, bj_character)>,
|
|
66
|
+
/// Wheeled vehicles — world-scoped; stores bj_vehicle + the chassis body
|
|
67
|
+
/// handle it mapped to (for TS-side getChassis()).
|
|
68
|
+
vehicles: HandleRegistry<(bj_world, bj_vehicle, f64 /*chassis body handle*/)>,
|
|
69
|
+
|
|
70
|
+
ray_hit_cache: RayHitCache,
|
|
71
|
+
overlap_cache: OverlapCache,
|
|
72
|
+
contact_cache: ContactCache,
|
|
73
|
+
|
|
74
|
+
/// Scratch streams for variable-size shape inputs (convex hull points,
|
|
75
|
+
/// mesh vertices/indices, heightfield samples). The TS layer pushes
|
|
76
|
+
/// values via `bloom_physics_scratch_push_*` then calls the shape factory.
|
|
77
|
+
scratch_f32: Vec<f32>,
|
|
78
|
+
scratch_u32: Vec<u32>,
|
|
79
|
+
/// Compound-shape builder state — cleared by compound_begin, extended
|
|
80
|
+
/// by compound_add_child, consumed by compound_end.
|
|
81
|
+
compound_children: Vec<(bj_shape, BjTransform)>,
|
|
82
|
+
|
|
83
|
+
/// Per-world fixed-timestep state, keyed by the raw bj_world handle.
|
|
84
|
+
/// Created lazily on the first step_fixed / set_fixed_timestep call;
|
|
85
|
+
/// worlds stepped only via the variable-rate `step()` never allocate
|
|
86
|
+
/// one.
|
|
87
|
+
step_states: std::collections::HashMap<u64, WorldStepState>,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Fixed-timestep accumulator + interpolation state for one world.
|
|
91
|
+
///
|
|
92
|
+
/// Variable-dt stepping feeds frame hitches straight into the solver
|
|
93
|
+
/// (tunneling, constraint explosions); the accumulator decouples
|
|
94
|
+
/// simulation rate from render rate the standard way: clamp the incoming
|
|
95
|
+
/// frame dt, simulate N whole fixed steps, carry the remainder, and
|
|
96
|
+
/// expose `alpha = remainder / fixed_dt` so rendering can interpolate
|
|
97
|
+
/// between the last two physics states.
|
|
98
|
+
struct WorldStepState {
|
|
99
|
+
fixed_dt: f32,
|
|
100
|
+
/// Spiral-of-death guard: at most this many fixed steps per frame.
|
|
101
|
+
/// When the cap hits, the surplus backlog is dropped — the simulation
|
|
102
|
+
/// slows down instead of feeding ever-longer frames back into itself.
|
|
103
|
+
max_steps_per_frame: u32,
|
|
104
|
+
accumulator: f32,
|
|
105
|
+
/// remainder / fixed_dt after the latest step_fixed, in [0, 1).
|
|
106
|
+
alpha: f32,
|
|
107
|
+
/// When true, body position/rotation getters blend between the state
|
|
108
|
+
/// snapshot taken before the latest step batch and the current state.
|
|
109
|
+
/// Physics queries (raycast/overlap) always see the real simulation
|
|
110
|
+
/// state regardless.
|
|
111
|
+
interpolate: bool,
|
|
112
|
+
/// bj_body → (position, rotation) captured before the latest batch.
|
|
113
|
+
prev: std::collections::HashMap<u64, (BjVec3, BjQuat)>,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
impl Default for WorldStepState {
|
|
117
|
+
fn default() -> Self {
|
|
118
|
+
Self {
|
|
119
|
+
fixed_dt: 1.0 / 60.0,
|
|
120
|
+
max_steps_per_frame: 4,
|
|
121
|
+
accumulator: 0.0,
|
|
122
|
+
alpha: 1.0,
|
|
123
|
+
interpolate: false,
|
|
124
|
+
prev: std::collections::HashMap::new(),
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Hard ceiling on a single frame's contribution to the accumulator.
|
|
130
|
+
/// A debugger pause or OS hitch produces one slowed-down frame instead of
|
|
131
|
+
/// minutes of simulated catch-up.
|
|
132
|
+
const MAX_FRAME_DT: f32 = 0.25;
|
|
133
|
+
|
|
134
|
+
impl Default for JoltPhysics {
|
|
135
|
+
fn default() -> Self { Self::new() }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
impl JoltPhysics {
|
|
139
|
+
pub fn new() -> Self {
|
|
140
|
+
ensure_jolt_initialised();
|
|
141
|
+
Self {
|
|
142
|
+
worlds: HandleRegistry::new(),
|
|
143
|
+
shapes: HandleRegistry::new(),
|
|
144
|
+
bodies: HandleRegistry::new(),
|
|
145
|
+
constraints: HandleRegistry::new(),
|
|
146
|
+
characters: HandleRegistry::new(),
|
|
147
|
+
vehicles: HandleRegistry::new(),
|
|
148
|
+
ray_hit_cache: RayHitCache::default(),
|
|
149
|
+
overlap_cache: OverlapCache::default(),
|
|
150
|
+
contact_cache: ContactCache::default(),
|
|
151
|
+
scratch_f32: Vec::with_capacity(1024),
|
|
152
|
+
scratch_u32: Vec::with_capacity(512),
|
|
153
|
+
compound_children: Vec::with_capacity(16),
|
|
154
|
+
step_states: std::collections::HashMap::new(),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// -------------------------------------------------------- Scratch buffer
|
|
159
|
+
|
|
160
|
+
pub fn scratch_reset(&mut self) {
|
|
161
|
+
self.scratch_f32.clear();
|
|
162
|
+
self.scratch_u32.clear();
|
|
163
|
+
}
|
|
164
|
+
pub fn scratch_push_f32(&mut self, v: f32) { self.scratch_f32.push(v); }
|
|
165
|
+
pub fn scratch_push_u32(&mut self, v: u32) { self.scratch_u32.push(v); }
|
|
166
|
+
|
|
167
|
+
pub fn shape_convex_hull_from_scratch(&mut self, num_points: u32, convex_radius: f32) -> f64 {
|
|
168
|
+
let need = (num_points * 3) as usize;
|
|
169
|
+
if self.scratch_f32.len() < need || num_points < 3 { return 0.0; }
|
|
170
|
+
let points: Vec<BjVec3> = (0..num_points as usize).map(|i| BjVec3 {
|
|
171
|
+
x: self.scratch_f32[i * 3],
|
|
172
|
+
y: self.scratch_f32[i * 3 + 1],
|
|
173
|
+
z: self.scratch_f32[i * 3 + 2],
|
|
174
|
+
}).collect();
|
|
175
|
+
self.create_convex_hull_shape(&points, convex_radius)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
pub fn shape_mesh_from_scratch(&mut self, vertex_count: u32, triangle_count: u32) -> f64 {
|
|
179
|
+
let need_f = (vertex_count * 3) as usize;
|
|
180
|
+
let need_u = (triangle_count * 3) as usize;
|
|
181
|
+
if self.scratch_f32.len() < need_f || self.scratch_u32.len() < need_u { return 0.0; }
|
|
182
|
+
if vertex_count == 0 || triangle_count == 0 { return 0.0; }
|
|
183
|
+
let vertices: Vec<BjVec3> = (0..vertex_count as usize).map(|i| BjVec3 {
|
|
184
|
+
x: self.scratch_f32[i * 3],
|
|
185
|
+
y: self.scratch_f32[i * 3 + 1],
|
|
186
|
+
z: self.scratch_f32[i * 3 + 2],
|
|
187
|
+
}).collect();
|
|
188
|
+
let indices: Vec<u32> = self.scratch_u32[..need_u].to_vec();
|
|
189
|
+
self.create_mesh_shape(&vertices, &indices)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#[allow(clippy::too_many_arguments)]
|
|
193
|
+
pub fn shape_heightfield_from_scratch(
|
|
194
|
+
&mut self, sample_count: u32,
|
|
195
|
+
ox: f32, oy: f32, oz: f32, sx: f32, sy: f32, sz: f32, block_size: u32,
|
|
196
|
+
) -> f64 {
|
|
197
|
+
let need = (sample_count * sample_count) as usize;
|
|
198
|
+
if self.scratch_f32.len() < need || sample_count < 2 { return 0.0; }
|
|
199
|
+
let samples = self.scratch_f32[..need].to_vec();
|
|
200
|
+
self.create_heightfield_shape(
|
|
201
|
+
&samples, sample_count,
|
|
202
|
+
BjVec3 { x: ox, y: oy, z: oz },
|
|
203
|
+
BjVec3 { x: sx, y: sy, z: sz },
|
|
204
|
+
block_size,
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
pub fn compound_begin(&mut self) {
|
|
209
|
+
self.compound_children.clear();
|
|
210
|
+
}
|
|
211
|
+
#[allow(clippy::too_many_arguments)]
|
|
212
|
+
pub fn compound_add_child(&mut self,
|
|
213
|
+
shape_h: f64,
|
|
214
|
+
px: f32, py: f32, pz: f32,
|
|
215
|
+
rx: f32, ry: f32, rz: f32, rw: f32,
|
|
216
|
+
) {
|
|
217
|
+
if let Some(&shape) = self.shapes.get(shape_h) {
|
|
218
|
+
self.compound_children.push((shape, BjTransform {
|
|
219
|
+
position: BjVec3 { x: px, y: py, z: pz },
|
|
220
|
+
rotation: BjQuat { x: rx, y: ry, z: rz, w: rw },
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
pub fn compound_end(&mut self) -> f64 {
|
|
225
|
+
if self.compound_children.is_empty() { return 0.0; }
|
|
226
|
+
let shapes: Vec<bj_shape> = self.compound_children.iter().map(|(s, _)| *s).collect();
|
|
227
|
+
let xforms: Vec<BjTransform> = self.compound_children.iter().map(|(_, x)| *x).collect();
|
|
228
|
+
let s = unsafe {
|
|
229
|
+
bj_shape_compound_static(shapes.as_ptr(), xforms.as_ptr(), shapes.len() as u32)
|
|
230
|
+
};
|
|
231
|
+
self.compound_children.clear();
|
|
232
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// -------------------------------------------------------- World
|
|
236
|
+
|
|
237
|
+
pub fn create_world(
|
|
238
|
+
&mut self,
|
|
239
|
+
gx: f32, gy: f32, gz: f32,
|
|
240
|
+
max_bodies: u32, num_threads: u32,
|
|
241
|
+
) -> f64 {
|
|
242
|
+
let desc = BjWorldDesc {
|
|
243
|
+
gravity: BjVec3 { x: gx, y: gy, z: gz },
|
|
244
|
+
max_bodies: if max_bodies > 0 { max_bodies } else { 65536 },
|
|
245
|
+
num_threads,
|
|
246
|
+
..Default::default()
|
|
247
|
+
};
|
|
248
|
+
let world = unsafe { bj_world_create(&desc) };
|
|
249
|
+
if world == BJ_INVALID { return 0.0; }
|
|
250
|
+
self.worlds.alloc(world)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
pub fn destroy_world(&mut self, handle: f64) {
|
|
254
|
+
if let Some(world) = self.worlds.free(handle) {
|
|
255
|
+
// Destroy bodies and constraints tied to this world first.
|
|
256
|
+
// (Iterate and collect to avoid borrow conflicts.)
|
|
257
|
+
let body_handles: Vec<f64> = self.bodies.iter()
|
|
258
|
+
.filter(|(_, (w, _))| *w == world)
|
|
259
|
+
.map(|(h, _)| h)
|
|
260
|
+
.collect();
|
|
261
|
+
for h in body_handles {
|
|
262
|
+
if let Some((w, b)) = self.bodies.free(h) {
|
|
263
|
+
unsafe { bj_body_destroy(w, b); }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
let c_handles: Vec<f64> = self.constraints.iter()
|
|
267
|
+
.filter(|(_, (w, _))| *w == world)
|
|
268
|
+
.map(|(h, _)| h)
|
|
269
|
+
.collect();
|
|
270
|
+
for h in c_handles {
|
|
271
|
+
if let Some((w, c)) = self.constraints.free(h) {
|
|
272
|
+
unsafe { bj_constraint_destroy(w, c); }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
self.step_states.remove(&world);
|
|
276
|
+
unsafe { bj_world_destroy(world); }
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
pub fn set_gravity(&self, world_h: f64, x: f32, y: f32, z: f32) {
|
|
281
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
282
|
+
unsafe { bj_world_set_gravity(world, BjVec3 { x, y, z }); }
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
pub fn get_gravity_axis(&self, world_h: f64, axis: u32) -> f64 {
|
|
287
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
288
|
+
let mut g = BjVec3::default();
|
|
289
|
+
unsafe { bj_world_get_gravity(world, &mut g); }
|
|
290
|
+
return match axis { 0 => g.x, 1 => g.y, 2 => g.z, _ => 0.0 } as f64;
|
|
291
|
+
}
|
|
292
|
+
0.0
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
pub fn optimize_broadphase(&self, world_h: f64) {
|
|
296
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
297
|
+
unsafe { bj_world_optimize_broadphase(world); }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
pub fn step(&mut self, world_h: f64, dt: f32, collision_steps: u32) {
|
|
302
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
303
|
+
unsafe { bj_world_step(world, dt, collision_steps.max(1)); }
|
|
304
|
+
self.drain_contacts(world);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Drain the shim's queued contact events into our cache so they
|
|
309
|
+
/// survive across queries. The shim queue accumulates across multiple
|
|
310
|
+
/// bj_world_step calls, so one drain after a fixed-step batch captures
|
|
311
|
+
/// the whole batch's events.
|
|
312
|
+
fn drain_contacts(&mut self, world: bj_world) {
|
|
313
|
+
let count = unsafe { bj_world_contact_count(world) };
|
|
314
|
+
if count > 0 {
|
|
315
|
+
self.contact_cache.events.resize(count as usize, unsafe { std::mem::zeroed() });
|
|
316
|
+
let drained = unsafe {
|
|
317
|
+
bj_world_pop_contacts(world, self.contact_cache.events.as_mut_ptr(), count)
|
|
318
|
+
};
|
|
319
|
+
self.contact_cache.events.truncate(drained as usize);
|
|
320
|
+
} else {
|
|
321
|
+
self.contact_cache.events.clear();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Advance the world by whole fixed steps, carrying the remainder.
|
|
326
|
+
/// Returns the interpolation alpha in [0, 1): how far the carried
|
|
327
|
+
/// remainder sits between the last simulated state and the next.
|
|
328
|
+
///
|
|
329
|
+
/// This is the default stepping mode for the TS `physics.step()` API;
|
|
330
|
+
/// the variable-rate `step()` above remains as an explicit opt-out.
|
|
331
|
+
pub fn step_fixed(&mut self, world_h: f64, frame_dt: f32, collision_steps: u32) -> f32 {
|
|
332
|
+
let Some(&world) = self.worlds.get(world_h) else { return 1.0 };
|
|
333
|
+
let dt = if frame_dt.is_finite() { frame_dt.clamp(0.0, MAX_FRAME_DT) } else { 0.0 };
|
|
334
|
+
|
|
335
|
+
let st = self.step_states.entry(world).or_default();
|
|
336
|
+
st.accumulator += dt;
|
|
337
|
+
let fixed = st.fixed_dt;
|
|
338
|
+
let mut steps = (st.accumulator / fixed) as u32;
|
|
339
|
+
if steps > st.max_steps_per_frame {
|
|
340
|
+
steps = st.max_steps_per_frame;
|
|
341
|
+
// Drop the surplus backlog (keep at most one extra step's worth
|
|
342
|
+
// so alpha stays meaningful) — slow down, don't spiral.
|
|
343
|
+
st.accumulator = st.accumulator.min(fixed * (steps as f32 + 1.0));
|
|
344
|
+
}
|
|
345
|
+
let interpolate = st.interpolate;
|
|
346
|
+
|
|
347
|
+
if steps > 0 {
|
|
348
|
+
// Interpolation blends between the two most recent simulated
|
|
349
|
+
// states, so the snapshot is taken before the LAST step of the
|
|
350
|
+
// batch — snapshotting before the whole batch would render a
|
|
351
|
+
// backward jump on multi-step catch-up frames.
|
|
352
|
+
let mut snap = None;
|
|
353
|
+
for i in 0..steps {
|
|
354
|
+
if interpolate && i + 1 == steps {
|
|
355
|
+
snap = Some(self.snapshot_world_bodies(world));
|
|
356
|
+
}
|
|
357
|
+
unsafe { bj_world_step(world, fixed, collision_steps.max(1)); }
|
|
358
|
+
}
|
|
359
|
+
if let Some(s) = snap {
|
|
360
|
+
self.step_states.get_mut(&world).unwrap().prev = s;
|
|
361
|
+
}
|
|
362
|
+
self.drain_contacts(world);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let st = self.step_states.get_mut(&world).unwrap();
|
|
366
|
+
st.accumulator -= steps as f32 * fixed;
|
|
367
|
+
st.alpha = (st.accumulator / fixed).clamp(0.0, 1.0);
|
|
368
|
+
st.alpha
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/// Configure the fixed step rate (`hz`, e.g. 60.0) and the per-frame
|
|
372
|
+
/// catch-up cap for a world. Values <= 0 keep the current setting.
|
|
373
|
+
pub fn set_fixed_timestep(&mut self, world_h: f64, hz: f32, max_steps: u32) {
|
|
374
|
+
let Some(&world) = self.worlds.get(world_h) else { return };
|
|
375
|
+
let st = self.step_states.entry(world).or_default();
|
|
376
|
+
if hz > 0.0 && hz.is_finite() {
|
|
377
|
+
st.fixed_dt = 1.0 / hz;
|
|
378
|
+
}
|
|
379
|
+
if max_steps > 0 {
|
|
380
|
+
st.max_steps_per_frame = max_steps;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Enable/disable render interpolation for a world's body transform
|
|
385
|
+
/// getters. Off by default (getters return the raw simulation state).
|
|
386
|
+
pub fn set_interpolation(&mut self, world_h: f64, on: bool) {
|
|
387
|
+
let Some(&world) = self.worlds.get(world_h) else { return };
|
|
388
|
+
let st = self.step_states.entry(world).or_default();
|
|
389
|
+
st.interpolate = on;
|
|
390
|
+
if !on {
|
|
391
|
+
st.prev.clear();
|
|
392
|
+
st.alpha = 1.0;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Interpolation alpha from the most recent step_fixed (1.0 when the
|
|
397
|
+
/// world has never been fixed-stepped).
|
|
398
|
+
pub fn step_alpha(&self, world_h: f64) -> f32 {
|
|
399
|
+
self.worlds
|
|
400
|
+
.get(world_h)
|
|
401
|
+
.and_then(|w| self.step_states.get(w))
|
|
402
|
+
.map(|st| st.alpha)
|
|
403
|
+
.unwrap_or(1.0)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// Capture (position, rotation) of every body in `world`.
|
|
407
|
+
fn snapshot_world_bodies(&self, world: bj_world) -> std::collections::HashMap<u64, (BjVec3, BjQuat)> {
|
|
408
|
+
self.bodies
|
|
409
|
+
.iter()
|
|
410
|
+
.filter(|(_, &(w, _))| w == world)
|
|
411
|
+
.map(|(_, &(w, b))| {
|
|
412
|
+
let mut v = BjVec3 { x: 0.0, y: 0.0, z: 0.0 };
|
|
413
|
+
let mut q = BjQuat { x: 0.0, y: 0.0, z: 0.0, w: 1.0 };
|
|
414
|
+
unsafe {
|
|
415
|
+
bj_body_get_position(w, b, &mut v);
|
|
416
|
+
bj_body_get_rotation(w, b, &mut q);
|
|
417
|
+
}
|
|
418
|
+
(b, (v, q))
|
|
419
|
+
})
|
|
420
|
+
.collect()
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/// Blend factor + previous state for a body, when its world has
|
|
424
|
+
/// interpolation enabled and a fresh snapshot. None → return raw state.
|
|
425
|
+
fn interp_prev(&self, world: bj_world, body: bj_body) -> Option<(f32, &(BjVec3, BjQuat))> {
|
|
426
|
+
let st = self.step_states.get(&world)?;
|
|
427
|
+
if !st.interpolate || st.alpha >= 1.0 {
|
|
428
|
+
return None;
|
|
429
|
+
}
|
|
430
|
+
st.prev.get(&body).map(|p| (st.alpha, p))
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
pub fn set_layer_collides(&self, world_h: f64, a: u32, b: u32, collides: bool) {
|
|
434
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
435
|
+
unsafe { bj_world_set_layer_collides(world, a, b, collides as u8); }
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
pub fn get_layer_collides(&self, world_h: f64, a: u32, b: u32) -> bool {
|
|
440
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
441
|
+
return unsafe { bj_world_get_layer_collides(world, a, b) } != 0;
|
|
442
|
+
}
|
|
443
|
+
false
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
pub fn body_count(&self, world_h: f64) -> u32 {
|
|
447
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
448
|
+
return unsafe { bj_world_body_count(world) };
|
|
449
|
+
}
|
|
450
|
+
0
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
pub fn active_body_count(&self, world_h: f64) -> u32 {
|
|
454
|
+
if let Some(&world) = self.worlds.get(world_h) {
|
|
455
|
+
return unsafe { bj_world_active_body_count(world) };
|
|
456
|
+
}
|
|
457
|
+
0
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// -------------------------------------------------------- Shapes
|
|
461
|
+
|
|
462
|
+
pub fn create_box_shape(&mut self, hx: f32, hy: f32, hz: f32, convex_radius: f32) -> f64 {
|
|
463
|
+
let s = unsafe { bj_shape_box(BjVec3 { x: hx, y: hy, z: hz }, convex_radius) };
|
|
464
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
pub fn create_sphere_shape(&mut self, radius: f32) -> f64 {
|
|
468
|
+
let s = unsafe { bj_shape_sphere(radius) };
|
|
469
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
pub fn create_capsule_shape(&mut self, half_height: f32, radius: f32) -> f64 {
|
|
473
|
+
let s = unsafe { bj_shape_capsule(half_height, radius) };
|
|
474
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
pub fn create_cylinder_shape(&mut self, half_height: f32, radius: f32, convex_radius: f32) -> f64 {
|
|
478
|
+
let s = unsafe { bj_shape_cylinder(half_height, radius, convex_radius) };
|
|
479
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
pub fn create_convex_hull_shape(&mut self, points: &[BjVec3], convex_radius: f32) -> f64 {
|
|
483
|
+
let s = unsafe {
|
|
484
|
+
bj_shape_convex_hull(points.as_ptr(), points.len() as u32, convex_radius)
|
|
485
|
+
};
|
|
486
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
pub fn create_mesh_shape(&mut self, vertices: &[BjVec3], indices: &[u32]) -> f64 {
|
|
490
|
+
debug_assert!(indices.len() % 3 == 0);
|
|
491
|
+
let s = unsafe {
|
|
492
|
+
bj_shape_mesh(
|
|
493
|
+
vertices.as_ptr(), vertices.len() as u32,
|
|
494
|
+
indices.as_ptr(), (indices.len() / 3) as u32,
|
|
495
|
+
)
|
|
496
|
+
};
|
|
497
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
pub fn create_heightfield_shape(
|
|
501
|
+
&mut self,
|
|
502
|
+
samples: &[f32], sample_count: u32,
|
|
503
|
+
offset: BjVec3, scale: BjVec3, block_size: u32,
|
|
504
|
+
) -> f64 {
|
|
505
|
+
let s = unsafe {
|
|
506
|
+
bj_shape_heightfield(
|
|
507
|
+
samples.as_ptr(), sample_count, offset, scale, block_size,
|
|
508
|
+
)
|
|
509
|
+
};
|
|
510
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
pub fn create_scaled_shape(&mut self, base_h: f64, sx: f32, sy: f32, sz: f32) -> f64 {
|
|
514
|
+
let base = match self.shapes.get(base_h) { Some(&s) => s, None => return 0.0 };
|
|
515
|
+
let s = unsafe { bj_shape_scaled(base, BjVec3 { x: sx, y: sy, z: sz }) };
|
|
516
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
pub fn create_offset_com_shape(&mut self, base_h: f64, ox: f32, oy: f32, oz: f32) -> f64 {
|
|
520
|
+
let base = match self.shapes.get(base_h) { Some(&s) => s, None => return 0.0 };
|
|
521
|
+
let s = unsafe { bj_shape_offset_com(base, BjVec3 { x: ox, y: oy, z: oz }) };
|
|
522
|
+
if s == BJ_INVALID { 0.0 } else { self.shapes.alloc(s) }
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
pub fn release_shape(&mut self, handle: f64) {
|
|
526
|
+
if let Some(s) = self.shapes.free(handle) {
|
|
527
|
+
unsafe { bj_shape_release(s); }
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
pub fn shape_bounds_axis(&self, handle: f64, axis: u32) -> f64 {
|
|
532
|
+
if let Some(&s) = self.shapes.get(handle) {
|
|
533
|
+
let mut mn = BjVec3::default();
|
|
534
|
+
let mut mx = BjVec3::default();
|
|
535
|
+
unsafe { bj_shape_get_local_bounds(s, &mut mn, &mut mx); }
|
|
536
|
+
return match axis {
|
|
537
|
+
0 => mn.x, 1 => mn.y, 2 => mn.z,
|
|
538
|
+
3 => mx.x, 4 => mx.y, 5 => mx.z,
|
|
539
|
+
_ => 0.0,
|
|
540
|
+
} as f64;
|
|
541
|
+
}
|
|
542
|
+
0.0
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
pub fn shape_volume(&self, handle: f64) -> f32 {
|
|
546
|
+
self.shapes.get(handle).map(|&s| unsafe { bj_shape_get_volume(s) }).unwrap_or(0.0)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// -------------------------------------------------------- Bodies
|
|
550
|
+
|
|
551
|
+
/// Full body descriptor — passed as flat scalars by the FFI wrapper.
|
|
552
|
+
#[allow(clippy::too_many_arguments)]
|
|
553
|
+
pub fn create_body(
|
|
554
|
+
&mut self,
|
|
555
|
+
world_h: f64, shape_h: f64,
|
|
556
|
+
motion_type: u32,
|
|
557
|
+
px: f32, py: f32, pz: f32,
|
|
558
|
+
rx: f32, ry: f32, rz: f32, rw: f32,
|
|
559
|
+
vx: f32, vy: f32, vz: f32,
|
|
560
|
+
wx: f32, wy: f32, wz: f32,
|
|
561
|
+
object_layer: u32,
|
|
562
|
+
is_sensor: bool, allow_sleeping: bool, use_ccd: bool, start_awake: bool,
|
|
563
|
+
friction: f32, restitution: f32,
|
|
564
|
+
lin_damp: f32, ang_damp: f32, gravity_factor: f32,
|
|
565
|
+
mass_override: f32,
|
|
566
|
+
ix: f32, iy: f32, iz: f32,
|
|
567
|
+
user_data: u64,
|
|
568
|
+
) -> f64 {
|
|
569
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0.0 };
|
|
570
|
+
let shape = match self.shapes.get(shape_h) { Some(&s) => s, None => return 0.0 };
|
|
571
|
+
|
|
572
|
+
let desc = BjBodyDesc {
|
|
573
|
+
motion_type: match motion_type {
|
|
574
|
+
0 => BjMotionType::Static,
|
|
575
|
+
1 => BjMotionType::Kinematic,
|
|
576
|
+
_ => BjMotionType::Dynamic,
|
|
577
|
+
},
|
|
578
|
+
position: BjVec3 { x: px, y: py, z: pz },
|
|
579
|
+
rotation: BjQuat { x: rx, y: ry, z: rz, w: rw },
|
|
580
|
+
linear_velocity: BjVec3 { x: vx, y: vy, z: vz },
|
|
581
|
+
angular_velocity: BjVec3 { x: wx, y: wy, z: wz },
|
|
582
|
+
gravity_factor,
|
|
583
|
+
linear_damping: lin_damp,
|
|
584
|
+
angular_damping: ang_damp,
|
|
585
|
+
friction,
|
|
586
|
+
restitution,
|
|
587
|
+
mass_override,
|
|
588
|
+
inertia_diag_override: BjVec3 { x: ix, y: iy, z: iz },
|
|
589
|
+
object_layer,
|
|
590
|
+
is_sensor: is_sensor as u8,
|
|
591
|
+
allow_sleeping: allow_sleeping as u8,
|
|
592
|
+
use_ccd: use_ccd as u8,
|
|
593
|
+
start_awake: start_awake as u8,
|
|
594
|
+
user_data,
|
|
595
|
+
};
|
|
596
|
+
let body = unsafe { bj_body_create(world, shape, &desc) };
|
|
597
|
+
if body == BJ_INVALID { 0.0 } else { self.bodies.alloc((world, body)) }
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
pub fn destroy_body(&mut self, handle: f64) {
|
|
601
|
+
if let Some((world, body)) = self.bodies.free(handle) {
|
|
602
|
+
unsafe { bj_body_destroy(world, body); }
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fn resolve_body(&self, h: f64) -> Option<(bj_world, bj_body)> {
|
|
607
|
+
self.bodies.get(h).copied()
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
pub fn body_activate(&self, h: f64) {
|
|
611
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_activate(w, b); } }
|
|
612
|
+
}
|
|
613
|
+
pub fn body_deactivate(&self, h: f64) {
|
|
614
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_deactivate(w, b); } }
|
|
615
|
+
}
|
|
616
|
+
pub fn body_is_active(&self, h: f64) -> bool {
|
|
617
|
+
self.resolve_body(h).map(|(w, b)| unsafe { bj_body_is_active(w, b) } != 0).unwrap_or(false)
|
|
618
|
+
}
|
|
619
|
+
pub fn body_is_valid(&self, h: f64) -> bool {
|
|
620
|
+
self.resolve_body(h).map(|(w, b)| unsafe { bj_body_is_valid(w, b) } != 0).unwrap_or(false)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/// Position for rendering. When the world has interpolation enabled
|
|
624
|
+
/// (set_interpolation), blends between the last two simulated states
|
|
625
|
+
/// using the step_fixed remainder; otherwise the raw simulation state.
|
|
626
|
+
pub fn body_get_position_axis(&self, h: f64, axis: u32) -> f64 {
|
|
627
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
628
|
+
let mut v = BjVec3::default();
|
|
629
|
+
unsafe { bj_body_get_position(w, b, &mut v); }
|
|
630
|
+
if let Some((a, (pv, _))) = self.interp_prev(w, b) {
|
|
631
|
+
v = BjVec3 {
|
|
632
|
+
x: pv.x + (v.x - pv.x) * a,
|
|
633
|
+
y: pv.y + (v.y - pv.y) * a,
|
|
634
|
+
z: pv.z + (v.z - pv.z) * a,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
638
|
+
}
|
|
639
|
+
0.0
|
|
640
|
+
}
|
|
641
|
+
/// Rotation for rendering — see body_get_position_axis. Interpolation
|
|
642
|
+
/// is a normalized lerp with shortest-path sign handling (nlerp): for
|
|
643
|
+
/// the sub-17ms rotation deltas a 60 Hz step produces, nlerp is
|
|
644
|
+
/// visually identical to slerp and considerably cheaper.
|
|
645
|
+
pub fn body_get_rotation_axis(&self, h: f64, axis: u32) -> f64 {
|
|
646
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
647
|
+
let mut q = BjQuat::default();
|
|
648
|
+
unsafe { bj_body_get_rotation(w, b, &mut q); }
|
|
649
|
+
if let Some((a, (_, pq))) = self.interp_prev(w, b) {
|
|
650
|
+
// shortest path: flip the previous quat if the hemispheres differ
|
|
651
|
+
let dot = pq.x * q.x + pq.y * q.y + pq.z * q.z + pq.w * q.w;
|
|
652
|
+
let s = if dot < 0.0 { -1.0 } else { 1.0 };
|
|
653
|
+
let mut r = BjQuat {
|
|
654
|
+
x: pq.x * s + (q.x - pq.x * s) * a,
|
|
655
|
+
y: pq.y * s + (q.y - pq.y * s) * a,
|
|
656
|
+
z: pq.z * s + (q.z - pq.z * s) * a,
|
|
657
|
+
w: pq.w * s + (q.w - pq.w * s) * a,
|
|
658
|
+
};
|
|
659
|
+
let len = (r.x * r.x + r.y * r.y + r.z * r.z + r.w * r.w).sqrt();
|
|
660
|
+
if len > 1e-6 {
|
|
661
|
+
r = BjQuat { x: r.x / len, y: r.y / len, z: r.z / len, w: r.w / len };
|
|
662
|
+
}
|
|
663
|
+
q = r;
|
|
664
|
+
}
|
|
665
|
+
return match axis { 0 => q.x, 1 => q.y, 2 => q.z, 3 => q.w, _ => 0.0 } as f64;
|
|
666
|
+
}
|
|
667
|
+
0.0
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
pub fn body_set_position(&self, h: f64, x: f32, y: f32, z: f32, activate: bool) {
|
|
671
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
672
|
+
unsafe { bj_body_set_position(w, b, BjVec3 { x, y, z }, if activate { BjActivation::Activate } else { BjActivation::DontActivate }); }
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
pub fn body_set_rotation(&self, h: f64, x: f32, y: f32, z: f32, ww: f32, activate: bool) {
|
|
676
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
677
|
+
unsafe { bj_body_set_rotation(w, b, BjQuat { x, y, z, w: ww }, if activate { BjActivation::Activate } else { BjActivation::DontActivate }); }
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
#[allow(clippy::too_many_arguments)]
|
|
681
|
+
pub fn body_set_transform(&self, h: f64, px: f32, py: f32, pz: f32, rx: f32, ry: f32, rz: f32, rw: f32, activate: bool) {
|
|
682
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
683
|
+
let x = BjTransform { position: BjVec3 { x: px, y: py, z: pz }, rotation: BjQuat { x: rx, y: ry, z: rz, w: rw } };
|
|
684
|
+
unsafe { bj_body_set_transform(w, b, &x, if activate { BjActivation::Activate } else { BjActivation::DontActivate }); }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
#[allow(clippy::too_many_arguments)]
|
|
688
|
+
pub fn body_move_kinematic(&self, h: f64, px: f32, py: f32, pz: f32, rx: f32, ry: f32, rz: f32, rw: f32, dt: f32) {
|
|
689
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
690
|
+
let target = BjTransform { position: BjVec3 { x: px, y: py, z: pz }, rotation: BjQuat { x: rx, y: ry, z: rz, w: rw } };
|
|
691
|
+
unsafe { bj_body_move_kinematic(w, b, &target, dt); }
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
pub fn body_get_linear_velocity_axis(&self, h: f64, axis: u32) -> f64 {
|
|
696
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
697
|
+
let mut v = BjVec3::default();
|
|
698
|
+
unsafe { bj_body_get_linear_velocity(w, b, &mut v); }
|
|
699
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
700
|
+
}
|
|
701
|
+
0.0
|
|
702
|
+
}
|
|
703
|
+
pub fn body_get_angular_velocity_axis(&self, h: f64, axis: u32) -> f64 {
|
|
704
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
705
|
+
let mut v = BjVec3::default();
|
|
706
|
+
unsafe { bj_body_get_angular_velocity(w, b, &mut v); }
|
|
707
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
708
|
+
}
|
|
709
|
+
0.0
|
|
710
|
+
}
|
|
711
|
+
pub fn body_get_point_velocity_axis(&self, h: f64, px: f32, py: f32, pz: f32, axis: u32) -> f64 {
|
|
712
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
713
|
+
let mut v = BjVec3::default();
|
|
714
|
+
unsafe { bj_body_get_point_velocity(w, b, BjVec3 { x: px, y: py, z: pz }, &mut v); }
|
|
715
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
716
|
+
}
|
|
717
|
+
0.0
|
|
718
|
+
}
|
|
719
|
+
pub fn body_set_linear_velocity(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
720
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_linear_velocity(w, b, BjVec3 { x, y, z }); } }
|
|
721
|
+
}
|
|
722
|
+
pub fn body_set_angular_velocity(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
723
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_angular_velocity(w, b, BjVec3 { x, y, z }); } }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
pub fn body_add_force(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
727
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_add_force(w, b, BjVec3 { x, y, z }); } }
|
|
728
|
+
}
|
|
729
|
+
pub fn body_add_impulse(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
730
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_add_impulse(w, b, BjVec3 { x, y, z }); } }
|
|
731
|
+
}
|
|
732
|
+
pub fn body_add_torque(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
733
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_add_torque(w, b, BjVec3 { x, y, z }); } }
|
|
734
|
+
}
|
|
735
|
+
pub fn body_add_angular_impulse(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
736
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_add_angular_impulse(w, b, BjVec3 { x, y, z }); } }
|
|
737
|
+
}
|
|
738
|
+
#[allow(clippy::too_many_arguments)]
|
|
739
|
+
pub fn body_add_force_at(&self, h: f64, fx: f32, fy: f32, fz: f32, px: f32, py: f32, pz: f32) {
|
|
740
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
741
|
+
unsafe { bj_body_add_force_at(w, b, BjVec3 { x: fx, y: fy, z: fz }, BjVec3 { x: px, y: py, z: pz }); }
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
#[allow(clippy::too_many_arguments)]
|
|
745
|
+
pub fn body_add_impulse_at(&self, h: f64, ix: f32, iy: f32, iz: f32, px: f32, py: f32, pz: f32) {
|
|
746
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
747
|
+
unsafe { bj_body_add_impulse_at(w, b, BjVec3 { x: ix, y: iy, z: iz }, BjVec3 { x: px, y: py, z: pz }); }
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
pub fn body_set_friction(&self, h: f64, v: f32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_friction(w, b, v); } } }
|
|
752
|
+
pub fn body_set_restitution(&self, h: f64, v: f32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_restitution(w, b, v); } } }
|
|
753
|
+
pub fn body_set_linear_damping(&self, h: f64, v: f32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_linear_damping(w, b, v); } } }
|
|
754
|
+
pub fn body_set_angular_damping(&self, h: f64, v: f32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_angular_damping(w, b, v); } } }
|
|
755
|
+
pub fn body_set_gravity_factor(&self, h: f64, v: f32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_gravity_factor(w, b, v); } } }
|
|
756
|
+
pub fn body_set_ccd(&self, h: f64, enabled: bool) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_ccd(w, b, enabled as u8); } } }
|
|
757
|
+
pub fn body_set_motion_type(&self, h: f64, t: u32, activate: bool) {
|
|
758
|
+
if let Some((w, b)) = self.resolve_body(h) {
|
|
759
|
+
let mt = match t { 0 => BjMotionType::Static, 1 => BjMotionType::Kinematic, _ => BjMotionType::Dynamic };
|
|
760
|
+
unsafe { bj_body_set_motion_type(w, b, mt, if activate { BjActivation::Activate } else { BjActivation::DontActivate }); }
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
pub fn body_set_object_layer(&self, h: f64, layer: u32) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_object_layer(w, b, layer); } } }
|
|
764
|
+
pub fn body_set_is_sensor(&self, h: f64, enabled: bool) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_is_sensor(w, b, enabled as u8); } } }
|
|
765
|
+
pub fn body_set_allow_sleeping(&self, h: f64, enabled: bool) { if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_allow_sleeping(w, b, enabled as u8); } } }
|
|
766
|
+
pub fn body_set_shape(&self, h: f64, shape_h: f64, update_mass: bool, activate: bool) {
|
|
767
|
+
if let (Some((w, b)), Some(&s)) = (self.resolve_body(h), self.shapes.get(shape_h)) {
|
|
768
|
+
unsafe { bj_body_set_shape(w, b, s, update_mass as u8, if activate { BjActivation::Activate } else { BjActivation::DontActivate }); }
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
pub fn body_lock_rotation_axes(&self, h: f64, x: bool, y: bool, z: bool) {
|
|
772
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_lock_rotation_axes(w, b, x as u8, y as u8, z as u8); } }
|
|
773
|
+
}
|
|
774
|
+
pub fn body_lock_translation_axes(&self, h: f64, x: bool, y: bool, z: bool) {
|
|
775
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_lock_translation_axes(w, b, x as u8, y as u8, z as u8); } }
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
pub fn body_get_mass(&self, h: f64) -> f32 { self.resolve_body(h).map(|(w, b)| unsafe { bj_body_get_mass(w, b) }).unwrap_or(0.0) }
|
|
779
|
+
pub fn body_get_friction(&self, h: f64) -> f32 { self.resolve_body(h).map(|(w, b)| unsafe { bj_body_get_friction(w, b) }).unwrap_or(0.0) }
|
|
780
|
+
pub fn body_get_restitution(&self, h: f64) -> f32 { self.resolve_body(h).map(|(w, b)| unsafe { bj_body_get_restitution(w, b) }).unwrap_or(0.0) }
|
|
781
|
+
pub fn body_get_object_layer(&self, h: f64) -> u32 { self.resolve_body(h).map(|(w, b)| unsafe { bj_body_get_object_layer(w, b) }).unwrap_or(0) }
|
|
782
|
+
|
|
783
|
+
pub fn body_set_user_data(&self, h: f64, user_data: u64) {
|
|
784
|
+
if let Some((w, b)) = self.resolve_body(h) { unsafe { bj_body_set_user_data(w, b, user_data); } }
|
|
785
|
+
}
|
|
786
|
+
pub fn body_get_user_data(&self, h: f64) -> u64 {
|
|
787
|
+
self.resolve_body(h).map(|(w, b)| unsafe { bj_body_get_user_data(w, b) }).unwrap_or(0)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// -------------------------------------------------------- Queries
|
|
791
|
+
|
|
792
|
+
/// Single-hit raycast. Stores the hit (if any) at cache index 0.
|
|
793
|
+
#[allow(clippy::too_many_arguments)]
|
|
794
|
+
pub fn raycast_closest(
|
|
795
|
+
&mut self, world_h: f64,
|
|
796
|
+
ox: f32, oy: f32, oz: f32,
|
|
797
|
+
dx: f32, dy: f32, dz: f32,
|
|
798
|
+
max_distance: f32, layer_mask: u32,
|
|
799
|
+
) -> bool {
|
|
800
|
+
self.ray_hit_cache.hits.clear();
|
|
801
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return false };
|
|
802
|
+
let mut hit: BjRayHit = unsafe { std::mem::zeroed() };
|
|
803
|
+
let any = unsafe {
|
|
804
|
+
bj_query_raycast_closest(world,
|
|
805
|
+
BjVec3 { x: ox, y: oy, z: oz },
|
|
806
|
+
BjVec3 { x: dx, y: dy, z: dz },
|
|
807
|
+
max_distance, layer_mask, &mut hit)
|
|
808
|
+
};
|
|
809
|
+
if any != 0 {
|
|
810
|
+
self.ray_hit_cache.hits.push(hit);
|
|
811
|
+
true
|
|
812
|
+
} else { false }
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/// Multi-hit raycast. Stores up to max_hits in the cache.
|
|
816
|
+
#[allow(clippy::too_many_arguments)]
|
|
817
|
+
pub fn raycast_all(
|
|
818
|
+
&mut self, world_h: f64,
|
|
819
|
+
ox: f32, oy: f32, oz: f32,
|
|
820
|
+
dx: f32, dy: f32, dz: f32,
|
|
821
|
+
max_distance: f32, layer_mask: u32, max_hits: u32,
|
|
822
|
+
) -> u32 {
|
|
823
|
+
self.ray_hit_cache.hits.clear();
|
|
824
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0 };
|
|
825
|
+
self.ray_hit_cache.hits.resize(max_hits as usize, unsafe { std::mem::zeroed() });
|
|
826
|
+
let n = unsafe {
|
|
827
|
+
bj_query_raycast_all(world,
|
|
828
|
+
BjVec3 { x: ox, y: oy, z: oz },
|
|
829
|
+
BjVec3 { x: dx, y: dy, z: dz },
|
|
830
|
+
max_distance, layer_mask,
|
|
831
|
+
self.ray_hit_cache.hits.as_mut_ptr(), max_hits)
|
|
832
|
+
};
|
|
833
|
+
self.ray_hit_cache.hits.truncate(n as usize);
|
|
834
|
+
n
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
pub fn ray_hit_count(&self) -> u32 { self.ray_hit_cache.hits.len() as u32 }
|
|
838
|
+
pub fn ray_hit_body(&self, i: usize) -> f64 {
|
|
839
|
+
self.ray_hit_cache.hits.get(i)
|
|
840
|
+
.and_then(|h| self.bodies.iter().find(|(_, (_, b))| *b == h.body).map(|(hh, _)| hh))
|
|
841
|
+
.unwrap_or(0.0)
|
|
842
|
+
}
|
|
843
|
+
/// field: 0..5 = point.xyz, normal.xyz
|
|
844
|
+
pub fn ray_hit_axis(&self, i: usize, field: u32) -> f64 {
|
|
845
|
+
let h = match self.ray_hit_cache.hits.get(i) { Some(v) => v, None => return 0.0 };
|
|
846
|
+
(match field {
|
|
847
|
+
0 => h.point.x, 1 => h.point.y, 2 => h.point.z,
|
|
848
|
+
3 => h.normal.x, 4 => h.normal.y, 5 => h.normal.z,
|
|
849
|
+
_ => 0.0,
|
|
850
|
+
}) as f64
|
|
851
|
+
}
|
|
852
|
+
pub fn ray_hit_fraction(&self, i: usize) -> f32 {
|
|
853
|
+
self.ray_hit_cache.hits.get(i).map(|h| h.fraction).unwrap_or(0.0)
|
|
854
|
+
}
|
|
855
|
+
pub fn ray_hit_sub_shape(&self, i: usize) -> u32 {
|
|
856
|
+
self.ray_hit_cache.hits.get(i).map(|h| h.sub_shape_id).unwrap_or(0)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
pub fn overlap_sphere(&mut self, world_h: f64, cx: f32, cy: f32, cz: f32, r: f32, layer_mask: u32, max_results: u32) -> u32 {
|
|
860
|
+
self.overlap_cache.bodies.clear();
|
|
861
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0 };
|
|
862
|
+
self.overlap_cache.bodies.resize(max_results as usize, BJ_INVALID);
|
|
863
|
+
let n = unsafe {
|
|
864
|
+
bj_query_overlap_sphere(world,
|
|
865
|
+
BjVec3 { x: cx, y: cy, z: cz }, r, layer_mask,
|
|
866
|
+
self.overlap_cache.bodies.as_mut_ptr(), max_results)
|
|
867
|
+
};
|
|
868
|
+
self.overlap_cache.bodies.truncate(n as usize);
|
|
869
|
+
n
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
pub fn overlap_point(&mut self, world_h: f64, px: f32, py: f32, pz: f32, layer_mask: u32, max_results: u32) -> u32 {
|
|
873
|
+
self.overlap_cache.bodies.clear();
|
|
874
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0 };
|
|
875
|
+
self.overlap_cache.bodies.resize(max_results as usize, BJ_INVALID);
|
|
876
|
+
let n = unsafe {
|
|
877
|
+
bj_query_overlap_point(world,
|
|
878
|
+
BjVec3 { x: px, y: py, z: pz }, layer_mask,
|
|
879
|
+
self.overlap_cache.bodies.as_mut_ptr(), max_results)
|
|
880
|
+
};
|
|
881
|
+
self.overlap_cache.bodies.truncate(n as usize);
|
|
882
|
+
n
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
#[allow(clippy::too_many_arguments)]
|
|
886
|
+
pub fn overlap_box(
|
|
887
|
+
&mut self, world_h: f64,
|
|
888
|
+
px: f32, py: f32, pz: f32, rx: f32, ry: f32, rz: f32, rw: f32,
|
|
889
|
+
hx: f32, hy: f32, hz: f32,
|
|
890
|
+
layer_mask: u32, max_results: u32,
|
|
891
|
+
) -> u32 {
|
|
892
|
+
self.overlap_cache.bodies.clear();
|
|
893
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0 };
|
|
894
|
+
self.overlap_cache.bodies.resize(max_results as usize, BJ_INVALID);
|
|
895
|
+
let xform = BjTransform { position: BjVec3 { x: px, y: py, z: pz }, rotation: BjQuat { x: rx, y: ry, z: rz, w: rw } };
|
|
896
|
+
let n = unsafe {
|
|
897
|
+
bj_query_overlap_box(world, &xform, BjVec3 { x: hx, y: hy, z: hz }, layer_mask,
|
|
898
|
+
self.overlap_cache.bodies.as_mut_ptr(), max_results)
|
|
899
|
+
};
|
|
900
|
+
self.overlap_cache.bodies.truncate(n as usize);
|
|
901
|
+
n
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
pub fn overlap_body(&self, i: usize) -> f64 {
|
|
905
|
+
self.overlap_cache.bodies.get(i)
|
|
906
|
+
.and_then(|bj| self.bodies.iter().find(|(_, (_, b))| *b == *bj).map(|(h, _)| h))
|
|
907
|
+
.unwrap_or(0.0)
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// -------------------------------------------------------- Constraints
|
|
911
|
+
|
|
912
|
+
fn make_anchors(&self, body_a_h: f64, body_b_h: f64, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32, world_space: bool) -> Option<(bj_world, BjConstraintAnchors)> {
|
|
913
|
+
let (wa, ba) = self.resolve_body(body_a_h)?;
|
|
914
|
+
let bb = if body_b_h == 0.0 {
|
|
915
|
+
BJ_INVALID
|
|
916
|
+
} else {
|
|
917
|
+
let (wb, bb) = self.resolve_body(body_b_h)?;
|
|
918
|
+
if wb != wa { return None; }
|
|
919
|
+
bb
|
|
920
|
+
};
|
|
921
|
+
Some((wa, BjConstraintAnchors {
|
|
922
|
+
body_a: ba, body_b: bb,
|
|
923
|
+
anchor_a: BjVec3 { x: ax, y: ay, z: az },
|
|
924
|
+
anchor_b: BjVec3 { x: bx, y: by, z: bz },
|
|
925
|
+
use_world_space: world_space as u8,
|
|
926
|
+
}))
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#[allow(clippy::too_many_arguments)]
|
|
930
|
+
pub fn constraint_fixed(&mut self, body_a: f64, body_b: f64, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32, world_space: bool) -> f64 {
|
|
931
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) { Some(v) => v, None => return 0.0 };
|
|
932
|
+
let c = unsafe { bj_constraint_fixed(w, &anchors) };
|
|
933
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
#[allow(clippy::too_many_arguments)]
|
|
937
|
+
pub fn constraint_point(&mut self, body_a: f64, body_b: f64, ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32, world_space: bool) -> f64 {
|
|
938
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) { Some(v) => v, None => return 0.0 };
|
|
939
|
+
let c = unsafe { bj_constraint_point(w, &anchors) };
|
|
940
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/// EN-025 — six-DOF, which is what a ragdoll joint actually is: translation
|
|
944
|
+
/// locked (a shoulder does not slide off the torso) and rotation limited (an
|
|
945
|
+
/// elbow does not bend backwards). An unlimited ball joint is cheaper and
|
|
946
|
+
/// gives you spaghetti; the limits are the whole difference between a corpse
|
|
947
|
+
/// and a novelty.
|
|
948
|
+
///
|
|
949
|
+
/// `rot_limits` is [xmin, xmax, ymin, ymax, zmin, zmax] in radians.
|
|
950
|
+
#[allow(clippy::too_many_arguments)]
|
|
951
|
+
pub fn constraint_six_dof_locked_translation(
|
|
952
|
+
&mut self, body_a: f64, body_b: f64,
|
|
953
|
+
ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32,
|
|
954
|
+
rot_limits: [f32; 6], world_space: bool,
|
|
955
|
+
) -> f64 {
|
|
956
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) {
|
|
957
|
+
Some(v) => v, None => return 0.0,
|
|
958
|
+
};
|
|
959
|
+
// min >= max means LOCKED (see the shim header), so this pins all three
|
|
960
|
+
// translation axes.
|
|
961
|
+
let trans: [f32; 6] = [1.0, -1.0, 1.0, -1.0, 1.0, -1.0];
|
|
962
|
+
let c = unsafe {
|
|
963
|
+
bj_constraint_six_dof(w, &anchors, trans.as_ptr(), rot_limits.as_ptr())
|
|
964
|
+
};
|
|
965
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/// Body world transform as (position, quaternion) — the ragdoll needs the
|
|
969
|
+
/// full frame, and the existing getters hand back one axis at a time.
|
|
970
|
+
pub fn body_transform(&self, h: f64) -> Option<([f32; 3], [f32; 4])> {
|
|
971
|
+
let p = [
|
|
972
|
+
self.body_get_position_axis(h, 0) as f32,
|
|
973
|
+
self.body_get_position_axis(h, 1) as f32,
|
|
974
|
+
self.body_get_position_axis(h, 2) as f32,
|
|
975
|
+
];
|
|
976
|
+
let q = [
|
|
977
|
+
self.body_get_rotation_axis(h, 0) as f32,
|
|
978
|
+
self.body_get_rotation_axis(h, 1) as f32,
|
|
979
|
+
self.body_get_rotation_axis(h, 2) as f32,
|
|
980
|
+
self.body_get_rotation_axis(h, 3) as f32,
|
|
981
|
+
];
|
|
982
|
+
Some((p, q))
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
#[allow(clippy::too_many_arguments)]
|
|
986
|
+
pub fn constraint_hinge(
|
|
987
|
+
&mut self, body_a: f64, body_b: f64,
|
|
988
|
+
ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32,
|
|
989
|
+
axis_x: f32, axis_y: f32, axis_z: f32,
|
|
990
|
+
limit_min: f32, limit_max: f32, world_space: bool,
|
|
991
|
+
) -> f64 {
|
|
992
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) { Some(v) => v, None => return 0.0 };
|
|
993
|
+
let c = unsafe {
|
|
994
|
+
bj_constraint_hinge(w, &anchors, BjVec3 { x: axis_x, y: axis_y, z: axis_z }, limit_min, limit_max)
|
|
995
|
+
};
|
|
996
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
#[allow(clippy::too_many_arguments)]
|
|
1000
|
+
pub fn constraint_slider(
|
|
1001
|
+
&mut self, body_a: f64, body_b: f64,
|
|
1002
|
+
ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32,
|
|
1003
|
+
axis_x: f32, axis_y: f32, axis_z: f32,
|
|
1004
|
+
limit_min: f32, limit_max: f32, world_space: bool,
|
|
1005
|
+
) -> f64 {
|
|
1006
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) { Some(v) => v, None => return 0.0 };
|
|
1007
|
+
let c = unsafe {
|
|
1008
|
+
bj_constraint_slider(w, &anchors, BjVec3 { x: axis_x, y: axis_y, z: axis_z }, limit_min, limit_max)
|
|
1009
|
+
};
|
|
1010
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
#[allow(clippy::too_many_arguments)]
|
|
1014
|
+
pub fn constraint_distance(
|
|
1015
|
+
&mut self, body_a: f64, body_b: f64,
|
|
1016
|
+
ax: f32, ay: f32, az: f32, bx: f32, by: f32, bz: f32,
|
|
1017
|
+
min_distance: f32, max_distance: f32, world_space: bool,
|
|
1018
|
+
) -> f64 {
|
|
1019
|
+
let (w, anchors) = match self.make_anchors(body_a, body_b, ax, ay, az, bx, by, bz, world_space) { Some(v) => v, None => return 0.0 };
|
|
1020
|
+
let c = unsafe { bj_constraint_distance(w, &anchors, min_distance, max_distance) };
|
|
1021
|
+
if c == BJ_INVALID { 0.0 } else { self.constraints.alloc((w, c)) }
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
pub fn constraint_destroy(&mut self, handle: f64) {
|
|
1025
|
+
if let Some((w, c)) = self.constraints.free(handle) {
|
|
1026
|
+
unsafe { bj_constraint_destroy(w, c); }
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
pub fn constraint_set_enabled(&self, handle: f64, enabled: bool) {
|
|
1031
|
+
if let Some(&(w, c)) = self.constraints.get(handle) {
|
|
1032
|
+
unsafe { bj_constraint_set_enabled(w, c, enabled as u8); }
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// -------------------------------------------------------- Contacts
|
|
1037
|
+
|
|
1038
|
+
pub fn contact_count(&self) -> u32 { self.contact_cache.events.len() as u32 }
|
|
1039
|
+
|
|
1040
|
+
/// field: 0=event, 1=bodyA, 2=bodyB, 3..5=pointA.xyz, 6..8=pointB.xyz,
|
|
1041
|
+
/// 9..11=normal.xyz, 12=depth, 13=friction, 14=restitution
|
|
1042
|
+
pub fn contact_field(&self, i: usize, field: u32) -> f64 {
|
|
1043
|
+
let c = match self.contact_cache.events.get(i) { Some(v) => v, None => return 0.0 };
|
|
1044
|
+
let bj_to_bloom = |bj: bj_body| -> f64 {
|
|
1045
|
+
self.bodies.iter().find(|(_, (_, b))| *b == bj).map(|(h, _)| h).unwrap_or(0.0)
|
|
1046
|
+
};
|
|
1047
|
+
match field {
|
|
1048
|
+
0 => c.event as u32 as f64,
|
|
1049
|
+
1 => bj_to_bloom(c.body_a),
|
|
1050
|
+
2 => bj_to_bloom(c.body_b),
|
|
1051
|
+
3 => c.point_a.x as f64, 4 => c.point_a.y as f64, 5 => c.point_a.z as f64,
|
|
1052
|
+
6 => c.point_b.x as f64, 7 => c.point_b.y as f64, 8 => c.point_b.z as f64,
|
|
1053
|
+
9 => c.normal.x as f64, 10=> c.normal.y as f64, 11=> c.normal.z as f64,
|
|
1054
|
+
12 => c.penetration_depth as f64,
|
|
1055
|
+
13 => c.combined_friction as f64,
|
|
1056
|
+
14 => c.combined_restitution as f64,
|
|
1057
|
+
_ => 0.0,
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
pub fn clear_contacts(&mut self) { self.contact_cache.events.clear(); }
|
|
1062
|
+
|
|
1063
|
+
// -------------------------------------------------------- Character controller
|
|
1064
|
+
|
|
1065
|
+
#[allow(clippy::too_many_arguments)]
|
|
1066
|
+
pub fn character_create(
|
|
1067
|
+
&mut self, world_h: f64, shape_h: f64,
|
|
1068
|
+
up_x: f32, up_y: f32, up_z: f32,
|
|
1069
|
+
max_slope_angle: f32, character_padding: f32,
|
|
1070
|
+
penetration_recovery_speed: f32, predictive_contact_distance: f32,
|
|
1071
|
+
max_strength: f32, mass: f32, object_layer: u32,
|
|
1072
|
+
px: f32, py: f32, pz: f32,
|
|
1073
|
+
rx: f32, ry: f32, rz: f32, rw: f32,
|
|
1074
|
+
) -> f64 {
|
|
1075
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0.0 };
|
|
1076
|
+
let shape = match self.shapes.get(shape_h) { Some(&s) => s, None => return 0.0 };
|
|
1077
|
+
let desc = BjCharacterDesc {
|
|
1078
|
+
up: BjVec3 { x: up_x, y: up_y, z: up_z },
|
|
1079
|
+
max_slope_angle, character_padding, penetration_recovery_speed,
|
|
1080
|
+
predictive_contact_distance, max_strength, mass, object_layer,
|
|
1081
|
+
};
|
|
1082
|
+
let c = unsafe {
|
|
1083
|
+
bj_character_create(world, shape, &desc,
|
|
1084
|
+
BjVec3 { x: px, y: py, z: pz },
|
|
1085
|
+
BjQuat { x: rx, y: ry, z: rz, w: rw })
|
|
1086
|
+
};
|
|
1087
|
+
if c == BJ_INVALID { 0.0 } else { self.characters.alloc((world, c)) }
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
pub fn character_destroy(&mut self, handle: f64) {
|
|
1091
|
+
if let Some((w, c)) = self.characters.free(handle) {
|
|
1092
|
+
unsafe { bj_character_destroy(w, c); }
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
fn resolve_character(&self, h: f64) -> Option<(bj_world, bj_character)> {
|
|
1097
|
+
self.characters.get(h).copied()
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
pub fn character_update(&self, h: f64, dt: f32, gx: f32, gy: f32, gz: f32) {
|
|
1101
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1102
|
+
unsafe { bj_character_update(w, c, dt, BjVec3 { x: gx, y: gy, z: gz }); }
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
pub fn character_get_position_axis(&self, h: f64, axis: u32) -> f64 {
|
|
1107
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1108
|
+
let mut v = BjVec3::default();
|
|
1109
|
+
unsafe { bj_character_get_position(w, c, &mut v); }
|
|
1110
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
1111
|
+
}
|
|
1112
|
+
0.0
|
|
1113
|
+
}
|
|
1114
|
+
pub fn character_get_rotation_axis(&self, h: f64, axis: u32) -> f64 {
|
|
1115
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1116
|
+
let mut q = BjQuat::default();
|
|
1117
|
+
unsafe { bj_character_get_rotation(w, c, &mut q); }
|
|
1118
|
+
return match axis { 0 => q.x, 1 => q.y, 2 => q.z, 3 => q.w, _ => 0.0 } as f64;
|
|
1119
|
+
}
|
|
1120
|
+
0.0
|
|
1121
|
+
}
|
|
1122
|
+
pub fn character_set_position(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
1123
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1124
|
+
unsafe { bj_character_set_position(w, c, BjVec3 { x, y, z }); }
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
pub fn character_set_rotation(&self, h: f64, x: f32, y: f32, z: f32, ww: f32) {
|
|
1128
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1129
|
+
unsafe { bj_character_set_rotation(w, c, BjQuat { x, y, z, w: ww }); }
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
pub fn character_get_linear_velocity_axis(&self, h: f64, axis: u32) -> f64 {
|
|
1133
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1134
|
+
let mut v = BjVec3::default();
|
|
1135
|
+
unsafe { bj_character_get_linear_velocity(w, c, &mut v); }
|
|
1136
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
1137
|
+
}
|
|
1138
|
+
0.0
|
|
1139
|
+
}
|
|
1140
|
+
pub fn character_set_linear_velocity(&self, h: f64, x: f32, y: f32, z: f32) {
|
|
1141
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1142
|
+
unsafe { bj_character_set_linear_velocity(w, c, BjVec3 { x, y, z }); }
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
pub fn character_get_ground_state(&self, h: f64) -> u32 {
|
|
1147
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1148
|
+
return unsafe { bj_character_get_ground_state(w, c) } as u32;
|
|
1149
|
+
}
|
|
1150
|
+
BjGroundState::InAir as u32
|
|
1151
|
+
}
|
|
1152
|
+
pub fn character_get_ground_normal_axis(&self, h: f64, axis: u32) -> f64 {
|
|
1153
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1154
|
+
let mut v = BjVec3::default();
|
|
1155
|
+
unsafe { bj_character_get_ground_normal(w, c, &mut v); }
|
|
1156
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
1157
|
+
}
|
|
1158
|
+
0.0
|
|
1159
|
+
}
|
|
1160
|
+
pub fn character_get_ground_position_axis(&self, h: f64, axis: u32) -> f64 {
|
|
1161
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1162
|
+
let mut v = BjVec3::default();
|
|
1163
|
+
unsafe { bj_character_get_ground_position(w, c, &mut v); }
|
|
1164
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
1165
|
+
}
|
|
1166
|
+
0.0
|
|
1167
|
+
}
|
|
1168
|
+
pub fn character_get_ground_body(&self, h: f64) -> f64 {
|
|
1169
|
+
if let Some((w, c)) = self.resolve_character(h) {
|
|
1170
|
+
let bj = unsafe { bj_character_get_ground_body(w, c) };
|
|
1171
|
+
if bj == BJ_INVALID { return 0.0; }
|
|
1172
|
+
return self.bodies.iter()
|
|
1173
|
+
.find(|(_, (_, b))| *b == bj)
|
|
1174
|
+
.map(|(h, _)| h)
|
|
1175
|
+
.unwrap_or(0.0);
|
|
1176
|
+
}
|
|
1177
|
+
0.0
|
|
1178
|
+
}
|
|
1179
|
+
pub fn character_set_shape(&self, h: f64, shape_h: f64) {
|
|
1180
|
+
if let (Some((w, c)), Some(&s)) = (self.resolve_character(h), self.shapes.get(shape_h)) {
|
|
1181
|
+
unsafe { bj_character_set_shape(w, c, s); }
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// -------------------------------------------------------- Soft bodies
|
|
1186
|
+
|
|
1187
|
+
/// Create a soft body from scratch-buffered vertex+index streams.
|
|
1188
|
+
/// scratch_f32 layout: vertex_count * 4 floats (x, y, z, invMass per vertex).
|
|
1189
|
+
/// scratch_u32 layout: triangle_count * 3 indices.
|
|
1190
|
+
/// inv_mass == 0 pins the vertex.
|
|
1191
|
+
#[allow(clippy::too_many_arguments)]
|
|
1192
|
+
pub fn soft_body_create_from_scratch(
|
|
1193
|
+
&mut self, world_h: f64,
|
|
1194
|
+
vertex_count: u32, triangle_count: u32,
|
|
1195
|
+
px: f32, py: f32, pz: f32, rx: f32, ry: f32, rz: f32, rw: f32,
|
|
1196
|
+
object_layer: u32,
|
|
1197
|
+
edge_compliance: f32, gravity_factor: f32, linear_damping: f32, pressure: f32,
|
|
1198
|
+
) -> f64 {
|
|
1199
|
+
let need_f = (vertex_count * 4) as usize;
|
|
1200
|
+
let need_u = (triangle_count * 3) as usize;
|
|
1201
|
+
if self.scratch_f32.len() < need_f || self.scratch_u32.len() < need_u {
|
|
1202
|
+
return 0.0;
|
|
1203
|
+
}
|
|
1204
|
+
if vertex_count < 3 || triangle_count == 0 { return 0.0; }
|
|
1205
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return 0.0 };
|
|
1206
|
+
|
|
1207
|
+
let body = unsafe {
|
|
1208
|
+
bj_soft_body_create(
|
|
1209
|
+
world,
|
|
1210
|
+
self.scratch_f32.as_ptr(), vertex_count,
|
|
1211
|
+
self.scratch_u32.as_ptr(), triangle_count,
|
|
1212
|
+
BjVec3 { x: px, y: py, z: pz },
|
|
1213
|
+
BjQuat { x: rx, y: ry, z: rz, w: rw },
|
|
1214
|
+
object_layer, edge_compliance, gravity_factor, linear_damping, pressure,
|
|
1215
|
+
)
|
|
1216
|
+
};
|
|
1217
|
+
if body == BJ_INVALID { 0.0 } else { self.bodies.alloc((world, body)) }
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
pub fn soft_body_vertex_count(&self, body_h: f64) -> u32 {
|
|
1221
|
+
self.resolve_body(body_h)
|
|
1222
|
+
.map(|(w, b)| unsafe { bj_soft_body_vertex_count(w, b) })
|
|
1223
|
+
.unwrap_or(0)
|
|
1224
|
+
}
|
|
1225
|
+
pub fn soft_body_get_vertex_axis(&self, body_h: f64, idx: u32, axis: u32) -> f64 {
|
|
1226
|
+
if let Some((w, b)) = self.resolve_body(body_h) {
|
|
1227
|
+
let mut v = BjVec3::default();
|
|
1228
|
+
unsafe { bj_soft_body_get_vertex(w, b, idx, &mut v); }
|
|
1229
|
+
return match axis { 0 => v.x, 1 => v.y, 2 => v.z, _ => 0.0 } as f64;
|
|
1230
|
+
}
|
|
1231
|
+
0.0
|
|
1232
|
+
}
|
|
1233
|
+
pub fn soft_body_set_vertex(&self, body_h: f64, idx: u32, x: f32, y: f32, z: f32) {
|
|
1234
|
+
if let Some((w, b)) = self.resolve_body(body_h) {
|
|
1235
|
+
unsafe { bj_soft_body_set_vertex(w, b, idx, BjVec3 { x, y, z }); }
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
pub fn soft_body_set_vertex_inv_mass(&self, body_h: f64, idx: u32, inv_mass: f32) {
|
|
1239
|
+
if let Some((w, b)) = self.resolve_body(body_h) {
|
|
1240
|
+
unsafe { bj_soft_body_set_vertex_inv_mass(w, b, idx, inv_mass); }
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// -------------------------------------------------------- Wheeled vehicles
|
|
1245
|
+
|
|
1246
|
+
#[allow(clippy::too_many_arguments)]
|
|
1247
|
+
pub fn vehicle_create(
|
|
1248
|
+
&mut self, world_h: f64, chassis_shape_h: f64,
|
|
1249
|
+
// Axes
|
|
1250
|
+
up_x: f32, up_y: f32, up_z: f32,
|
|
1251
|
+
fw_x: f32, fw_y: f32, fw_z: f32,
|
|
1252
|
+
// 4 wheel positions (flat array of 12 f32s: fl.xyz, fr.xyz, rl.xyz, rr.xyz)
|
|
1253
|
+
w0x: f32, w0y: f32, w0z: f32,
|
|
1254
|
+
w1x: f32, w1y: f32, w1z: f32,
|
|
1255
|
+
w2x: f32, w2y: f32, w2z: f32,
|
|
1256
|
+
w3x: f32, w3y: f32, w3z: f32,
|
|
1257
|
+
// Shared wheel + engine parameters
|
|
1258
|
+
wheel_radius: f32, wheel_width: f32,
|
|
1259
|
+
suspension_min: f32, suspension_max: f32,
|
|
1260
|
+
max_steer_angle: f32, max_brake_torque: f32, max_handbrake_torque: f32,
|
|
1261
|
+
engine_max_torque: f32, max_pitch_roll_angle: f32,
|
|
1262
|
+
object_layer: u32,
|
|
1263
|
+
// Pose
|
|
1264
|
+
px: f32, py: f32, pz: f32, rx: f32, ry: f32, rz: f32, rw: f32,
|
|
1265
|
+
) -> (f64, f64) {
|
|
1266
|
+
// Returns (vehicle_handle, chassis_body_handle).
|
|
1267
|
+
let world = match self.worlds.get(world_h) { Some(&w) => w, None => return (0.0, 0.0) };
|
|
1268
|
+
let shape = match self.shapes.get(chassis_shape_h) { Some(&s) => s, None => return (0.0, 0.0) };
|
|
1269
|
+
|
|
1270
|
+
let desc = BjVehicleDesc {
|
|
1271
|
+
up: BjVec3 { x: up_x, y: up_y, z: up_z },
|
|
1272
|
+
forward: BjVec3 { x: fw_x, y: fw_y, z: fw_z },
|
|
1273
|
+
wheel_positions: [
|
|
1274
|
+
BjVec3 { x: w0x, y: w0y, z: w0z },
|
|
1275
|
+
BjVec3 { x: w1x, y: w1y, z: w1z },
|
|
1276
|
+
BjVec3 { x: w2x, y: w2y, z: w2z },
|
|
1277
|
+
BjVec3 { x: w3x, y: w3y, z: w3z },
|
|
1278
|
+
],
|
|
1279
|
+
wheel_radius, wheel_width,
|
|
1280
|
+
suspension_min_length: suspension_min,
|
|
1281
|
+
suspension_max_length: suspension_max,
|
|
1282
|
+
max_steer_angle, max_brake_torque, max_handbrake_torque,
|
|
1283
|
+
engine_max_torque, max_pitch_roll_angle, object_layer,
|
|
1284
|
+
};
|
|
1285
|
+
|
|
1286
|
+
let vehicle = unsafe {
|
|
1287
|
+
bj_vehicle_create(world, shape, &desc,
|
|
1288
|
+
BjVec3 { x: px, y: py, z: pz },
|
|
1289
|
+
BjQuat { x: rx, y: ry, z: rz, w: rw })
|
|
1290
|
+
};
|
|
1291
|
+
if vehicle == BJ_INVALID { return (0.0, 0.0); }
|
|
1292
|
+
|
|
1293
|
+
// Register the chassis as a regular body so TS can query its transform
|
|
1294
|
+
// via normal body APIs.
|
|
1295
|
+
let chassis_bj = unsafe { bj_vehicle_get_chassis(world, vehicle) };
|
|
1296
|
+
let chassis_h = self.bodies.alloc((world, chassis_bj));
|
|
1297
|
+
let vh = self.vehicles.alloc((world, vehicle, chassis_h));
|
|
1298
|
+
(vh, chassis_h)
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
pub fn vehicle_destroy(&mut self, handle: f64) {
|
|
1302
|
+
if let Some((w, v, chassis_h)) = self.vehicles.free(handle) {
|
|
1303
|
+
unsafe { bj_vehicle_destroy(w, v); }
|
|
1304
|
+
// Chassis was removed by vehicle_destroy; free its Rust handle too.
|
|
1305
|
+
let _ = self.bodies.free(chassis_h);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
pub fn vehicle_get_chassis(&self, handle: f64) -> f64 {
|
|
1310
|
+
self.vehicles.get(handle).map(|&(_, _, h)| h).unwrap_or(0.0)
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
fn resolve_vehicle(&self, h: f64) -> Option<(bj_world, bj_vehicle)> {
|
|
1314
|
+
self.vehicles.get(h).map(|&(w, v, _)| (w, v))
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
pub fn vehicle_set_input(&self, handle: f64, forward: f32, right: f32, brake: f32, handbrake: f32) {
|
|
1318
|
+
if let Some((w, v)) = self.resolve_vehicle(handle) {
|
|
1319
|
+
unsafe { bj_vehicle_set_input(w, v, forward, right, brake, handbrake); }
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
pub fn vehicle_get_wheel_transform(&self, handle: f64, wheel_idx: u32, axis: u32) -> f32 {
|
|
1324
|
+
self.resolve_vehicle(handle)
|
|
1325
|
+
.map(|(w, v)| unsafe { bj_vehicle_get_wheel_transform(w, v, wheel_idx, axis) })
|
|
1326
|
+
.unwrap_or(0.0)
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
pub fn vehicle_get_engine_rpm(&self, handle: f64) -> f32 {
|
|
1330
|
+
self.resolve_vehicle(handle)
|
|
1331
|
+
.map(|(w, v)| unsafe { bj_vehicle_get_engine_rpm(w, v) })
|
|
1332
|
+
.unwrap_or(0.0)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
pub fn vehicle_get_wheel_angular_velocity(&self, handle: f64, wheel_idx: u32) -> f32 {
|
|
1336
|
+
self.resolve_vehicle(handle)
|
|
1337
|
+
.map(|(w, v)| unsafe { bj_vehicle_get_wheel_angular_velocity(w, v, wheel_idx) })
|
|
1338
|
+
.unwrap_or(0.0)
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// ============================================================================
|
|
1343
|
+
// FFI macro — generates the bloom_physics_* symbol set in the invoking crate.
|
|
1344
|
+
//
|
|
1345
|
+
// Usage (in a platform crate, e.g. native/macos/src/lib.rs):
|
|
1346
|
+
//
|
|
1347
|
+
// #[inline]
|
|
1348
|
+
// fn bloom_jolt_ffi_physics() -> impl std::ops::DerefMut<Target = bloom_shared::physics_jolt::JoltPhysics> {
|
|
1349
|
+
// engine_jolt_guard()
|
|
1350
|
+
// }
|
|
1351
|
+
// bloom_shared::define_physics_ffi!();
|
|
1352
|
+
//
|
|
1353
|
+
// The macro emits #[no_mangle] extern "C" functions, so each platform crate's
|
|
1354
|
+
// staticlib/cdylib independently re-exports the full Perry FFI surface.
|
|
1355
|
+
// ============================================================================
|
|
1356
|
+
|
|
1357
|
+
#[macro_export]
|
|
1358
|
+
macro_rules! define_physics_ffi {
|
|
1359
|
+
() => {
|
|
1360
|
+
// --- World ---
|
|
1361
|
+
|
|
1362
|
+
#[no_mangle] pub extern "C" fn bloom_physics_create_world(gx: f64, gy: f64, gz: f64, max_bodies: f64, num_threads: f64) -> f64 {
|
|
1363
|
+
bloom_jolt_ffi_physics().create_world(gx as f32, gy as f32, gz as f32, max_bodies as u32, num_threads as u32)
|
|
1364
|
+
}
|
|
1365
|
+
#[no_mangle] pub extern "C" fn bloom_physics_destroy_world(world: f64) { bloom_jolt_ffi_physics().destroy_world(world); }
|
|
1366
|
+
#[no_mangle] pub extern "C" fn bloom_physics_set_gravity(world: f64, gx: f64, gy: f64, gz: f64) {
|
|
1367
|
+
bloom_jolt_ffi_physics().set_gravity(world, gx as f32, gy as f32, gz as f32);
|
|
1368
|
+
}
|
|
1369
|
+
#[no_mangle] pub extern "C" fn bloom_physics_get_gravity(world: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().get_gravity_axis(world, axis as u32) }
|
|
1370
|
+
#[no_mangle] pub extern "C" fn bloom_physics_optimize_broadphase(world: f64) { bloom_jolt_ffi_physics().optimize_broadphase(world); }
|
|
1371
|
+
#[no_mangle] pub extern "C" fn bloom_physics_step(world: f64, dt: f64, collision_steps: f64) {
|
|
1372
|
+
bloom_jolt_ffi_physics().step(world, dt as f32, collision_steps as u32);
|
|
1373
|
+
}
|
|
1374
|
+
#[no_mangle] pub extern "C" fn bloom_physics_step_fixed(world: f64, dt: f64, collision_steps: f64) -> f64 {
|
|
1375
|
+
bloom_jolt_ffi_physics().step_fixed(world, dt as f32, collision_steps as u32) as f64
|
|
1376
|
+
}
|
|
1377
|
+
#[no_mangle] pub extern "C" fn bloom_physics_set_fixed_timestep(world: f64, hz: f64, max_steps: f64) {
|
|
1378
|
+
bloom_jolt_ffi_physics().set_fixed_timestep(world, hz as f32, max_steps as u32);
|
|
1379
|
+
}
|
|
1380
|
+
#[no_mangle] pub extern "C" fn bloom_physics_set_interpolation(world: f64, on: f64) {
|
|
1381
|
+
bloom_jolt_ffi_physics().set_interpolation(world, on != 0.0);
|
|
1382
|
+
}
|
|
1383
|
+
#[no_mangle] pub extern "C" fn bloom_physics_get_step_alpha(world: f64) -> f64 {
|
|
1384
|
+
bloom_jolt_ffi_physics().step_alpha(world) as f64
|
|
1385
|
+
}
|
|
1386
|
+
#[no_mangle] pub extern "C" fn bloom_physics_set_layer_collides(world: f64, a: f64, b: f64, collides: f64) {
|
|
1387
|
+
bloom_jolt_ffi_physics().set_layer_collides(world, a as u32, b as u32, collides != 0.0);
|
|
1388
|
+
}
|
|
1389
|
+
#[no_mangle] pub extern "C" fn bloom_physics_get_layer_collides(world: f64, a: f64, b: f64) -> f64 {
|
|
1390
|
+
if bloom_jolt_ffi_physics().get_layer_collides(world, a as u32, b as u32) { 1.0 } else { 0.0 }
|
|
1391
|
+
}
|
|
1392
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_count(world: f64) -> f64 { bloom_jolt_ffi_physics().body_count(world) as f64 }
|
|
1393
|
+
#[no_mangle] pub extern "C" fn bloom_physics_active_body_count(world: f64) -> f64 { bloom_jolt_ffi_physics().active_body_count(world) as f64 }
|
|
1394
|
+
|
|
1395
|
+
// --- Shapes ---
|
|
1396
|
+
|
|
1397
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_box(hx: f64, hy: f64, hz: f64, convex_radius: f64) -> f64 {
|
|
1398
|
+
bloom_jolt_ffi_physics().create_box_shape(hx as f32, hy as f32, hz as f32, convex_radius as f32)
|
|
1399
|
+
}
|
|
1400
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_sphere(r: f64) -> f64 { bloom_jolt_ffi_physics().create_sphere_shape(r as f32) }
|
|
1401
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_capsule(h: f64, r: f64) -> f64 { bloom_jolt_ffi_physics().create_capsule_shape(h as f32, r as f32) }
|
|
1402
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_cylinder(h: f64, r: f64, cr: f64) -> f64 {
|
|
1403
|
+
bloom_jolt_ffi_physics().create_cylinder_shape(h as f32, r as f32, cr as f32)
|
|
1404
|
+
}
|
|
1405
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_scaled(base: f64, sx: f64, sy: f64, sz: f64) -> f64 {
|
|
1406
|
+
bloom_jolt_ffi_physics().create_scaled_shape(base, sx as f32, sy as f32, sz as f32)
|
|
1407
|
+
}
|
|
1408
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_offset_com(base: f64, ox: f64, oy: f64, oz: f64) -> f64 {
|
|
1409
|
+
bloom_jolt_ffi_physics().create_offset_com_shape(base, ox as f32, oy as f32, oz as f32)
|
|
1410
|
+
}
|
|
1411
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_release(shape: f64) { bloom_jolt_ffi_physics().release_shape(shape); }
|
|
1412
|
+
|
|
1413
|
+
// Scratch streams for variable-size shape inputs.
|
|
1414
|
+
#[no_mangle] pub extern "C" fn bloom_physics_scratch_reset() { bloom_jolt_ffi_physics().scratch_reset(); }
|
|
1415
|
+
#[no_mangle] pub extern "C" fn bloom_physics_scratch_push_f32(v: f64) { bloom_jolt_ffi_physics().scratch_push_f32(v as f32); }
|
|
1416
|
+
#[no_mangle] pub extern "C" fn bloom_physics_scratch_push_u32(v: f64) { bloom_jolt_ffi_physics().scratch_push_u32(v as u32); }
|
|
1417
|
+
|
|
1418
|
+
// Complex shape factories — consume scratch streams populated by the caller.
|
|
1419
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_convex_hull(num_points: f64, convex_radius: f64) -> f64 {
|
|
1420
|
+
bloom_jolt_ffi_physics().shape_convex_hull_from_scratch(num_points as u32, convex_radius as f32)
|
|
1421
|
+
}
|
|
1422
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_mesh(vertex_count: f64, triangle_count: f64) -> f64 {
|
|
1423
|
+
bloom_jolt_ffi_physics().shape_mesh_from_scratch(vertex_count as u32, triangle_count as u32)
|
|
1424
|
+
}
|
|
1425
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_heightfield(
|
|
1426
|
+
sample_count: f64, ox: f64, oy: f64, oz: f64, sx: f64, sy: f64, sz: f64, block_size: f64,
|
|
1427
|
+
) -> f64 {
|
|
1428
|
+
bloom_jolt_ffi_physics().shape_heightfield_from_scratch(
|
|
1429
|
+
sample_count as u32, ox as f32, oy as f32, oz as f32,
|
|
1430
|
+
sx as f32, sy as f32, sz as f32, block_size as u32,
|
|
1431
|
+
)
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// Compound shape builder — begin / add_child (repeat) / end.
|
|
1435
|
+
#[no_mangle] pub extern "C" fn bloom_physics_compound_begin() { bloom_jolt_ffi_physics().compound_begin(); }
|
|
1436
|
+
#[no_mangle] pub extern "C" fn bloom_physics_compound_add_child(
|
|
1437
|
+
shape: f64, px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1438
|
+
) {
|
|
1439
|
+
bloom_jolt_ffi_physics().compound_add_child(
|
|
1440
|
+
shape,
|
|
1441
|
+
px as f32, py as f32, pz as f32,
|
|
1442
|
+
rx as f32, ry as f32, rz as f32, rw as f32,
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
#[no_mangle] pub extern "C" fn bloom_physics_compound_end() -> f64 {
|
|
1446
|
+
bloom_jolt_ffi_physics().compound_end()
|
|
1447
|
+
}
|
|
1448
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_bounds(shape: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().shape_bounds_axis(shape, axis as u32) }
|
|
1449
|
+
#[no_mangle] pub extern "C" fn bloom_physics_shape_volume(shape: f64) -> f64 { bloom_jolt_ffi_physics().shape_volume(shape) as f64 }
|
|
1450
|
+
|
|
1451
|
+
// --- Bodies ---
|
|
1452
|
+
|
|
1453
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_create(
|
|
1454
|
+
world: f64, shape: f64, motion_type: f64,
|
|
1455
|
+
px: f64, py: f64, pz: f64,
|
|
1456
|
+
rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1457
|
+
layer: f64,
|
|
1458
|
+
) -> f64 {
|
|
1459
|
+
bloom_jolt_ffi_physics().create_body(
|
|
1460
|
+
world, shape, motion_type as u32,
|
|
1461
|
+
px as f32, py as f32, pz as f32,
|
|
1462
|
+
rx as f32, ry as f32, rz as f32, rw as f32,
|
|
1463
|
+
0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
|
1464
|
+
layer as u32,
|
|
1465
|
+
false, true, false, true,
|
|
1466
|
+
0.2, 0.0, 0.05, 0.05, 1.0,
|
|
1467
|
+
0.0, 0.0, 0.0, 0.0,
|
|
1468
|
+
0,
|
|
1469
|
+
)
|
|
1470
|
+
}
|
|
1471
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_destroy(body: f64) { bloom_jolt_ffi_physics().destroy_body(body); }
|
|
1472
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_activate(body: f64) { bloom_jolt_ffi_physics().body_activate(body); }
|
|
1473
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_deactivate(body: f64) { bloom_jolt_ffi_physics().body_deactivate(body); }
|
|
1474
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_is_active(body: f64) -> f64 { if bloom_jolt_ffi_physics().body_is_active(body) { 1.0 } else { 0.0 } }
|
|
1475
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_is_valid(body: f64) -> f64 { if bloom_jolt_ffi_physics().body_is_valid(body) { 1.0 } else { 0.0 } }
|
|
1476
|
+
|
|
1477
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_position(body: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().body_get_position_axis(body, axis as u32) }
|
|
1478
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_rotation(body: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().body_get_rotation_axis(body, axis as u32) }
|
|
1479
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_position(body: f64, x: f64, y: f64, z: f64, activate: f64) {
|
|
1480
|
+
bloom_jolt_ffi_physics().body_set_position(body, x as f32, y as f32, z as f32, activate != 0.0);
|
|
1481
|
+
}
|
|
1482
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_rotation(body: f64, x: f64, y: f64, z: f64, w: f64, activate: f64) {
|
|
1483
|
+
bloom_jolt_ffi_physics().body_set_rotation(body, x as f32, y as f32, z as f32, w as f32, activate != 0.0);
|
|
1484
|
+
}
|
|
1485
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_transform(
|
|
1486
|
+
body: f64, px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64, activate: f64,
|
|
1487
|
+
) {
|
|
1488
|
+
bloom_jolt_ffi_physics().body_set_transform(body, px as f32, py as f32, pz as f32, rx as f32, ry as f32, rz as f32, rw as f32, activate != 0.0);
|
|
1489
|
+
}
|
|
1490
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_move_kinematic(
|
|
1491
|
+
body: f64, px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64, dt: f64,
|
|
1492
|
+
) {
|
|
1493
|
+
bloom_jolt_ffi_physics().body_move_kinematic(body, px as f32, py as f32, pz as f32, rx as f32, ry as f32, rz as f32, rw as f32, dt as f32);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_linear_velocity(body: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().body_get_linear_velocity_axis(body, axis as u32) }
|
|
1497
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_angular_velocity(body: f64, axis: f64) -> f64 { bloom_jolt_ffi_physics().body_get_angular_velocity_axis(body, axis as u32) }
|
|
1498
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_point_velocity(body: f64, px: f64, py: f64, pz: f64, axis: f64) -> f64 {
|
|
1499
|
+
bloom_jolt_ffi_physics().body_get_point_velocity_axis(body, px as f32, py as f32, pz as f32, axis as u32)
|
|
1500
|
+
}
|
|
1501
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_linear_velocity(body: f64, x: f64, y: f64, z: f64) {
|
|
1502
|
+
bloom_jolt_ffi_physics().body_set_linear_velocity(body, x as f32, y as f32, z as f32);
|
|
1503
|
+
}
|
|
1504
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_angular_velocity(body: f64, x: f64, y: f64, z: f64) {
|
|
1505
|
+
bloom_jolt_ffi_physics().body_set_angular_velocity(body, x as f32, y as f32, z as f32);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_force(body: f64, x: f64, y: f64, z: f64) { bloom_jolt_ffi_physics().body_add_force(body, x as f32, y as f32, z as f32); }
|
|
1509
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_impulse(body: f64, x: f64, y: f64, z: f64) { bloom_jolt_ffi_physics().body_add_impulse(body, x as f32, y as f32, z as f32); }
|
|
1510
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_torque(body: f64, x: f64, y: f64, z: f64) { bloom_jolt_ffi_physics().body_add_torque(body, x as f32, y as f32, z as f32); }
|
|
1511
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_angular_impulse(body: f64, x: f64, y: f64, z: f64) { bloom_jolt_ffi_physics().body_add_angular_impulse(body, x as f32, y as f32, z as f32); }
|
|
1512
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_force_at(body: f64, fx: f64, fy: f64, fz: f64, px: f64, py: f64, pz: f64) {
|
|
1513
|
+
bloom_jolt_ffi_physics().body_add_force_at(body, fx as f32, fy as f32, fz as f32, px as f32, py as f32, pz as f32);
|
|
1514
|
+
}
|
|
1515
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_add_impulse_at(body: f64, ix: f64, iy: f64, iz: f64, px: f64, py: f64, pz: f64) {
|
|
1516
|
+
bloom_jolt_ffi_physics().body_add_impulse_at(body, ix as f32, iy as f32, iz as f32, px as f32, py as f32, pz as f32);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_friction(body: f64, v: f64) { bloom_jolt_ffi_physics().body_set_friction(body, v as f32); }
|
|
1520
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_restitution(body: f64, v: f64) { bloom_jolt_ffi_physics().body_set_restitution(body, v as f32); }
|
|
1521
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_linear_damping(body: f64, v: f64) { bloom_jolt_ffi_physics().body_set_linear_damping(body, v as f32); }
|
|
1522
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_angular_damping(body: f64, v: f64) { bloom_jolt_ffi_physics().body_set_angular_damping(body, v as f32); }
|
|
1523
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_gravity_factor(body: f64, v: f64) { bloom_jolt_ffi_physics().body_set_gravity_factor(body, v as f32); }
|
|
1524
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_ccd(body: f64, enabled: f64) { bloom_jolt_ffi_physics().body_set_ccd(body, enabled != 0.0); }
|
|
1525
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_motion_type(body: f64, t: f64, activate: f64) { bloom_jolt_ffi_physics().body_set_motion_type(body, t as u32, activate != 0.0); }
|
|
1526
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_object_layer(body: f64, layer: f64) { bloom_jolt_ffi_physics().body_set_object_layer(body, layer as u32); }
|
|
1527
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_is_sensor(body: f64, enabled: f64) { bloom_jolt_ffi_physics().body_set_is_sensor(body, enabled != 0.0); }
|
|
1528
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_allow_sleeping(body: f64, enabled: f64) { bloom_jolt_ffi_physics().body_set_allow_sleeping(body, enabled != 0.0); }
|
|
1529
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_shape(body: f64, shape: f64, update_mass: f64, activate: f64) {
|
|
1530
|
+
bloom_jolt_ffi_physics().body_set_shape(body, shape, update_mass != 0.0, activate != 0.0);
|
|
1531
|
+
}
|
|
1532
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_lock_rotation_axes(body: f64, x: f64, y: f64, z: f64) {
|
|
1533
|
+
bloom_jolt_ffi_physics().body_lock_rotation_axes(body, x != 0.0, y != 0.0, z != 0.0);
|
|
1534
|
+
}
|
|
1535
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_lock_translation_axes(body: f64, x: f64, y: f64, z: f64) {
|
|
1536
|
+
bloom_jolt_ffi_physics().body_lock_translation_axes(body, x != 0.0, y != 0.0, z != 0.0);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_mass(body: f64) -> f64 { bloom_jolt_ffi_physics().body_get_mass(body) as f64 }
|
|
1540
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_friction(body: f64) -> f64 { bloom_jolt_ffi_physics().body_get_friction(body) as f64 }
|
|
1541
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_restitution(body: f64) -> f64 { bloom_jolt_ffi_physics().body_get_restitution(body) as f64 }
|
|
1542
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_object_layer(body: f64) -> f64 { bloom_jolt_ffi_physics().body_get_object_layer(body) as f64 }
|
|
1543
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_set_user_data(body: f64, lo: f64, hi: f64) {
|
|
1544
|
+
let user = (lo as u64) | ((hi as u64) << 32);
|
|
1545
|
+
bloom_jolt_ffi_physics().body_set_user_data(body, user);
|
|
1546
|
+
}
|
|
1547
|
+
#[no_mangle] pub extern "C" fn bloom_physics_body_get_user_data(body: f64, part: f64) -> f64 {
|
|
1548
|
+
let u = bloom_jolt_ffi_physics().body_get_user_data(body);
|
|
1549
|
+
(if part as u32 == 1 { (u >> 32) as u32 } else { u as u32 }) as f64
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// --- Queries ---
|
|
1553
|
+
|
|
1554
|
+
#[no_mangle] pub extern "C" fn bloom_physics_raycast(
|
|
1555
|
+
world: f64, ox: f64, oy: f64, oz: f64,
|
|
1556
|
+
dx: f64, dy: f64, dz: f64, max_dist: f64, layer_mask: f64,
|
|
1557
|
+
) -> f64 {
|
|
1558
|
+
if bloom_jolt_ffi_physics().raycast_closest(world, ox as f32, oy as f32, oz as f32, dx as f32, dy as f32, dz as f32, max_dist as f32, layer_mask as u32) { 1.0 } else { 0.0 }
|
|
1559
|
+
}
|
|
1560
|
+
#[no_mangle] pub extern "C" fn bloom_physics_raycast_all(
|
|
1561
|
+
world: f64, ox: f64, oy: f64, oz: f64,
|
|
1562
|
+
dx: f64, dy: f64, dz: f64, max_dist: f64, layer_mask: f64, max_hits: f64,
|
|
1563
|
+
) -> f64 {
|
|
1564
|
+
bloom_jolt_ffi_physics().raycast_all(world, ox as f32, oy as f32, oz as f32, dx as f32, dy as f32, dz as f32, max_dist as f32, layer_mask as u32, max_hits as u32) as f64
|
|
1565
|
+
}
|
|
1566
|
+
#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_count() -> f64 { bloom_jolt_ffi_physics().ray_hit_count() as f64 }
|
|
1567
|
+
#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_body(i: f64) -> f64 { bloom_jolt_ffi_physics().ray_hit_body(i as usize) }
|
|
1568
|
+
#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_axis(i: f64, field: f64) -> f64 { bloom_jolt_ffi_physics().ray_hit_axis(i as usize, field as u32) }
|
|
1569
|
+
#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_fraction(i: f64) -> f64 { bloom_jolt_ffi_physics().ray_hit_fraction(i as usize) as f64 }
|
|
1570
|
+
#[no_mangle] pub extern "C" fn bloom_physics_ray_hit_sub_shape(i: f64) -> f64 { bloom_jolt_ffi_physics().ray_hit_sub_shape(i as usize) as f64 }
|
|
1571
|
+
|
|
1572
|
+
#[no_mangle] pub extern "C" fn bloom_physics_overlap_sphere(world: f64, cx: f64, cy: f64, cz: f64, r: f64, layer_mask: f64, max_results: f64) -> f64 {
|
|
1573
|
+
bloom_jolt_ffi_physics().overlap_sphere(world, cx as f32, cy as f32, cz as f32, r as f32, layer_mask as u32, max_results as u32) as f64
|
|
1574
|
+
}
|
|
1575
|
+
#[no_mangle] pub extern "C" fn bloom_physics_overlap_point(world: f64, px: f64, py: f64, pz: f64, layer_mask: f64, max_results: f64) -> f64 {
|
|
1576
|
+
bloom_jolt_ffi_physics().overlap_point(world, px as f32, py as f32, pz as f32, layer_mask as u32, max_results as u32) as f64
|
|
1577
|
+
}
|
|
1578
|
+
#[no_mangle] pub extern "C" fn bloom_physics_overlap_box(
|
|
1579
|
+
world: f64, px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1580
|
+
hx: f64, hy: f64, hz: f64, layer_mask: f64, max_results: f64,
|
|
1581
|
+
) -> f64 {
|
|
1582
|
+
bloom_jolt_ffi_physics().overlap_box(world, px as f32, py as f32, pz as f32, rx as f32, ry as f32, rz as f32, rw as f32, hx as f32, hy as f32, hz as f32, layer_mask as u32, max_results as u32) as f64
|
|
1583
|
+
}
|
|
1584
|
+
#[no_mangle] pub extern "C" fn bloom_physics_overlap_body(i: f64) -> f64 { bloom_jolt_ffi_physics().overlap_body(i as usize) }
|
|
1585
|
+
|
|
1586
|
+
// --- Constraints ---
|
|
1587
|
+
|
|
1588
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_fixed(
|
|
1589
|
+
body_a: f64, body_b: f64, ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64, world_space: f64,
|
|
1590
|
+
) -> f64 {
|
|
1591
|
+
bloom_jolt_ffi_physics().constraint_fixed(body_a, body_b, ax as f32, ay as f32, az as f32, bx as f32, by as f32, bz as f32, world_space != 0.0)
|
|
1592
|
+
}
|
|
1593
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_point(
|
|
1594
|
+
body_a: f64, body_b: f64, ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64, world_space: f64,
|
|
1595
|
+
) -> f64 {
|
|
1596
|
+
bloom_jolt_ffi_physics().constraint_point(body_a, body_b, ax as f32, ay as f32, az as f32, bx as f32, by as f32, bz as f32, world_space != 0.0)
|
|
1597
|
+
}
|
|
1598
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_hinge(
|
|
1599
|
+
body_a: f64, body_b: f64, ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64,
|
|
1600
|
+
axx: f64, axy: f64, axz: f64, lmin: f64, lmax: f64, world_space: f64,
|
|
1601
|
+
) -> f64 {
|
|
1602
|
+
bloom_jolt_ffi_physics().constraint_hinge(
|
|
1603
|
+
body_a, body_b,
|
|
1604
|
+
ax as f32, ay as f32, az as f32, bx as f32, by as f32, bz as f32,
|
|
1605
|
+
axx as f32, axy as f32, axz as f32,
|
|
1606
|
+
lmin as f32, lmax as f32, world_space != 0.0,
|
|
1607
|
+
)
|
|
1608
|
+
}
|
|
1609
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_slider(
|
|
1610
|
+
body_a: f64, body_b: f64, ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64,
|
|
1611
|
+
axx: f64, axy: f64, axz: f64, lmin: f64, lmax: f64, world_space: f64,
|
|
1612
|
+
) -> f64 {
|
|
1613
|
+
bloom_jolt_ffi_physics().constraint_slider(
|
|
1614
|
+
body_a, body_b,
|
|
1615
|
+
ax as f32, ay as f32, az as f32, bx as f32, by as f32, bz as f32,
|
|
1616
|
+
axx as f32, axy as f32, axz as f32,
|
|
1617
|
+
lmin as f32, lmax as f32, world_space != 0.0,
|
|
1618
|
+
)
|
|
1619
|
+
}
|
|
1620
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_distance(
|
|
1621
|
+
body_a: f64, body_b: f64, ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64,
|
|
1622
|
+
min_d: f64, max_d: f64, world_space: f64,
|
|
1623
|
+
) -> f64 {
|
|
1624
|
+
bloom_jolt_ffi_physics().constraint_distance(body_a, body_b, ax as f32, ay as f32, az as f32, bx as f32, by as f32, bz as f32, min_d as f32, max_d as f32, world_space != 0.0)
|
|
1625
|
+
}
|
|
1626
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_destroy(c: f64) { bloom_jolt_ffi_physics().constraint_destroy(c); }
|
|
1627
|
+
#[no_mangle] pub extern "C" fn bloom_physics_constraint_set_enabled(c: f64, enabled: f64) { bloom_jolt_ffi_physics().constraint_set_enabled(c, enabled != 0.0); }
|
|
1628
|
+
|
|
1629
|
+
// --- Contact events ---
|
|
1630
|
+
|
|
1631
|
+
#[no_mangle] pub extern "C" fn bloom_physics_contact_count() -> f64 { bloom_jolt_ffi_physics().contact_count() as f64 }
|
|
1632
|
+
#[no_mangle] pub extern "C" fn bloom_physics_contact_field(i: f64, field: f64) -> f64 { bloom_jolt_ffi_physics().contact_field(i as usize, field as u32) }
|
|
1633
|
+
#[no_mangle] pub extern "C" fn bloom_physics_clear_contacts(world: f64) { let _ = world; bloom_jolt_ffi_physics().clear_contacts(); }
|
|
1634
|
+
|
|
1635
|
+
// --- Character controller (Tier 2) ---
|
|
1636
|
+
|
|
1637
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_create(
|
|
1638
|
+
world: f64, shape: f64,
|
|
1639
|
+
up_x: f64, up_y: f64, up_z: f64,
|
|
1640
|
+
max_slope_angle: f64, character_padding: f64,
|
|
1641
|
+
penetration_recovery_speed: f64, predictive_contact_distance: f64,
|
|
1642
|
+
max_strength: f64, mass: f64, object_layer: f64,
|
|
1643
|
+
px: f64, py: f64, pz: f64,
|
|
1644
|
+
rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1645
|
+
) -> f64 {
|
|
1646
|
+
bloom_jolt_ffi_physics().character_create(
|
|
1647
|
+
world, shape,
|
|
1648
|
+
up_x as f32, up_y as f32, up_z as f32,
|
|
1649
|
+
max_slope_angle as f32, character_padding as f32,
|
|
1650
|
+
penetration_recovery_speed as f32, predictive_contact_distance as f32,
|
|
1651
|
+
max_strength as f32, mass as f32, object_layer as u32,
|
|
1652
|
+
px as f32, py as f32, pz as f32,
|
|
1653
|
+
rx as f32, ry as f32, rz as f32, rw as f32,
|
|
1654
|
+
)
|
|
1655
|
+
}
|
|
1656
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_destroy(c: f64) { bloom_jolt_ffi_physics().character_destroy(c); }
|
|
1657
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_update(c: f64, dt: f64, gx: f64, gy: f64, gz: f64) {
|
|
1658
|
+
bloom_jolt_ffi_physics().character_update(c, dt as f32, gx as f32, gy as f32, gz as f32);
|
|
1659
|
+
}
|
|
1660
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_position(c: f64, axis: f64) -> f64 {
|
|
1661
|
+
bloom_jolt_ffi_physics().character_get_position_axis(c, axis as u32)
|
|
1662
|
+
}
|
|
1663
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_rotation(c: f64, axis: f64) -> f64 {
|
|
1664
|
+
bloom_jolt_ffi_physics().character_get_rotation_axis(c, axis as u32)
|
|
1665
|
+
}
|
|
1666
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_set_position(c: f64, x: f64, y: f64, z: f64) {
|
|
1667
|
+
bloom_jolt_ffi_physics().character_set_position(c, x as f32, y as f32, z as f32);
|
|
1668
|
+
}
|
|
1669
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_set_rotation(c: f64, x: f64, y: f64, z: f64, w: f64) {
|
|
1670
|
+
bloom_jolt_ffi_physics().character_set_rotation(c, x as f32, y as f32, z as f32, w as f32);
|
|
1671
|
+
}
|
|
1672
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_linear_velocity(c: f64, axis: f64) -> f64 {
|
|
1673
|
+
bloom_jolt_ffi_physics().character_get_linear_velocity_axis(c, axis as u32)
|
|
1674
|
+
}
|
|
1675
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_set_linear_velocity(c: f64, x: f64, y: f64, z: f64) {
|
|
1676
|
+
bloom_jolt_ffi_physics().character_set_linear_velocity(c, x as f32, y as f32, z as f32);
|
|
1677
|
+
}
|
|
1678
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_state(c: f64) -> f64 {
|
|
1679
|
+
bloom_jolt_ffi_physics().character_get_ground_state(c) as f64
|
|
1680
|
+
}
|
|
1681
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_normal(c: f64, axis: f64) -> f64 {
|
|
1682
|
+
bloom_jolt_ffi_physics().character_get_ground_normal_axis(c, axis as u32)
|
|
1683
|
+
}
|
|
1684
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_position(c: f64, axis: f64) -> f64 {
|
|
1685
|
+
bloom_jolt_ffi_physics().character_get_ground_position_axis(c, axis as u32)
|
|
1686
|
+
}
|
|
1687
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_get_ground_body(c: f64) -> f64 {
|
|
1688
|
+
bloom_jolt_ffi_physics().character_get_ground_body(c)
|
|
1689
|
+
}
|
|
1690
|
+
#[no_mangle] pub extern "C" fn bloom_physics_character_set_shape(c: f64, shape: f64) {
|
|
1691
|
+
bloom_jolt_ffi_physics().character_set_shape(c, shape);
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// --- Soft bodies (Tier 2) ---
|
|
1695
|
+
|
|
1696
|
+
#[no_mangle] pub extern "C" fn bloom_physics_soft_body_create(
|
|
1697
|
+
world: f64, vertex_count: f64, triangle_count: f64,
|
|
1698
|
+
px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1699
|
+
object_layer: f64,
|
|
1700
|
+
edge_compliance: f64, gravity_factor: f64, linear_damping: f64, pressure: f64,
|
|
1701
|
+
) -> f64 {
|
|
1702
|
+
bloom_jolt_ffi_physics().soft_body_create_from_scratch(
|
|
1703
|
+
world, vertex_count as u32, triangle_count as u32,
|
|
1704
|
+
px as f32, py as f32, pz as f32,
|
|
1705
|
+
rx as f32, ry as f32, rz as f32, rw as f32,
|
|
1706
|
+
object_layer as u32,
|
|
1707
|
+
edge_compliance as f32, gravity_factor as f32,
|
|
1708
|
+
linear_damping as f32, pressure as f32,
|
|
1709
|
+
)
|
|
1710
|
+
}
|
|
1711
|
+
#[no_mangle] pub extern "C" fn bloom_physics_soft_body_vertex_count(body: f64) -> f64 {
|
|
1712
|
+
bloom_jolt_ffi_physics().soft_body_vertex_count(body) as f64
|
|
1713
|
+
}
|
|
1714
|
+
#[no_mangle] pub extern "C" fn bloom_physics_soft_body_get_vertex(body: f64, idx: f64, axis: f64) -> f64 {
|
|
1715
|
+
bloom_jolt_ffi_physics().soft_body_get_vertex_axis(body, idx as u32, axis as u32)
|
|
1716
|
+
}
|
|
1717
|
+
#[no_mangle] pub extern "C" fn bloom_physics_soft_body_set_vertex(body: f64, idx: f64, x: f64, y: f64, z: f64) {
|
|
1718
|
+
bloom_jolt_ffi_physics().soft_body_set_vertex(body, idx as u32, x as f32, y as f32, z as f32);
|
|
1719
|
+
}
|
|
1720
|
+
#[no_mangle] pub extern "C" fn bloom_physics_soft_body_set_vertex_inv_mass(body: f64, idx: f64, inv_mass: f64) {
|
|
1721
|
+
bloom_jolt_ffi_physics().soft_body_set_vertex_inv_mass(body, idx as u32, inv_mass as f32);
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// --- Wheeled vehicles (Tier 2) ---
|
|
1725
|
+
//
|
|
1726
|
+
// vehicle_create returns a packed f64 carrying BOTH the vehicle handle
|
|
1727
|
+
// and the chassis body handle: (vehicle << 20) | chassis_body. This keeps
|
|
1728
|
+
// the FFI scalar-only while letting TS unpack both. Handles stay <2^20
|
|
1729
|
+
// in practice for any realistic scene.
|
|
1730
|
+
|
|
1731
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_create(
|
|
1732
|
+
world: f64, chassis_shape: f64,
|
|
1733
|
+
up_x: f64, up_y: f64, up_z: f64,
|
|
1734
|
+
fw_x: f64, fw_y: f64, fw_z: f64,
|
|
1735
|
+
w0x: f64, w0y: f64, w0z: f64,
|
|
1736
|
+
w1x: f64, w1y: f64, w1z: f64,
|
|
1737
|
+
w2x: f64, w2y: f64, w2z: f64,
|
|
1738
|
+
w3x: f64, w3y: f64, w3z: f64,
|
|
1739
|
+
wheel_radius: f64, wheel_width: f64,
|
|
1740
|
+
suspension_min: f64, suspension_max: f64,
|
|
1741
|
+
max_steer_angle: f64, max_brake_torque: f64, max_handbrake_torque: f64,
|
|
1742
|
+
engine_max_torque: f64, max_pitch_roll_angle: f64,
|
|
1743
|
+
object_layer: f64,
|
|
1744
|
+
px: f64, py: f64, pz: f64, rx: f64, ry: f64, rz: f64, rw: f64,
|
|
1745
|
+
) -> f64 {
|
|
1746
|
+
let (vh, _) = bloom_jolt_ffi_physics().vehicle_create(
|
|
1747
|
+
world, chassis_shape,
|
|
1748
|
+
up_x as f32, up_y as f32, up_z as f32,
|
|
1749
|
+
fw_x as f32, fw_y as f32, fw_z as f32,
|
|
1750
|
+
w0x as f32, w0y as f32, w0z as f32,
|
|
1751
|
+
w1x as f32, w1y as f32, w1z as f32,
|
|
1752
|
+
w2x as f32, w2y as f32, w2z as f32,
|
|
1753
|
+
w3x as f32, w3y as f32, w3z as f32,
|
|
1754
|
+
wheel_radius as f32, wheel_width as f32,
|
|
1755
|
+
suspension_min as f32, suspension_max as f32,
|
|
1756
|
+
max_steer_angle as f32, max_brake_torque as f32, max_handbrake_torque as f32,
|
|
1757
|
+
engine_max_torque as f32, max_pitch_roll_angle as f32,
|
|
1758
|
+
object_layer as u32,
|
|
1759
|
+
px as f32, py as f32, pz as f32,
|
|
1760
|
+
rx as f32, ry as f32, rz as f32, rw as f32,
|
|
1761
|
+
);
|
|
1762
|
+
vh
|
|
1763
|
+
}
|
|
1764
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_destroy(v: f64) { bloom_jolt_ffi_physics().vehicle_destroy(v); }
|
|
1765
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_chassis(v: f64) -> f64 { bloom_jolt_ffi_physics().vehicle_get_chassis(v) }
|
|
1766
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_set_input(v: f64, forward: f64, right: f64, brake: f64, handbrake: f64) {
|
|
1767
|
+
bloom_jolt_ffi_physics().vehicle_set_input(v, forward as f32, right as f32, brake as f32, handbrake as f32);
|
|
1768
|
+
}
|
|
1769
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_wheel_transform(v: f64, wheel_index: f64, axis: f64) -> f64 {
|
|
1770
|
+
bloom_jolt_ffi_physics().vehicle_get_wheel_transform(v, wheel_index as u32, axis as u32) as f64
|
|
1771
|
+
}
|
|
1772
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_engine_rpm(v: f64) -> f64 {
|
|
1773
|
+
bloom_jolt_ffi_physics().vehicle_get_engine_rpm(v) as f64
|
|
1774
|
+
}
|
|
1775
|
+
#[no_mangle] pub extern "C" fn bloom_physics_vehicle_get_wheel_angular_velocity(v: f64, wheel_index: f64) -> f64 {
|
|
1776
|
+
bloom_jolt_ffi_physics().vehicle_get_wheel_angular_velocity(v, wheel_index as u32) as f64
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// ---------------------------------------------------------------------------
|
|
1782
|
+
// Fixed-timestep stepping tests. Run with the same `jolt` feature gate the
|
|
1783
|
+
// jolt_sys smoke tests use (CI's shared-tests job exercises them on all
|
|
1784
|
+
// three host OSes).
|
|
1785
|
+
// ---------------------------------------------------------------------------
|
|
1786
|
+
|
|
1787
|
+
#[cfg(test)]
|
|
1788
|
+
mod step_tests {
|
|
1789
|
+
use super::*;
|
|
1790
|
+
|
|
1791
|
+
/// Dynamic sphere falling under default gravity in a fresh world.
|
|
1792
|
+
fn world_with_falling_sphere(p: &mut JoltPhysics) -> (f64, f64) {
|
|
1793
|
+
let world = p.create_world(0.0, -9.81, 0.0, 1024, 1);
|
|
1794
|
+
assert_ne!(world, 0.0);
|
|
1795
|
+
let shape = p.create_sphere_shape(0.5);
|
|
1796
|
+
assert_ne!(shape, 0.0);
|
|
1797
|
+
#[rustfmt::skip]
|
|
1798
|
+
let body = p.create_body(
|
|
1799
|
+
world, shape, 2 /* dynamic */,
|
|
1800
|
+
0.0, 100.0, 0.0, // position
|
|
1801
|
+
0.0, 0.0, 0.0, 1.0, // rotation
|
|
1802
|
+
0.0, 0.0, 0.0, // linear velocity
|
|
1803
|
+
0.0, 0.0, 0.0, // angular velocity
|
|
1804
|
+
1, // moving layer
|
|
1805
|
+
false, false, false, true, // sensor/sleep/ccd/awake
|
|
1806
|
+
0.5, 0.0, // friction, restitution
|
|
1807
|
+
0.0, 0.0, 1.0, // damping, gravity factor
|
|
1808
|
+
0.0, // mass override (0 = from shape)
|
|
1809
|
+
0.0, 0.0, 0.0, // inertia override
|
|
1810
|
+
0, // user data
|
|
1811
|
+
);
|
|
1812
|
+
assert_ne!(body, 0.0);
|
|
1813
|
+
(world, body)
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/// Irregular frame times through the accumulator land on the same
|
|
1817
|
+
/// trajectory as the equivalent number of manual fixed steps.
|
|
1818
|
+
#[test]
|
|
1819
|
+
fn fixed_accumulator_matches_manual_stepping() {
|
|
1820
|
+
let mut p = JoltPhysics::new();
|
|
1821
|
+
let (wa, ba) = world_with_falling_sphere(&mut p);
|
|
1822
|
+
let (wb, bb) = world_with_falling_sphere(&mut p);
|
|
1823
|
+
|
|
1824
|
+
// A: irregular frames summing to 12.5 fixed steps — the half-step
|
|
1825
|
+
// margin keeps the floor() robustly at 12 across f32 accumulation
|
|
1826
|
+
// rounding (an exact-boundary sum floors to 11 or 12 depending on
|
|
1827
|
+
// rounding direction, which is correct but untestable).
|
|
1828
|
+
let frames = [0.04, 0.05, 0.03, 0.06, 12.5 / 60.0 - 0.18];
|
|
1829
|
+
let mut alpha = 1.0;
|
|
1830
|
+
for dt in frames {
|
|
1831
|
+
alpha = p.step_fixed(wa, dt as f32, 1);
|
|
1832
|
+
}
|
|
1833
|
+
assert!((0.3..=0.7).contains(&alpha), "expected ~half-step remainder, alpha = {alpha}");
|
|
1834
|
+
// B: 12 manual steps at exactly 1/60.
|
|
1835
|
+
for _ in 0..12 {
|
|
1836
|
+
p.step(wb, 1.0 / 60.0, 1);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
let ya = p.body_get_position_axis(ba, 1);
|
|
1840
|
+
let yb = p.body_get_position_axis(bb, 1);
|
|
1841
|
+
// 0.2 s of free fall from rest ≈ ½·9.81·0.04 ≈ 0.196 m
|
|
1842
|
+
assert!(ya < 99.9, "sphere did not fall: y = {ya}");
|
|
1843
|
+
assert!(
|
|
1844
|
+
(ya - yb).abs() < 1e-4,
|
|
1845
|
+
"accumulator diverged from manual stepping: {ya} vs {yb}"
|
|
1846
|
+
);
|
|
1847
|
+
|
|
1848
|
+
p.destroy_world(wa);
|
|
1849
|
+
p.destroy_world(wb);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
/// A huge hitch frame consumes at most max_steps_per_frame steps and
|
|
1853
|
+
/// drops the backlog instead of simulating seconds of catch-up.
|
|
1854
|
+
#[test]
|
|
1855
|
+
fn hitch_frame_is_clamped_and_capped() {
|
|
1856
|
+
let mut p = JoltPhysics::new();
|
|
1857
|
+
let (w, b) = world_with_falling_sphere(&mut p);
|
|
1858
|
+
|
|
1859
|
+
p.step_fixed(w, 10.0, 1); // debugger-pause-sized frame
|
|
1860
|
+
let y = p.body_get_position_axis(b, 1);
|
|
1861
|
+
// 4 steps at 1/60 ≈ 67ms of fall from rest ≈ 2.2cm. If the clamp or
|
|
1862
|
+
// cap failed we'd have simulated 0.25–10s (0.3m–490m of fall).
|
|
1863
|
+
let fallen = 100.0 - y;
|
|
1864
|
+
assert!(fallen > 0.0, "no simulation happened");
|
|
1865
|
+
assert!(
|
|
1866
|
+
fallen < 0.1,
|
|
1867
|
+
"hitch frame simulated too much catch-up: fell {fallen}m"
|
|
1868
|
+
);
|
|
1869
|
+
|
|
1870
|
+
// The dropped backlog must not leak into later frames: a zero-dt
|
|
1871
|
+
// follow-up frame steps at most once.
|
|
1872
|
+
let y1 = p.body_get_position_axis(b, 1);
|
|
1873
|
+
p.step_fixed(w, 0.0, 1);
|
|
1874
|
+
let y2 = p.body_get_position_axis(b, 1);
|
|
1875
|
+
assert!(
|
|
1876
|
+
(y1 - y2).abs() < 0.01,
|
|
1877
|
+
"backlog leaked into the next frame: {y1} -> {y2}"
|
|
1878
|
+
);
|
|
1879
|
+
|
|
1880
|
+
p.destroy_world(w);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
/// With interpolation on, the position getter blends between the last
|
|
1884
|
+
/// two simulated states by the carried remainder.
|
|
1885
|
+
#[test]
|
|
1886
|
+
fn interpolation_blends_between_steps() {
|
|
1887
|
+
let mut p = JoltPhysics::new();
|
|
1888
|
+
let (w, b) = world_with_falling_sphere(&mut p);
|
|
1889
|
+
p.set_interpolation(w, true);
|
|
1890
|
+
|
|
1891
|
+
// One and a half fixed steps: alpha should be 0.5 and the reported
|
|
1892
|
+
// position should sit between the step-1 and step-2 states.
|
|
1893
|
+
let alpha = p.step_fixed(w, 1.5 / 60.0, 1);
|
|
1894
|
+
assert!((alpha - 0.5).abs() < 1e-3, "alpha = {alpha}");
|
|
1895
|
+
|
|
1896
|
+
let y_blended = p.body_get_position_axis(b, 1);
|
|
1897
|
+
p.set_interpolation(w, false);
|
|
1898
|
+
let y_raw = p.body_get_position_axis(b, 1);
|
|
1899
|
+
// The sphere is falling, so the blended (half-step-old) position
|
|
1900
|
+
// must be strictly above the raw current position.
|
|
1901
|
+
assert!(
|
|
1902
|
+
y_blended > y_raw,
|
|
1903
|
+
"interpolated y ({y_blended}) not above raw y ({y_raw})"
|
|
1904
|
+
);
|
|
1905
|
+
|
|
1906
|
+
p.destroy_world(w);
|
|
1907
|
+
}
|
|
1908
|
+
}
|