react-three-game 0.0.99 → 0.0.101

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.
Files changed (34) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +1 -0
  3. package/dist/tools/assetviewer/page.js +70 -58
  4. package/dist/tools/dragdrop/DragDropLoader.d.ts +3 -0
  5. package/dist/tools/dragdrop/DragDropLoader.js +183 -44
  6. package/dist/tools/dragdrop/index.d.ts +1 -1
  7. package/dist/tools/dragdrop/index.js +1 -1
  8. package/dist/tools/dragdrop/modelLoader.js +2 -0
  9. package/dist/tools/prefabeditor/EditorUI.js +7 -8
  10. package/dist/tools/prefabeditor/PrefabEditor.d.ts +3 -0
  11. package/dist/tools/prefabeditor/PrefabEditor.js +28 -11
  12. package/dist/tools/prefabeditor/PrefabRoot.d.ts +2 -0
  13. package/dist/tools/prefabeditor/PrefabRoot.js +51 -35
  14. package/dist/tools/prefabeditor/components/BufferGeometryComponent.js +20 -0
  15. package/dist/tools/prefabeditor/components/CameraComponent.js +1 -14
  16. package/dist/tools/prefabeditor/components/ComponentRegistry.d.ts +5 -0
  17. package/dist/tools/prefabeditor/components/ComponentRegistry.js +31 -0
  18. package/dist/tools/prefabeditor/components/DataComponent.js +1 -0
  19. package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +31 -119
  20. package/dist/tools/prefabeditor/components/EnvironmentComponent.js +1 -0
  21. package/dist/tools/prefabeditor/components/GeometryComponent.js +1 -0
  22. package/dist/tools/prefabeditor/components/Input.d.ts +1 -0
  23. package/dist/tools/prefabeditor/components/MaterialComponent.d.ts +1 -0
  24. package/dist/tools/prefabeditor/components/MaterialComponent.js +89 -52
  25. package/dist/tools/prefabeditor/components/ModelComponent.js +45 -3
  26. package/dist/tools/prefabeditor/components/PointLightComponent.js +19 -25
  27. package/dist/tools/prefabeditor/components/SpotLightComponent.js +27 -42
  28. package/dist/tools/prefabeditor/components/SpriteComponent.js +1 -0
  29. package/dist/tools/prefabeditor/components/TransformComponent.js +1 -0
  30. package/dist/tools/prefabeditor/modelPrefab.d.ts +16 -0
  31. package/dist/tools/prefabeditor/modelPrefab.js +180 -0
  32. package/dist/tools/prefabeditor/prefabStore.d.ts +1 -0
  33. package/dist/tools/prefabeditor/prefabStore.js +75 -42
  34. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useHelper } from "@react-three/drei";
3
- import { useRef, useEffect, useState } from "react";
4
- import { useFrame } from "@react-three/fiber";
3
+ import { useEffect, useRef } from "react";
5
4
  import { CameraHelper } from "three";
6
5
  import { useNode } from "../assetRuntime";
7
6
  import { BooleanField, ColorField, NumberField, NumberInput, Vector3Input } from "./Input";
@@ -23,141 +22,54 @@ const directionalLightDefaults = {
23
22
  shadowCameraRight: 5,
24
23
  targetOffset: [0, -5, 0],
25
24
  };
