@nice2dev/ui-3d 1.3.15 → 1.3.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,324 +1,554 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var ui = require('@nice2dev/ui');
5
- var React = require('react');
6
- var THREE = require('three');
7
- var i18n = require('../core/i18n.js');
8
-
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const ui = require("@nice2dev/ui");
5
+ const React = require("react");
6
+ const THREE = require("three");
7
+ const i18n = require("../core/i18n.js");
9
8
  function _interopNamespaceDefault(e) {
10
- var n = Object.create(null);
11
- if (e) {
12
- Object.keys(e).forEach(function (k) {
13
- if (k !== 'default') {
14
- var d = Object.getOwnPropertyDescriptor(e, k);
15
- Object.defineProperty(n, k, d.get ? d : {
16
- enumerable: true,
17
- get: function () { return e[k]; }
18
- });
19
- }
9
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
10
+ if (e) {
11
+ for (const k in e) {
12
+ if (k !== "default") {
13
+ const d = Object.getOwnPropertyDescriptor(e, k);
14
+ Object.defineProperty(n, k, d.get ? d : {
15
+ enumerable: true,
16
+ get: () => e[k]
20
17
  });
18
+ }
21
19
  }
22
- n.default = e;
23
- return Object.freeze(n);
20
+ }
21
+ n.default = e;
22
+ return Object.freeze(n);
24
23
  }
25
-
26
- var THREE__namespace = /*#__PURE__*/_interopNamespaceDefault(THREE);
27
-
24
+ const THREE__namespace = /* @__PURE__ */ _interopNamespaceDefault(THREE);
28
25
  function inferColliderShape(object) {
29
- if (object instanceof THREE__namespace.Mesh) {
30
- const geo = object.geometry;
31
- if (geo instanceof THREE__namespace.SphereGeometry) {
32
- return 'sphere';
33
- }
34
- if (geo instanceof THREE__namespace.CapsuleGeometry) {
35
- return 'capsule';
36
- }
37
- if (geo instanceof THREE__namespace.BoxGeometry) {
38
- return 'box';
39
- }
40
- // Complex meshes default to convex hull
41
- if (geo.attributes.position && geo.attributes.position.count > 24) {
42
- return 'convexHull';
43
- }
26
+ if (object instanceof THREE__namespace.Mesh) {
27
+ const geo = object.geometry;
28
+ if (geo instanceof THREE__namespace.SphereGeometry) {
29
+ return "sphere";
30
+ }
31
+ if (geo instanceof THREE__namespace.CapsuleGeometry) {
32
+ return "capsule";
33
+ }
34
+ if (geo instanceof THREE__namespace.BoxGeometry) {
35
+ return "box";
36
+ }
37
+ if (geo.attributes.position && geo.attributes.position.count > 24) {
38
+ return "convexHull";
44
39
  }
45
- return 'box';
40
+ }
41
+ return "box";
46
42
  }
47
43
  function computeAABB(object) {
48
- return new THREE__namespace.Box3().setFromObject(object);
44
+ return new THREE__namespace.Box3().setFromObject(object);
49
45
  }
50
- /* ── Hook ── */
51
46
  let bodyIdCounter = 0;
52
47
  let jointIdCounter = 0;
53
48
  function usePhysicsSimulation(scene, onSimulationStep) {
54
- const [worldConfig, setWorldConfigState] = React.useState({
55
- gravity: new THREE__namespace.Vector3(0, -9.81, 0),
56
- timestep: 1 / 60,
57
- maxSubsteps: 4,
58
- });
59
- const [bodies, setBodies] = React.useState([]);
60
- const [joints, setJoints] = React.useState([]);
61
- const [isRunning, setIsRunning] = React.useState(false);
62
- const [debugDraw, setDebugDraw] = React.useState(false);
63
- const internalBodies = React.useRef(new Map());
64
- const debugGroup = React.useRef(new THREE__namespace.Group());
65
- const animFrameId = React.useRef(0);
66
- const lastTime = React.useRef(0);
67
- // Debug wireframe colliders
68
- React.useEffect(() => {
69
- if (!scene) {
70
- return;
49
+ const [worldConfig, setWorldConfigState] = React.useState({
50
+ gravity: new THREE__namespace.Vector3(0, -9.81, 0),
51
+ timestep: 1 / 60,
52
+ maxSubsteps: 4
53
+ });
54
+ const [bodies, setBodies] = React.useState([]);
55
+ const [joints, setJoints] = React.useState([]);
56
+ const [isRunning, setIsRunning] = React.useState(false);
57
+ const [debugDraw, setDebugDraw] = React.useState(false);
58
+ const internalBodies = React.useRef(/* @__PURE__ */ new Map());
59
+ const debugGroup = React.useRef(new THREE__namespace.Group());
60
+ const animFrameId = React.useRef(0);
61
+ const lastTime = React.useRef(0);
62
+ React.useEffect(() => {
63
+ if (!scene) {
64
+ return;
65
+ }
66
+ const group = debugGroup.current;
67
+ group.name = "__physics_debug__";
68
+ if (debugDraw) {
69
+ scene.add(group);
70
+ }
71
+ return () => {
72
+ scene.remove(group);
73
+ };
74
+ }, [scene, debugDraw]);
75
+ const setWorldConfig = React.useCallback((partial) => {
76
+ setWorldConfigState((prev) => ({ ...prev, ...partial }));
77
+ }, []);
78
+ const addBody = React.useCallback(
79
+ (object) => {
80
+ const id = `phys_${++bodyIdCounter}`;
81
+ const desc = {
82
+ id,
83
+ objectName: object.name || object.uuid,
84
+ bodyType: "dynamic",
85
+ colliderShape: inferColliderShape(object),
86
+ mass: 1,
87
+ restitution: 0.3,
88
+ friction: 0.5,
89
+ linearDamping: 0.01,
90
+ angularDamping: 0.05,
91
+ gravityScale: 1,
92
+ isSensor: false
93
+ };
94
+ const internal = {
95
+ desc,
96
+ object,
97
+ velocity: new THREE__namespace.Vector3(),
98
+ angularVelocity: new THREE__namespace.Vector3(),
99
+ initialPosition: object.position.clone(),
100
+ initialRotation: object.quaternion.clone(),
101
+ initialScale: object.scale.clone()
102
+ };
103
+ internalBodies.current.set(id, internal);
104
+ setBodies((prev) => [...prev, desc]);
105
+ if (debugDraw) {
106
+ addDebugCollider(object, desc.colliderShape, id);
107
+ }
108
+ return desc;
109
+ },
110
+ [debugDraw]
111
+ );
112
+ const removeBody = React.useCallback((id) => {
113
+ internalBodies.current.delete(id);
114
+ setBodies((prev) => prev.filter((b) => b.id !== id));
115
+ const debugObj = debugGroup.current.getObjectByName(`debug_${id}`);
116
+ if (debugObj) {
117
+ debugGroup.current.remove(debugObj);
118
+ }
119
+ }, []);
120
+ const updateBody = React.useCallback((id, partial) => {
121
+ const internal = internalBodies.current.get(id);
122
+ if (internal) {
123
+ Object.assign(internal.desc, partial);
124
+ }
125
+ setBodies((prev) => prev.map((b) => b.id === id ? { ...b, ...partial } : b));
126
+ }, []);
127
+ const addJoint = React.useCallback((joint) => {
128
+ const id = `joint_${++jointIdCounter}`;
129
+ setJoints((prev) => [...prev, { ...joint, id }]);
130
+ }, []);
131
+ const removeJoint = React.useCallback((id) => {
132
+ setJoints((prev) => prev.filter((j) => j.id !== id));
133
+ }, []);
134
+ const getBodyForObject = React.useCallback(
135
+ (objectName) => bodies.find((b) => b.objectName === objectName),
136
+ [bodies]
137
+ );
138
+ const simulationStep = React.useCallback(
139
+ (dt) => {
140
+ const gravity = worldConfig.gravity;
141
+ internalBodies.current.forEach((body) => {
142
+ if (body.desc.bodyType !== "dynamic") {
143
+ return;
71
144
  }
72
- const group = debugGroup.current;
73
- group.name = '__physics_debug__';
74
- if (debugDraw) {
75
- scene.add(group);
145
+ body.velocity.addScaledVector(gravity, dt * body.desc.gravityScale);
146
+ body.velocity.multiplyScalar(1 - body.desc.linearDamping);
147
+ body.angularVelocity.multiplyScalar(1 - body.desc.angularDamping);
148
+ body.object.position.addScaledVector(body.velocity, dt);
149
+ if (body.angularVelocity.lengthSq() > 1e-4) {
150
+ const q = new THREE__namespace.Quaternion().setFromAxisAngle(
151
+ body.angularVelocity.clone().normalize(),
152
+ body.angularVelocity.length() * dt
153
+ );
154
+ body.object.quaternion.premultiply(q);
76
155
  }
77
- return () => {
78
- scene.remove(group);
79
- };
80
- }, [scene, debugDraw]);
81
- const setWorldConfig = React.useCallback((partial) => {
82
- setWorldConfigState((prev) => ({ ...prev, ...partial }));
83
- }, []);
84
- const addBody = React.useCallback((object) => {
85
- const id = `phys_${++bodyIdCounter}`;
86
- const desc = {
87
- id,
88
- objectName: object.name || object.uuid,
89
- bodyType: 'dynamic',
90
- colliderShape: inferColliderShape(object),
91
- mass: 1,
92
- restitution: 0.3,
93
- friction: 0.5,
94
- linearDamping: 0.01,
95
- angularDamping: 0.05,
96
- gravityScale: 1,
97
- isSensor: false,
98
- };
99
- const internal = {
100
- desc,
101
- object,
102
- velocity: new THREE__namespace.Vector3(),
103
- angularVelocity: new THREE__namespace.Vector3(),
104
- initialPosition: object.position.clone(),
105
- initialRotation: object.quaternion.clone(),
106
- initialScale: object.scale.clone(),
107
- };
108
- internalBodies.current.set(id, internal);
109
- setBodies((prev) => [...prev, desc]);
110
- // Add debug wireframe
111
- if (debugDraw) {
112
- addDebugCollider(object, desc.colliderShape, id);
156
+ const aabb = computeAABB(body.object);
157
+ if (aabb.min.y < 0) {
158
+ body.object.position.y -= aabb.min.y;
159
+ body.velocity.y = -body.velocity.y * body.desc.restitution;
160
+ body.velocity.x *= 1 - body.desc.friction * dt;
161
+ body.velocity.z *= 1 - body.desc.friction * dt;
113
162
  }
114
- return desc;
115
- }, [debugDraw]);
116
- const removeBody = React.useCallback((id) => {
117
- internalBodies.current.delete(id);
118
- setBodies((prev) => prev.filter((b) => b.id !== id));
119
- // Remove debug wireframe
120
- const debugObj = debugGroup.current.getObjectByName(`debug_${id}`);
163
+ const debugObj = debugGroup.current.getObjectByName(`debug_${body.desc.id}`);
121
164
  if (debugObj) {
122
- debugGroup.current.remove(debugObj);
165
+ debugObj.position.copy(body.object.position);
166
+ debugObj.quaternion.copy(body.object.quaternion);
123
167
  }
124
- }, []);
125
- const updateBody = React.useCallback((id, partial) => {
126
- const internal = internalBodies.current.get(id);
127
- if (internal) {
128
- Object.assign(internal.desc, partial);
129
- }
130
- setBodies((prev) => prev.map((b) => (b.id === id ? { ...b, ...partial } : b)));
131
- }, []);
132
- const addJoint = React.useCallback((joint) => {
133
- const id = `joint_${++jointIdCounter}`;
134
- setJoints((prev) => [...prev, { ...joint, id }]);
135
- }, []);
136
- const removeJoint = React.useCallback((id) => {
137
- setJoints((prev) => prev.filter((j) => j.id !== id));
138
- }, []);
139
- const getBodyForObject = React.useCallback((objectName) => bodies.find((b) => b.objectName === objectName), [bodies]);
140
- // Simplified physics step (Euler integration with collision detection)
141
- const simulationStep = React.useCallback((dt) => {
142
- const gravity = worldConfig.gravity;
143
- internalBodies.current.forEach((body) => {
144
- if (body.desc.bodyType !== 'dynamic') {
145
- return;
168
+ });
169
+ const dynamicBodies = [...internalBodies.current.values()].filter(
170
+ (b) => b.desc.bodyType === "dynamic"
171
+ );
172
+ for (let i = 0; i < dynamicBodies.length; i++) {
173
+ for (let j = i + 1; j < dynamicBodies.length; j++) {
174
+ const a = dynamicBodies[i];
175
+ const b = dynamicBodies[j];
176
+ const aabbA = computeAABB(a.object);
177
+ const aabbB = computeAABB(b.object);
178
+ if (aabbA.intersectsBox(aabbB)) {
179
+ const normal = new THREE__namespace.Vector3().subVectors(b.object.position, a.object.position).normalize();
180
+ const relVel = new THREE__namespace.Vector3().subVectors(a.velocity, b.velocity);
181
+ const velAlongNormal = relVel.dot(normal);
182
+ if (velAlongNormal > 0) {
183
+ continue;
146
184
  }
147
- // Apply gravity
148
- body.velocity.addScaledVector(gravity, dt * body.desc.gravityScale);
149
- // Apply damping
150
- body.velocity.multiplyScalar(1 - body.desc.linearDamping);
151
- body.angularVelocity.multiplyScalar(1 - body.desc.angularDamping);
152
- // Integrate position
153
- body.object.position.addScaledVector(body.velocity, dt);
154
- // Integrate rotation (simplified)
155
- if (body.angularVelocity.lengthSq() > 0.0001) {
156
- const q = new THREE__namespace.Quaternion().setFromAxisAngle(body.angularVelocity.clone().normalize(), body.angularVelocity.length() * dt);
157
- body.object.quaternion.premultiply(q);
185
+ const restitution = Math.min(a.desc.restitution, b.desc.restitution);
186
+ const impulseMag = -(1 + restitution) * velAlongNormal / (1 / a.desc.mass + 1 / b.desc.mass);
187
+ const impulse = normal.clone().multiplyScalar(impulseMag);
188
+ a.velocity.addScaledVector(impulse, 1 / a.desc.mass);
189
+ b.velocity.addScaledVector(impulse, -1 / b.desc.mass);
190
+ const overlap = new THREE__namespace.Vector3();
191
+ aabbA.intersect(aabbB).getSize(overlap);
192
+ const sep = Math.min(overlap.x, overlap.y, overlap.z) * 0.5;
193
+ a.object.position.addScaledVector(normal, -sep);
194
+ b.object.position.addScaledVector(normal, sep);
195
+ }
196
+ }
197
+ }
198
+ onSimulationStep?.(dt);
199
+ },
200
+ [worldConfig.gravity, onSimulationStep]
201
+ );
202
+ React.useEffect(() => {
203
+ if (!isRunning) {
204
+ return;
205
+ }
206
+ lastTime.current = performance.now();
207
+ const loop = (now) => {
208
+ const dt = Math.min(
209
+ (now - lastTime.current) / 1e3,
210
+ worldConfig.timestep * worldConfig.maxSubsteps
211
+ );
212
+ lastTime.current = now;
213
+ let accum = dt;
214
+ while (accum >= worldConfig.timestep) {
215
+ simulationStep(worldConfig.timestep);
216
+ accum -= worldConfig.timestep;
217
+ }
218
+ animFrameId.current = requestAnimationFrame(loop);
219
+ };
220
+ animFrameId.current = requestAnimationFrame(loop);
221
+ return () => cancelAnimationFrame(animFrameId.current);
222
+ }, [isRunning, simulationStep, worldConfig.timestep, worldConfig.maxSubsteps]);
223
+ const play = React.useCallback(() => setIsRunning(true), []);
224
+ const pause = React.useCallback(() => setIsRunning(false), []);
225
+ const step = React.useCallback(() => {
226
+ simulationStep(worldConfig.timestep);
227
+ }, [simulationStep, worldConfig.timestep]);
228
+ const reset = React.useCallback(() => {
229
+ setIsRunning(false);
230
+ internalBodies.current.forEach((body) => {
231
+ body.object.position.copy(body.initialPosition);
232
+ body.object.quaternion.copy(body.initialRotation);
233
+ body.object.scale.copy(body.initialScale);
234
+ body.velocity.set(0, 0, 0);
235
+ body.angularVelocity.set(0, 0, 0);
236
+ });
237
+ }, []);
238
+ function addDebugCollider(object, shape, id) {
239
+ let geo;
240
+ const aabb = computeAABB(object);
241
+ const size = new THREE__namespace.Vector3();
242
+ aabb.getSize(size);
243
+ switch (shape) {
244
+ case "sphere":
245
+ geo = new THREE__namespace.SphereGeometry(Math.max(size.x, size.y, size.z) / 2, 12, 8);
246
+ break;
247
+ case "capsule":
248
+ geo = new THREE__namespace.CapsuleGeometry(size.x / 2, size.y - size.x, 8, 4);
249
+ break;
250
+ default:
251
+ geo = new THREE__namespace.BoxGeometry(size.x, size.y, size.z);
252
+ }
253
+ const wireframe = new THREE__namespace.LineSegments(
254
+ new THREE__namespace.WireframeGeometry(geo),
255
+ new THREE__namespace.LineBasicMaterial({ color: 65280, opacity: 0.5, transparent: true })
256
+ );
257
+ wireframe.name = `debug_${id}`;
258
+ wireframe.position.copy(object.position);
259
+ wireframe.quaternion.copy(object.quaternion);
260
+ debugGroup.current.add(wireframe);
261
+ }
262
+ return {
263
+ worldConfig,
264
+ setWorldConfig,
265
+ bodies,
266
+ joints,
267
+ addBody,
268
+ removeBody,
269
+ updateBody,
270
+ addJoint,
271
+ removeJoint,
272
+ getBodyForObject,
273
+ isRunning,
274
+ play,
275
+ pause,
276
+ step,
277
+ reset,
278
+ debugDraw,
279
+ setDebugDraw
280
+ };
281
+ }
282
+ const NicePhysicsSimulation = ({
283
+ scene,
284
+ selectedObject,
285
+ onSimulationStep,
286
+ className = ""
287
+ }) => {
288
+ const { t } = i18n.useNiceTranslation();
289
+ const {
290
+ worldConfig,
291
+ setWorldConfig,
292
+ bodies,
293
+ addBody,
294
+ removeBody,
295
+ updateBody,
296
+ getBodyForObject,
297
+ isRunning,
298
+ play,
299
+ pause,
300
+ step,
301
+ reset,
302
+ debugDraw,
303
+ setDebugDraw
304
+ } = usePhysicsSimulation(scene, onSimulationStep);
305
+ const selectedBody = selectedObject ? getBodyForObject(selectedObject.name || selectedObject.uuid) : void 0;
306
+ return /* @__PURE__ */ jsxRuntime.jsxs(
307
+ "div",
308
+ {
309
+ className: `nice-physics-simulation ${className}`,
310
+ style: { display: "grid", gap: 8, fontSize: 13 },
311
+ children: [
312
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
313
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
314
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }),
315
+ " ",
316
+ t("physicsSimulation.physics", "Physics")
317
+ ] }),
318
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 4, fontSize: 11 }, children: [
319
+ /* @__PURE__ */ jsxRuntime.jsx(
320
+ "input",
321
+ {
322
+ type: "checkbox",
323
+ checked: debugDraw,
324
+ onChange: (e) => setDebugDraw(e.target.checked)
325
+ }
326
+ ),
327
+ t("physicsSimulation.debug", "Debug")
328
+ ] })
329
+ ] }),
330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 4 }, children: [
331
+ /* @__PURE__ */ jsxRuntime.jsx(
332
+ "button",
333
+ {
334
+ type: "button",
335
+ onClick: isRunning ? pause : play,
336
+ style: {
337
+ flex: 1,
338
+ background: isRunning ? "var(--nice-danger, #ef4444)" : "var(--nice-success, #22c55e)",
339
+ color: "var(--nice-color-white, #fff)",
340
+ border: "none",
341
+ borderRadius: 4,
342
+ padding: "5px 8px",
343
+ cursor: "pointer"
344
+ },
345
+ children: isRunning ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
346
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }),
347
+ " ",
348
+ t("physicsSimulation.pause", "Pause")
349
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }),
351
+ " ",
352
+ t("physicsSimulation.play", "Play")
353
+ ] })
158
354
  }
