react-three-game 0.0.23 → 0.0.24
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.
|
@@ -2,30 +2,30 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { createContext, useContext, useMemo, useRef, useState, useEffect, useCallback } from "react";
|
|
3
3
|
import { Merged } from '@react-three/drei';
|
|
4
4
|
import { InstancedRigidBodies } from "@react-three/rapier";
|
|
5
|
-
import {
|
|
6
|
-
import { Mesh, Matrix4, Euler, Quaternion, Vector3 } from "three";
|
|
5
|
+
import { Mesh, Matrix4 } from "three";
|
|
7
6
|
// --- Helpers ---
|
|
8
|
-
const
|
|
9
|
-
const
|
|
7
|
+
const tupleEqual = (a, b) => a.length === b.length && a.every((v, i) => v === b[i]);
|
|
8
|
+
const instanceChanged = (a, b) => {
|
|
10
9
|
var _a, _b;
|
|
11
|
-
return a.
|
|
12
|
-
a.
|
|
13
|
-
(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
arraysEqual(a.scale, b.scale);
|
|
10
|
+
return a.meshPath !== b.meshPath ||
|
|
11
|
+
((_a = a.physics) === null || _a === void 0 ? void 0 : _a.type) !== ((_b = b.physics) === null || _b === void 0 ? void 0 : _b.type) ||
|
|
12
|
+
!tupleEqual(a.position, b.position) ||
|
|
13
|
+
!tupleEqual(a.rotation, b.rotation) ||
|
|
14
|
+
!tupleEqual(a.scale, b.scale);
|
|
17
15
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
function extractMeshParts(model) {
|
|
17
|
+
model.updateWorldMatrix(false, true);
|
|
18
|
+
const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
|
|
19
|
+
const parts = [];
|
|
20
|
+
model.traverse(child => {
|
|
21
|
+
if (child.isMesh) {
|
|
22
|
+
const mesh = child;
|
|
23
|
+
const geometry = mesh.geometry.clone();
|
|
24
|
+
geometry.applyMatrix4(mesh.matrixWorld.clone().premultiply(rootInverse));
|
|
25
|
+
parts.push(new Mesh(geometry, mesh.material));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
return parts;
|
|
29
29
|
}
|
|
30
30
|
// --- Context ---
|
|
31
31
|
const GameInstanceContext = createContext(null);
|
|
@@ -37,7 +37,7 @@ export function GameInstanceProvider({ children, models, onSelect, registerRef }
|
|
|
37
37
|
const idx = prev.findIndex(i => i.id === instance.id);
|
|
38
38
|
if (idx === -1)
|
|
39
39
|
return [...prev, instance];
|
|
40
|
-
if (
|
|
40
|
+
if (!instanceChanged(prev[idx], instance))
|
|
41
41
|
return prev;
|
|
42
42
|
const updated = [...prev];
|
|
43
43
|
updated[idx] = instance;
|
|
@@ -52,19 +52,11 @@ export function GameInstanceProvider({ children, models, onSelect, registerRef }
|
|
|
52
52
|
const meshParts = {};
|
|
53
53
|
const partCounts = {};
|
|
54
54
|
Object.entries(models).forEach(([modelKey, model]) => {
|
|
55
|
-
model
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
model.traverse((child) => {
|
|
59
|
-
if (child.isMesh) {
|
|
60
|
-
const mesh = child;
|
|
61
|
-
const geometry = mesh.geometry.clone();
|
|
62
|
-
geometry.applyMatrix4(mesh.matrixWorld.clone().premultiply(rootInverse));
|
|
63
|
-
meshParts[`${modelKey}__${partIndex}`] = new Mesh(geometry, mesh.material);
|
|
64
|
-
partIndex++;
|
|
65
|
-
}
|
|
55
|
+
const parts = extractMeshParts(model);
|
|
56
|
+
parts.forEach((mesh, i) => {
|
|
57
|
+
meshParts[`${modelKey}__${i}`] = mesh;
|
|
66
58
|
});
|
|
67
|
-
partCounts[modelKey] =
|
|
59
|
+
partCounts[modelKey] = parts.length;
|
|
68
60
|
});
|
|
69
61
|
return { meshParts, partCounts };
|
|
70
62
|
}, [models]);
|
|
@@ -99,23 +91,14 @@ export function GameInstanceProvider({ children, models, onSelect, registerRef }
|
|
|
99
91
|
})] }));
|
|
100
92
|
}
|
|
101
93
|
// --- Physics Instances ---
|
|
94
|
+
// InstancedRigidBodies handles position/rotation/scale via the instances prop.
|
|
95
|
+
// We pass scale in instances and let the library manage matrix updates.
|
|
102
96
|
function PhysicsInstances({ instances, physicsType, modelKey, partCount, meshParts }) {
|
|
103
|
-
|
|
97
|
+
// InstancedRigidBodies expects { key, position, rotation, scale }
|
|
104
98
|
const rigidBodyInstances = useMemo(() => instances.map(({ id, position, rotation, scale }) => ({ key: id, position, rotation, scale })), [instances]);
|
|
105
|
-
// Sync visual matrices each frame (physics updates position/rotation, we need to apply scale)
|
|
106
|
-
useFrame(() => {
|
|
107
|
-
meshRefs.current.forEach(mesh => {
|
|
108
|
-
if (!mesh)
|
|
109
|
-
return;
|
|
110
|
-
instances.forEach((inst, i) => {
|
|
111
|
-
mesh.setMatrixAt(i, composeMatrix(inst.position, inst.rotation, inst.scale));
|
|
112
|
-
});
|
|
113
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
99
|
return (_jsx(InstancedRigidBodies, { instances: rigidBodyInstances, type: physicsType, colliders: physicsType === 'fixed' ? 'trimesh' : 'hull', children: Array.from({ length: partCount }, (_, i) => {
|
|
117
100
|
const mesh = meshParts[`${modelKey}__${i}`];
|
|
118
|
-
return mesh ? (_jsx("instancedMesh", {
|
|
101
|
+
return mesh ? (_jsx("instancedMesh", { args: [mesh.geometry, mesh.material, instances.length], frustumCulled: false, castShadow: true, receiveShadow: true }, i)) : null;
|
|
119
102
|
}) }));
|
|
120
103
|
}
|
|
121
104
|
// --- Static Instances (non-physics) ---
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React, { createContext, useContext, useMemo, useRef, useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { Merged } from '@react-three/drei';
|
|
3
3
|
import { InstancedRigidBodies, RigidBodyProps } from "@react-three/rapier";
|
|
4
|
-
import {
|
|
5
|
-
import { Mesh, Matrix4, Object3D, Euler, Quaternion, Vector3, InstancedMesh } from "three";
|
|
4
|
+
import { Mesh, Matrix4, Object3D } from "three";
|
|
6
5
|
|
|
7
6
|
// --- Types ---
|
|
8
7
|
export type InstanceData = {
|
|
@@ -25,34 +24,31 @@ type GameInstanceContextType = {
|
|
|
25
24
|
};
|
|
26
25
|
|
|
27
26
|
// --- Helpers ---
|
|
28
|
-
const
|
|
27
|
+
const tupleEqual = (a: readonly number[], b: readonly number[]) =>
|
|
29
28
|
a.length === b.length && a.every((v, i) => v === b[i]);
|
|
30
29
|
|
|
31
|
-
const
|
|
32
|
-
a.
|
|
33
|
-
a.
|
|
34
|
-
a.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
_quaternion.setFromEuler(_euler.set(...rotation));
|
|
54
|
-
_scale.set(...scale);
|
|
55
|
-
return target.compose(_position, _quaternion, _scale);
|
|
30
|
+
const instanceChanged = (a: InstanceData, b: InstanceData) =>
|
|
31
|
+
a.meshPath !== b.meshPath ||
|
|
32
|
+
a.physics?.type !== b.physics?.type ||
|
|
33
|
+
!tupleEqual(a.position, b.position) ||
|
|
34
|
+
!tupleEqual(a.rotation, b.rotation) ||
|
|
35
|
+
!tupleEqual(a.scale, b.scale);
|
|
36
|
+
|
|
37
|
+
function extractMeshParts(model: Object3D): Mesh[] {
|
|
38
|
+
model.updateWorldMatrix(false, true);
|
|
39
|
+
const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
|
|
40
|
+
const parts: Mesh[] = [];
|
|
41
|
+
|
|
42
|
+
model.traverse(child => {
|
|
43
|
+
if ((child as Mesh).isMesh) {
|
|
44
|
+
const mesh = child as Mesh;
|
|
45
|
+
const geometry = mesh.geometry.clone();
|
|
46
|
+
geometry.applyMatrix4(mesh.matrixWorld.clone().premultiply(rootInverse));
|
|
47
|
+
parts.push(new Mesh(geometry, mesh.material));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return parts;
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
// --- Context ---
|
|
@@ -76,7 +72,7 @@ export function GameInstanceProvider({
|
|
|
76
72
|
setInstances(prev => {
|
|
77
73
|
const idx = prev.findIndex(i => i.id === instance.id);
|
|
78
74
|
if (idx === -1) return [...prev, instance];
|
|
79
|
-
if (
|
|
75
|
+
if (!instanceChanged(prev[idx], instance)) return prev;
|
|
80
76
|
const updated = [...prev];
|
|
81
77
|
updated[idx] = instance;
|
|
82
78
|
return updated;
|
|
@@ -93,20 +89,11 @@ export function GameInstanceProvider({
|
|
|
93
89
|
const partCounts: Record<string, number> = {};
|
|
94
90
|
|
|
95
91
|
Object.entries(models).forEach(([modelKey, model]) => {
|
|
96
|
-
model
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
model.traverse((child: Object3D) => {
|
|
101
|
-
if ((child as Mesh).isMesh) {
|
|
102
|
-
const mesh = child as Mesh;
|
|
103
|
-
const geometry = mesh.geometry.clone();
|
|
104
|
-
geometry.applyMatrix4(mesh.matrixWorld.clone().premultiply(rootInverse));
|
|
105
|
-
meshParts[`${modelKey}__${partIndex}`] = new Mesh(geometry, mesh.material);
|
|
106
|
-
partIndex++;
|
|
107
|
-
}
|
|
92
|
+
const parts = extractMeshParts(model);
|
|
93
|
+
parts.forEach((mesh, i) => {
|
|
94
|
+
meshParts[`${modelKey}__${i}`] = mesh;
|
|
108
95
|
});
|
|
109
|
-
partCounts[modelKey] =
|
|
96
|
+
partCounts[modelKey] = parts.length;
|
|
110
97
|
});
|
|
111
98
|
|
|
112
99
|
return { meshParts, partCounts };
|
|
@@ -177,6 +164,8 @@ export function GameInstanceProvider({
|
|
|
177
164
|
}
|
|
178
165
|
|
|
179
166
|
// --- Physics Instances ---
|
|
167
|
+
// InstancedRigidBodies handles position/rotation/scale via the instances prop.
|
|
168
|
+
// We pass scale in instances and let the library manage matrix updates.
|
|
180
169
|
function PhysicsInstances({
|
|
181
170
|
instances,
|
|
182
171
|
physicsType,
|
|
@@ -190,24 +179,12 @@ function PhysicsInstances({
|
|
|
190
179
|
partCount: number;
|
|
191
180
|
meshParts: Record<string, Mesh>;
|
|
192
181
|
}) {
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
// InstancedRigidBodies expects { key, position, rotation, scale }
|
|
195
183
|
const rigidBodyInstances = useMemo(() =>
|
|
196
184
|
instances.map(({ id, position, rotation, scale }) => ({ key: id, position, rotation, scale })),
|
|
197
185
|
[instances]
|
|
198
186
|
);
|
|
199
187
|
|
|
200
|
-
// Sync visual matrices each frame (physics updates position/rotation, we need to apply scale)
|
|
201
|
-
useFrame(() => {
|
|
202
|
-
meshRefs.current.forEach(mesh => {
|
|
203
|
-
if (!mesh) return;
|
|
204
|
-
instances.forEach((inst, i) => {
|
|
205
|
-
mesh.setMatrixAt(i, composeMatrix(inst.position, inst.rotation, inst.scale));
|
|
206
|
-
});
|
|
207
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
|
|
211
188
|
return (
|
|
212
189
|
<InstancedRigidBodies
|
|
213
190
|
instances={rigidBodyInstances}
|
|
@@ -219,7 +196,6 @@ function PhysicsInstances({
|
|
|
219
196
|
return mesh ? (
|
|
220
197
|
<instancedMesh
|
|
221
198
|
key={i}
|
|
222
|
-
ref={el => { meshRefs.current[i] = el; }}
|
|
223
199
|
args={[mesh.geometry, mesh.material, instances.length]}
|
|
224
200
|
frustumCulled={false}
|
|
225
201
|
castShadow
|