react-three-game 0.0.106 → 0.0.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +74 -46
- package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
- package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +23 -32
- package/dist/plugins/crashcat/index.d.ts +1 -0
- package/dist/plugins/crashcat/index.js +1 -0
- package/dist/tools/assetviewer/page.js +4 -4
- package/dist/tools/prefabeditor/EditorTree.js +5 -2
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +17 -5
- package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +2 -1
- package/dist/tools/prefabeditor/PrefabEditor.js +26 -13
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +3 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +61 -25
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +15 -0
- package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
- package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
- package/dist/tools/prefabeditor/modelPrefab.js +44 -11
- package/dist/tools/prefabeditor/prefab.d.ts +5 -4
- package/dist/tools/prefabeditor/prefab.js +47 -29
- package/dist/tools/prefabeditor/utils.d.ts +8 -1
- package/dist/tools/prefabeditor/utils.js +74 -22
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -110,6 +110,13 @@ That means authored content stays as a prefab, and the same prefab can be:
|
|
|
110
110
|
|
|
111
111
|
Custom component `View`s use normal React Three Fiber composition with `children`.
|
|
112
112
|
|
|
113
|
+
When you import or decompose a `.glb` or `.gltf` model, mesh names can opt into imported Crashcat colliders:
|
|
114
|
+
|
|
115
|
+
* `MeshName_col` keeps the mesh visible and adds a fixed `CrashcatPhysics` `trimesh` collider.
|
|
116
|
+
* `MeshName_colonly` adds the same collider but hides the decomposed mesh render.
|
|
117
|
+
|
|
118
|
+
This mirrors the common Blender authoring workflow: export helper collision meshes in the GLB, then edit the generated `CrashcatPhysics` properties if that body needs a different motion type or collider shape.
|
|
119
|
+
|
|
113
120
|
For agent-authored custom meshes, use `BufferGeometry` with flat numeric arrays:
|
|
114
121
|
|
|
115
122
|
```json
|
|
@@ -198,6 +198,49 @@ function bodyTransformChanged(body, lastPosition, lastQuaternion) {
|
|
|
198
198
|
function getRegisteredBody(api, nodeId, body) {
|
|
199
199
|
return api && body && api.getBody(nodeId) === body ? body : null;
|
|
200
200
|
}
|
|
201
|
+
function createAndRegisterBody(api, nodeId, object, physics) {
|
|
202
|
+
const shape = createShapeForObject(object, physics);
|
|
203
|
+
if (!shape)
|
|
204
|
+
return null;
|
|
205
|
+
object.updateWorldMatrix(true, true);
|
|
206
|
+
object.getWorldPosition(scratchPosition);
|
|
207
|
+
const wq = new Quaternion();
|
|
208
|
+
object.getWorldQuaternion(wq);
|
|
209
|
+
const motionType = toMotionType(physics);
|
|
210
|
+
const motionQuality = toMotionQuality(physics);
|
|
211
|
+
const isKinematic = motionType === MotionType.KINEMATIC;
|
|
212
|
+
const isStatic = motionType === MotionType.STATIC;
|
|
213
|
+
const body = rigidBody.create(api.world, {
|
|
214
|
+
shape,
|
|
215
|
+
motionType,
|
|
216
|
+
motionQuality,
|
|
217
|
+
objectLayer: isStatic ? api.staticObjectLayer : api.movingObjectLayer,
|
|
218
|
+
position: [scratchPosition.x, scratchPosition.y, scratchPosition.z],
|
|
219
|
+
quaternion: [wq.x, wq.y, wq.z, wq.w],
|
|
220
|
+
sensor: Boolean(physics.sensor),
|
|
221
|
+
collideKinematicVsNonDynamic: isKinematic,
|
|
222
|
+
friction: physics.friction,
|
|
223
|
+
restitution: physics.restitution,
|
|
224
|
+
userData: { nodeId },
|
|
225
|
+
});
|
|
226
|
+
if (physics.linearVelocity) {
|
|
227
|
+
rigidBody.setLinearVelocity(api.world, body, physics.linearVelocity);
|
|
228
|
+
}
|
|
229
|
+
if (physics.angularVelocity) {
|
|
230
|
+
rigidBody.setAngularVelocity(api.world, body, physics.angularVelocity);
|
|
231
|
+
}
|
|
232
|
+
api.register(nodeId, body, {
|
|
233
|
+
motionType,
|
|
234
|
+
sensor: Boolean(physics.sensor),
|
|
235
|
+
events: {
|
|
236
|
+
collisionEnter: physics.collisionEnterEventName,
|
|
237
|
+
collisionExit: physics.collisionExitEventName,
|
|
238
|
+
sensorEnter: physics.sensorEnterEventName,
|
|
239
|
+
sensorExit: physics.sensorExitEventName,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
return { body, motionType };
|
|
243
|
+
}
|
|
201
244
|
function CrashcatPhysicsView({ properties, children }) {
|
|
202
245
|
const { nodeId, getObject } = useNode();
|
|
203
246
|
const scene = useScene();
|
|
@@ -207,6 +250,7 @@ function CrashcatPhysicsView({ properties, children }) {
|
|
|
207
250
|
const revision = getAssetRevision();
|
|
208
251
|
const bodyRef = useRef(null);
|
|
209
252
|
const motionTypeRef = useRef(MotionType.STATIC);
|
|
253
|
+
const needsRegistrationRef = useRef(false);
|
|
210
254
|
const syncPositionRef = useRef([0, 0, 0]);
|
|
211
255
|
const syncQuaternionRef = useRef([0, 0, 0, 1]);
|
|
212
256
|
const lastPositionRef = useRef(null);
|
|
@@ -241,61 +285,40 @@ function CrashcatPhysicsView({ properties, children }) {
|
|
|
241
285
|
sensorExitEventName,
|
|
242
286
|
type,
|
|
243
287
|
]);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!api)
|
|
288
|
+
const tryRegisterBody = () => {
|
|
289
|
+
if (!api || getRegisteredBody(api, nodeId, bodyRef.current)) {
|
|
290
|
+
needsRegistrationRef.current = false;
|
|
248
291
|
return;
|
|
292
|
+
}
|
|
249
293
|
const object = getObject();
|
|
250
|
-
if (!object)
|
|
294
|
+
if (!object) {
|
|
295
|
+
needsRegistrationRef.current = true;
|
|
251
296
|
return;
|
|
252
|
-
const shape = createShapeForObject(object, physics);
|
|
253
|
-
if (!shape)
|
|
254
|
-
return;
|
|
255
|
-
object.updateWorldMatrix(true, true);
|
|
256
|
-
object.getWorldPosition(scratchPosition);
|
|
257
|
-
const wq = new Quaternion();
|
|
258
|
-
object.getWorldQuaternion(wq);
|
|
259
|
-
const motionType = toMotionType(physics);
|
|
260
|
-
const motionQuality = toMotionQuality(physics);
|
|
261
|
-
const isKinematic = motionType === MotionType.KINEMATIC;
|
|
262
|
-
const isStatic = motionType === MotionType.STATIC;
|
|
263
|
-
const body = rigidBody.create(api.world, {
|
|
264
|
-
shape,
|
|
265
|
-
motionType,
|
|
266
|
-
motionQuality,
|
|
267
|
-
objectLayer: isStatic ? api.staticObjectLayer : api.movingObjectLayer,
|
|
268
|
-
position: [scratchPosition.x, scratchPosition.y, scratchPosition.z],
|
|
269
|
-
quaternion: [wq.x, wq.y, wq.z, wq.w],
|
|
270
|
-
sensor: Boolean(physics.sensor),
|
|
271
|
-
collideKinematicVsNonDynamic: isKinematic,
|
|
272
|
-
friction: physics.friction,
|
|
273
|
-
restitution: physics.restitution,
|
|
274
|
-
userData: { nodeId },
|
|
275
|
-
});
|
|
276
|
-
if (physics.linearVelocity) {
|
|
277
|
-
rigidBody.setLinearVelocity(api.world, body, physics.linearVelocity);
|
|
278
297
|
}
|
|
279
|
-
|
|
280
|
-
|
|
298
|
+
const registration = createAndRegisterBody(api, nodeId, object, physics);
|
|
299
|
+
if (!registration) {
|
|
300
|
+
needsRegistrationRef.current = true;
|
|
301
|
+
return;
|
|
281
302
|
}
|
|
282
|
-
bodyRef.current = body;
|
|
283
|
-
motionTypeRef.current = motionType;
|
|
303
|
+
bodyRef.current = registration.body;
|
|
304
|
+
motionTypeRef.current = registration.motionType;
|
|
305
|
+
needsRegistrationRef.current = false;
|
|
284
306
|
lastPositionRef.current = null;
|
|
285
307
|
lastQuaternionRef.current = null;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
308
|
+
};
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
// Rebuild mesh-derived colliders when referenced assets finish loading.
|
|
311
|
+
void revision;
|
|
312
|
+
needsRegistrationRef.current = true;
|
|
313
|
+
if (api) {
|
|
314
|
+
api.unregister(nodeId);
|
|
315
|
+
}
|
|
316
|
+
bodyRef.current = null;
|
|
317
|
+
tryRegisterBody();
|
|
296
318
|
return () => {
|
|
297
319
|
bodyRef.current = null;
|
|
298
|
-
|
|
320
|
+
needsRegistrationRef.current = false;
|
|
321
|
+
api === null || api === void 0 ? void 0 : api.unregister(nodeId);
|
|
299
322
|
};
|
|
300
323
|
}, [
|
|
301
324
|
api,
|
|
@@ -304,6 +327,11 @@ function CrashcatPhysicsView({ properties, children }) {
|
|
|
304
327
|
physics,
|
|
305
328
|
revision,
|
|
306
329
|
]);
|
|
330
|
+
useFrame(() => {
|
|
331
|
+
if (needsRegistrationRef.current) {
|
|
332
|
+
tryRegisterBody();
|
|
333
|
+
}
|
|
334
|
+
}, -3);
|
|
307
335
|
useEffect(() => {
|
|
308
336
|
const syncEditBody = () => {
|
|
309
337
|
const body = getRegisteredBody(api, nodeId, bodyRef.current);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { Vec3 } from "mathcat";
|
|
2
|
+
import { rigidBody, type World } from "crashcat";
|
|
3
|
+
import type { Component, NodeInteractionHandlers } from "../../tools/prefabeditor/components/ComponentRegistry";
|
|
4
|
+
export declare enum RagdollBodyPart {
|
|
5
|
+
UpperBody = 0,
|
|
6
|
+
Head = 1,
|
|
7
|
+
UpperLeftArm = 2,
|
|
8
|
+
LowerLeftArm = 3,
|
|
9
|
+
UpperRightArm = 4,
|
|
10
|
+
LowerRightArm = 5,
|
|
11
|
+
Pelvis = 6,
|
|
12
|
+
UpperLeftLeg = 7,
|
|
13
|
+
LowerLeftLeg = 8,
|
|
14
|
+
UpperRightLeg = 9,
|
|
15
|
+
LowerRightLeg = 10
|
|
16
|
+
}
|
|
17
|
+
type ShapeConfig = {
|
|
18
|
+
args: Vec3;
|
|
19
|
+
density: number;
|
|
20
|
+
position: Vec3;
|
|
21
|
+
};
|
|
22
|
+
type JointConfig = {
|
|
23
|
+
bodyA: RagdollBodyPart;
|
|
24
|
+
bodyB: RagdollBodyPart;
|
|
25
|
+
pivotA: Vec3;
|
|
26
|
+
pivotB: Vec3;
|
|
27
|
+
axisA: Vec3;
|
|
28
|
+
axisB: Vec3;
|
|
29
|
+
angle: number;
|
|
30
|
+
twistAngle: number;
|
|
31
|
+
};
|
|
32
|
+
type SkeletonJoint = {
|
|
33
|
+
bodyPart: RagdollBodyPart;
|
|
34
|
+
parentBodyPart: RagdollBodyPart | null;
|
|
35
|
+
};
|
|
36
|
+
export type RagdollSettings = {
|
|
37
|
+
shapes: Map<RagdollBodyPart, ShapeConfig>;
|
|
38
|
+
joints: JointConfig[];
|
|
39
|
+
skeleton: SkeletonJoint[];
|
|
40
|
+
};
|
|
41
|
+
export type CrashcatRagdollProps = {
|
|
42
|
+
position?: [number, number, number];
|
|
43
|
+
scale?: number;
|
|
44
|
+
swingAngle?: number;
|
|
45
|
+
shoulderAngle?: number;
|
|
46
|
+
twistAngle?: number;
|
|
47
|
+
stabilize?: boolean;
|
|
48
|
+
initialLinearVelocity?: [number, number, number];
|
|
49
|
+
initialAngularVelocity?: [number, number, number];
|
|
50
|
+
color?: string;
|
|
51
|
+
clickImpulse?: number;
|
|
52
|
+
nodeInteractionHandlers?: NodeInteractionHandlers;
|
|
53
|
+
};
|
|
54
|
+
export declare function createRagdollSettings(scale?: number, angleA?: number, angleB?: number, twistAngle?: number): RagdollSettings;
|
|
55
|
+
export declare function CrashcatRagdoll({ position, scale, swingAngle, shoulderAngle, twistAngle, stabilize, initialLinearVelocity, initialAngularVelocity, color, clickImpulse, nodeInteractionHandlers, }: CrashcatRagdollProps): import("react/jsx-runtime").JSX.Element;
|
|
56
|
+
declare const CrashcatRagdollComponent: Component;
|
|
57
|
+
export default CrashcatRagdollComponent;
|
|
58
|
+
export declare function createStaticBoxBody(world: World, objectLayer: number, halfExtents: Vec3, position: Vec3): rigidBody.RigidBody;
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { createPortal, useFrame, useThree } from "@react-three/fiber";
|
|
4
|
+
import { mat3, mat4, quat, vec3 } from "mathcat";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
6
|
+
import { box, ConstraintSpace, massProperties, motionProperties, MotionType, rigidBody, swingTwistConstraint, } from "crashcat";
|
|
7
|
+
import { Quaternion, Vector3 } from "three";
|
|
8
|
+
import { BooleanField, FieldRenderer, StringField, Vector3Field, } from "../../tools/prefabeditor/components/Input";
|
|
9
|
+
import { useCrashcat } from "./CrashcatRuntime";
|
|
10
|
+
export var RagdollBodyPart;
|
|
11
|
+
(function (RagdollBodyPart) {
|
|
12
|
+
RagdollBodyPart[RagdollBodyPart["UpperBody"] = 0] = "UpperBody";
|
|
13
|
+
RagdollBodyPart[RagdollBodyPart["Head"] = 1] = "Head";
|
|
14
|
+
RagdollBodyPart[RagdollBodyPart["UpperLeftArm"] = 2] = "UpperLeftArm";
|
|
15
|
+
RagdollBodyPart[RagdollBodyPart["LowerLeftArm"] = 3] = "LowerLeftArm";
|
|
16
|
+
RagdollBodyPart[RagdollBodyPart["UpperRightArm"] = 4] = "UpperRightArm";
|
|
17
|
+
RagdollBodyPart[RagdollBodyPart["LowerRightArm"] = 5] = "LowerRightArm";
|
|
18
|
+
RagdollBodyPart[RagdollBodyPart["Pelvis"] = 6] = "Pelvis";
|
|
19
|
+
RagdollBodyPart[RagdollBodyPart["UpperLeftLeg"] = 7] = "UpperLeftLeg";
|
|
20
|
+
RagdollBodyPart[RagdollBodyPart["LowerLeftLeg"] = 8] = "LowerLeftLeg";
|
|
21
|
+
RagdollBodyPart[RagdollBodyPart["UpperRightLeg"] = 9] = "UpperRightLeg";
|
|
22
|
+
RagdollBodyPart[RagdollBodyPart["LowerRightLeg"] = 10] = "LowerRightLeg";
|
|
23
|
+
})(RagdollBodyPart || (RagdollBodyPart = {}));
|
|
24
|
+
let nextRagdollId = 0;
|
|
25
|
+
const DEFAULT_POSITION = [0, 0, 0];
|
|
26
|
+
const ZERO_VECTOR = [0, 0, 0];
|
|
27
|
+
export function createRagdollSettings(scale = 1, angleA = Math.PI / 4, angleB = Math.PI / 4, twistAngle = 0) {
|
|
28
|
+
const shouldersDistance = 0.45 * scale;
|
|
29
|
+
const upperArmLength = 0.4 * scale;
|
|
30
|
+
const lowerArmLength = 0.4 * scale;
|
|
31
|
+
const upperArmSize = 0.15 * scale;
|
|
32
|
+
const lowerArmSize = 0.15 * scale;
|
|
33
|
+
const neckLength = 0.1 * scale;
|
|
34
|
+
const headRadius = 0.2 * scale;
|
|
35
|
+
const upperBodyLength = 0.6 * scale;
|
|
36
|
+
const pelvisLength = 0.2 * scale;
|
|
37
|
+
const pelvisSize = 0.25 * scale;
|
|
38
|
+
const upperLegLength = 0.5 * scale;
|
|
39
|
+
const upperLegSize = 0.15 * scale;
|
|
40
|
+
const lowerLegSize = 0.15 * scale;
|
|
41
|
+
const lowerLegLength = 0.5 * scale;
|
|
42
|
+
const lowerLeftLegPos = [-shouldersDistance / 3, lowerLegLength / 2, 0];
|
|
43
|
+
const lowerRightLegPos = [shouldersDistance / 3, lowerLegLength / 2, 0];
|
|
44
|
+
const upperLeftLegPos = [-shouldersDistance / 3, lowerLeftLegPos[1] + lowerLegLength / 2 + upperLegLength / 2, 0];
|
|
45
|
+
const upperRightLegPos = [shouldersDistance / 3, lowerRightLegPos[1] + lowerLegLength / 2 + upperLegLength / 2, 0];
|
|
46
|
+
const pelvisPos = [0, upperLeftLegPos[1] + upperLegLength / 2 + pelvisLength / 2, 0];
|
|
47
|
+
const upperBodyPos = [0, pelvisPos[1] + pelvisLength / 2 + upperBodyLength / 2, 0];
|
|
48
|
+
const headPos = [0, upperBodyPos[1] + upperBodyLength / 2 + headRadius / 2 + neckLength, 0];
|
|
49
|
+
const upperLeftArmPos = [-shouldersDistance / 2 - upperArmLength / 2, upperBodyPos[1] + upperBodyLength / 2, 0];
|
|
50
|
+
const upperRightArmPos = [shouldersDistance / 2 + upperArmLength / 2, upperBodyPos[1] + upperBodyLength / 2, 0];
|
|
51
|
+
const lowerLeftArmPos = [upperLeftArmPos[0] - lowerArmLength / 2 - upperArmLength / 2, upperLeftArmPos[1], 0];
|
|
52
|
+
const lowerRightArmPos = [upperRightArmPos[0] + lowerArmLength / 2 + upperArmLength / 2, upperRightArmPos[1], 0];
|
|
53
|
+
const shapes = new Map([
|
|
54
|
+
[RagdollBodyPart.LowerLeftLeg, { args: [lowerLegSize * 0.5, lowerLegLength * 0.5, lowerLegSize * 0.5], density: scale, position: lowerLeftLegPos }],
|
|
55
|
+
[RagdollBodyPart.LowerRightLeg, { args: [lowerLegSize * 0.5, lowerLegLength * 0.5, lowerLegSize * 0.5], density: scale, position: lowerRightLegPos }],
|
|
56
|
+
[RagdollBodyPart.UpperLeftLeg, { args: [upperLegSize * 0.5, upperLegLength * 0.5, upperLegSize * 0.5], density: scale, position: upperLeftLegPos }],
|
|
57
|
+
[RagdollBodyPart.UpperRightLeg, { args: [upperLegSize * 0.5, upperLegLength * 0.5, upperLegSize * 0.5], density: scale, position: upperRightLegPos }],
|
|
58
|
+
[RagdollBodyPart.Pelvis, { args: [shouldersDistance * 0.5, pelvisLength * 0.5, pelvisSize * 0.5], density: scale, position: pelvisPos }],
|
|
59
|
+
[RagdollBodyPart.UpperBody, { args: [shouldersDistance * 0.5, upperBodyLength * 0.5, lowerArmSize * 0.75], density: scale, position: upperBodyPos }],
|
|
60
|
+
[RagdollBodyPart.Head, { args: [headRadius * 0.6, headRadius * 0.7, headRadius * 0.6], density: scale, position: headPos }],
|
|
61
|
+
[RagdollBodyPart.UpperLeftArm, { args: [upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5], density: scale, position: upperLeftArmPos }],
|
|
62
|
+
[RagdollBodyPart.UpperRightArm, { args: [upperArmLength * 0.5, upperArmSize * 0.5, upperArmSize * 0.5], density: scale, position: upperRightArmPos }],
|
|
63
|
+
[RagdollBodyPart.LowerLeftArm, { args: [lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5], density: scale, position: lowerLeftArmPos }],
|
|
64
|
+
[RagdollBodyPart.LowerRightArm, { args: [lowerArmLength * 0.5, lowerArmSize * 0.5, lowerArmSize * 0.5], density: scale, position: lowerRightArmPos }],
|
|
65
|
+
]);
|
|
66
|
+
const joints = [
|
|
67
|
+
{ bodyA: RagdollBodyPart.Head, bodyB: RagdollBodyPart.UpperBody, pivotA: [0, -headRadius - neckLength / 2, 0], pivotB: [0, upperBodyLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
68
|
+
{ bodyA: RagdollBodyPart.LowerLeftLeg, bodyB: RagdollBodyPart.UpperLeftLeg, pivotA: [0, lowerLegLength / 2, 0], pivotB: [0, -upperLegLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
69
|
+
{ bodyA: RagdollBodyPart.LowerRightLeg, bodyB: RagdollBodyPart.UpperRightLeg, pivotA: [0, lowerLegLength / 2, 0], pivotB: [0, -upperLegLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
70
|
+
{ bodyA: RagdollBodyPart.UpperLeftLeg, bodyB: RagdollBodyPart.Pelvis, pivotA: [0, upperLegLength / 2, 0], pivotB: [-shouldersDistance / 3, -pelvisLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
71
|
+
{ bodyA: RagdollBodyPart.UpperRightLeg, bodyB: RagdollBodyPart.Pelvis, pivotA: [0, upperLegLength / 2, 0], pivotB: [shouldersDistance / 3, -pelvisLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
72
|
+
{ bodyA: RagdollBodyPart.Pelvis, bodyB: RagdollBodyPart.UpperBody, pivotA: [0, pelvisLength / 2, 0], pivotB: [0, -upperBodyLength / 2, 0], axisA: [0, 1, 0], axisB: [0, 1, 0], angle: angleA, twistAngle },
|
|
73
|
+
{ bodyA: RagdollBodyPart.UpperBody, bodyB: RagdollBodyPart.UpperLeftArm, pivotA: [-shouldersDistance / 2, upperBodyLength / 2, 0], pivotB: [upperArmLength / 2, 0, 0], axisA: [1, 0, 0], axisB: [1, 0, 0], angle: angleB, twistAngle },
|
|
74
|
+
{ bodyA: RagdollBodyPart.UpperBody, bodyB: RagdollBodyPart.UpperRightArm, pivotA: [shouldersDistance / 2, upperBodyLength / 2, 0], pivotB: [-upperArmLength / 2, 0, 0], axisA: [1, 0, 0], axisB: [1, 0, 0], angle: angleB, twistAngle },
|
|
75
|
+
{ bodyA: RagdollBodyPart.LowerLeftArm, bodyB: RagdollBodyPart.UpperLeftArm, pivotA: [lowerArmLength / 2, 0, 0], pivotB: [-upperArmLength / 2, 0, 0], axisA: [1, 0, 0], axisB: [1, 0, 0], angle: angleA, twistAngle },
|
|
76
|
+
{ bodyA: RagdollBodyPart.LowerRightArm, bodyB: RagdollBodyPart.UpperRightArm, pivotA: [-lowerArmLength / 2, 0, 0], pivotB: [upperArmLength / 2, 0, 0], axisA: [1, 0, 0], axisB: [1, 0, 0], angle: angleA, twistAngle },
|
|
77
|
+
];
|
|
78
|
+
const skeleton = [
|
|
79
|
+
{ bodyPart: RagdollBodyPart.Pelvis, parentBodyPart: null },
|
|
80
|
+
{ bodyPart: RagdollBodyPart.UpperBody, parentBodyPart: RagdollBodyPart.Pelvis },
|
|
81
|
+
{ bodyPart: RagdollBodyPart.Head, parentBodyPart: RagdollBodyPart.UpperBody },
|
|
82
|
+
{ bodyPart: RagdollBodyPart.UpperLeftArm, parentBodyPart: RagdollBodyPart.UpperBody },
|
|
83
|
+
{ bodyPart: RagdollBodyPart.LowerLeftArm, parentBodyPart: RagdollBodyPart.UpperLeftArm },
|
|
84
|
+
{ bodyPart: RagdollBodyPart.UpperRightArm, parentBodyPart: RagdollBodyPart.UpperBody },
|
|
85
|
+
{ bodyPart: RagdollBodyPart.LowerRightArm, parentBodyPart: RagdollBodyPart.UpperRightArm },
|
|
86
|
+
{ bodyPart: RagdollBodyPart.UpperLeftLeg, parentBodyPart: RagdollBodyPart.Pelvis },
|
|
87
|
+
{ bodyPart: RagdollBodyPart.LowerLeftLeg, parentBodyPart: RagdollBodyPart.UpperLeftLeg },
|
|
88
|
+
{ bodyPart: RagdollBodyPart.UpperRightLeg, parentBodyPart: RagdollBodyPart.Pelvis },
|
|
89
|
+
{ bodyPart: RagdollBodyPart.LowerRightLeg, parentBodyPart: RagdollBodyPart.UpperRightLeg },
|
|
90
|
+
];
|
|
91
|
+
return { shapes, joints, skeleton };
|
|
92
|
+
}
|
|
93
|
+
function getTangent(out, axis) {
|
|
94
|
+
const ax = Math.abs(axis[0]);
|
|
95
|
+
const ay = Math.abs(axis[1]);
|
|
96
|
+
const az = Math.abs(axis[2]);
|
|
97
|
+
if (ax <= ay && ax <= az) {
|
|
98
|
+
vec3.set(out, 0, -axis[2], axis[1]);
|
|
99
|
+
}
|
|
100
|
+
else if (ay <= az) {
|
|
101
|
+
vec3.set(out, axis[2], 0, -axis[0]);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
vec3.set(out, -axis[1], axis[0], 0);
|
|
105
|
+
}
|
|
106
|
+
vec3.normalize(out, out);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function createRagdollBodies(api, instanceId, settings, offset, stabilize) {
|
|
110
|
+
const bodies = new Map();
|
|
111
|
+
const constraints = [];
|
|
112
|
+
const nodeIds = [];
|
|
113
|
+
for (const [part, config] of settings.shapes) {
|
|
114
|
+
const body = rigidBody.create(api.world, {
|
|
115
|
+
shape: box.create({
|
|
116
|
+
halfExtents: vec3.fromValues(config.args[0], config.args[1], config.args[2]),
|
|
117
|
+
convexRadius: Math.min(0.05, Math.min(config.args[0], config.args[1], config.args[2]) * 0.45),
|
|
118
|
+
density: config.density,
|
|
119
|
+
}),
|
|
120
|
+
objectLayer: api.movingObjectLayer,
|
|
121
|
+
motionType: MotionType.DYNAMIC,
|
|
122
|
+
position: vec3.fromValues(config.position[0] + offset[0], config.position[1] + offset[1], config.position[2] + offset[2]),
|
|
123
|
+
quaternion: quat.create(),
|
|
124
|
+
linearDamping: 0.05,
|
|
125
|
+
angularDamping: 0.05,
|
|
126
|
+
restitution: 0,
|
|
127
|
+
userData: { nodeId: `${instanceId}-${part}` },
|
|
128
|
+
});
|
|
129
|
+
const nodeId = `${instanceId}-${part}`;
|
|
130
|
+
api.register(nodeId, body, { motionType: MotionType.DYNAMIC, sensor: false });
|
|
131
|
+
bodies.set(part, body);
|
|
132
|
+
nodeIds.push(nodeId);
|
|
133
|
+
}
|
|
134
|
+
if (stabilize) {
|
|
135
|
+
stabilizeRagdoll(bodies, settings.skeleton);
|
|
136
|
+
}
|
|
137
|
+
for (const joint of settings.joints) {
|
|
138
|
+
const bodyA = bodies.get(joint.bodyA);
|
|
139
|
+
const bodyB = bodies.get(joint.bodyB);
|
|
140
|
+
if (!bodyA || !bodyB)
|
|
141
|
+
continue;
|
|
142
|
+
constraints.push(swingTwistConstraint.create(api.world, {
|
|
143
|
+
bodyIdA: bodyA.id,
|
|
144
|
+
bodyIdB: bodyB.id,
|
|
145
|
+
position1: vec3.fromValues(joint.pivotA[0], joint.pivotA[1], joint.pivotA[2]),
|
|
146
|
+
position2: vec3.fromValues(joint.pivotB[0], joint.pivotB[1], joint.pivotB[2]),
|
|
147
|
+
twistAxis1: vec3.fromValues(joint.axisA[0], joint.axisA[1], joint.axisA[2]),
|
|
148
|
+
planeAxis1: getTangent(vec3.create(), joint.axisA),
|
|
149
|
+
twistAxis2: vec3.fromValues(joint.axisB[0], joint.axisB[1], joint.axisB[2]),
|
|
150
|
+
planeAxis2: getTangent(vec3.create(), joint.axisB),
|
|
151
|
+
space: ConstraintSpace.LOCAL,
|
|
152
|
+
normalHalfConeAngle: joint.angle,
|
|
153
|
+
planeHalfConeAngle: joint.angle,
|
|
154
|
+
twistMinAngle: -joint.twistAngle,
|
|
155
|
+
twistMaxAngle: joint.twistAngle,
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
158
|
+
return { bodies, constraints, nodeIds };
|
|
159
|
+
}
|
|
160
|
+
function stabilizeRagdoll(bodies, skeleton) {
|
|
161
|
+
var _a, _b, _c, _d, _e;
|
|
162
|
+
const minMassRatio = 0.8;
|
|
163
|
+
const maxMassRatio = 1.2;
|
|
164
|
+
const maxInertiaIncrease = 2;
|
|
165
|
+
const visited = new Set();
|
|
166
|
+
const massRatios = new Map();
|
|
167
|
+
const roots = skeleton.filter((joint) => joint.parentBodyPart === null);
|
|
168
|
+
for (const root of roots) {
|
|
169
|
+
const chain = [];
|
|
170
|
+
const toProcess = [root.bodyPart];
|
|
171
|
+
while (toProcess.length > 0) {
|
|
172
|
+
const current = toProcess.shift();
|
|
173
|
+
if (current === undefined || visited.has(current))
|
|
174
|
+
continue;
|
|
175
|
+
visited.add(current);
|
|
176
|
+
chain.push(current);
|
|
177
|
+
for (const joint of skeleton) {
|
|
178
|
+
if (joint.parentBodyPart === current && !visited.has(joint.bodyPart)) {
|
|
179
|
+
toProcess.push(joint.bodyPart);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (chain.length <= 1)
|
|
184
|
+
continue;
|
|
185
|
+
let totalMassRatio = 1;
|
|
186
|
+
massRatios.set(chain[0], 1);
|
|
187
|
+
for (let i = 1; i < chain.length; i += 1) {
|
|
188
|
+
const childPart = chain[i];
|
|
189
|
+
const parentPart = (_a = skeleton.find((joint) => joint.bodyPart === childPart)) === null || _a === void 0 ? void 0 : _a.parentBodyPart;
|
|
190
|
+
if (parentPart === undefined || parentPart === null)
|
|
191
|
+
continue;
|
|
192
|
+
const childBody = bodies.get(childPart);
|
|
193
|
+
const parentBody = bodies.get(parentPart);
|
|
194
|
+
if (!childBody || !parentBody)
|
|
195
|
+
continue;
|
|
196
|
+
const ratio = childBody.massProperties.mass / parentBody.massProperties.mass;
|
|
197
|
+
const clampedRatio = Math.max(minMassRatio, Math.min(maxMassRatio, ratio));
|
|
198
|
+
const parentRatio = (_b = massRatios.get(parentPart)) !== null && _b !== void 0 ? _b : 1;
|
|
199
|
+
const childRatio = parentRatio * clampedRatio;
|
|
200
|
+
massRatios.set(childPart, childRatio);
|
|
201
|
+
totalMassRatio += childRatio;
|
|
202
|
+
}
|
|
203
|
+
let totalMass = 0;
|
|
204
|
+
for (const part of chain) {
|
|
205
|
+
totalMass += (_d = (_c = bodies.get(part)) === null || _c === void 0 ? void 0 : _c.massProperties.mass) !== null && _d !== void 0 ? _d : 0;
|
|
206
|
+
}
|
|
207
|
+
const ratioToMass = totalMass / totalMassRatio;
|
|
208
|
+
for (const part of chain) {
|
|
209
|
+
const body = bodies.get(part);
|
|
210
|
+
const ratio = massRatios.get(part);
|
|
211
|
+
if (!body || ratio === undefined)
|
|
212
|
+
continue;
|
|
213
|
+
const oldMass = body.massProperties.mass;
|
|
214
|
+
const newMass = ratio * ratioToMass;
|
|
215
|
+
body.massProperties.mass = newMass;
|
|
216
|
+
const massScale = oldMass > 0 ? newMass / oldMass : 1;
|
|
217
|
+
for (let i = 0; i < 15; i += 1) {
|
|
218
|
+
body.massProperties.inertia[i] *= massScale;
|
|
219
|
+
}
|
|
220
|
+
body.massProperties.inertia[15] = 1;
|
|
221
|
+
}
|
|
222
|
+
const principals = new Map();
|
|
223
|
+
for (const part of chain) {
|
|
224
|
+
const body = bodies.get(part);
|
|
225
|
+
if (!body)
|
|
226
|
+
continue;
|
|
227
|
+
const rotation = mat3.create();
|
|
228
|
+
const diagonal = vec3.create();
|
|
229
|
+
if (motionProperties.decomposePrincipalMomentsOfInertia(body.massProperties.inertia, rotation, diagonal)) {
|
|
230
|
+
principals.set(part, { rotation, diagonal, childSum: 0 });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (let i = chain.length - 1; i > 0; i -= 1) {
|
|
234
|
+
const childPart = chain[i];
|
|
235
|
+
const parentPart = (_e = skeleton.find((joint) => joint.bodyPart === childPart)) === null || _e === void 0 ? void 0 : _e.parentBodyPart;
|
|
236
|
+
if (parentPart === undefined || parentPart === null)
|
|
237
|
+
continue;
|
|
238
|
+
const childPrincipal = principals.get(childPart);
|
|
239
|
+
const parentPrincipal = principals.get(parentPart);
|
|
240
|
+
if (childPrincipal && parentPrincipal) {
|
|
241
|
+
parentPrincipal.childSum += childPrincipal.diagonal[0] + childPrincipal.childSum;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const part of chain) {
|
|
245
|
+
const principal = principals.get(part);
|
|
246
|
+
const body = bodies.get(part);
|
|
247
|
+
if (!principal || !body || principal.childSum === 0)
|
|
248
|
+
continue;
|
|
249
|
+
const minimum = Math.min(maxInertiaIncrease * principal.diagonal[0], principal.childSum);
|
|
250
|
+
principal.diagonal[0] = Math.max(principal.diagonal[0], minimum);
|
|
251
|
+
principal.diagonal[1] = Math.max(principal.diagonal[1], minimum);
|
|
252
|
+
principal.diagonal[2] = Math.max(principal.diagonal[2], minimum);
|
|
253
|
+
const scale = mat4.create();
|
|
254
|
+
mat4.fromScaling(scale, principal.diagonal);
|
|
255
|
+
const rot4x4 = mat4.create();
|
|
256
|
+
mat4.identity(rot4x4);
|
|
257
|
+
for (let i = 0; i < 3; i += 1) {
|
|
258
|
+
for (let j = 0; j < 3; j += 1) {
|
|
259
|
+
rot4x4[i + j * 4] = principal.rotation[i + j * 3];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const temp1 = mat4.create();
|
|
263
|
+
const temp2 = mat4.create();
|
|
264
|
+
mat4.multiply(temp1, rot4x4, scale);
|
|
265
|
+
mat4.transpose(temp2, rot4x4);
|
|
266
|
+
mat4.multiply(body.massProperties.inertia, temp1, temp2);
|
|
267
|
+
body.massProperties.inertia[15] = 1;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const body of bodies.values()) {
|
|
271
|
+
if (body.motionType !== MotionType.DYNAMIC)
|
|
272
|
+
continue;
|
|
273
|
+
const mp = massProperties.create();
|
|
274
|
+
massProperties.copy(mp, body.massProperties);
|
|
275
|
+
body.massPropertiesOverride = rigidBody.MassPropertiesOverride.MASS_AND_INERTIA_PROVIDED;
|
|
276
|
+
body.motionProperties.invMass = mp.mass > 0 ? 1 / mp.mass : 0;
|
|
277
|
+
const rotation = mat3.create();
|
|
278
|
+
const diagonal = vec3.create();
|
|
279
|
+
if (motionProperties.decomposePrincipalMomentsOfInertia(mp.inertia, rotation, diagonal)) {
|
|
280
|
+
vec3.set(body.motionProperties.invInertiaDiagonal, diagonal[0] !== 0 ? 1 / diagonal[0] : 0, diagonal[1] !== 0 ? 1 / diagonal[1] : 0, diagonal[2] !== 0 ? 1 / diagonal[2] : 0);
|
|
281
|
+
quat.fromMat3(body.motionProperties.inertiaRotation, rotation);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const meshPosition = new Vector3();
|
|
286
|
+
const meshQuaternion = new Quaternion();
|
|
287
|
+
const ragdollFields = [
|
|
288
|
+
{ name: "scale", type: "number", label: "Scale", step: 0.1 },
|
|
289
|
+
{ name: "swingAngle", type: "number", label: "Swing Angle", step: 0.05 },
|
|
290
|
+
{ name: "shoulderAngle", type: "number", label: "Shoulder Angle", step: 0.05 },
|
|
291
|
+
{ name: "twistAngle", type: "number", label: "Twist Angle", step: 0.05 },
|
|
292
|
+
{ name: "clickImpulse", type: "number", label: "Click Impulse", min: 0, step: 0.5 },
|
|
293
|
+
];
|
|
294
|
+
export function CrashcatRagdoll({ position = DEFAULT_POSITION, scale = 1.8, swingAngle = Math.PI / 4, shoulderAngle = Math.PI / 4, twistAngle = 0, stabilize = true, initialLinearVelocity = ZERO_VECTOR, initialAngularVelocity = ZERO_VECTOR, color = "#f97316", clickImpulse = 8, nodeInteractionHandlers, }) {
|
|
295
|
+
const api = useCrashcat();
|
|
296
|
+
const instanceId = useRef(`crashcat-ragdoll-${nextRagdollId++}`);
|
|
297
|
+
const stateRef = useRef(null);
|
|
298
|
+
const meshRefs = useRef(new Map());
|
|
299
|
+
const settings = useMemo(() => createRagdollSettings(scale, swingAngle, shoulderAngle, twistAngle), [scale, shoulderAngle, swingAngle, twistAngle]);
|
|
300
|
+
const shapeEntries = useMemo(() => [...settings.shapes], [settings]);
|
|
301
|
+
const [px, py, pz] = position;
|
|
302
|
+
const [lvx, lvy, lvz] = initialLinearVelocity;
|
|
303
|
+
const [avx, avy, avz] = initialAngularVelocity;
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (!api)
|
|
306
|
+
return undefined;
|
|
307
|
+
const state = createRagdollBodies(api, instanceId.current, settings, [px, py, pz], stabilize);
|
|
308
|
+
stateRef.current = state;
|
|
309
|
+
for (const body of state.bodies.values()) {
|
|
310
|
+
rigidBody.addLinearVelocity(api.world, body, [lvx, lvy, lvz]);
|
|
311
|
+
rigidBody.addAngularVelocity(api.world, body, [avx, avy, avz]);
|
|
312
|
+
}
|
|
313
|
+
return () => {
|
|
314
|
+
for (const constraint of state.constraints) {
|
|
315
|
+
swingTwistConstraint.remove(api.world, constraint);
|
|
316
|
+
}
|
|
317
|
+
for (const nodeId of state.nodeIds) {
|
|
318
|
+
api.unregister(nodeId);
|
|
319
|
+
}
|
|
320
|
+
stateRef.current = null;
|
|
321
|
+
};
|
|
322
|
+
}, [api, avx, avy, avz, lvx, lvy, lvz, px, py, pz, settings, stabilize]);
|
|
323
|
+
useFrame(() => {
|
|
324
|
+
const state = stateRef.current;
|
|
325
|
+
if (!state)
|
|
326
|
+
return;
|
|
327
|
+
for (const [part, body] of state.bodies) {
|
|
328
|
+
const mesh = meshRefs.current.get(part);
|
|
329
|
+
if (!mesh)
|
|
330
|
+
continue;
|
|
331
|
+
meshPosition.set(body.position[0], body.position[1], body.position[2]);
|
|
332
|
+
meshQuaternion.set(body.quaternion[0], body.quaternion[1], body.quaternion[2], body.quaternion[3]);
|
|
333
|
+
mesh.position.copy(meshPosition);
|
|
334
|
+
mesh.quaternion.copy(meshQuaternion);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
const handleClick = useCallback((event) => {
|
|
338
|
+
var _a;
|
|
339
|
+
(_a = nodeInteractionHandlers === null || nodeInteractionHandlers === void 0 ? void 0 : nodeInteractionHandlers.onClick) === null || _a === void 0 ? void 0 : _a.call(nodeInteractionHandlers, event);
|
|
340
|
+
const apiWorld = api === null || api === void 0 ? void 0 : api.world;
|
|
341
|
+
const state = stateRef.current;
|
|
342
|
+
if (!apiWorld || !state || clickImpulse <= 0)
|
|
343
|
+
return;
|
|
344
|
+
let hitBody = null;
|
|
345
|
+
let minDistanceSq = Infinity;
|
|
346
|
+
for (const body of state.bodies.values()) {
|
|
347
|
+
const dx = body.position[0] - event.point.x;
|
|
348
|
+
const dy = body.position[1] - event.point.y;
|
|
349
|
+
const dz = body.position[2] - event.point.z;
|
|
350
|
+
const distanceSq = dx * dx + dy * dy + dz * dz;
|
|
351
|
+
if (distanceSq < minDistanceSq) {
|
|
352
|
+
minDistanceSq = distanceSq;
|
|
353
|
+
hitBody = body;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!hitBody)
|
|
357
|
+
return;
|
|
358
|
+
rigidBody.addImpulseAtPosition(apiWorld, hitBody, [
|
|
359
|
+
event.ray.direction.x * clickImpulse,
|
|
360
|
+
event.ray.direction.y * clickImpulse + clickImpulse * 0.35,
|
|
361
|
+
event.ray.direction.z * clickImpulse,
|
|
362
|
+
], [event.point.x, event.point.y, event.point.z]);
|
|
363
|
+
}, [api, clickImpulse, nodeInteractionHandlers]);
|
|
364
|
+
const interactionHandlers = Object.assign(Object.assign({}, nodeInteractionHandlers), { onClick: handleClick });
|
|
365
|
+
return (_jsx("group", Object.assign({}, interactionHandlers, { children: shapeEntries.map(([part, config]) => (_jsxs("mesh", { ref: (mesh) => {
|
|
366
|
+
if (mesh)
|
|
367
|
+
meshRefs.current.set(part, mesh);
|
|
368
|
+
else
|
|
369
|
+
meshRefs.current.delete(part);
|
|
370
|
+
}, castShadow: true, receiveShadow: true, position: [
|
|
371
|
+
config.position[0] + px,
|
|
372
|
+
config.position[1] + py,
|
|
373
|
+
config.position[2] + pz,
|
|
374
|
+
], children: [_jsx("boxGeometry", { args: [config.args[0] * 2, config.args[1] * 2, config.args[2] * 2] }), _jsx("meshStandardMaterial", { color: color, roughness: 0.72, metalness: 0.05 })] }, part))) })));
|
|
375
|
+
}
|
|
376
|
+
function CrashcatRagdollEditor({ component, onUpdate, }) {
|
|
377
|
+
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [_jsx(FieldRenderer, { fields: ragdollFields, values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "stabilize", label: "Stabilize", values: component.properties, onChange: onUpdate, fallback: true }), _jsx(StringField, { name: "color", label: "Color", values: component.properties, onChange: onUpdate, fallback: "#f97316" }), _jsx(Vector3Field, { name: "initialLinearVelocity", label: "Initial Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "initialAngularVelocity", label: "Initial Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] })] }));
|
|
378
|
+
}
|
|
379
|
+
function CrashcatRagdollView({ properties, children, editMode, nodeInteractionHandlers, worldPosition, }) {
|
|
380
|
+
var _a, _b, _c, _d, _e, _f;
|
|
381
|
+
const scene = useThree((state) => state.scene);
|
|
382
|
+
return (_jsxs(_Fragment, { children: [children, worldPosition
|
|
383
|
+
? createPortal(_jsx(CrashcatRagdoll, { position: worldPosition, scale: (_a = properties.scale) !== null && _a !== void 0 ? _a : 1.8, swingAngle: (_b = properties.swingAngle) !== null && _b !== void 0 ? _b : Math.PI / 4, shoulderAngle: (_c = properties.shoulderAngle) !== null && _c !== void 0 ? _c : Math.PI / 4, twistAngle: (_d = properties.twistAngle) !== null && _d !== void 0 ? _d : 0, stabilize: properties.stabilize !== false, color: (_e = properties.color) !== null && _e !== void 0 ? _e : "#f97316", clickImpulse: editMode ? 0 : (_f = properties.clickImpulse) !== null && _f !== void 0 ? _f : 8, initialLinearVelocity: properties.initialLinearVelocity, initialAngularVelocity: properties.initialAngularVelocity, nodeInteractionHandlers: nodeInteractionHandlers }), scene)
|
|
384
|
+
: null] }));
|
|
385
|
+
}
|
|
386
|
+
const CrashcatRagdollComponent = {
|
|
387
|
+
name: "CrashcatRagdoll",
|
|
388
|
+
Editor: CrashcatRagdollEditor,
|
|
389
|
+
View: CrashcatRagdollView,
|
|
390
|
+
defaultProperties: {
|
|
391
|
+
scale: 1.8,
|
|
392
|
+
swingAngle: Math.PI / 4,
|
|
393
|
+
shoulderAngle: Math.PI / 4,
|
|
394
|
+
twistAngle: 0,
|
|
395
|
+
stabilize: true,
|
|
396
|
+
color: "#f97316",
|
|
397
|
+
clickImpulse: 8,
|
|
398
|
+
initialLinearVelocity: [0, 0, 0],
|
|
399
|
+
initialAngularVelocity: [0, 0, 0],
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
export default CrashcatRagdollComponent;
|
|
403
|
+
export function createStaticBoxBody(world, objectLayer, halfExtents, position) {
|
|
404
|
+
return rigidBody.create(world, {
|
|
405
|
+
shape: box.create({ halfExtents }),
|
|
406
|
+
objectLayer,
|
|
407
|
+
motionType: MotionType.STATIC,
|
|
408
|
+
position,
|
|
409
|
+
});
|
|
410
|
+
}
|