react-three-game 0.0.101 → 0.0.103
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 +36 -4
- package/dist/tools/prefabeditor/components/MaterialComponent.js +69 -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,44 @@ 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
|
+
let cancelled = false;
|
|
143
|
+
textureRef.current = null;
|
|
144
|
+
setTexture(null);
|
|
145
|
+
const loader = new TextureLoader();
|
|
146
|
+
loader.load(url, loadedTexture => {
|
|
147
|
+
if (cancelled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
textureRef.current = loadedTexture;
|
|
151
|
+
setTexture(loadedTexture);
|
|
152
|
+
}, undefined, () => {
|
|
153
|
+
var _a;
|
|
154
|
+
if (!cancelled) {
|
|
155
|
+
(_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return () => {
|
|
159
|
+
cancelled = true;
|
|
160
|
+
textureRef.current = null;
|
|
161
|
+
};
|
|
162
|
+
}, [url]);
|
|
163
|
+
if (!texture)
|
|
164
|
+
return null;
|
|
133
165
|
return (_jsxs("mesh", { position: [0, 0, 0], children: [_jsx("sphereGeometry", { args: [1, 32, 32] }), _jsx("meshStandardMaterial", { map: texture })] }));
|
|
134
166
|
}
|
|
135
167
|
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,
|
|
13
|
+
import { createContext, useContext, 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,50 @@ 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 useConfiguredTexture(texture, options) {
|
|
61
|
+
const { colorSpace, repeat, repeatCount, offset, generateMipmaps, minFilter, magFilter, } = options;
|
|
62
|
+
const configuredTexture = useMemo(() => texture === null || texture === void 0 ? void 0 : texture.clone(), [texture]);
|
|
63
|
+
useLayoutEffect(() => {
|
|
64
|
+
configureTexture(configuredTexture, {
|
|
65
|
+
colorSpace,
|
|
66
|
+
repeat,
|
|
67
|
+
repeatCount,
|
|
68
|
+
offset,
|
|
69
|
+
generateMipmaps,
|
|
70
|
+
minFilter,
|
|
71
|
+
magFilter,
|
|
72
|
+
});
|
|
73
|
+
}, [
|
|
74
|
+
configuredTexture,
|
|
75
|
+
colorSpace,
|
|
76
|
+
repeat,
|
|
77
|
+
repeatCount,
|
|
78
|
+
offset,
|
|
79
|
+
generateMipmaps,
|
|
80
|
+
minFilter,
|
|
81
|
+
magFilter,
|
|
82
|
+
]);
|
|
83
|
+
return configuredTexture;
|
|
84
|
+
}
|
|
41
85
|
export function useMaterialOverrides() {
|
|
42
86
|
return useContext(MaterialOverridesContext);
|
|
43
87
|
}
|
|
@@ -52,13 +96,14 @@ extend({
|
|
|
52
96
|
SpriteNodeMaterial,
|
|
53
97
|
});
|
|
54
98
|
function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
55
|
-
var _a;
|
|
99
|
+
var _a, _b, _c, _d;
|
|
56
100
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
57
101
|
const hasTexture = !!component.properties.texture;
|
|
58
102
|
const hasRepeat = component.properties.repeat;
|
|
59
103
|
const animateOffset = component.properties.animateOffset;
|
|
60
104
|
const isStandardMaterial = materialType === 'standard';
|
|
61
105
|
const isSpriteMaterial = materialType === 'sprite';
|
|
106
|
+
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
107
|
const fields = [
|
|
63
108
|
{
|
|
64
109
|
name: 'attach',
|
|
@@ -151,12 +196,12 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
151
196
|
type: 'select',
|
|
152
197
|
label: 'Min Filter',
|
|
153
198
|
options: [
|
|
199
|
+
{ value: 'LinearMipmapLinearFilter', label: 'Linear Mipmap Linear (Default)' },
|
|
200
|
+
{ value: 'LinearFilter', label: 'Linear' },
|
|
201
|
+
{ value: 'LinearMipmapNearestFilter', label: 'Linear Mipmap Nearest' },
|
|
154
202
|
{ value: 'NearestFilter', label: 'Nearest' },
|
|
155
203
|
{ value: 'NearestMipmapNearestFilter', label: 'Nearest Mipmap Nearest' },
|
|
156
204
|
{ 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
205
|
],
|
|
161
206
|
},
|
|
162
207
|
{
|
|
@@ -164,17 +209,17 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
|
|
|
164
209
|
type: 'select',
|
|
165
210
|
label: 'Mag Filter',
|
|
166
211
|
options: [
|
|
167
|
-
{ value: 'NearestFilter', label: 'Nearest' },
|
|
168
212
|
{ value: 'LinearFilter', label: 'Linear (Default)' },
|
|
213
|
+
{ value: 'NearestFilter', label: 'Nearest' },
|
|
169
214
|
],
|
|
170
215
|
},
|
|
171
216
|
] : []),
|
|
172
217
|
];
|
|
173
|
-
return (_jsx(FieldRenderer, { fields: fields, values:
|
|
218
|
+
return (_jsx(FieldRenderer, { fields: fields, values: editorValues, onChange: onUpdate }));
|
|
174
219
|
}
|
|
175
220
|
// View for Material component
|
|
176
221
|
function MaterialComponentView({ properties: rawProps }) {
|
|
177
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
222
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
178
223
|
const { getTexture } = useAssetRuntime();
|
|
179
224
|
const properties = rawProps;
|
|
180
225
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
@@ -198,80 +243,16 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
198
243
|
const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
|
|
199
244
|
const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
|
|
200
245
|
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,
|
|
246
|
+
const textureConfig = {
|
|
229
247
|
repeat,
|
|
230
|
-
repeatCount
|
|
231
|
-
|
|
232
|
-
offset === null || offset === void 0 ? void 0 : offset[0],
|
|
233
|
-
offset === null || offset === void 0 ? void 0 : offset[1],
|
|
248
|
+
repeatCount,
|
|
249
|
+
offset,
|
|
234
250
|
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
|
-
]);
|
|
251
|
+
minFilter: resolvedMinFilter,
|
|
252
|
+
magFilter: resolvedMagFilter,
|
|
253
|
+
};
|
|
254
|
+
const finalTexture = useConfiguredTexture(texture, Object.assign(Object.assign({}, textureConfig), { colorSpace: SRGBColorSpace }));
|
|
255
|
+
const finalNormalMap = useConfiguredTexture(normalMapTexture, Object.assign(Object.assign({}, textureConfig), { colorSpace: NoColorSpace }));
|
|
275
256
|
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
257
|
useFrame((_, delta) => {
|
|
277
258
|
var _a, _b;
|
|
@@ -294,17 +275,23 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
294
275
|
materialType,
|
|
295
276
|
textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
|
|
296
277
|
normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
|
|
278
|
+
repeat ? 'repeat' : 'clamp',
|
|
279
|
+
(_l = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0]) !== null && _l !== void 0 ? _l : 1,
|
|
280
|
+
(_m = repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1]) !== null && _m !== void 0 ? _m : 1,
|
|
281
|
+
generateMipmaps ? 'mips' : 'no-mips',
|
|
282
|
+
minFilter,
|
|
283
|
+
magFilter,
|
|
297
284
|
].join('|');
|
|
298
285
|
if (materialType === 'basic') {
|
|
299
286
|
return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
|
|
300
287
|
}
|
|
301
288
|
if (materialType === 'sprite') {
|
|
302
289
|
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: (
|
|
290
|
+
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
291
|
material.needsUpdate = true;
|
|
305
292
|
} }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
|
|
306
293
|
}
|
|
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 ? [(
|
|
294
|
+
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
295
|
}
|
|
309
296
|
const MaterialComponent = {
|
|
310
297
|
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.103",
|
|
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",
|