26
- const frustumLabelStyle = {
27
- fontSize: 10,
28
- textTransform: 'uppercase',
29
- letterSpacing: '0.06em',
30
- color: colors.textMuted,
31
- textAlign: 'center',
32
- };
33
- const frustumCellStyle = {
34
- display: 'flex',
35
- alignItems: 'center',
36
- justifyContent: 'center',
37
- };
38
- const frustumInputStyle = {
39
- width: 62,
40
- minWidth: 62,
41
- textAlign: 'center',
42
- };
43
- const centerLockButtonStyle = {
44
- width: 34,
45
- height: 34,
46
- borderRadius: 0,
47
- border: `1px solid ${colors.border}`,
48
- background: colors.bgInput,
49
- color: colors.textMuted,
50
- cursor: 'pointer',
51
- fontSize: 14,
52
- lineHeight: 1,
53
- padding: 0,
54
- };
55
- function areFrustumSidesLocked(values) {
56
- const top = Math.abs(values.shadowCameraTop);
57
- const bottom = Math.abs(values.shadowCameraBottom);
58
- const left = Math.abs(values.shadowCameraLeft);
59
- const right = Math.abs(values.shadowCameraRight);
60
- return top === bottom && top === left && top === right;
61
- }
62
- function ShadowFrustumField({ values, onChange, }) {
63
- const [locked, setLocked] = useState(() => areFrustumSidesLocked(values));
64
- const updateSide = (side, nextValue) => {
65
- if (!locked) {
66
- onChange({ [side]: nextValue });
67
- return;
68
- }
69
- const magnitude = Math.abs(nextValue);
70
- onChange({
71
- shadowCameraTop: magnitude,
72
- shadowCameraBottom: -magnitude,
73
- shadowCameraLeft: -magnitude,
74
- shadowCameraRight: magnitude,
75
- });
76
- };
77
- const toggleLocked = () => {
78
- setLocked(current => {
79
- const nextLocked = !current;
80
- if (nextLocked) {
81
- const magnitude = Math.max(Math.abs(values.shadowCameraTop), Math.abs(values.shadowCameraBottom), Math.abs(values.shadowCameraLeft), Math.abs(values.shadowCameraRight));
82
- onChange({
83
- shadowCameraTop: magnitude,
84
- shadowCameraBottom: -magnitude,
85
- shadowCameraLeft: -magnitude,
86
- shadowCameraRight: magnitude,
87
- });
88
- }
89
- return nextLocked;
90
- });
91
- };
92
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx("div", { style: Object.assign(Object.assign({}, frustumLabelStyle), { textAlign: 'left' }), children: "Shadow Frustum" }), _jsxs("div", { style: {
93
- display: 'grid',
94
- gridTemplateColumns: '1fr auto 1fr',
95
- gridTemplateRows: 'auto auto auto',
96
- gap: 8,
97
- alignItems: 'center',
98
- }, children: [_jsx("div", {}), _jsx("div", { style: frustumCellStyle, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }, children: [_jsx("div", { style: frustumLabelStyle, children: "Top" }), _jsx(NumberInput, { value: values.shadowCameraTop, onChange: nextValue => updateSide('shadowCameraTop', nextValue), step: 0.5, style: frustumInputStyle })] }) }), _jsx("div", {}), _jsx("div", { style: frustumCellStyle, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }, children: [_jsx("div", { style: frustumLabelStyle, children: "Left" }), _jsx(NumberInput, { value: values.shadowCameraLeft, onChange: nextValue => updateSide('shadowCameraLeft', nextValue), step: 0.5, style: frustumInputStyle })] }) }), _jsx("div", { style: frustumCellStyle, children: _jsx("button", { type: "button", onClick: toggleLocked, style: Object.assign(Object.assign({}, centerLockButtonStyle), { color: locked ? colors.accent : colors.textMuted, borderColor: locked ? colors.accentBorder : colors.border, background: locked ? colors.accentBg : colors.bgInput }), title: locked ? 'Frustum sides locked' : 'Frustum sides unlocked', children: locked ? '🔒' : '🔓' }) }), _jsx("div", { style: frustumCellStyle, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }, children: [_jsx("div", { style: frustumLabelStyle, children: "Right" }), _jsx(NumberInput, { value: values.shadowCameraRight, onChange: nextValue => updateSide('shadowCameraRight', nextValue), step: 0.5, style: frustumInputStyle })] }) }), _jsx("div", {}), _jsx("div", { style: frustumCellStyle, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center' }, children: [_jsx("div", { style: frustumLabelStyle, children: "Bottom" }), _jsx(NumberInput, { value: values.shadowCameraBottom, onChange: nextValue => updateSide('shadowCameraBottom', nextValue), step: 0.5, style: frustumInputStyle })] }) }), _jsx("div", {})] })] }));
25
+ function ShadowFrustumField({ values, onChange }) {
26
+ // Minimal, no lock UI for simplicity (can add back if needed)
27
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx("div", { style: { fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.06em', color: colors.textMuted, textAlign: 'left' }, children: "Shadow Frustum" }), _jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx(NumberInput, { value: values.shadowCameraTop, onChange: v => onChange({ shadowCameraTop: v }), step: 0.5, style: { width: 62, minWidth: 62, textAlign: 'center' }, label: "Top" }), _jsx(NumberInput, { value: values.shadowCameraBottom, onChange: v => onChange({ shadowCameraBottom: v }), step: 0.5, style: { width: 62, minWidth: 62, textAlign: 'center' }, label: "Bottom" }), _jsx(NumberInput, { value: values.shadowCameraLeft, onChange: v => onChange({ shadowCameraLeft: v }), step: 0.5, style: { width: 62, minWidth: 62, textAlign: 'center' }, label: "Left" }), _jsx(NumberInput, { value: values.shadowCameraRight, onChange: v => onChange({ shadowCameraRight: v }), step: 0.5, style: { width: 62, minWidth: 62, textAlign: 'center' }, label: "Right" })] })] }));
99
28
  }
100
29
  function DirectionalLightComponentEditor({ component, onUpdate }) {
101
30
  const values = mergeWithDefaults(directionalLightDefaults, component.properties);
102
31
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [_jsxs(LightSection, { title: "Light", children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(Vector3Input, { label: "Target Offset", value: values.targetOffset, onChange: targetOffset => onUpdate({ targetOffset }), snap: 0.5 })] }), _jsxs(LightSection, { title: "Shadow", children: [_jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: false }), values.castShadow ? (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "shadowAutoUpdate", label: "Auto Update", values: values, onChange: onUpdate, fallback: true }), _jsx(NumberField, { name: "shadowMapSize", label: "Map Size", values: values, onChange: onUpdate, min: 128, step: 128, fallback: 512 }), _jsx(ShadowBiasField, { name: "shadowBias", label: "Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(ShadowBiasField, { name: "shadowNormalBias", label: "Normal Bias", values: values, onChange: onUpdate, fallback: 0 }), _jsx(NumberField, { name: "shadowCameraNear", label: "Near", values: values, onChange: onUpdate, min: 0.001, step: 0.1, fallback: 0.5 }), _jsx(NumberField, { name: "shadowCameraFar", label: "Far", values: values, onChange: onUpdate, min: 0.1, step: 1, fallback: 500 }), _jsx(ShadowFrustumField, { values: values, onChange: onUpdate })] })) : null] })] }));
103
32
  }
104
33
  function DirectionalLightView({ properties, children }) {
34
+ var _a;
105
35
  const { editMode, isSelected } = useNode();
106
36
  const merged = mergeWithDefaults(directionalLightDefaults, properties);
107
- const color = merged.color;
108
- const intensity = merged.intensity;
109
- const castShadow = merged.castShadow;
110
- const shadowMapSize = merged.shadowMapSize;
111
- const shadowBias = merged.shadowBias;
112
- const shadowNormalBias = merged.shadowNormalBias;
113
- const shadowAutoUpdate = merged.shadowAutoUpdate;
114
- const shadowCameraNear = merged.shadowCameraNear;
115
- const shadowCameraFar = merged.shadowCameraFar;
116
- const shadowCameraTop = merged.shadowCameraTop;
117
- const shadowCameraBottom = merged.shadowCameraBottom;
118
- const shadowCameraLeft = merged.shadowCameraLeft;
119
- const shadowCameraRight = merged.shadowCameraRight;
120
- const targetOffset = merged.targetOffset;
37
+ const lightProps = {
38
+ color: merged.color,
39
+ intensity: merged.intensity,
40
+ castShadow: merged.castShadow,
41
+ "shadow-mapSize-width": merged.shadowMapSize,
42
+ "shadow-mapSize-height": merged.shadowMapSize,
43
+ "shadow-bias": merged.shadowBias,
44
+ "shadow-normalBias": merged.shadowNormalBias,
45
+ "shadow-autoUpdate": merged.shadowAutoUpdate,
46
+ "shadow-camera-near": merged.shadowCameraNear,
47
+ "shadow-camera-far": merged.shadowCameraFar,
48
+ "shadow-camera-top": merged.shadowCameraTop,
49
+ "shadow-camera-bottom": merged.shadowCameraBottom,
50
+ "shadow-camera-left": merged.shadowCameraLeft,
51
+ "shadow-camera-right": merged.shadowCameraRight,
52
+ };
121
53
  const directionalLightRef = useRef(null);
122
54
  const targetRef = useRef(null);
123
55
  const shadowCameraRef = useRef(null);
124
- const [shadowCamera, setShadowCamera] = useState(null);
125
- const helperTarget = editMode && isSelected && castShadow && shadowCameraRef.current
126
- ? { current: shadowCameraRef.current }
127
- : null;
56
+ // Show CameraHelper only in edit mode, selected, and castShadow
57
+ const showHelper = editMode && isSelected && merged.castShadow;
58
+ const helperTarget = showHelper && shadowCameraRef.current ? { current: shadowCameraRef.current } : null;
128
59
  useHelper(helperTarget, CameraHelper);
129
- // Use a local target object so node transforms rotate the light direction naturally.
130
- useEffect(() => {
131
- if (directionalLightRef.current && targetRef.current) {
132
- directionalLightRef.current.target = targetRef.current;
133
- const nextShadowCamera = directionalLightRef.current.shadow.camera;
134
- shadowCameraRef.current = nextShadowCamera;
135
- setShadowCamera(castShadow ? nextShadowCamera : null);
136
- }
137
- });
138
60
  useEffect(() => {
139
- var _a;
140
- const shadow = (_a = directionalLightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
141
- if (!shadow)
142
- return;
143
- shadow.needsUpdate = true;
144
- shadow.camera.updateProjectionMatrix();
145
- });
146
- useFrame(() => {
147
- if (!directionalLightRef.current || !targetRef.current)
148
- return;
149
- directionalLightRef.current.target.updateMatrixWorld();
150
- if (shadowCamera && castShadow) {
151
- shadowCamera.updateProjectionMatrix();
152
- shadowCamera.updateMatrixWorld();
61
+ if (directionalLightRef.current) {
62
+ shadowCameraRef.current = directionalLightRef.current.shadow.camera;
153
63
  }
154
- });
155
- return (_jsxs(_Fragment, { children: [_jsx("directionalLight", { ref: directionalLightRef, color: color, intensity: intensity, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar, "shadow-camera-top": shadowCameraTop, "shadow-camera-bottom": shadowCameraBottom, "shadow-camera-left": shadowCameraLeft, "shadow-camera-right": shadowCameraRight, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate }), _jsx("object3D", { ref: targetRef, position: targetOffset }), editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { children: _jsx("bufferAttribute", { attach: "attributes-position", args: [new Float32Array([0, 0, 0, targetOffset[0], targetOffset[1], targetOffset[2]]), 3] }) }), _jsx("lineBasicMaterial", { color: color, opacity: 0.6, transparent: true })] })] })), children] }));
64
+ }, []);
65
+ return (_jsxs("group", { children: [_jsxs("directionalLight", Object.assign({ ref: directionalLightRef }, lightProps, {
66
+ // Attach the target object
67
+ target: (_a = targetRef.current) !== null && _a !== void 0 ? _a : undefined, children: [children, editMode && isSelected && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.3, 8, 6] }), _jsx("meshBasicMaterial", { color: merged.color, wireframe: true })] }), _jsxs("mesh", { position: merged.targetOffset, children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: merged.color, wireframe: true, opacity: 0.5, transparent: true })] }), _jsxs("line", { children: [_jsx("bufferGeometry", { children: _jsx("bufferAttribute", { attach: "attributes-position", args: [new Float32Array([0, 0, 0, merged.targetOffset[0], merged.targetOffset[1], merged.targetOffset[2]]), 3] }) }), _jsx("lineBasicMaterial", { color: merged.color, opacity: 0.6, transparent: true })] })] }))] })), _jsx("object3D", { ref: targetRef, position: merged.targetOffset })] }));
156
68
  }
