react-three-game 0.0.107 → 0.0.109
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 +12 -5
- package/dist/editor.d.ts +22 -0
- package/dist/editor.js +15 -0
- package/dist/plugins/crashcat/CrashcatPhysicsComponent.js +75 -47
- package/dist/plugins/crashcat/CrashcatRagdoll.d.ts +58 -0
- package/dist/plugins/crashcat/CrashcatRagdoll.js +410 -0
- package/dist/plugins/crashcat/CrashcatRuntime.js +19 -15
- package/dist/plugins/crashcat/index.d.ts +1 -0
- package/dist/plugins/crashcat/index.js +1 -0
- package/dist/tools/assetviewer/page.js +4 -4
- package/dist/tools/prefabeditor/EditorContext.d.ts +36 -0
- package/dist/tools/prefabeditor/EditorContext.js +17 -0
- package/dist/tools/prefabeditor/EditorTree.js +6 -3
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +2 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +18 -6
- package/dist/tools/prefabeditor/EditorUI.js +1 -1
- package/dist/tools/prefabeditor/GameEvents.d.ts +1 -0
- package/dist/tools/prefabeditor/PrefabEditor.d.ts +5 -37
- package/dist/tools/prefabeditor/PrefabEditor.js +41 -43
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +5 -27
- package/dist/tools/prefabeditor/PrefabRoot.js +133 -78
- package/dist/tools/prefabeditor/SceneContext.d.ts +28 -0
- package/dist/tools/prefabeditor/SceneContext.js +14 -0
- package/dist/tools/prefabeditor/assetRuntime.d.ts +4 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +16 -1
- package/dist/tools/prefabeditor/components/ModelComponent.js +1 -1
- package/dist/tools/prefabeditor/components/PrefabRefComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/PrefabRefComponent.js +72 -0
- package/dist/tools/prefabeditor/components/TextComponent.js +8 -5
- package/dist/tools/prefabeditor/components/TransformComponent.js +1 -1
- package/dist/tools/prefabeditor/components/index.d.ts +1 -0
- package/dist/tools/prefabeditor/components/index.js +10 -0
- package/dist/tools/prefabeditor/components/runtime.d.ts +4 -0
- package/dist/tools/prefabeditor/components/runtime.js +372 -0
- package/dist/tools/prefabeditor/modelPrefab.d.ts +3 -0
- package/dist/tools/prefabeditor/modelPrefab.js +44 -11
- package/dist/tools/prefabeditor/prefab.d.ts +5 -4
- package/dist/tools/prefabeditor/prefab.js +47 -29
- package/dist/tools/prefabeditor/prefabStore.d.ts +1 -1
- package/dist/tools/prefabeditor/prefabStore.js +5 -1
- package/dist/tools/prefabeditor/runtimeUtils.d.ts +10 -0
- package/dist/tools/prefabeditor/runtimeUtils.js +30 -0
- package/dist/tools/prefabeditor/utils.d.ts +7 -8
- package/dist/tools/prefabeditor/utils.js +63 -36
- package/dist/viewer.d.ts +22 -0
- package/dist/viewer.js +14 -0
- package/package.json +22 -20
- package/dist/index.d.ts +0 -40
- package/dist/index.js +0 -32
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// biome-ignore assist/source/organizeImports: <in order of display in the editor>
|
|
2
2
|
import TransformComponent from "./TransformComponent";
|
|
3
|
+
import PrefabRefComponent from "./PrefabRefComponent";
|
|
3
4
|
import GeometryComponent from "./GeometryComponent";
|
|
4
5
|
import BufferGeometryComponent from "./BufferGeometryComponent";
|
|
5
6
|
import ModelComponent from "./ModelComponent";
|
|
@@ -14,6 +15,7 @@ import EnvironmentComponent from "./EnvironmentComponent";
|
|
|
14
15
|
import CameraComponent from "./CameraComponent";
|
|
15
16
|
import SoundComponent from "./SoundComponent";
|
|
16
17
|
import DataComponent from "./DataComponent";
|
|
18
|
+
import { registerComponent } from "./ComponentRegistry";
|
|
17
19
|
// this controls the order of components in the editor, and also which components are available to add
|
|
18
20
|
export const builtinComponents = [
|
|
19
21
|
TransformComponent,
|
|
@@ -35,4 +37,12 @@ export const builtinComponents = [
|
|
|
35
37
|
CameraComponent,
|
|
36
38
|
SoundComponent,
|
|
37
39
|
DataComponent,
|
|
40
|
+
PrefabRefComponent,
|
|
38
41
|
];
|
|
42
|
+
let didRegisterBuiltinComponents = false;
|
|
43
|
+
export function registerBuiltinComponents() {
|
|
44
|
+
if (didRegisterBuiltinComponents)
|
|
45
|
+
return;
|
|
46
|
+
builtinComponents.forEach(registerComponent);
|
|
47
|
+
didRegisterBuiltinComponents = true;
|
|
48
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Environment } from "@react-three/drei";
|
|
3
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
|
+
import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { AudioListener, ClampToEdgeWrapping, DoubleSide, LinearFilter, LinearMipmapLinearFilter, Mesh, NoColorSpace, RepeatWrapping, SRGBColorSpace } from "three";
|
|
6
|
+
import { Text } from "three-text/three/react";
|
|
7
|
+
import { assetRef, assetRefs, getComponentDef, registerComponent } from "./ComponentRegistry";
|
|
8
|
+
import { useAssetRuntime, useNode } from "../assetRuntime";
|
|
9
|
+
import { gameEvents } from "../GameEvents";
|
|
10
|
+
import { withBasePath } from "../runtimeUtils";
|
|
11
|
+
const PrefabRoot = lazy(() => import("../PrefabRoot"));
|
|
12
|
+
const RuntimeText = Text;
|
|
13
|
+
function GeometryView({ properties }) {
|
|
14
|
+
const { geometryType } = properties;
|
|
15
|
+
const args = Array.isArray(properties.args) ? properties.args : [];
|
|
16
|
+
const geometryKey = `${geometryType !== null && geometryType !== void 0 ? geometryType : "box"}:${JSON.stringify(args)}`;
|
|
17
|
+
switch (geometryType) {
|
|
18
|
+
case "sphere":
|
|
19
|
+
return _jsx("sphereGeometry", { args: args }, geometryKey);
|
|
20
|
+
case "plane":
|
|
21
|
+
return _jsx("planeGeometry", { args: args }, geometryKey);
|
|
22
|
+
case "cylinder":
|
|
23
|
+
return _jsx("cylinderGeometry", { args: args }, geometryKey);
|
|
24
|
+
case "box":
|
|
25
|
+
default:
|
|
26
|
+
return _jsx("boxGeometry", { args: (args.length ? args : [1, 1, 1]) }, geometryKey);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const DEFAULT_TRIANGLE_POSITIONS = [0, 0, 0, 1, 0, 0, 0, 1, 0];
|
|
30
|
+
const DEFAULT_TRIANGLE_INDICES = [0, 1, 2];
|
|
31
|
+
const DEFAULT_TRIANGLE_UVS = [0, 0, 1, 0, 0, 1];
|
|
32
|
+
function numberArray(value, fallback) {
|
|
33
|
+
return Array.isArray(value) && value.every(entry => typeof entry === "number" && Number.isFinite(entry))
|
|
34
|
+
? value
|
|
35
|
+
: fallback;
|
|
36
|
+
}
|
|
37
|
+
function indexArray(indices) {
|
|
38
|
+
if (indices.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
return Math.max(...indices) > 65535 ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
41
|
+
}
|
|
42
|
+
function BufferGeometryView({ properties }) {
|
|
43
|
+
const positions = numberArray(properties.positions, DEFAULT_TRIANGLE_POSITIONS);
|
|
44
|
+
const indices = numberArray(properties.indices, DEFAULT_TRIANGLE_INDICES);
|
|
45
|
+
const normals = numberArray(properties.normals, []);
|
|
46
|
+
const uvs = numberArray(properties.uvs, DEFAULT_TRIANGLE_UVS);
|
|
47
|
+
const hasNormals = normals.length === positions.length;
|
|
48
|
+
const hasUvs = uvs.length >= (positions.length / 3) * 2;
|
|
49
|
+
const indicesArray = indexArray(indices);
|
|
50
|
+
return (_jsxs("bufferGeometry", { onUpdate: (geometry) => {
|
|
51
|
+
if (properties.computeVertexNormals !== false && !hasNormals)
|
|
52
|
+
geometry.computeVertexNormals();
|
|
53
|
+
geometry.computeBoundingBox();
|
|
54
|
+
geometry.computeBoundingSphere();
|
|
55
|
+
}, children: [_jsx("bufferAttribute", { attach: "attributes-position", args: [new Float32Array(positions), 3] }), indicesArray ? _jsx("bufferAttribute", { attach: "index", args: [indicesArray, 1] }) : null, hasNormals ? _jsx("bufferAttribute", { attach: "attributes-normal", args: [new Float32Array(normals), 3] }) : null, hasUvs ? _jsx("bufferAttribute", { attach: "attributes-uv", args: [new Float32Array(uvs), 2] }) : null] }));
|
|
56
|
+
}
|
|
57
|
+
function ModelView({ properties, children }) {
|
|
58
|
+
const { getModel } = useAssetRuntime();
|
|
59
|
+
const sourceModel = properties.filename ? getModel(properties.filename) : null;
|
|
60
|
+
const clonedModel = useMemo(() => {
|
|
61
|
+
if (!sourceModel || !properties.filename || properties.instanced)
|
|
62
|
+
return null;
|
|
63
|
+
const clone = sourceModel.clone();
|
|
64
|
+
clone.traverse((object) => {
|
|
65
|
+
if (object instanceof Mesh) {
|
|
66
|
+
object.castShadow = true;
|
|
67
|
+
object.receiveShadow = true;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return clone;
|
|
71
|
+
}, [properties.filename, properties.instanced, sourceModel]);
|
|
72
|
+
return clonedModel ? _jsx("primitive", { object: clonedModel, children: children }) : _jsx(_Fragment, { children: children });
|
|
73
|
+
}
|
|
74
|
+
function configureTexture(texture, properties, colorSpace) {
|
|
75
|
+
var _a, _b, _c, _d;
|
|
76
|
+
if (!texture)
|
|
77
|
+
return;
|
|
78
|
+
const repeat = Boolean(properties.repeat);
|
|
79
|
+
const repeatCount = Array.isArray(properties.repeatCount) ? properties.repeatCount : [1, 1];
|
|
80
|
+
const offset = Array.isArray(properties.offset) ? properties.offset : [0, 0];
|
|
81
|
+
texture.wrapS = texture.wrapT = repeat ? RepeatWrapping : ClampToEdgeWrapping;
|
|
82
|
+
texture.repeat.set(Number((_a = repeatCount[0]) !== null && _a !== void 0 ? _a : 1), Number((_b = repeatCount[1]) !== null && _b !== void 0 ? _b : 1));
|
|
83
|
+
texture.offset.set(Number((_c = offset[0]) !== null && _c !== void 0 ? _c : 0), Number((_d = offset[1]) !== null && _d !== void 0 ? _d : 0));
|
|
84
|
+
texture.colorSpace = colorSpace;
|
|
85
|
+
texture.generateMipmaps = properties.generateMipmaps !== false;
|
|
86
|
+
texture.minFilter = (properties.minFilter === "LinearFilter" ? LinearFilter : LinearMipmapLinearFilter);
|
|
87
|
+
texture.magFilter = LinearFilter;
|
|
88
|
+
texture.needsUpdate = true;
|
|
89
|
+
}
|
|
90
|
+
function useConfiguredTexture(path, properties, colorSpace) {
|
|
91
|
+
const { getTexture } = useAssetRuntime();
|
|
92
|
+
const sourceTexture = typeof path === "string" ? getTexture(path) : null;
|
|
93
|
+
const texture = useMemo(() => sourceTexture === null || sourceTexture === void 0 ? void 0 : sourceTexture.clone(), [sourceTexture]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
configureTexture(texture, properties, colorSpace);
|
|
96
|
+
}, [properties, texture, colorSpace]);
|
|
97
|
+
return texture !== null && texture !== void 0 ? texture : null;
|
|
98
|
+
}
|
|
99
|
+
function MaterialView({ properties }) {
|
|
100
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
101
|
+
const materialType = (_a = properties.materialType) !== null && _a !== void 0 ? _a : "standard";
|
|
102
|
+
const colorMap = useConfiguredTexture(properties.texture, properties, SRGBColorSpace);
|
|
103
|
+
const normalMap = useConfiguredTexture(properties.normalMapTexture, properties, NoColorSpace);
|
|
104
|
+
const offsetSpeed = Array.isArray(properties.offsetSpeed) ? properties.offsetSpeed : [0, 0];
|
|
105
|
+
const animateOffset = Boolean(properties.animateOffset);
|
|
106
|
+
useFrame((_state, delta) => {
|
|
107
|
+
var _a, _b;
|
|
108
|
+
if (!animateOffset)
|
|
109
|
+
return;
|
|
110
|
+
for (const texture of [colorMap, normalMap]) {
|
|
111
|
+
if (!texture)
|
|
112
|
+
continue;
|
|
113
|
+
texture.offset.x += Number((_a = offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
|
|
114
|
+
texture.offset.y += Number((_b = offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const sharedProps = {
|
|
118
|
+
attach: (_b = properties.attach) !== null && _b !== void 0 ? _b : "material",
|
|
119
|
+
color: (_c = properties.color) !== null && _c !== void 0 ? _c : "#ffffff",
|
|
120
|
+
opacity: (_d = properties.opacity) !== null && _d !== void 0 ? _d : 1,
|
|
121
|
+
transparent: Boolean(properties.transparent),
|
|
122
|
+
wireframe: Boolean(properties.wireframe),
|
|
123
|
+
toneMapped: properties.toneMapped !== false,
|
|
124
|
+
side: DoubleSide,
|
|
125
|
+
map: colorMap,
|
|
126
|
+
};
|
|
127
|
+
if (materialType === "basic")
|
|
128
|
+
return _jsx("meshBasicMaterial", Object.assign({}, sharedProps));
|
|
129
|
+
if (materialType === "sprite") {
|
|
130
|
+
return (_jsx("spriteMaterial", Object.assign({}, sharedProps, { rotation: (_e = properties.rotation) !== null && _e !== void 0 ? _e : 0, sizeAttenuation: properties.sizeAttenuation !== false, depthTest: properties.depthTest !== false, depthWrite: Boolean(properties.depthWrite) })));
|
|
131
|
+
}
|
|
132
|
+
const normalScale = Array.isArray(properties.normalScale) ? properties.normalScale : [1, 1];
|
|
133
|
+
return (_jsx("meshStandardMaterial", Object.assign({}, sharedProps, { metalness: (_f = properties.metalness) !== null && _f !== void 0 ? _f : 0, roughness: (_g = properties.roughness) !== null && _g !== void 0 ? _g : 1, normalMap: normalMap, normalScale: normalMap ? [Number((_h = normalScale[0]) !== null && _h !== void 0 ? _h : 1), Number((_j = normalScale[1]) !== null && _j !== void 0 ? _j : 1)] : undefined })));
|
|
134
|
+
}
|
|
135
|
+
function TextView({ properties, children, basePath = "" }) {
|
|
136
|
+
const { text = "", font, size, depth, width, align, color } = properties;
|
|
137
|
+
const textContent = String(text || "");
|
|
138
|
+
const resolvedFont = typeof font === "string" ? withBasePath(basePath, font) : undefined;
|
|
139
|
+
const [offset, setOffset] = useState([0, 0, 0]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
Text.setHarfBuzzPath(withBasePath(basePath, "/fonts/hb.wasm"));
|
|
142
|
+
}, [basePath]);
|
|
143
|
+
if (!textContent)
|
|
144
|
+
return null;
|
|
145
|
+
const textProps = {
|
|
146
|
+
size: size,
|
|
147
|
+
depth: depth,
|
|
148
|
+
layout: { align: align, width: width },
|
|
149
|
+
color: color,
|
|
150
|
+
onLoad: (_geometry, info) => {
|
|
151
|
+
const bounds = info === null || info === void 0 ? void 0 : info.planeBounds;
|
|
152
|
+
if (!bounds)
|
|
153
|
+
return;
|
|
154
|
+
const x = align === "center" ? -(bounds.min.x + bounds.max.x) / 2 : align === "right" ? -bounds.max.x : -bounds.min.x;
|
|
155
|
+
setOffset([x, -(bounds.min.y + bounds.max.y) / 2, 0]);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
if (resolvedFont)
|
|
159
|
+
textProps.font = resolvedFont;
|
|
160
|
+
return (_jsxs("group", { position: offset, children: [_jsx(RuntimeText, Object.assign({}, textProps, { children: textContent })), children] }));
|
|
161
|
+
}
|
|
162
|
+
function merge(defaults, properties) {
|
|
163
|
+
return Object.assign(Object.assign({}, defaults), properties);
|
|
164
|
+
}
|
|
165
|
+
function AmbientLightView({ properties, children }) {
|
|
166
|
+
const props = merge({ color: "#ffffff", intensity: 1 }, properties);
|
|
167
|
+
return _jsxs(_Fragment, { children: [_jsx("ambientLight", { color: props.color, intensity: props.intensity }), children] });
|
|
168
|
+
}
|
|
169
|
+
function PointLightView({ properties, children }) {
|
|
170
|
+
const props = merge({ color: "#ffffff", intensity: 1, distance: 0, decay: 2 }, properties);
|
|
171
|
+
return (_jsx("pointLight", { color: props.color, intensity: props.intensity, distance: props.distance, decay: props.decay, children: children }));
|
|
172
|
+
}
|
|
173
|
+
function DirectionalLightView({ properties, children }) {
|
|
174
|
+
const props = merge({ color: "#ffffff", intensity: 1, castShadow: false }, properties);
|
|
175
|
+
return _jsx("directionalLight", { color: props.color, intensity: props.intensity, castShadow: Boolean(props.castShadow), children: children });
|
|
176
|
+
}
|
|
177
|
+
function SpotLightView({ properties, children }) {
|
|
178
|
+
const props = merge({ color: "#ffffff", intensity: 1, angle: Math.PI / 3, penumbra: 0, distance: 0, decay: 2, castShadow: false }, properties);
|
|
179
|
+
return (_jsx("spotLight", { color: props.color, intensity: props.intensity, angle: props.angle, penumbra: props.penumbra, distance: props.distance, decay: props.decay, castShadow: Boolean(props.castShadow), children: children }));
|
|
180
|
+
}
|
|
181
|
+
function CameraView({ properties, children }) {
|
|
182
|
+
var _a, _b, _c;
|
|
183
|
+
const projection = (_a = properties.projection) !== null && _a !== void 0 ? _a : "perspective";
|
|
184
|
+
if (projection === "orthographic") {
|
|
185
|
+
return _jsx("orthographicCamera", { zoom: (_b = properties.zoom) !== null && _b !== void 0 ? _b : 1, children: children });
|
|
186
|
+
}
|
|
187
|
+
return _jsx("perspectiveCamera", { fov: (_c = properties.fov) !== null && _c !== void 0 ? _c : 50, children: children });
|
|
188
|
+
}
|
|
189
|
+
function EnvironmentView({ properties, children }) {
|
|
190
|
+
var _a, _b;
|
|
191
|
+
const { getAssetRevision } = useAssetRuntime();
|
|
192
|
+
const intensity = (_a = properties.intensity) !== null && _a !== void 0 ? _a : 1;
|
|
193
|
+
const resolution = (_b = properties.resolution) !== null && _b !== void 0 ? _b : 256;
|
|
194
|
+
return (_jsx(Environment, { background: true, environmentIntensity: intensity, resolution: resolution, frames: 1, children: children }, `${getAssetRevision()}::${intensity}::${resolution}`));
|
|
195
|
+
}
|
|
196
|
+
function PrefabRefView({ properties, children, basePath = "" }) {
|
|
197
|
+
const [loadedPrefab, setLoadedPrefab] = useState(null);
|
|
198
|
+
const resolvedUrl = properties.url ? withBasePath(basePath, properties.url) : "";
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (!resolvedUrl)
|
|
201
|
+
return undefined;
|
|
202
|
+
let cancelled = false;
|
|
203
|
+
void fetch(resolvedUrl)
|
|
204
|
+
.then(response => response.json())
|
|
205
|
+
.then(data => {
|
|
206
|
+
if (!cancelled)
|
|
207
|
+
setLoadedPrefab(data);
|
|
208
|
+
})
|
|
209
|
+
.catch(error => console.warn("[PrefabRef] Failed to load:", resolvedUrl, error));
|
|
210
|
+
return () => {
|
|
211
|
+
cancelled = true;
|
|
212
|
+
};
|
|
213
|
+
}, [resolvedUrl]);
|
|
214
|
+
return (_jsxs(_Fragment, { children: [loadedPrefab ? (_jsx(Suspense, { fallback: null, children: _jsx(PrefabRoot, { data: loadedPrefab, editMode: false, basePath: basePath }) })) : null, children] }));
|
|
215
|
+
}
|
|
216
|
+
let sharedAudioListener = null;
|
|
217
|
+
function getSharedAudioListener() {
|
|
218
|
+
if (!sharedAudioListener)
|
|
219
|
+
sharedAudioListener = new AudioListener();
|
|
220
|
+
return sharedAudioListener;
|
|
221
|
+
}
|
|
222
|
+
function clampRange(min, max, fallbackMin, fallbackMax) {
|
|
223
|
+
const safeMin = Number.isFinite(min) ? Number(min) : fallbackMin;
|
|
224
|
+
const safeMax = Number.isFinite(max) ? Number(max) : fallbackMax;
|
|
225
|
+
return safeMin <= safeMax ? [safeMin, safeMax] : [safeMax, safeMin];
|
|
226
|
+
}
|
|
227
|
+
function sampleRange(min, max) {
|
|
228
|
+
return min + Math.random() * (max - min);
|
|
229
|
+
}
|
|
230
|
+
function getPitchValue(properties) {
|
|
231
|
+
if (properties.randomizePitch) {
|
|
232
|
+
const [pitchFloor, pitchCeiling] = clampRange(properties.minPitch, properties.maxPitch, 0.96, 1.04);
|
|
233
|
+
return sampleRange(pitchFloor, pitchCeiling);
|
|
234
|
+
}
|
|
235
|
+
return Number.isFinite(properties.pitch) ? Number(properties.pitch) : 1;
|
|
236
|
+
}
|
|
237
|
+
function getVolumeValue(properties) {
|
|
238
|
+
if (properties.randomizeVolume) {
|
|
239
|
+
const [volumeFloor, volumeCeiling] = clampRange(properties.minVolume, properties.maxVolume, 0.9, 1);
|
|
240
|
+
return sampleRange(volumeFloor, volumeCeiling);
|
|
241
|
+
}
|
|
242
|
+
return Number.isFinite(properties.volume) ? Number(properties.volume) : 1;
|
|
243
|
+
}
|
|
244
|
+
function resolveClipPaths({ clips, clipMode }) {
|
|
245
|
+
const paths = (clips !== null && clips !== void 0 ? clips : []).map(clip => clip.trim()).filter(Boolean);
|
|
246
|
+
return { paths, mode: paths.length > 0 ? clipMode !== null && clipMode !== void 0 ? clipMode : "random" : "single" };
|
|
247
|
+
}
|
|
248
|
+
function pickClip(paths, mode, sequenceIndexRef) {
|
|
249
|
+
if (paths.length <= 1 || mode === "single")
|
|
250
|
+
return paths[0];
|
|
251
|
+
if (mode === "sequence") {
|
|
252
|
+
const clip = paths[sequenceIndexRef.current % paths.length];
|
|
253
|
+
sequenceIndexRef.current += 1;
|
|
254
|
+
return clip;
|
|
255
|
+
}
|
|
256
|
+
return paths[Math.floor(Math.random() * paths.length)];
|
|
257
|
+
}
|
|
258
|
+
function payloadMatchesNode(nodeId, payload) {
|
|
259
|
+
if (!nodeId || !payload || typeof payload !== "object")
|
|
260
|
+
return true;
|
|
261
|
+
const eventPayload = payload;
|
|
262
|
+
const relatedNodeIds = [
|
|
263
|
+
eventPayload.nodeId,
|
|
264
|
+
eventPayload.sourceEntityId,
|
|
265
|
+
eventPayload.sourceNodeId,
|
|
266
|
+
eventPayload.targetEntityId,
|
|
267
|
+
eventPayload.targetNodeId,
|
|
268
|
+
eventPayload.instanceEntityId,
|
|
269
|
+
].filter((value) => typeof value === "string");
|
|
270
|
+
return relatedNodeIds.length > 0 ? relatedNodeIds.includes(nodeId) : true;
|
|
271
|
+
}
|
|
272
|
+
function playBufferedAudio(audio, buffer, properties) {
|
|
273
|
+
void audio.listener.context.resume();
|
|
274
|
+
if (audio.isPlaying)
|
|
275
|
+
audio.stop();
|
|
276
|
+
audio.setBuffer(buffer);
|
|
277
|
+
audio.setLoop(Boolean(properties.loop));
|
|
278
|
+
audio.setPlaybackRate(getPitchValue(properties));
|
|
279
|
+
audio.setVolume(getVolumeValue(properties));
|
|
280
|
+
audio.play();
|
|
281
|
+
}
|
|
282
|
+
function SoundView({ properties, children }) {
|
|
283
|
+
const { getSound } = useAssetRuntime();
|
|
284
|
+
const { editMode, nodeId } = useNode();
|
|
285
|
+
const { camera } = useThree();
|
|
286
|
+
const { eventName, autoplay = false, positional = false, refDistance = 1, maxDistance = 24, rolloffFactor = 1, distanceModel = "inverse" } = properties;
|
|
287
|
+
const sequenceIndexRef = useRef(0);
|
|
288
|
+
const listenerRef = useRef(null);
|
|
289
|
+
const positionalAudioRef = useRef(null);
|
|
290
|
+
const { paths, mode } = resolveClipPaths(properties);
|
|
291
|
+
if (!listenerRef.current)
|
|
292
|
+
listenerRef.current = getSharedAudioListener();
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
var _a;
|
|
295
|
+
const listener = listenerRef.current;
|
|
296
|
+
if (!listener)
|
|
297
|
+
return;
|
|
298
|
+
if (listener.parent !== camera) {
|
|
299
|
+
(_a = listener.parent) === null || _a === void 0 ? void 0 : _a.remove(listener);
|
|
300
|
+
camera.add(listener);
|
|
301
|
+
}
|
|
302
|
+
return () => {
|
|
303
|
+
if (listener.parent === camera)
|
|
304
|
+
camera.remove(listener);
|
|
305
|
+
};
|
|
306
|
+
}, [camera]);
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
const audio = positionalAudioRef.current;
|
|
309
|
+
if (!audio)
|
|
310
|
+
return;
|
|
311
|
+
audio.setRefDistance(positional ? refDistance : Math.max(refDistance, 1));
|
|
312
|
+
audio.setMaxDistance(positional ? maxDistance : 1000000);
|
|
313
|
+
audio.setRolloffFactor(positional ? rolloffFactor : 0);
|
|
314
|
+
audio.setDistanceModel(positional ? distanceModel : "inverse");
|
|
315
|
+
}, [distanceModel, maxDistance, positional, refDistance, rolloffFactor]);
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (editMode || paths.length === 0 || !eventName)
|
|
318
|
+
return;
|
|
319
|
+
return gameEvents.on(eventName, (payload) => {
|
|
320
|
+
if (!payloadMatchesNode(nodeId, payload))
|
|
321
|
+
return;
|
|
322
|
+
const clip = pickClip(paths, mode, sequenceIndexRef);
|
|
323
|
+
const audio = positionalAudioRef.current;
|
|
324
|
+
const buffer = clip ? getSound(clip) : null;
|
|
325
|
+
if (audio && buffer)
|
|
326
|
+
playBufferedAudio(audio, buffer, properties);
|
|
327
|
+
});
|
|
328
|
+
}, [editMode, eventName, getSound, mode, nodeId, paths, properties]);
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
if (editMode || !autoplay || paths.length === 0)
|
|
331
|
+
return;
|
|
332
|
+
const clip = pickClip(paths, mode, sequenceIndexRef);
|
|
333
|
+
const audio = positionalAudioRef.current;
|
|
334
|
+
const buffer = clip ? getSound(clip) : null;
|
|
335
|
+
if (audio && buffer)
|
|
336
|
+
playBufferedAudio(audio, buffer, properties);
|
|
337
|
+
return () => {
|
|
338
|
+
if (audio === null || audio === void 0 ? void 0 : audio.isPlaying)
|
|
339
|
+
audio.stop();
|
|
340
|
+
};
|
|
341
|
+
}, [autoplay, editMode, getSound, mode, paths, properties]);
|
|
342
|
+
return (_jsxs(_Fragment, { children: [listenerRef.current ? _jsx("positionalAudio", { ref: positionalAudioRef, args: [listenerRef.current] }) : null, children] }));
|
|
343
|
+
}
|
|
344
|
+
const runtimeComponents = [
|
|
345
|
+
{ name: "Transform", disableSiblingComposition: true, defaultProperties: {} },
|
|
346
|
+
{ name: "Geometry", disableSiblingComposition: "geometry", View: GeometryView, defaultProperties: { geometryType: "box", args: [1, 1, 1], emitClickEvent: false, clickEventName: "" } },
|
|
347
|
+
{ name: "BufferGeometry", disableSiblingComposition: "geometry", View: BufferGeometryView, defaultProperties: { positions: DEFAULT_TRIANGLE_POSITIONS, indices: DEFAULT_TRIANGLE_INDICES, normals: [], uvs: DEFAULT_TRIANGLE_UVS, groups: [], computeVertexNormals: true, emitClickEvent: false, clickEventName: "" } },
|
|
348
|
+
{ name: "Model", View: ModelView, defaultProperties: {}, getAssetRefs: properties => assetRefs(assetRef("model", properties.filename)) },
|
|
349
|
+
{ name: "Sprite", disableSiblingComposition: true, defaultProperties: { center: [0.5, 0.5], emitClickEvent: false, clickEventName: "node:click" } },
|
|
350
|
+
{ name: "Text", View: TextView, defaultProperties: { text: "Hello World", color: "#ffffff", size: 1, depth: 0.05, align: "center" } },
|
|
351
|
+
{ name: "Material", View: MaterialView, defaultProperties: { attach: "material", materialType: "standard", color: "#ffffff", toneMapped: true, wireframe: false, transparent: false, opacity: 1, sizeAttenuation: true, offset: [0, 0], animateOffset: false, offsetSpeed: [0, 0], metalness: 0, roughness: 1 }, getAssetRefs: properties => assetRefs(assetRef("texture", properties.texture), assetRef("texture", properties.normalMapTexture)) },
|
|
352
|
+
{ name: "SpotLight", View: SpotLightView, defaultProperties: {}, getAssetRefs: properties => assetRefs(assetRef("texture", properties.map)) },
|
|
353
|
+
{ name: "PointLight", View: PointLightView, defaultProperties: {} },
|
|
354
|
+
{ name: "DirectionalLight", View: DirectionalLightView, defaultProperties: {} },
|
|
355
|
+
{ name: "AmbientLight", View: AmbientLightView, defaultProperties: {} },
|
|
356
|
+
{ name: "Environment", disableSiblingComposition: true, View: EnvironmentView, defaultProperties: {} },
|
|
357
|
+
{ name: "Camera", disableSiblingComposition: true, View: CameraView, defaultProperties: { projection: "perspective", fov: 50, near: 0.1, zoom: 1, far: 1000 } },
|
|
358
|
+
{ name: "Sound", View: SoundView, defaultProperties: { eventName: "", autoplay: false, loop: false, clips: [], clipMode: "single", positional: false, refDistance: 1, maxDistance: 24, rolloffFactor: 1, distanceModel: "inverse", pitch: 1, randomizePitch: false, minPitch: 0.96, maxPitch: 1.04, volume: 1, randomizeVolume: false, minVolume: 0.9, maxVolume: 1 }, getAssetRefs: (properties) => Array.isArray(properties.clips) ? properties.clips.filter((clip) => typeof clip === "string").map(path => ({ type: "sound", path })) : [] },
|
|
359
|
+
{ name: "Data", defaultProperties: { values: {} } },
|
|
360
|
+
{ name: "PrefabRef", View: PrefabRefView, defaultProperties: { url: "" } },
|
|
361
|
+
];
|
|
362
|
+
let didRegisterRuntimeComponents = false;
|
|
363
|
+
export function registerRuntimeComponents() {
|
|
364
|
+
if (didRegisterRuntimeComponents)
|
|
365
|
+
return;
|
|
366
|
+
runtimeComponents.forEach(component => {
|
|
367
|
+
if (!getComponentDef(component.name))
|
|
368
|
+
registerComponent(component);
|
|
369
|
+
});
|
|
370
|
+
didRegisterRuntimeComponents = true;
|
|
371
|
+
}
|
|
372
|
+
export { runtimeComponents };
|
|
@@ -6,9 +6,12 @@ export interface DecomposeModelOptions {
|
|
|
6
6
|
idPrefix?: string;
|
|
7
7
|
/** Include invisible Three objects in the generated prefab tree. */
|
|
8
8
|
includeInvisible?: boolean;
|
|
9
|
+
/** Create CrashcatPhysics components from Blender-style mesh name suffixes. */
|
|
10
|
+
inferCollisionMeshes?: boolean;
|
|
9
11
|
/** Return a serializable texture ref for embedded or externally loaded textures. */
|
|
10
12
|
getTexturePath?: (texture: Texture, usage: 'map' | 'normalMap') => string | null | undefined;
|
|
11
13
|
}
|
|
14
|
+
export declare function hasCollisionMeshConventions(object: Object3D, enabled?: boolean): boolean;
|
|
12
15
|
/**
|
|
13
16
|
* Converts a live Three object hierarchy into prefab JSON nodes made from
|
|
14
17
|
* Transform, BufferGeometry, and Material components.
|
|
@@ -52,6 +52,27 @@ function serializeGeometry(geometry) {
|
|
|
52
52
|
computeVertexNormals: normals.length === 0,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
|
+
function getCollisionMeshConvention(name, enabled) {
|
|
56
|
+
if (!enabled)
|
|
57
|
+
return null;
|
|
58
|
+
const match = name.match(/^(.*)_(colonly|col)(?:\.\d+)?$/i);
|
|
59
|
+
if (!match)
|
|
60
|
+
return null;
|
|
61
|
+
const [, baseName, suffix] = match;
|
|
62
|
+
return {
|
|
63
|
+
displayName: baseName || name,
|
|
64
|
+
renderMesh: suffix.toLowerCase() === 'col',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function hasCollisionMeshConventions(object, enabled = true) {
|
|
68
|
+
let hasConvention = false;
|
|
69
|
+
object.traverse(child => {
|
|
70
|
+
if (hasConvention)
|
|
71
|
+
return;
|
|
72
|
+
hasConvention = child instanceof Mesh && getCollisionMeshConvention(child.name, enabled) != null;
|
|
73
|
+
});
|
|
74
|
+
return hasConvention;
|
|
75
|
+
}
|
|
55
76
|
function getSideName(side) {
|
|
56
77
|
if (side === BackSide)
|
|
57
78
|
return 'BackSide';
|
|
@@ -138,17 +159,19 @@ function createTransformComponent(object) {
|
|
|
138
159
|
},
|
|
139
160
|
};
|
|
140
161
|
}
|
|
141
|
-
function createNode(object, idPrefix, children = [], components = {}) {
|
|
162
|
+
function createNode(object, idPrefix, children = [], components = {}, options = {}) {
|
|
163
|
+
var _a, _b;
|
|
142
164
|
return {
|
|
143
165
|
id: createId(idPrefix),
|
|
144
|
-
name: object.name || object.type,
|
|
145
|
-
hidden: object.visible === false ? true : undefined,
|
|
166
|
+
name: ((_a = options.name) !== null && _a !== void 0 ? _a : object.name) || object.type,
|
|
167
|
+
hidden: (_b = options.hidden) !== null && _b !== void 0 ? _b : (object.visible === false ? true : undefined),
|
|
146
168
|
components: Object.assign({ transform: createTransformComponent(object) }, components),
|
|
147
169
|
children,
|
|
148
170
|
};
|
|
149
171
|
}
|
|
150
172
|
function decomposeObject(object, options) {
|
|
151
|
-
|
|
173
|
+
const collisionMesh = getCollisionMeshConvention(object.name, options.inferCollisionMeshes);
|
|
174
|
+
if (!options.includeInvisible && !object.visible && !collisionMesh)
|
|
152
175
|
return null;
|
|
153
176
|
const childNodes = object.children
|
|
154
177
|
.map(child => decomposeObject(child, options))
|
|
@@ -161,20 +184,30 @@ function decomposeObject(object, options) {
|
|
|
161
184
|
result[part.key] = serializeMaterial(part.material, part.attach, options);
|
|
162
185
|
return result;
|
|
163
186
|
}, {});
|
|
164
|
-
return createNode(object, options.idPrefix, childNodes, Object.assign({ geometry: {
|
|
187
|
+
return createNode(object, options.idPrefix, childNodes, Object.assign(Object.assign({ geometry: {
|
|
165
188
|
type: 'BufferGeometry',
|
|
166
|
-
properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
|
|
167
|
-
} }, materialComponents)
|
|
189
|
+
properties: Object.assign(Object.assign({}, serializeGeometry(object.geometry)), { visible: collisionMesh ? collisionMesh.renderMesh : object.visible, castShadow: object.castShadow, receiveShadow: object.receiveShadow }),
|
|
190
|
+
} }, materialComponents), (collisionMesh ? {
|
|
191
|
+
crashcatPhysics: {
|
|
192
|
+
type: 'CrashcatPhysics',
|
|
193
|
+
properties: {
|
|
194
|
+
type: 'fixed',
|
|
195
|
+
colliders: 'trimesh',
|
|
196
|
+
sensor: false,
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
} : null)), collisionMesh ? { name: collisionMesh.displayName, hidden: false } : undefined);
|
|
168
200
|
}
|
|
169
201
|
/**
|
|
170
202
|
* Converts a live Three object hierarchy into prefab JSON nodes made from
|
|
171
203
|
* Transform, BufferGeometry, and Material components.
|
|
172
204
|
*/
|
|
173
205
|
export function decomposeModelToPrefabNodes(object, options = {}) {
|
|
174
|
-
var _a, _b, _c, _d, _e;
|
|
175
|
-
return (
|
|
206
|
+
var _a, _b, _c, _d, _e, _f;
|
|
207
|
+
return (_e = decomposeObject(object, {
|
|
176
208
|
idPrefix: (_a = options.idPrefix) !== null && _a !== void 0 ? _a : 'model',
|
|
177
209
|
includeInvisible: (_b = options.includeInvisible) !== null && _b !== void 0 ? _b : false,
|
|
178
|
-
|
|
179
|
-
|
|
210
|
+
inferCollisionMeshes: (_c = options.inferCollisionMeshes) !== null && _c !== void 0 ? _c : true,
|
|
211
|
+
getTexturePath: (_d = options.getTexturePath) !== null && _d !== void 0 ? _d : (() => undefined),
|
|
212
|
+
})) !== null && _e !== void 0 ? _e : createNode(object, (_f = options.idPrefix) !== null && _f !== void 0 ? _f : 'model');
|
|
180
213
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ComponentData, GameObject, Prefab } from
|
|
2
|
-
export type PrefabNodeRecord = Omit<GameObject,
|
|
1
|
+
import type { ComponentData, GameObject, Prefab } from "./types";
|
|
2
|
+
export type PrefabNodeRecord = Omit<GameObject, "children">;
|
|
3
3
|
export type PrefabAssetRefCounts = Record<string, number>;
|
|
4
4
|
export interface PrefabState {
|
|
5
5
|
prefabId?: string;
|
|
@@ -24,12 +24,13 @@ export declare function createEmptyNode(name?: string): GameObject;
|
|
|
24
24
|
export declare function createEmptyPrefab(): Prefab;
|
|
25
25
|
export declare function createModelNode(filename: string, name?: string): GameObject;
|
|
26
26
|
export declare function createImageNode(texturePath: string, name?: string): GameObject;
|
|
27
|
+
export declare function createPackedPrefabNode(url: string): GameObject;
|
|
27
28
|
export declare function normalizePrefab(prefab: Prefab): PrefabState;
|
|
28
29
|
export declare function createPrefabPatch(state: PrefabState, patch: Partial<PrefabState>, nextAssetRefCounts?: PrefabAssetRefCounts): Partial<PrefabState>;
|
|
29
|
-
export declare function denormalizePrefab(state: Pick<PrefabState,
|
|
30
|
+
export declare function denormalizePrefab(state: Pick<PrefabState, "prefabId" | "prefabName" | "rootId" | "nodesById" | "childIdsById">): Prefab;
|
|
30
31
|
export declare function collectSubtreeIds(id: string, childIdsById: Record<string, string[]>): string[];
|
|
31
32
|
export declare function insertSubtree(node: GameObject, parentId: string | null, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): void;
|
|
32
|
-
export declare function cloneSubtree(id: string, parentId: string | null, source: Pick<PrefabState,
|
|
33
|
+
export declare function cloneSubtree(id: string, parentId: string | null, source: Pick<PrefabState, "nodesById" | "childIdsById">, nodesById: Record<string, PrefabNodeRecord>, childIdsById: Record<string, string[]>, parentIdById: Record<string, string | null>): string | null;
|
|
33
34
|
export declare function isDescendant(id: string, potentialAncestorId: string, parentIdById: Record<string, string | null>): boolean;
|
|
34
35
|
export declare function updateAssetRefsForNodeChange(assetRefCounts: PrefabAssetRefCounts, currentNode: PrefabNodeRecord, nextNode: PrefabNodeRecord): PrefabAssetRefCounts;
|
|
35
36
|
export declare function collectSubtreeAssetRefs(node: GameObject): string[];
|