react-three-game 0.0.101 → 0.0.102
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 +2 -1
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.d.ts +3 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +352 -0
- package/dist/plugins/crashcat/CrashcatRuntime.d.ts +27 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +154 -0
- package/dist/plugins/crashcat/index.d.ts +2 -0
- package/dist/plugins/crashcat/index.js +2 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/tools/assetviewer/page.js +41 -4
- package/dist/tools/prefabeditor/components/MaterialComponent.js +73 -82
- package/package.json +27 -2
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ Built on top of [three.js](https://github.com/mrdoob/three.js), [@react-three/fi
|
|
|
16
16
|
|
|
17
17
|
* Website: https://prnth.com/react-three-game
|
|
18
18
|
* Editor: https://prnth.com/react-three-game/editor
|
|
19
|
+
* Starter template: https://github.com/prnthh/react-three-game-starter
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
|
@@ -294,4 +295,4 @@ npm run release
|
|
|
294
295
|
|
|
295
296
|
## License
|
|
296
297
|
|
|
297
|
-
VPL
|
|
298
|
+
PFYL / VPL
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
5
|
+
import { BooleanField, FieldRenderer, StringField, Vector3Field, } from "../../tools/prefabeditor/components/Input";
|
|
6
|
+
import { useAssetRuntime, useNode } from "../../tools/prefabeditor/assetRuntime";
|
|
7
|
+
import { usePrefabStoreApi } from "../../tools/prefabeditor/prefabStore";
|
|
8
|
+
import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
|
|
9
|
+
import { box, capsule, convexHull, MotionQuality, MotionType, rigidBody, sphere, triangleMesh, } from "crashcat";
|
|
10
|
+
import { Matrix4, Quaternion, Vector3 } from "three";
|
|
11
|
+
import { useCrashcat } from "./CrashcatRuntime";
|
|
12
|
+
const MAX_PHYSICS_DELTA = 1 / 30;
|
|
13
|
+
const crashcatPhysicsFields = [
|
|
14
|
+
{
|
|
15
|
+
name: "type",
|
|
16
|
+
type: "select",
|
|
17
|
+
label: "Motion Type",
|
|
18
|
+
options: [
|
|
19
|
+
{ value: "fixed", label: "Fixed" },
|
|
20
|
+
{ value: "dynamic", label: "Dynamic" },
|
|
21
|
+
{ value: "kinematicPosition", label: "Kinematic Position" },
|
|
22
|
+
{ value: "kinematicVelocity", label: "Kinematic Velocity" },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "colliders",
|
|
27
|
+
type: "select",
|
|
28
|
+
label: "Collider",
|
|
29
|
+
options: [
|
|
30
|
+
{ value: "cuboid", label: "Cuboid" },
|
|
31
|
+
{ value: "ball", label: "Ball" },
|
|
32
|
+
{ value: "capsule", label: "Capsule" },
|
|
33
|
+
{ value: "hull", label: "Hull" },
|
|
34
|
+
{ value: "trimesh", label: "Tri Mesh" },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
{ name: "friction", type: "number", label: "Friction", step: 0.05 },
|
|
38
|
+
{ name: "restitution", type: "number", label: "Restitution", step: 0.05 },
|
|
39
|
+
{ name: "capsuleRadius", type: "number", label: "Capsule Radius", step: 0.05 },
|
|
40
|
+
{ name: "capsuleHalfHeight", type: "number", label: "Capsule Half Height", step: 0.05 },
|
|
41
|
+
];
|
|
42
|
+
function CrashcatPhysicsEditor({ component, onUpdate }) {
|
|
43
|
+
return (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [_jsx(FieldRenderer, { fields: crashcatPhysicsFields, values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(StringField, { name: "collisionEnterEventName", label: "Collision Enter", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "collisionExitEventName", label: "Collision Exit", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "sensorEnterEventName", label: "Sensor Enter", values: component.properties, onChange: onUpdate, fallback: "" }), _jsx(StringField, { name: "sensorExitEventName", label: "Sensor Exit", values: component.properties, onChange: onUpdate, fallback: "" })] }));
|
|
44
|
+
}
|
|
45
|
+
const inverseWorldMatrix = new Matrix4();
|
|
46
|
+
const childToLocalMatrix = new Matrix4();
|
|
47
|
+
const scratchVertex = new Vector3();
|
|
48
|
+
const scratchScale = new Vector3();
|
|
49
|
+
const scratchPosition = new Vector3();
|
|
50
|
+
const scratchBoundsSize = new Vector3();
|
|
51
|
+
const worldQuaternion = new Quaternion();
|
|
52
|
+
const parentWorldQuaternion = new Quaternion();
|
|
53
|
+
const localQuaternion = new Quaternion();
|
|
54
|
+
function collectGeometryData(object) {
|
|
55
|
+
const positions = [];
|
|
56
|
+
const indices = [];
|
|
57
|
+
let vertexOffset = 0;
|
|
58
|
+
inverseWorldMatrix.copy(object.matrixWorld).invert();
|
|
59
|
+
object.traverse((child) => {
|
|
60
|
+
var _a;
|
|
61
|
+
const geometry = child.geometry;
|
|
62
|
+
const positionAttribute = (_a = geometry === null || geometry === void 0 ? void 0 : geometry.attributes) === null || _a === void 0 ? void 0 : _a.position;
|
|
63
|
+
if (!positionAttribute)
|
|
64
|
+
return;
|
|
65
|
+
childToLocalMatrix.multiplyMatrices(inverseWorldMatrix, child.matrixWorld);
|
|
66
|
+
for (let i = 0; i < positionAttribute.count; i += 1) {
|
|
67
|
+
scratchVertex
|
|
68
|
+
.set(positionAttribute.getX(i), positionAttribute.getY(i), positionAttribute.getZ(i))
|
|
69
|
+
.applyMatrix4(childToLocalMatrix);
|
|
70
|
+
positions.push(scratchVertex.x, scratchVertex.y, scratchVertex.z);
|
|
71
|
+
}
|
|
72
|
+
if (geometry.index) {
|
|
73
|
+
for (let i = 0; i < geometry.index.count; i += 1) {
|
|
74
|
+
indices.push(vertexOffset + geometry.index.getX(i));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
for (let i = 0; i < positionAttribute.count; i += 1) {
|
|
79
|
+
indices.push(vertexOffset + i);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
vertexOffset += positionAttribute.count;
|
|
83
|
+
});
|
|
84
|
+
if (positions.length === 0 || indices.length < 3)
|
|
85
|
+
return null;
|
|
86
|
+
return { positions, indices };
|
|
87
|
+
}
|
|
88
|
+
function createShapeForObject(object, physics) {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
object.updateWorldMatrix(true, true);
|
|
91
|
+
if (physics.colliders === "trimesh") {
|
|
92
|
+
const geometry = collectGeometryData(object);
|
|
93
|
+
return geometry ? triangleMesh.create(geometry) : null;
|
|
94
|
+
}
|
|
95
|
+
if (physics.colliders === "hull") {
|
|
96
|
+
const geometry = collectGeometryData(object);
|
|
97
|
+
return geometry ? convexHull.create({ positions: geometry.positions }) : null;
|
|
98
|
+
}
|
|
99
|
+
if (physics.colliders === "capsule") {
|
|
100
|
+
return capsule.create({
|
|
101
|
+
radius: Math.max((_a = physics.capsuleRadius) !== null && _a !== void 0 ? _a : 0.35, 0.01),
|
|
102
|
+
halfHeightOfCylinder: Math.max((_b = physics.capsuleHalfHeight) !== null && _b !== void 0 ? _b : 0.45, 0.01),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
object.getWorldScale(scratchScale);
|
|
106
|
+
const geometry = collectGeometryData(object);
|
|
107
|
+
if (!geometry)
|
|
108
|
+
return null;
|
|
109
|
+
if (physics.colliders === "ball") {
|
|
110
|
+
let maxRadiusSq = 0;
|
|
111
|
+
for (let i = 0; i < geometry.positions.length; i += 3) {
|
|
112
|
+
const x = geometry.positions[i] * scratchScale.x;
|
|
113
|
+
const y = geometry.positions[i + 1] * scratchScale.y;
|
|
114
|
+
const z = geometry.positions[i + 2] * scratchScale.z;
|
|
115
|
+
maxRadiusSq = Math.max(maxRadiusSq, x * x + y * y + z * z);
|
|
116
|
+
}
|
|
117
|
+
return sphere.create({ radius: Math.max(Math.sqrt(maxRadiusSq), 0.01) });
|
|
118
|
+
}
|
|
119
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
120
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
121
|
+
for (let i = 0; i < geometry.positions.length; i += 3) {
|
|
122
|
+
const x = geometry.positions[i] * scratchScale.x;
|
|
123
|
+
const y = geometry.positions[i + 1] * scratchScale.y;
|
|
124
|
+
const z = geometry.positions[i + 2] * scratchScale.z;
|
|
125
|
+
if (x < minX)
|
|
126
|
+
minX = x;
|
|
127
|
+
if (x > maxX)
|
|
128
|
+
maxX = x;
|
|
129
|
+
if (y < minY)
|
|
130
|
+
minY = y;
|
|
131
|
+
if (y > maxY)
|
|
132
|
+
maxY = y;
|
|
133
|
+
if (z < minZ)
|
|
134
|
+
minZ = z;
|
|
135
|
+
if (z > maxZ)
|
|
136
|
+
maxZ = z;
|
|
137
|
+
}
|
|
138
|
+
scratchBoundsSize.set(maxX - minX, maxY - minY, maxZ - minZ);
|
|
139
|
+
return box.create({
|
|
140
|
+
halfExtents: [
|
|
141
|
+
Math.max(scratchBoundsSize.x * 0.5, 0.01),
|
|
142
|
+
Math.max(scratchBoundsSize.y * 0.5, 0.01),
|
|
143
|
+
Math.max(scratchBoundsSize.z * 0.5, 0.01),
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function toMotionType(physics) {
|
|
148
|
+
if (physics.type === "dynamic")
|
|
149
|
+
return MotionType.DYNAMIC;
|
|
150
|
+
if (physics.type === "kinematicPosition" || physics.type === "kinematicVelocity")
|
|
151
|
+
return MotionType.KINEMATIC;
|
|
152
|
+
return MotionType.STATIC;
|
|
153
|
+
}
|
|
154
|
+
function toMotionQuality(physics) {
|
|
155
|
+
return physics.type === "kinematicPosition" ? MotionQuality.LINEAR_CAST : undefined;
|
|
156
|
+
}
|
|
157
|
+
function setObjectWorldTransform(object, position, quaternion) {
|
|
158
|
+
if (!object.parent) {
|
|
159
|
+
object.position.set(position[0], position[1], position[2]);
|
|
160
|
+
object.quaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
|
|
161
|
+
object.updateMatrixWorld(true);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
scratchPosition.set(position[0], position[1], position[2]);
|
|
165
|
+
object.parent.worldToLocal(scratchPosition);
|
|
166
|
+
object.position.copy(scratchPosition);
|
|
167
|
+
object.parent.getWorldQuaternion(parentWorldQuaternion);
|
|
168
|
+
worldQuaternion.set(quaternion[0], quaternion[1], quaternion[2], quaternion[3]);
|
|
169
|
+
localQuaternion.copy(parentWorldQuaternion).invert().multiply(worldQuaternion);
|
|
170
|
+
object.quaternion.copy(localQuaternion);
|
|
171
|
+
object.updateMatrixWorld(true);
|
|
172
|
+
}
|
|
173
|
+
function syncObjectToBody(world, body, object, position, quaternion, delta) {
|
|
174
|
+
object.getWorldPosition(scratchPosition);
|
|
175
|
+
object.getWorldQuaternion(worldQuaternion);
|
|
176
|
+
position.splice(0, 3, scratchPosition.x, scratchPosition.y, scratchPosition.z);
|
|
177
|
+
quaternion.splice(0, 4, worldQuaternion.x, worldQuaternion.y, worldQuaternion.z, worldQuaternion.w);
|
|
178
|
+
if (delta === undefined) {
|
|
179
|
+
rigidBody.setPosition(world, body, position, false);
|
|
180
|
+
rigidBody.setQuaternion(world, body, quaternion, false);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
rigidBody.moveKinematic(body, position, quaternion, delta);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function bodyTransformChanged(body, lastPosition, lastQuaternion) {
|
|
187
|
+
const position = body.position;
|
|
188
|
+
const quaternion = body.quaternion;
|
|
189
|
+
return !lastPosition
|
|
190
|
+
|| position[0] !== lastPosition[0]
|
|
191
|
+
|| position[1] !== lastPosition[1]
|
|
192
|
+
|| position[2] !== lastPosition[2]
|
|
193
|
+
|| quaternion[0] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[0])
|
|
194
|
+
|| quaternion[1] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[1])
|
|
195
|
+
|| quaternion[2] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[2])
|
|
196
|
+
|| quaternion[3] !== (lastQuaternion === null || lastQuaternion === void 0 ? void 0 : lastQuaternion[3]);
|
|
197
|
+
}
|
|
198
|
+
function getRegisteredBody(api, nodeId, body) {
|
|
199
|
+
return api && body && api.getBody(nodeId) === body ? body : null;
|
|
200
|
+
}
|
|
201
|
+
function CrashcatPhysicsView({ properties, children }) {
|
|
202
|
+
const { nodeId, getObject } = useNode();
|
|
203
|
+
const scene = useScene();
|
|
204
|
+
const store = usePrefabStoreApi();
|
|
205
|
+
const api = useCrashcat();
|
|
206
|
+
const { getAssetRevision } = useAssetRuntime();
|
|
207
|
+
const revision = getAssetRevision();
|
|
208
|
+
const bodyRef = useRef(null);
|
|
209
|
+
const motionTypeRef = useRef(MotionType.STATIC);
|
|
210
|
+
const syncPositionRef = useRef([0, 0, 0]);
|
|
211
|
+
const syncQuaternionRef = useRef([0, 0, 0, 1]);
|
|
212
|
+
const lastPositionRef = useRef(null);
|
|
213
|
+
const lastQuaternionRef = useRef(null);
|
|
214
|
+
const { angularVelocity, capsuleHalfHeight, capsuleRadius, colliders, collisionEnterEventName, collisionExitEventName, friction, linearVelocity, restitution, sensor, sensorEnterEventName, sensorExitEventName, type, } = properties;
|
|
215
|
+
const physics = useMemo(() => ({
|
|
216
|
+
angularVelocity,
|
|
217
|
+
capsuleHalfHeight,
|
|
218
|
+
capsuleRadius,
|
|
219
|
+
colliders,
|
|
220
|
+
collisionEnterEventName,
|
|
221
|
+
collisionExitEventName,
|
|
222
|
+
friction,
|
|
223
|
+
linearVelocity,
|
|
224
|
+
restitution,
|
|
225
|
+
sensor,
|
|
226
|
+
sensorEnterEventName,
|
|
227
|
+
sensorExitEventName,
|
|
228
|
+
type,
|
|
229
|
+
}), [
|
|
230
|
+
angularVelocity,
|
|
231
|
+
capsuleHalfHeight,
|
|
232
|
+
capsuleRadius,
|
|
233
|
+
colliders,
|
|
234
|
+
collisionEnterEventName,
|
|
235
|
+
collisionExitEventName,
|
|
236
|
+
friction,
|
|
237
|
+
linearVelocity,
|
|
238
|
+
restitution,
|
|
239
|
+
sensor,
|
|
240
|
+
sensorEnterEventName,
|
|
241
|
+
sensorExitEventName,
|
|
242
|
+
type,
|
|
243
|
+
]);
|
|
244
|
+
useEffect(() => {
|
|
245
|
+
// Rebuild mesh-derived colliders when referenced assets finish loading.
|
|
246
|
+
void revision;
|
|
247
|
+
if (!api)
|
|
248
|
+
return;
|
|
249
|
+
const object = getObject();
|
|
250
|
+
if (!object)
|
|
251
|
+
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
|
+
}
|
|
279
|
+
if (physics.angularVelocity) {
|
|
280
|
+
rigidBody.setAngularVelocity(api.world, body, physics.angularVelocity);
|
|
281
|
+
}
|
|
282
|
+
bodyRef.current = body;
|
|
283
|
+
motionTypeRef.current = motionType;
|
|
284
|
+
lastPositionRef.current = null;
|
|
285
|
+
lastQuaternionRef.current = null;
|
|
286
|
+
api.register(nodeId, body, {
|
|
287
|
+
motionType,
|
|
288
|
+
sensor: Boolean(physics.sensor),
|
|
289
|
+
events: {
|
|
290
|
+
collisionEnter: physics.collisionEnterEventName,
|
|
291
|
+
collisionExit: physics.collisionExitEventName,
|
|
292
|
+
sensorEnter: physics.sensorEnterEventName,
|
|
293
|
+
sensorExit: physics.sensorExitEventName,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
return () => {
|
|
297
|
+
bodyRef.current = null;
|
|
298
|
+
api.unregister(nodeId);
|
|
299
|
+
};
|
|
300
|
+
}, [
|
|
301
|
+
api,
|
|
302
|
+
getObject,
|
|
303
|
+
nodeId,
|
|
304
|
+
physics,
|
|
305
|
+
revision,
|
|
306
|
+
]);
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
const syncEditBody = () => {
|
|
309
|
+
const body = getRegisteredBody(api, nodeId, bodyRef.current);
|
|
310
|
+
const object = getObject();
|
|
311
|
+
if (!api || !body || !object || scene.mode !== PrefabEditorMode.Edit)
|
|
312
|
+
return;
|
|
313
|
+
syncObjectToBody(api.world, body, object, syncPositionRef.current, syncQuaternionRef.current);
|
|
314
|
+
};
|
|
315
|
+
syncEditBody();
|
|
316
|
+
return store.subscribe(syncEditBody);
|
|
317
|
+
}, [api, getObject, nodeId, scene.mode, store]);
|
|
318
|
+
useFrame((_, delta) => {
|
|
319
|
+
const body = getRegisteredBody(api, nodeId, bodyRef.current);
|
|
320
|
+
const object = getObject();
|
|
321
|
+
if (!api || !body || !object || scene.mode !== PrefabEditorMode.Play || motionTypeRef.current !== MotionType.KINEMATIC)
|
|
322
|
+
return;
|
|
323
|
+
syncObjectToBody(api.world, body, object, syncPositionRef.current, syncQuaternionRef.current, Math.min(delta, MAX_PHYSICS_DELTA));
|
|
324
|
+
}, -2);
|
|
325
|
+
useFrame(() => {
|
|
326
|
+
const body = getRegisteredBody(api, nodeId, bodyRef.current);
|
|
327
|
+
const object = getObject();
|
|
328
|
+
if (!api || !body || !object || motionTypeRef.current !== MotionType.DYNAMIC || scene.mode !== PrefabEditorMode.Play)
|
|
329
|
+
return;
|
|
330
|
+
if (bodyTransformChanged(body, lastPositionRef.current, lastQuaternionRef.current)) {
|
|
331
|
+
setObjectWorldTransform(object, body.position, body.quaternion);
|
|
332
|
+
lastPositionRef.current = [body.position[0], body.position[1], body.position[2]];
|
|
333
|
+
lastQuaternionRef.current = [body.quaternion[0], body.quaternion[1], body.quaternion[2], body.quaternion[3]];
|
|
334
|
+
}
|
|
335
|
+
if (body.position[1] < -40) {
|
|
336
|
+
bodyRef.current = null;
|
|
337
|
+
api.unregister(nodeId);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
return _jsx(_Fragment, { children: children });
|
|
341
|
+
}
|
|
342
|
+
const CrashcatPhysicsComponent = {
|
|
343
|
+
name: "CrashcatPhysics",
|
|
344
|
+
Editor: CrashcatPhysicsEditor,
|
|
345
|
+
View: CrashcatPhysicsView,
|
|
346
|
+
defaultProperties: {
|
|
347
|
+
type: "fixed",
|
|
348
|
+
colliders: "cuboid",
|
|
349
|
+
sensor: false,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
export default CrashcatPhysicsComponent;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MotionType, type Filter, type RigidBody, type World } from "crashcat";
|
|
2
|
+
export type CrashcatEventConfig = {
|
|
3
|
+
collisionEnter?: string;
|
|
4
|
+
collisionExit?: string;
|
|
5
|
+
sensorEnter?: string;
|
|
6
|
+
sensorExit?: string;
|
|
7
|
+
};
|
|
8
|
+
export type BodyMeta = {
|
|
9
|
+
nodeId: string;
|
|
10
|
+
motionType: MotionType;
|
|
11
|
+
sensor: boolean;
|
|
12
|
+
events?: CrashcatEventConfig;
|
|
13
|
+
};
|
|
14
|
+
export interface CrashcatApi {
|
|
15
|
+
world: World;
|
|
16
|
+
queryFilter: Filter;
|
|
17
|
+
staticObjectLayer: number;
|
|
18
|
+
movingObjectLayer: number;
|
|
19
|
+
register: (nodeId: string, body: RigidBody, meta: Omit<BodyMeta, "nodeId">) => void;
|
|
20
|
+
unregister: (nodeId: string) => void;
|
|
21
|
+
getBody: (nodeId: string) => RigidBody | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function useCrashcat(): CrashcatApi | null;
|
|
24
|
+
export declare function CrashcatRuntime({ debug, children }: {
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useFrame } from "@react-three/fiber";
|
|
4
|
+
import { addBroadphaseLayer, addObjectLayer, createWorld, createWorldSettings, enableCollision, filter, registerAll, rigidBody, updateWorld, } from "crashcat";
|
|
5
|
+
import { debugRenderer } from "crashcat/three";
|
|
6
|
+
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
7
|
+
import { gameEvents } from "../../tools/prefabeditor/GameEvents";
|
|
8
|
+
import { PrefabEditorMode, useScene } from "../../tools/prefabeditor/PrefabRoot";
|
|
9
|
+
const SLEEP_TIME_BEFORE_REST = 0.1;
|
|
10
|
+
const SLEEP_POINT_VELOCITY_THRESHOLD = 0.06;
|
|
11
|
+
const MAX_PHYSICS_DELTA = 1 / 30;
|
|
12
|
+
let didRegisterCrashcat = false;
|
|
13
|
+
function ensureCrashcatRegistered() {
|
|
14
|
+
if (didRegisterCrashcat)
|
|
15
|
+
return;
|
|
16
|
+
registerAll();
|
|
17
|
+
didRegisterCrashcat = true;
|
|
18
|
+
}
|
|
19
|
+
const crashcatListeners = new Set();
|
|
20
|
+
let crashcatApi = null;
|
|
21
|
+
export function useCrashcat() {
|
|
22
|
+
return useSyncExternalStore((listener) => (crashcatListeners.add(listener), () => crashcatListeners.delete(listener)), () => crashcatApi, () => crashcatApi);
|
|
23
|
+
}
|
|
24
|
+
function setCrashcatApi(api) {
|
|
25
|
+
crashcatApi = api;
|
|
26
|
+
crashcatListeners.forEach((listener) => listener());
|
|
27
|
+
}
|
|
28
|
+
function emitConfiguredEvent(eventName, sourceNodeId, targetNodeId, collisionNormal) {
|
|
29
|
+
const trimmed = eventName === null || eventName === void 0 ? void 0 : eventName.trim();
|
|
30
|
+
if (!trimmed)
|
|
31
|
+
return;
|
|
32
|
+
gameEvents.emit(trimmed, Object.assign({ sourceEntityId: sourceNodeId, sourceNodeId, targetEntityId: targetNodeId, targetNodeId }, (collisionNormal ? { collisionNormal } : {})));
|
|
33
|
+
}
|
|
34
|
+
function createDebugState() {
|
|
35
|
+
const options = debugRenderer.createDefaultOptions();
|
|
36
|
+
options.bodies.wireframe = true;
|
|
37
|
+
options.bodies.color = debugRenderer.BodyColorMode.MOTION_TYPE;
|
|
38
|
+
options.bodies.showAngularVelocity = false;
|
|
39
|
+
options.bodies.showLinearVelocity = false;
|
|
40
|
+
options.contacts.enabled = false;
|
|
41
|
+
options.contactConstraints.enabled = false;
|
|
42
|
+
return debugRenderer.init(options);
|
|
43
|
+
}
|
|
44
|
+
function getBodyMeta(bodyById, body) {
|
|
45
|
+
var _a;
|
|
46
|
+
const registered = bodyById.get(Number(body.id));
|
|
47
|
+
if (registered)
|
|
48
|
+
return registered;
|
|
49
|
+
const nodeId = (_a = body.userData) === null || _a === void 0 ? void 0 : _a.nodeId;
|
|
50
|
+
if (typeof nodeId !== "string")
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
nodeId,
|
|
54
|
+
motionType: body.motionType,
|
|
55
|
+
sensor: body.sensor,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function CrashcatRuntime({ debug = false, children }) {
|
|
59
|
+
const { mode } = useScene();
|
|
60
|
+
const bodiesRef = useRef(new Map());
|
|
61
|
+
const bodyByIdRef = useRef(new Map());
|
|
62
|
+
const apiRef = useRef(null);
|
|
63
|
+
const [debugState] = useState(() => debug ? createDebugState() : null);
|
|
64
|
+
const listener = useMemo(() => ({
|
|
65
|
+
onContactAdded: (bodyA, bodyB, manifold) => {
|
|
66
|
+
var _a, _b;
|
|
67
|
+
const metaA = getBodyMeta(bodyByIdRef.current, bodyA);
|
|
68
|
+
const metaB = getBodyMeta(bodyByIdRef.current, bodyB);
|
|
69
|
+
const n = manifold === null || manifold === void 0 ? void 0 : manifold.worldSpaceNormal;
|
|
70
|
+
const nA = n ? [n[0], n[1], n[2]] : undefined;
|
|
71
|
+
const nB = n ? [-n[0], -n[1], -n[2]] : undefined;
|
|
72
|
+
if (metaA === null || metaA === void 0 ? void 0 : metaA.events)
|
|
73
|
+
emitConfiguredEvent(metaA.sensor ? metaA.events.sensorEnter : metaA.events.collisionEnter, metaA.nodeId, (_a = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _a !== void 0 ? _a : null, nA);
|
|
74
|
+
if (metaB === null || metaB === void 0 ? void 0 : metaB.events)
|
|
75
|
+
emitConfiguredEvent(metaB.sensor ? metaB.events.sensorEnter : metaB.events.collisionEnter, metaB.nodeId, (_b = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _b !== void 0 ? _b : null, nB);
|
|
76
|
+
},
|
|
77
|
+
onContactRemoved: (idA, idB) => {
|
|
78
|
+
var _a, _b;
|
|
79
|
+
const metaA = bodyByIdRef.current.get(Number(idA));
|
|
80
|
+
const metaB = bodyByIdRef.current.get(Number(idB));
|
|
81
|
+
if (metaA === null || metaA === void 0 ? void 0 : metaA.events)
|
|
82
|
+
emitConfiguredEvent(metaA.sensor ? metaA.events.sensorExit : metaA.events.collisionExit, metaA.nodeId, (_a = metaB === null || metaB === void 0 ? void 0 : metaB.nodeId) !== null && _a !== void 0 ? _a : null);
|
|
83
|
+
if (metaB === null || metaB === void 0 ? void 0 : metaB.events)
|
|
84
|
+
emitConfiguredEvent(metaB.sensor ? metaB.events.sensorExit : metaB.events.collisionExit, metaB.nodeId, (_b = metaA === null || metaA === void 0 ? void 0 : metaA.nodeId) !== null && _b !== void 0 ? _b : null);
|
|
85
|
+
},
|
|
86
|
+
}), []);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
ensureCrashcatRegistered();
|
|
89
|
+
const settings = createWorldSettings();
|
|
90
|
+
settings.narrowphase.collideWithBackfaces = true;
|
|
91
|
+
settings.sleeping.timeBeforeSleep = SLEEP_TIME_BEFORE_REST;
|
|
92
|
+
settings.sleeping.pointVelocitySleepThreshold = SLEEP_POINT_VELOCITY_THRESHOLD;
|
|
93
|
+
const movingBroadphase = addBroadphaseLayer(settings);
|
|
94
|
+
const staticBroadphase = addBroadphaseLayer(settings);
|
|
95
|
+
const movingObjectLayer = addObjectLayer(settings, movingBroadphase);
|
|
96
|
+
const staticObjectLayer = addObjectLayer(settings, staticBroadphase);
|
|
97
|
+
enableCollision(settings, movingObjectLayer, staticObjectLayer);
|
|
98
|
+
enableCollision(settings, movingObjectLayer, movingObjectLayer);
|
|
99
|
+
const world = createWorld(settings);
|
|
100
|
+
const queryFilter = filter.forWorld(world);
|
|
101
|
+
const bodies = bodiesRef.current;
|
|
102
|
+
const bodyById = bodyByIdRef.current;
|
|
103
|
+
const unregister = (nodeId) => {
|
|
104
|
+
const entry = bodies.get(nodeId);
|
|
105
|
+
if (!entry)
|
|
106
|
+
return;
|
|
107
|
+
bodyById.delete(Number(entry.body.id));
|
|
108
|
+
rigidBody.remove(world, entry.body);
|
|
109
|
+
bodies.delete(nodeId);
|
|
110
|
+
};
|
|
111
|
+
const runtimeApi = {
|
|
112
|
+
world,
|
|
113
|
+
queryFilter,
|
|
114
|
+
staticObjectLayer,
|
|
115
|
+
movingObjectLayer,
|
|
116
|
+
register: (nodeId, body, meta) => {
|
|
117
|
+
unregister(nodeId);
|
|
118
|
+
const full = Object.assign({ nodeId }, meta);
|
|
119
|
+
bodies.set(nodeId, { body, meta: full });
|
|
120
|
+
bodyById.set(Number(body.id), full);
|
|
121
|
+
},
|
|
122
|
+
unregister,
|
|
123
|
+
getBody: (nodeId) => { var _a, _b; return (_b = (_a = bodies.get(nodeId)) === null || _a === void 0 ? void 0 : _a.body) !== null && _b !== void 0 ? _b : null; },
|
|
124
|
+
};
|
|
125
|
+
apiRef.current = runtimeApi;
|
|
126
|
+
setCrashcatApi(runtimeApi);
|
|
127
|
+
return () => {
|
|
128
|
+
for (const entry of bodies.values()) {
|
|
129
|
+
rigidBody.remove(world, entry.body);
|
|
130
|
+
}
|
|
131
|
+
apiRef.current = null;
|
|
132
|
+
if (crashcatApi === runtimeApi)
|
|
133
|
+
setCrashcatApi(null);
|
|
134
|
+
bodies.clear();
|
|
135
|
+
bodyById.clear();
|
|
136
|
+
if (debugState)
|
|
137
|
+
debugRenderer.dispose(debugState);
|
|
138
|
+
};
|
|
139
|
+
}, [debugState]);
|
|
140
|
+
useFrame((_, delta) => {
|
|
141
|
+
const runtimeApi = apiRef.current;
|
|
142
|
+
if (!runtimeApi)
|
|
143
|
+
return;
|
|
144
|
+
const { world } = runtimeApi;
|
|
145
|
+
const stepDelta = Math.min(delta, MAX_PHYSICS_DELTA);
|
|
146
|
+
if (mode === PrefabEditorMode.Play)
|
|
147
|
+
updateWorld(world, listener, stepDelta);
|
|
148
|
+
if (debugState)
|
|
149
|
+
debugRenderer.update(debugState, world);
|
|
150
|
+
}, -1);
|
|
151
|
+
return (_jsxs(_Fragment, { children: [children, debugState && mode === PrefabEditorMode.Edit
|
|
152
|
+
? _jsx("primitive", { object: debugState.object3d })
|
|
153
|
+
: null] }));
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./crashcat";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./crashcat";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Canvas
|
|
2
|
+
import { Canvas } from "@react-three/fiber";
|
|
3
3
|
import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
|
|
4
4
|
import { Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
@@ -124,12 +124,49 @@ export function TextureListViewer({ files, selected, onSelect, basePath = "" })
|
|
|
124
124
|
}
|
|
125
125
|
function TextureCard({ file, onSelect, basePath = "" }) {
|
|
126
126
|
const [isHovered, setIsHovered] = useState(false);
|
|
127
|
+
const [error, setError] = useState(false);
|
|
127
128
|
const { ref, isInView } = useInView();
|
|
128
129
|
const fullPath = resolvePrefabAssetPath(basePath, file);
|
|
129
|
-
|
|
130
|
+
const fileName = file.split('/').pop();
|
|
131
|
+
if (error) {
|
|
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 })] }));
|
|
133
|
+
}
|
|
134
|
+
return (_jsxs("div", { ref: ref, style: Object.assign({ maxWidth: 60, aspectRatio: '1 / 1' }, assetTileStyle), onClick: () => onSelect(file), onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), children: [_jsx("div", { style: { flex: 1, position: 'relative' }, children: isInView ? (_jsxs(View, { style: { width: '100%', height: '100%' }, children: [_jsx(PerspectiveCamera, { makeDefault: true, position: [0, 0, 2.5], fov: 50 }), _jsx("ambientLight", { intensity: 0.8 }), _jsx("pointLight", { position: [5, 5, 5], intensity: 0.5 }), _jsx(TextureSphere, { url: fullPath, onError: () => setError(true) }), _jsx(OrbitControls, { enableZoom: false, enablePan: false, autoRotate: isHovered, autoRotateSpeed: 2 })] })) : null }), _jsx("div", { style: styles.bottomLabel, children: fileName })] }));
|
|
130
135
|
}
|
|
131
|
-
function TextureSphere({ url }) {
|
|
132
|
-
const texture =
|
|
136
|
+
function TextureSphere({ url, onError }) {
|
|
137
|
+
const [texture, setTexture] = useState(null);
|
|
138
|
+
const textureRef = useRef(null);
|
|
139
|
+
const onErrorRef = useRef(onError);
|
|
140
|
+
onErrorRef.current = onError;
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
var _a;
|
|
143
|
+
let cancelled = false;
|
|
144
|
+
(_a = textureRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
145
|
+
textureRef.current = null;
|
|
146
|
+
setTexture(null);
|
|
147
|
+
const loader = new TextureLoader();
|
|
148
|
+
loader.load(url, loadedTexture => {
|
|
149
|
+
if (cancelled) {
|
|
150
|
+
loadedTexture.dispose();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
textureRef.current = loadedTexture;
|
|
154
|
+
setTexture(loadedTexture);
|
|
155
|
+
}, undefined, () => {
|
|
156
|
+
var _a;
|
|
157
|
+
if (!cancelled) {
|
|
158
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return () => {
|
|
162
|
+
var _a;
|
|
163
|
+
cancelled = true;
|
|
164
|
+
(_a = textureRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
165
|
+
textureRef.current = null;
|
|
166
|
+
};
|
|
167
|
+
}, [url]);
|
|
168
|
+
if (!texture)
|
|
169
|
+
return null;
|
|
133
170
|
return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
|
|
134
171
|
}
|
|
135
172
|
export function ModelListViewer({ files, selected, onSelect, basePath = "" }) {
|
|
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
-
import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
|
13
|
+
import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
14
14
|
import { extend } from '@react-three/fiber';
|
|
15
15
|
import { useFrame } from '@react-three/fiber';
|
|
16
16
|
import { assetRef, assetRefs } from './ComponentRegistry';
|
|
@@ -38,6 +38,54 @@ const MAG_FILTER_MAP = {
|
|
|
38
38
|
NearestFilter,
|
|
39
39
|
LinearFilter,
|
|
40
40
|
};
|
|
41
|
+
function configureTexture(texture, options) {
|
|
42
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
43
|
+
if (!texture)
|
|
44
|
+
return;
|
|
45
|
+
if (options.repeat) {
|
|
46
|
+
texture.wrapS = texture.wrapT = RepeatWrapping;
|
|
47
|
+
texture.repeat.set((_b = (_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 1, (_d = (_c = options.repeatCount) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : 1);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
texture.wrapS = texture.wrapT = ClampToEdgeWrapping;
|
|
51
|
+
texture.repeat.set(1, 1);
|
|
52
|
+
}
|
|
53
|
+
texture.offset.set((_f = (_e = options.offset) === null || _e === void 0 ? void 0 : _e[0]) !== null && _f !== void 0 ? _f : 0, (_h = (_g = options.offset) === null || _g === void 0 ? void 0 : _g[1]) !== null && _h !== void 0 ? _h : 0);
|
|
54
|
+
texture.colorSpace = options.colorSpace;
|
|
55
|
+
texture.generateMipmaps = options.generateMipmaps;
|
|
56
|
+
texture.minFilter = options.minFilter;
|
|
57
|
+
texture.magFilter = options.magFilter;
|
|
58
|
+
texture.needsUpdate = true;
|
|
59
|
+
}
|
|
60
|
+
function cloneConfiguredTexture(texture, options) {
|
|
61
|
+
if (!texture)
|
|
62
|
+
return undefined;
|
|
63
|
+
const nextTexture = texture.clone();
|
|
64
|
+
configureTexture(nextTexture, options);
|
|
65
|
+
return nextTexture;
|
|
66
|
+
}
|
|
67
|
+
function useConfiguredTexture(texture, options) {
|
|
68
|
+
var _a, _b, _c, _d;
|
|
69
|
+
const configuredTexture = useMemo(() => cloneConfiguredTexture(texture, options), [texture]);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
return () => configuredTexture === null || configuredTexture === void 0 ? void 0 : configuredTexture.dispose();
|
|
72
|
+
}, [configuredTexture]);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
configureTexture(configuredTexture, options);
|
|
75
|
+
}, [
|
|
76
|
+
configuredTexture,
|
|
77
|
+
options.colorSpace,
|
|
78
|
+
options.repeat,
|
|
79
|
+
(_a = options.repeatCount) === null || _a === void 0 ? void 0 : _a[0],
|
|
80
|
+
(_b = options.repeatCount) === null || _b === void 0 ? void 0 : _b[1],
|
|
81
|
+
(_c = options.offset) === null || _c === void 0 ? void 0 : _c[0],
|
|
82
|
+
(_d = options.offset) === null || _d === void 0 ? void 0 : _d[1],
|
|
83
|
+
options.generateMipmaps,
|
|
84
|
+
options.minFilter,
|
|
85
|
+
options.magFilter,
|
|
86
|
+
]);
|
|
87
|
+
return configuredTexture;
|
|
88
|
+
}
|
|
41
89
|
export function useMaterialOverrides() {
|
|
42
90
|
return useContext(MaterialOverridesContext);
|
|
43
91
|
}
|
|
@@ -52,13 +100,14 @@ extend({
|
|
|
52
100
|
SpriteNodeMaterial,
|
|
53
101
|
});
|
|
54
102
|
function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
55
|
-
var _a;
|
|
103
|
+
var _a, _b, _c, _d;
|
|
56
104
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
57
105
|
const hasTexture = !!component.properties.texture;
|
|
58
106
|
const hasRepeat = component.properties.repeat;
|
|
59
107
|
const animateOffset = component.properties.animateOffset;
|
|
60
108
|
const isStandardMaterial = materialType === 'standard';
|
|
61
109
|
const isSpriteMaterial = materialType === 'sprite';
|
|
110
|
+
const editorValues = Object.assign(Object.assign({}, component.properties), { generateMipmaps: (_b = component.properties.generateMipmaps) !== null && _b !== void 0 ? _b : true, minFilter: (_c = component.properties.minFilter) !== null && _c !== void 0 ? _c : 'LinearMipmapLinearFilter', magFilter: (_d = component.properties.magFilter) !== null && _d !== void 0 ? _d : 'LinearFilter' });
|
|
62
111
|
const fields = [
|
|
63
112
|
{
|
|
64
113
|
name: 'attach',
|
|
@@ -151,12 +200,12 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
151
200
|
type: 'select',
|
|
152
201
|
label: 'Min Filter',
|
|
153
202
|
options: [
|
|
203
|
+
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
204
|
+
{ value: 'LinearFilter', label: 'Linear' },
|
|
205
|
+
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
154
206
|
{ value: 'NearestFilter', label: 'Nearest' },
|
|
155
207
|
{ value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
|
|
156
208
|
{ value: 'NearestMipmapLinearFilter', label: 'Nearest Mipmap Linear' },
|
|
157
|
-
{ value: 'LinearFilter', label: 'Linear' },
|
|
158
|
-
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
159
|
-
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
160
209
|
],
|
|
161
210
|
},
|
|
162
211
|
{
|
|
@@ -164,17 +213,17 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
164
213
|
type: 'select',
|
|
165
214
|
label: 'Mag Filter',
|
|
166
215
|
options: [
|
|
167
|
-
{ value: 'NearestFilter', label: 'Nearest' },
|
|
168
216
|
{ value: 'LinearFilter', label: 'Linear (Default)' },
|
|
217
|
+
{ value: 'NearestFilter', label: 'Nearest' },
|
|
169
218
|
],
|
|
170
219
|
},
|
|
171
220
|
] : []),
|
|
172
221
|
];
|
|
173
|
-
return (_jsx(FieldRenderer, { fields: fields, values:
|
|
222
|
+
return (_jsx(FieldRenderer, { fields: fields, values: editorValues, onChange: onUpdate }));
|
|
174
223
|
}
|
|
175
224
|
// View for Material component
|
|
176
225
|
function MaterialComponentView({ properties: rawProps }) {
|
|
177
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
226
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
178
227
|
const { getTexture } = useAssetRuntime();
|
|
179
228
|
const properties = rawProps;
|
|
180
229
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
@@ -198,80 +247,16 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
198
247
|
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
199
248
|
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
200
249
|
const animatedOffsetRef = useRef([(_g = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _g !== void 0 ? _g : 0, (_h = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _h !== void 0 ? _h : 0]);
|
|
201
|
-
const
|
|
202
|
-
return texture ? texture.clone() : undefined;
|
|
203
|
-
}, [texture]);
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
return () => finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.dispose();
|
|
206
|
-
}, [finalTexture]);
|
|
207
|
-
useEffect(() => {
|
|
208
|
-
var _a, _b;
|
|
209
|
-
if (!finalTexture)
|
|
210
|
-
return;
|
|
211
|
-
if (repeat) {
|
|
212
|
-
finalTexture.wrapS = finalTexture.wrapT = RepeatWrapping;
|
|
213
|
-
if (repeatCount) {
|
|
214
|
-
finalTexture.repeat.set(repeatCount[0], repeatCount[1]);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
finalTexture.wrapS = finalTexture.wrapT = ClampToEdgeWrapping;
|
|
219
|
-
finalTexture.repeat.set(1, 1);
|
|
220
|
-
}
|
|
221
|
-
finalTexture.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
222
|
-
finalTexture.colorSpace = SRGBColorSpace;
|
|
223
|
-
finalTexture.generateMipmaps = generateMipmaps;
|
|
224
|
-
finalTexture.minFilter = resolvedMinFilter;
|
|
225
|
-
finalTexture.magFilter = resolvedMagFilter;
|
|
226
|
-
finalTexture.needsUpdate = true;
|
|
227
|
-
}, [
|
|
228
|
-
finalTexture,
|
|
250
|
+
const textureConfig = {
|
|
229
251
|
repeat,
|
|
230
|
-
repeatCount
|
|
231
|
-
|
|
232
|
-
offset === null || offset === void 0 ? void 0 : offset[0],
|
|
233
|
-
offset === null || offset === void 0 ? void 0 : offset[1],
|
|
252
|
+
repeatCount,
|
|
253
|
+
offset,
|
|
234
254
|
generateMipmaps,
|
|
235
|
-
resolvedMinFilter,
|
|
236
|
-
resolvedMagFilter
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
}, [normalMapTexture]);
|
|
241
|
-
useEffect(() => {
|
|
242
|
-
return () => finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.dispose();
|
|
243
|
-
}, [finalNormalMap]);
|
|
244
|
-
useEffect(() => {
|
|
245
|
-
var _a, _b;
|
|
246
|
-
if (!finalNormalMap)
|
|
247
|
-
return;
|
|
248
|
-
if (repeat) {
|
|
249
|
-
finalNormalMap.wrapS = finalNormalMap.wrapT = RepeatWrapping;
|
|
250
|
-
if (repeatCount) {
|
|
251
|
-
finalNormalMap.repeat.set(repeatCount[0], repeatCount[1]);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
finalNormalMap.wrapS = finalNormalMap.wrapT = ClampToEdgeWrapping;
|
|
256
|
-
finalNormalMap.repeat.set(1, 1);
|
|
257
|
-
}
|
|
258
|
-
finalNormalMap.offset.set((_a = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _a !== void 0 ? _a : 0, (_b = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _b !== void 0 ? _b : 0);
|
|
259
|
-
finalNormalMap.colorSpace = NoColorSpace;
|
|
260
|
-
finalNormalMap.generateMipmaps = generateMipmaps;
|
|
261
|
-
finalNormalMap.minFilter = resolvedMinFilter;
|
|
262
|
-
finalNormalMap.magFilter = resolvedMagFilter;
|
|
263
|
-
finalNormalMap.needsUpdate = true;
|
|
264
|
-
}, [
|
|
265
|
-
finalNormalMap,
|
|
266
|
-
repeat,
|
|
267
|
-
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
|
|
268
|
-
repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
|
|
269
|
-
offset === null || offset === void 0 ? void 0 : offset[0],
|
|
270
|
-
offset === null || offset === void 0 ? void 0 : offset[1],
|
|
271
|
-
generateMipmaps,
|
|
272
|
-
resolvedMinFilter,
|
|
273
|
-
resolvedMagFilter
|
|
274
|
-
]);
|
|
255
|
+
minFilter: resolvedMinFilter,
|
|
256
|
+
magFilter: resolvedMagFilter,
|
|
257
|
+
};
|
|
258
|
+
const finalTexture = useConfiguredTexture(texture, Object.assign(Object.assign({}, textureConfig), { colorSpace: SRGBColorSpace }));
|
|
259
|
+
const finalNormalMap = useConfiguredTexture(normalMapTexture, Object.assign(Object.assign({}, textureConfig), { colorSpace: NoColorSpace }));
|
|
275
260
|
animatedOffsetRef.current = [(_j = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _j !== void 0 ? _j : 0, (_k = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _k !== void 0 ? _k : 0];
|
|
276
261
|
useFrame((_, delta) => {
|
|
277
262
|
var _a, _b;
|
|
@@ -294,17 +279,23 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
294
279
|
materialType,
|
|
295
280
|
textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
|
|
296
281
|
normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
|
|
282
|
+
repeat ? 'repeat' : 'clamp',
|
|
283
|
+
(_l = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0]) !== null && _l !== void 0 ? _l : 1,
|
|
284
|
+
(_m = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]) !== null && _m !== void 0 ? _m : 1,
|
|
285
|
+
generateMipmaps ? 'mips' : 'no-mips',
|
|
286
|
+
minFilter,
|
|
287
|
+
magFilter,
|
|
297
288
|
].join('|');
|
|
298
289
|
if (materialType === 'basic') {
|
|
299
290
|
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
|
|
300
291
|
}
|
|
301
292
|
if (materialType === 'sprite') {
|
|
302
293
|
const spriteTransparent = materialSource.transparent !== false;
|
|
303
|
-
return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (
|
|
294
|
+
return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, color: (_o = materialSource.color) !== null && _o !== void 0 ? _o : '#ffffff', opacity: (_p = materialSource.opacity) !== null && _p !== void 0 ? _p : 1, transparent: spriteTransparent, alphaTest: (_q = materialSource.alphaTest) !== null && _q !== void 0 ? _q : 0, depthTest: (_r = materialSource.depthTest) !== null && _r !== void 0 ? _r : false, depthWrite: (_s = materialSource.depthWrite) !== null && _s !== void 0 ? _s : false, toneMapped: (_t = materialSource.toneMapped) !== null && _t !== void 0 ? _t : true, onUpdate: material => {
|
|
304
295
|
material.needsUpdate = true;
|
|
305
296
|
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
|
|
306
297
|
}
|
|
307
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(
|
|
298
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, normalScale: finalNormalMap ? [(_u = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _u !== void 0 ? _u : 1, (_v = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _v !== void 0 ? _v : 1] : undefined }), materialKey));
|
|
308
299
|
}
|
|
309
300
|
const MaterialComponent = {
|
|
310
301
|
name: 'Material',
|
package/package.json
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-three-game",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.102",
|
|
4
4
|
"description": "high performance 3D game engine built in React",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./plugins": {
|
|
15
|
+
"types": "./dist/plugins/index.d.ts",
|
|
16
|
+
"import": "./dist/plugins/index.js",
|
|
17
|
+
"default": "./dist/plugins/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./plugins/crashcat": {
|
|
20
|
+
"types": "./dist/plugins/crashcat/index.d.ts",
|
|
21
|
+
"import": "./dist/plugins/crashcat/index.js",
|
|
22
|
+
"default": "./dist/plugins/crashcat/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
8
26
|
"files": [
|
|
9
27
|
"dist",
|
|
10
28
|
"README.md",
|
|
@@ -30,7 +48,13 @@
|
|
|
30
48
|
"react": ">=18.0.0",
|
|
31
49
|
"react-dom": ">=18.0.0",
|
|
32
50
|
"three": ">=0.182.0",
|
|
33
|
-
"three-text": ">=0.4.4"
|
|
51
|
+
"three-text": ">=0.4.4",
|
|
52
|
+
"crashcat": ">=0.0.4"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"crashcat": {
|
|
56
|
+
"optional": true
|
|
57
|
+
}
|
|
34
58
|
},
|
|
35
59
|
"devDependencies": {
|
|
36
60
|
"@react-three/drei": "^10.7.7",
|
|
@@ -39,6 +63,7 @@
|
|
|
39
63
|
"@types/react-dom": "^19.2.3",
|
|
40
64
|
"@types/three": "^0.182.0",
|
|
41
65
|
"concurrently": "^9.2.1",
|
|
66
|
+
"crashcat": "^0.0.4",
|
|
42
67
|
"react": "^19.2.4",
|
|
43
68
|
"react-dom": "^19.2.4",
|
|
44
69
|
"three": "^0.184.0",
|