157
69
  const DirectionalLightComponent = {
158
70
  name: 'DirectionalLight',
159
71
  Editor: DirectionalLightComponentEditor,
160
72
  View: DirectionalLightView,
161
- defaultProperties: {}
73
+ defaultProperties: {},
162
74
  };
163
75
  export default DirectionalLightComponent;
@@ -10,6 +10,7 @@ function EnvironmentView({ properties, children, }) {
10
10
  }
11
11
  const EnvironmentComponent = {
12
12
  name: 'Environment',
13
+ disableSiblingComposition: true,
13
14
  Editor: ({ component, onUpdate }) => (_jsxs(FieldGroup, { children: [_jsx(NumberField, { name: "intensity", label: "Intensity", values: component.properties, onChange: onUpdate, min: 0, step: 0.1, fallback: 1 }), _jsx(NumberField, { name: "resolution", label: "Resolution", values: component.properties, onChange: onUpdate, min: 64, step: 64, fallback: 256 })] })),
14
15
  View: EnvironmentView,
15
16
  defaultProperties: {},
@@ -82,6 +82,7 @@ function GeometryComponentView({ properties, children }) {
82
82
  }
83
83
  const GeometryComponent = {
84
84
  name: 'Geometry',
85
+ disableSiblingComposition: 'geometry',
85
86
  Editor: GeometryComponentEditor,
86
87
  View: GeometryComponentView,
87
88
  defaultProperties: {
@@ -57,6 +57,7 @@ interface InputProps {
57
57
  min?: number;
58
58
  max?: number;
59
59
  style?: React.CSSProperties;
60
+ label?: string;
60
61
  }
61
62
  export declare function NumberInput({ value, onChange, step, min, max, style }: InputProps): import("react/jsx-runtime").JSX.Element;
62
63
  export declare function Label({ children }: {
@@ -11,6 +11,7 @@ declare module '@react-three/fiber' {
11
11
  }
12
12
  }
13
13
  export interface MaterialProps extends Omit<MeshStandardMaterialProperties & MeshBasicMaterialProperties, 'args' | 'normalScale' | 'side'> {
14
+ attach?: string;
14
15
  materialType?: 'standard' | 'basic' | 'sprite';
15
16
  transmission?: number;
16
17
  thickness?: number;
@@ -10,7 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
13
- import { createContext, useContext, useMemo, useRef } from 'react';
13
+ import { createContext, useContext, useEffect, useMemo, useRef } from 'react';
14
14
  import { extend } from '@react-three/fiber';
15
15
  import { useFrame } from '@react-three/fiber';
16
16
  import { assetRef, assetRefs } from './ComponentRegistry';
@@ -38,26 +38,6 @@ const MAG_FILTER_MAP = {
38
38
  NearestFilter,
39
39
  LinearFilter,
40
40
  };
41
- function cloneConfiguredTexture({ texture, repeat, repeatCount, offset, colorSpace, generateMipmaps, minFilter, magFilter, }) {
42
- var _a, _b;
43
- const clonedTexture = texture.clone();
44
- if (repeat) {
45
- clonedTexture.wrapS = clonedTexture.wrapT = RepeatWrapping;
46
- if (repeatCount)
47
- clonedTexture.repeat.set(repeatCount[0], repeatCount[1]);
48
- }
49
- else {
50
- clonedTexture.wrapS = clonedTexture.wrapT = ClampToEdgeWrapping;
51
- clonedTexture.repeat.set(1, 1);
52
- }
53
- 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);
54
- clonedTexture.colorSpace = colorSpace;
55
- clonedTexture.generateMipmaps = generateMipmaps;
56
- clonedTexture.minFilter = minFilter;
57
- clonedTexture.magFilter = magFilter;
58
- clonedTexture.needsUpdate = true;
59
- return clonedTexture;
60
- }
61
41
  export function useMaterialOverrides() {
62
42
  return useContext(MaterialOverridesContext);
63
43
  }
@@ -80,6 +60,11 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "", }) {
80
60
  const isStandardMaterial = materialType === 'standard';
81
61
  const isSpriteMaterial = materialType === 'sprite';
82
62
  const fields = [
63
+ {
64
+ name: 'attach',
65
+ type: 'string',
66
+ label: 'Attach',
67
+ },
83
68
  {
84
69
  name: 'materialType',
85
70
  type: 'select',
@@ -208,39 +193,85 @@ function MaterialComponentView({ properties: rawProps }) {
208
193
  const normalScaleProp = materialSource.normalScale;
209
194
  const normalMapTexture = normalMapTextureName ? getTexture(normalMapTextureName) : undefined;
210
195
  // Destructure all material props and separate custom texture handling props
211
- 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"]);
196
+ const { texture: _texture, offset: _offset, repeat: _repeat, repeatCount: _repeatCount, animateOffset: _animateOffset, offsetSpeed: _offsetSpeed, generateMipmaps: _generateMipmaps, minFilter: _minFilter, magFilter: _magFilter, map: _map, materialType: _materialType, attach, 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", "attach", "normalMapTexture", "normalScale", "normalMap", "rotation", "sizeAttenuation", "side"]);
212
197
  const resolvedSide = sideProp ? (_d = SIDE_MAP[sideProp]) !== null && _d !== void 0 ? _d : FrontSide : FrontSide;
213
198
  const resolvedMinFilter = (_e = MIN_FILTER_MAP[minFilter]) !== null && _e !== void 0 ? _e : LinearMipmapLinearFilter;
214
199
  const resolvedMagFilter = (_f = MAG_FILTER_MAP[magFilter]) !== null && _f !== void 0 ? _f : LinearFilter;
215
200
  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]);
216
201
  const finalTexture = useMemo(() => {
217
- if (!texture)
218
- return undefined;
219
- return cloneConfiguredTexture({
220
- texture,
221
- repeat,
222
- repeatCount,
223
- offset,
224
- colorSpace: SRGBColorSpace,
225
- generateMipmaps,
226
- minFilter: resolvedMinFilter,
227
- magFilter: resolvedMagFilter,
228
- });
229
- }, [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]);
202
+ return texture ? texture.clone() : undefined;
203
+ }, [texture]);
204
+ useEffect(() => {
205
+ return () => finalTexture === null || finalTexture === void 0 ? void 0 : finalTexture.dispose();
206
+ }, [finalTexture]);
207
+ useEffect(() => {
208
+ var _a, _b;
209
+ if (!finalTexture)
210
+ return;
211
+ if (repeat) {
212
+ finalTexture.wrapS = finalTexture.wrapT = RepeatWrapping;
213
+ if (repeatCount) {
214
+ finalTexture.repeat.set(repeatCount[0], repeatCount[1]);
215
+ }
216
+ }
217
+ else {
218
+ finalTexture.wrapS = finalTexture.wrapT = ClampToEdgeWrapping;
219
+ finalTexture.repeat.set(1, 1);
220
+ }
221
+ finalTexture.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);
222
+ finalTexture.colorSpace = SRGBColorSpace;
223
+ finalTexture.generateMipmaps = generateMipmaps;
224
+ finalTexture.minFilter = resolvedMinFilter;
225
+ finalTexture.magFilter = resolvedMagFilter;
226
+ finalTexture.needsUpdate = true;
227
+ }, [
228
+ finalTexture,
229
+ repeat,
230
+ repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
231
+ repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
232
+ offset === null || offset === void 0 ? void 0 : offset[0],
233
+ offset === null || offset === void 0 ? void 0 : offset[1],
234
+ generateMipmaps,
235
+ resolvedMinFilter,
236
+ resolvedMagFilter
237
+ ]);
230
238
  const finalNormalMap = useMemo(() => {
231
- if (!normalMapTexture)
232
- return undefined;
233
- return cloneConfiguredTexture({
234
- texture: normalMapTexture,
235
- repeat,
236
- repeatCount,
237
- offset,
238
- colorSpace: NoColorSpace,
239
- generateMipmaps,
240
- minFilter: resolvedMinFilter,
241
- magFilter: resolvedMagFilter,
242
- });
243
- }, [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]);
239
+ return normalMapTexture ? normalMapTexture.clone() : undefined;
240
+ }, [normalMapTexture]);
241
+ useEffect(() => {
242
+ return () => finalNormalMap === null || finalNormalMap === void 0 ? void 0 : finalNormalMap.dispose();
243
+ }, [finalNormalMap]);
244
+ useEffect(() => {
245
+ var _a, _b;
246
+ if (!finalNormalMap)
247
+ return;
248
+ if (repeat) {
249
+ finalNormalMap.wrapS = finalNormalMap.wrapT = RepeatWrapping;
250
+ if (repeatCount) {
251
+ finalNormalMap.repeat.set(repeatCount[0], repeatCount[1]);
252
+ }
253
+ }
254
+ else {
255
+ finalNormalMap.wrapS = finalNormalMap.wrapT = ClampToEdgeWrapping;
256
+ finalNormalMap.repeat.set(1, 1);
257
+ }
258
+ finalNormalMap.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);
259
+ finalNormalMap.colorSpace = NoColorSpace;
260
+ finalNormalMap.generateMipmaps = generateMipmaps;
261
+ finalNormalMap.minFilter = resolvedMinFilter;
262
+ finalNormalMap.magFilter = resolvedMagFilter;
263
+ finalNormalMap.needsUpdate = true;
264
+ }, [
265
+ finalNormalMap,
266
+ repeat,
267
+ repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[0],
268
+ repeatCount === null || repeatCount === void 0 ? void 0 : repeatCount[1],
269
+ offset === null || offset === void 0 ? void 0 : offset[0],
270
+ offset === null || offset === void 0 ? void 0 : offset[1],
271
+ generateMipmaps,
272
+ resolvedMinFilter,
273
+ resolvedMagFilter
274
+ ]);
244
275
  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];
