react-three-game 0.0.94 → 0.0.96

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.
@@ -1,6 +1,6 @@
1
- import React from "react";
2
- import { ThreeEvent } from '@react-three/fiber';
3
- import { Object3D, Group } from "three";
1
+ import type { ReactNode } from "react";
2
+ import type { ThreeEvent } from '@react-three/fiber';
3
+ import type { Object3D } from "three";
4
4
  export type RepeatAxisConfig = {
5
5
  axis: 'x' | 'y' | 'z';
6
6
  count: number;
@@ -8,7 +8,7 @@ export type RepeatAxisConfig = {
8
8
  };
9
9
  export declare const DEFAULT_REPEAT_AXES: RepeatAxisConfig[];
10
10
  export declare function normalizeRepeatAxes(value: unknown): RepeatAxisConfig[];
11
- export declare function getRepeatAxesFromModelProperties(properties: Record<string, any>): RepeatAxisConfig[];
11
+ export declare function getRepeatAxesFromModelProperties(properties: Record<string, unknown>): RepeatAxisConfig[];
12
12
  export type InstanceData = {
13
13
  id: string;
14
14
  sourceId: string;
@@ -20,7 +20,7 @@ export type InstanceData = {
20
20
  meshPath: string;
21
21
  };
22
22
  export declare function GameInstanceProvider({ children, models, onSelect, onClick, registerRef, selectedId, editMode }: {
23
- children: React.ReactNode;
23
+ children: ReactNode;
24
24
  models: {
25
25
  [filename: string]: Object3D;
26
26
  };
@@ -31,7 +31,7 @@ export declare function GameInstanceProvider({ children, models, onSelect, onCli
31
31
  editMode?: boolean;
32
32
  }): import("react/jsx-runtime").JSX.Element;
33
33
  export declare function useInstanceCheck(id: string): boolean;
34
- export declare const GameInstance: React.ForwardRefExoticComponent<{
34
+ export declare function GameInstance({ id, sourceId, modelUrl, locked, position, rotation, scale, visible, onClick: _onClick, }: {
35
35
  id: string;
36
36
  sourceId?: string;
37
37
  modelUrl: string;
@@ -41,4 +41,4 @@ export declare const GameInstance: React.ForwardRefExoticComponent<{
41
41
  scale: [number, number, number];
42
42
  visible?: boolean;
43
43
  onClick?: (event: ThreeEvent<PointerEvent>, nodeId: string, object: Object3D | null) => void;
44
- } & React.RefAttributes<Group<import("three").Object3DEventMap>>>;
44
+ }): null;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { createContext, useContext, useMemo, useRef, useState, useEffect } from "react";
2
+ import { createContext, useContext, useMemo, useRef, useState, useEffect } from "react";
3
3
  import { Merged, useHelper } from '@react-three/drei';
4
4
  import { Mesh, Matrix4, BoxHelper } from "three";
5
5
  import { useStore } from "zustand";
6
6
  import { createStore } from "zustand/vanilla";
7
7
  import { usePointerEvents } from "./usePointerEvents";
8
8
  export const DEFAULT_REPEAT_AXES = [{ axis: 'x', count: 1, offset: 1 }];
9
+ const EMPTY_INSTANCE_STORE = createInstanceRegistryStore();
9
10
  export function normalizeRepeatAxes(value) {
10
11
  if (!Array.isArray(value)) {
11
12
  return DEFAULT_REPEAT_AXES;
@@ -14,14 +15,15 @@ export function normalizeRepeatAxes(value) {
14
15
  const normalized = value.reduce((result, entry) => {
15
16
  if (!entry || typeof entry !== 'object')
16
17
  return result;
17
- const axisValue = entry.axis;
18
+ const record = entry;
19
+ const axisValue = record.axis;
18
20
  if (axisValue !== 'x' && axisValue !== 'y' && axisValue !== 'z')
19
21
  return result;
20
22
  if (seen.has(axisValue))
21
23
  return result;
22
24
  seen.add(axisValue);
23
- const countValue = Number(entry.count);
24
- const offsetValue = Number(entry.offset);
25
+ const countValue = Number(record.count);
26
+ const offsetValue = Number(record.offset);
25
27
  result.push({
26
28
  axis: axisValue,
27
29
  count: Number.isFinite(countValue) ? Math.max(1, Math.floor(countValue)) : 1,
@@ -141,7 +143,7 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
141
143
  const rootInverse = new Matrix4().copy(model.matrixWorld).invert();
142
144
  let partIndex = 0;
143
145
  model.traverse((obj) => {
144
- if (obj.isMesh) {
146
+ if (obj instanceof Mesh) {
145
147
  // Clone geometry and bake relative transform
146
148
  const geom = obj.geometry.clone();
147
149
  geom.applyMatrix4(obj.matrixWorld.clone().premultiply(rootInverse));
@@ -157,7 +159,9 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
157
159
  // Cleanup geometries when models change
158
160
  useEffect(() => {
159
161
  return () => {
160
- Object.values(flatMeshes).forEach(mesh => mesh.geometry.dispose());
162
+ Object.values(flatMeshes).forEach(mesh => {
163
+ mesh.geometry.dispose();
164
+ });
161
165
  };
162
166
  }, [flatMeshes]);
163
167
  const instances = useMemo(() => Object.values(instancesById), [instancesById]);
@@ -194,12 +198,16 @@ export function GameInstanceProvider({ children, models, onSelect, onClick, regi
194
198
  })] }));
195
199
  }
196
200
  function InstancedGroup({ modelKey, group, partCount, instancesMap, onSelect, onClick, registerRef, selectedId, editMode }) {
197
- const InstanceComponents = useMemo(() => Array.from({ length: partCount }, (_, i) => instancesMap[`${modelKey}__${i}`]).filter(Boolean), [instancesMap, modelKey, partCount]);
201
+ const instanceEntries = useMemo(() => Array.from({ length: partCount }, (_, i) => {
202
+ const partKey = `${modelKey}__${i}`;
203
+ const Component = instancesMap[partKey];
204
+ return Component ? { partKey, Component } : null;
205
+ }).filter((entry) => Boolean(entry)), [instancesMap, modelKey, partCount]);
198
206
  const visibleInstances = useMemo(() => group.instances.filter(instance => instance.visible !== false), [group.instances]);
199
- return (_jsx(_Fragment, { children: visibleInstances.map(inst => (_jsx(InstanceGroupItem, { instance: inst, InstanceComponents: InstanceComponents, onSelect: onSelect, onClick: onClick, registerRef: registerRef, selectedId: selectedId, editMode: editMode }, inst.id))) }));
207
+ return (_jsx(_Fragment, { children: visibleInstances.map(inst => (_jsx(InstanceGroupItem, { instance: inst, instanceEntries: instanceEntries, onSelect: onSelect, onClick: onClick, registerRef: registerRef, selectedId: selectedId, editMode: editMode }, inst.id))) }));
200
208
  }
201
209
  // Individual instance item with its own click state
202
- function InstanceGroupItem({ instance, InstanceComponents, onSelect, onClick, registerRef, selectedId, editMode }) {
210
+ function InstanceGroupItem({ instance, instanceEntries, onSelect, onClick, registerRef, selectedId, editMode }) {
203
211
  const groupRef = useRef(null);
204
212
  const isLocked = Boolean(instance.locked);
205
213
  const isSelected = selectedId === instance.id || selectedId === instance.sourceId;
@@ -217,24 +225,26 @@ function InstanceGroupItem({ instance, InstanceComponents, onSelect, onClick, re
217
225
  },
218
226
  });
219
227
  // Use BoxHelper when object is selected in edit mode
220
- useHelper(editMode && isSelected ? groupRef : null, BoxHelper, 'cyan');
228
+ const helperTarget = editMode && isSelected && groupRef.current
229
+ ? { current: groupRef.current }
230
+ : null;
231
+ useHelper(helperTarget, BoxHelper, 'cyan');
221
232
  useEffect(() => {
222
233
  if (editMode)
223
234
  return;
224
235
  registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, groupRef.current);
225
236
  return () => registerRef === null || registerRef === void 0 ? void 0 : registerRef(instance.id, null);
226
237
  }, [editMode, instance.id, registerRef]);
227
- return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children: InstanceComponents.map((Instance, i) => _jsx(Instance, {}, i)) })));
238
+ return (_jsx("group", Object.assign({ ref: groupRef, position: instance.position, rotation: instance.rotation, scale: instance.scale }, pointerHandlers, { children: instanceEntries.map(({ partKey, Component }) => _jsx(Component, {}, partKey)) })));
228
239
  }
229
240
  export function useInstanceCheck(id) {
241
+ var _a;
230
242
  const ctx = useContext(GameInstanceContext);
231
- return ctx ? useStore(ctx.store, state => Boolean(state.instancesById[id] || state.sourceInstanceIdsById[id])) : false;
243
+ const store = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.store) !== null && _a !== void 0 ? _a : EMPTY_INSTANCE_STORE;
244
+ return useStore(store, state => Boolean(state.instancesById[id] || state.sourceInstanceIdsById[id]));
232
245
  }
233
- export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked = false, position, rotation, scale, visible = true, onClick: _onClick, }, ref) => {
246
+ export function GameInstance({ id, sourceId, modelUrl, locked = false, position, rotation, scale, visible = true, onClick: _onClick, }) {
234
247
  const ctx = useContext(GameInstanceContext);
235
- const [positionX, positionY, positionZ] = position;
236
- const [rotationX, rotationY, rotationZ] = rotation;
237
- const [scaleX, scaleY, scaleZ] = scale;
238
248
  const instance = useMemo(() => ({
239
249
  id,
240
250
  sourceId: sourceId !== null && sourceId !== void 0 ? sourceId : id,
@@ -244,22 +254,7 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
244
254
  position,
245
255
  rotation,
246
256
  scale,
247
- }), [
248
- id,
249
- sourceId,
250
- locked,
251
- visible,
252
- modelUrl,
253
- positionX,
254
- positionY,
255
- positionZ,
256
- rotationX,
257
- rotationY,
258
- rotationZ,
259
- scaleX,
260
- scaleY,
261
- scaleZ,
262
- ]);
257
+ }), [id, sourceId, locked, visible, modelUrl, position, rotation, scale]);
263
258
  useEffect(() => {
264
259
  if (!ctx)
265
260
  return;
@@ -269,6 +264,6 @@ export const GameInstance = React.forwardRef(({ id, sourceId, modelUrl, locked =
269
264
  return () => {
270
265
  removeInstance(instance.id);
271
266
  };
272
- }, [ctx === null || ctx === void 0 ? void 0 : ctx.store, instance]);
267
+ }, [ctx, instance]);
273
268
  return null;
274
- });
269
+ }
@@ -1,5 +1,5 @@
1
1
  import GameCanvas from "../../shared/GameCanvas";
