react-three-game 0.0.23 → 0.0.25
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, Quaternion, Vector3 } 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,20 +91,31 @@ export function GameInstanceProvider({ children, models, onSelect, registerRef }
|
|
|
99
91
|
})] }));
|
|
100
92
|
}
|
|
101
93
|
// --- Physics Instances ---
|
|
94
|
+
// InstancedRigidBodies manages position/rotation from physics simulation.
|
|
95
|
+
// We apply scale once when instances change via useEffect.
|
|
102
96
|
function PhysicsInstances({ instances, physicsType, modelKey, partCount, meshParts }) {
|
|
103
97
|
const meshRefs = useRef([]);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
98
|
+
// InstancedRigidBodies needs position/rotation (scale handled separately)
|
|
99
|
+
const rigidBodyInstances = useMemo(() => instances.map(({ id, position, rotation }) => ({ key: id, position, rotation })), [instances]);
|
|
100
|
+
// Apply scale when instances change
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const matrix = new Matrix4();
|
|
103
|
+
const pos = new Vector3();
|
|
104
|
+
const quat = new Quaternion();
|
|
105
|
+
const scl = new Vector3();
|
|
107
106
|
meshRefs.current.forEach(mesh => {
|
|
108
107
|
if (!mesh)
|
|
109
108
|
return;
|
|
110
109
|
instances.forEach((inst, i) => {
|
|
111
|
-
mesh.
|
|
110
|
+
mesh.getMatrixAt(i, matrix);
|
|
111
|
+
matrix.decompose(pos, quat, scl);
|
|
112
|
+
scl.set(...inst.scale);
|
|
113
|
+
matrix.compose(pos, quat, scl);
|
|
114
|
+
mesh.setMatrixAt(i, matrix);
|
|
112
115
|
});
|
|
113
116
|
mesh.instanceMatrix.needsUpdate = true;
|
|
114
117
|
});
|
|
115
|
-
});
|
|
118
|
+
}, [instances]);
|
|
116
119
|
return (_jsx(InstancedRigidBodies, { instances: rigidBodyInstances, type: physicsType, colliders: physicsType === 'fixed' ? 'trimesh' : 'hull', children: Array.from({ length: partCount }, (_, i) => {
|
|
117
120
|
const mesh = meshParts[`${modelKey}__${i}`];
|
|
118
121
|
return mesh ? (_jsx("instancedMesh", { ref: el => { meshRefs.current[i] = el; }, args: [mesh.geometry, mesh.material, instances.length], frustumCulled: false, castShadow: true, receiveShadow: true }, i)) : null;
|
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, Quaternion, Vector3, InstancedMesh } 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 manages position/rotation from physics simulation.
|
|
168
|
+
// We apply scale once when instances change via useEffect.
|
|
180
169
|
function PhysicsInstances({
|
|
181
170
|
instances,
|
|
182
171
|
physicsType,
|
|
@@ -192,21 +181,32 @@ function PhysicsInstances({
|
|
|
192
181
|
}) {
|
|
193
182
|
const meshRefs = useRef<(InstancedMesh | null)[]>([]);
|
|
194
183
|
|
|
184
|
+
// InstancedRigidBodies needs position/rotation (scale handled separately)
|
|
195
185
|
const rigidBodyInstances = useMemo(() =>
|
|
196
|
-
instances.map(({ id, position, rotation
|
|
186
|
+
instances.map(({ id, position, rotation }) => ({ key: id, position, rotation })),
|
|
197
187
|
[instances]
|
|
198
188
|
);
|
|
199
189
|
|
|
200
|
-
//
|
|
201
|
-
|
|
190
|
+
// Apply scale when instances change
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
const matrix = new Matrix4();
|
|
193
|
+
const pos = new Vector3();
|
|
194
|
+
const quat = new Quaternion();
|
|
195
|
+
const scl = new Vector3();
|
|
196
|
+
|
|
202
197
|
meshRefs.current.forEach(mesh => {
|
|
203
198
|
if (!mesh) return;
|
|
199
|
+
|
|
204
200
|
instances.forEach((inst, i) => {
|
|
205
|
-
mesh.
|
|
201
|
+
mesh.getMatrixAt(i, matrix);
|
|
202
|
+
matrix.decompose(pos, quat, scl);
|
|
203
|
+
scl.set(...inst.scale);
|
|
204
|
+
matrix.compose(pos, quat, scl);
|
|
205
|
+
mesh.setMatrixAt(i, matrix);
|
|
206
206
|
});
|
|
207
207
|
mesh.instanceMatrix.needsUpdate = true;
|
|
208
208
|
});
|
|
209
|
-
});
|
|
209
|
+
}, [instances]);
|
|
210
210
|
|
|
211
211
|
return (
|
|
212
212
|
<InstancedRigidBodies
|