245
276
  useFrame((_, delta) => {
246
277
  var _a, _b;
@@ -259,22 +290,28 @@ function MaterialComponentView({ properties: rawProps }) {
259
290
  const sharedProps = Object.assign(Object.assign({ map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, side: resolvedSide, onUpdate: (material) => {
260
291
  material.needsUpdate = true;
261
292
  } }, materialProps), overrides);
293
+ const materialKey = [
294
+ materialType,
295
+ textureName !== null && textureName !== void 0 ? textureName : 'no-texture',
296
+ normalMapTextureName !== null && normalMapTextureName !== void 0 ? normalMapTextureName : 'no-normal',
297
+ ].join('|');
262
298
  if (materialType === 'basic') {
263
- return _jsx("meshBasicNodeMaterial", Object.assign({ attach: "material" }, sharedProps));
299
+ return _jsx("meshBasicNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps), materialKey);
264
300
  }
265
301
  if (materialType === 'sprite') {
266
302
  const spriteTransparent = materialSource.transparent !== false;
267
- return (_jsx("spriteNodeMaterial", Object.assign({ attach: "material", map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, 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, onUpdate: material => {
303
+ return (_jsx("spriteNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material', map: finalTexture !== null && finalTexture !== void 0 ? finalTexture : null, 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, onUpdate: material => {
268
304
  material.needsUpdate = true;
269
- } }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true })));
305
+ } }, overrides, { rotation: rotation !== null && rotation !== void 0 ? rotation : 0, sizeAttenuation: sizeAttenuation !== null && sizeAttenuation !== void 0 ? sizeAttenuation : true }), materialKey));
270
306
  }