2
- import { Prefab } from "./types";
2
+ import type { Prefab } from "./types";
3
3
  import { PrefabEditorMode, type Scene } from "./PrefabRoot";
4
4
  import type { ExportGLBOptions } from "./utils";
5
5
  export interface PrefabEditorRef extends Scene {
@@ -79,6 +79,10 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
79
79
  const transformControlsRef = useRef(null);
80
80
  const onChangeRef = useRef(onChange);
81
81
  const isEditMode = mode === PrefabEditorMode.Edit;
82
+ const detachTransformControls = useCallback(() => {
83
+ var _a;
84
+ (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
85
+ }, []);
82
86
  const getPrefab = useCallback(() => denormalizePrefab(prefabStore.getState()), [prefabStore]);
83
87
  const getNode = useCallback((nodeId) => { var _a; return (_a = prefabStore.getState().nodesById[nodeId]) !== null && _a !== void 0 ? _a : null; }, [prefabStore]);
84
88
  const getRoot = useCallback(() => { var _a, _b; return (_b = (_a = prefabRootRef.current) === null || _a === void 0 ? void 0 : _a.root) !== null && _b !== void 0 ? _b : null; }, []);
@@ -158,13 +162,13 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
158
162
  updateMode(initialMode);
159
163
  }, [initialMode, updateMode]);
160
164
  const loadPrefab = useCallback((prefab, options) => {
161
- var _a, _b;
162
- (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
165
+ var _a;
166
+ detachTransformControls();
163
167
  const before = prefabStore.getState();
164
168
  prefabStore.getState().replacePrefab(prefab);
165
169
  const after = prefabStore.getState();
166
170
  if (after !== before && (options === null || options === void 0 ? void 0 : options.notifyChange) !== false) {
167
- (_b = onChangeRef.current) === null || _b === void 0 ? void 0 : _b.call(onChangeRef, prefab);
171
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, prefab);
168
172
  }
169
173
  if (options === null || options === void 0 ? void 0 : options.resetHistory) {
170
174
  setSelectedId(null);
@@ -175,7 +179,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
175
179
  else {
176
180
  setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
177
181
  }
178
- }, [prefabStore]);
182
+ }, [detachTransformControls, prefabStore]);
179
183
  useEffect(() => {
180
184
  if (initialPrefab)
181
185
  loadPrefab(initialPrefab, { resetHistory: true, notifyChange: false });
@@ -205,17 +209,25 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
205
209
  const importPrefab = useCallback((prefab) => {
206
210
  add(regenerateIds(prefab.root));
207
211
  }, [add]);
208
- const applyHistory = (index) => {
209
- var _a, _b;
210
- (_a = transformControlsRef.current) === null || _a === void 0 ? void 0 : _a.detach();
212
+ const applyHistory = useCallback((index) => {
213
+ var _a;
214
+ detachTransformControls();
211
215
  prefabStore.getState().replacePrefab(history[index]);
212
- (_b = onChangeRef.current) === null || _b === void 0 ? void 0 : _b.call(onChangeRef, history[index]);
216
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, history[index]);
213
217
  historyIndexRef.current = index;
214
218
  setHistoryIndex(index);
215
219
  setSelectedId(prev => prev && prefabStore.getState().nodesById[prev] ? prev : null);
216
- };
217
- const undo = () => historyIndex > 0 && applyHistory(historyIndex - 1);
218
- const redo = () => historyIndex < history.length - 1 && applyHistory(historyIndex + 1);
220
+ }, [detachTransformControls, history, prefabStore]);
221
+ const undo = useCallback(() => {
222
+ if (historyIndex > 0) {
223
+ applyHistory(historyIndex - 1);
224
+ }
225
+ }, [applyHistory, historyIndex]);
226
+ const redo = useCallback(() => {
227
+ if (historyIndex < history.length - 1) {
228
+ applyHistory(historyIndex + 1);
229
+ }
230
+ }, [applyHistory, history.length, historyIndex]);
219
231
  useEffect(() => {
220
232
  if (!isEditMode)
221
233
  return;
@@ -233,7 +245,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
233
245
  };
234
246
  window.addEventListener('keydown', handleKeyDown);
235
247
  return () => window.removeEventListener('keydown', handleKeyDown);
236
- }, [isEditMode, historyIndex, history]);
248
+ }, [isEditMode, redo, undo]);
237
249
  const handleScreenshot = useCallback(() => {
238
250
  const canvas = canvasRef.current;
239
251
  if (!canvas)
@@ -383,7 +395,7 @@ const PrefabEditor = forwardRef(({ basePath, initialPrefab, mode: initialMode =
383
395
  }
384
396
  (_d = canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed) === null || _d === void 0 ? void 0 : _d.call(canvasProps, event);
385
397
  }
386
- : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) });
398
+ : canvasProps === null || canvasProps === void 0 ? void 0 : canvasProps.onPointerMissed, children: [content, isEditMode ? _jsx(SelectionHelper, { object: transformObject }) : null, isEditMode && (_jsxs(_Fragment, { children: [_jsx(MapControls, { ref: controlsRef, enableDamping: false, makeDefault: true }), transformObject && (_jsx(TransformControls, { ref: transformControlsRef, object: transformObject, mode: transformMode, space: transformMode === "translate" ? "world" : "local", onObjectChange: handleTransformChange, translationSnap: positionSnap > 0 ? positionSnap : undefined, rotationSnap: rotationSnap > 0 ? rotationSnap : undefined, scaleSnap: scaleSnap > 0 ? scaleSnap : undefined }, `transform-${selectedId}-${transformMode}-${positionSnap}-${rotationSnap}-${scaleSnap}`))] }))] })), showUI && (_jsxs(_Fragment, { children: [_jsxs("div", { style: toolbar.panel, children: [_jsx("button", { type: "button", style: base.btn, onClick: toggleMode, children: isEditMode ? "▶" : "⏸" }), uiPlugins] }), isEditMode && (_jsx(EditorUI, { selectedId: selectedId, setSelectedId: setSelection, getPrefab: getPrefab, onReplacePrefab: (prefab) => loadPrefab(prefab, { resetHistory: true }), onImportPrefab: importPrefab, basePath: basePath, onUndo: undo, onRedo: redo, canUndo: historyIndex > 0, canRedo: historyIndex < history.length - 1 }))] }))] }) }) });
387
399
  });
