react-three-game 0.0.83 → 0.0.85
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 +72 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/tools/prefabeditor/PrefabRoot.js +64 -54
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +96 -0
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -2
- package/dist/tools/prefabeditor/components/ClickComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +3 -5
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -9
- package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -1
- package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +10 -0
- package/dist/tools/prefabeditor/components/MaterialComponent.js +63 -28
- package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PointLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/SoundComponent.js +1 -2
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.js +8 -6
- package/dist/tools/prefabeditor/runtime.d.ts +61 -0
- package/dist/tools/prefabeditor/runtime.js +184 -0
- package/dist/tools/prefabeditor/scene.d.ts +8 -0
- package/dist/tools/prefabeditor/scene.js +45 -8
- package/package.json +2 -2
- package/dist/tools/prefabeditor/runtimeContext.d.ts +0 -40
- package/dist/tools/prefabeditor/runtimeContext.js +0 -45
|
@@ -9,14 +9,29 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
import {
|
|
12
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { createContext, useContext, useMemo, useRef } from 'react';
|
|
13
14
|
import { extend } from '@react-three/fiber';
|
|
15
|
+
import { useFrame } from '@react-three/fiber';
|
|
14
16
|
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
15
|
-
import { useAssetRuntime } from '../
|
|
16
|
-
import { useMemo } from 'react';
|
|
17
|
+
import { useAssetRuntime } from '../runtime';
|
|
17
18
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
18
19
|
import { TexturePicker } from '../../assetviewer/page';
|
|
19
|
-
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace,
|
|
20
|
+
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
21
|
+
function Vector2Editor({ label, value, onChange, min, max, step, }) {
|
|
22
|
+
var _a, _b;
|
|
23
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsxs(Label, { children: [label, " X"] }), _jsx(NumberInput, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 0, onChange: x => { var _a; return onChange([x, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 0]); }, min: min, max: max, step: step, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsxs(Label, { children: [label, " Y"] }), _jsx(NumberInput, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 0, onChange: y => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 0, y]); }, min: min, max: max, step: step, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] }));
|
|
24
|
+
}
|
|
25
|
+
const EMPTY_MATERIAL_OVERRIDES = Object.freeze({});
|
|
26
|
+
const MaterialOverridesContext = createContext(EMPTY_MATERIAL_OVERRIDES);
|
|
27
|
+
export function useMaterialOverrides() {
|
|
28
|
+
return useContext(MaterialOverridesContext);
|
|
29
|
+
}
|
|
30
|
+
export function MaterialOverridesProvider({ overrides, children, }) {
|
|
31
|
+
const parent = useContext(MaterialOverridesContext);
|
|
32
|
+
const merged = useMemo(() => (Object.assign(Object.assign({}, parent), overrides)), [parent, overrides]);
|
|
33
|
+
return _jsx(MaterialOverridesContext.Provider, { value: merged, children: children });
|
|
34
|
+
}
|
|
20
35
|
extend({
|
|
21
36
|
MeshBasicNodeMaterial,
|
|
22
37
|
MeshStandardNodeMaterial,
|
|
@@ -26,6 +41,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
26
41
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
27
42
|
const hasTexture = !!component.properties.texture;
|
|
28
43
|
const hasRepeat = component.properties.repeat;
|
|
44
|
+
const animateOffset = component.properties.animateOffset;
|
|
29
45
|
const isStandardMaterial = materialType === 'standard';
|
|
30
46
|
const fields = [
|
|
31
47
|
{
|
|
@@ -72,10 +88,20 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
72
88
|
name: 'repeatCount',
|
|
73
89
|
type: 'custom',
|
|
74
90
|
label: 'Repeat (X, Y)',
|
|
75
|
-
render: ({ value, onChange }) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Repeat", value: value, onChange: onChange, min: 0.01, max: 100, step: 0.1 })),
|
|
92
|
+
}] : []),
|
|
93
|
+
{
|
|
94
|
+
name: 'offset',
|
|
95
|
+
type: 'custom',
|
|
96
|
+
label: 'Offset (X, Y)',
|
|
97
|
+
render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Offset", value: value, onChange: onChange, step: 0.01 })),
|
|
98
|
+
},
|
|
99
|
+
{ name: 'animateOffset', type: 'boolean', label: 'Animate Offset' },
|
|
100
|
+
...(animateOffset ? [{
|
|
101
|
+
name: 'offsetSpeed',
|
|
102
|
+
type: 'custom',
|
|
103
|
+
label: 'Speed (X, Y)',
|
|
104
|
+
render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Speed", value: value, onChange: onChange, step: 0.01 })),
|
|
79
105
|
}] : []),
|
|
80
106
|
{
|
|
81
107
|
name: 'normalMapTexture',
|
|
@@ -87,10 +113,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
87
113
|
name: 'normalScale',
|
|
88
114
|
type: 'custom',
|
|
89
115
|
label: 'Normal Scale (X, Y)',
|
|
90
|
-
render: ({ value, onChange }) => {
|
|
91
|
-
var _a, _b;
|
|
92
|
-
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "X" }), _jsx(NumberInput, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0, max: 5, step: 0.01, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "Y" }), _jsx(NumberInput, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0, max: 5, step: 0.01, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] }));
|
|
93
|
-
},
|
|
116
|
+
render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Normal", value: value, onChange: onChange, min: 0, max: 5, step: 0.01 })),
|
|
94
117
|
}] : []),
|
|
95
118
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
96
119
|
{
|
|
@@ -121,13 +144,16 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
121
144
|
}
|
|
122
145
|
// View for Material component
|
|
123
146
|
function MaterialComponentView({ properties: rawProps }) {
|
|
124
|
-
var _a, _b, _c, _d, _e;
|
|
147
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
125
148
|
const { getTexture } = useAssetRuntime();
|
|
126
149
|
const properties = rawProps;
|
|
127
150
|
const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
128
151
|
const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
|
|
152
|
+
const offset = properties === null || properties === void 0 ? void 0 : properties.offset;
|
|
129
153
|
const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
|
|
130
154
|
const repeatCount = properties === null || properties === void 0 ? void 0 : properties.repeatCount;
|
|
155
|
+
const animateOffset = properties === null || properties === void 0 ? void 0 : properties.animateOffset;
|
|
156
|
+
const offsetSpeed = properties === null || properties === void 0 ? void 0 : properties.offsetSpeed;
|
|
131
157
|
const generateMipmaps = (properties === null || properties === void 0 ? void 0 : properties.generateMipmaps) !== false;
|
|
132
158
|
const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
|
|
133
159
|
const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
|
|
@@ -137,7 +163,7 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
137
163
|
const normalMapTexture = normalMapTextureName ? (_c = getTexture(normalMapTextureName)) !== null && _c !== void 0 ? _c : undefined : undefined;
|
|
138
164
|
const materialSource = properties !== null && properties !== void 0 ? properties : {};
|
|
139
165
|
// Destructure all material props and separate custom texture handling props
|
|
140
|
-
const { texture: _texture, repeat: _repeat, repeatCount: _repeatCount, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "repeat", "repeatCount", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side"]);
|
|
166
|
+
const { texture: _texture, offset: _offset, repeat: _repeat, repeatCount: _repeatCount, animateOffset: _animateOffset, offsetSpeed: _offsetSpeed, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, normalMapTexture: _normalMapTexture, normalScale: _normalScale, normalMap: _normalMap, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "offset", "repeat", "repeatCount", "animateOffset", "offsetSpeed", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "side"]);
|
|
141
167
|
const sideMap = { FrontSide, BackSide, DoubleSide };
|
|
142
168
|
const resolvedSide = sideProp ? ((_d = sideMap[sideProp]) !== null && _d !== void 0 ? _d : FrontSide) : FrontSide;
|
|
143
169
|
const minFilterMap = {
|
|
@@ -152,8 +178,9 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
152
178
|
NearestFilter,
|
|
153
179
|
LinearFilter
|
|
154
180
|
};
|
|
181
|
+
const animatedOffsetRef = useRef([(_e = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _e !== void 0 ? _e : 0, (_f = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _f !== void 0 ? _f : 0]);
|
|
155
182
|
const finalTexture = useMemo(() => {
|
|
156
|
-
var _a, _b;
|
|
183
|
+
var _a, _b, _c, _d;
|
|
157
184
|
if (!texture)
|
|
158
185
|
return undefined;
|
|
159
186
|
const t = texture.clone();
|
|
@@ -166,13 +193,24 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
166
193
|
t.wrapS = t.wrapT = ClampToEdgeWrapping;
|
|
167
194
|
t.repeat.set(1, 1);
|
|
168
195
|
}
|
|
196
|
+
t.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);
|
|
169
197
|
t.colorSpace = SRGBColorSpace;
|
|
170
198
|
t.generateMipmaps = generateMipmaps;
|
|
171
|
-
t.minFilter = (
|
|
172
|
-
t.magFilter = (
|
|
199
|
+
t.minFilter = (_c = minFilterMap[minFilter]) !== null && _c !== void 0 ? _c : LinearMipmapLinearFilter;
|
|
200
|
+
t.magFilter = (_d = magFilterMap[magFilter]) !== null && _d !== void 0 ? _d : LinearFilter;
|
|
173
201
|
t.needsUpdate = true;
|
|
174
202
|
return t;
|
|
175
|
-
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], generateMipmaps, minFilter, magFilter]);
|
|
203
|
+
}, [texture, repeat, repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0], repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1], offset === null || offset === void 0 ? void 0 : offset[0], offset === null || offset === void 0 ? void 0 : offset[1], generateMipmaps, minFilter, magFilter]);
|
|
204
|
+
animatedOffsetRef.current = [(_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];
|
|
205
|
+
useFrame((_, delta) => {
|
|
206
|
+
var _a, _b;
|
|
207
|
+
if (!finalTexture || !animateOffset)
|
|
208
|
+
return;
|
|
209
|
+
const nextX = animatedOffsetRef.current[0] + ((_a = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
|
|
210
|
+
const nextY = animatedOffsetRef.current[1] + ((_b = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
|
|
211
|
+
animatedOffsetRef.current = [nextX, nextY];
|
|
212
|
+
finalTexture.offset.set(nextX, nextY);
|
|
213
|
+
});
|
|
176
214
|
const finalNormalMap = useMemo(() => {
|
|
177
215
|
if (!normalMapTexture)
|
|
178
216
|
return undefined;
|
|
@@ -181,21 +219,15 @@ function MaterialComponentView({ properties: rawProps }) {
|
|
|
181
219
|
t.needsUpdate = true;
|
|
182
220
|
return t;
|
|
183
221
|
}, [normalMapTexture]);
|
|
184
|
-
const normalScaleVec = useMemo(() => {
|
|
185
|
-
var _a, _b;
|
|
186
|
-
if (!finalNormalMap)
|
|
187
|
-
return undefined;
|
|
188
|
-
return new Vector2((_a = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _a !== void 0 ? _a : 1, (_b = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _b !== void 0 ? _b : 1);
|
|
189
|
-
}, [finalNormalMap, normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0], normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]]);
|
|
190
222
|
if (!properties) {
|
|
191
223
|
return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
|
|
192
224
|
}
|
|
193
|
-
const
|
|
194
|
-
const sharedProps = Object.assign({ map: finalTexture, side: resolvedSide }, materialProps);
|
|
225
|
+
const overrides = useMaterialOverrides();
|
|
226
|
+
const sharedProps = Object.assign(Object.assign({ map: finalTexture, side: resolvedSide }, materialProps), overrides);
|
|
195
227
|
if (materialType === 'basic') {
|
|
196
|
-
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps)
|
|
228
|
+
return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps));
|
|
197
229
|
}
|
|
198
|
-
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale:
|
|
230
|
+
return (_jsx("meshStandardNodeMaterial", Object.assign({}, sharedProps, { normalMap: finalNormalMap, normalScale: finalNormalMap ? [(_j = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _j !== void 0 ? _j : 1, (_k = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _k !== void 0 ? _k : 1] : undefined })));
|
|
199
231
|
}
|
|
200
232
|
const MaterialComponent = {
|
|
201
233
|
name: 'Material',
|
|
@@ -208,6 +240,9 @@ const MaterialComponent = {
|
|
|
208
240
|
wireframe: false,
|
|
209
241
|
transparent: false,
|
|
210
242
|
opacity: 1,
|
|
243
|
+
offset: [0, 0],
|
|
244
|
+
animateOffset: false,
|
|
245
|
+
offsetSpeed: [0, 0],
|
|
211
246
|
metalness: 0,
|
|
212
247
|
roughness: 1
|
|
213
248
|
},
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ModelPicker } from '../../assetviewer/page';
|
|
3
3
|
import { useContext, useMemo } from 'react';
|
|
4
4
|
import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput } from './Input';
|
|
5
|
-
import { useAssetRuntime } from '../
|
|
5
|
+
import { useAssetRuntime } from '../runtime';
|
|
6
6
|
import { EditorContext } from '../PrefabEditor';
|
|
7
7
|
import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
|
|
8
8
|
import { colors } from '../styles';
|
|
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
13
|
import { CapsuleCollider, RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
15
|
+
import { useAssetRuntime, useEntityRuntime } from "../runtime";
|
|
16
16
|
import { BooleanField, EventInput, FieldGroup, ListEditor, NumberField, SelectField, SelectInput, Vector3Field } from "./Input";
|
|
17
17
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
18
18
|
import { colors } from "../styles";
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
3
|
import { useHelper } from '@react-three/drei';
|
|
4
4
|
import { PointLightHelper } from 'three';
|
|
5
|
-
import { useEntityRuntime } from '../
|
|
5
|
+
import { useEntityRuntime } from '../runtime';
|
|
6
6
|
import { BooleanField, ColorField, NumberField } from './Input';
|
|
7
7
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from './lightUtils';
|
|
8
8
|
const pointLightDefaults = {
|
|
@@ -4,7 +4,7 @@ import { useThree } from '@react-three/fiber';
|
|
|
4
4
|
import { SoundPicker } from '../../assetviewer/page';
|
|
5
5
|
import { sound as soundManager } from '../../../helpers/SoundManager';
|
|
6
6
|
import { gameEvents } from '../GameEvents';
|
|
7
|
-
import { useAssetRuntime, useEntityRuntime } from '../
|
|
7
|
+
import { useAssetRuntime, useEntityRuntime } from '../runtime';
|
|
8
8
|
import { BooleanField, EventField, FieldGroup, FieldRenderer, ListEditor, NumberField, SelectField } from './Input';
|
|
9
9
|
import { colors } from '../styles';
|
|
10
10
|
import { AudioListener } from 'three';
|
|
@@ -212,7 +212,6 @@ const SoundComponent = {
|
|
|
212
212
|
name: 'Sound',
|
|
213
213
|
Editor: SoundComponentEditor,
|
|
214
214
|
View: SoundComponentView,
|
|
215
|
-
composition: 'sibling',
|
|
216
215
|
defaultProperties: {
|
|
217
216
|
eventName: '',
|
|
218
217
|
clips: [],
|
|
@@ -3,7 +3,7 @@ import { useHelper } from "@react-three/drei";
|
|
|
3
3
|
import { useRef, useEffect } from "react";
|
|
4
4
|
import { BooleanField, ColorField, Label, NumberField, Vector3Input } from "./Input";
|
|
5
5
|
import { SpotLightHelper } from "three";
|
|
6
|
-
import { useAssetRuntime, useEntityRuntime } from "../
|
|
6
|
+
import { useAssetRuntime, useEntityRuntime } from "../runtime";
|
|
7
7
|
import { useFrame } from "@react-three/fiber";
|
|
8
8
|
import { TexturePicker } from "../../assetviewer/page";
|
|
9
9
|
import { LightSection, ShadowBiasField, mergeWithDefaults } from "./lightUtils";
|
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
import GeometryComponent from './GeometryComponent';
|
|
2
1
|
import TransformComponent from './TransformComponent';
|
|
2
|
+
import GeometryComponent from './GeometryComponent';
|
|
3
|
+
import BufferGeometryComponent from './BufferGeometryComponent';
|
|
4
|
+
import ModelComponent from './ModelComponent';
|
|
5
|
+
import TextComponent from './TextComponent';
|
|
3
6
|
import MaterialComponent from './MaterialComponent';
|
|
4
7
|
import PhysicsComponent from './PhysicsComponent';
|
|
5
8
|
import SpotLightComponent from './SpotLightComponent';
|
|
6
9
|
import PointLightComponent from './PointLightComponent';
|
|
7
10
|
import DirectionalLightComponent from './DirectionalLightComponent';
|
|
8
11
|
import AmbientLightComponent from './AmbientLightComponent';
|
|
9
|
-
import ModelComponent from './ModelComponent';
|
|
10
|
-
import TextComponent from './TextComponent';
|
|
11
12
|
import EnvironmentComponent from './EnvironmentComponent';
|
|
12
13
|
import CameraComponent from './CameraComponent';
|
|
13
14
|
import ClickComponent from './ClickComponent';
|
|
14
15
|
import SoundComponent from './SoundComponent';
|
|
15
16
|
export const builtinComponents = [
|
|
16
|
-
GeometryComponent,
|
|
17
17
|
TransformComponent,
|
|
18
|
+
GeometryComponent,
|
|
19
|
+
BufferGeometryComponent,
|
|
20
|
+
ModelComponent,
|
|
21
|
+
TextComponent,
|
|
18
22
|
MaterialComponent,
|
|
19
23
|
PhysicsComponent,
|
|
20
24
|
SpotLightComponent,
|
|
21
25
|
PointLightComponent,
|
|
22
26
|
DirectionalLightComponent,
|
|
23
27
|
AmbientLightComponent,
|
|
24
|
-
ModelComponent,
|
|
25
|
-
TextComponent,
|
|
26
28
|
EnvironmentComponent,
|
|
27
29
|
CameraComponent,
|
|
28
30
|
ClickComponent,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Object3D, Texture } from "three";
|
|
3
|
+
import type { PrefabStoreApi } from "./prefabStore";
|
|
4
|
+
import { type EntityComponent, type Scene } from "./scene";
|
|
5
|
+
export interface AssetRuntime {
|
|
6
|
+
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
7
|
+
getModel: (path: string) => Object3D | null;
|
|
8
|
+
getTexture: (path: string) => Texture | null;
|
|
9
|
+
getSound: (path: string) => AudioBuffer | null;
|
|
10
|
+
getAssetRevision: () => string;
|
|
11
|
+
getObject: (id: string) => Object3D | null;
|
|
12
|
+
getRigidBody: (id: string) => any;
|
|
13
|
+
}
|
|
14
|
+
export interface AssetRuntimeContextValue extends AssetRuntime {
|
|
15
|
+
}
|
|
16
|
+
export interface EntityRuntime {
|
|
17
|
+
nodeId: string;
|
|
18
|
+
editMode?: boolean;
|
|
19
|
+
isSelected?: boolean;
|
|
20
|
+
getObject: <T extends Object3D = Object3D>() => T | null;
|
|
21
|
+
getRigidBody: <T = any>() => T | null;
|
|
22
|
+
}
|
|
23
|
+
export interface LiveRef<T> {
|
|
24
|
+
readonly current: T | null;
|
|
25
|
+
}
|
|
26
|
+
export type LiveObjectRef<T extends Object3D = Object3D> = LiveRef<T>;
|
|
27
|
+
export type LiveRigidBodyRef<T = any> = LiveRef<T>;
|
|
28
|
+
export declare const AssetRuntimeContext: import("react").Context<AssetRuntimeContextValue | null>;
|
|
29
|
+
export declare function useAssetRuntime(): AssetRuntime;
|
|
30
|
+
export declare function useEntityRuntime(): EntityRuntime;
|
|
31
|
+
export declare function useEntityObjectRef<T extends Object3D = Object3D>(): LiveRef<T>;
|
|
32
|
+
export declare function useEntityRigidBodyRef<T = any>(): LiveRef<T>;
|
|
33
|
+
export declare function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }: {
|
|
34
|
+
nodeId: string;
|
|
35
|
+
editMode?: boolean;
|
|
36
|
+
isSelected?: boolean;
|
|
37
|
+
children: ReactNode;
|
|
38
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
/** Runtime behaviour produced by `Component.create(ctx)`. All methods optional. */
|
|
40
|
+
export interface ComponentInstance {
|
|
41
|
+
start?(): void;
|
|
42
|
+
update?(dt: number): void;
|
|
43
|
+
destroy?(): void;
|
|
44
|
+
}
|
|
45
|
+
export interface ComponentRuntimeContext<TProperties = Record<string, any>> {
|
|
46
|
+
scene: Scene;
|
|
47
|
+
component: EntityComponent<TProperties>;
|
|
48
|
+
object: Object3D;
|
|
49
|
+
rigidBody: any;
|
|
50
|
+
}
|
|
51
|
+
export interface RuntimeEngine {
|
|
52
|
+
tick: (dt: number) => void;
|
|
53
|
+
setActive: (active: boolean) => void;
|
|
54
|
+
invalidate: () => void;
|
|
55
|
+
dispose: () => void;
|
|
56
|
+
}
|
|
57
|
+
export declare function createRuntimeEngine({ store, getObject, getRigidBody, }: {
|
|
58
|
+
store: PrefabStoreApi;
|
|
59
|
+
getObject: (id: string) => Object3D | null;
|
|
60
|
+
getRigidBody: (id: string) => any;
|
|
61
|
+
}): RuntimeEngine;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { getComponentDef } from "./components/ComponentRegistry";
|
|
4
|
+
import { createScene } from "./scene";
|
|
5
|
+
export const AssetRuntimeContext = createContext(null);
|
|
6
|
+
const EntityRuntimeContext = createContext(null);
|
|
7
|
+
export function useAssetRuntime() {
|
|
8
|
+
const ctx = useContext(AssetRuntimeContext);
|
|
9
|
+
if (!ctx)
|
|
10
|
+
throw new Error("useAssetRuntime must be used inside <PrefabRoot>");
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
export function useEntityRuntime() {
|
|
14
|
+
const ctx = useContext(EntityRuntimeContext);
|
|
15
|
+
if (!ctx)
|
|
16
|
+
throw new Error("useEntityRuntime must be used inside a component View rendered by <PrefabRoot>");
|
|
17
|
+
return ctx;
|
|
18
|
+
}
|
|
19
|
+
export function useEntityObjectRef() {
|
|
20
|
+
const { getObject } = useEntityRuntime();
|
|
21
|
+
return useMemo(() => ({ get current() { return getObject(); } }), [getObject]);
|
|
22
|
+
}
|
|
23
|
+
export function useEntityRigidBodyRef() {
|
|
24
|
+
const { getRigidBody } = useEntityRuntime();
|
|
25
|
+
return useMemo(() => ({ get current() { return getRigidBody(); } }), [getRigidBody]);
|
|
26
|
+
}
|
|
27
|
+
export function EntityRuntimeScope({ nodeId, editMode, isSelected, children, }) {
|
|
28
|
+
const asset = useContext(AssetRuntimeContext);
|
|
29
|
+
if (!asset)
|
|
30
|
+
throw new Error("EntityRuntimeScope must be used inside <PrefabRoot>");
|
|
31
|
+
const value = useMemo(() => ({
|
|
32
|
+
nodeId,
|
|
33
|
+
editMode,
|
|
34
|
+
isSelected,
|
|
35
|
+
getObject: () => asset.getObject(nodeId),
|
|
36
|
+
getRigidBody: () => asset.getRigidBody(nodeId),
|
|
37
|
+
}), [asset, editMode, isSelected, nodeId]);
|
|
38
|
+
return _jsx(EntityRuntimeContext.Provider, { value: value, children: children });
|
|
39
|
+
}
|
|
40
|
+
export function createRuntimeEngine({ store, getObject, getRigidBody, }) {
|
|
41
|
+
const scene = createScene({
|
|
42
|
+
getRootId: () => store.getState().rootId,
|
|
43
|
+
getNode: (id) => { var _a; return (_a = store.getState().nodesById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
44
|
+
getChildIds: (id) => { var _a; return (_a = store.getState().childIdsById[id]) !== null && _a !== void 0 ? _a : []; },
|
|
45
|
+
getParentId: (id) => { var _a; return (_a = store.getState().parentIdById[id]) !== null && _a !== void 0 ? _a : null; },
|
|
46
|
+
updateNode: (id, update) => store.getState().updateNode(id, update),
|
|
47
|
+
updateNodes: (updates) => {
|
|
48
|
+
store.getState().updateNodes(Object.entries(updates).map(([id, update]) => ({ id, update })));
|
|
49
|
+
},
|
|
50
|
+
addNode: (node, options) => {
|
|
51
|
+
var _a;
|
|
52
|
+
const parentId = (_a = options === null || options === void 0 ? void 0 : options.parentId) !== null && _a !== void 0 ? _a : store.getState().rootId;
|
|
53
|
+
store.getState().addChild(parentId, node);
|
|
54
|
+
return node.id;
|
|
55
|
+
},
|
|
56
|
+
removeNode: (id) => store.getState().deleteNode(id),
|
|
57
|
+
getObject,
|
|
58
|
+
getRigidBody,
|
|
59
|
+
});
|
|
60
|
+
// key = `${nodeId}:${componentKey}:${componentType}`
|
|
61
|
+
const instances = new Map();
|
|
62
|
+
let active = false;
|
|
63
|
+
let dirty = true;
|
|
64
|
+
let lastRevision = store.getState().revision;
|
|
65
|
+
const unsubscribe = store.subscribe((state) => {
|
|
66
|
+
if (state.revision === lastRevision)
|
|
67
|
+
return;
|
|
68
|
+
lastRevision = state.revision;
|
|
69
|
+
dirty = true;
|
|
70
|
+
});
|
|
71
|
+
function destroy(key) {
|
|
72
|
+
var _a, _b;
|
|
73
|
+
const record = instances.get(key);
|
|
74
|
+
if (!record)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
(_b = (_a = record.instance).destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`[runtime] destroy ${key}`, error);
|
|
81
|
+
}
|
|
82
|
+
instances.delete(key);
|
|
83
|
+
}
|
|
84
|
+
function sync() {
|
|
85
|
+
const state = store.getState();
|
|
86
|
+
const live = new Set();
|
|
87
|
+
let hasPendingMount = false;
|
|
88
|
+
const visit = (nodeId) => {
|
|
89
|
+
var _a, _b, _c;
|
|
90
|
+
const node = state.nodesById[nodeId];
|
|
91
|
+
if (!node)
|
|
92
|
+
return;
|
|
93
|
+
if (!node.disabled && node.components) {
|
|
94
|
+
for (const componentKey in node.components) {
|
|
95
|
+
const data = node.components[componentKey];
|
|
96
|
+
if (!(data === null || data === void 0 ? void 0 : data.type))
|
|
97
|
+
continue;
|
|
98
|
+
const def = getComponentDef(data.type);
|
|
99
|
+
if (!(def === null || def === void 0 ? void 0 : def.create))
|
|
100
|
+
continue;
|
|
101
|
+
const key = `${nodeId}:${componentKey}:${data.type}`;
|
|
102
|
+
live.add(key);
|
|
103
|
+
const object = getObject(nodeId);
|
|
104
|
+
if (!object) {
|
|
105
|
+
hasPendingMount = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const rigidBody = getRigidBody(nodeId);
|
|
109
|
+
const existing = instances.get(key);
|
|
110
|
+
if (existing && existing.object === object && existing.rigidBody === rigidBody) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (existing) {
|
|
114
|
+
destroy(key);
|
|
115
|
+
}
|
|
116
|
+
const entity = scene.find(nodeId);
|
|
117
|
+
const component = entity === null || entity === void 0 ? void 0 : entity.getComponent(componentKey);
|
|
118
|
+
if (!entity || !component)
|
|
119
|
+
continue;
|
|
120
|
+
let instance;
|
|
121
|
+
try {
|
|
122
|
+
instance = (_a = def.create({ scene, component, object, rigidBody })) !== null && _a !== void 0 ? _a : {};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error(`[runtime] create ${key}`, error);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
instances.set(key, { instance, object, rigidBody });
|
|
129
|
+
try {
|
|
130
|
+
(_b = instance.start) === null || _b === void 0 ? void 0 : _b.call(instance);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.error(`[runtime] start ${key}`, error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const childId of (_c = state.childIdsById[nodeId]) !== null && _c !== void 0 ? _c : [])
|
|
138
|
+
visit(childId);
|
|
139
|
+
};
|
|
140
|
+
visit(state.rootId);
|
|
141
|
+
for (const key of Array.from(instances.keys())) {
|
|
142
|
+
if (!live.has(key))
|
|
143
|
+
destroy(key);
|
|
144
|
+
}
|
|
145
|
+
dirty = hasPendingMount;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
tick(dt) {
|
|
149
|
+
if (!active)
|
|
150
|
+
return;
|
|
151
|
+
if (dirty)
|
|
152
|
+
sync();
|
|
153
|
+
for (const [key, record] of instances) {
|
|
154
|
+
if (!record.instance.update)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
record.instance.update(dt);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error(`[runtime] update ${key}`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
setActive(nextActive) {
|
|
165
|
+
if (active === nextActive)
|
|
166
|
+
return;
|
|
167
|
+
active = nextActive;
|
|
168
|
+
dirty = true;
|
|
169
|
+
if (!active) {
|
|
170
|
+
for (const key of Array.from(instances.keys()))
|
|
171
|
+
destroy(key);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
invalidate() {
|
|
175
|
+
dirty = true;
|
|
176
|
+
},
|
|
177
|
+
dispose() {
|
|
178
|
+
active = false;
|
|
179
|
+
for (const key of Array.from(instances.keys()))
|
|
180
|
+
destroy(key);
|
|
181
|
+
unsubscribe();
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -45,6 +45,14 @@ export interface Scene {
|
|
|
45
45
|
};
|
|
46
46
|
add: (node: GameObject, options?: SpawnOptions) => Entity;
|
|
47
47
|
remove: (id: string) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Coalesce many entity / component updates into a single store revision.
|
|
50
|
+
* Entity `update`/`set`, `EntityComponent` `set`/`update`, `addComponent`,
|
|
51
|
+
* and `removeComponent` calls inside the callback are buffered and flushed
|
|
52
|
+
* as one batched write. `add`, `remove`, and `destroy` (structural tree ops)
|
|
53
|
+
* still commit immediately.
|
|
54
|
+
*/
|
|
55
|
+
batch: (fn: () => void) => void;
|
|
48
56
|
}
|
|
49
57
|
interface SceneAdapter {
|
|
50
58
|
getRootId: () => string;
|
|
@@ -57,6 +57,42 @@ function setValueAtPath(value, path, nextValue) {
|
|
|
57
57
|
return cloneBranch(value, 0);
|
|
58
58
|
}
|
|
59
59
|
export function createScene(adapter) {
|
|
60
|
+
let batchBuffer = null;
|
|
61
|
+
function routeUpdate(id, update) {
|
|
62
|
+
if (batchBuffer) {
|
|
63
|
+
const prev = batchBuffer.get(id);
|
|
64
|
+
batchBuffer.set(id, prev ? (node) => update(prev(node)) : update);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
adapter.updateNode(id, update);
|
|
68
|
+
}
|
|
69
|
+
function routeUpdates(updates) {
|
|
70
|
+
if (batchBuffer) {
|
|
71
|
+
for (const id in updates)
|
|
72
|
+
routeUpdate(id, updates[id]);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
adapter.updateNodes(updates);
|
|
76
|
+
}
|
|
77
|
+
function batch(fn) {
|
|
78
|
+
if (batchBuffer) {
|
|
79
|
+
fn();
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
batchBuffer = new Map();
|
|
83
|
+
try {
|
|
84
|
+
fn();
|
|
85
|
+
if (batchBuffer.size > 0) {
|
|
86
|
+
const updates = {};
|
|
87
|
+
for (const [id, update] of batchBuffer)
|
|
88
|
+
updates[id] = update;
|
|
89
|
+
adapter.updateNodes(updates);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
batchBuffer = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
60
96
|
const getNode = (id) => {
|
|
61
97
|
if (!adapter.getNode(id))
|
|
62
98
|
missingNode(id);
|
|
@@ -76,7 +112,7 @@ export function createScene(adapter) {
|
|
|
76
112
|
return getValueAtPath(component.properties, path);
|
|
77
113
|
},
|
|
78
114
|
set(path, value) {
|
|
79
|
-
|
|
115
|
+
routeUpdate(entityId, node => {
|
|
80
116
|
var _a;
|
|
81
117
|
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
82
118
|
if (!component) {
|
|
@@ -86,7 +122,7 @@ export function createScene(adapter) {
|
|
|
86
122
|
});
|
|
87
123
|
},
|
|
88
124
|
update(update) {
|
|
89
|
-
|
|
125
|
+
routeUpdate(entityId, node => {
|
|
90
126
|
var _a;
|
|
91
127
|
const component = (_a = node.components) === null || _a === void 0 ? void 0 : _a[componentKey];
|
|
92
128
|
if (!component) {
|
|
@@ -128,10 +164,10 @@ export function createScene(adapter) {
|
|
|
128
164
|
return (_b = (_a = adapter.getRigidBody) === null || _a === void 0 ? void 0 : _a.call(adapter, id)) !== null && _b !== void 0 ? _b : null;
|
|
129
165
|
},
|
|
130
166
|
set(data) {
|
|
131
|
-
|
|
167
|
+
routeUpdate(id, () => data);
|
|
132
168
|
},
|
|
133
169
|
update(update) {
|
|
134
|
-
|
|
170
|
+
routeUpdate(id, update);
|
|
135
171
|
},
|
|
136
172
|
getComponent(name) {
|
|
137
173
|
const node = adapter.getNode(id);
|
|
@@ -145,11 +181,11 @@ export function createScene(adapter) {
|
|
|
145
181
|
},
|
|
146
182
|
addComponent(type, properties) {
|
|
147
183
|
const key = type.toLowerCase();
|
|
148
|
-
|
|
184
|
+
routeUpdate(id, node => (Object.assign(Object.assign({}, node), { components: Object.assign(Object.assign({}, node.components), { [key]: createComponentData(type, properties) }) })));
|
|
149
185
|
return createComponent(id, key, type);
|
|
150
186
|
},
|
|
151
187
|
removeComponent(name) {
|
|
152
|
-
|
|
188
|
+
routeUpdate(id, node => {
|
|
153
189
|
var _a;
|
|
154
190
|
const entry = findComponentEntry(node, name);
|
|
155
191
|
if (!entry)
|
|
@@ -169,13 +205,13 @@ export function createScene(adapter) {
|
|
|
169
205
|
if (!mutate) {
|
|
170
206
|
return;
|
|
171
207
|
}
|
|
172
|
-
|
|
208
|
+
routeUpdate(idOrUpdates, mutate);
|
|
173
209
|
return;
|
|
174
210
|
}
|
|
175
211
|
if (Object.keys(idOrUpdates).length === 0) {
|
|
176
212
|
return;
|
|
177
213
|
}
|
|
178
|
-
|
|
214
|
+
routeUpdates(idOrUpdates);
|
|
179
215
|
}
|
|
180
216
|
return {
|
|
181
217
|
get rootId() {
|
|
@@ -196,5 +232,6 @@ export function createScene(adapter) {
|
|
|
196
232
|
remove(id) {
|
|
197
233
|
adapter.removeNode(id);
|
|
198
234
|
},
|
|
235
|
+
batch,
|
|
199
236
|
};
|
|
200
237
|
}
|