159
- // Ground plane collision (y = 0)
160
- const aabb = computeAABB(body.object);
161
- if (aabb.min.y < 0) {
162
- body.object.position.y -= aabb.min.y;
163
- body.velocity.y = -body.velocity.y * body.desc.restitution;
164
- // Friction on ground
165
- body.velocity.x *= 1 - body.desc.friction * dt;
166
- body.velocity.z *= 1 - body.desc.friction * dt;
355
+ ),
356
+ /* @__PURE__ */ jsxRuntime.jsxs(
357
+ "button",
358
+ {
359
+ type: "button",
360
+ onClick: step,
361
+ disabled: isRunning,
362
+ style: { padding: "5px 8px", cursor: "pointer" },
363
+ children: [
364
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }),
365
+ " ",
366
+ t("physicsSimulation.step", "Step")
367
+ ]
167
368
  }
168
- // Update debug wireframe position
169
- const debugObj = debugGroup.current.getObjectByName(`debug_${body.desc.id}`);
170
- if (debugObj) {
171
- debugObj.position.copy(body.object.position);
172
- debugObj.quaternion.copy(body.object.quaternion);
369
+ ),
370
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: "5px 8px", cursor: "pointer" }, children: [
371
+ /* @__PURE__ */ jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }),
372
+ " ",
373
+ t("physicsSimulation.reset", "Reset")
374
+ ] })
375
+ ] }),
376
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
377
+ t("physicsSimulation.gravityY", "Gravity Y: {y}").replace(
378
+ "{y}",
379
+ String(worldConfig.gravity.y.toFixed(1))
380
+ ),
381
+ /* @__PURE__ */ jsxRuntime.jsx(
382
+ "input",
383
+ {
384
+ type: "range",
385
+ min: -20,
386
+ max: 0,
387
+ step: 0.1,
388
+ value: worldConfig.gravity.y,
389
+ onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }),
390
+ style: { width: "100%" }
173
391
  }