388
400
  PrefabEditor.displayName = "PrefabEditor";
389
401
  export default PrefabEditor;
@@ -1,8 +1,9 @@
1
- import { Matrix4, Object3D, Texture } from "three";
2
- import { ThreeEvent } from "@react-three/fiber";
3
- import { GameObject as GameObjectType, Prefab } from "./types";
4
- import { LoadedModels } from "../dragdrop";
5
- import { PrefabStoreApi } from "./prefabStore";
1
+ import { Matrix4 } from "three";
2
+ import type { Object3D, Texture } from "three";
3
+ import type { ThreeEvent } from "@react-three/fiber";
4
+ import type { GameObject as GameObjectType, Prefab } from "./types";
5
+ import type { LoadedModels } from "../dragdrop";
6
+ import type { PrefabStoreApi } from "./prefabStore";
6
7
  export declare enum PrefabEditorMode {
7
8
  Edit = "edit",
8
9
  Play = "play"
@@ -11,14 +11,14 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
14
- import { Euler, Matrix4, } from "three";
14
+ import { Euler, Matrix4 } from "three";
15
15
  import { useStore } from "zustand";
16
16
  import { useClickValid } from "./useClickValid";
17
17
  import { findComponent, getNodeUserData } from "./types";
18
18
  import { getComponentDef, getComponentAssetRefs, registerComponent } from "./components/ComponentRegistry";
19
19
  import { builtinComponents } from "./components";
20
20
  import { loadModel, loadSound, loadTexture } from "../dragdrop";
21
- import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties, useInstanceCheck } from "./InstanceProvider";
21
+ import { GameInstance, GameInstanceProvider, getRepeatAxesFromModelProperties } from "./InstanceProvider";
22
22
  import { composeTransform, decompose } from "./utils";
23
23
  import { createPrefabStore, PrefabStoreProvider, usePrefabChildIds, usePrefabNode, usePrefabRootId } from "./prefabStore";
24
24
  import { AssetRuntimeContext, NodeScope } from "./assetRuntime";
@@ -29,6 +29,14 @@ const IDENTITY = new Matrix4();
29
29
  const EMPTY_MODELS = {};
30
30
  const EMPTY_TEXTURES = {};
31
31
  const EMPTY_SOUNDS = {};
32
+ const EMPTY_NODE_COMPONENTS = {
33
+ geometry: undefined,
34
+ material: undefined,
35
+ model: undefined,
36
+ sprite: undefined,
37
+ clickEventName: null,
38
+ composition: [],
39
+ };
32
40
  /** Resolve a relative or absolute asset file path against a base path. */
33
41
  function resolveAssetPath(basePath, file) {
34
42
  if (file.startsWith("http://") || file.startsWith("https://"))
@@ -84,6 +92,9 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
84
92
  throw new Error("PrefabRoot requires either a `data` or `store` prop");
85
93
  });
86
94
  const resolvedStore = store !== null && store !== void 0 ? store : ownedStore;
95
+ if (!resolvedStore) {
96
+ throw new Error("PrefabRoot requires either a `data` or `store` prop");
97
+ }
87
98
  const usesOwnedStore = resolvedStore === ownedStore;
88
99
  const rootId = useStore(resolvedStore, state => state.rootId);
89
100
  const assetManifestKey = useStore(resolvedStore, state => state.assetManifestKey);
@@ -155,6 +166,7 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
155
166
  }
156
167
  }, [data, resolvedStore, usesOwnedStore]);
157
168
  useEffect(() => {
169
+ void assetManifestKey;
158
170
  const syncAssets = (snapshot = resolvedStore.getState()) => {
159
171
  const modelsToLoad = new Set();
160
172
  const texturesToLoad = new Set();
@@ -187,23 +199,34 @@ export const PrefabRoot = forwardRef(({ editMode, data, store, selectedId, onSel
187
199
  }
188
200
  });
189
201
  };
190
- modelsToLoad.forEach(file => loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
191
- if (result.success && result.model)
192
- setModels(m => (Object.assign(Object.assign({}, m), { [file]: result.model })));
193
- return result;
194
- })));
195
- texturesToLoad.forEach(file => loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
196
- if (result.success && result.texture)
197
- setTextures(t => (Object.assign(Object.assign({}, t), { [file]: result.texture })));
198
- return result;
199
- })));
200
- soundsToLoad.forEach(file => loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
201
- if (result.success && result.sound) {
202
- soundManager.setBuffer(file, result.sound);
203
- setSounds(s => (Object.assign(Object.assign({}, s), { [file]: result.sound })));
204
- }
205
- return result;
206
- })));
202
+ modelsToLoad.forEach(file => {
203
+ loadAsset(file, models, injectedModels, failedModels.current, path => loadModel(path).then(result => {
204
+ const loadedModel = result.model;
205
+ if (result.success && loadedModel) {
206
+ setModels(currentModels => (Object.assign(Object.assign({}, currentModels), { [file]: loadedModel })));
207
+ }
208
+ return result;
209
+ }));
210
+ });
211
+ texturesToLoad.forEach(file => {
212
+ loadAsset(file, textures, injectedTextures, failedTextures.current, path => loadTexture(path).then(result => {
213
+ const loadedTexture = result.texture;
214
+ if (result.success && loadedTexture) {
215
+ setTextures(currentTextures => (Object.assign(Object.assign({}, currentTextures), { [file]: loadedTexture })));
216
+ }
217
+ return result;
218
+ }));
219
+ });
220
+ soundsToLoad.forEach(file => {
221
+ loadAsset(file, sounds, injectedSounds, failedSounds.current, path => loadSound(path).then(result => {
222
+ const loadedSound = result.sound;
223
+ if (result.success && loadedSound) {
224
+ soundManager.setBuffer(file, loadedSound);
225
+ setSounds(currentSounds => (Object.assign(Object.assign({}, currentSounds), { [file]: loadedSound })));
226
+ }
227
+ return result;
228
+ }));
229
+ });
207
230
  };
208
231
  syncAssets();
209
232
  }, [resolvedStore, assetManifestKey, basePath, injectedModels, injectedSounds, injectedTextures, models, sounds, textures]);
@@ -240,11 +263,12 @@ function getClickEventName(component) {
240
263
  return typeof eventName === 'string' && eventName.trim() ? eventName.trim() : null;
241
264
  }
