@iwsdk/core 0.5.3 → 1.0.0-rc.1

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.
Files changed (233) hide show
  1. package/README.md +22 -1
  2. package/dist/asset/asset-manager.d.ts.map +1 -1
  3. package/dist/asset/asset-manager.js +3 -2
  4. package/dist/asset/asset-manager.js.map +1 -1
  5. package/dist/asset/cache-manager.js.map +1 -1
  6. package/dist/asset/loaders/audio-loader.js.map +1 -1
  7. package/dist/asset/loaders/cached-asset-load.js.map +1 -1
  8. package/dist/asset/loaders/gltf-loader.js.map +1 -1
  9. package/dist/asset/loaders/hdr-texture-loader.js.map +1 -1
  10. package/dist/asset/loaders/texture-loader.js.map +1 -1
  11. package/dist/asset/loaders/uikitml-loader.js.map +1 -1
  12. package/dist/audio/audio-pool.js.map +1 -1
  13. package/dist/audio/audio-system.js.map +1 -1
  14. package/dist/audio/audio-utils.js.map +1 -1
  15. package/dist/audio/audio.js.map +1 -1
  16. package/dist/camera/camera-source.js.map +1 -1
  17. package/dist/camera/camera-system.d.ts +7 -3
  18. package/dist/camera/camera-system.d.ts.map +1 -1
  19. package/dist/camera/camera-system.js +81 -50
  20. package/dist/camera/camera-system.js.map +1 -1
  21. package/dist/camera/camera-utils.d.ts +5 -0
  22. package/dist/camera/camera-utils.d.ts.map +1 -1
  23. package/dist/camera/camera-utils.js +8 -1
  24. package/dist/camera/camera-utils.js.map +1 -1
  25. package/dist/camera/types.js.map +1 -1
  26. package/dist/depth/depth-occludable.js.map +1 -1
  27. package/dist/depth/depth-sensing-system.d.ts +3 -0
  28. package/dist/depth/depth-sensing-system.d.ts.map +1 -1
  29. package/dist/depth/depth-sensing-system.js +55 -1
  30. package/dist/depth/depth-sensing-system.js.map +1 -1
  31. package/dist/depth/depth-textures.js.map +1 -1
  32. package/dist/depth/occlusion/preprocessing-pass.js.map +1 -1
  33. package/dist/depth/occlusion/preprocessing-shader.glsl.js.map +1 -1
  34. package/dist/ecs/builtin-components.js.map +1 -1
  35. package/dist/ecs/component-editor-metadata.js.map +1 -1
  36. package/dist/ecs/component-manifest.js.map +1 -1
  37. package/dist/ecs/entity.js.map +1 -1
  38. package/dist/ecs/system.js.map +1 -1
  39. package/dist/ecs/world.d.ts +2 -0
  40. package/dist/ecs/world.d.ts.map +1 -1
  41. package/dist/ecs/world.js +4 -0
  42. package/dist/ecs/world.js.map +1 -1
  43. package/dist/environment/dome-gradient.js.map +1 -1
  44. package/dist/environment/dome-texture.js.map +1 -1
  45. package/dist/environment/environment-system.js.map +1 -1
  46. package/dist/environment/gradient-environment.js.map +1 -1
  47. package/dist/environment/ibl-gradient.js.map +1 -1
  48. package/dist/environment/ibl-texture.js.map +1 -1
  49. package/dist/environment/scene-ar-environment.js.map +1 -1
  50. package/dist/environment-raycast/environment-raycast.js.map +1 -1
  51. package/dist/environment-raycast/raycast-target.js.map +1 -1
  52. package/dist/grab/distance-grabbable.d.ts +5 -0
  53. package/dist/grab/distance-grabbable.d.ts.map +1 -1
  54. package/dist/grab/distance-grabbable.js +5 -0
  55. package/dist/grab/distance-grabbable.js.map +1 -1
  56. package/dist/grab/grab-helpers.js.map +1 -1
  57. package/dist/grab/grab-system.js.map +1 -1
  58. package/dist/grab/grab-warnings.js.map +1 -1
  59. package/dist/grab/grabbed.js.map +1 -1
  60. package/dist/grab/handles.d.ts +6 -0
  61. package/dist/grab/handles.d.ts.map +1 -1
  62. package/dist/grab/handles.js +46 -12
  63. package/dist/grab/handles.js.map +1 -1
  64. package/dist/grab/index.js.map +1 -1
  65. package/dist/grab/one-hand-grabbable.d.ts +5 -0
  66. package/dist/grab/one-hand-grabbable.d.ts.map +1 -1
  67. package/dist/grab/one-hand-grabbable.js +5 -0
  68. package/dist/grab/one-hand-grabbable.js.map +1 -1
  69. package/dist/grab/pointer-events-patch.js.map +1 -1
  70. package/dist/grab/two-hands-grabbable.d.ts +5 -0
  71. package/dist/grab/two-hands-grabbable.d.ts.map +1 -1
  72. package/dist/grab/two-hands-grabbable.js +5 -0
  73. package/dist/grab/two-hands-grabbable.js.map +1 -1
  74. package/dist/index.js +4 -3
  75. package/dist/index.js.map +1 -1
  76. package/dist/init/browser-camera.js.map +1 -1
  77. package/dist/init/index.d.ts +2 -2
  78. package/dist/init/index.d.ts.map +1 -1
  79. package/dist/init/player-camera.js.map +1 -1
  80. package/dist/init/session-grant.d.ts +34 -0
  81. package/dist/init/session-grant.d.ts.map +1 -0
  82. package/dist/init/session-grant.js +89 -0
  83. package/dist/init/session-grant.js.map +1 -0
  84. package/dist/init/world-initializer.d.ts +87 -4
  85. package/dist/init/world-initializer.d.ts.map +1 -1
  86. package/dist/init/world-initializer.js +45 -41
  87. package/dist/init/world-initializer.js.map +1 -1
  88. package/dist/init/xr.d.ts +19 -0
  89. package/dist/init/xr.d.ts.map +1 -1
  90. package/dist/init/xr.js +136 -41
  91. package/dist/init/xr.js.map +1 -1
  92. package/dist/input/canvas-pointer-system.d.ts +2 -0
  93. package/dist/input/canvas-pointer-system.d.ts.map +1 -1
  94. package/dist/input/canvas-pointer-system.js +33 -13
  95. package/dist/input/canvas-pointer-system.js.map +1 -1
  96. package/dist/input/input-actions.js.map +1 -1
  97. package/dist/input/input-manager.js.map +1 -1
  98. package/dist/input/input-system.d.ts.map +1 -1
  99. package/dist/input/input-system.js +5 -2
  100. package/dist/input/input-system.js.map +1 -1
  101. package/dist/input/state-tags.js.map +1 -1
  102. package/dist/input/stateful-browser-gamepad.js.map +1 -1
  103. package/dist/input/stateful-keyboard.js.map +1 -1
  104. package/dist/layers/xr-cylinder-layer.d.ts +2 -1
  105. package/dist/layers/xr-cylinder-layer.d.ts.map +1 -1
  106. package/dist/layers/xr-cylinder-layer.js +2 -1
  107. package/dist/layers/xr-cylinder-layer.js.map +1 -1
  108. package/dist/layers/xr-layer-init.d.ts +19 -0
  109. package/dist/layers/xr-layer-init.d.ts.map +1 -0
  110. package/dist/layers/xr-layer-init.js +20 -0
  111. package/dist/layers/xr-layer-init.js.map +1 -0
  112. package/dist/layers/xr-layer-state.js.map +1 -1
  113. package/dist/layers/xr-layer-system.d.ts +41 -1
  114. package/dist/layers/xr-layer-system.d.ts.map +1 -1
  115. package/dist/layers/xr-layer-system.js +506 -132
  116. package/dist/layers/xr-layer-system.js.map +1 -1
  117. package/dist/layers/xr-layer-transform.d.ts +18 -0
  118. package/dist/layers/xr-layer-transform.d.ts.map +1 -0
  119. package/dist/layers/xr-layer-transform.js +115 -0
  120. package/dist/layers/xr-layer-transform.js.map +1 -0
  121. package/dist/layers/xr-quad-layer.d.ts +2 -1
  122. package/dist/layers/xr-quad-layer.d.ts.map +1 -1
  123. package/dist/layers/xr-quad-layer.js +2 -1
  124. package/dist/layers/xr-quad-layer.js.map +1 -1
  125. package/dist/level/level-component-applier.d.ts +2 -0
  126. package/dist/level/level-component-applier.d.ts.map +1 -1
  127. package/dist/level/level-component-applier.js +24 -5
  128. package/dist/level/level-component-applier.js.map +1 -1
  129. package/dist/level/level-player-rig.js.map +1 -1
  130. package/dist/level/level-root.js.map +1 -1
  131. package/dist/level/level-scene-environment.js.map +1 -1
  132. package/dist/level/level-scene-json-importer.js.map +1 -1
  133. package/dist/level/level-scene-material.js.map +1 -1
  134. package/dist/level/level-scene-object.js.map +1 -1
  135. package/dist/level/level-scene-primitive.js.map +1 -1
  136. package/dist/level/level-system.js.map +1 -1
  137. package/dist/level/level-tag.js.map +1 -1
  138. package/dist/lighting/light-binding.js.map +1 -1
  139. package/dist/lighting/light-components.js.map +1 -1
  140. package/dist/lighting/light-system.js.map +1 -1
  141. package/dist/locomotion/geometries/beveled-cylinder.js.map +1 -1
  142. package/dist/locomotion/locomotion-environment.js.map +1 -1
  143. package/dist/locomotion/locomotion-input-provider.js.map +1 -1
  144. package/dist/locomotion/locomotion.js.map +1 -1
  145. package/dist/locomotion/materials/line.js.map +1 -1
  146. package/dist/locomotion/slide.js.map +1 -1
  147. package/dist/locomotion/teleport.js.map +1 -1
  148. package/dist/locomotion/turn.js.map +1 -1
  149. package/dist/mcp/ecs-debug-tools.d.ts +4 -1
  150. package/dist/mcp/ecs-debug-tools.d.ts.map +1 -1
  151. package/dist/mcp/ecs-debug-tools.js +113 -10
  152. package/dist/mcp/ecs-debug-tools.js.map +1 -1
  153. package/dist/mcp/index.d.ts +1 -0
  154. package/dist/mcp/index.d.ts.map +1 -1
  155. package/dist/mcp/mcp-runtime.d.ts +2 -0
  156. package/dist/mcp/mcp-runtime.d.ts.map +1 -1
  157. package/dist/mcp/mcp-runtime.js +41 -0
  158. package/dist/mcp/mcp-runtime.js.map +1 -1
  159. package/dist/mcp/scene-tools.js.map +1 -1
  160. package/dist/mcp/ui-debug-tools.d.ts +56 -0
  161. package/dist/mcp/ui-debug-tools.d.ts.map +1 -0
  162. package/dist/mcp/ui-debug-tools.js +412 -0
  163. package/dist/mcp/ui-debug-tools.js.map +1 -0
  164. package/dist/physics/index.js +1 -1
  165. package/dist/physics/physics-runtime.d.ts +22 -0
  166. package/dist/physics/physics-runtime.d.ts.map +1 -0
  167. package/dist/physics/physics-runtime.js +423 -0
  168. package/dist/physics/physics-runtime.js.map +1 -0
  169. package/dist/physics/physics-system.d.ts +84 -108
  170. package/dist/physics/physics-system.d.ts.map +1 -1
  171. package/dist/physics/physics-system.js +686 -555
  172. package/dist/physics/physics-system.js.map +1 -1
  173. package/dist/physics/physics-transport.d.ts +20 -0
  174. package/dist/physics/physics-transport.d.ts.map +1 -0
  175. package/dist/physics/physics-transport.js +111 -0
  176. package/dist/physics/physics-transport.js.map +1 -0
  177. package/dist/physics/physics-worker-protocol.d.ts +144 -0
  178. package/dist/physics/physics-worker-protocol.d.ts.map +1 -0
  179. package/dist/physics/physics-worker-protocol.js +41 -0
  180. package/dist/physics/physics-worker-protocol.js.map +1 -0
  181. package/dist/physics/physics-worker-utils.d.ts +8 -0
  182. package/dist/physics/physics-worker-utils.d.ts.map +1 -0
  183. package/dist/physics/physics-worker-utils.js +12 -0
  184. package/dist/physics/physics-worker-utils.js.map +1 -0
  185. package/dist/physics/physics-worker.d.ts +8 -0
  186. package/dist/physics/physics-worker.d.ts.map +1 -0
  187. package/dist/physics/physics-worker.js +66 -0
  188. package/dist/physics/physics-worker.js.map +1 -0
  189. package/dist/physics/physicsBody.js.map +1 -1
  190. package/dist/physics/physicsManipulation.js.map +1 -1
  191. package/dist/physics/physicsShape.js.map +1 -1
  192. package/dist/physics/utils.js.map +1 -1
  193. package/dist/project/normalize.js +7 -1
  194. package/dist/project/normalize.js.map +1 -1
  195. package/dist/project/paths.js.map +1 -1
  196. package/dist/project/types.d.ts +7 -1
  197. package/dist/project/types.d.ts.map +1 -1
  198. package/dist/project/types.js.map +1 -1
  199. package/dist/project/validation.js +36 -2
  200. package/dist/project/validation.js.map +1 -1
  201. package/dist/runtime/three.js.map +1 -1
  202. package/dist/scene-understanding/anchor.js.map +1 -1
  203. package/dist/scene-understanding/mesh.js.map +1 -1
  204. package/dist/scene-understanding/plane.js.map +1 -1
  205. package/dist/scene-understanding/scene-understanding-system.d.ts +42 -6
  206. package/dist/scene-understanding/scene-understanding-system.d.ts.map +1 -1
  207. package/dist/scene-understanding/scene-understanding-system.js +391 -106
  208. package/dist/scene-understanding/scene-understanding-system.js.map +1 -1
  209. package/dist/schemas/iwsdk-project.v1.schema.json +20 -1
  210. package/dist/transform/synced-euler.js.map +1 -1
  211. package/dist/transform/synced-quaternion.js.map +1 -1
  212. package/dist/transform/synced-vector3.js.map +1 -1
  213. package/dist/transform/transform-component.js.map +1 -1
  214. package/dist/transform/transform-utils.js.map +1 -1
  215. package/dist/transform/transform.js.map +1 -1
  216. package/dist/ui/document.js.map +1 -1
  217. package/dist/ui/follow-component.js.map +1 -1
  218. package/dist/ui/follow.d.ts.map +1 -1
  219. package/dist/ui/follow.js +0 -1
  220. package/dist/ui/follow.js.map +1 -1
  221. package/dist/ui/panel-components.js.map +1 -1
  222. package/dist/ui/screenspace-component.js.map +1 -1
  223. package/dist/ui/screenspace.js.map +1 -1
  224. package/dist/ui/ui.js.map +1 -1
  225. package/dist/ui/uikitml-asset.js.map +1 -1
  226. package/dist/ui/uikitml.js.map +1 -1
  227. package/dist/version.d.ts +1 -1
  228. package/dist/version.d.ts.map +1 -1
  229. package/dist/version.js +1 -1
  230. package/dist/version.js.map +1 -1
  231. package/dist/visibility/visibility-component.js.map +1 -1
  232. package/dist/visibility/visibility.js.map +1 -1
  233. package/package.json +5 -5