174
- });
175
- // AABB body-body collision (simplified)
176
- const dynamicBodies = [...internalBodies.current.values()].filter((b) => b.desc.bodyType === 'dynamic');
177
- for (let i = 0; i < dynamicBodies.length; i++) {
178
- for (let j = i + 1; j < dynamicBodies.length; j++) {
179
- const a = dynamicBodies[i];
180
- const b = dynamicBodies[j];
181
- const aabbA = computeAABB(a.object);
182
- const aabbB = computeAABB(b.object);
183
- if (aabbA.intersectsBox(aabbB)) {
184
- // Simple elastic collision response
185
- const normal = new THREE__namespace.Vector3()
186
- .subVectors(b.object.position, a.object.position)
187
- .normalize();
188
- const relVel = new THREE__namespace.Vector3().subVectors(a.velocity, b.velocity);
189
- const velAlongNormal = relVel.dot(normal);
190
- if (velAlongNormal > 0) {
191
- continue;
192
- } // separating
193
- const restitution = Math.min(a.desc.restitution, b.desc.restitution);
194
- const impulseMag = (-(1 + restitution) * velAlongNormal) / (1 / a.desc.mass + 1 / b.desc.mass);
195
- const impulse = normal.clone().multiplyScalar(impulseMag);
196
- a.velocity.addScaledVector(impulse, 1 / a.desc.mass);
197
- b.velocity.addScaledVector(impulse, -1 / b.desc.mass);
198
- // Separate overlapping bodies
199
- const overlap = new THREE__namespace.Vector3();
200
- aabbA.intersect(aabbB).getSize(overlap);
201
- const sep = Math.min(overlap.x, overlap.y, overlap.z) * 0.5;
202
- a.object.position.addScaledVector(normal, -sep);
203
- b.object.position.addScaledVector(normal, sep);
392
+ )
393
+ ] }),
394
+ selectedObject && /* @__PURE__ */ jsxRuntime.jsxs(
395
+ "fieldset",
396
+ {
397
+ style: {
398
+ border: "1px solid var(--nice-border, #e2e8f0)",
399
+ borderRadius: 6,
400
+ padding: 6,
401
+ display: "grid",
402
+ gap: 4
403
+ },
404
+ children: [
405
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t("physicsSimulation.objectPhysics", "{name} — Physics").replace(
406
+ "{name}",
407
+ selectedObject.name || t("physicsSimulation.object", "Object")
408
+ ) }),
409
+ !selectedBody ? /* @__PURE__ */ jsxRuntime.jsx(
410
+ "button",
411
+ {
412
+ type: "button",
413
+ onClick: () => addBody(selectedObject),
414
+ style: {
415
+ background: "var(--nice-color-accent, #6366f1)",
416
+ color: "var(--nice-text-on-primary, #fff)",
417
+ border: "none",
418
+ borderRadius: 4,
419
+ padding: "5px 8px",
420
+ cursor: "pointer"
421
+ },
422
+ children: t("physicsSimulation.addRigidBody", "+ Add Rigid Body")
204
423
  }
205
- }
206
- }
207
- onSimulationStep === null || onSimulationStep === void 0 ? void 0 : onSimulationStep(dt);
208
- }, [worldConfig.gravity, onSimulationStep]);
209
- // Animation loop
210
- React.useEffect(() => {
211
- if (!isRunning) {
212
- return;
213
- }
214
- lastTime.current = performance.now();
215
- const loop = (now) => {
216
- const dt = Math.min((now - lastTime.current) / 1000, worldConfig.timestep * worldConfig.maxSubsteps);
217
- lastTime.current = now;
218
- let accum = dt;
219
- while (accum >= worldConfig.timestep) {
220
- simulationStep(worldConfig.timestep);
221
- accum -= worldConfig.timestep;
222
- }
223
- animFrameId.current = requestAnimationFrame(loop);
224
- };
225
- animFrameId.current = requestAnimationFrame(loop);
226
- return () => cancelAnimationFrame(animFrameId.current);
227
- }, [isRunning, simulationStep, worldConfig.timestep, worldConfig.maxSubsteps]);
228
- const play = React.useCallback(() => setIsRunning(true), []);
229
- const pause = React.useCallback(() => setIsRunning(false), []);
230
- const step = React.useCallback(() => {
231
- simulationStep(worldConfig.timestep);
232
- }, [simulationStep, worldConfig.timestep]);
233
- const reset = React.useCallback(() => {
234
- setIsRunning(false);
235
- internalBodies.current.forEach((body) => {
236
- body.object.position.copy(body.initialPosition);
237
- body.object.quaternion.copy(body.initialRotation);
238
- body.object.scale.copy(body.initialScale);
239
- body.velocity.set(0, 0, 0);
240
- body.angularVelocity.set(0, 0, 0);
241
- });
242
- }, []);
243
- function addDebugCollider(object, shape, id) {
244
- let geo;
245
- const aabb = computeAABB(object);
246
- const size = new THREE__namespace.Vector3();
247
- aabb.getSize(size);
248
- switch (shape) {
249
- case 'sphere':
250
- geo = new THREE__namespace.SphereGeometry(Math.max(size.x, size.y, size.z) / 2, 12, 8);
251
- break;
252
- case 'capsule':
253
- geo = new THREE__namespace.CapsuleGeometry(size.x / 2, size.y - size.x, 8, 4);
254
- break;
255
- default:
256
- geo = new THREE__namespace.BoxGeometry(size.x, size.y, size.z);
257
- }
258
- const wireframe = new THREE__namespace.LineSegments(new THREE__namespace.WireframeGeometry(geo), new THREE__namespace.LineBasicMaterial({ color: 0x00ff00, opacity: 0.5, transparent: true }));
259
- wireframe.name = `debug_${id}`;
260
- wireframe.position.copy(object.position);
261
- wireframe.quaternion.copy(object.quaternion);
262
- debugGroup.current.add(wireframe);
424
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
425
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
426
+ t("physicsSimulation.type", "Type:"),
427
+ /* @__PURE__ */ jsxRuntime.jsxs(
428
+ "select",
429
+ {
430
+ value: selectedBody.bodyType,
431
+ onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }),
432
+ style: { marginLeft: 8, width: "100%" },
433
+ children: [
434
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "dynamic", children: t("physicsSimulation.dynamic", "Dynamic") }),
435
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "static", children: t("physicsSimulation.static", "Static") }),
436
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "kinematic", children: t("physicsSimulation.kinematic", "Kinematic") })
437
+ ]
438
+ }
439
+ )
440
+ ] }),
441
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
442
+ t("physicsSimulation.shape", "Shape:"),
443
+ /* @__PURE__ */ jsxRuntime.jsxs(
444
+ "select",
445
+ {
446
+ value: selectedBody.colliderShape,
447
+ onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }),
448
+ style: { marginLeft: 8, width: "100%" },
449
+ children: [
450
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "box", children: t("physicsSimulation.box", "Box") }),
451
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "sphere", children: t("physicsSimulation.sphere", "Sphere") }),
452
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "capsule", children: t("physicsSimulation.capsule", "Capsule") }),
453
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "convexHull", children: t("physicsSimulation.convexHull", "Convex Hull") }),
454
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "trimesh", children: t("physicsSimulation.trimesh", "Trimesh") })
455
+ ]
456
+ }
457
+ )
458
+ ] }),
459
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
460
+ t("physicsSimulation.mass", "Mass: {mass}").replace(
461
+ "{mass}",
462
+ String(selectedBody.mass.toFixed(1))
463
+ ),
464
+ /* @__PURE__ */ jsxRuntime.jsx(
465
+ "input",
466
+ {
467
+ type: "range",
468
+ min: 0.1,
469
+ max: 100,
470
+ step: 0.1,
471
+ value: selectedBody.mass,
472
+ onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }),
473
+ style: { width: "100%" }
474
+ }
475
+ )
476
+ ] }),
477
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
478
+ t("physicsSimulation.restitution", "Restitution: {restitution}").replace(
479
+ "{restitution}",
480
+ String(selectedBody.restitution.toFixed(2))
481
+ ),
482
+ /* @__PURE__ */ jsxRuntime.jsx(
483
+ "input",
484
+ {
485
+ type: "range",
486
+ min: 0,
487
+ max: 1,
488
+ step: 0.01,
489
+ value: selectedBody.restitution,
490
+ onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }),
491
+ style: { width: "100%" }
492
+ }
493
+ )
494
+ ] }),
495
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
496
+ t("physicsSimulation.friction", "Friction: {friction}").replace(
497
+ "{friction}",
498
+ String(selectedBody.friction.toFixed(2))
499
+ ),
500
+ /* @__PURE__ */ jsxRuntime.jsx(
501
+ "input",
502
+ {
503
+ type: "range",
504
+ min: 0,
505
+ max: 1,
506
+ step: 0.01,
507
+ value: selectedBody.friction,
508
+ onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }),
509
+ style: { width: "100%" }
510
+ }
511
+ )
512
+ ] }),
513
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
514
+ /* @__PURE__ */ jsxRuntime.jsx(
515
+ "input",
516
+ {
517
+ type: "checkbox",
518
+ checked: selectedBody.isSensor,
519
+ onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked })
520
+ }
521
+ ),
522
+ t("physicsSimulation.sensorTriggerOnly", "Sensor (trigger only)")
523
+ ] }),
524
+ /* @__PURE__ */ jsxRuntime.jsx(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ onClick: () => removeBody(selectedBody.id),
529
+ style: {
530
+ background: "var(--nice-danger, #ef4444)",
531
+ color: "var(--nice-text-on-primary, #fff)",
532
+ border: "none",
533
+ borderRadius: 4,
534
+ padding: "4px 8px",
535
+ cursor: "pointer",
536
+ fontSize: 11
537
+ },
538
+ children: t("physicsSimulation.removeRigidBody", "Remove Rigid Body")
539
+ }
540
+ )
541
+ ] })
542
+ ]
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t("physicsSimulation.totalBodies", "Total bodies: {bodies}").replace(
546
+ "{bodies}",
547
+ String(bodies.length)
548
+ ) })
549
+ ]
263
550
  }