242
265
  function analyzeNodeComponents(node) {
243
- var _a, _b, _c;
266
+ var _a, _b, _c, _d;
244
267
  let bufferGeometry;
245
268
  let geometry;
246
269
  let material;
247
270
  let model;
271
+ let sprite;
248
272
  const composition = [];
249
273
  for (const [key, component] of Object.entries((_a = node.components) !== null && _a !== void 0 ? _a : {})) {
250
274
  if (!(component === null || component === void 0 ? void 0 : component.type))
@@ -264,6 +288,9 @@ function analyzeNodeComponents(node) {
264
288
  case "Model":
265
289
  model = component;
266
290
  break;
291
+ case "Sprite":
292
+ sprite = component;
293
+ break;
267
294
  default: {
268
295
  const def = getComponentDef(component.type);
269
296
  if (!(def === null || def === void 0 ? void 0 : def.View))
@@ -281,7 +308,8 @@ function analyzeNodeComponents(node) {
281
308
  geometry: bufferGeometry !== null && bufferGeometry !== void 0 ? bufferGeometry : geometry,
282
309
  material,
283
310
  model,
284
- clickEventName: (_c = (_b = getClickEventName(bufferGeometry)) !== null && _b !== void 0 ? _b : getClickEventName(geometry)) !== null && _c !== void 0 ? _c : getClickEventName(model),
311
+ sprite,
312
+ clickEventName: (_d = (_c = (_b = getClickEventName(bufferGeometry)) !== null && _b !== void 0 ? _b : getClickEventName(geometry)) !== null && _c !== void 0 ? _c : getClickEventName(model)) !== null && _d !== void 0 ? _d : getClickEventName(sprite),
285
313
  composition,
286
314
  };
287
315
  }
@@ -330,13 +358,11 @@ export function GameObjectRenderer(props) {
330
358
  function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef, onSelect, onEditNodeClick, onClick, isVisible = true }) {
331
359
  var _a, _b;
332
360
  const gameObject = usePrefabNode(nodeId);
333
- if (!gameObject)
334
- return null;
335
- const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
361
+ const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
336
362
  const localTransform = getNodeTransformProps(gameObject);
337
- const isLocked = Boolean(gameObject.locked);
338
- const nodeVisible = isVisible && !gameObject.hidden;
339
- const metadataProps = getNodeMetadataProps(gameObject);
363
+ const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
364
+ const nodeVisible = isVisible && !(gameObject === null || gameObject === void 0 ? void 0 : gameObject.hidden);
365
+ const metadataProps = gameObject ? getNodeMetadataProps(gameObject) : { name: '', userData: {} };
340
366
  const groupProps = Object.assign(Object.assign({}, metadataProps), { visible: nodeVisible, position: localTransform.position, rotation: localTransform.rotation, scale: localTransform.scale });
341
367
  const modelUrl = (_b = (_a = analyzedComponents.model) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.filename;
342
368
  const instances = useMemo(() => buildRepeatedInstances(gameObject, parentMatrix, modelUrl), [gameObject, modelUrl, parentMatrix]);
@@ -348,9 +374,13 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
348
374
  }
349
375
  }, [editMode, nodeId, registerRef]);
350
376
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
377
+ if (!gameObject)
378
+ return;
351
379
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
352
380
  onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
353
381
  });
382
+ if (!gameObject)
383
+ return null;
354
384
  const renderedInstances = instances.map(instance => (_jsx(GameInstance, { id: instance.id, sourceId: gameObject.id, modelUrl: instance.modelUrl, position: instance.position, rotation: instance.rotation, scale: instance.scale, visible: nodeVisible, locked: isLocked, onClick: onClick }, instance.id)));
355
385
  if (editMode) {
356
386
  return (_jsxs(_Fragment, { children: [_jsx("group", Object.assign({ ref: handleGroupRef }, groupProps, editClickHandlers, { children: _jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) }) })), renderedInstances] }));
@@ -360,20 +390,19 @@ function InstancedNode({ nodeId, parentMatrix = IDENTITY, editMode, registerRef,
360
390
  function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick, registerRef, loadedModels, editMode, parentMatrix = IDENTITY, isVisible = true, }) {
361
391
  const gameObject = usePrefabNode(nodeId);
362
392
  const childIds = usePrefabChildIds(nodeId);
363
- if (!gameObject)
364
- return null;
365
- const analyzedComponents = useMemo(() => analyzeNodeComponents(gameObject), [gameObject]);
393
+ const analyzedComponents = useMemo(() => gameObject ? analyzeNodeComponents(gameObject) : EMPTY_NODE_COMPONENTS, [gameObject]);
366
394
  const isSelected = selectedId === nodeId;
367
- const isLocked = Boolean(gameObject.locked);
368
- const nodeVisible = isVisible && !gameObject.hidden;
369
- const stillInstanced = useInstanceCheck(nodeId);
370
- const metadataProps = getNodeMetadataProps(gameObject);
395
+ const isLocked = Boolean(gameObject === null || gameObject === void 0 ? void 0 : gameObject.locked);
396
+ const nodeVisible = isVisible && !(gameObject === null || gameObject === void 0 ? void 0 : gameObject.hidden);
397
+ const metadataProps = gameObject ? getNodeMetadataProps(gameObject) : { name: '', userData: {} };
371
398
  const groupRef = useRef(null);
372
399
  const handleGroupRef = useCallback((object) => {
373
400
  groupRef.current = object;
374
401
  registerRef(nodeId, object);
375
402
  }, [nodeId, registerRef]);
376
403
  const editClickHandlers = useClickValid(!!editMode && !isLocked, (event) => {
404
+ if (!gameObject)
405
+ return;
377
406
  onSelect === null || onSelect === void 0 ? void 0 : onSelect(nodeId);
378
407
  onEditNodeClick === null || onEditNodeClick === void 0 ? void 0 : onEditNodeClick(event, gameObject);
379
408
  });
@@ -386,7 +415,6 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
386
415
  }
387
416
  : undefined;
388
417
  const world = parentMatrix.clone().multiply(compose(gameObject));
389
- const ready = isNodeReady(analyzedComponents.model, loadedModels);
390
418
  const transform = getNodeTransformProps(gameObject);
391
419
  const transformProps = {
392
420
  position: transform.position,
@@ -395,6 +423,8 @@ function StandardNode({ nodeId, selectedId, onSelect, onClick, onEditNodeClick,
395
423
  };
396
424
  const groupProps = Object.assign(Object.assign({}, metadataProps), transformProps);
397
425
  const childNodes = _jsx(ChildNodes, { childIds: childIds, parentMatrix: world, selectedId: selectedId, onSelect: onSelect, onClick: onClick, onEditNodeClick: onEditNodeClick, registerRef: registerRef, loadedModels: loadedModels, editMode: editMode, isVisible: nodeVisible });
426
+ if (!gameObject)
427
+ return null;
398
428
  const inner = renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes);
399
429
  const editAnchor = editMode ? (_jsx("mesh", { visible: false, children: _jsx("boxGeometry", { args: [0.01, 0.01, 0.01] }) })) : null;
400
430
  const standardNode = (_jsxs("group", Object.assign({ ref: handleGroupRef }, groupProps, { visible: nodeVisible }, (editMode ? editClickHandlers : undefined), { children: [editAnchor, inner] })));
@@ -417,7 +447,7 @@ function getModelRepeatSettings(node) {
417
447
  };
418
448
  }