271
- return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: "material" }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, 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 })));
307
+ return (_jsx("meshStandardNodeMaterial", Object.assign({ attach: attach !== null && attach !== void 0 ? attach : 'material' }, sharedProps, { normalMap: finalNormalMap !== null && finalNormalMap !== void 0 ? finalNormalMap : null, 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));
272
308
  }
273
309
  const MaterialComponent = {
274
310
  name: 'Material',
275
311
  Editor: MaterialComponentEditor,
276
312
  View: MaterialComponentView,
277
313
  defaultProperties: {
314
+ attach: 'material',
278
315
  materialType: 'standard',
279
316
  color: '#ffffff',
280
317
  toneMapped: true,
@@ -5,9 +5,10 @@ import { Mesh } from 'three';
5
5
  import { assetRef, assetRefs } from './ComponentRegistry';
6
6
  import { BooleanField, FieldGroup, Label, ListEditor, NumberInput, SelectInput, StringField } from './Input';
7
7
  import { useAssetRuntime } from '../assetRuntime';
8
- import { useEditorContext } from '../PrefabEditor';
8
+ import { useEditorContext, useEditorRef } from '../PrefabEditor';
9
9
  import { getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
10
- import { colors, ui } from '../styles';
10
+ import { base, colors, ui } from '../styles';
11
+ import { decomposeModelToPrefabNodes } from '../modelPrefab';
11
12
  const AXIS_OPTIONS = [
12
13
  { value: 'x', label: 'X' },
13
14
  { value: 'y', label: 'Y' },
@@ -42,8 +43,49 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
42
43
  }
43
44
  function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
44
45
  const { positionSnap } = useEditorContext();
46
+ const editor = useEditorRef();
45
47
  const repeatAxes = getRepeatAxesFromModelProperties(component.properties);
46
- return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: component.properties.filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), !component.properties.instanced ? (_jsxs(_Fragment, { 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, placeholder: "node:click" })) : null] })) : null, component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
48
+ const filename = component.properties.filename;
49
+ const canDecompose = Boolean(node && filename);
50
+ const handleDecompose = () => {
51
+ var _a, _b, _c, _d;
52
+ if (!node || !filename)
53
+ return;
54
+ const model = editor.getModel(filename);
55
+ if (!model) {
56
+ console.warn(`Model is not loaded yet: ${filename}`);
57
+ return;
58
+ }
59
+ const textureRefs = new Map();
60
+ const decomposed = decomposeModelToPrefabNodes(model, {
61
+ idPrefix: node.id,
62
+ getTexturePath: (texture, usage) => {
63
+ const key = `embedded/${node.id}/${usage}/${texture.uuid}`;
64
+ textureRefs.set(key, texture);
65
+ return key;
66
+ },
67
+ });
68
+ textureRefs.forEach((texture, path) => {
69
+ editor.addTexture(path, texture);
70
+ });
71
+ const preservedComponents = Object.entries((_a = node.components) !== null && _a !== void 0 ? _a : {}).reduce((result, [key, entry]) => {
72
+ if (!(entry === null || entry === void 0 ? void 0 : entry.type))
73
+ return result;
74
+ if (entry.type === 'Model' || entry.type === 'Geometry' || entry.type === 'BufferGeometry' || entry.type === 'Material') {
75
+ return result;
76
+ }
77
+ result[key] = entry;
78
+ return result;
79
+ }, {});
80
+ const decomposedComponents = Object.entries((_b = decomposed.components) !== null && _b !== void 0 ? _b : {}).reduce((result, [key, entry]) => {
81
+ if (!(entry === null || entry === void 0 ? void 0 : entry.type) || entry.type === 'Transform')
82
+ return result;
83
+ result[key] = entry;
84
+ return result;
85
+ }, {});
86
+ editor.replaceNode(node.id, Object.assign(Object.assign({}, node), { name: (_c = node.name) !== null && _c !== void 0 ? _c : decomposed.name, components: Object.assign(Object.assign({}, preservedComponents), decomposedComponents), children: (_d = decomposed.children) !== null && _d !== void 0 ? _d : [] }));
87
+ };
88
+ return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath, pickerKey: node === null || node === void 0 ? void 0 : node.id }), _jsx("button", { type: "button", style: Object.assign(Object.assign({}, base.btn), { width: '100%' }), onClick: handleDecompose, disabled: !canDecompose, title: canDecompose ? 'Replace this model node with editable geometry and material nodes' : 'Choose a model before decomposing', children: "Decompose Model" }), _jsx(BooleanField, { name: "instanced", label: "Instanced", values: component.properties, onChange: onUpdate, fallback: false }), !component.properties.instanced ? (_jsxs(_Fragment, { 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, placeholder: "node:click" })) : null] })) : null, component.properties.instanced && (_jsxs(_Fragment, { children: [_jsx(BooleanField, { name: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
47
89
  }
48
90
  // View for Model component
49
91
  function ModelComponentView({ properties, children }) {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
2
+ import { useRef } from 'react';
3
3
  import { useHelper } from '@react-three/drei';
4
4
  import { PointLightHelper } from 'three';
5
5
  import { useNode } from '../assetRuntime';
@@ -25,31 +25,25 @@ function PointLightComponentEditor({ component, onUpdate }) {
25
25
  function PointLightView({ properties, children }) {
26
26
  const { editMode, isSelected } = useNode();
27
27
  const merged = mergeWithDefaults(pointLightDefaults, properties);
28
- const color = merged.color;
29
- const intensity = merged.intensity;
30
- const distance = merged.distance;
31
- const decay = merged.decay;
32
- const castShadow = merged.castShadow;
33
- const shadowMapSize = merged.shadowMapSize;
34
- const shadowBias = merged.shadowBias;
35
- const shadowNormalBias = merged.shadowNormalBias;
36
- const shadowAutoUpdate = merged.shadowAutoUpdate;
37
- const shadowCameraNear = merged.shadowCameraNear;
38
- const shadowCameraFar = merged.shadowCameraFar;
28
+ const lightProps = {
29
+ color: merged.color,
30
+ intensity: merged.intensity,
31
+ distance: merged.distance,
32
+ decay: merged.decay,
33
+ castShadow: merged.castShadow,
34
+ "shadow-mapSize-width": merged.shadowMapSize,
35
+ "shadow-mapSize-height": merged.shadowMapSize,
36
+ "shadow-bias": merged.shadowBias,
37
+ "shadow-normalBias": merged.shadowNormalBias,
38
+ "shadow-autoUpdate": merged.shadowAutoUpdate,
39
+ "shadow-camera-near": merged.shadowCameraNear,
40
+ "shadow-camera-far": merged.shadowCameraFar,
41
+ };
39
42
  const lightRef = useRef(null);
40
- const helperTarget = editMode && isSelected && lightRef.current
41
- ? { current: lightRef.current }
42
- : null;
43
- useHelper(helperTarget, PointLightHelper, 0.5, color);
44
- useEffect(() => {
45
- var _a;
46
- const shadow = (_a = lightRef.current) === null || _a === void 0 ? void 0 : _a.shadow;
47
- if (!shadow)
48
- return;
49
- shadow.needsUpdate = true;
50
- shadow.camera.updateProjectionMatrix();
51
- });
52
- return (_jsxs(_Fragment, { children: [_jsx("pointLight", { ref: lightRef, color: color, intensity: intensity, distance: distance, decay: decay, castShadow: castShadow, "shadow-mapSize-width": shadowMapSize, "shadow-mapSize-height": shadowMapSize, "shadow-bias": shadowBias, "shadow-normalBias": shadowNormalBias, "shadow-autoUpdate": shadowAutoUpdate, "shadow-camera-near": shadowCameraNear, "shadow-camera-far": shadowCameraFar }), editMode && isSelected ? (_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 10, 8] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] })) : null, children] }));
43
+ const showHelper = editMode && isSelected && lightRef.current;
44
+ const helperTarget = showHelper && lightRef.current ? { current: lightRef.current } : null;
45
+ useHelper(helperTarget, PointLightHelper, 0.5);
46
+ return (_jsx("group", { children: _jsxs("pointLight", Object.assign({ ref: lightRef }, lightProps, { children: [children, editMode && isSelected && (_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 10, 8] }), _jsx("meshBasicMaterial", { color: merged.color, wireframe: true })] }))] })) }));
53
47
  }
54
48
  const PointLightComponent = {
55
49
  name: 'PointLight',