264
- return {
265
- worldConfig,
266
- setWorldConfig,
267
- bodies,
268
- joints,
269
- addBody,
270
- removeBody,
271
- updateBody,
272
- addJoint,
273
- removeJoint,
274
- getBodyForObject,
275
- isRunning,
276
- play,
277
- pause,
278
- step,
279
- reset,
280
- debugDraw,
281
- setDebugDraw,
282
- };
283
- }
284
- /* ── Component ── */
285
- const NicePhysicsSimulation = ({ scene, selectedObject, onSimulationStep, className = '', }) => {
286
- const { t } = i18n.useNiceTranslation();
287
- const { worldConfig, setWorldConfig, bodies, addBody, removeBody, updateBody, getBodyForObject, isRunning, play, pause, step, reset, debugDraw, setDebugDraw, } = usePhysicsSimulation(scene, onSimulationStep);
288
- const selectedBody = selectedObject
289
- ? getBodyForObject(selectedObject.name || selectedObject.uuid)
290
- : undefined;
291
- return (jsxRuntime.jsxs("div", { className: `nice-physics-simulation ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "zap", size: 14 }), " ", t('physicsSimulation.physics', 'Physics')] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: debugDraw, onChange: (e) => setDebugDraw(e.target.checked) }), t('physicsSimulation.debug', 'Debug')] })] }), jsxRuntime.jsxs("div", { style: { display: 'flex', gap: 4 }, children: [jsxRuntime.jsx("button", { type: "button", onClick: isRunning ? pause : play, style: {
292
- flex: 1,
293
- background: isRunning ? 'var(--nice-danger, #ef4444)' : 'var(--nice-success, #22c55e)',
294
- color: 'var(--nice-color-white, #fff)',
295
- border: 'none',
296
- borderRadius: 4,
297
- padding: '5px 8px',
298
- cursor: 'pointer',
299
- }, children: isRunning ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "pause", size: 14 }), " ", t('physicsSimulation.pause', 'Pause')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(ui.NiceIcon, { name: "play", size: 14 }), " ", t('physicsSimulation.play', 'Play')] })) }), jsxRuntime.jsxs("button", { type: "button", onClick: step, disabled: isRunning, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "skip-forward", size: 14 }), " ", t('physicsSimulation.step', 'Step')] }), jsxRuntime.jsxs("button", { type: "button", onClick: reset, style: { padding: '5px 8px', cursor: 'pointer' }, children: [jsxRuntime.jsx(ui.NiceIcon, { name: "refresh", size: 14 }), " ", t('physicsSimulation.reset', 'Reset')] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.gravityY', 'Gravity Y: {y}').replace('{y}', String(worldConfig.gravity.y.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: -20, max: 0, step: 0.1, value: worldConfig.gravity.y, onChange: (e) => setWorldConfig({ gravity: new THREE__namespace.Vector3(0, parseFloat(e.target.value), 0) }), style: { width: '100%' } })] }), selectedObject && (jsxRuntime.jsxs("fieldset", { style: {
300
- border: '1px solid var(--nice-border, #e2e8f0)',
301
- borderRadius: 6,
302
- padding: 6,
303
- display: 'grid',
304
- gap: 4,
305
- }, children: [jsxRuntime.jsx("legend", { style: { fontSize: 11 }, children: t('physicsSimulation.objectPhysics', '{name} — Physics').replace('{name}', selectedObject.name || t('physicsSimulation.object', 'Object')) }), !selectedBody ? (jsxRuntime.jsx("button", { type: "button", onClick: () => addBody(selectedObject), style: {
306
- background: 'var(--nice-color-accent, #6366f1)',
307
- color: 'var(--nice-text-on-primary, #fff)',
308
- border: 'none',
309
- borderRadius: 4,
310
- padding: '5px 8px',
311
- cursor: 'pointer',
312
- }, children: t('physicsSimulation.addRigidBody', '+ Add Rigid Body') })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsxs("label", { children: [t('physicsSimulation.type', 'Type:'), jsxRuntime.jsxs("select", { value: selectedBody.bodyType, onChange: (e) => updateBody(selectedBody.id, { bodyType: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "dynamic", children: t('physicsSimulation.dynamic', 'Dynamic') }), jsxRuntime.jsx("option", { value: "static", children: t('physicsSimulation.static', 'Static') }), jsxRuntime.jsx("option", { value: "kinematic", children: t('physicsSimulation.kinematic', 'Kinematic') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.shape', 'Shape:'), jsxRuntime.jsxs("select", { value: selectedBody.colliderShape, onChange: (e) => updateBody(selectedBody.id, { colliderShape: e.target.value }), style: { marginLeft: 8, width: '100%' }, children: [jsxRuntime.jsx("option", { value: "box", children: t('physicsSimulation.box', 'Box') }), jsxRuntime.jsx("option", { value: "sphere", children: t('physicsSimulation.sphere', 'Sphere') }), jsxRuntime.jsx("option", { value: "capsule", children: t('physicsSimulation.capsule', 'Capsule') }), jsxRuntime.jsx("option", { value: "convexHull", children: t('physicsSimulation.convexHull', 'Convex Hull') }), jsxRuntime.jsx("option", { value: "trimesh", children: t('physicsSimulation.trimesh', 'Trimesh') })] })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.mass', 'Mass: {mass}').replace('{mass}', String(selectedBody.mass.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 100, step: 0.1, value: selectedBody.mass, onChange: (e) => updateBody(selectedBody.id, { mass: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.restitution', 'Restitution: {restitution}').replace('{restitution}', String(selectedBody.restitution.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.restitution, onChange: (e) => updateBody(selectedBody.id, { restitution: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('physicsSimulation.friction', 'Friction: {friction}').replace('{friction}', String(selectedBody.friction.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: selectedBody.friction, onChange: (e) => updateBody(selectedBody.id, { friction: parseFloat(e.target.value) }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: selectedBody.isSensor, onChange: (e) => updateBody(selectedBody.id, { isSensor: e.target.checked }) }), t('physicsSimulation.sensorTriggerOnly', 'Sensor (trigger only)')] }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeBody(selectedBody.id), style: {
313
- background: 'var(--nice-danger, #ef4444)',
314
- color: 'var(--nice-text-on-primary, #fff)',
315
- border: 'none',
316
- borderRadius: 4,
317
- padding: '4px 8px',
318
- cursor: 'pointer',
319
- fontSize: 11,
320
- }, children: t('physicsSimulation.removeRigidBody', 'Remove Rigid Body') })] }))] })), jsxRuntime.jsx("div", { style: { fontSize: 11, opacity: 0.7 }, children: t('physicsSimulation.totalBodies', 'Total bodies: {bodies}').replace('{bodies}', String(bodies.length)) })] }));
551
+ );
321
552
  };
322
-
323
553
  exports.NicePhysicsSimulation = NicePhysicsSimulation;
324
554
  exports.usePhysicsSimulation = usePhysicsSimulation;