419
449
  function buildRepeatedInstances(gameObject, parentMatrix, modelUrl) {
420
- if (!modelUrl)
450
+ if (!gameObject || !modelUrl)
421
451
  return [];
422
452
  const transform = getNodeTransformProps(gameObject);
423
453
  const repeat = getModelRepeatSettings(gameObject);
@@ -471,24 +501,58 @@ function getNodeTransformProps(node) {
471
501
  function renderNodeContent(analyzedComponents, loadedModels, primaryClickHandlers, childNodes) {
472
502
  var _a, _b, _c, _d;
473
503
  const geometry = analyzedComponents.geometry;
474
- const geometryDef = analyzedComponents.geometry && getComponentDef(analyzedComponents.geometry.type);
475
- const materialDef = analyzedComponents.material && getComponentDef(analyzedComponents.material.type);
476
- const modelDef = analyzedComponents.model && getComponentDef(analyzedComponents.model.type);
477
- const geometryProperties = (_a = geometry === null || geometry === void 0 ? void 0 : geometry.properties) !== null && _a !== void 0 ? _a : {};
478
- const meshVisible = geometryProperties.visible !== false;
479
- const meshCastShadow = meshVisible && geometryProperties.castShadow !== false;
480
- const meshReceiveShadow = meshVisible && geometryProperties.receiveShadow !== false;
481
- let primaryContent = null;
482
- if (((_b = analyzedComponents.geometry) === null || _b === void 0 ? void 0 : _b.type) && (geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View)) {
483
- primaryContent = (_jsxs("mesh", Object.assign({ visible: meshVisible, castShadow: meshCastShadow, receiveShadow: meshReceiveShadow }, primaryClickHandlers, { children: [_jsx(geometryDef.View, { properties: analyzedComponents.geometry.properties }), analyzedComponents.material && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View) && (_jsx(materialDef.View, { properties: analyzedComponents.material.properties }, "material"))] })));
504
+ const model = analyzedComponents.model;
505
+ const material = analyzedComponents.material;
506
+ const sprite = analyzedComponents.sprite;
507
+ const shapeKind = (sprite === null || sprite === void 0 ? void 0 : sprite.type) ? 'sprite' : (geometry === null || geometry === void 0 ? void 0 : geometry.type) ? 'mesh' : (model === null || model === void 0 ? void 0 : model.type) ? 'model' : 'none';
508
+ let materialContent = null;
509
+ switch (shapeKind) {
510
+ case 'sprite': {
511
+ const materialDef = (material === null || material === void 0 ? void 0 : material.type) ? getComponentDef(material.type) : undefined;
512
+ if ((material === null || material === void 0 ? void 0 : material.properties) && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View)) {
513
+ const materialIsSprite = material.properties.materialType === 'sprite';
514
+ materialContent = (_jsx(materialDef.View, { properties: Object.assign(Object.assign({}, material.properties), { materialType: 'sprite', transparent: materialIsSprite ? material.properties.transparent : true, depthTest: materialIsSprite ? material.properties.depthTest : false, depthWrite: materialIsSprite ? material.properties.depthWrite : false }) }, "material"));
515
+ }
516
+ break;
517
+ }
518
+ case 'mesh': {
519
+ const materialDef = (material === null || material === void 0 ? void 0 : material.type) ? getComponentDef(material.type) : undefined;
520
+ if ((material === null || material === void 0 ? void 0 : material.properties) && (materialDef === null || materialDef === void 0 ? void 0 : materialDef.View)) {
521
+ materialContent = _jsx(materialDef.View, { properties: material.properties }, "material");
522
+ }
523
+ break;
524
+ }
484
525
  }
485
- else if (((_c = analyzedComponents.model) === null || _c === void 0 ? void 0 : _c.type)
486
- && (modelDef === null || modelDef === void 0 ? void 0 : modelDef.View)
487
- && !((_d = analyzedComponents.model.properties) === null || _d === void 0 ? void 0 : _d.instanced)
488
- && isNodeReady(analyzedComponents.model, loadedModels)) {
489
- primaryContent = primaryClickHandlers ? (_jsx("group", Object.assign({}, primaryClickHandlers, { children: _jsx(modelDef.View, { properties: analyzedComponents.model.properties }) }))) : (_jsx(modelDef.View, { properties: analyzedComponents.model.properties }));
526
+ let primaryContent = null;
527
+ let contentChildren = childNodes;
528
+ switch (shapeKind) {
529
+ case 'sprite': {
530
+ primaryContent = (_jsxs("sprite", Object.assign({ center: (_b = (_a = sprite === null || sprite === void 0 ? void 0 : sprite.properties) === null || _a === void 0 ? void 0 : _a.center) !== null && _b !== void 0 ? _b : [0.5, 0.5] }, primaryClickHandlers, { children: [materialContent, childNodes] })));
531
+ contentChildren = null;
532
+ break;
533
+ }
534
+ case 'mesh': {
535
+ const geometryDef = (geometry === null || geometry === void 0 ? void 0 : geometry.type) ? getComponentDef(geometry.type) : undefined;
536
+ if (!(geometry === null || geometry === void 0 ? void 0 : geometry.properties) || !(geometryDef === null || geometryDef === void 0 ? void 0 : geometryDef.View))
537
+ break;
538
+ const GeometryView = geometryDef.View;
539
+ const geometryProperties = (_c = geometry.properties) !== null && _c !== void 0 ? _c : {};
540
+ const visible = geometryProperties.visible !== false;
541
+ primaryContent = (_jsxs("mesh", Object.assign({ visible: visible, castShadow: visible && geometryProperties.castShadow !== false, receiveShadow: visible && geometryProperties.receiveShadow !== false }, primaryClickHandlers, { children: [_jsx(GeometryView, { properties: geometry.properties }), materialContent] })));
542
+ break;
543
+ }
544
+ case 'model': {
545
+ if (!(model === null || model === void 0 ? void 0 : model.type) || ((_d = model.properties) === null || _d === void 0 ? void 0 : _d.instanced) || !isNodeReady(model, loadedModels))
546
+ break;
547
+ const modelDef = getComponentDef(model.type);
548
+ if (!(modelDef === null || modelDef === void 0 ? void 0 : modelDef.View))
549
+ break;
550
+ const modelContent = _jsx(modelDef.View, { properties: model.properties });
551
+ primaryContent = primaryClickHandlers ? _jsx("group", Object.assign({}, primaryClickHandlers, { children: modelContent })) : modelContent;
552
+ break;
553
+ }
490
554
  }
491
- let content = _jsxs(_Fragment, { children: [primaryContent, childNodes] });
555
+ let content = _jsxs(_Fragment, { children: [primaryContent, contentChildren] });
492
556
  for (const { key, View, properties } of analyzedComponents.composition) {
493
557
  content = (_jsx(View, { properties: properties, children: content }, key));
494
558
  }
@@ -1,19 +1,22 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { ThreeElement } from '@react-three/fiber';
3
- import { Component } from './ComponentRegistry';
4
- import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
5
- import { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
3
+ import type { Component } from './ComponentRegistry';
4
+ import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
5
+ import type { MeshBasicMaterialProperties, MeshStandardMaterialProperties } from 'three';
6
6
  declare module '@react-three/fiber' {
7
7
  interface ThreeElements {
8
8
  meshBasicNodeMaterial: ThreeElement<typeof MeshBasicNodeMaterial>;
9
9
  meshStandardNodeMaterial: ThreeElement<typeof MeshStandardNodeMaterial>;
10
+ spriteNodeMaterial: ThreeElement<typeof SpriteNodeMaterial>;
10
11
  }
11
12
  }
12
- export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale'> {
13
- materialType?: 'standard' | 'basic';
13
+ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale' | 'side'> {
14
+ materialType?: 'standard' | 'basic' | 'sprite';
14
15
  transmission?: number;
15
16
  thickness?: number;
16
17
  ior?: number;
18
+ rotation?: number;
19
+ sizeAttenuation?: boolean;
17
20
  texture?: string;
18
21
  offset?: [number, number];
19
22
  repeat?: boolean;
@@ -25,8 +28,14 @@ export interface MaterialProps extends Omit<MeshStandardMaterialProperties & Mes
25
28
  magFilter?: string;
26
29
  normalMapTexture?: string;
27
30
  normalScale?: [number, number];
31
+ side?: keyof typeof SIDE_MAP;
28
32
  }
29
33
  export type MaterialOverrides = Record<string, unknown>;
34
+ declare const SIDE_MAP: {
35
+ readonly FrontSide: 0;
36
+ readonly BackSide: 1;
37
+ readonly DoubleSide: 2;
38
+ };
30
39
  export declare function useMaterialOverrides(): MaterialOverrides;
31
40
  export declare function MaterialOverridesProvider({ overrides, children, }: {
32
41
  overrides: MaterialOverrides;
@@ -15,7 +15,7 @@ import { extend } from '@react-three/fiber';
15
15
  import { useFrame } from '@react-three/fiber';
16
16
  import { FieldRenderer, Label, NumberInput } from './Input';
17
17
  import { useAssetRuntime } from '../assetRuntime';
18
- import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
18
+ import { MeshBasicNodeMaterial, MeshStandardNodeMaterial, SpriteNodeMaterial } from 'three/webgpu';
19
19
  import { TexturePicker } from '../../assetviewer/page';
20
20
  import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
21
21
  function Vector2Editor({ label, value, onChange, min, max, step, }) {
@@ -24,6 +24,39 @@ function Vector2Editor({ label, value, onChange, min, max, step, }) {
24
24
  }
25
25
  const EMPTY_MATERIAL_OVERRIDES = Object.freeze({});
26
26
  const MaterialOverridesContext = createContext(EMPTY_MATERIAL_OVERRIDES);
27
+ const SIDE_MAP = { FrontSide, BackSide, DoubleSide };
28
+ const MIN_FILTER_MAP = {
29
+ NearestFilter,
30
+ LinearFilter,
31
+ NearestMipmapNearestFilter,
32
+ NearestMipmapLinearFilter,
33
+ LinearMipmapNearestFilter,
34
+ LinearMipmapLinearFilter,
35
+ };
36
+ const MAG_FILTER_MAP = {
37
+ NearestFilter,
38
+ LinearFilter,
39
+ };
40
+ function cloneConfiguredTexture({ texture, repeat, repeatCount, offset, colorSpace, generateMipmaps, minFilter, magFilter, }) {
41
+ var _a, _b;
42
+ const clonedTexture = texture.clone();
43
+ if (repeat) {
44
+ clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping;
45
+ if (repeatCount)
46
+ clonedTexture.repeat.set(repeatCount[0], repeatCount[1]);
47
+ }
48
+ else {
49
+ clonedTexture.wrapS = clonedTexture.wrapT = ClampToEdgeWrapping;
50
+ clonedTexture.repeat.set(1, 1);
51
+ }
52
+ clonedTexture.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);
53
+ clonedTexture.colorSpace = colorSpace;
54
+ clonedTexture.generateMipmaps = generateMipmaps;
55
+ clonedTexture.minFilter = minFilter;
56
+ clonedTexture.magFilter = magFilter;
57
+ clonedTexture.needsUpdate = true;
58
+ return clonedTexture;
59
+ }
27
60
  export function useMaterialOverrides() {
28
61
  return useContext(MaterialOverridesContext);
29
62
  }
@@ -35,14 +68,16 @@ export function MaterialOverridesProvider({ overrides, children, }) {
35
68
  extend({
36
69
  MeshBasicNodeMaterial,
37
70
  MeshStandardNodeMaterial,
71
+ SpriteNodeMaterial,
38
72
  });
39
- function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
73
+ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
40
74
  var _a;
41
75
  const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
42
76
  const hasTexture = !!component.properties.texture;
43
77
  const hasRepeat = component.properties.repeat;
44
78
  const animateOffset = component.properties.animateOffset;
45
79
  const isStandardMaterial = materialType === 'standard';
80
+ const isSpriteMaterial = materialType === 'sprite';
46
81
  const fields = [
47
82
  {
48
83
  name: 'materialType',
@@ -51,13 +86,22 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
51
86
  options: [
52
87
  { value: 'standard', label: 'Standard' },
53
88
  { value: 'basic', label: 'Basic' },
89
+ { value: 'sprite', label: 'Sprite' },
54
90
  ],
55
91
  },
56
92
  { name: 'color', type: 'color', label: 'Color' },
57
93
  { name: 'toneMapped', type: 'boolean', label: 'Tone Mapped' },
58
- { name: 'wireframe', type: 'boolean', label: 'Wireframe' },
94
+ ...(!isSpriteMaterial ? [
95
+ { name: 'wireframe', type: 'boolean', label: 'Wireframe' },
96
+ ] : []),
59
97
  { name: 'transparent', type: 'boolean', label: 'Transparent' },
60
98
  { name: 'opacity', type: 'number', label: 'Opacity', min: 0, max: 1, step: 0.01 },
99
+ ...(isSpriteMaterial ? [
100
+ { name: 'rotation', type: 'number', label: 'Rotation', step: 0.01 },
101
+ { name: 'sizeAttenuation', type: 'boolean', label: 'Size Attenuation' },
102
+ { name: 'depthTest', type: 'boolean', label: 'Depth Test' },
103
+ { name: 'depthWrite', type: 'boolean', label: 'Depth Write' },
104
+ ] : []),
61
105
  ...(isStandardMaterial ? [
62
106
  { name: 'metalness', type: 'number', label: 'Metalness', min: 0, max: 1, step: 0.01 },
63
107
  { name: 'roughness', type: 'number', label: 'Roughness', min: 0, max: 1, step: 0.01 },
@@ -65,16 +109,16 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
65
109
  { name: 'thickness', type: 'number', label: 'Thickness', min: 0, step: 0.1 },
66
110
  { name: 'ior', type: 'number', label: 'IOR (Index of Refraction)', min: 1, max: 2.333, step: 0.01 },
67
111
  ] : []),
68
- {
69
- name: 'side',
70
- type: 'select',
71
- label: 'Side',
72
- options: [
73
- { value: 'FrontSide', label: 'Front' },
74
- { value: 'BackSide', label: 'Back' },
75
- { value: 'DoubleSide', label: 'Double' },
76
- ],
77
- },
112
+ ...(!isSpriteMaterial ? [{
113
+ name: 'side',
114
+ type: 'select',
115
+ label: 'Side',
116
+ options: [
117
+ { value: 'FrontSide', label: 'Front' },
118
+ { value: 'BackSide', label: 'Back' },
119
+ { value: 'DoubleSide', label: 'Double' },
120
+ ],
121
+ }] : []),
78
122
  {
79
123
  name: 'texture',
80
124
  type: 'custom',
@@ -103,13 +147,13 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
103
147
  label: 'Speed (X, Y)',
104
148
  render: ({ value, onChange }) => (_jsx(Vector2Editor, { label: "Speed", value: value, onChange: onChange, step: 0.01 })),
105
149
  }] : []),
106
- {
107
- name: 'normalMapTexture',
108
- type: 'custom',
109
- label: 'Normal Map',
110
- render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
111
- },
112
- ...(component.properties.normalMapTexture ? [{
150
+ ...(!isSpriteMaterial ? [{
151
+ name: 'normalMapTexture',
152
+ type: 'custom',
153
+ label: 'Normal Map',
154
+ render: ({ value, onChange }) => (_jsx(TexturePicker, { value: value, onChange: onChange, basePath: basePath })),
155
+ }] : []),
156
+ ...(!isSpriteMaterial && component.properties.normalMapTexture ? [{
113
157
  name: 'normalScale',
114
158
  type: 'custom',
115
159
  label: 'Normal Scale (X, Y)',
@@ -144,90 +188,83 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
144
188
  }
145
189
  // View for Material component
146
190
  function MaterialComponentView({ properties: rawProps }) {
147
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
191
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
148
192
  const { getTexture } = useAssetRuntime();
149
193
  const properties = rawProps;
150
- const materialType = (_a = properties === null || properties === void 0 ? void 0 : properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
151
- const textureName = properties === null || properties === void 0 ? void 0 : properties.texture;
152
- const offset = properties === null || properties === void 0 ? void 0 : properties.offset;
153
- const repeat = properties === null || properties === void 0 ? void 0 : properties.repeat;
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;
157
- const generateMipmaps = (properties === null || properties === void 0 ? void 0 : properties.generateMipmaps) !== false;
158
- const minFilter = (properties === null || properties === void 0 ? void 0 : properties.minFilter) || 'LinearMipmapLinearFilter';
159
- const magFilter = (properties === null || properties === void 0 ? void 0 : properties.magFilter) || 'LinearFilter';
160
- const texture = textureName ? (_b = getTexture(textureName)) !== null && _b !== void 0 ? _b : undefined : undefined;
161
- const normalMapTextureName = properties === null || properties === void 0 ? void 0 : properties.normalMapTexture;
162
- const normalScaleProp = properties === null || properties === void 0 ? void 0 : properties.normalScale;
163
- const normalMapTexture = normalMapTextureName ? (_c = getTexture(normalMapTextureName)) !== null && _c !== void 0 ? _c : undefined : undefined;
164
194
  const materialSource = properties !== null && properties !== void 0 ? properties : {};
195
+ const materialType = (_a = materialSource.materialType) !== null && _a !== void 0 ? _a : 'standard';
196
+ const textureName = materialSource.texture;
197
+ const normalMapTextureName = materialSource.normalMapTexture;
198
+ const offset = materialSource.offset;
199
+ const repeat = materialSource.repeat;
200
+ const repeatCount = materialSource.repeatCount;
201
+ const animateOffset = materialSource.animateOffset;
202
+ const offsetSpeed = materialSource.offsetSpeed;
203
+ const generateMipmaps = materialSource.generateMipmaps !== false;
204
+ const minFilter = (_b = materialSource.minFilter) !== null && _b !== void 0 ? _b : 'LinearMipmapLinearFilter';
205
+ const magFilter = (_c = materialSource.magFilter) !== null && _c !== void 0 ? _c : 'LinearFilter';
206
+ const texture = textureName ? getTexture(textureName) : undefined;
207
+ const normalScaleProp = materialSource.normalScale;
208
+ const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
165
209
  // Destructure all material props and separate custom texture handling props
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"]);
167
- const sideMap = { FrontSide, BackSide, DoubleSide };
168
- const resolvedSide = sideProp ? ((_d = sideMap[sideProp]) !== null && _d !== void 0 ? _d : FrontSide) : FrontSide;
169
- const minFilterMap = {
170
- NearestFilter,
171
- LinearFilter,
172
- NearestMipmapNearestFilter,
173
- NearestMipmapLinearFilter,
174
- LinearMipmapNearestFilter,
175
- LinearMipmapLinearFilter
176
- };
177
- const magFilterMap = {
178
- NearestFilter,
179
- LinearFilter
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]);
210
+ 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, rotation, sizeAttenuation, side: sideProp } = materialSource, materialProps = __rest(materialSource, ["texture", "offset", "repeat", "repeatCount", "animateOffset", "offsetSpeed", "generateMipmaps", "minFilter", "magFilter", "map", "materialType", "normalMapTexture", "normalScale", "normalMap", "rotation", "sizeAttenuation", "side"]);
211
+ const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
212
+ const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
213
+ const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
214
+ const animatedOffsetRef = useRef([(_g = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _g !== void 0 ? _g : 0, (_h = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _h !== void 0 ? _h : 0]);
182
215
  const finalTexture = useMemo(() => {
183
- var _a, _b, _c, _d;
184
216
  if (!texture)
185
217
  return undefined;
186
- const t = texture.clone();
187
- if (repeat) {
188
- t.wrapS = t.wrapT = RepeatWrapping;
189
- if (repeatCount)
190
- t.repeat.set(repeatCount[0], repeatCount[1]);
191
- }
192
- else {
193
- t.wrapS = t.wrapT = ClampToEdgeWrapping;
194
- t.repeat.set(1, 1);
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);
197
- t.colorSpace = SRGBColorSpace;
198
- t.generateMipmaps = generateMipmaps;
199
- t.minFilter = (_c = minFilterMap[minFilter]) !== null && _c !== void 0 ? _c : LinearMipmapLinearFilter;
200
- t.magFilter = (_d = magFilterMap[magFilter]) !== null && _d !== void 0 ? _d : LinearFilter;
201
- t.needsUpdate = true;
202
- return t;
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];
218
+ return cloneConfiguredTexture({
219
+ texture,
220
+ repeat,
221
+ repeatCount,
222
+ offset,
223
+ colorSpace: SRGBColorSpace,
224
+ generateMipmaps,
225
+ minFilter: resolvedMinFilter,
226
+ magFilter: resolvedMagFilter,
227
+ });
228
+ }, [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, resolvedMinFilter, resolvedMagFilter]);
229
+ const finalNormalMap = useMemo(() => {
230
+ if (!normalMapTexture)
231
+ return undefined;
232
+ return cloneConfiguredTexture({
233
+ texture: normalMapTexture,
234
+ repeat,
235
+ repeatCount,
236
+ offset,
237
+ colorSpace: LinearSRGBColorSpace,
238
+ generateMipmaps,
239
+ minFilter: resolvedMinFilter,
240
+ magFilter: resolvedMagFilter,
241
+ });
242
+ }, [normalMapTexture, 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, resolvedMinFilter, resolvedMagFilter]);
243
+ animatedOffsetRef.current = [(_j = offset === null || offset === void 0 ? void 0 : offset[0]) !== null && _j !== void 0 ? _j : 0, (_k = offset === null || offset === void 0 ? void 0 : offset[1]) !== null && _k !== void 0 ? _k : 0];
205
244
  useFrame((_, delta) => {
206
245
  var _a, _b;
207
- if (!finalTexture || !animateOffset)
246
+ if ((!finalTexture && !finalNormalMap) || !animateOffset)
208
247
  return;
209
248
  const nextX = animatedOffsetRef.current[0] + ((_a = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[0]) !== null && _a !== void 0 ? _a : 0) * delta;
210
249
  const nextY = animatedOffsetRef.current[1] + ((_b = offsetSpeed === null || offsetSpeed === void 0 ? void 0 : offsetSpeed[1]) !== null && _b !== void 0 ? _b : 0) * delta;
211
250
  animatedOffsetRef.current = [nextX, nextY];
212
- finalTexture.offset.set(nextX, nextY);
251
+ finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.offset.set(nextX, nextY);
252
+ finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.offset.set(nextX, nextY);
213
253
  });
214
- const finalNormalMap = useMemo(() => {
215
- if (!normalMapTexture)
216
- return undefined;
217
- const t = normalMapTexture.clone();
218
- t.colorSpace = LinearSRGBColorSpace;
219
- t.needsUpdate = true;
220
- return t;
221
- }, [normalMapTexture]);
254
+ const overrides = useMaterialOverrides();
222
255
  if (!properties) {
223
- return _jsx("meshStandardNodeMaterial", { color: "red", wireframe: true });
256
+ return _jsx("meshStandardNodeMaterial", { attach: "material", color: "red", wireframe: true });
224
257
  }
225
- const overrides = useMaterialOverrides();
258
+ const materialKey = `${materialType}:${textureName !== null && textureName !== void 0 ? textureName : 'none'}:${normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'none'}`;
226
259
  const sharedProps = Object.assign(Object.assign({ map: finalTexture, side: resolvedSide }, materialProps), overrides);
227
260
  if (materialType === 'basic') {
228
- return _jsx("meshBasicNodeMaterial", Object.assign({}, sharedProps));
261
+ return _jsx("meshBasicNodeMaterial", Object.assign({ attach: "material" }, sharedProps), materialKey);
262
+ }
263
+ if (materialType === 'sprite') {
264
+ const spriteTransparent = materialSource.transparent !== false;
265
+ return (_jsx("spriteNodeMaterial", Object.assign({ attach: "material", map: finalTexture, color: (_l = materialSource.color) !== null && _l !== void 0 ? _l : '#ffffff', opacity: (_m = materialSource.opacity) !== null && _m !== void 0 ? _m : 1, transparent: spriteTransparent, alphaTest: (_o = materialSource.alphaTest) !== null && _o !== void 0 ? _o : 0, depthTest: (_p = materialSource.depthTest) !== null && _p !== void 0 ? _p : false, depthWrite: (_q = materialSource.depthWrite) !== null && _q !== void 0 ? _q : false, toneMapped: (_r = materialSource.toneMapped) !== null && _r !== void 0 ? _r : true }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
229
266
  }
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 })));
267
+ return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: "material" }, sharedProps, { normalMap: finalNormalMap, normalScale: finalNormalMap ? [(_s = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[0]) !== null && _s !== void 0 ? _s : 1, (_t = normalScaleProp === null || normalScaleProp === void 0 ? void 0 : normalScaleProp[1]) !== null && _t !== void 0 ? _t : 1] : undefined }), materialKey));
231
268
  }
232
269
  const MaterialComponent = {
233
270
  name: 'Material',
@@ -240,6 +277,7 @@ const MaterialComponent = {
240
277
  wireframe: false,
241
278
  transparent: false,
242
279
  opacity: 1,
280
+ sizeAttenuation: true,
243
281
  offset: [0, 0],
244
282
  animateOffset: false,
245
283
  offsetSpeed: [0, 0],
@@ -1,3 +1,3 @@
1
- import { Component } from './ComponentRegistry';
1
+ import type { Component } from './ComponentRegistry';
2
2
  declare const ModelComponent: Component;
3
3
  export default ModelComponent;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ModelPicker } from '../../assetviewer/page';
3
3
  import { useMemo } from 'react';
4
+ import { Mesh } from 'three';
4
5
  import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
5
6
  import { useAssetRuntime } from '../assetRuntime';
6
7
  import { useEditorContext } from '../PrefabEditor';
@@ -46,23 +47,20 @@ function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
46
47
  // View for Model component
47
48
  function ModelComponentView({ properties, children }) {
48
49
  const { getModel } = useAssetRuntime();
49
- // Instanced models are handled elsewhere (GameInstance), so only render non-instanced here
50
- if (!properties.filename || properties.instanced)
51
- return _jsx(_Fragment, { children: children });
52
- const sourceModel = getModel(properties.filename);
50
+ const sourceModel = properties.filename ? getModel(properties.filename) : null;
53
51
  // Clone model once and set up shadows - memoized to avoid cloning on every render
54
52
  const clonedModel = useMemo(() => {
55
- if (!sourceModel)
53
+ if (!sourceModel || !properties.filename || properties.instanced)
56
54
  return null;
57
55
  const clone = sourceModel.clone();
58
56
  clone.traverse((obj) => {
59
- if (obj.isMesh) {
57
+ if (obj instanceof Mesh) {
60
58
  obj.castShadow = true;
61
59
  obj.receiveShadow = true;
62
60
  }
63
61
  });
64
62
  return clone;
65
- }, [sourceModel]);
63
+ }, [properties.filename, properties.instanced, sourceModel]);
66
64
  if (!clonedModel)
67
65
  return _jsx(_Fragment, { children: children });
68
66
  return _jsx("primitive", { object: clonedModel, children: children });
@@ -0,0 +1,8 @@
1
+ import type { Component } from './ComponentRegistry';
2
+ export interface SpriteProps {
3
+ center?: [number, number];
4
+ emitClickEvent?: boolean;
5
+ clickEventName?: string;
6
+ }
7
+ declare const SpriteComponent: Component;
8
+ export default SpriteComponent;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { BooleanField, FieldRenderer, NumberInput, StringField } from './Input';
3
+ function Vector2Editor({ value, onChange, min, max, step, }) {
4
+ var _a, _b;
5
+ return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_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' } }), _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' } })] }));
6
+ }
7
+ function SpriteComponentEditor({ component, onUpdate, }) {
8
+ const fields = [
9
+ {
10
+ name: 'center',
11
+ type: 'custom',
12
+ label: 'Center',
13
+ render: ({ value, onChange }) => (_jsx(Vector2Editor, { value: value, onChange: onChange, min: 0, max: 1, step: 0.01 })),
14
+ },
15
+ ];
16
+ return (_jsxs(_Fragment, { children: [_jsx(FieldRenderer, { fields: fields, values: component.properties, onChange: onUpdate }), _jsxs("div", { style: { marginTop: 8 }, children: [_jsx(BooleanField, { name: "emitClickEvent", label: "Emit Click Event", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.emitClickEvent ? (_jsx(StringField, { name: "clickEventName", label: "Click Event Name", values: component.properties, onChange: onUpdate, fallback: "node:click" })) : null] })] }));
17
+ }
18
+ const SpriteComponent = {
19
+ name: 'Sprite',
20
+ Editor: SpriteComponentEditor,
21
+ defaultProperties: {
22
+ center: [0.5, 0.5],
23
+ emitClickEvent: false,
24
+ clickEventName: 'node:click',
25
+ },
26
+ };
27
+ export default SpriteComponent;
@@ -1,28 +1,36 @@
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';
6
- import MaterialComponent from './MaterialComponent';
7
- import SpotLightComponent from './SpotLightComponent';
8
- import PointLightComponent from './PointLightComponent';
9
- import DirectionalLightComponent from './DirectionalLightComponent';
10
- import AmbientLightComponent from './AmbientLightComponent';
11
- import EnvironmentComponent from './EnvironmentComponent';
12
- import CameraComponent from './CameraComponent';
13
- import SoundComponent from './SoundComponent';
14
- import DataComponent from './DataComponent';
1
+ // biome-ignore assist/source/organizeImports: <in order of display in the editor>
2
+ import TransformComponent from "./TransformComponent";
3
+ import GeometryComponent from "./GeometryComponent";
4
+ import BufferGeometryComponent from "./BufferGeometryComponent";
5
+ import ModelComponent from "./ModelComponent";
6
+ import SpriteComponent from "./SpriteComponent";
7
+ import TextComponent from "./TextComponent";
8
+ import MaterialComponent from "./MaterialComponent";
9
+ import SpotLightComponent from "./SpotLightComponent";
10
+ import PointLightComponent from "./PointLightComponent";
11
+ import DirectionalLightComponent from "./DirectionalLightComponent";
12
+ import AmbientLightComponent from "./AmbientLightComponent";
13
+ import EnvironmentComponent from "./EnvironmentComponent";
14
+ import CameraComponent from "./CameraComponent";
15
+ import SoundComponent from "./SoundComponent";
16
+ import DataComponent from "./DataComponent";
17
+ // this controls the order of components in the editor, and also which components are available to add
15
18
  export const builtinComponents = [
16
19
  TransformComponent,
20
+ // geometry components
17
21
  GeometryComponent,
18
22
  BufferGeometryComponent,
19
23
  ModelComponent,
24
+ SpriteComponent,
20
25
  TextComponent,
26
+ // material components
21
27
  MaterialComponent,
28
+ // light components
22
29
  SpotLightComponent,
23
30
  PointLightComponent,
24
31
  DirectionalLightComponent,
25
32
  AmbientLightComponent,
33
+ // other components
26
34
  EnvironmentComponent,
27
35
  CameraComponent,
28
36
  SoundComponent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-three-game",
3
- "version": "0.0.94",
3
+ "version": "0.0.96",
4
4
  "description": "high performance 3D game engine built in React",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",