@@ -1,4 +1,7 @@
1
+ import { effect } from '@preact/signals-core';
1
2
  import '../runtime/three.js';
3
+ import { createPhysicsTransport } from './physics-transport.js';
4
+ import { physicsExchangeByteLength, PHYSICS_HEADER_WORDS, PHYSICS_PROTOCOL_VERSION, getCapsuleAxisEndpoints, PHYSICS_OUTPUT_RECORD_FLOATS, PHYSICS_INPUT_RECORD_FLOATS } from './physics-worker-protocol.js';
2
5
  import { PhysicsBody, DEFAULT_ANGULAR_DAMPING, DEFAULT_LINEAR_DAMPING, DEFAULT_GRAVITY_FACTOR, PhysicsState } from './physicsBody.js';
3
6
  import { PhysicsManipulation } from './physicsManipulation.js';
4
7
  import { PhysicsShape, PhysicsShapeType } from './physicsShape.js';
@@ -6,7 +9,7 @@ import { detectShapeFromGeometry, generateMergedGeometry, sequentialIndices } fr
6
9
  import { createSystem } from '../ecs/system.js';
7
10
  import { Types, ne } from 'elics';
8
11
  import { Grabbed } from '../grab/grabbed.js';
9
- import { Vector3, Matrix4, Mesh } from 'three';
12
+ import { Mesh } from 'three';
10
13
 
11
14
  /**
12
15
  * Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -15,25 +18,11 @@ import { Vector3, Matrix4, Mesh } from 'three';
15
18
  * LICENSE file in the root directory of this source tree.
16
19
  */
17
20
  const ZERO_VECTOR = [0, 0, 0];
