@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,1519 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// jolt_bridge.js — web-side implementation of the bloom_physics_* FFI surface.
|
|
3
|
+
//
|
|
4
|
+
// The native Rust/Jolt backend is replaced on the web target by JoltPhysics.js
|
|
5
|
+
// (https://github.com/jrouwe/JoltPhysics.js). This module exports one function
|
|
6
|
+
// per `bloom_physics_*` entry so `bloom_web.wasm` can forward calls verbatim.
|
|
7
|
+
//
|
|
8
|
+
// Lifecycle:
|
|
9
|
+
// 1. Before Perry WASM boots, the host page awaits `initJolt()` below, which
|
|
10
|
+
// loads the Jolt WASM module and wires allocators + types.
|
|
11
|
+
// 2. Once ready, all bridge calls operate on live Jolt objects.
|
|
12
|
+
// 3. If a bridge call arrives before init (shouldn't happen if the host page
|
|
13
|
+
// sequences correctly), the bridge logs once and returns 0 / a no-op.
|
|
14
|
+
//
|
|
15
|
+
// Handle model mirrors the native Rust side (HandleRegistry<f64>):
|
|
16
|
+
// - 1-based numeric handles ("slots") issued sequentially per resource type.
|
|
17
|
+
// - 0 == INVALID.
|
|
18
|
+
// - Separate maps for worlds / shapes / bodies / constraints.
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
let JoltModule = null;
|
|
22
|
+
|
|
23
|
+
const state = {
|
|
24
|
+
worlds: new Map(), // handle → { system, bodyInterface, temp, jobs, contacts, rayHits, overlaps }
|
|
25
|
+
shapes: new Map(), // handle → Jolt Shape
|
|
26
|
+
bodies: new Map(), // handle → { world: number, bodyId: number }
|
|
27
|
+
constraints: new Map(), // handle → { world: number, constraint: Jolt.Constraint }
|
|
28
|
+
characters: new Map(), // handle → { world: number, character: Jolt.CharacterVirtual, layer: number }
|
|
29
|
+
nextWorld: 1, nextShape: 1, nextBody: 1, nextConstraint: 1, nextCharacter: 1,
|
|
30
|
+
// Most-recent query results (drained on read)
|
|
31
|
+
rayHits: [], // array of { body, point:[x,y,z], normal:[x,y,z], fraction, subShapeId }
|
|
32
|
+
overlapBodies: [], // array of body handles
|
|
33
|
+
contacts: [], // array of contact objects
|
|
34
|
+
// Scratch streams for variable-size shape inputs.
|
|
35
|
+
scratchF32: [],
|
|
36
|
+
scratchU32: [],
|
|
37
|
+
// Compound-shape builder state (cleared by compoundBegin).
|
|
38
|
+
compoundChildren: [],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function warnUninit(fnName) {
|
|
42
|
+
if (!JoltModule) {
|
|
43
|
+
if (!warnUninit._warned) {
|
|
44
|
+
console.warn('[jolt_bridge] Jolt not initialised; bloom_physics_* calls are no-ops until initJolt() resolves');
|
|
45
|
+
warnUninit._warned = true;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Initialisation — must be awaited before Perry calls any bloom_physics_* fn.
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Object} joltFactory - the default export of jolt-physics (a function
|
|
58
|
+
* returning a Promise<Jolt>). Accepting it via parameter lets the host page
|
|
59
|
+
* pick how to load it (CDN script tag, bundler, etc.).
|
|
60
|
+
*/
|
|
61
|
+
export async function initJolt(joltFactory) {
|
|
62
|
+
if (JoltModule) return JoltModule;
|
|
63
|
+
if (typeof joltFactory !== 'function') {
|
|
64
|
+
throw new Error('initJolt requires a joltFactory function (Jolt.default or window.Jolt)');
|
|
65
|
+
}
|
|
66
|
+
JoltModule = await joltFactory();
|
|
67
|
+
return JoltModule;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function isJoltReady() { return !!JoltModule; }
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function vec3(x, y, z) { return new JoltModule.Vec3(x, y, z); }
|
|
77
|
+
function rvec3(x, y, z) { return new JoltModule.RVec3(x, y, z); }
|
|
78
|
+
function quat(x, y, z, w) { return new JoltModule.Quat(x, y, z, w); }
|
|
79
|
+
|
|
80
|
+
const OBJECT_LAYER_NON_MOVING = 0;
|
|
81
|
+
const OBJECT_LAYER_MOVING = 1;
|
|
82
|
+
const BP_LAYER_NON_MOVING = 0;
|
|
83
|
+
const BP_LAYER_MOVING = 1;
|
|
84
|
+
|
|
85
|
+
function buildSettings(J) {
|
|
86
|
+
// Object-layer pair filter — 16 layers, mirrors the native C++ shim.
|
|
87
|
+
const pairFilter = new J.ObjectLayerPairFilterTable(16);
|
|
88
|
+
// Default: all layers collide except non-moving↔non-moving.
|
|
89
|
+
for (let a = 0; a < 16; a++) {
|
|
90
|
+
for (let b = 0; b < 16; b++) {
|
|
91
|
+
if (a === OBJECT_LAYER_NON_MOVING && b === OBJECT_LAYER_NON_MOVING) continue;
|
|
92
|
+
pairFilter.EnableCollision(a, b);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Broadphase-layer interface — 2 broadphase buckets.
|
|
96
|
+
const bpInterface = new J.BroadPhaseLayerInterfaceTable(16, 2);
|
|
97
|
+
bpInterface.MapObjectToBroadPhaseLayer(OBJECT_LAYER_NON_MOVING, new J.BroadPhaseLayer(BP_LAYER_NON_MOVING));
|
|
98
|
+
for (let i = 1; i < 16; i++) {
|
|
99
|
+
bpInterface.MapObjectToBroadPhaseLayer(i, new J.BroadPhaseLayer(BP_LAYER_MOVING));
|
|
100
|
+
}
|
|
101
|
+
const objectVsBp = new J.ObjectVsBroadPhaseLayerFilterTable(bpInterface, 2, pairFilter, 16);
|
|
102
|
+
|
|
103
|
+
const settings = new J.JoltSettings();
|
|
104
|
+
settings.mObjectLayerPairFilter = pairFilter;
|
|
105
|
+
settings.mBroadPhaseLayerInterface = bpInterface;
|
|
106
|
+
settings.mObjectVsBroadPhaseLayerFilter= objectVsBp;
|
|
107
|
+
settings.mMaxBodies = 65536;
|
|
108
|
+
settings.mMaxBodyPairs = 65536;
|
|
109
|
+
settings.mMaxContactConstraints = 10240;
|
|
110
|
+
// The filter tables ride along: builds without
|
|
111
|
+
// system.GetDefaultBroadPhaseLayerFilter() need them to construct the
|
|
112
|
+
// per-layer query filters (see queryFilters).
|
|
113
|
+
return { settings, pairFilter, objectVsBp };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function motionTypeFrom(t) {
|
|
117
|
+
const J = JoltModule;
|
|
118
|
+
switch (t | 0) {
|
|
119
|
+
case 0: return J.EMotionType_Static;
|
|
120
|
+
case 1: return J.EMotionType_Kinematic;
|
|
121
|
+
default: return J.EMotionType_Dynamic;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function activationFrom(flag) {
|
|
126
|
+
const J = JoltModule;
|
|
127
|
+
return flag !== 0 ? J.EActivation_Activate : J.EActivation_DontActivate;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// World
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
export function createWorld(gx, gy, gz, maxBodies, numThreads) {
|
|
135
|
+
if (warnUninit('createWorld')) return 0;
|
|
136
|
+
const J = JoltModule;
|
|
137
|
+
const { settings, pairFilter, objectVsBp } = buildSettings(J);
|
|
138
|
+
if ((maxBodies | 0) > 0) settings.mMaxBodies = maxBodies | 0;
|
|
139
|
+
const jolt = new J.JoltInterface(settings);
|
|
140
|
+
const system = jolt.GetPhysicsSystem();
|
|
141
|
+
system.SetGravity(vec3(gx, gy, gz));
|
|
142
|
+
|
|
143
|
+
const handle = state.nextWorld++;
|
|
144
|
+
|
|
145
|
+
// Contact listener — bridges Jolt contact callbacks into the state.contacts queue.
|
|
146
|
+
// JoltPhysics.js provides ContactListenerJS with virtual methods exposed as JS fields.
|
|
147
|
+
const listener = new J.ContactListenerJS();
|
|
148
|
+
const pushContact = (event, body1, body2, manifold, settings_out) => {
|
|
149
|
+
const bodyIdA = body1.GetID();
|
|
150
|
+
const bodyIdB = body2.GetID();
|
|
151
|
+
const bodyAh = bodyIdToHandle(handle, bodyIdA);
|
|
152
|
+
const bodyBh = bodyIdToHandle(handle, bodyIdB);
|
|
153
|
+
let pointA = [0, 0, 0], pointB = [0, 0, 0], normal = [0, 1, 0], depth = 0;
|
|
154
|
+
if (manifold) {
|
|
155
|
+
const base = manifold.mBaseOffset;
|
|
156
|
+
const n = manifold.mWorldSpaceNormal;
|
|
157
|
+
normal = [n.GetX(), n.GetY(), n.GetZ()];
|
|
158
|
+
depth = manifold.mPenetrationDepth;
|
|
159
|
+
if (manifold.mRelativeContactPointsOn1.size() > 0) {
|
|
160
|
+
const p = manifold.mRelativeContactPointsOn1.at(0);
|
|
161
|
+
pointA = [base.GetX() + p.GetX(), base.GetY() + p.GetY(), base.GetZ() + p.GetZ()];
|
|
162
|
+
}
|
|
163
|
+
if (manifold.mRelativeContactPointsOn2.size() > 0) {
|
|
164
|
+
const p = manifold.mRelativeContactPointsOn2.at(0);
|
|
165
|
+
pointB = [base.GetX() + p.GetX(), base.GetY() + p.GetY(), base.GetZ() + p.GetZ()];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
let friction = 0, restitution = 0;
|
|
169
|
+
if (settings_out) {
|
|
170
|
+
friction = settings_out.mCombinedFriction ?? 0;
|
|
171
|
+
restitution = settings_out.mCombinedRestitution ?? 0;
|
|
172
|
+
}
|
|
173
|
+
state.contacts.push({
|
|
174
|
+
event, bodyA: bodyAh, bodyB: bodyBh,
|
|
175
|
+
pointA, pointB, normal,
|
|
176
|
+
penetrationDepth: depth,
|
|
177
|
+
combinedFriction: friction,
|
|
178
|
+
combinedRestitution: restitution,
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
listener.OnContactValidate = () => J.ValidateResult_AcceptAllContactsForThisBodyPair;
|
|
182
|
+
listener.OnContactAdded = (b1, b2, m, s) => pushContact(0, b1, b2, m, s);
|
|
183
|
+
listener.OnContactPersisted = (b1, b2, m, s) => pushContact(1, b1, b2, m, s);
|
|
184
|
+
listener.OnContactRemoved = (pair) => {
|
|
185
|
+
const b1 = pair.GetBody1ID();
|
|
186
|
+
const b2 = pair.GetBody2ID();
|
|
187
|
+
state.contacts.push({
|
|
188
|
+
event: 2,
|
|
189
|
+
bodyA: bodyIdToHandle(handle, b1),
|
|
190
|
+
bodyB: bodyIdToHandle(handle, b2),
|
|
191
|
+
pointA: [0, 0, 0], pointB: [0, 0, 0], normal: [0, 1, 0],
|
|
192
|
+
penetrationDepth: 0, combinedFriction: 0, combinedRestitution: 0,
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
system.SetContactListener(listener);
|
|
196
|
+
|
|
197
|
+
state.worlds.set(handle, {
|
|
198
|
+
jolt,
|
|
199
|
+
system,
|
|
200
|
+
bodyInterface: system.GetBodyInterface(),
|
|
201
|
+
settings,
|
|
202
|
+
pairFilter, // for DefaultObjectLayerFilter construction
|
|
203
|
+
objectVsBp, // for DefaultBroadPhaseLayerFilter construction
|
|
204
|
+
listener, // hold ref so GC doesn't collect
|
|
205
|
+
});
|
|
206
|
+
return handle;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function bodyIdToHandle(worldH, bodyId) {
|
|
210
|
+
for (const [h, b] of state.bodies) {
|
|
211
|
+
if (b.world === worldH && b.bodyId.GetIndexAndSequenceNumber() === bodyId.GetIndexAndSequenceNumber()) {
|
|
212
|
+
return h;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function destroyWorld(h) {
|
|
219
|
+
const w = state.worlds.get(h);
|
|
220
|
+
if (!w) return;
|
|
221
|
+
// Remove any bodies/constraints tied to this world first.
|
|
222
|
+
for (const [bh, b] of state.bodies) {
|
|
223
|
+
if (b.world === h) { destroyBodyInternal(w, b); state.bodies.delete(bh); }
|
|
224
|
+
}
|
|
225
|
+
for (const [ch, c] of state.constraints) {
|
|
226
|
+
if (c.world === h) { w.system.RemoveConstraint(c.constraint); state.constraints.delete(ch); }
|
|
227
|
+
}
|
|
228
|
+
JoltModule.destroy(w.jolt);
|
|
229
|
+
state.worlds.delete(h);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function setGravity(h, gx, gy, gz) {
|
|
233
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
234
|
+
w.system.SetGravity(vec3(gx, gy, gz));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getGravity(h, axis) {
|
|
238
|
+
const w = state.worlds.get(h); if (!w) return 0;
|
|
239
|
+
const g = w.system.GetGravity();
|
|
240
|
+
if (axis === 0) return g.GetX();
|
|
241
|
+
if (axis === 1) return g.GetY();
|
|
242
|
+
if (axis === 2) return g.GetZ();
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function optimizeBroadphase(h) {
|
|
247
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
248
|
+
w.system.OptimizeBroadPhase();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function step(h, dt, collisionSteps) {
|
|
252
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
253
|
+
const steps = Math.max(1, collisionSteps | 0);
|
|
254
|
+
w.jolt.Step(dt, steps);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// --- fixed-timestep stepping (mirrors physics_jolt.rs WorldStepState) ------
|
|
258
|
+
// Semantics must match the native implementation exactly: clamp the frame
|
|
259
|
+
// dt (0.25s), simulate whole fixed steps with a catch-up cap, carry the
|
|
260
|
+
// remainder, snapshot before the LAST step of a batch for interpolation,
|
|
261
|
+
// and return alpha = remainder / fixedDt.
|
|
262
|
+
|
|
263
|
+
function stepState(w) {
|
|
264
|
+
if (!w.stepState) {
|
|
265
|
+
w.stepState = { fixedDt: 1 / 60, maxSteps: 4, acc: 0, alpha: 1, interpolate: false, prev: new Map() };
|
|
266
|
+
}
|
|
267
|
+
return w.stepState;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function snapshotWorldBodies(worldHandle, st) {
|
|
271
|
+
st.prev.clear();
|
|
272
|
+
for (const [bh, b] of state.bodies) {
|
|
273
|
+
if (b.world !== worldHandle) continue;
|
|
274
|
+
const bi = resolveBodyInterface(b); if (!bi) continue;
|
|
275
|
+
const p = bi.GetPosition(b.bodyId), q = bi.GetRotation(b.bodyId);
|
|
276
|
+
st.prev.set(bh, [p.GetX(), p.GetY(), p.GetZ(), q.GetX(), q.GetY(), q.GetZ(), q.GetW()]);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function stepFixed(h, frameDt, collisionSteps) {
|
|
281
|
+
const w = state.worlds.get(h); if (!w) return 1;
|
|
282
|
+
const st = stepState(w);
|
|
283
|
+
const dt = Number.isFinite(frameDt) ? Math.min(Math.max(frameDt, 0), 0.25) : 0;
|
|
284
|
+
st.acc += dt;
|
|
285
|
+
let steps = Math.floor(st.acc / st.fixedDt);
|
|
286
|
+
if (steps > st.maxSteps) {
|
|
287
|
+
steps = st.maxSteps;
|
|
288
|
+
st.acc = Math.min(st.acc, st.fixedDt * (steps + 1));
|
|
289
|
+
}
|
|
290
|
+
if (steps > 0) {
|
|
291
|
+
const cs = Math.max(1, collisionSteps | 0);
|
|
292
|
+
for (let i = 0; i < steps; i++) {
|
|
293
|
+
if (st.interpolate && i + 1 === steps) snapshotWorldBodies(h, st);
|
|
294
|
+
w.jolt.Step(st.fixedDt, cs);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
st.acc -= steps * st.fixedDt;
|
|
298
|
+
st.alpha = Math.min(Math.max(st.acc / st.fixedDt, 0), 1);
|
|
299
|
+
return st.alpha;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function setFixedTimestep(h, hz, maxSteps) {
|
|
303
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
304
|
+
const st = stepState(w);
|
|
305
|
+
if (hz > 0 && Number.isFinite(hz)) st.fixedDt = 1 / hz;
|
|
306
|
+
if (maxSteps > 0) st.maxSteps = maxSteps | 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function setInterpolation(h, on) {
|
|
310
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
311
|
+
const st = stepState(w);
|
|
312
|
+
st.interpolate = !!on;
|
|
313
|
+
if (!st.interpolate) { st.prev.clear(); st.alpha = 1; }
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function getStepAlpha(h) {
|
|
317
|
+
const w = state.worlds.get(h);
|
|
318
|
+
return w && w.stepState ? w.stepState.alpha : 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Interpolation blend for a body's getters, or null for raw state. */
|
|
322
|
+
function interpPrev(b, bh) {
|
|
323
|
+
const w = state.worlds.get(b.world);
|
|
324
|
+
const st = w && w.stepState;
|
|
325
|
+
if (!st || !st.interpolate || st.alpha >= 1) return null;
|
|
326
|
+
const prev = st.prev.get(bh);
|
|
327
|
+
return prev ? { a: st.alpha, prev } : null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function setLayerCollides(h, a, b, collides) {
|
|
331
|
+
const w = state.worlds.get(h); if (!w) return;
|
|
332
|
+
// JoltSettings filter was already baked; runtime mutation isn't supported via
|
|
333
|
+
// the JS bindings. This is a no-op for now; layer setup must happen before
|
|
334
|
+
// createWorld in a future API tweak.
|
|
335
|
+
void a; void b; void collides;
|
|
336
|
+
}
|
|
337
|
+
export function getLayerCollides(h, a, b) {
|
|
338
|
+
void h; void a; void b;
|
|
339
|
+
return 1; // default permissive
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export function bodyCount(h) { const w = state.worlds.get(h); return w ? w.system.GetNumBodies() : 0; }
|
|
343
|
+
export function activeBodyCount(h) { const w = state.worlds.get(h); return w ? w.system.GetNumActiveBodies() : 0; }
|
|
344
|
+
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Shapes
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
function registerShape(shape) {
|
|
350
|
+
if (!shape) return 0;
|
|
351
|
+
const h = state.nextShape++;
|
|
352
|
+
state.shapes.set(h, shape);
|
|
353
|
+
return h;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function shapeBox(hx, hy, hz, convexRadius) {
|
|
357
|
+
if (warnUninit('shapeBox')) return 0;
|
|
358
|
+
const settings = new JoltModule.BoxShapeSettings(vec3(hx, hy, hz), convexRadius);
|
|
359
|
+
const result = settings.Create();
|
|
360
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
361
|
+
}
|
|
362
|
+
export function shapeSphere(r) {
|
|
363
|
+
if (warnUninit('shapeSphere')) return 0;
|
|
364
|
+
const settings = new JoltModule.SphereShapeSettings(r);
|
|
365
|
+
const result = settings.Create();
|
|
366
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
367
|
+
}
|
|
368
|
+
export function shapeCapsule(h, r) {
|
|
369
|
+
if (warnUninit('shapeCapsule')) return 0;
|
|
370
|
+
const settings = new JoltModule.CapsuleShapeSettings(h, r);
|
|
371
|
+
const result = settings.Create();
|
|
372
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
373
|
+
}
|
|
374
|
+
export function shapeCylinder(h, r, cr) {
|
|
375
|
+
if (warnUninit('shapeCylinder')) return 0;
|
|
376
|
+
const settings = new JoltModule.CylinderShapeSettings(h, r, cr);
|
|
377
|
+
const result = settings.Create();
|
|
378
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
379
|
+
}
|
|
380
|
+
export function shapeScaled(base, sx, sy, sz) {
|
|
381
|
+
if (warnUninit('shapeScaled')) return 0;
|
|
382
|
+
const inner = state.shapes.get(base); if (!inner) return 0;
|
|
383
|
+
const settings = new JoltModule.ScaledShapeSettings(inner, vec3(sx, sy, sz));
|
|
384
|
+
const result = settings.Create();
|
|
385
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
386
|
+
}
|
|
387
|
+
export function shapeOffsetCom(base, ox, oy, oz) {
|
|
388
|
+
if (warnUninit('shapeOffsetCom')) return 0;
|
|
389
|
+
const inner = state.shapes.get(base); if (!inner) return 0;
|
|
390
|
+
const settings = new JoltModule.OffsetCenterOfMassShapeSettings(vec3(ox, oy, oz), inner);
|
|
391
|
+
const result = settings.Create();
|
|
392
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
393
|
+
}
|
|
394
|
+
export function shapeRelease(h) {
|
|
395
|
+
const s = state.shapes.get(h); if (!s) return;
|
|
396
|
+
state.shapes.delete(h);
|
|
397
|
+
// JoltPhysics.js shapes are refcounted internally; letting GC handle it is fine.
|
|
398
|
+
}
|
|
399
|
+
export function shapeBounds(h, axis) {
|
|
400
|
+
const s = state.shapes.get(h); if (!s) return 0;
|
|
401
|
+
const box = s.GetLocalBounds();
|
|
402
|
+
switch (axis | 0) {
|
|
403
|
+
case 0: return box.mMin.GetX();
|
|
404
|
+
case 1: return box.mMin.GetY();
|
|
405
|
+
case 2: return box.mMin.GetZ();
|
|
406
|
+
case 3: return box.mMax.GetX();
|
|
407
|
+
case 4: return box.mMax.GetY();
|
|
408
|
+
case 5: return box.mMax.GetZ();
|
|
409
|
+
default: return 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
export function shapeVolume(h) {
|
|
413
|
+
const s = state.shapes.get(h); if (!s) return 0;
|
|
414
|
+
return s.GetVolume ? s.GetVolume() : 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Bodies
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
function destroyBodyInternal(w, b) {
|
|
422
|
+
if (w.bodyInterface.IsAdded(b.bodyId)) w.bodyInterface.RemoveBody(b.bodyId);
|
|
423
|
+
w.bodyInterface.DestroyBody(b.bodyId);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function resolveBody(h) { return state.bodies.get(h); }
|
|
427
|
+
function resolveBodyInterface(b) {
|
|
428
|
+
const w = state.worlds.get(b.world); return w ? w.bodyInterface : null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function bodyCreate(worldH, shapeH, motionType, px, py, pz, rx, ry, rz, rw, layer) {
|
|
432
|
+
if (warnUninit('bodyCreate')) return 0;
|
|
433
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
434
|
+
const s = state.shapes.get(shapeH); if (!s) return 0;
|
|
435
|
+
const J = JoltModule;
|
|
436
|
+
const settings = new J.BodyCreationSettings(
|
|
437
|
+
s,
|
|
438
|
+
rvec3(px, py, pz),
|
|
439
|
+
quat(rx, ry, rz, rw),
|
|
440
|
+
motionTypeFrom(motionType),
|
|
441
|
+
(layer | 0),
|
|
442
|
+
);
|
|
443
|
+
const bodyId = w.bodyInterface.CreateAndAddBody(settings, J.EActivation_Activate);
|
|
444
|
+
J.destroy(settings);
|
|
445
|
+
const h = state.nextBody++;
|
|
446
|
+
state.bodies.set(h, { world: worldH, bodyId });
|
|
447
|
+
return h;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export function bodyDestroy(h) {
|
|
451
|
+
const b = resolveBody(h); if (!b) return;
|
|
452
|
+
const w = state.worlds.get(b.world); if (w) destroyBodyInternal(w, b);
|
|
453
|
+
state.bodies.delete(h);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function bodyActivate(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.ActivateBody(b.bodyId); }
|
|
457
|
+
export function bodyDeactivate(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.DeactivateBody(b.bodyId); }
|
|
458
|
+
export function bodyIsActive(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); return bi && bi.IsActive(b.bodyId) ? 1 : 0; }
|
|
459
|
+
export function bodyIsValid(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); return bi && bi.IsAdded(b.bodyId) ? 1 : 0; }
|
|
460
|
+
|
|
461
|
+
export function bodyGetPosition(h, axis) {
|
|
462
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
463
|
+
const p = bi.GetPosition(b.bodyId);
|
|
464
|
+
let x = p.GetX(), y = p.GetY(), z = p.GetZ();
|
|
465
|
+
const ip = interpPrev(b, h);
|
|
466
|
+
if (ip) {
|
|
467
|
+
const [px, py, pz] = ip.prev;
|
|
468
|
+
x = px + (x - px) * ip.a; y = py + (y - py) * ip.a; z = pz + (z - pz) * ip.a;
|
|
469
|
+
}
|
|
470
|
+
if (axis === 0) return x; if (axis === 1) return y; if (axis === 2) return z;
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
export function bodyGetRotation(h, axis) {
|
|
474
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
475
|
+
const q = bi.GetRotation(b.bodyId);
|
|
476
|
+
let x = q.GetX(), y = q.GetY(), z = q.GetZ(), w = q.GetW();
|
|
477
|
+
const ip = interpPrev(b, h);
|
|
478
|
+
if (ip) {
|
|
479
|
+
// nlerp with shortest-path sign handling — matches the native getter
|
|
480
|
+
let [, , , px, py, pz, pw] = ip.prev;
|
|
481
|
+
const dot = px * x + py * y + pz * z + pw * w;
|
|
482
|
+
const s = dot < 0 ? -1 : 1;
|
|
483
|
+
px *= s; py *= s; pz *= s; pw *= s;
|
|
484
|
+
const a = ip.a;
|
|
485
|
+
x = px + (x - px) * a; y = py + (y - py) * a; z = pz + (z - pz) * a; w = pw + (w - pw) * a;
|
|
486
|
+
const len = Math.hypot(x, y, z, w);
|
|
487
|
+
if (len > 1e-6) { x /= len; y /= len; z /= len; w /= len; }
|
|
488
|
+
}
|
|
489
|
+
if (axis === 0) return x; if (axis === 1) return y;
|
|
490
|
+
if (axis === 2) return z; if (axis === 3) return w;
|
|
491
|
+
return 0;
|
|
492
|
+
}
|
|
493
|
+
export function bodySetPosition(h, x, y, z, activate) {
|
|
494
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
495
|
+
bi.SetPosition(b.bodyId, rvec3(x, y, z), activationFrom(activate));
|
|
496
|
+
}
|
|
497
|
+
export function bodySetRotation(h, x, y, z, w, activate) {
|
|
498
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
499
|
+
bi.SetRotation(b.bodyId, quat(x, y, z, w), activationFrom(activate));
|
|
500
|
+
}
|
|
501
|
+
export function bodySetTransform(h, px, py, pz, rx, ry, rz, rw, activate) {
|
|
502
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
503
|
+
bi.SetPositionAndRotation(b.bodyId, rvec3(px, py, pz), quat(rx, ry, rz, rw), activationFrom(activate));
|
|
504
|
+
}
|
|
505
|
+
export function bodyMoveKinematic(h, px, py, pz, rx, ry, rz, rw, dt) {
|
|
506
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
507
|
+
bi.MoveKinematic(b.bodyId, rvec3(px, py, pz), quat(rx, ry, rz, rw), dt);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function bodyGetLinearVelocity(h, axis) {
|
|
511
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
512
|
+
const v = bi.GetLinearVelocity(b.bodyId);
|
|
513
|
+
if (axis === 0) return v.GetX(); if (axis === 1) return v.GetY(); if (axis === 2) return v.GetZ();
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
export function bodyGetAngularVelocity(h, axis) {
|
|
517
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
518
|
+
const v = bi.GetAngularVelocity(b.bodyId);
|
|
519
|
+
if (axis === 0) return v.GetX(); if (axis === 1) return v.GetY(); if (axis === 2) return v.GetZ();
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
export function bodyGetPointVelocity(h, px, py, pz, axis) {
|
|
523
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
524
|
+
const v = bi.GetPointVelocity(b.bodyId, rvec3(px, py, pz));
|
|
525
|
+
if (axis === 0) return v.GetX(); if (axis === 1) return v.GetY(); if (axis === 2) return v.GetZ();
|
|
526
|
+
return 0;
|
|
527
|
+
}
|
|
528
|
+
export function bodySetLinearVelocity(h, x, y, z) {
|
|
529
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
530
|
+
bi.SetLinearVelocity(b.bodyId, vec3(x, y, z));
|
|
531
|
+
}
|
|
532
|
+
export function bodySetAngularVelocity(h, x, y, z) {
|
|
533
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
534
|
+
bi.SetAngularVelocity(b.bodyId, vec3(x, y, z));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function bodyAddForce(h, x, y, z) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddForce(b.bodyId, vec3(x, y, z)); }
|
|
538
|
+
export function bodyAddImpulse(h, x, y, z) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddImpulse(b.bodyId, vec3(x, y, z)); }
|
|
539
|
+
export function bodyAddTorque(h, x, y, z) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddTorque(b.bodyId, vec3(x, y, z)); }
|
|
540
|
+
export function bodyAddAngularImpulse(h, x, y, z) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddAngularImpulse(b.bodyId, vec3(x, y, z)); }
|
|
541
|
+
export function bodyAddForceAt(h, fx, fy, fz, px, py, pz) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddForceAt ? bi.AddForceAt(b.bodyId, vec3(fx, fy, fz), rvec3(px, py, pz)) : bi.AddForce(b.bodyId, vec3(fx, fy, fz), rvec3(px, py, pz)); }
|
|
542
|
+
export function bodyAddImpulseAt(h, ix, iy, iz, px, py, pz) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.AddImpulseAt ? bi.AddImpulseAt(b.bodyId, vec3(ix, iy, iz), rvec3(px, py, pz)) : bi.AddImpulse(b.bodyId, vec3(ix, iy, iz), rvec3(px, py, pz)); }
|
|
543
|
+
|
|
544
|
+
export function bodySetFriction(h, v) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.SetFriction(b.bodyId, v); }
|
|
545
|
+
export function bodySetRestitution(h, v) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi) bi.SetRestitution(b.bodyId, v); }
|
|
546
|
+
export function bodySetLinearDamping(h, v) {
|
|
547
|
+
const b = resolveBody(h); if (!b) return;
|
|
548
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
549
|
+
const J = JoltModule;
|
|
550
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
551
|
+
if (lock.SucceededAndIsInBroadPhase()) {
|
|
552
|
+
const mp = lock.GetBody().GetMotionPropertiesUnchecked();
|
|
553
|
+
if (mp) mp.SetLinearDamping(v);
|
|
554
|
+
}
|
|
555
|
+
lock.ReleaseLock();
|
|
556
|
+
}
|
|
557
|
+
export function bodySetAngularDamping(h, v) {
|
|
558
|
+
const b = resolveBody(h); if (!b) return;
|
|
559
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
560
|
+
const J = JoltModule;
|
|
561
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
562
|
+
if (lock.SucceededAndIsInBroadPhase()) {
|
|
563
|
+
const mp = lock.GetBody().GetMotionPropertiesUnchecked();
|
|
564
|
+
if (mp) mp.SetAngularDamping(v);
|
|
565
|
+
}
|
|
566
|
+
lock.ReleaseLock();
|
|
567
|
+
}
|
|
568
|
+
export function bodySetGravityFactor(h, v) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi && bi.SetGravityFactor) bi.SetGravityFactor(b.bodyId, v); }
|
|
569
|
+
export function bodySetCcd(h, enabled) {
|
|
570
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
571
|
+
const J = JoltModule;
|
|
572
|
+
bi.SetMotionQuality(b.bodyId, enabled ? J.EMotionQuality_LinearCast : J.EMotionQuality_Discrete);
|
|
573
|
+
}
|
|
574
|
+
export function bodySetMotionType(h, t, activate) {
|
|
575
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
576
|
+
bi.SetMotionType(b.bodyId, motionTypeFrom(t), activationFrom(activate));
|
|
577
|
+
}
|
|
578
|
+
export function bodySetObjectLayer(h, layer) {
|
|
579
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (bi && bi.SetObjectLayer) bi.SetObjectLayer(b.bodyId, layer | 0);
|
|
580
|
+
}
|
|
581
|
+
export function bodySetIsSensor(h, enabled) {
|
|
582
|
+
const b = resolveBody(h); if (!b) return;
|
|
583
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
584
|
+
const J = JoltModule;
|
|
585
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
586
|
+
if (lock.SucceededAndIsInBroadPhase()) lock.GetBody().SetIsSensor(enabled !== 0);
|
|
587
|
+
lock.ReleaseLock();
|
|
588
|
+
}
|
|
589
|
+
export function bodySetAllowSleeping(h, enabled) {
|
|
590
|
+
const b = resolveBody(h); if (!b) return;
|
|
591
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
592
|
+
const J = JoltModule;
|
|
593
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
594
|
+
if (lock.SucceededAndIsInBroadPhase()) lock.GetBody().SetAllowSleeping(enabled !== 0);
|
|
595
|
+
lock.ReleaseLock();
|
|
596
|
+
}
|
|
597
|
+
export function bodySetShape(h, shapeH, updateMass, activate) {
|
|
598
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return;
|
|
599
|
+
const s = state.shapes.get(shapeH); if (!s) return;
|
|
600
|
+
bi.SetShape(b.bodyId, s, updateMass !== 0, activationFrom(activate));
|
|
601
|
+
}
|
|
602
|
+
export function bodyLockRotationAxes(h, x, y, z) {
|
|
603
|
+
void h; void x; void y; void z; // stub — requires recreating the body on JS side
|
|
604
|
+
}
|
|
605
|
+
export function bodyLockTranslationAxes(h, x, y, z) {
|
|
606
|
+
void h; void x; void y; void z;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function bodyGetMass(h) {
|
|
610
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi) return 0;
|
|
611
|
+
const inv = bi.GetInverseMass ? bi.GetInverseMass(b.bodyId) : 0;
|
|
612
|
+
return inv > 0 ? 1 / inv : 0;
|
|
613
|
+
}
|
|
614
|
+
export function bodyGetFriction(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); return bi ? bi.GetFriction(b.bodyId) : 0; }
|
|
615
|
+
export function bodyGetRestitution(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); return bi ? bi.GetRestitution(b.bodyId) : 0; }
|
|
616
|
+
export function bodyGetObjectLayer(h) { const b = resolveBody(h); const bi = b && resolveBodyInterface(b); return bi && bi.GetObjectLayer ? bi.GetObjectLayer(b.bodyId) : 0; }
|
|
617
|
+
export function bodySetUserData(h, lo, hi) {
|
|
618
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi || !bi.SetUserData) return;
|
|
619
|
+
// Pack lo (uint32) + hi (uint32) into uint64. Whether a u64 crosses
|
|
620
|
+
// JoltPhysics.js as BigInt or Number depends on how the build was linked
|
|
621
|
+
// (-sWASM_BIGINT or not) — the CDN 1.0.0 build wants Number and THROWS on
|
|
622
|
+
// BigInt, so probe once and remember. Number packing is exact here: call
|
|
623
|
+
// sites keep hi = 0, far below 2^53. Most call sites use just `lo`.
|
|
624
|
+
try {
|
|
625
|
+
bi.SetUserData(b.bodyId, BigInt(lo >>> 0) | (BigInt(hi >>> 0) << 32n));
|
|
626
|
+
} catch {
|
|
627
|
+
bi.SetUserData(b.bodyId, (hi >>> 0) * 0x100000000 + (lo >>> 0));
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
export function bodyGetUserData(h, part) {
|
|
631
|
+
const b = resolveBody(h); const bi = b && resolveBodyInterface(b); if (!bi || !bi.GetUserData) return 0;
|
|
632
|
+
const u = bi.GetUserData(b.bodyId);
|
|
633
|
+
if (typeof u === 'bigint') {
|
|
634
|
+
return part === 1 ? Number(u >> 32n) : Number(u & 0xFFFFFFFFn);
|
|
635
|
+
}
|
|
636
|
+
return Number(u);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ---------------------------------------------------------------------------
|
|
640
|
+
// Queries (Tier 1 — closest-hit only; all-hits/overlap = stubs for Tier 2)
|
|
641
|
+
// ---------------------------------------------------------------------------
|
|
642
|
+
|
|
643
|
+
export function raycast(worldH, ox, oy, oz, dx, dy, dz, maxDist, layerMask) {
|
|
644
|
+
state.rayHits.length = 0;
|
|
645
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
646
|
+
const J = JoltModule;
|
|
647
|
+
const dir = vec3(dx, dy, dz);
|
|
648
|
+
if (dir.LengthSq() === 0) return 0;
|
|
649
|
+
const scaled = dir.Mul ? dir.Mul(maxDist / Math.sqrt(dir.LengthSq())) : dir;
|
|
650
|
+
const ray = new J.RRayCast(rvec3(ox, oy, oz), scaled);
|
|
651
|
+
// Layer 1 (MOVING) collides with every layer in the pair table, so its
|
|
652
|
+
// Default filters are the pass-all query. Two npm-build gotchas here:
|
|
653
|
+
// the abstract BroadPhaseLayerFilter/ObjectLayerFilter bases trap with a
|
|
654
|
+
// null vtable, and the closest-hit `CastRay(ray, RayCastResult, ...)`
|
|
655
|
+
// overload does not exist — only the collector form from the official
|
|
656
|
+
// examples is bound.
|
|
657
|
+
const qf = queryFilters(w, worldH, 1);
|
|
658
|
+
const rcSettings = new J.RayCastSettings();
|
|
659
|
+
const collector = new J.CastRayClosestHitCollisionCollector();
|
|
660
|
+
w.system.GetNarrowPhaseQuery().CastRay(ray, rcSettings, collector, qf.bp, qf.obj, qf.body, qf.shape);
|
|
661
|
+
const hit = collector.HadHit();
|
|
662
|
+
void layerMask; // layer filtering is handled by ObjectLayerFilter; Tier 2 adds mask support
|
|
663
|
+
if (hit) {
|
|
664
|
+
// Resolve body handle from BodyID.
|
|
665
|
+
const result = collector.mHit;
|
|
666
|
+
const bodyId = result.mBodyID;
|
|
667
|
+
let bodyHandle = 0;
|
|
668
|
+
for (const [h, b] of state.bodies) { if (b.world === worldH && b.bodyId.GetIndexAndSequenceNumber() === bodyId.GetIndexAndSequenceNumber()) { bodyHandle = h; break; } }
|
|
669
|
+
const fraction = result.mFraction;
|
|
670
|
+
state.rayHits.push({
|
|
671
|
+
body: bodyHandle,
|
|
672
|
+
point: [ox + (dx * maxDist) * fraction, oy + (dy * maxDist) * fraction, oz + (dz * maxDist) * fraction],
|
|
673
|
+
normal: [0, 1, 0], // TODO: world-space surface normal — needs a BodyLock
|
|
674
|
+
fraction,
|
|
675
|
+
subShapeId: 0,
|
|
676
|
+
});
|
|
677
|
+
J.destroy(collector); J.destroy(rcSettings); J.destroy(ray);
|
|
678
|
+
return 1;
|
|
679
|
+
}
|
|
680
|
+
J.destroy(collector); J.destroy(rcSettings); J.destroy(ray);
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
export function raycastAll(worldH, ox, oy, oz, dx, dy, dz, maxDist, layerMask, maxHits) {
|
|
684
|
+
state.rayHits.length = 0;
|
|
685
|
+
void layerMask;
|
|
686
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
687
|
+
const J = JoltModule;
|
|
688
|
+
const dir = vec3(dx, dy, dz);
|
|
689
|
+
if (dir.LengthSq() === 0) return 0;
|
|
690
|
+
const scaled = vec3(dx * maxDist, dy * maxDist, dz * maxDist);
|
|
691
|
+
const ray = new J.RRayCast(rvec3(ox, oy, oz), scaled);
|
|
692
|
+
const settings = new J.RayCastSettings();
|
|
693
|
+
const collector = new J.CastRayAllHitCollisionCollectorJS();
|
|
694
|
+
const qf = queryFilters(w, worldH, 1);
|
|
695
|
+
w.system.GetNarrowPhaseQuery().CastRay(
|
|
696
|
+
ray, settings, collector,
|
|
697
|
+
qf.bp, qf.obj, qf.body, qf.shape,
|
|
698
|
+
);
|
|
699
|
+
collector.Sort();
|
|
700
|
+
const count = Math.min(maxHits | 0, collector.mHits.size());
|
|
701
|
+
for (let i = 0; i < count; i++) {
|
|
702
|
+
const hit = collector.mHits.at(i);
|
|
703
|
+
const frac = hit.mFraction;
|
|
704
|
+
let bodyHandle = 0;
|
|
705
|
+
for (const [h, b] of state.bodies) {
|
|
706
|
+
if (b.world === worldH && b.bodyId.GetIndexAndSequenceNumber() === hit.mBodyID.GetIndexAndSequenceNumber()) {
|
|
707
|
+
bodyHandle = h; break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
state.rayHits.push({
|
|
711
|
+
body: bodyHandle,
|
|
712
|
+
point: [ox + dx * maxDist * frac, oy + dy * maxDist * frac, oz + dz * maxDist * frac],
|
|
713
|
+
normal: [0, 1, 0], // world-space normal requires a BodyLock; skipped for now
|
|
714
|
+
fraction: frac,
|
|
715
|
+
subShapeId: hit.mSubShapeID2 ? hit.mSubShapeID2.GetValue() : 0,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return state.rayHits.length;
|
|
719
|
+
}
|
|
720
|
+
export function rayHitCount() { return state.rayHits.length; }
|
|
721
|
+
export function rayHitBody(i) { return state.rayHits[i|0]?.body ?? 0; }
|
|
722
|
+
export function rayHitAxis(i, f) {
|
|
723
|
+
const h = state.rayHits[i|0]; if (!h) return 0;
|
|
724
|
+
return f < 3 ? h.point[f|0] : h.normal[(f|0)-3];
|
|
725
|
+
}
|
|
726
|
+
export function rayHitFraction(i) { return state.rayHits[i|0]?.fraction ?? 0; }
|
|
727
|
+
export function rayHitSubShape(i) { return state.rayHits[i|0]?.subShapeId ?? 0; }
|
|
728
|
+
|
|
729
|
+
function collectOverlapBodies(w, shapeCollider, maxResults) {
|
|
730
|
+
// JoltPhysics.js exposes broadphase query collectors via *_JS suffixed classes.
|
|
731
|
+
const J = JoltModule;
|
|
732
|
+
const collector = new J.CollideShapeBodyCollectorJS();
|
|
733
|
+
shapeCollider(collector);
|
|
734
|
+
const count = Math.min(maxResults | 0, collector.mHits.size());
|
|
735
|
+
const out = [];
|
|
736
|
+
for (let i = 0; i < count; i++) {
|
|
737
|
+
const bodyId = collector.mHits.at(i);
|
|
738
|
+
for (const [h, b] of state.bodies) {
|
|
739
|
+
if (b.world === w && b.bodyId.GetIndexAndSequenceNumber() === bodyId.GetIndexAndSequenceNumber()) {
|
|
740
|
+
out.push(h); break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return out;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export function overlapSphere(worldH, cx, cy, cz, r, layerMask, maxResults) {
|
|
748
|
+
state.overlapBodies.length = 0;
|
|
749
|
+
void layerMask;
|
|
750
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
751
|
+
const J = JoltModule;
|
|
752
|
+
state.overlapBodies = collectOverlapBodies(worldH, collector => {
|
|
753
|
+
w.system.GetBroadPhaseQuery().CollideSphere(
|
|
754
|
+
vec3(cx, cy, cz), r, collector,
|
|
755
|
+
queryFilters(w, worldH, 1).bp, queryFilters(w, worldH, 1).obj,
|
|
756
|
+
);
|
|
757
|
+
}, maxResults);
|
|
758
|
+
return state.overlapBodies.length;
|
|
759
|
+
}
|
|
760
|
+
export function overlapPoint(worldH, px, py, pz, layerMask, maxResults) {
|
|
761
|
+
state.overlapBodies.length = 0;
|
|
762
|
+
void layerMask;
|
|
763
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
764
|
+
const J = JoltModule;
|
|
765
|
+
state.overlapBodies = collectOverlapBodies(worldH, collector => {
|
|
766
|
+
w.system.GetBroadPhaseQuery().CollidePoint(
|
|
767
|
+
vec3(px, py, pz), collector,
|
|
768
|
+
queryFilters(w, worldH, 1).bp, queryFilters(w, worldH, 1).obj,
|
|
769
|
+
);
|
|
770
|
+
}, maxResults);
|
|
771
|
+
return state.overlapBodies.length;
|
|
772
|
+
}
|
|
773
|
+
export function overlapBox(worldH, px, py, pz, rx, ry, rz, rw, hx, hy, hz, layerMask, maxResults) {
|
|
774
|
+
state.overlapBodies.length = 0;
|
|
775
|
+
void layerMask;
|
|
776
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
777
|
+
const J = JoltModule;
|
|
778
|
+
// Conservative AABB around the rotated box (matches native C++ shim behavior).
|
|
779
|
+
const q = quat(rx, ry, rz, rw);
|
|
780
|
+
const rotMat = q.GetRotation ? q.GetRotation() : null;
|
|
781
|
+
// Jolt.js may not expose Quat.GetRotation; fall back to axis-aligned extents.
|
|
782
|
+
const ex = Math.abs(hx) + Math.abs(hy) * 0.001 + Math.abs(hz) * 0.001;
|
|
783
|
+
const ey = Math.abs(hy) + Math.abs(hx) * 0.001 + Math.abs(hz) * 0.001;
|
|
784
|
+
const ez = Math.abs(hz) + Math.abs(hx) * 0.001 + Math.abs(hy) * 0.001;
|
|
785
|
+
const box = new J.AABox(vec3(px - ex, py - ey, pz - ez), vec3(px + ex, py + ey, pz + ez));
|
|
786
|
+
state.overlapBodies = collectOverlapBodies(worldH, collector => {
|
|
787
|
+
w.system.GetBroadPhaseQuery().CollideAABox(
|
|
788
|
+
box, collector,
|
|
789
|
+
queryFilters(w, worldH, 1).bp, queryFilters(w, worldH, 1).obj,
|
|
790
|
+
);
|
|
791
|
+
}, maxResults);
|
|
792
|
+
void rotMat;
|
|
793
|
+
return state.overlapBodies.length;
|
|
794
|
+
}
|
|
795
|
+
export function overlapBody(i) { return state.overlapBodies[i|0] ?? 0; }
|
|
796
|
+
|
|
797
|
+
// ---------------------------------------------------------------------------
|
|
798
|
+
// Constraints
|
|
799
|
+
// ---------------------------------------------------------------------------
|
|
800
|
+
|
|
801
|
+
function resolveConstraintBodies(bodyAh, bodyBh) {
|
|
802
|
+
const J = JoltModule;
|
|
803
|
+
const ba = state.bodies.get(bodyAh); if (!ba) return null;
|
|
804
|
+
const w = state.worlds.get(ba.world); if (!w) return null;
|
|
805
|
+
// Retrieve the actual Body refs via the lock interface.
|
|
806
|
+
const lockA = new J.BodyLockRead(w.system.GetBodyLockInterface(), ba.bodyId);
|
|
807
|
+
if (!lockA.SucceededAndIsInBroadPhase()) { lockA.ReleaseLock(); return null; }
|
|
808
|
+
const body1 = lockA.GetBody();
|
|
809
|
+
let body2 = J.Body.prototype.sFixedToWorld ?? null;
|
|
810
|
+
let lockB = null;
|
|
811
|
+
if (bodyBh !== 0) {
|
|
812
|
+
const bb = state.bodies.get(bodyBh); if (!bb) { lockA.ReleaseLock(); return null; }
|
|
813
|
+
lockB = new J.BodyLockRead(w.system.GetBodyLockInterface(), bb.bodyId);
|
|
814
|
+
if (!lockB.SucceededAndIsInBroadPhase()) { lockA.ReleaseLock(); lockB.ReleaseLock(); return null; }
|
|
815
|
+
body2 = lockB.GetBody();
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
w, body1, body2,
|
|
819
|
+
release: () => { lockA.ReleaseLock(); if (lockB) lockB.ReleaseLock(); },
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function registerConstraint(worldId, constraint) {
|
|
824
|
+
state.worlds.get(worldId).system.AddConstraint(constraint);
|
|
825
|
+
const h = state.nextConstraint++;
|
|
826
|
+
state.constraints.set(h, { world: worldId, constraint });
|
|
827
|
+
return h;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export function constraintFixed(bodyA, bodyB, ax, ay, az, bx, by, bz, worldSpace) {
|
|
831
|
+
if (warnUninit('constraintFixed')) return 0;
|
|
832
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
833
|
+
const J = JoltModule;
|
|
834
|
+
const settings = new J.FixedConstraintSettings();
|
|
835
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
836
|
+
settings.mPoint1 = rvec3(ax, ay, az);
|
|
837
|
+
settings.mPoint2 = rvec3(bx, by, bz);
|
|
838
|
+
settings.mAutoDetectPoint = false;
|
|
839
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
840
|
+
ctx.release();
|
|
841
|
+
return registerConstraint(ctx.w === state.worlds.get(state.bodies.get(bodyA).world) ? state.bodies.get(bodyA).world : 0, c);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
export function constraintPoint(bodyA, bodyB, ax, ay, az, bx, by, bz, worldSpace) {
|
|
845
|
+
if (warnUninit('constraintPoint')) return 0;
|
|
846
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
847
|
+
const J = JoltModule;
|
|
848
|
+
const settings = new J.PointConstraintSettings();
|
|
849
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
850
|
+
settings.mPoint1 = rvec3(ax, ay, az);
|
|
851
|
+
settings.mPoint2 = rvec3(bx, by, bz);
|
|
852
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
853
|
+
ctx.release();
|
|
854
|
+
return registerConstraint(state.bodies.get(bodyA).world, c);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
export function constraintHinge(bodyA, bodyB, ax, ay, az, bx, by, bz, axX, axY, axZ, lmin, lmax, worldSpace) {
|
|
858
|
+
if (warnUninit('constraintHinge')) return 0;
|
|
859
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
860
|
+
const J = JoltModule;
|
|
861
|
+
const settings = new J.HingeConstraintSettings();
|
|
862
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
863
|
+
settings.mPoint1 = rvec3(ax, ay, az);
|
|
864
|
+
settings.mPoint2 = rvec3(bx, by, bz);
|
|
865
|
+
const axis = vec3(axX, axY, axZ);
|
|
866
|
+
settings.mHingeAxis1 = axis;
|
|
867
|
+
settings.mHingeAxis2 = axis;
|
|
868
|
+
const perp = axis.GetNormalizedPerpendicular ? axis.GetNormalizedPerpendicular() : vec3(1, 0, 0);
|
|
869
|
+
settings.mNormalAxis1 = perp;
|
|
870
|
+
settings.mNormalAxis2 = perp;
|
|
871
|
+
if (lmin < lmax) { settings.mLimitsMin = lmin; settings.mLimitsMax = lmax; }
|
|
872
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
873
|
+
ctx.release();
|
|
874
|
+
return registerConstraint(state.bodies.get(bodyA).world, c);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export function constraintSlider(bodyA, bodyB, ax, ay, az, bx, by, bz, axX, axY, axZ, lmin, lmax, worldSpace) {
|
|
878
|
+
if (warnUninit('constraintSlider')) return 0;
|
|
879
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
880
|
+
const J = JoltModule;
|
|
881
|
+
const settings = new J.SliderConstraintSettings();
|
|
882
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
883
|
+
settings.mPoint1 = rvec3(ax, ay, az);
|
|
884
|
+
settings.mPoint2 = rvec3(bx, by, bz);
|
|
885
|
+
// Normalize axis.
|
|
886
|
+
const len = Math.hypot(axX, axY, axZ) || 1;
|
|
887
|
+
const axis = vec3(axX / len, axY / len, axZ / len);
|
|
888
|
+
settings.mSliderAxis1 = axis;
|
|
889
|
+
settings.mSliderAxis2 = axis;
|
|
890
|
+
const perp = axis.GetNormalizedPerpendicular ? axis.GetNormalizedPerpendicular() : vec3(1, 0, 0);
|
|
891
|
+
settings.mNormalAxis1 = perp;
|
|
892
|
+
settings.mNormalAxis2 = perp;
|
|
893
|
+
if (lmin < lmax) { settings.mLimitsMin = lmin; settings.mLimitsMax = lmax; }
|
|
894
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
895
|
+
ctx.release();
|
|
896
|
+
return registerConstraint(state.bodies.get(bodyA).world, c);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export function constraintDistance(bodyA, bodyB, ax, ay, az, bx, by, bz, minD, maxD, worldSpace) {
|
|
900
|
+
if (warnUninit('constraintDistance')) return 0;
|
|
901
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
902
|
+
const J = JoltModule;
|
|
903
|
+
const settings = new J.DistanceConstraintSettings();
|
|
904
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
905
|
+
settings.mPoint1 = rvec3(ax, ay, az);
|
|
906
|
+
settings.mPoint2 = rvec3(bx, by, bz);
|
|
907
|
+
settings.mMinDistance = minD;
|
|
908
|
+
settings.mMaxDistance = maxD;
|
|
909
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
910
|
+
ctx.release();
|
|
911
|
+
return registerConstraint(state.bodies.get(bodyA).world, c);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// EN-063 (web ragdolls): six-DOF with all three translation axes locked and
|
|
915
|
+
// per-axis rotation limits — the articulation the engine's ragdoll builder
|
|
916
|
+
// uses. Mirrors the native C++ shim's convention: a rotation axis whose
|
|
917
|
+
// min >= max is LOCKED, otherwise limited to [min, max]. Wrapped in
|
|
918
|
+
// try/catch so a JoltPhysics.js build without the SixDOF surface degrades
|
|
919
|
+
// to "no ragdolls" instead of killing the frame.
|
|
920
|
+
export function constraintSixDofLockedTranslation(
|
|
921
|
+
bodyA, bodyB, ax, ay, az, bx, by, bz,
|
|
922
|
+
rxMin, rxMax, ryMin, ryMax, rzMin, rzMax, worldSpace,
|
|
923
|
+
) {
|
|
924
|
+
if (warnUninit('constraintSixDofLockedTranslation')) return 0;
|
|
925
|
+
const ctx = resolveConstraintBodies(bodyA, bodyB); if (!ctx) return 0;
|
|
926
|
+
const J = JoltModule;
|
|
927
|
+
try {
|
|
928
|
+
const settings = new J.SixDOFConstraintSettings();
|
|
929
|
+
settings.mSpace = worldSpace ? J.EConstraintSpace_WorldSpace : J.EConstraintSpace_LocalToBodyCOM;
|
|
930
|
+
settings.mPosition1 = rvec3(ax, ay, az);
|
|
931
|
+
settings.mPosition2 = rvec3(bx, by, bz);
|
|
932
|
+
const AX = [
|
|
933
|
+
[J.SixDOFConstraintSettings_EAxis_TranslationX, 1, -1],
|
|
934
|
+
[J.SixDOFConstraintSettings_EAxis_TranslationY, 1, -1],
|
|
935
|
+
[J.SixDOFConstraintSettings_EAxis_TranslationZ, 1, -1],
|
|
936
|
+
[J.SixDOFConstraintSettings_EAxis_RotationX, rxMin, rxMax],
|
|
937
|
+
[J.SixDOFConstraintSettings_EAxis_RotationY, ryMin, ryMax],
|
|
938
|
+
[J.SixDOFConstraintSettings_EAxis_RotationZ, rzMin, rzMax],
|
|
939
|
+
];
|
|
940
|
+
for (const [axis, lo, hi] of AX) {
|
|
941
|
+
if (lo >= hi) settings.MakeFixedAxis(axis);
|
|
942
|
+
else settings.SetLimitedAxis(axis, lo, hi);
|
|
943
|
+
}
|
|
944
|
+
const c = settings.Create(ctx.body1, ctx.body2);
|
|
945
|
+
ctx.release();
|
|
946
|
+
return registerConstraint(state.bodies.get(bodyA).world, c);
|
|
947
|
+
} catch (e) {
|
|
948
|
+
ctx.release();
|
|
949
|
+
console.warn('[jolt_bridge] SixDOF constraint unavailable:', e);
|
|
950
|
+
return 0;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
export function constraintDestroy(h) {
|
|
955
|
+
const c = state.constraints.get(h); if (!c) return;
|
|
956
|
+
const w = state.worlds.get(c.world); if (w) w.system.RemoveConstraint(c.constraint);
|
|
957
|
+
state.constraints.delete(h);
|
|
958
|
+
}
|
|
959
|
+
export function constraintSetEnabled(h, enabled) {
|
|
960
|
+
const c = state.constraints.get(h); if (!c) return;
|
|
961
|
+
if (c.constraint.SetEnabled) c.constraint.SetEnabled(enabled !== 0);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// ---------------------------------------------------------------------------
|
|
965
|
+
// Contact events (Tier 2 — listener wiring not yet ported to JS)
|
|
966
|
+
// ---------------------------------------------------------------------------
|
|
967
|
+
|
|
968
|
+
export function contactCount() { return state.contacts.length; }
|
|
969
|
+
export function contactField(i, field) {
|
|
970
|
+
const c = state.contacts[i|0]; if (!c) return 0;
|
|
971
|
+
switch (field|0) {
|
|
972
|
+
case 0: return c.event;
|
|
973
|
+
case 1: return c.bodyA;
|
|
974
|
+
case 2: return c.bodyB;
|
|
975
|
+
case 3: return c.pointA[0]; case 4: return c.pointA[1]; case 5: return c.pointA[2];
|
|
976
|
+
case 6: return c.pointB[0]; case 7: return c.pointB[1]; case 8: return c.pointB[2];
|
|
977
|
+
case 9: return c.normal[0]; case 10: return c.normal[1]; case 11: return c.normal[2];
|
|
978
|
+
case 12: return c.penetrationDepth;
|
|
979
|
+
case 13: return c.combinedFriction;
|
|
980
|
+
case 14: return c.combinedRestitution;
|
|
981
|
+
default: return 0;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
export function clearContacts(worldH) { void worldH; state.contacts.length = 0; }
|
|
985
|
+
|
|
986
|
+
// ============================================================================
|
|
987
|
+
// Phase 5 additions — complex shapes + character controller
|
|
988
|
+
// ============================================================================
|
|
989
|
+
|
|
990
|
+
// --- Scratch streams ---
|
|
991
|
+
export function scratchReset() { state.scratchF32.length = 0; state.scratchU32.length = 0; }
|
|
992
|
+
export function scratchPushF32(v) { state.scratchF32.push(v); }
|
|
993
|
+
export function scratchPushU32(v) { state.scratchU32.push(v >>> 0); }
|
|
994
|
+
|
|
995
|
+
// --- Complex shape factories ---
|
|
996
|
+
|
|
997
|
+
export function shapeConvexHull(numPoints, convexRadius) {
|
|
998
|
+
if (warnUninit('shapeConvexHull')) return 0;
|
|
999
|
+
const J = JoltModule;
|
|
1000
|
+
if (state.scratchF32.length < numPoints * 3 || numPoints < 3) return 0;
|
|
1001
|
+
const settings = new J.ConvexHullShapeSettings();
|
|
1002
|
+
// JoltPhysics.js exposes mPoints as a JS-friendly Array<Vec3>.
|
|
1003
|
+
for (let i = 0; i < numPoints; i++) {
|
|
1004
|
+
const p = vec3(state.scratchF32[i*3], state.scratchF32[i*3+1], state.scratchF32[i*3+2]);
|
|
1005
|
+
settings.mPoints.push_back(p);
|
|
1006
|
+
}
|
|
1007
|
+
settings.mConvexRadius = convexRadius;
|
|
1008
|
+
const result = settings.Create();
|
|
1009
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
export function shapeMesh(vertexCount, triangleCount) {
|
|
1013
|
+
if (warnUninit('shapeMesh')) return 0;
|
|
1014
|
+
const J = JoltModule;
|
|
1015
|
+
if (state.scratchF32.length < vertexCount * 3 || state.scratchU32.length < triangleCount * 3) return 0;
|
|
1016
|
+
if (vertexCount === 0 || triangleCount === 0) return 0;
|
|
1017
|
+
const settings = new J.MeshShapeSettings();
|
|
1018
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
1019
|
+
settings.mTriangleVertices.push_back(new J.Float3(
|
|
1020
|
+
state.scratchF32[i*3], state.scratchF32[i*3+1], state.scratchF32[i*3+2]
|
|
1021
|
+
));
|
|
1022
|
+
}
|
|
1023
|
+
for (let t = 0; t < triangleCount; t++) {
|
|
1024
|
+
const idx = new J.IndexedTriangle(
|
|
1025
|
+
state.scratchU32[t*3], state.scratchU32[t*3+1], state.scratchU32[t*3+2], 0
|
|
1026
|
+
);
|
|
1027
|
+
settings.mIndexedTriangles.push_back(idx);
|
|
1028
|
+
}
|
|
1029
|
+
settings.Sanitize();
|
|
1030
|
+
const result = settings.Create();
|
|
1031
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
export function shapeHeightfield(sampleCount, ox, oy, oz, sx, sy, sz, blockSize) {
|
|
1035
|
+
if (warnUninit('shapeHeightfield')) return 0;
|
|
1036
|
+
const J = JoltModule;
|
|
1037
|
+
const need = sampleCount * sampleCount;
|
|
1038
|
+
if (state.scratchF32.length < need || sampleCount < 2) return 0;
|
|
1039
|
+
// The convenience constructor taking a JS Float32Array does not exist in
|
|
1040
|
+
// the npm/CDN JoltPhysics.js builds (Create() returns invalid — the
|
|
1041
|
+
// samples never reach the shape). Canonical pattern from the official
|
|
1042
|
+
// examples: default-construct the settings, size mHeightSamples, and
|
|
1043
|
+
// write the floats straight into the emscripten heap.
|
|
1044
|
+
const settings = new J.HeightFieldShapeSettings();
|
|
1045
|
+
settings.mOffset = vec3(ox, oy, oz);
|
|
1046
|
+
settings.mScale = vec3(sx, sy, sz);
|
|
1047
|
+
settings.mSampleCount = sampleCount;
|
|
1048
|
+
settings.mBlockSize = blockSize || 4;
|
|
1049
|
+
settings.mHeightSamples.resize(need);
|
|
1050
|
+
const heap = new Float32Array(
|
|
1051
|
+
J.HEAPF32.buffer,
|
|
1052
|
+
J.getPointer(settings.mHeightSamples.data()),
|
|
1053
|
+
need,
|
|
1054
|
+
);
|
|
1055
|
+
for (let i = 0; i < need; i++) heap[i] = state.scratchF32[i];
|
|
1056
|
+
const result = settings.Create();
|
|
1057
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// --- Compound builder ---
|
|
1061
|
+
|
|
1062
|
+
export function compoundBegin() { state.compoundChildren.length = 0; }
|
|
1063
|
+
export function compoundAddChild(shape, px, py, pz, rx, ry, rz, rw) {
|
|
1064
|
+
const s = state.shapes.get(shape); if (!s) return;
|
|
1065
|
+
state.compoundChildren.push({ shape: s, px, py, pz, rx, ry, rz, rw });
|
|
1066
|
+
}
|
|
1067
|
+
export function compoundEnd() {
|
|
1068
|
+
if (warnUninit('compoundEnd')) return 0;
|
|
1069
|
+
const J = JoltModule;
|
|
1070
|
+
if (state.compoundChildren.length === 0) return 0;
|
|
1071
|
+
const settings = new J.StaticCompoundShapeSettings();
|
|
1072
|
+
for (const c of state.compoundChildren) {
|
|
1073
|
+
settings.AddShape(vec3(c.px, c.py, c.pz), quat(c.rx, c.ry, c.rz, c.rw), c.shape, 0);
|
|
1074
|
+
}
|
|
1075
|
+
state.compoundChildren.length = 0;
|
|
1076
|
+
const result = settings.Create();
|
|
1077
|
+
return result.IsValid() ? registerShape(result.Get()) : 0;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// --- Character controller (CharacterVirtual) ---
|
|
1081
|
+
|
|
1082
|
+
// Query-filter compatibility + caching. Two JoltPhysics.js API generations
|
|
1083
|
+
// exist: some builds expose `system.GetDefaultBroadPhaseLayerFilter(layer)`,
|
|
1084
|
+
// the npm/CDN 1.0.0 build only has the constructor forms
|
|
1085
|
+
// (`new Jolt.DefaultBroadPhaseLayerFilter(system.GetObjectVsBroadPhaseLayerFilter(), layer)`).
|
|
1086
|
+
// Cached per (world, layer) — the character path runs every frame, and
|
|
1087
|
+
// constructing emscripten objects per call is a real leak.
|
|
1088
|
+
const queryFilterCache = new Map(); // "worldH:layer" → { bp, obj, body, shape }
|
|
1089
|
+
function queryFilters(w, worldH, layer) {
|
|
1090
|
+
const key = worldH + ':' + (layer | 0);
|
|
1091
|
+
let f = queryFilterCache.get(key);
|
|
1092
|
+
if (f) return f;
|
|
1093
|
+
const J = JoltModule;
|
|
1094
|
+
let bp, obj;
|
|
1095
|
+
if (typeof w.system.GetDefaultBroadPhaseLayerFilter === 'function') {
|
|
1096
|
+
bp = w.system.GetDefaultBroadPhaseLayerFilter(layer);
|
|
1097
|
+
obj = w.system.GetDefaultLayerFilter(layer);
|
|
1098
|
+
} else {
|
|
1099
|
+
// npm/CDN builds: the canonical form from the JoltPhysics.js examples —
|
|
1100
|
+
// the filter getters live on the JoltInterface, and constructing the
|
|
1101
|
+
// Default* filters from the raw tables instead trips a null virtual
|
|
1102
|
+
// inside ExtendedUpdate.
|
|
1103
|
+
bp = new J.DefaultBroadPhaseLayerFilter(w.jolt.GetObjectVsBroadPhaseLayerFilter(), layer);
|
|
1104
|
+
obj = new J.DefaultObjectLayerFilter(w.jolt.GetObjectLayerPairFilter(), layer);
|
|
1105
|
+
}
|
|
1106
|
+
f = { bp, obj, body: new J.BodyFilter(), shape: new J.ShapeFilter() };
|
|
1107
|
+
queryFilterCache.set(key, f);
|
|
1108
|
+
return f;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
export function characterCreate(
|
|
1112
|
+
worldH, shapeH,
|
|
1113
|
+
upX, upY, upZ,
|
|
1114
|
+
maxSlopeAngle, characterPadding,
|
|
1115
|
+
penetrationRecoverySpeed, predictiveContactDistance,
|
|
1116
|
+
maxStrength, mass, objectLayer,
|
|
1117
|
+
px, py, pz, rx, ry, rz, rw,
|
|
1118
|
+
) {
|
|
1119
|
+
if (warnUninit('characterCreate')) return 0;
|
|
1120
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
1121
|
+
const shape = state.shapes.get(shapeH); if (!shape) return 0;
|
|
1122
|
+
const J = JoltModule;
|
|
1123
|
+
const settings = new J.CharacterVirtualSettings();
|
|
1124
|
+
settings.mShape = shape;
|
|
1125
|
+
settings.mUp = vec3(upX, upY, upZ);
|
|
1126
|
+
settings.mMaxSlopeAngle = maxSlopeAngle;
|
|
1127
|
+
settings.mCharacterPadding = characterPadding;
|
|
1128
|
+
settings.mPenetrationRecoverySpeed = penetrationRecoverySpeed;
|
|
1129
|
+
settings.mPredictiveContactDistance = predictiveContactDistance;
|
|
1130
|
+
settings.mMaxStrength = maxStrength;
|
|
1131
|
+
settings.mMass = mass;
|
|
1132
|
+
// Two constructor generations exist. -sWASM_BIGINT builds take
|
|
1133
|
+
// (settings, pos, rot, userData: u64-as-BigInt, system); the npm/CDN
|
|
1134
|
+
// builds bind FOUR args (settings, pos, rot, system). Crucially, do NOT
|
|
1135
|
+
// fall back to a 5-arg call with a Number userData: emscripten dispatches
|
|
1136
|
+
// by declared arity, so on a 4-arg build the `0` lands in the SYSTEM slot
|
|
1137
|
+
// and the character is constructed around a null PhysicsSystem — nothing
|
|
1138
|
+
// fails until ExtendedUpdate dies with "null function". The BigInt
|
|
1139
|
+
// TypeError is the reliable discriminator between the generations.
|
|
1140
|
+
let character;
|
|
1141
|
+
try {
|
|
1142
|
+
character = new J.CharacterVirtual(
|
|
1143
|
+
settings, rvec3(px, py, pz), quat(rx, ry, rz, rw), 0n, w.system
|
|
1144
|
+
);
|
|
1145
|
+
} catch {
|
|
1146
|
+
character = new J.CharacterVirtual(
|
|
1147
|
+
settings, rvec3(px, py, pz), quat(rx, ry, rz, rw), w.system
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
const h = state.nextCharacter++;
|
|
1151
|
+
state.characters.set(h, { world: worldH, character, layer: objectLayer | 0 });
|
|
1152
|
+
return h;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
export function characterDestroy(h) {
|
|
1156
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1157
|
+
JoltModule.destroy(e.character);
|
|
1158
|
+
state.characters.delete(h);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export function characterUpdate(h, dt, gx, gy, gz) {
|
|
1162
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1163
|
+
const w = state.worlds.get(e.world); if (!w) return;
|
|
1164
|
+
const J = JoltModule;
|
|
1165
|
+
// Integrate gravity into velocity (match native C shim behaviour).
|
|
1166
|
+
const v = e.character.GetLinearVelocity();
|
|
1167
|
+
const newV = vec3(v.GetX() + gx * dt, v.GetY() + gy * dt, v.GetZ() + gz * dt);
|
|
1168
|
+
e.character.SetLinearVelocity(newV);
|
|
1169
|
+
const settings = new J.ExtendedUpdateSettings();
|
|
1170
|
+
const f = queryFilters(w, e.world, e.layer);
|
|
1171
|
+
e.character.ExtendedUpdate(
|
|
1172
|
+
dt, vec3(gx, gy, gz),
|
|
1173
|
+
settings,
|
|
1174
|
+
f.bp, f.obj, f.body, f.shape,
|
|
1175
|
+
w.jolt.GetTempAllocator()
|
|
1176
|
+
);
|
|
1177
|
+
J.destroy(settings);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
export function characterGetPosition(h, axis) {
|
|
1181
|
+
const e = state.characters.get(h); if (!e) return 0;
|
|
1182
|
+
const p = e.character.GetPosition();
|
|
1183
|
+
if (axis === 0) return p.GetX(); if (axis === 1) return p.GetY(); if (axis === 2) return p.GetZ();
|
|
1184
|
+
return 0;
|
|
1185
|
+
}
|
|
1186
|
+
export function characterGetRotation(h, axis) {
|
|
1187
|
+
const e = state.characters.get(h); if (!e) return axis === 3 ? 1 : 0;
|
|
1188
|
+
const q = e.character.GetRotation();
|
|
1189
|
+
if (axis === 0) return q.GetX(); if (axis === 1) return q.GetY();
|
|
1190
|
+
if (axis === 2) return q.GetZ(); if (axis === 3) return q.GetW();
|
|
1191
|
+
return 0;
|
|
1192
|
+
}
|
|
1193
|
+
export function characterSetPosition(h, x, y, z) {
|
|
1194
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1195
|
+
e.character.SetPosition(rvec3(x, y, z));
|
|
1196
|
+
}
|
|
1197
|
+
export function characterSetRotation(h, x, y, z, w) {
|
|
1198
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1199
|
+
e.character.SetRotation(quat(x, y, z, w));
|
|
1200
|
+
}
|
|
1201
|
+
export function characterGetLinearVelocity(h, axis) {
|
|
1202
|
+
const e = state.characters.get(h); if (!e) return 0;
|
|
1203
|
+
const v = e.character.GetLinearVelocity();
|
|
1204
|
+
if (axis === 0) return v.GetX(); if (axis === 1) return v.GetY(); if (axis === 2) return v.GetZ();
|
|
1205
|
+
return 0;
|
|
1206
|
+
}
|
|
1207
|
+
export function characterSetLinearVelocity(h, x, y, z) {
|
|
1208
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1209
|
+
e.character.SetLinearVelocity(vec3(x, y, z));
|
|
1210
|
+
}
|
|
1211
|
+
export function characterGetGroundState(h) {
|
|
1212
|
+
const e = state.characters.get(h); if (!e) return 3;
|
|
1213
|
+
const J = JoltModule;
|
|
1214
|
+
const s = e.character.GetGroundState();
|
|
1215
|
+
// Map Jolt enum → bj_ground_state.
|
|
1216
|
+
if (s === J.EGroundState_OnGround) return 0;
|
|
1217
|
+
if (s === J.EGroundState_OnSteepGround) return 1;
|
|
1218
|
+
if (s === J.EGroundState_NotSupported) return 2;
|
|
1219
|
+
return 3;
|
|
1220
|
+
}
|
|
1221
|
+
export function characterGetGroundNormal(h, axis) {
|
|
1222
|
+
const e = state.characters.get(h); if (!e) return axis === 1 ? 1 : 0;
|
|
1223
|
+
const n = e.character.GetGroundNormal();
|
|
1224
|
+
if (axis === 0) return n.GetX(); if (axis === 1) return n.GetY(); if (axis === 2) return n.GetZ();
|
|
1225
|
+
return 0;
|
|
1226
|
+
}
|
|
1227
|
+
export function characterGetGroundPosition(h, axis) {
|
|
1228
|
+
const e = state.characters.get(h); if (!e) return 0;
|
|
1229
|
+
const p = e.character.GetGroundPosition();
|
|
1230
|
+
if (axis === 0) return p.GetX(); if (axis === 1) return p.GetY(); if (axis === 2) return p.GetZ();
|
|
1231
|
+
return 0;
|
|
1232
|
+
}
|
|
1233
|
+
export function characterGetGroundBody(h) {
|
|
1234
|
+
const e = state.characters.get(h); if (!e) return 0;
|
|
1235
|
+
const bodyId = e.character.GetGroundBodyID();
|
|
1236
|
+
for (const [bh, b] of state.bodies) {
|
|
1237
|
+
if (b.world === e.world && b.bodyId.GetIndexAndSequenceNumber() === bodyId.GetIndexAndSequenceNumber()) {
|
|
1238
|
+
return bh;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return 0;
|
|
1242
|
+
}
|
|
1243
|
+
export function characterSetShape(h, shapeH) {
|
|
1244
|
+
const e = state.characters.get(h); if (!e) return;
|
|
1245
|
+
const w = state.worlds.get(e.world); if (!w) return;
|
|
1246
|
+
const s = state.shapes.get(shapeH); if (!s) return;
|
|
1247
|
+
const J = JoltModule;
|
|
1248
|
+
const f = queryFilters(w, e.world, e.layer);
|
|
1249
|
+
e.character.SetShape(
|
|
1250
|
+
s, Number.MAX_VALUE,
|
|
1251
|
+
f.bp, f.obj, f.body, f.shape,
|
|
1252
|
+
w.jolt.GetTempAllocator()
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// ---------------------------------------------------------------------------
|
|
1257
|
+
// Soft bodies (Tier 2 — cloth / rope / jelly)
|
|
1258
|
+
// ---------------------------------------------------------------------------
|
|
1259
|
+
|
|
1260
|
+
export function softBodyCreate(
|
|
1261
|
+
worldH, vertexCount, triangleCount,
|
|
1262
|
+
px, py, pz, rx, ry, rz, rw,
|
|
1263
|
+
objectLayer, edgeCompliance, gravityFactor, linearDamping, pressure,
|
|
1264
|
+
) {
|
|
1265
|
+
if (warnUninit('softBodyCreate')) return 0;
|
|
1266
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
1267
|
+
const needF = vertexCount * 4, needU = triangleCount * 3;
|
|
1268
|
+
if (state.scratchF32.length < needF || state.scratchU32.length < needU) return 0;
|
|
1269
|
+
if (vertexCount < 3 || triangleCount === 0) return 0;
|
|
1270
|
+
|
|
1271
|
+
const J = JoltModule;
|
|
1272
|
+
const shared = new J.SoftBodySharedSettings();
|
|
1273
|
+
for (let i = 0; i < vertexCount; i++) {
|
|
1274
|
+
const v = new J.SoftBodySharedSettingsVertex();
|
|
1275
|
+
v.mPosition = new J.Float3(state.scratchF32[i*4], state.scratchF32[i*4+1], state.scratchF32[i*4+2]);
|
|
1276
|
+
v.mVelocity = new J.Float3(0, 0, 0);
|
|
1277
|
+
v.mInvMass = state.scratchF32[i*4+3];
|
|
1278
|
+
shared.mVertices.push_back(v);
|
|
1279
|
+
}
|
|
1280
|
+
for (let t = 0; t < triangleCount; t++) {
|
|
1281
|
+
const f = new J.SoftBodySharedSettingsFace(
|
|
1282
|
+
state.scratchU32[t*3], state.scratchU32[t*3+1], state.scratchU32[t*3+2], 0
|
|
1283
|
+
);
|
|
1284
|
+
shared.AddFace(f);
|
|
1285
|
+
}
|
|
1286
|
+
const attrs = new J.SoftBodySharedSettingsVertexAttributes();
|
|
1287
|
+
attrs.mCompliance = edgeCompliance;
|
|
1288
|
+
attrs.mShearCompliance = edgeCompliance;
|
|
1289
|
+
attrs.mBendCompliance = edgeCompliance;
|
|
1290
|
+
shared.CreateConstraints(attrs, 1, J.EBendType_Distance ?? 0);
|
|
1291
|
+
shared.Optimize();
|
|
1292
|
+
|
|
1293
|
+
const bcs = new J.SoftBodyCreationSettings(shared, rvec3(px, py, pz), quat(rx, ry, rz, rw), objectLayer | 0);
|
|
1294
|
+
bcs.mGravityFactor = gravityFactor;
|
|
1295
|
+
bcs.mLinearDamping = linearDamping;
|
|
1296
|
+
bcs.mPressure = pressure;
|
|
1297
|
+
bcs.mUpdatePosition = true;
|
|
1298
|
+
|
|
1299
|
+
const bodyId = w.bodyInterface.CreateAndAddSoftBody(bcs, J.EActivation_Activate);
|
|
1300
|
+
const h = state.nextBody++;
|
|
1301
|
+
state.bodies.set(h, { world: worldH, bodyId, isSoftBody: true });
|
|
1302
|
+
return h;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function softMotionProperties(h) {
|
|
1306
|
+
const b = state.bodies.get(h); if (!b) return null;
|
|
1307
|
+
const w = state.worlds.get(b.world); if (!w) return null;
|
|
1308
|
+
const J = JoltModule;
|
|
1309
|
+
// Use read-locking to safely access SoftBodyMotionProperties.
|
|
1310
|
+
const lock = new J.BodyLockRead(w.system.GetBodyLockInterface(), b.bodyId);
|
|
1311
|
+
if (!lock.SucceededAndIsInBroadPhase()) { lock.ReleaseLock(); return null; }
|
|
1312
|
+
const body = lock.GetBody();
|
|
1313
|
+
if (!body.IsSoftBody()) { lock.ReleaseLock(); return null; }
|
|
1314
|
+
const mp = J.castObject ? J.castObject(body.GetMotionPropertiesUnchecked(), J.SoftBodyMotionProperties)
|
|
1315
|
+
: body.GetMotionPropertiesUnchecked();
|
|
1316
|
+
return { lock, body, mp };
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
export function softBodyVertexCount(h) {
|
|
1320
|
+
const ctx = softMotionProperties(h); if (!ctx) return 0;
|
|
1321
|
+
const n = ctx.mp.GetVertices ? ctx.mp.GetVertices().size() : 0;
|
|
1322
|
+
ctx.lock.ReleaseLock();
|
|
1323
|
+
return n;
|
|
1324
|
+
}
|
|
1325
|
+
export function softBodyGetVertex(h, idx, axis) {
|
|
1326
|
+
const ctx = softMotionProperties(h); if (!ctx) return 0;
|
|
1327
|
+
const verts = ctx.mp.GetVertices ? ctx.mp.GetVertices() : null;
|
|
1328
|
+
if (!verts || idx >= verts.size()) { ctx.lock.ReleaseLock(); return 0; }
|
|
1329
|
+
const v = verts.at(idx);
|
|
1330
|
+
const local = v.mPosition; // Vec3 in body-local space
|
|
1331
|
+
const xform = ctx.body.GetWorldTransform();
|
|
1332
|
+
const worldPos = xform.Multiply3x4(local);
|
|
1333
|
+
ctx.lock.ReleaseLock();
|
|
1334
|
+
if (axis === 0) return worldPos.GetX();
|
|
1335
|
+
if (axis === 1) return worldPos.GetY();
|
|
1336
|
+
if (axis === 2) return worldPos.GetZ();
|
|
1337
|
+
return 0;
|
|
1338
|
+
}
|
|
1339
|
+
export function softBodySetVertex(h, idx, x, y, z) {
|
|
1340
|
+
const b = state.bodies.get(h); if (!b) return;
|
|
1341
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
1342
|
+
const J = JoltModule;
|
|
1343
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
1344
|
+
if (lock.SucceededAndIsInBroadPhase()) {
|
|
1345
|
+
const body = lock.GetBody();
|
|
1346
|
+
if (body.IsSoftBody()) {
|
|
1347
|
+
const mp = J.castObject ? J.castObject(body.GetMotionPropertiesUnchecked(), J.SoftBodyMotionProperties)
|
|
1348
|
+
: body.GetMotionPropertiesUnchecked();
|
|
1349
|
+
const verts = mp.GetVertices();
|
|
1350
|
+
if (idx < verts.size()) {
|
|
1351
|
+
const xform = body.GetWorldTransform();
|
|
1352
|
+
const inv = xform.Inversed();
|
|
1353
|
+
const local = inv.Multiply3x4(vec3(x, y, z));
|
|
1354
|
+
verts.at(idx).mPosition = local;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
lock.ReleaseLock();
|
|
1359
|
+
}
|
|
1360
|
+
export function softBodySetVertexInvMass(h, idx, invMass) {
|
|
1361
|
+
const b = state.bodies.get(h); if (!b) return;
|
|
1362
|
+
const w = state.worlds.get(b.world); if (!w) return;
|
|
1363
|
+
const J = JoltModule;
|
|
1364
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), b.bodyId);
|
|
1365
|
+
if (lock.SucceededAndIsInBroadPhase()) {
|
|
1366
|
+
const body = lock.GetBody();
|
|
1367
|
+
if (body.IsSoftBody()) {
|
|
1368
|
+
const mp = J.castObject ? J.castObject(body.GetMotionPropertiesUnchecked(), J.SoftBodyMotionProperties)
|
|
1369
|
+
: body.GetMotionPropertiesUnchecked();
|
|
1370
|
+
const verts = mp.GetVertices();
|
|
1371
|
+
if (idx < verts.size()) verts.at(idx).mInvMass = invMass;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
lock.ReleaseLock();
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// ---------------------------------------------------------------------------
|
|
1378
|
+
// Wheeled vehicles (Tier 2 — 4-wheel car)
|
|
1379
|
+
// ---------------------------------------------------------------------------
|
|
1380
|
+
|
|
1381
|
+
export function vehicleCreate(
|
|
1382
|
+
worldH, chassisShapeH,
|
|
1383
|
+
upX, upY, upZ, fwX, fwY, fwZ,
|
|
1384
|
+
w0x, w0y, w0z, w1x, w1y, w1z, w2x, w2y, w2z, w3x, w3y, w3z,
|
|
1385
|
+
wheelRadius, wheelWidth, suspensionMin, suspensionMax,
|
|
1386
|
+
maxSteerAngle, maxBrakeTorque, maxHandbrakeTorque,
|
|
1387
|
+
engineMaxTorque, maxPitchRollAngle, objectLayer,
|
|
1388
|
+
px, py, pz, rx, ry, rz, rw,
|
|
1389
|
+
) {
|
|
1390
|
+
if (warnUninit('vehicleCreate')) return 0;
|
|
1391
|
+
const w = state.worlds.get(worldH); if (!w) return 0;
|
|
1392
|
+
const shape = state.shapes.get(chassisShapeH); if (!shape) return 0;
|
|
1393
|
+
const J = JoltModule;
|
|
1394
|
+
|
|
1395
|
+
// Chassis body.
|
|
1396
|
+
const chassisSettings = new J.BodyCreationSettings(
|
|
1397
|
+
shape, rvec3(px, py, pz), quat(rx, ry, rz, rw),
|
|
1398
|
+
J.EMotionType_Dynamic, objectLayer | 0,
|
|
1399
|
+
);
|
|
1400
|
+
chassisSettings.mOverrideMassProperties = J.EOverrideMassProperties_CalculateInertia;
|
|
1401
|
+
chassisSettings.mMassPropertiesOverride.mMass = 1500.0;
|
|
1402
|
+
const chassisId = w.bodyInterface.CreateAndAddBody(chassisSettings, J.EActivation_Activate);
|
|
1403
|
+
const chassisHandle = state.nextBody++;
|
|
1404
|
+
state.bodies.set(chassisHandle, { world: worldH, bodyId: chassisId });
|
|
1405
|
+
|
|
1406
|
+
// VehicleConstraintSettings.
|
|
1407
|
+
const vcs = new J.VehicleConstraintSettings();
|
|
1408
|
+
vcs.mUp = vec3(upX, upY, upZ);
|
|
1409
|
+
vcs.mForward = vec3(fwX, fwY, fwZ);
|
|
1410
|
+
vcs.mMaxPitchRollAngle = maxPitchRollAngle;
|
|
1411
|
+
|
|
1412
|
+
// 4 wheels — FL/FR steer, RL/RR drive + handbrake.
|
|
1413
|
+
const wheelPositions = [
|
|
1414
|
+
[w0x, w0y, w0z], [w1x, w1y, w1z], [w2x, w2y, w2z], [w3x, w3y, w3z],
|
|
1415
|
+
];
|
|
1416
|
+
for (let i = 0; i < 4; i++) {
|
|
1417
|
+
const wheel = new J.WheelSettingsWV();
|
|
1418
|
+
wheel.mPosition = vec3(wheelPositions[i][0], wheelPositions[i][1], wheelPositions[i][2]);
|
|
1419
|
+
wheel.mRadius = wheelRadius;
|
|
1420
|
+
wheel.mWidth = wheelWidth;
|
|
1421
|
+
wheel.mSuspensionMinLength = suspensionMin;
|
|
1422
|
+
wheel.mSuspensionMaxLength = suspensionMax;
|
|
1423
|
+
wheel.mMaxSteerAngle = (i < 2) ? maxSteerAngle : 0.0;
|
|
1424
|
+
wheel.mMaxBrakeTorque = maxBrakeTorque;
|
|
1425
|
+
wheel.mMaxHandBrakeTorque = (i >= 2) ? maxHandbrakeTorque : 0.0;
|
|
1426
|
+
vcs.mWheels.push_back(wheel);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// WheeledVehicleController with a single rear-axle differential.
|
|
1430
|
+
const controller = new J.WheeledVehicleControllerSettings();
|
|
1431
|
+
controller.mEngine.mMaxTorque = engineMaxTorque;
|
|
1432
|
+
const diff = new J.VehicleDifferentialSettings();
|
|
1433
|
+
diff.mLeftWheel = 2;
|
|
1434
|
+
diff.mRightWheel = 3;
|
|
1435
|
+
controller.mDifferentials.push_back(diff);
|
|
1436
|
+
vcs.mController = controller;
|
|
1437
|
+
|
|
1438
|
+
// Lock chassis to construct the constraint.
|
|
1439
|
+
const lock = new J.BodyLockWrite(w.system.GetBodyLockInterface(), chassisId);
|
|
1440
|
+
if (!lock.SucceededAndIsInBroadPhase()) { lock.ReleaseLock(); return 0; }
|
|
1441
|
+
const constraint = new J.VehicleConstraint(lock.GetBody(), vcs);
|
|
1442
|
+
lock.ReleaseLock();
|
|
1443
|
+
|
|
1444
|
+
const tester = new J.VehicleCollisionTesterRay(OBJECT_LAYER_NON_MOVING, vec3(upX, upY, upZ));
|
|
1445
|
+
constraint.SetVehicleCollisionTester(tester);
|
|
1446
|
+
w.system.AddConstraint(constraint);
|
|
1447
|
+
w.system.AddStepListener(constraint);
|
|
1448
|
+
|
|
1449
|
+
const vh = state.nextConstraint++;
|
|
1450
|
+
state.constraints.set(vh, {
|
|
1451
|
+
world: worldH, constraint, tester,
|
|
1452
|
+
isVehicle: true, chassisHandle, chassisId,
|
|
1453
|
+
});
|
|
1454
|
+
return vh;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
export function vehicleDestroy(h) {
|
|
1458
|
+
const v = state.constraints.get(h); if (!v || !v.isVehicle) return;
|
|
1459
|
+
const w = state.worlds.get(v.world); if (!w) return;
|
|
1460
|
+
w.system.RemoveStepListener(v.constraint);
|
|
1461
|
+
w.system.RemoveConstraint(v.constraint);
|
|
1462
|
+
if (w.bodyInterface.IsAdded(v.chassisId)) w.bodyInterface.RemoveBody(v.chassisId);
|
|
1463
|
+
w.bodyInterface.DestroyBody(v.chassisId);
|
|
1464
|
+
state.bodies.delete(v.chassisHandle);
|
|
1465
|
+
state.constraints.delete(h);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
export function vehicleGetChassis(h) {
|
|
1469
|
+
const v = state.constraints.get(h); return (v && v.isVehicle) ? v.chassisHandle : 0;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
export function vehicleSetInput(h, forward, right, brake, handbrake) {
|
|
1473
|
+
const v = state.constraints.get(h); if (!v || !v.isVehicle) return;
|
|
1474
|
+
const J = JoltModule;
|
|
1475
|
+
const ctrl = J.castObject ? J.castObject(v.constraint.GetController(), J.WheeledVehicleController)
|
|
1476
|
+
: v.constraint.GetController();
|
|
1477
|
+
ctrl.SetDriverInput(forward, right, brake, handbrake);
|
|
1478
|
+
if (forward !== 0 || brake !== 0 || handbrake !== 0 || right !== 0) {
|
|
1479
|
+
const w = state.worlds.get(v.world);
|
|
1480
|
+
if (w) w.bodyInterface.ActivateBody(v.chassisId);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export function vehicleGetWheelTransform(h, wheelIndex, axis) {
|
|
1485
|
+
const v = state.constraints.get(h); if (!v || !v.isVehicle) return 0;
|
|
1486
|
+
const J = JoltModule;
|
|
1487
|
+
const xform = v.constraint.GetWheelWorldTransform(wheelIndex, J.Vec3.prototype.sAxisY(), J.Vec3.prototype.sAxisX());
|
|
1488
|
+
if (axis < 3) {
|
|
1489
|
+
const p = xform.GetTranslation();
|
|
1490
|
+
if (axis === 0) return p.GetX();
|
|
1491
|
+
if (axis === 1) return p.GetY();
|
|
1492
|
+
return p.GetZ();
|
|
1493
|
+
}
|
|
1494
|
+
if (axis < 7) {
|
|
1495
|
+
const q = xform.GetQuaternion();
|
|
1496
|
+
if (axis === 3) return q.GetX();
|
|
1497
|
+
if (axis === 4) return q.GetY();
|
|
1498
|
+
if (axis === 5) return q.GetZ();
|
|
1499
|
+
return q.GetW();
|
|
1500
|
+
}
|
|
1501
|
+
return 0;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
export function vehicleGetEngineRpm(h) {
|
|
1505
|
+
const v = state.constraints.get(h); if (!v || !v.isVehicle) return 0;
|
|
1506
|
+
const J = JoltModule;
|
|
1507
|
+
const ctrl = J.castObject ? J.castObject(v.constraint.GetController(), J.WheeledVehicleController)
|
|
1508
|
+
: v.constraint.GetController();
|
|
1509
|
+
return ctrl.GetEngine().GetCurrentRPM();
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
export function vehicleGetWheelAngularVelocity(h, wheelIndex) {
|
|
1513
|
+
const v = state.constraints.get(h); if (!v || !v.isVehicle) return 0;
|
|
1514
|
+
const J = JoltModule;
|
|
1515
|
+
const wheel = v.constraint.GetWheel(wheelIndex);
|
|
1516
|
+
if (!wheel) return 0;
|
|
1517
|
+
const wv = J.castObject ? J.castObject(wheel, J.WheelWV) : wheel;
|
|
1518
|
+
return wv.GetAngularVelocity ? wv.GetAngularVelocity() : 0;
|
|
1519
|
+
}
|