react-three-game 0.0.107 → 0.0.109
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 +12 -5
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +15 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +75 -47
- package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
- package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +19 -15
- 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/EditorContext.d.ts +36 -0
- package/dist/tools/prefabeditor/EditorContext.js +17 -0
- package/dist/tools/prefabeditor/EditorTree.js +6 -3
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +18 -6
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +5 -37
- package/dist/tools/prefabeditor/PrefabEditor.js +41 -43
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -27
- package/dist/tools/prefabeditor/PrefabRoot.js +133 -78
- package/dist/tools/prefabeditor/SceneContext.d.ts +28 -0
- package/dist/tools/prefabeditor/SceneContext.js +14 -0
- package/dist/tools/prefabeditor/assetRuntime.d.ts +4 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +16 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
- 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/TransformComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.d.ts +1 -0
- package/dist/tools/prefabeditor/components/index.js +10 -0
- package/dist/tools/prefabeditor/components/runtime.d.ts +4 -0
- package/dist/tools/prefabeditor/components/runtime.js +372 -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/prefabStore.d.ts +1 -1
- package/dist/tools/prefabeditor/prefabStore.js +5 -1
- package/dist/tools/prefabeditor/runtimeUtils.d.ts +10 -0
- package/dist/tools/prefabeditor/runtimeUtils.js +30 -0
- package/dist/tools/prefabeditor/utils.d.ts +7 -8
- package/dist/tools/prefabeditor/utils.js +63 -36
- package/dist/viewer.d.ts +22 -0
- package/dist/viewer.js +14 -0
- package/package.json +22 -20
- package/dist/index.d.ts +0 -40
- package/dist/index.js +0 -32
|
@@ -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
|
+
}
|
|
@@ -5,7 +5,7 @@ import { addBroadphaseLayer, addObjectLayer, createWorld, createWorldSettings, e
|
|
|
5
5
|
import { debugRenderer } from "crashcat/three";
|
|
6
6
|
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
7
|
import { gameEvents } from "../../tools/prefabeditor/GameEvents";
|
|
8
|
-
import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/
|
|
8
|
+
import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/SceneContext";
|
|
9
9
|
const SLEEP_TIME_BEFORE_REST = 0.1;
|
|
10
10
|
const SLEEP_POINT_VELOCITY_THRESHOLD = 0.06;
|
|
11
11
|
const MAX_PHYSICS_STEP_DELTA = 1 / 60;
|
|
@@ -33,11 +33,14 @@ function setCrashcatApi(api) {
|
|
|
33
33
|
listener();
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
36
|
+
function emitPhysicsEvent(eventType, eventName, sourceNodeId, targetNodeId, collisionNormal) {
|
|
37
|
+
const alias = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
|
|
38
|
+
if (!gameEvents.hasListeners(eventType) && (!alias || !gameEvents.hasListeners(alias)))
|
|
39
39
|
return;
|
|
40
|
-
|
|
40
|
+
const payload = Object.assign({ sourceEntityId: sourceNodeId, sourceNodeId, targetEntityId: targetNodeId, targetNodeId }, (collisionNormal ? { collisionNormal } : {}));
|
|
41
|
+
gameEvents.emit(eventType, payload);
|
|
42
|
+
if (alias)
|
|
43
|
+
gameEvents.emit(alias, payload);
|
|
41
44
|
}
|
|
42
45
|
function createDebugState() {
|
|
43
46
|
const options = debugRenderer.createDefaultOptions();
|
|
@@ -71,26 +74,27 @@ export function CrashcatRuntime({ debug = false, children }) {
|
|
|
71
74
|
const [debugState] = useState(() => debug ? createDebugState() : null);
|
|
72
75
|
const listener = useMemo(() => ({
|
|
73
76
|
onContactAdded: (bodyA, bodyB, manifold) => {
|
|
74
|
-
var _a, _b;
|
|
77
|
+
var _a, _b, _c, _d, _e, _f;
|
|
75
78
|
const metaA = getBodyMeta(bodyByIdRef.current, bodyA);
|
|
76
79
|
const metaB = getBodyMeta(bodyByIdRef.current, bodyB);
|
|
77
80
|
const n = manifold === null || manifold === void 0 ? void 0 : manifold.worldSpaceNormal;
|
|
78
81
|
const nA = n ? [n[0], n[1], n[2]] : undefined;
|
|
79
82
|
const nB = n ? [-n[0], -n[1], -n[2]] : undefined;
|
|
80
|
-
if (metaA
|
|
81
|
-
|
|
82
|
-
if (metaB
|
|
83
|
-
|
|
83
|
+
if (metaA)
|
|
84
|
+
emitPhysicsEvent(metaA.sensor ? "sensor:enter" : "collision:enter", metaA.sensor ? (_a = metaA.events) === null || _a === void 0 ? void 0 : _a.sensorEnter : (_b = metaA.events) === null || _b === void 0 ? void 0 : _b.collisionEnter, metaA.nodeId, (_c = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _c !== void 0 ? _c : null, nA);
|
|
85
|
+
if (metaB)
|
|
86
|
+
emitPhysicsEvent(metaB.sensor ? "sensor:enter" : "collision:enter", metaB.sensor ? (_d = metaB.events) === null || _d === void 0 ? void 0 : _d.sensorEnter : (_e = metaB.events) === null || _e === void 0 ? void 0 : _e.collisionEnter, metaB.nodeId, (_f = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _f !== void 0 ? _f : null, nB);
|
|
84
87
|
},
|
|
85
88
|
onContactRemoved: (idA, idB) => {
|
|
86
|
-
var _a, _b;
|
|
89
|
+
var _a, _b, _c, _d, _e, _f;
|
|
87
90
|
const metaA = bodyByIdRef.current.get(Number(idA));
|
|
88
91
|
const metaB = bodyByIdRef.current.get(Number(idB));
|
|
89
|
-
if (metaA
|
|
90
|
-
|
|
91
|
-
if (metaB
|
|
92
|
-
|
|
92
|
+
if (metaA)
|
|
93
|
+
emitPhysicsEvent(metaA.sensor ? "sensor:exit" : "collision:exit", metaA.sensor ? (_a = metaA.events) === null || _a === void 0 ? void 0 : _a.sensorExit : (_b = metaA.events) === null || _b === void 0 ? void 0 : _b.collisionExit, metaA.nodeId, (_c = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _c !== void 0 ? _c : null);
|
|
94
|
+
if (metaB)
|
|
95
|
+
emitPhysicsEvent(metaB.sensor ? "sensor:exit" : "collision:exit", metaB.sensor ? (_d = metaB.events) === null || _d === void 0 ? void 0 : _d.sensorExit : (_e = metaB.events) === null || _e === void 0 ? void 0 : _e.collisionExit, metaB.nodeId, (_f = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _f !== void 0 ? _f : null);
|
|
93
96
|
},
|
|
97
|
+
onBodyPairValidate: (bodyA, bodyB) => !rigidBody.bodiesShareConstraint(bodyA, bodyB),
|
|
94
98
|
}), []);
|
|
95
99
|
useEffect(() => {
|
|
96
100
|
ensureCrashcatRegistered();
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CrashcatRuntime, useCrashcat, type BodyMeta, type CrashcatApi, type CrashcatEventConfig, } from "./CrashcatRuntime";
|
|
2
2
|
export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
|
|
3
|
+
export { CrashcatRagdoll, default as CrashcatRagdollComponent, RagdollBodyPart, createRagdollSettings, createStaticBoxBody, type CrashcatRagdollProps, type RagdollSettings, } from "./CrashcatRagdoll";
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { CrashcatRuntime, useCrashcat, } from "./CrashcatRuntime";
|
|
2
2
|
export { default as CrashcatPhysicsComponent, default, } from "./CrashcatPhysicsComponent";
|
|
3
|
+
export { CrashcatRagdoll, default as CrashcatRagdollComponent, RagdollBodyPart, createRagdollSettings, createStaticBoxBody, } from "./CrashcatRagdoll";
|
|
@@ -5,7 +5,7 @@ import { Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
|
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { Mesh, TextureLoader } from "three";
|
|
7
7
|
import { loadModel } from "../dragdrop/modelLoader";
|
|
8
|
-
import {
|
|
8
|
+
import { withBasePath } from "../prefabeditor/utils";
|
|
9
9
|
import { base, colors, fonts } from "../prefabeditor/styles";
|
|
10
10
|
const styles = {
|
|
11
11
|
errorIcon: { color: colors.danger, fontSize: 12 },
|
|
@@ -126,7 +126,7 @@ function TextureCard({ file, onSelect, basePath = "" }) {
|
|
|
126
126
|
const [isHovered, setIsHovered] = useState(false);
|
|
127
127
|
const [error, setError] = useState(false);
|
|
128
128
|
const { ref, isInView } = useInView();
|
|
129
|
-
const fullPath =
|
|
129
|
+
const fullPath = withBasePath(basePath, file);
|
|
130
130
|
const fileName = file.split('/').pop();
|
|
131
131
|
if (error) {
|
|
132
132
|
return (_jsxs("div", { ref: ref, style: Object.assign(Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), { backgroundColor: assetViewerColors.errorBg }), onClick: () => onSelect(file), title: `Could not load ${file}`, children: [_jsx("div", { style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }, children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }), _jsx("div", { style: styles.bottomLabel, children: fileName })] }));
|
|
@@ -170,7 +170,7 @@ export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
170
170
|
function ModelCard({ file, onSelect, basePath = "", size = 60, }) {
|
|
171
171
|
const [error, setError] = useState(false);
|
|
172
172
|
const { ref, isInView } = useInView();
|
|
173
|
-
const fullPath =
|
|
173
|
+
const fullPath = withBasePath(basePath, file);
|
|
174
174
|
if (error) {
|
|
175
175
|
return (_jsx("div", { ref: ref, style: { aspectRatio: '1 / 1', backgroundColor: assetViewerColors.errorBg, cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', border: `1px solid ${colors.dangerBorder}` }, onClick: () => onSelect(file), children: _jsx("div", { style: styles.errorIcon, children: "\u2717" }) }));
|
|
176
176
|
}
|
|
@@ -215,7 +215,7 @@ export function SoundListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
|
215
215
|
}
|
|
216
216
|
function SoundCard({ file, onSelect, basePath = "" }) {
|
|
217
217
|
const fileName = file.split('/').pop() || '';
|
|
218
|
-
const fullPath =
|
|
218
|
+
const fullPath = withBasePath(basePath, file);
|
|
219
219
|
return (_jsxs("div", { onClick: () => onSelect(file), style: Object.assign(Object.assign({ aspectRatio: '1 / 1' }, assetTileStyle), { alignItems: 'center', justifyContent: 'center' }), children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { color: colors.text, fontSize: fonts.size, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
220
220
|
}
|
|
221
221
|
const PICKER_POPUP_WIDTH = 260;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PrefabEditorMode, Scene } from "./SceneContext";
|
|
2
|
+
import type { Prefab } from "./types";
|
|
3
|
+
import type { ExportGLBOptions } from "./utils";
|
|
4
|
+
export interface PrefabEditorRef extends Scene {
|
|
5
|
+
save: () => Prefab;
|
|
6
|
+
load: (prefab: Prefab, options?: {
|
|
7
|
+
resetHistory?: boolean;
|
|
8
|
+
notifyChange?: boolean;
|
|
9
|
+
}) => void;
|
|
10
|
+
undo: () => void;
|
|
11
|
+
redo: () => void;
|
|
12
|
+
screenshot: () => void;
|
|
13
|
+
exportGLB: (options?: ExportGLBOptions) => Promise<ArrayBuffer | undefined>;
|
|
14
|
+
exportGLBData: () => Promise<ArrayBuffer | undefined>;
|
|
15
|
+
clearSelection: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface EditorContextType {
|
|
18
|
+
mode: PrefabEditorMode;
|
|
19
|
+
basePath: string;
|
|
20
|
+
setMode: (mode: PrefabEditorMode) => void;
|
|
21
|
+
transformMode: "translate" | "rotate" | "scale";
|
|
22
|
+
setTransformMode: (mode: "translate" | "rotate" | "scale") => void;
|
|
23
|
+
scaleSnap: number;
|
|
24
|
+
setScaleSnap: (resolution: number) => void;
|
|
25
|
+
positionSnap: number;
|
|
26
|
+
setPositionSnap: (resolution: number) => void;
|
|
27
|
+
rotationSnap: number;
|
|
28
|
+
setRotationSnap: (resolution: number) => void;
|
|
29
|
+
onFocusNode?: (nodeId: string) => void;
|
|
30
|
+
onScreenshot?: () => void;
|
|
31
|
+
onExportGLB?: () => void;
|
|
32
|
+
}
|
|
33
|
+
export declare const EditorContext: import("react").Context<EditorContextType | null>;
|
|
34
|
+
export declare const EditorRefContext: import("react").Context<PrefabEditorRef | null>;
|
|
35
|
+
export declare function useEditorContext(): EditorContextType;
|
|
36
|
+
export declare function useEditorRef(): PrefabEditorRef;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
export const EditorContext = createContext(null);
|
|
3
|
+
export const EditorRefContext = createContext(null);
|
|
4
|
+
export function useEditorContext() {
|
|
5
|
+
const context = useContext(EditorContext);
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error("useEditorContext must be used within EditorContext.Provider");
|
|
8
|
+
}
|
|
9
|
+
return context;
|
|
10
|
+
}
|
|
11
|
+
export function useEditorRef() {
|
|
12
|
+
const editorRef = useContext(EditorRefContext);
|
|
13
|
+
if (!editorRef) {
|
|
14
|
+
throw new Error("useEditorRef must be used within PrefabEditor");
|
|
15
|
+
}
|
|
16
|
+
return editorRef;
|
|
17
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useCallback, useState } from 'react';
|
|
3
3
|
import { base, colors, tree } from './styles';
|
|
4
|
-
import { useEditorContext, useEditorRef } from './
|
|
4
|
+
import { useEditorContext, useEditorRef } from './EditorContext';
|
|
5
5
|
import { Dropdown } from './Dropdown';
|
|
6
6
|
import { FileMenu, TreeContextMenu, TreeNodeMenu } from './EditorTreeMenus';
|
|
7
|
-
import { createEmptyNode } from './prefab';
|
|
7
|
+
import { createEmptyNode, createPackedPrefabNode } from './prefab';
|
|
8
8
|
import { usePrefabChildIds, usePrefabNode, usePrefabRootId, usePrefabStore, usePrefabStoreApi } from './prefabStore';
|
|
9
9
|
export default function EditorTree({ selectedId, setSelectedId, getPrefab, onReplacePrefab, onImportPrefab, onUndo, onRedo, canUndo, canRedo }) {
|
|
10
10
|
const { onFocusNode } = useEditorContext();
|
|
@@ -30,6 +30,9 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
|
|
|
30
30
|
editor.add(newNode, parentId);
|
|
31
31
|
setSelectedId(newNode.id);
|
|
32
32
|
};
|
|
33
|
+
const handleImportPackedPrefab = (url) => {
|
|
34
|
+
editor.add(createPackedPrefabNode(url), rootId);
|
|
35
|
+
};
|
|
33
36
|
const handleDuplicate = (nodeId) => {
|
|
34
37
|
if (nodeId === rootId)
|
|
35
38
|
return;
|
|
@@ -103,7 +106,7 @@ export default function EditorTree({ selectedId, setSelectedId, getPrefab, onRep
|
|
|
103
106
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: Object.assign(Object.assign({}, tree.panel), { width: collapsed ? 'auto' : 224 }), children: [_jsxs("div", { style: base.header, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }, onClick: () => setCollapsed(!collapsed), children: [_jsx("span", { children: collapsed ? '▶' : '▼' }), _jsx("span", { children: "Prefab" })] }), !collapsed && (_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canUndo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onUndo === null || onUndo === void 0 ? void 0 : onUndo(); }, disabled: !canUndo, title: "Undo", children: "\u21B6" }), _jsx("button", { style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10, opacity: canRedo ? 1 : 0.4 }), onClick: (e) => { e.stopPropagation(); onRedo === null || onRedo === void 0 ? void 0 : onRedo(); }, disabled: !canRedo, title: "Redo", children: "\u21B7" }), _jsx(Dropdown, { placement: "bottom-end", trigger: ({ ref, toggle }) => (_jsx("button", { ref: ref, title: "Menu", style: Object.assign(Object.assign({}, base.btn), { padding: '2px 6px', fontSize: 10 }), onClick: (e) => {
|
|
104
107
|
e.stopPropagation();
|
|
105
108
|
toggle();
|
|
106
|
-
}, children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onClose: close })) })] }))] }), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: '4px 4px', borderBottom: `1px solid ${colors.borderLight}` }, children: _jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onClick: (e) => e.stopPropagation(), style: Object.assign(Object.assign({}, base.input), { padding: '4px 8px' }) }) }), _jsx("div", { style: tree.scroll, children: _jsx(TreeNode, { nodeId: rootId, depth: 0, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedId, onToggleCollapse: toggleCollapse, onOpenContextMenu: openContextMenu, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: handleToggleDisabled, setSelectedId: setSelectedId }) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === rootId, close) })] }));
|
|
109
|
+
}, children: "\u22EE" })), children: (close) => (_jsx(FileMenu, { getPrefab: getPrefab, onReplacePrefab: onReplacePrefab, onImportPrefab: onImportPrefab, onImportPackedPrefab: handleImportPackedPrefab, onClose: close })) })] }))] }), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { style: { padding: '4px 4px', borderBottom: `1px solid ${colors.borderLight}` }, children: _jsx("input", { type: "text", placeholder: "Search nodes...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onClick: (e) => e.stopPropagation(), style: Object.assign(Object.assign({}, base.input), { padding: '4px 8px' }) }) }), _jsx("div", { style: tree.scroll, children: _jsx(TreeNode, { nodeId: rootId, depth: 0, rootId: rootId, visibleIds: visibleIds, collapsedIds: collapsedIds, dropTarget: dropTarget, selectedNodeId: selectedId, onToggleCollapse: toggleCollapse, onOpenContextMenu: openContextMenu, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onDragEnd: () => { setDraggedId(null); setDropTarget(null); }, renderTreeNodeMenu: renderTreeNodeMenu, onToggleDisabled: handleToggleDisabled, setSelectedId: setSelectedId }) })] }))] }), _jsx(TreeContextMenu, { contextMenu: contextMenu, onClose: closeContextMenu, children: (nodeId, close) => renderTreeNodeMenu(nodeId, nodeId === rootId, close) })] }));
|
|
107
110
|
}
|
|
108
111
|
const TreeNode = memo(function TreeNode({ nodeId, depth, rootId, visibleIds, collapsedIds, dropTarget, selectedNodeId, onToggleCollapse, onOpenContextMenu, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd, renderTreeNodeMenu, onToggleDisabled, setSelectedId, }) {
|
|
109
112
|
var _a;
|
|
@@ -20,9 +20,10 @@ export declare function TreeContextMenu({ contextMenu, onClose, children, }: {
|
|
|
20
20
|
onClose: () => void;
|
|
21
21
|
children: (nodeId: string, onClose: () => void) => React.ReactNode;
|
|
22
22
|
}): import("react").ReactPortal | null;
|
|
23
|
-
export declare function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onClose }: {
|
|
23
|
+
export declare function FileMenu({ getPrefab, onReplacePrefab, onImportPrefab, onImportPackedPrefab, onClose }: {
|
|
24
24
|
getPrefab: () => Prefab;
|
|
25
25
|
onReplacePrefab: (prefab: Prefab) => void;
|
|
26
26
|
onImportPrefab: (prefab: Prefab) => void;
|
|
27
|
+
onImportPackedPrefab: (url: string) => void;
|
|
27
28
|
onClose: () => void;
|
|
28
29
|
}): import("react/jsx-runtime").JSX.Element;
|