18
- function getCapsuleAxisEndpoints(radius, totalHeight) {
19
- if (!Number.isFinite(radius) ||
20
- !Number.isFinite(totalHeight) ||
21
- radius <= 0 ||
22
- totalHeight < radius * 2) {
23
- return null;
24
- }
25
- const halfSegment = totalHeight / 2 - radius;
26
- if (halfSegment === 0) {
27
- return [
28
- [0, 0, 0],
29
- [0, 0, 0],
30
- ];
31
- }
32
- return [
33
- [0, -halfSegment, 0],
34
- [0, halfSegment, 0],
35
- ];
36
- }
21
+ const EXCHANGE_BUFFER_COUNT = 2;
22
+ const INITIAL_RESULT_CAPACITY = 64;
23
+ const DEFAULT_UPDATE_FREQUENCY = 60;
24
+ const MAX_UPDATE_FREQUENCY = 240;
25
+ const MAX_ACCUMULATED_STEPS = 4;
37
26
  function vector3ToArray(value) {
38
27
  return 'x' in value
39
28
  ? [value.x, value.y, value.z]
@@ -44,35 +33,41 @@ function quaternionToArray(value) {
44
33
  ? [value.x, value.y, value.z, value.w]
45
34
  : [value[0], value[1], value[2], value[3]];
46
35
  }
36
+ function createPendingCommand(handle) {
37
+ return {
38
+ handle,
39
+ flags: 0,
40
+ position: [0, 0, 0],
41
+ quaternion: [0, 0, 0, 1],
42
+ impulsePoint: [0, 0, 0],
43
+ impulse: [0, 0, 0],
44
+ linearVelocity: [0, 0, 0],
45
+ angularVelocity: [0, 0, 0],
46
+ gravityFactor: DEFAULT_GRAVITY_FACTOR,
47
+ linearDamping: DEFAULT_LINEAR_DAMPING,
48
+ angularDamping: DEFAULT_ANGULAR_DAMPING,
49
+ };
50
+ }
51
+ function isZeroVector(value) {
52
+ return value[0] === 0 && value[1] === 0 && value[2] === 0;
53
+ }
47
54
  /**
48
- * Manages physics simulation using the Havok physics engine.
49
- *
50
- * @remarks
51
- * - Initializes Havok physics engine and creates a physics world with gravity.
52
- * - Supports automatic physics shapes creation based on entity geometry when {@link PhysicsShapeType.Auto} is used.
53
- * - Supports multiple collision shapes: Sphere, Box, Cylinder, ConvexHull, and TriMesh.
54
- * - Synchronizes physics body transforms with Three.js Object3D positions and rotations using {@link PhysicsBody}.
55
- * - Handles physics manipulations like applying forces and setting velocities in {@link PhysicsManipulation}.
56
- * - Automatically cleans up physics resources when entities are removed.
55
+ * Manages a Havok simulation on either a dedicated worker or the main thread.
57
56
  *
58
- * @example Basic physics setup
59
- * ```ts
60
- * // Add to your world to enable physics
61
- * world.addSystem(PhysicsSystem)
57
+ * Per-frame commands and poses travel through a pair of transferable
58
+ * ArrayBuffers.
59
+ * Both execution modes use the same binary protocol and shared Havok runtime.
60
+ * Worker mode alternates ownership of transferable ArrayBuffers, avoiding both
61
+ * structured-clone copies and SharedArrayBuffer's cross-origin-isolation
62
+ * requirement. Shape creation/removal uses infrequent control messages.
62
63
  *
63
- * // Create a dynamic box that falls due to gravity
64
- * const box = world.createTransformEntity(boxMesh)
65
- * box.addComponent(PhysicsShape, {
66
- * shape: PhysicsShapeType.Box,
67
- * dimensions: [1, 1, 1]
68
- * })
69
- * box.addComponent(PhysicsBody, { state: PhysicsState.Dynamic })
70
- * ```
64
+ * @remarks
65
+ * - Supports Sphere, Box, Cylinder, Capsule, ConvexHull, TriMesh, and Auto shapes.
66
+ * - Synchronizes dynamic body poses and velocities back to ECS entities.
67
+ * - Sends grab targets, impulses, velocity edits, damping, and gravity changes through one transport contract.
68
+ * - Automatically releases bodies and shapes when entities are removed.
71
69
  *
72
70
  * @category Physics
73
- * @see {@link PhysicsBody}
74
- * @see {@link PhysicsShape}
75
- * @see {@link PhysicsManipulation}
76
71
  */
77
72
  class PhysicsSystem extends createSystem({
78
73
  physicsEntities: {
@@ -82,11 +77,6 @@ class PhysicsSystem extends createSystem({
82
77
  required: [PhysicsBody, PhysicsManipulation],
83
78
  where: [ne(PhysicsBody, '_engineBody', 0)],
84
79
  },
85
- // Bodies whose gravityFactor / linearDamping / angularDamping differ from
86
- // the defaults. The value predicate keeps each set limited to overridden
87
- // bodies, so the common case (default values) is never visited by the
88
- // per-frame reactive sync below, and the queries re-fire whenever the field
89
- // is `setValue`-d.
90
80
  gravityOverrides: {
91
81
  required: [PhysicsBody, PhysicsShape],
92
82
  where: [ne(PhysicsBody, 'gravityFactor', DEFAULT_GRAVITY_FACTOR)],
@@ -101,612 +91,753 @@ class PhysicsSystem extends createSystem({
101
91
  },
102
92
  }, {
103
93
  gravity: { type: Types.Vec3, default: [0, -9.81, 0] },
94
+ useWorker: { type: Types.Boolean, default: true },
95
+ updateFrequency: { type: Types.Float32, default: DEFAULT_UPDATE_FREQUENCY },
96
+ interpolation: { type: Types.Boolean, default: true },
104
97
  }) {
105
98
  constructor() {
106
99
  super(...arguments);
107
- this.scaleBuffer = new Vector3();
108
- this.matrixBuffer = new Matrix4();
100
+ this.transportReady = false;
101
+ this.nextBodyHandle = 1;
102
+ this.nextSequence = 1;
103
+ this.bodyEntities = new Map();
104
+ this.bodySnapshots = new Map();
105
+ this.bodyResultSequenceFloors = new Map();
106
+ this.failedBodyEntities = new WeakSet();
107
+ this.pendingCommands = new Map();
108
+ this.commandPool = [];
109
+ this.freeExchangeBuffers = [];
110
+ this.completedExchanges = [];
111
+ this.inFlightExchangeCount = 0;
112
+ this.stepAccumulator = 0;
113
+ this.debugFlushWaiters = new Set();
109
114
  }
110
- /** Float32 view over the current Havok heap buffer (see {@link heapFloatView}). */
111
- getHeapFloatView() {
112
- const buffer = this.havok.HEAPU8.buffer;
113
- if (!this.heapFloatView || this.heapFloatView.buffer !== buffer) {
114
- this.heapFloatView = new Float32Array(buffer);
115
- }
116
- return this.heapFloatView;
117
- }
118
- async init() {
119
- const { default: HavokPhysics } = await import('@babylonjs/havok');
120
- this.havok = await HavokPhysics();
121
- this.havokWorld = this.havok.HP_World_Create()[1];
122
- this.havok.HP_World_SetGravity(this.havokWorld, this.config.gravity.value);
123
- // Unified cleanup
124
- this.queries.physicsEntities.subscribe('disqualify', (entity) => {
125
- if (!this.havok || !this.havokWorld) {
126
- return;
127
- }
128
- const engineShape = entity.getValue(PhysicsShape, '_engineShape');
129
- if (engineShape) {
130
- this.havok.HP_Shape_Release([BigInt(engineShape)]);
131
- }
132
- const engineBody = entity.getValue(PhysicsBody, '_engineBody');
133
- if (engineBody) {
134
- this.havok.HP_World_RemoveBody(this.havokWorld, [BigInt(engineBody)]);
135
- }
136
- });
115
+ init() {
116
+ var _a;
117
+ const initialByteLength = physicsExchangeByteLength(0, INITIAL_RESULT_CAPACITY);
118
+ for (let index = 0; index < EXCHANGE_BUFFER_COUNT; index++) {
119
+ this.freeExchangeBuffers.push(new ArrayBuffer(initialByteLength));
120
+ }
121
+ try {
122
+ const transport = createPhysicsTransport(this.config.useWorker.value);
123
+ transport.onmessage = (event) => this.handleTransportMessage(event.data);
124
+ transport.onerror = (event) => {
125
+ this.handleTransportFailure(`Physics transport error: ${event.message}`);
126
+ };
127
+ transport.onmessageerror = () => {
128
+ this.handleTransportFailure('Physics transport message could not be decoded');
129
+ };
130
+ this.transport = transport;
131
+ transport.postMessage({
132
+ type: 'init',
133
+ gravity: [...this.config.gravity.value],
134
+ });
135
+ this.cleanupFuncs.push(effect(() => {
136
+ var _a;
137
+ const gravity = this.config.gravity.value;
138
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.postMessage({
139
+ type: 'set-gravity',
140
+ gravity: [gravity[0], gravity[1], gravity[2]],
141
+ });
142
+ }));
143
+ }
144
+ catch (error) {
145
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.terminate();
146
+ this.transport = undefined;
147
+ console.error(this.config.useWorker.value
148
+ ? 'Failed to start physics worker. Set features.physics.useWorker to false to run physics on the main thread:'
149
+ : 'Failed to start main-thread physics:', error);
150
+ }
151
+ this.cleanupFuncs.push(this.queries.physicsEntities.subscribe('disqualify', (entity) => {
152
+ this.removeBody(entity);
153
+ }));
137
154
  this.subscribeReactiveOverrides();
155
+ this.cleanupFuncs.push(() => {
156
+ var _a;
157
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.terminate();
158
+ this.transport = undefined;
159
+ this.transportReady = false;
160
+ this.bodyEntities.clear();
161
+ this.bodySnapshots.clear();
162
+ this.bodyResultSequenceFloors.clear();
163
+ this.failedBodyEntities = new WeakSet();
164
+ this.pendingCommands.clear();
165
+ this.freeExchangeBuffers.length = 0;
166
+ this.completedExchanges.length = 0;
167
+ this.stepAccumulator = 0;
168
+ this.pendingDebugStepDelta = undefined;
169
+ this.resolveDebugFlushWaiters(true);
170
+ });
138
171
  }
139
172
  /**
140
- * Teleport an existing Havok body to `pose`.
141
- *
142
- * @remarks
143
- * No-ops until the entity has a live {@link PhysicsBody}. The pose is pushed
144
- * directly to Havok and mirrored onto `entity.object3D` immediately so callers
145
- * do not need to wait for the next physics tick before reading the reset
146
- * transform. Linear and angular velocity are cleared by default, which is the
147
- * expected behavior for reset/home-position flows.
148
- *
149
- * @example Reset a fallen prop to its home pose
150
- * ```ts
151
- * const physics = world.getSystem(PhysicsSystem);
152
- * physics.setBodyTransform(prop, {
153
- * position: homePosition,
154
- * quaternion: homeQuaternion,
155
- * });
156
- * ```
173
+ * Teleport an existing physics body and mirror the pose immediately on its
174
+ * Object3D. The runtime consumes the command on the next available exchange.
157
175
  */
158
176
  setBodyTransform(entity, pose, options = {}) {
159
- if (!this.havok || !entity.hasComponent(PhysicsBody)) {
177
+ if (!entity.hasComponent(PhysicsBody)) {
160
178
  return;
161
179
  }
162
- const engineBody = entity.getValue(PhysicsBody, '_engineBody');
163
- if (!engineBody) {
180
+ const handle = entity.getValue(PhysicsBody, '_engineBody');
181
+ if (!handle) {
164
182
  return;
165
183
  }
166
- const body = [BigInt(engineBody)];
167
184
  const position = vector3ToArray(pose.position);
168
185
  const quaternion = quaternionToArray(pose.quaternion);
169
- this.havok.HP_Body_SetQTransform(body, [position, quaternion]);
186
+ const command = this.getPendingCommand(handle);
187
+ command.flags |= 2 /* PhysicsCommandFlag.SetTransform */;
188
+ command.position = position;
189
+ command.quaternion = quaternion;
190
+ if (this.bodySnapshots.has(handle)) {
191
+ this.bodyResultSequenceFloors.set(handle, this.nextSequence);
192
+ }
170
193
  if (entity.object3D) {
171
194
  entity.object3D.position.set(position[0], position[1], position[2]);
172
195
  entity.object3D.quaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
173
196
  entity.object3D.updateMatrixWorld(true);
197
+ this.syncSnapshotToObject(handle, entity.object3D);
174
198
  }
175
199
  if (options.resetVelocity === false) {
176
200
  return;
177
201
  }
178
- this.havok.HP_Body_SetLinearVelocity(body, [...ZERO_VECTOR]);
179
- this.havok.HP_Body_SetAngularVelocity(body, [...ZERO_VECTOR]);
202
+ command.flags |=
203
+ 4 /* PhysicsCommandFlag.ResetLinearVelocity */ |
204
+ 8 /* PhysicsCommandFlag.ResetAngularVelocity */;
180
205
  entity.getVectorView(PhysicsBody, '_linearVelocity').set(ZERO_VECTOR);
181
206
  entity.getVectorView(PhysicsBody, '_angularVelocity').set(ZERO_VECTOR);
182
207
  }
183
- /**
184
- * Reset a body's gravityFactor / linearDamping / angularDamping to the engine
185
- * default when its field returns to the default value. The entity then drops
186
- * out of the override set, so {@link syncReactiveOverrides} no longer visits
187
- * it — this is the one place that restores the default. The `hasComponent`
188
- * guard distinguishes the value-returned-to-default case from the other
189
- * disqualify cause (PhysicsBody/PhysicsShape removal), where the body is being
190
- * released and must not be written to.
191
- */
192
- subscribeReactiveOverrides() {
193
- this.queries.gravityOverrides.subscribe('disqualify', (entity) => {
194
- if (entity.hasComponent(PhysicsBody) &&
195
- entity.hasComponent(PhysicsShape)) {
196
- this.syncGravityFactor(entity);
197
- }
198
- });
199
- this.queries.linearDampingOverrides.subscribe('disqualify', (entity) => {
200
- if (entity.hasComponent(PhysicsBody) &&
201
- entity.hasComponent(PhysicsShape)) {
202
- this.syncLinearDamping(entity);
208
+ update(delta) {
209
+ this.applyCompletedExchange();
210
+ if (!this.transport) {
211
+ return;
212
+ }
213
+ this.queries.physicsEntities.entities.forEach((entity) => {
214
+ this.ensureBody(entity);
215
+ const handle = entity.getValue(PhysicsBody, '_engineBody');
216
+ if (handle &&
217
+ entity.object3D &&
218
+ Grabbed.bitmask &&
219
+ entity.hasComponent(Grabbed)) {
220
+ const command = this.getPendingCommand(handle);
221
+ command.flags |= 1 /* PhysicsCommandFlag.SetTargetTransform */;
222
+ command.position[0] = entity.object3D.position.x;
223
+ command.position[1] = entity.object3D.position.y;
224
+ command.position[2] = entity.object3D.position.z;
225
+ command.quaternion[0] = entity.object3D.quaternion.x;
226
+ command.quaternion[1] = entity.object3D.quaternion.y;
227
+ command.quaternion[2] = entity.object3D.quaternion.z;
228
+ command.quaternion[3] = entity.object3D.quaternion.w;
229
+ this.syncSnapshotToObject(handle, entity.object3D);
203
230
  }
204
231
  });
205
- this.queries.angularDampingOverrides.subscribe('disqualify', (entity) => {
206
- if (entity.hasComponent(PhysicsBody) &&
207
- entity.hasComponent(PhysicsShape)) {
208
- this.syncAngularDamping(entity);
232
+ this.syncReactiveOverrides();
233
+ this.queueManipulations();
234
+ if (this.pendingDebugStepDelta !== undefined) {
235
+ this.stepAccumulator = 0;
236
+ this.trySendPendingDebugStep();
237
+ this.renderInterpolatedTransforms(1);
238
+ return;
239
+ }
240
+ const stepInterval = this.getStepInterval();
241
+ this.stepAccumulator = Math.min(this.stepAccumulator + Math.max(0, delta), stepInterval * MAX_ACCUMULATED_STEPS);
242
+ if (this.transportReady) {
243
+ const stepCount = Math.min(Math.floor(this.stepAccumulator / stepInterval + 1e-9), MAX_ACCUMULATED_STEPS);
244
+ if (stepCount > 0 && this.sendExchange(stepInterval, stepCount)) {
245
+ this.stepAccumulator = Math.max(0, this.stepAccumulator - stepInterval * stepCount);
209
246
  }
210
- });
247
+ }
248
+ const interpolationAlpha = this.config.interpolation.value
249
+ ? Math.min(this.stepAccumulator / stepInterval, 1)
250
+ : 1;
251
+ this.renderInterpolatedTransforms(interpolationAlpha);
211
252
  }
212
- update(delta) {
213
- if (this.havok && this.havokWorld) {
214
- this.havok.HP_World_SetIdealStepTime(this.havokWorld, delta);
215
- this.havok.HP_World_Step(this.havokWorld, delta);
216
- this.bodyBuffer = this.havok.HP_World_GetBodyBuffer(this.havokWorld)[1];
253
+ /** Prepare one exact-delta physics tick for an ECS debugger step. */
254
+ prepareDebugFrame(delta) {
255
+ this.pendingDebugStepDelta =
256
+ Number.isFinite(delta) && delta > 0 ? delta : this.getStepInterval();
257
+ this.stepAccumulator = 0;
258
+ }
259
+ /**
260
+ * Wait until all submitted physics work is reflected in ECS transforms.
261
+ * Used by debugger pause/step so worker and inline execution expose the same
262
+ * settled state at command boundaries.
263
+ */
264
+ async flushDebugFrame() {
265
+ this.applyCompletedExchange();
266
+ this.trySendPendingDebugStep();
267
+ await this.waitForDebugWork();
268
+ this.applyCompletedExchange();
269
+ // Pausing between fixed ticks must commit queued commands without moving
270
+ // simulation time; a zero-step exchange preserves the old inline behavior.
271
+ if (this.pendingDebugStepDelta === undefined &&
272
+ this.pendingCommands.size > 0 &&
273
+ this.transportReady &&
274
+ this.sendExchange(0, 0)) {
275
+ await this.waitForDebugWork();
276
+ this.applyCompletedExchange();
277
+ }
278
+ this.renderInterpolatedTransforms(1);
279
+ for (const snapshot of this.bodySnapshots.values()) {
280
+ snapshot.previousPosition.copy(snapshot.currentPosition);
281
+ snapshot.previousQuaternion.copy(snapshot.currentQuaternion);
217
282
  }
218
- this.queries.physicsEntities.entities.forEach((entity) => {
219
- var _a, _b, _c, _d;
220
- if (!entity.object3D || !this.havok || !this.havokWorld) {
221
- return;
222
- }
223
- const engineShape = entity.getValue(PhysicsShape, '_engineShape');
224
- const engineBody = entity.getValue(PhysicsBody, '_engineBody');
225
- if (!engineShape) {
226
- const dimensionsView = entity.getVectorView(PhysicsShape, 'dimensions');
227
- this.createHavokShapes(entity, dimensionsView);
228
- return;
283
+ }
284
+ handleTransportMessage(message) {
285
+ switch (message.type) {
286
+ case 'ready':
287
+ this.transportReady = true;
288
+ this.trySendPendingDebugStep();
289
+ break;
290
+ case 'body-created': {
291
+ const entity = this.bodyEntities.get(message.handle);
292
+ if (entity === null || entity === void 0 ? void 0 : entity.hasComponent(PhysicsBody)) {
293
+ entity
294
+ .getVectorView(PhysicsBody, 'centerOfMass')
295
+ .set(message.centerOfMass);
296
+ }
297
+ break;
229
298
  }
230
- else {
231
- if (!engineBody && engineShape) {
232
- const linearDamping = (_a = entity.getValue(PhysicsBody, 'linearDamping')) !== null && _a !== void 0 ? _a : DEFAULT_LINEAR_DAMPING;
233
- const angularDamping = (_b = entity.getValue(PhysicsBody, 'angularDamping')) !== null && _b !== void 0 ? _b : DEFAULT_ANGULAR_DAMPING;
234
- const gravityFactor = (_c = entity.getValue(PhysicsBody, 'gravityFactor')) !== null && _c !== void 0 ? _c : DEFAULT_GRAVITY_FACTOR;
235
- const bodyRepsonse = this.createBody([BigInt(engineShape)], entity.object3D.position, entity.object3D.quaternion, entity.getValue(PhysicsBody, 'state'), linearDamping, angularDamping, gravityFactor, entity.getVectorView(PhysicsBody, 'centerOfMass'));
236
- if (bodyRepsonse) {
237
- entity.setValue(PhysicsBody, '_engineBody', Number(bodyRepsonse.createdBody));
238
- entity.setValue(PhysicsBody, '_engineOffset', bodyRepsonse.offset);
239
- // Seed the reactive shadows with what createBody just pushed, so the
240
- // first per-frame sync sees no drift and skips a redundant write.
241
- PhysicsBody.data._engineGravityFactor[entity.index] = gravityFactor;
242
- PhysicsBody.data._engineLinearDamping[entity.index] = linearDamping;
243
- PhysicsBody.data._engineAngularDamping[entity.index] =
244
- angularDamping;
245
- }
299
+ case 'step-result':
300
+ this.inFlightExchangeCount = Math.max(0, this.inFlightExchangeCount - 1);
301
+ const ints = new Int32Array(message.buffer);
302
+ this.completedExchanges.push({
303
+ buffer: message.buffer,
304
+ ints,
305
+ floats: new Float32Array(message.buffer),
306
+ sequence: ints[1 /* PhysicsHeaderIndex.Sequence */],
307
+ });
308
+ if (this.pendingDebugStepDelta !== undefined) {
309
+ this.applyCompletedExchange();
310
+ this.trySendPendingDebugStep();
246
311
  }
247
- else if (engineBody && this.bodyBuffer) {
248
- const linearVelocity = this.havok.HP_Body_GetLinearVelocity([
249
- BigInt(engineBody),
250
- ]);
251
- const angularVelocity = this.havok.HP_Body_GetAngularVelocity([
252
- BigInt(engineBody),
253
- ]);
254
- const linearVelocityView = entity.getVectorView(PhysicsBody, '_linearVelocity');
255
- const angularVelocityView = entity.getVectorView(PhysicsBody, '_angularVelocity');
256
- linearVelocityView.set(linearVelocity[1]);
257
- angularVelocityView.set(angularVelocity[1]);
258
- // Processing physics body motion here
259
- const position = entity.object3D.position;
260
- const quaternion = entity.object3D.quaternion;
261
- if (Grabbed.bitmask && entity.hasComponent(Grabbed)) {
262
- this.havok.HP_Body_SetTargetQTransform([BigInt(engineBody)], [
263
- [position.x, position.y, position.z],
264
- [quaternion.x, quaternion.y, quaternion.z, quaternion.w],
265
- ]);
266
- return;
267
- }
268
- const bodyOffset = (_d = entity.getValue(PhysicsBody, '_engineOffset')) !== null && _d !== void 0 ? _d : 0;
269
- // Read the 16-float transform straight out of the cached heap view.
270
- // `bodyBuffer + bodyOffset` is 4-byte aligned (it was a valid
271
- // Float32Array byteOffset previously), so `>> 2` is exact.
272
- const heap = this.getHeapFloatView();
273
- const base = (this.bodyBuffer + bodyOffset) >> 2;
274
- for (let mi = 0; mi < 15; mi++) {
275
- if ((mi & 3) != 3) {
276
- this.matrixBuffer.elements[mi] = heap[base + mi];
277
- }
312
+ if (this.inFlightExchangeCount === 0) {
313
+ this.resolveDebugFlushWaiters();
314
+ }
315
+ break;
316
+ case 'error':
317
+ if (message.handle !== undefined) {
318
+ console.error(`Physics runtime: ${message.message}`);
319
+ const entity = this.bodyEntities.get(message.handle);
320
+ if (entity) {
321
+ this.failedBodyEntities.add(entity);
322
+ this.setEngineHandles(entity, 0);
278
323
  }
279
- this.matrixBuffer.elements[15] = 1.0;
280
- this.matrixBuffer.decompose(position, quaternion, this.scaleBuffer);
324
+ this.bodyEntities.delete(message.handle);
325
+ this.bodySnapshots.delete(message.handle);
326
+ this.bodyResultSequenceFloors.delete(message.handle);
327
+ this.discardPendingCommand(message.handle);
281
328
  }
282
- }
329
+ else {
330
+ this.handleTransportFailure(`Physics runtime: ${message.message}`);
331
+ }
332
+ break;
333
+ }
334
+ }
335
+ handleTransportFailure(message) {
336
+ var _a;
337
+ console.error(this.config.useWorker.value
338
+ ? `${message}. Set features.physics.useWorker to false to run physics on the main thread.`
339
+ : message);
340
+ this.transportReady = false;
341
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.terminate();
342
+ this.transport = undefined;
343
+ this.inFlightExchangeCount = 0;
344
+ this.pendingDebugStepDelta = undefined;
345
+ this.resolveDebugFlushWaiters();
346
+ }
347
+ async waitForDebugWork() {
348
+ if (this.pendingDebugStepDelta === undefined &&
349
+ this.inFlightExchangeCount === 0) {
350
+ return;
351
+ }
352
+ await new Promise((resolve) => {
353
+ this.debugFlushWaiters.add(resolve);
283
354
  });
284
- this.syncReactiveOverrides();
285
- this.queries.manipluatedEntities.entities.forEach((entity) => {
286
- const engineBody = entity.getValue(PhysicsBody, '_engineBody');
287
- if (!entity.object3D || !this.havok || !this.havokWorld || !engineBody) {
288
- return;
355
+ }
356
+ resolveDebugFlushWaiters(force = false) {
357
+ if (!force &&
358
+ (this.pendingDebugStepDelta !== undefined ||
359
+ this.inFlightExchangeCount > 0)) {
360
+ return;
361
+ }
362
+ for (const resolve of this.debugFlushWaiters) {
363
+ resolve();
364
+ }
365
+ this.debugFlushWaiters.clear();
366
+ }
367
+ applyCompletedExchange() {
368
+ if (this.completedExchanges.length === 0) {
369
+ return;
370
+ }
371
+ this.completedExchanges.sort((left, right) => left.sequence - right.sequence);
372
+ for (const exchange of this.completedExchanges) {
373
+ this.applyExchangeBuffer(exchange);
374
+ this.freeExchangeBuffers.push(exchange.buffer);
375
+ }
376
+ this.completedExchanges.length = 0;
377
+ }
378
+ applyExchangeBuffer({ ints, floats }) {
379
+ const status = ints[4 /* PhysicsHeaderIndex.Status */];
380
+ if (status & 1 /* PhysicsExchangeStatus.ResultsTruncated */) {
381
+ console.warn('Physics result buffer was unexpectedly truncated');
382
+ }
383
+ const resultCount = ints[3 /* PhysicsHeaderIndex.ResultCount */];
384
+ const sequence = ints[1 /* PhysicsHeaderIndex.Sequence */];
385
+ const stepCount = Math.max(1, ints[5 /* PhysicsHeaderIndex.StepCount */]);
386
+ for (let index = 0; index < resultCount; index++) {
387
+ const base = PHYSICS_HEADER_WORDS + index * PHYSICS_OUTPUT_RECORD_FLOATS;
388
+ const handle = floats[base + 0 /* PhysicsOutputOffset.Handle */];
389
+ const entity = this.bodyEntities.get(handle);
390
+ if (!(entity === null || entity === void 0 ? void 0 : entity.object3D) || !entity.hasComponent(PhysicsBody)) {
391
+ continue;
289
392
  }
290
- // Applying one time force to the body
291
- if (!entity
292
- .getVectorView(PhysicsManipulation, 'force')
293
- .every((element) => element === 0)) {
294
- const force = entity.getVectorView(PhysicsManipulation, 'force');
295
- this.havok.HP_Body_ApplyImpulse([BigInt(engineBody)], [
296
- entity.object3D.position.x,
297
- entity.object3D.position.y,
298
- entity.object3D.position.z,
299
- ], [force[0] * delta, force[1] * delta, force[2] * delta]);
393
+ const sequenceFloor = this.bodyResultSequenceFloors.get(handle);
394
+ if (sequenceFloor !== undefined) {
395
+ if (sequence < sequenceFloor) {
396
+ continue;
397
+ }
398
+ this.bodyResultSequenceFloors.delete(handle);
300
399
  }
301
- // Applying one time linear velocity to the body
302
- if (!entity
303
- .getVectorView(PhysicsManipulation, 'linearVelocity')
304
- .every((element) => element === 0)) {
305
- const linearVelocity = entity.getVectorView(PhysicsManipulation, 'linearVelocity');
306
- this.havok.HP_Body_SetLinearVelocity([BigInt(engineBody)], [linearVelocity[0], linearVelocity[1], linearVelocity[2]]);
400
+ const linearVelocity = entity.getVectorView(PhysicsBody, '_linearVelocity');
401
+ linearVelocity[0] = floats[base + 8 /* PhysicsOutputOffset.LinearVelocityX */];
402
+ linearVelocity[1] = floats[base + 9 /* PhysicsOutputOffset.LinearVelocityY */];
403
+ linearVelocity[2] = floats[base + 10 /* PhysicsOutputOffset.LinearVelocityZ */];
404
+ const angularVelocity = entity.getVectorView(PhysicsBody, '_angularVelocity');
405
+ angularVelocity[0] = floats[base + 11 /* PhysicsOutputOffset.AngularVelocityX */];
406
+ angularVelocity[1] = floats[base + 12 /* PhysicsOutputOffset.AngularVelocityY */];
407
+ angularVelocity[2] = floats[base + 13 /* PhysicsOutputOffset.AngularVelocityZ */];
408
+ if (Grabbed.bitmask && entity.hasComponent(Grabbed)) {
409
+ continue;
307
410
  }
308
- // Applying one time angular velocity to the body
309
- if (!entity
310
- .getVectorView(PhysicsManipulation, 'angularVelocity')
311
- .every((element) => element === 0)) {
312
- const angularVelocity = entity.getVectorView(PhysicsManipulation, 'angularVelocity');
313
- this.havok.HP_Body_SetAngularVelocity([BigInt(engineBody)], [angularVelocity[0], angularVelocity[1], angularVelocity[2]]);
411
+ const snapshot = this.bodySnapshots.get(handle);
412
+ if (!snapshot) {
413
+ continue;
314
414
  }
315
- entity.removeComponent(PhysicsManipulation);
316
- });
415
+ if (stepCount === 1) {
416
+ snapshot.previousPosition.copy(snapshot.currentPosition);
417
+ snapshot.previousQuaternion.copy(snapshot.currentQuaternion);
418
+ }
419
+ snapshot.currentPosition.set(floats[base + 1 /* PhysicsOutputOffset.PositionX */], floats[base + 2 /* PhysicsOutputOffset.PositionY */], floats[base + 3 /* PhysicsOutputOffset.PositionZ */]);
420
+ snapshot.currentQuaternion.set(floats[base + 4 /* PhysicsOutputOffset.QuaternionX */], floats[base + 5 /* PhysicsOutputOffset.QuaternionY */], floats[base + 6 /* PhysicsOutputOffset.QuaternionZ */], floats[base + 7 /* PhysicsOutputOffset.QuaternionW */]);
421
+ if (stepCount > 1) {
422
+ snapshot.previousPosition.copy(snapshot.currentPosition);
423
+ snapshot.previousQuaternion.copy(snapshot.currentQuaternion);
424
+ }
425
+ }
317
426
  }
318
- createBody(shape, position, quaternion, state, linearDamping, angularDamping, gravityFactor, centerOfMass) {
319
- if (!this.havok || !this.havokWorld) {
320
- return;
427
+ renderInterpolatedTransforms(alpha) {
428
+ for (const [handle, snapshot] of this.bodySnapshots) {
429
+ const entity = this.bodyEntities.get(handle);
430
+ if (!(entity === null || entity === void 0 ? void 0 : entity.object3D) ||
431
+ (Grabbed.bitmask && entity.hasComponent(Grabbed))) {
432
+ continue;
433
+ }
434
+ entity.object3D.position.lerpVectors(snapshot.previousPosition, snapshot.currentPosition, alpha);
435
+ entity.object3D.quaternion.slerpQuaternions(snapshot.previousQuaternion, snapshot.currentQuaternion, alpha);
321
436
  }
322
- const body = this.havok.HP_Body_Create()[1];
323
- this.havok.HP_Body_SetShape(body, shape);
324
- this.havok.HP_Body_SetQTransform(body, [
325
- [position.x, position.y, position.z],
326
- [quaternion.x, quaternion.y, quaternion.z, quaternion.w],
327
- ]);
328
- this.havok.HP_Body_SetLinearDamping(body, linearDamping);
329
- this.havok.HP_Body_SetAngularDamping(body, angularDamping);
330
- this.havok.HP_Body_SetGravityFactor(body, gravityFactor);
331
- const shapeMass = this.havok.HP_Shape_BuildMassProperties(shape);
332
- const massProps = shapeMass[0] == this.havok.Result.RESULT_OK
333
- ? shapeMass[1]
334
- : [[0, 0, 0], 1, [1, 1, 1], [0, 0, 0, 1]];
335
- if (!centerOfMass.every((e) => e == Infinity)) {
336
- massProps[0] = [centerOfMass[0], centerOfMass[1], centerOfMass[2]];
437
+ }
438
+ syncSnapshotToObject(handle, object3D) {
439
+ const snapshot = this.bodySnapshots.get(handle);
440
+ if (!snapshot) {
441
+ return;
337
442
  }
338
- else {
339
- // Update the centerOfMass with the computed value from mass properties
340
- centerOfMass[0] = massProps[0][0];
341
- centerOfMass[1] = massProps[0][1];
342
- centerOfMass[2] = massProps[0][2];
343
- }
344
- this.havok.HP_Body_SetMassProperties(body, massProps);
345
- let motionType;
346
- switch (state) {
347
- case PhysicsState.Static:
348
- motionType = this.havok.MotionType.STATIC;
349
- break;
350
- case PhysicsState.Kinematic:
351
- motionType = this.havok.MotionType.KINEMATIC;
352
- break;
353
- case PhysicsState.Dynamic:
354
- default:
355
- motionType = this.havok.MotionType.DYNAMIC;
356
- }
357
- this.havok.HP_Body_SetMotionType(body, motionType);
358
- this.havok.HP_World_AddBody(this.havokWorld, body, false);
359
- return {
360
- offset: this.havok.HP_Body_GetWorldTransformOffset(body)[1],
361
- createdBody: body,
362
- };
443
+ snapshot.previousPosition.copy(object3D.position);
444
+ snapshot.currentPosition.copy(object3D.position);
445
+ snapshot.previousQuaternion.copy(object3D.quaternion);
446
+ snapshot.currentQuaternion.copy(object3D.quaternion);
363
447
  }
364
- /**
365
- * Push post-creation gravityFactor / linearDamping / angularDamping edits to
366
- * Havok. Only overridden bodies are visited (see the value-predicate queries
367
- * in the system definition); each sync diffs the component value against the
368
- * last-pushed shadow, so a body whose field is unchanged this frame costs only
369
- * a typed-array compare.
370
- */
371
- syncReactiveOverrides() {
372
- this.queries.gravityOverrides.entities.forEach((entity) => {
373
- this.syncGravityFactor(entity);
374
- });
375
- this.queries.linearDampingOverrides.entities.forEach((entity) => {
376
- this.syncLinearDamping(entity);
377
- });
378
- this.queries.angularDampingOverrides.entities.forEach((entity) => {
379
- this.syncAngularDamping(entity);
380
- });
448
+ getStepInterval() {
449
+ const configuredFrequency = this.config.updateFrequency.value;
450
+ const frequency = Number.isFinite(configuredFrequency) && configuredFrequency > 0
451
+ ? Math.min(configuredFrequency, MAX_UPDATE_FREQUENCY)
452
+ : DEFAULT_UPDATE_FREQUENCY;
453
+ return 1 / frequency;
381
454
  }
382
- /**
383
- * Sync the entity's current {@link PhysicsBody.gravityFactor} onto its Havok
384
- * body. Diffs the component value against `_engineGravityFactor` (the last
385
- * value pushed) rather than reading back from Havok, so an unchanged body
386
- * costs only a typed-array compare — no WASM call and no allocation. The Havok
387
- * write and shadow update happen only on an actual change. No-op until the body
388
- * exists. See {@link gravityOverrides}.
389
- */
390
- syncGravityFactor(entity) {
391
- if (!this.havok) {
392
- return;
455
+ sendExchange(delta, stepCount = 1) {
456
+ const transport = this.transport;
457
+ const buffer = this.freeExchangeBuffers.pop();
458
+ if (!transport || !buffer) {
459
+ return false;
460
+ }
461
+ const resultCapacity = this.bodySnapshots.size;
462
+ const requiredByteLength = physicsExchangeByteLength(this.pendingCommands.size, resultCapacity);
463
+ const exchangeBuffer = buffer.byteLength >= requiredByteLength
464
+ ? buffer
465
+ : new ArrayBuffer(requiredByteLength);
466
+ const ints = new Int32Array(exchangeBuffer);
467
+ const floats = new Float32Array(exchangeBuffer);
468
+ ints.fill(0, 0, PHYSICS_HEADER_WORDS);
469
+ ints[0 /* PhysicsHeaderIndex.Version */] = PHYSICS_PROTOCOL_VERSION;
470
+ const sequence = this.nextSequence++;
471
+ ints[1 /* PhysicsHeaderIndex.Sequence */] = sequence;
472
+ ints[2 /* PhysicsHeaderIndex.CommandCount */] = this.pendingCommands.size;
473
+ ints[5 /* PhysicsHeaderIndex.StepCount */] = stepCount;
474
+ floats[8 /* PhysicsHeaderIndex.Delta */] = delta;
475
+ let commandIndex = 0;
476
+ for (const command of this.pendingCommands.values()) {
477
+ this.writeCommand(floats, commandIndex++, command);
478
+ if (command.flags & 2 /* PhysicsCommandFlag.SetTransform */) {
479
+ this.bodyResultSequenceFloors.set(command.handle, sequence);
480
+ }
393
481
  }
394
- const index = entity.index;
395
- const engineBody = PhysicsBody.data._engineBody[index];
396
- if (!engineBody) {
397
- return;
482
+ try {
483
+ transport.postMessage({ type: 'step', buffer: exchangeBuffer }, [
484
+ exchangeBuffer,
485
+ ]);
486
+ this.inFlightExchangeCount++;
487
+ this.releasePendingCommands();
488
+ return true;
398
489
  }
399
- const target = PhysicsBody.data.gravityFactor[index];
400
- if (PhysicsBody.data._engineGravityFactor[index] === target) {
401
- return;
490
+ catch (error) {
491
+ this.freeExchangeBuffers.push(exchangeBuffer);
492
+ console.error('Failed to post physics exchange buffer:', error);
493
+ return false;
402
494
  }
403
- this.havok.HP_Body_SetGravityFactor([BigInt(engineBody)], target);
404
- PhysicsBody.data._engineGravityFactor[index] = target;
405
495
  }
406
- /**
407
- * Sync the entity's current {@link PhysicsBody.linearDamping} onto its Havok
408
- * body. Diffs against `_engineLinearDamping` (the last value pushed) rather
409
- * than reading back from Havok, so an unchanged body costs only a typed-array
410
- * compare — no WASM call and no allocation. The Havok write and shadow update
411
- * happen only on an actual change. No-op until the body exists.
412
- * See {@link linearDampingOverrides}.
413
- */
414
- syncLinearDamping(entity) {
415
- if (!this.havok) {
496
+ trySendPendingDebugStep() {
497
+ if (this.pendingDebugStepDelta === undefined) {
416
498
  return;
417
499
  }
418
- const index = entity.index;
419
- const engineBody = PhysicsBody.data._engineBody[index];
420
- if (!engineBody) {
500
+ if (!this.transport) {
501
+ this.pendingDebugStepDelta = undefined;
502
+ this.resolveDebugFlushWaiters();
421
503
  return;
422
504
  }
423
- const target = PhysicsBody.data.linearDamping[index];
424
- if (PhysicsBody.data._engineLinearDamping[index] === target) {
505
+ if (!this.transportReady) {
425
506
  return;
426
507
  }
427
- this.havok.HP_Body_SetLinearDamping([BigInt(engineBody)], target);
428
- PhysicsBody.data._engineLinearDamping[index] = target;
508
+ if (this.sendExchange(this.pendingDebugStepDelta, 1)) {
509
+ this.pendingDebugStepDelta = undefined;
510
+ }
429
511
  }
430
- /**
431
- * Sync the entity's current {@link PhysicsBody.angularDamping} onto its Havok
432
- * body. Diffs against `_engineAngularDamping` (the last value pushed) rather
433
- * than reading back from Havok, so an unchanged body costs only a typed-array
434
- * compare — no WASM call and no allocation. The Havok write and shadow update
435
- * happen only on an actual change. No-op until the body exists.
436
- * See {@link angularDampingOverrides}.
437
- */
438
- syncAngularDamping(entity) {
439
- if (!this.havok) {
512
+ writeCommand(output, index, command) {
513
+ const base = PHYSICS_HEADER_WORDS + index * PHYSICS_INPUT_RECORD_FLOATS;
514
+ output[base + 0 /* PhysicsInputOffset.Handle */] = command.handle;
515
+ output[base + 1 /* PhysicsInputOffset.Flags */] = command.flags;
516
+ output[base + 2 /* PhysicsInputOffset.PositionX */] = command.position[0];
517
+ output[base + 3 /* PhysicsInputOffset.PositionY */] = command.position[1];
518
+ output[base + 4 /* PhysicsInputOffset.PositionZ */] = command.position[2];
519
+ output[base + 5 /* PhysicsInputOffset.QuaternionX */] = command.quaternion[0];
520
+ output[base + 6 /* PhysicsInputOffset.QuaternionY */] = command.quaternion[1];
521
+ output[base + 7 /* PhysicsInputOffset.QuaternionZ */] = command.quaternion[2];
522
+ output[base + 8 /* PhysicsInputOffset.QuaternionW */] = command.quaternion[3];
523
+ output[base + 9 /* PhysicsInputOffset.ImpulsePointX */] = command.impulsePoint[0];
524
+ output[base + 10 /* PhysicsInputOffset.ImpulsePointY */] = command.impulsePoint[1];
525
+ output[base + 11 /* PhysicsInputOffset.ImpulsePointZ */] = command.impulsePoint[2];
526
+ output[base + 12 /* PhysicsInputOffset.ImpulseX */] = command.impulse[0];
527
+ output[base + 13 /* PhysicsInputOffset.ImpulseY */] = command.impulse[1];
528
+ output[base + 14 /* PhysicsInputOffset.ImpulseZ */] = command.impulse[2];
529
+ output[base + 15 /* PhysicsInputOffset.LinearVelocityX */] =
530
+ command.linearVelocity[0];
531
+ output[base + 16 /* PhysicsInputOffset.LinearVelocityY */] =
532
+ command.linearVelocity[1];
533
+ output[base + 17 /* PhysicsInputOffset.LinearVelocityZ */] =
534
+ command.linearVelocity[2];
535
+ output[base + 18 /* PhysicsInputOffset.AngularVelocityX */] =
536
+ command.angularVelocity[0];
537
+ output[base + 19 /* PhysicsInputOffset.AngularVelocityY */] =
538
+ command.angularVelocity[1];
539
+ output[base + 20 /* PhysicsInputOffset.AngularVelocityZ */] =
540
+ command.angularVelocity[2];
541
+ output[base + 21 /* PhysicsInputOffset.GravityFactor */] = command.gravityFactor;
542
+ output[base + 22 /* PhysicsInputOffset.LinearDamping */] = command.linearDamping;
543
+ output[base + 23 /* PhysicsInputOffset.AngularDamping */] = command.angularDamping;
544
+ }
545
+ getPendingCommand(handle) {
546
+ var _a;
547
+ let command = this.pendingCommands.get(handle);
548
+ if (!command) {
549
+ command = (_a = this.commandPool.pop()) !== null && _a !== void 0 ? _a : createPendingCommand(handle);
550
+ command.handle = handle;
551
+ command.flags = 0;
552
+ command.impulse[0] = 0;
553
+ command.impulse[1] = 0;
554
+ command.impulse[2] = 0;
555
+ this.pendingCommands.set(handle, command);
556
+ }
557
+ return command;
558
+ }
559
+ releasePendingCommands() {
560
+ for (const command of this.pendingCommands.values()) {
561
+ command.flags = 0;
562
+ this.commandPool.push(command);
563
+ }
564
+ this.pendingCommands.clear();
565
+ }
566
+ discardPendingCommand(handle) {
567
+ const command = this.pendingCommands.get(handle);
568
+ if (command) {
569
+ command.flags = 0;
570
+ this.commandPool.push(command);
571
+ this.pendingCommands.delete(handle);
572
+ }
573
+ }
574
+ ensureBody(entity) {
575
+ var _a, _b, _c;
576
+ if (!this.transport || !entity.object3D) {
440
577
  return;
441
578
  }
442
- const index = entity.index;
443
- const engineBody = PhysicsBody.data._engineBody[index];
444
- if (!engineBody) {
579
+ if (this.failedBodyEntities.has(entity)) {
445
580
  return;
446
581
  }
447
- const target = PhysicsBody.data.angularDamping[index];
448
- if (PhysicsBody.data._engineAngularDamping[index] === target) {
582
+ if (entity.getValue(PhysicsBody, '_engineBody')) {
449
583
  return;
450
584
  }
451
- this.havok.HP_Body_SetAngularDamping([BigInt(engineBody)], target);
452
- PhysicsBody.data._engineAngularDamping[index] = target;
453
- }
454
- createHavokShapes(entity, dimensionsView) {
455
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
456
- if (!entity.object3D) {
457
- console.warn('PhysicsSystem: No object3D attached to entity', entity.index);
585
+ const serialized = this.serializeShape(entity, entity.object3D);
586
+ if (!serialized) {
458
587
  return;
459
588
  }
460
- // Determine the actual shape type (resolve Auto if needed)
589
+ const handle = this.nextBodyHandle++;
590
+ const linearDamping = (_a = entity.getValue(PhysicsBody, 'linearDamping')) !== null && _a !== void 0 ? _a : DEFAULT_LINEAR_DAMPING;
591
+ const angularDamping = (_b = entity.getValue(PhysicsBody, 'angularDamping')) !== null && _b !== void 0 ? _b : DEFAULT_ANGULAR_DAMPING;
592
+ const gravityFactor = (_c = entity.getValue(PhysicsBody, 'gravityFactor')) !== null && _c !== void 0 ? _c : DEFAULT_GRAVITY_FACTOR;
593
+ const centerOfMass = entity.getVectorView(PhysicsBody, 'centerOfMass');
594
+ const message = {
595
+ type: 'add-body',
596
+ handle,
597
+ shape: serialized.descriptor,
598
+ state: entity.getValue(PhysicsBody, 'state'),
599
+ position: [
600
+ entity.object3D.position.x,
601
+ entity.object3D.position.y,
602
+ entity.object3D.position.z,
603
+ ],
604
+ quaternion: [
605
+ entity.object3D.quaternion.x,
606
+ entity.object3D.quaternion.y,
607
+ entity.object3D.quaternion.z,
608
+ entity.object3D.quaternion.w,
609
+ ],
610
+ linearDamping,
611
+ angularDamping,
612
+ gravityFactor,
613
+ centerOfMass: [centerOfMass[0], centerOfMass[1], centerOfMass[2]],
614
+ };
615
+ this.setEngineHandles(entity, handle);
616
+ PhysicsBody.data._engineGravityFactor[entity.index] = gravityFactor;
617
+ PhysicsBody.data._engineLinearDamping[entity.index] = linearDamping;
618
+ PhysicsBody.data._engineAngularDamping[entity.index] = angularDamping;
619
+ this.bodyEntities.set(handle, entity);
620
+ if (message.state !== PhysicsState.Static) {
621
+ this.bodySnapshots.set(handle, {
622
+ previousPosition: entity.object3D.position.clone(),
623
+ currentPosition: entity.object3D.position.clone(),
624
+ previousQuaternion: entity.object3D.quaternion.clone(),
625
+ currentQuaternion: entity.object3D.quaternion.clone(),
626
+ });
627
+ }
628
+ try {
629
+ this.transport.postMessage(message, serialized.transfer);
630
+ }
631
+ catch (error) {
632
+ this.failedBodyEntities.add(entity);
633
+ this.setEngineHandles(entity, 0);
634
+ this.bodyEntities.delete(handle);
635
+ this.bodySnapshots.delete(handle);
636
+ this.bodyResultSequenceFloors.delete(handle);
637
+ console.error('Failed to create physics body:', error);
638
+ }
639
+ }
640
+ serializeShape(entity, object3D) {
641
+ var _a, _b, _c, _d, _e;
642
+ const dimensionsView = entity.getVectorView(PhysicsShape, 'dimensions');
461
643
  let shapeType = entity.getValue(PhysicsShape, 'shape');
462
644
  if (shapeType === PhysicsShapeType.Auto) {
463
- const detection = detectShapeFromGeometry(entity.object3D);
464
- // Update the entity's shape type and dimensions if they were auto-detected
645
+ const detection = detectShapeFromGeometry(object3D);
465
646
  entity.setValue(PhysicsShape, 'shape', detection.shapeType);
466
647
  shapeType = detection.shapeType;
467
648
  if (detection.dimensions) {
468
- // Re-read the updated dimensions view
469
649
  dimensionsView.set(detection.dimensions);
470
650
  }
471
651
  }
472
- switch (shapeType) {
473
- case PhysicsShapeType.Sphere: {
474
- const ballShape = this.createBallShape(dimensionsView[0], (_a = entity.getValue(PhysicsShape, 'density')) !== null && _a !== void 0 ? _a : 1.0, (_b = entity.getValue(PhysicsShape, 'restitution')) !== null && _b !== void 0 ? _b : 0, (_c = entity.getValue(PhysicsShape, 'friction')) !== null && _c !== void 0 ? _c : 0.5);
475
- if (ballShape) {
476
- PhysicsShape.data._engineShape[entity.index] = Number(ballShape);
477
- }
478
- else {
479
- console.warn('PhysicsSystem: Failed to create ball shape for entity', entity.index);
480
- }
481
- break;
482
- }
483
- case PhysicsShapeType.Box: {
484
- const boxShape = this.createBoxShape(dimensionsView, (_d = entity.getValue(PhysicsShape, 'density')) !== null && _d !== void 0 ? _d : 1.0, (_e = entity.getValue(PhysicsShape, 'restitution')) !== null && _e !== void 0 ? _e : 0, (_f = entity.getValue(PhysicsShape, 'friction')) !== null && _f !== void 0 ? _f : 0.5);
485
- if (boxShape) {
486
- PhysicsShape.data._engineShape[entity.index] = Number(boxShape);
487
- }
488
- else {
489
- console.warn('PhysicsSystem: Failed to create box shape for entity', entity.index);
490
- }
491
- break;
492
- }
493
- case PhysicsShapeType.Cylinder: {
494
- const cylinderShape = this.createCylinderShape(dimensionsView[0], // radius
495
- dimensionsView[1], // height
496
- (_g = entity.getValue(PhysicsShape, 'density')) !== null && _g !== void 0 ? _g : 1.0, (_h = entity.getValue(PhysicsShape, 'restitution')) !== null && _h !== void 0 ? _h : 0, (_j = entity.getValue(PhysicsShape, 'friction')) !== null && _j !== void 0 ? _j : 0.5);
497
- if (cylinderShape) {
498
- PhysicsShape.data._engineShape[entity.index] = Number(cylinderShape);
499
- }
500
- else {
501
- console.warn('PhysicsSystem: Failed to create cylinder shape for entity', entity.index);
502
- }
503
- break;
504
- }
505
- case PhysicsShapeType.Capsules: {
506
- const capsuleShape = this.createCapsuleShape(dimensionsView[0], // radius
507
- dimensionsView[1], // total end-to-end height
508
- (_k = entity.getValue(PhysicsShape, 'density')) !== null && _k !== void 0 ? _k : 1.0, (_l = entity.getValue(PhysicsShape, 'restitution')) !== null && _l !== void 0 ? _l : 0, (_m = entity.getValue(PhysicsShape, 'friction')) !== null && _m !== void 0 ? _m : 0.5);
509
- if (capsuleShape) {
510
- PhysicsShape.data._engineShape[entity.index] = Number(capsuleShape);
511
- }
512
- else {
513
- console.warn('PhysicsSystem: Failed to create capsule shape for entity', entity.index);
514
- }
515
- break;
516
- }
517
- case PhysicsShapeType.ConvexHull: {
518
- const convexHullShape = this.createConvexHullShape(entity.object3D, (_o = entity.getValue(PhysicsShape, 'density')) !== null && _o !== void 0 ? _o : 1.0, (_p = entity.getValue(PhysicsShape, 'restitution')) !== null && _p !== void 0 ? _p : 0, (_q = entity.getValue(PhysicsShape, 'friction')) !== null && _q !== void 0 ? _q : 0.5);
519
- if (convexHullShape) {
520
- PhysicsShape.data._engineShape[entity.index] =
521
- Number(convexHullShape);
652
+ if (shapeType === PhysicsShapeType.Capsules &&
653
+ !getCapsuleAxisEndpoints(dimensionsView[0], dimensionsView[1])) {
654
+ console.warn('PhysicsSystem: Capsule dimensions require a positive radius and a total height greater than or equal to twice the radius');
655
+ return;
656
+ }
657
+ const descriptor = {
658
+ type: shapeType,
659
+ dimensions: [dimensionsView[0], dimensionsView[1], dimensionsView[2]],
660
+ density: (_a = entity.getValue(PhysicsShape, 'density')) !== null && _a !== void 0 ? _a : 1,
661
+ restitution: (_b = entity.getValue(PhysicsShape, 'restitution')) !== null && _b !== void 0 ? _b : 0,
662
+ friction: (_c = entity.getValue(PhysicsShape, 'friction')) !== null && _c !== void 0 ? _c : 0.5,
663
+ };
664
+ const transfer = [];
665
+ if (shapeType === PhysicsShapeType.ConvexHull ||
666
+ shapeType === PhysicsShapeType.TriMesh) {
667
+ const ownsGeometry = !(object3D instanceof Mesh);
668
+ const geometry = object3D instanceof Mesh
669
+ ? object3D.geometry
670
+ : generateMergedGeometry(object3D);
671
+ try {
672
+ const positionAttribute = geometry.attributes.position;
673
+ if (!positionAttribute) {
674
+ console.warn(`PhysicsSystem: Failed to get vertices for ${shapeType} shape with object3D name ${object3D.name} &id ${object3D.id}`);
675
+ return;
522
676
  }
523
- else {
524
- console.warn('PhysicsSystem: Failed to create convex hull shape for entity', entity.index);
677
+ const vertices = Float32Array.from(positionAttribute.array);
678
+ descriptor.vertices = vertices.buffer;
679
+ transfer.push(descriptor.vertices);
680
+ if (shapeType === PhysicsShapeType.TriMesh) {
681
+ const sourceIndices = (_e = (_d = geometry.index) === null || _d === void 0 ? void 0 : _d.array) !== null && _e !== void 0 ? _e : sequentialIndices(positionAttribute.count);
682
+ const indices = Uint32Array.from(sourceIndices);
683
+ descriptor.indices = indices.buffer;
684
+ transfer.push(descriptor.indices);
525
685
  }
526
- break;
527
686
  }
528
- case PhysicsShapeType.TriMesh: {
529
- const triMeshShape = this.createTriMeshShape(entity.object3D, (_r = entity.getValue(PhysicsShape, 'density')) !== null && _r !== void 0 ? _r : 1.0, (_s = entity.getValue(PhysicsShape, 'restitution')) !== null && _s !== void 0 ? _s : 0, (_t = entity.getValue(PhysicsShape, 'friction')) !== null && _t !== void 0 ? _t : 0.5);
530
- if (triMeshShape) {
531
- PhysicsShape.data._engineShape[entity.index] = Number(triMeshShape);
687
+ finally {
688
+ if (ownsGeometry) {
689
+ geometry.dispose();
532
690
  }
533
- else {
534
- console.warn('PhysicsSystem: Failed to create tri-mesh shape for entity', entity.index);
535
- }
536
- break;
537
691
  }
538
692
  }
693
+ return { descriptor, transfer };
539
694
  }
540
- createBallShape(radius, density, restitution, friction) {
541
- if (!this.havok) {
542
- console.warn('PhysicsSystem: Cannot create ball shape - Havok physics engine not initialized');
695
+ removeBody(entity) {
696
+ var _a;
697
+ this.failedBodyEntities.delete(entity);
698
+ const handle = PhysicsBody.data._engineBody[entity.index];
699
+ if (!handle) {
543
700
  return;
544
701
  }
545
- const ballShape = this.havok.HP_Shape_CreateSphere([0, 0, 0], radius)[1];
546
- this.havok.HP_Shape_SetDensity(ballShape, density);
547
- this.havok.HP_Shape_SetMaterial(ballShape, [
548
- friction,
549
- friction,
550
- restitution,
551
- this.havok.MaterialCombine.MINIMUM,
552
- this.havok.MaterialCombine.MAXIMUM,
553
- ]);
554
- return ballShape;
702
+ (_a = this.transport) === null || _a === void 0 ? void 0 : _a.postMessage({ type: 'remove-body', handle });
703
+ this.bodyEntities.delete(handle);
704
+ this.bodySnapshots.delete(handle);
705
+ this.bodyResultSequenceFloors.delete(handle);
706
+ this.discardPendingCommand(handle);
707
+ this.setEngineHandles(entity, 0);
555
708
  }
556
- createBoxShape(scale, density, restitution, friction) {
557
- if (!this.havok) {
558
- console.warn('PhysicsSystem: Cannot create box shape - Havok physics engine not initialized');
559
- return;
709
+ setEngineHandles(entity, handle) {
710
+ if (entity.hasComponent(PhysicsBody)) {
711
+ entity.setValue(PhysicsBody, '_engineBody', handle);
712
+ entity.setValue(PhysicsBody, '_engineOffset', 0);
560
713
  }
561
- const boxShape = this.havok.HP_Shape_CreateBox([0, 0, 0], [0, 0, 0, 1], [scale[0], scale[1], scale[2]])[1];
562
- this.havok.HP_Shape_SetDensity(boxShape, density);
563
- this.havok.HP_Shape_SetMaterial(boxShape, [
564
- friction,
565
- friction,
566
- restitution,
567
- this.havok.MaterialCombine.MINIMUM,
568
- this.havok.MaterialCombine.MAXIMUM,
569
- ]);
570
- return boxShape;
571
- }
572
- createCylinderShape(radius, height, density, restitution, friction) {
573
- if (!this.havok) {
574
- console.warn('PhysicsSystem: Cannot create cylinder shape - Havok physics engine not initialized');
575
- return;
714
+ else {
715
+ PhysicsBody.data._engineBody[entity.index] = handle;
716
+ PhysicsBody.data._engineOffset[entity.index] = 0;
717
+ }
718
+ if (entity.hasComponent(PhysicsShape)) {
719
+ entity.setValue(PhysicsShape, '_engineShape', handle);
720
+ }
721
+ else {
722
+ PhysicsShape.data._engineShape[entity.index] = handle;
576
723
  }
577
- const cylinderShape = this.havok.HP_Shape_CreateCylinder([0, -height / 2, 0], [0, height / 2, 0], radius)[1];
578
- this.havok.HP_Shape_SetDensity(cylinderShape, density);
579
- this.havok.HP_Shape_SetMaterial(cylinderShape, [
580
- friction,
581
- friction,
582
- restitution,
583
- this.havok.MaterialCombine.MINIMUM,
584
- this.havok.MaterialCombine.MAXIMUM,
585
- ]);
586
- return cylinderShape;
587
724
  }
588
- createCapsuleShape(radius, totalHeight, density, restitution, friction) {
589
- if (!this.havok) {
590
- console.warn('PhysicsSystem: Cannot create capsule shape - Havok physics engine not initialized');
725
+ subscribeReactiveOverrides() {
726
+ this.cleanupFuncs.push(this.queries.gravityOverrides.subscribe('disqualify', (entity) => {
727
+ if (entity.hasComponent(PhysicsBody) &&
728
+ entity.hasComponent(PhysicsShape)) {
729
+ this.syncGravityFactor(entity);
730
+ }
731
+ }), this.queries.linearDampingOverrides.subscribe('disqualify', (entity) => {
732
+ if (entity.hasComponent(PhysicsBody) &&
733
+ entity.hasComponent(PhysicsShape)) {
734
+ this.syncLinearDamping(entity);
735
+ }
736
+ }), this.queries.angularDampingOverrides.subscribe('disqualify', (entity) => {
737
+ if (entity.hasComponent(PhysicsBody) &&
738
+ entity.hasComponent(PhysicsShape)) {
739
+ this.syncAngularDamping(entity);
740
+ }
741
+ }));
742
+ }
743
+ syncReactiveOverrides() {
744
+ this.queries.gravityOverrides.entities.forEach((entity) => {
745
+ this.syncGravityFactor(entity);
746
+ });
747
+ this.queries.linearDampingOverrides.entities.forEach((entity) => {
748
+ this.syncLinearDamping(entity);
749
+ });
750
+ this.queries.angularDampingOverrides.entities.forEach((entity) => {
751
+ this.syncAngularDamping(entity);
752
+ });
753
+ }
754
+ syncGravityFactor(entity) {
755
+ const index = entity.index;
756
+ const handle = PhysicsBody.data._engineBody[index];
757
+ if (!handle) {
591
758
  return;
592
759
  }
593
- const endpoints = getCapsuleAxisEndpoints(radius, totalHeight);
594
- if (!endpoints) {
595
- console.warn('PhysicsSystem: Capsule dimensions require a positive radius and a total height greater than or equal to twice the radius');
760
+ const target = PhysicsBody.data.gravityFactor[index];
761
+ if (PhysicsBody.data._engineGravityFactor[index] === target) {
596
762
  return;
597
763
  }
598
- const [pointA, pointB] = endpoints;
599
- const capsuleShape = this.havok.HP_Shape_CreateCapsule(pointA, pointB, radius)[1];
600
- this.havok.HP_Shape_SetDensity(capsuleShape, density);
601
- this.havok.HP_Shape_SetMaterial(capsuleShape, [
602
- friction,
603
- friction,
604
- restitution,
605
- this.havok.MaterialCombine.MINIMUM,
606
- this.havok.MaterialCombine.MAXIMUM,
607
- ]);
608
- return capsuleShape;
764
+ const command = this.getPendingCommand(handle);
765
+ command.flags |= 128 /* PhysicsCommandFlag.SetGravityFactor */;
766
+ command.gravityFactor = target;
767
+ PhysicsBody.data._engineGravityFactor[index] = target;
609
768
  }
610
- createConvexHullShape(object3D, density, restitution, friction) {
611
- if (!this.havok) {
612
- console.warn('PhysicsSystem: Cannot create convex hull shape - Havok physics engine not initialized');
769
+ syncLinearDamping(entity) {
770
+ const index = entity.index;
771
+ const handle = PhysicsBody.data._engineBody[index];
772
+ if (!handle) {
613
773
  return;
614
774
  }
615
- // When object3D is not a Mesh we build a merged geometry here and must
616
- // dispose it once the shape is created; a Mesh's own geometry stays in use.
617
- const ownsGeometry = !(object3D instanceof Mesh);
618
- const geometry = object3D instanceof Mesh
619
- ? object3D.geometry
620
- : generateMergedGeometry(object3D);
621
- const positionAttribute = geometry.attributes.position;
622
- if (!positionAttribute) {
623
- console.warn('PhysicsSystem: Failed to get vertices for convex hull shape with object3D name ' +
624
- object3D.name +
625
- ' &id ' +
626
- object3D.id);
627
- if (ownsGeometry) {
628
- geometry.dispose();
629
- }
775
+ const target = PhysicsBody.data.linearDamping[index];
776
+ if (PhysicsBody.data._engineLinearDamping[index] === target) {
630
777
  return;
631
778
  }
632
- const vertices = this.getVertices(positionAttribute.array);
633
- const convexHullShape = this.havok.HP_Shape_CreateConvexHull(vertices.offset, vertices.numObjects / 3)[1];
634
- this.havok._free(vertices.offset);
635
- if (ownsGeometry) {
636
- geometry.dispose();
637
- }
638
- this.havok.HP_Shape_SetDensity(convexHullShape, density);
639
- this.havok.HP_Shape_SetMaterial(convexHullShape, [
640
- friction,
641
- friction,
642
- restitution,
643
- this.havok.MaterialCombine.MINIMUM,
644
- this.havok.MaterialCombine.MAXIMUM,
645
- ]);
646
- return convexHullShape;
779
+ const command = this.getPendingCommand(handle);
780
+ command.flags |= 256 /* PhysicsCommandFlag.SetLinearDamping */;
781
+ command.linearDamping = target;
782
+ PhysicsBody.data._engineLinearDamping[index] = target;
647
783
  }
648
- createTriMeshShape(object3D, density, restitution, friction) {
649
- var _a, _b;
650
- if (!this.havok) {
651
- console.warn('PhysicsSystem: Cannot create tri-mesh shape - Havok physics engine not initialized');
784
+ syncAngularDamping(entity) {
785
+ const index = entity.index;
786
+ const handle = PhysicsBody.data._engineBody[index];
787
+ if (!handle) {
652
788
  return;
653
789
  }
654
- // When object3D is not a Mesh we build a merged geometry here and must
655
- // dispose it once the shape is created; a Mesh's own geometry stays in use.
656
- const ownsGeometry = !(object3D instanceof Mesh);
657
- const geometry = object3D instanceof Mesh
658
- ? object3D.geometry
659
- : generateMergedGeometry(object3D);
660
- const positionAttribute = geometry.attributes.position;
661
- if (!positionAttribute) {
662
- console.warn('PhysicsSystem: Failed to get vertices for tri-mesh shape with object3D name ' +
663
- object3D.name +
664
- ' &id ' +
665
- object3D.id);
666
- if (ownsGeometry) {
667
- geometry.dispose();
668
- }
790
+ const target = PhysicsBody.data.angularDamping[index];
791
+ if (PhysicsBody.data._engineAngularDamping[index] === target) {
669
792
  return;
670
793
  }
671
- const vertices = this.getVertices(positionAttribute.array);
672
- // geometry.index is null for non-indexed geometry; synthesize the implicit
673
- // sequential index list so non-indexed meshes don't dereference null here.
674
- const indices = this.getIndices((_b = (_a = geometry.index) === null || _a === void 0 ? void 0 : _a.array) !== null && _b !== void 0 ? _b : sequentialIndices(positionAttribute.count));
675
- const triMeshShape = this.havok.HP_Shape_CreateMesh(vertices.offset, vertices.numObjects / 3, indices.offset, indices.numObjects / 3)[1];
676
- this.havok._free(vertices.offset);
677
- this.havok._free(indices.offset);
678
- if (ownsGeometry) {
679
- geometry.dispose();
680
- }
681
- this.havok.HP_Shape_SetDensity(triMeshShape, density);
682
- this.havok.HP_Shape_SetMaterial(triMeshShape, [
683
- friction,
684
- friction,
685
- restitution,
686
- this.havok.MaterialCombine.MINIMUM,
687
- this.havok.MaterialCombine.MAXIMUM,
688
- ]);
689
- return triMeshShape;
690
- }
691
- getVertices(vertices) {
692
- const bytesPerFloat = 4;
693
- const nBytes = vertices.length * bytesPerFloat;
694
- const bufferBegin = this.havok._malloc(nBytes);
695
- const ret = new Float32Array(this.havok.HEAPU8.buffer, bufferBegin, vertices.length);
696
- for (let i = 0; i < vertices.length; i++) {
697
- ret[i] = vertices[i];
698
- }
699
- return { offset: bufferBegin, numObjects: vertices.length };
794
+ const command = this.getPendingCommand(handle);
795
+ command.flags |= 512 /* PhysicsCommandFlag.SetAngularDamping */;
796
+ command.angularDamping = target;
797
+ PhysicsBody.data._engineAngularDamping[index] = target;
700
798
  }
701
- getIndices(indices) {
702
- const bytesPerInt = 4;
703
- const nBytes = indices.length * bytesPerInt;
704
- const bufferBegin = this.havok._malloc(nBytes);
705
- const ret = new Int32Array(this.havok.HEAPU8.buffer, bufferBegin, indices.length);
706
- for (let i = 0; i < indices.length; i++) {
707
- ret[i] = indices[i];
708
- }
709
- return { offset: bufferBegin, numObjects: indices.length };
799
+ queueManipulations() {
800
+ this.queries.manipluatedEntities.entities.forEach((entity) => {
801
+ var _a, _b;
802
+ const handle = entity.getValue(PhysicsBody, '_engineBody');
803
+ if (!handle || !entity.object3D) {
804
+ return;
805
+ }
806
+ const force = entity.getVectorView(PhysicsManipulation, 'force');
807
+ const linearVelocity = entity.getVectorView(PhysicsManipulation, 'linearVelocity');
808
+ const angularVelocity = entity.getVectorView(PhysicsManipulation, 'angularVelocity');
809
+ const hasForce = !isZeroVector(force);
810
+ const hasLinearVelocity = !isZeroVector(linearVelocity);
811
+ const hasAngularVelocity = !isZeroVector(angularVelocity);
812
+ if (!hasForce && !hasLinearVelocity && !hasAngularVelocity) {
813
+ entity.removeComponent(PhysicsManipulation);
814
+ return;
815
+ }
816
+ const command = this.getPendingCommand(handle);
817
+ if (hasForce) {
818
+ command.flags |= 16 /* PhysicsCommandFlag.ApplyImpulse */;
819
+ const impulsePosition = (_b = (_a = this.bodySnapshots.get(handle)) === null || _a === void 0 ? void 0 : _a.currentPosition) !== null && _b !== void 0 ? _b : entity.object3D.position;
820
+ command.impulsePoint[0] = impulsePosition.x;
821
+ command.impulsePoint[1] = impulsePosition.y;
822
+ command.impulsePoint[2] = impulsePosition.z;
823
+ command.impulse[0] += force[0];
824
+ command.impulse[1] += force[1];
825
+ command.impulse[2] += force[2];
826
+ }
827
+ if (hasLinearVelocity) {
828
+ command.flags |= 32 /* PhysicsCommandFlag.SetLinearVelocity */;
829
+ command.linearVelocity[0] = linearVelocity[0];
830
+ command.linearVelocity[1] = linearVelocity[1];
831
+ command.linearVelocity[2] = linearVelocity[2];
832
+ }
833
+ if (hasAngularVelocity) {
834
+ command.flags |= 64 /* PhysicsCommandFlag.SetAngularVelocity */;
835
+ command.angularVelocity[0] = angularVelocity[0];
836
+ command.angularVelocity[1] = angularVelocity[1];
837
+ command.angularVelocity[2] = angularVelocity[2];
838
+ }
839
+ entity.removeComponent(PhysicsManipulation);
840
+ });
710
841
  }
711
842
  }
712
843