react-three-game 0.0.66 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/shared/GameCanvas.js +1 -1
- package/dist/tools/assetviewer/page.d.ts +13 -2
- package/dist/tools/assetviewer/page.js +57 -3
- package/dist/tools/dragdrop/index.d.ts +1 -1
- package/dist/tools/dragdrop/modelLoader.d.ts +2 -0
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +6 -5
- package/dist/tools/prefabeditor/PrefabRoot.js +16 -7
- package/dist/tools/prefabeditor/components/MaterialComponent.js +1 -66
- package/dist/tools/prefabeditor/components/ModelComponent.js +3 -78
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +9 -7
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -19,4 +19,4 @@ export type { GameEventType, GameEventMap, GameEventPayload, PhysicsEventType, I
|
|
|
19
19
|
export { entityEvents, useEntityEvent } from './tools/prefabeditor/GameEvents';
|
|
20
20
|
export type { EntityEventType, EntityEventPayload } from './tools/prefabeditor/GameEvents';
|
|
21
21
|
export * from './tools/dragdrop';
|
|
22
|
-
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
22
|
+
export { TextureListViewer, ModelListViewer, SoundListViewer, TexturePicker, ModelPicker, SingleTextureViewer, SingleModelViewer, SingleSoundViewer, SharedCanvas, } from './tools/assetviewer/page';
|
package/dist/index.js
CHANGED
|
@@ -19,4 +19,4 @@ export { gameEvents, useGameEvent, getEntityIdFromRigidBody } from './tools/pref
|
|
|
19
19
|
export { entityEvents, useEntityEvent } from './tools/prefabeditor/GameEvents';
|
|
20
20
|
// Asset Tools
|
|
21
21
|
export * from './tools/dragdrop';
|
|
22
|
-
export { TextureListViewer, ModelListViewer, SoundListViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
22
|
+
export { TextureListViewer, ModelListViewer, SoundListViewer, TexturePicker, ModelPicker, SingleTextureViewer, SingleModelViewer, SingleSoundViewer, SharedCanvas, } from './tools/assetviewer/page';
|
|
@@ -33,7 +33,7 @@ extend({
|
|
|
33
33
|
export default function GameCanvas(_a) {
|
|
34
34
|
var { loader = false, children, glConfig, canvasRef, onCreated, style } = _a, props = __rest(_a, ["loader", "children", "glConfig", "canvasRef", "onCreated", "style"]);
|
|
35
35
|
const [frameloop, setFrameloop] = useState("never");
|
|
36
|
-
return _jsx(_Fragment, { children: _jsxs(Canvas, Object.assign({ style: Object.assign({ touchAction: 'none', userSelect: 'none' }, style), shadows: { type: PCFShadowMap
|
|
36
|
+
return _jsx(_Fragment, { children: _jsxs(Canvas, Object.assign({ style: Object.assign({ touchAction: 'none', userSelect: 'none' }, style), shadows: { type: PCFShadowMap }, frameloop: frameloop, gl: (_a) => __awaiter(this, [_a], void 0, function* ({ canvas }) {
|
|
37
37
|
const renderer = new WebGPURenderer(Object.assign({ canvas: canvas,
|
|
38
38
|
// @ts-expect-error futuristic
|
|
39
39
|
shadowMap: true, antialias: true }, glConfig));
|
|
@@ -19,14 +19,25 @@ interface SoundListViewerProps {
|
|
|
19
19
|
basePath?: string;
|
|
20
20
|
}
|
|
21
21
|
export declare function SoundListViewer({ files, selected, onSelect, basePath }: SoundListViewerProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function TexturePicker({ value, onChange, basePath }: {
|
|
23
|
+
value: string | undefined;
|
|
24
|
+
onChange: (value: string | undefined) => void;
|
|
25
|
+
basePath?: string;
|
|
26
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export declare function ModelPicker({ value, onChange, basePath, pickerKey }: {
|
|
28
|
+
value: string | undefined;
|
|
29
|
+
onChange: (value: string | undefined) => void;
|
|
30
|
+
basePath?: string;
|
|
31
|
+
pickerKey?: string;
|
|
32
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
22
33
|
export declare function SingleTextureViewer({ file, basePath }: {
|
|
23
34
|
file?: string;
|
|
24
35
|
basePath?: string;
|
|
25
|
-
}): import("react/jsx-runtime").JSX.Element
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
26
37
|
export declare function SingleModelViewer({ file, basePath }: {
|
|
27
38
|
file?: string;
|
|
28
39
|
basePath?: string;
|
|
29
|
-
}): import("react/jsx-runtime").JSX.Element
|
|
40
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
30
41
|
export declare function SingleSoundViewer({ file, basePath }: {
|
|
31
42
|
file?: string;
|
|
32
43
|
basePath?: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Canvas } from "@react-three/fiber";
|
|
3
3
|
import { OrbitControls, View, PerspectiveCamera } from "@react-three/drei";
|
|
4
|
-
import { Component as ReactComponent, Suspense, useEffect, useState, useRef } from "react";
|
|
4
|
+
import { Component as ReactComponent, Suspense, useEffect, useLayoutEffect, useState, useRef } from "react";
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
import { TextureLoader } from "three";
|
|
6
7
|
import { loadModel } from "../dragdrop";
|
|
7
8
|
class ErrorBoundary extends ReactComponent {
|
|
@@ -155,15 +156,68 @@ function SoundCard({ file, onSelect, basePath = "" }) {
|
|
|
155
156
|
const fullPath = basePath ? `/${basePath}${file}` : file;
|
|
156
157
|
return (_jsxs("div", { onClick: () => onSelect(file), style: { aspectRatio: '1 / 1', backgroundColor: '#374151', color: '#f9fafb', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }, children: [_jsx("div", { style: styles.iconLarge, children: "\uD83D\uDD0A" }), _jsx("div", { style: { color: '#f9fafb', fontSize: 12, padding: '0 4px', marginTop: 4, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'center', width: '100%' }, children: fileName })] }));
|
|
157
158
|
}
|
|
159
|
+
const PICKER_POPUP_WIDTH = 260;
|
|
160
|
+
const PICKER_POPUP_HEIGHT = 360;
|
|
161
|
+
function AssetPicker({ value, onChange, basePath, manifestFolder, preview, renderList, rootStyle, controlsStyle, changeButtonStyle, clearButtonStyle, popupStyle, }) {
|
|
162
|
+
const [files, setFiles] = useState([]);
|
|
163
|
+
const [showPicker, setShowPicker] = useState(false);
|
|
164
|
+
const [resolvedPopupStyle, setResolvedPopupStyle] = useState(null);
|
|
165
|
+
const triggerRef = useRef(null);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
fetch(`${basePath}/${manifestFolder}/manifest.json`)
|
|
168
|
+
.then(r => r.json())
|
|
169
|
+
.then(data => setFiles(Array.isArray(data) ? data : data.files || []))
|
|
170
|
+
.catch(console.error);
|
|
171
|
+
}, [basePath, manifestFolder]);
|
|
172
|
+
useLayoutEffect(() => {
|
|
173
|
+
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
174
|
+
return;
|
|
175
|
+
const updatePosition = () => {
|
|
176
|
+
var _a;
|
|
177
|
+
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
178
|
+
if (!rect)
|
|
179
|
+
return;
|
|
180
|
+
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
181
|
+
const fallbackLeft = rect.right + 8;
|
|
182
|
+
const fitsLeft = preferredLeft >= 8;
|
|
183
|
+
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
184
|
+
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
185
|
+
setResolvedPopupStyle(Object.assign({ position: 'fixed', left,
|
|
186
|
+
top, padding: 12, width: PICKER_POPUP_WIDTH, height: PICKER_POPUP_HEIGHT, overflow: 'hidden', zIndex: 1000, boxShadow: '0 4px 16px rgba(0,0,0,0.6)', background: '#111827', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 6 }, popupStyle));
|
|
187
|
+
};
|
|
188
|
+
updatePosition();
|
|
189
|
+
window.addEventListener('resize', updatePosition);
|
|
190
|
+
window.addEventListener('scroll', updatePosition, true);
|
|
191
|
+
return () => {
|
|
192
|
+
window.removeEventListener('resize', updatePosition);
|
|
193
|
+
window.removeEventListener('scroll', updatePosition, true);
|
|
194
|
+
};
|
|
195
|
+
}, [popupStyle, showPicker]);
|
|
196
|
+
return (_jsxs("div", { style: rootStyle, children: [preview, _jsxs("div", { style: controlsStyle, children: [_jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: changeButtonStyle, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => onChange(undefined), style: clearButtonStyle, children: "Clear" })] }), showPicker && resolvedPopupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: resolvedPopupStyle, onMouseLeave: () => setShowPicker(false), children: renderList({
|
|
197
|
+
files,
|
|
198
|
+
value,
|
|
199
|
+
onSelect: (file) => {
|
|
200
|
+
onChange(file);
|
|
201
|
+
setShowPicker(false);
|
|
202
|
+
},
|
|
203
|
+
basePath,
|
|
204
|
+
}) }), document.body)] }));
|
|
205
|
+
}
|
|
206
|
+
export function TexturePicker({ value, onChange, basePath = "" }) {
|
|
207
|
+
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "textures", rootStyle: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, changeButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4 }, clearButtonStyle: { padding: '4px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(255,255,255,0.12)', borderRadius: 3, marginTop: 4, marginLeft: 4 }, preview: _jsx(SingleTextureViewer, { file: value, basePath: basePath }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(TextureListViewer, { files: files, selected: selectedValue || undefined, onSelect: onSelect, basePath: currentBasePath })) }));
|
|
208
|
+
}
|
|
209
|
+
export function ModelPicker({ value, onChange, basePath = "", pickerKey }) {
|
|
210
|
+
return (_jsx(AssetPicker, { value: value, onChange: onChange, basePath: basePath, manifestFolder: "models", rootStyle: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, controlsStyle: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, changeButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, clearButtonStyle: { width: '100%', padding: '6px 8px', backgroundColor: '#1f2937', color: 'inherit', fontSize: 10, cursor: 'pointer', border: '1px solid rgba(34, 211, 238, 0.3)' }, popupStyle: { background: 'rgba(0,0,0,0.9)', border: '1px solid rgba(34, 211, 238, 0.3)' }, preview: _jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), renderList: ({ files, value: selectedValue, onSelect, basePath: currentBasePath }) => (_jsx(ModelListViewer, { files: files, selected: selectedValue ? `/${selectedValue}` : undefined, onSelect: (file) => onSelect(file.startsWith('/') ? file.slice(1) : file), basePath: currentBasePath }, pickerKey)) }));
|
|
211
|
+
}
|
|
158
212
|
// Single Asset Viewer Components - display only one selected asset
|
|
159
213
|
export function SingleTextureViewer({ file, basePath = "" }) {
|
|
160
214
|
if (!file)
|
|
161
|
-
return
|
|
215
|
+
return _jsx("div", { style: { width: 60, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
|
|
162
216
|
return (_jsxs(_Fragment, { children: [_jsx(TextureCard, { file: file, basePath: basePath, onSelect: () => { } }), _jsx(SharedCanvas, {})] }));
|
|
163
217
|
}
|
|
164
218
|
export function SingleModelViewer({ file, basePath = "" }) {
|
|
165
219
|
if (!file)
|
|
166
|
-
return
|
|
220
|
+
return _jsx("div", { style: { width: 112, aspectRatio: '1 / 1', backgroundColor: '#1f2937', border: '1px dashed rgba(255,255,255,0.12)' } });
|
|
167
221
|
return (_jsxs(_Fragment, { children: [_jsx(ModelCard, { file: file, basePath: basePath, onSelect: () => { }, size: 112 }), _jsx(SharedCanvas, {})] }));
|
|
168
222
|
}
|
|
169
223
|
export function SingleSoundViewer({ file, basePath = "" }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { DragDropLoader, FilePicker, loadFiles } from "./DragDropLoader";
|
|
2
2
|
export type { AssetLoadOptions, DragDropLoaderProps, FilePickerProps } from "./DragDropLoader";
|
|
3
3
|
export { loadModel, loadTexture, parseModelFromFile, parseTextureFromFile } from "./modelLoader";
|
|
4
|
-
export type { LoadedModel, LoadedTexture, ModelLoadResult, ProgressCallback, TextureLoadResult } from "./modelLoader";
|
|
4
|
+
export type { LoadedModel, LoadedTexture, LoadedModels, LoadedTextures, ModelLoadResult, ProgressCallback, TextureLoadResult } from "./modelLoader";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Object3D, Texture } from "three";
|
|
2
2
|
export type LoadedModel = Object3D;
|
|
3
3
|
export type LoadedTexture = Texture;
|
|
4
|
+
export type LoadedModels = Record<string, LoadedModel>;
|
|
5
|
+
export type LoadedTextures = Record<string, LoadedTexture>;
|
|
4
6
|
export type ModelLoadResult = {
|
|
5
7
|
success: boolean;
|
|
6
8
|
model?: LoadedModel;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Group, Matrix4, Object3D
|
|
1
|
+
import { Group, Matrix4, Object3D } from "three";
|
|
2
2
|
import { ThreeEvent } from "@react-three/fiber";
|
|
3
3
|
import { Prefab, GameObject as GameObjectType } from "./types";
|
|
4
|
+
import { LoadedModels, LoadedTextures } from "../dragdrop";
|
|
4
5
|
export interface PrefabRootRef {
|
|
5
6
|
root: Group | null;
|
|
6
7
|
rigidBodyRefs: Map<string, any>;
|
|
@@ -16,8 +17,8 @@ export declare const PrefabRoot: import("react").ForwardRefExoticComponent<{
|
|
|
16
17
|
onSelectedObjectChange?: (object: Object3D | null) => void;
|
|
17
18
|
onFocusNode?: (nodeId: string) => void;
|
|
18
19
|
basePath?: string;
|
|
19
|
-
injectedModels?:
|
|
20
|
-
injectedTextures?:
|
|
20
|
+
injectedModels?: LoadedModels;
|
|
21
|
+
injectedTextures?: LoadedTextures;
|
|
21
22
|
} & import("react").RefAttributes<PrefabRootRef>>;
|
|
22
23
|
export declare function GameObjectRenderer(props: RendererProps): import("react/jsx-runtime").JSX.Element | null;
|
|
23
24
|
interface RendererProps {
|
|
@@ -27,8 +28,8 @@ interface RendererProps {
|
|
|
27
28
|
onClick?: (event: ThreeEvent<PointerEvent>, entity: GameObjectType) => void;
|
|
28
29
|
registerRef: (id: string, obj: Object3D | null) => void;
|
|
29
30
|
registerRigidBodyRef: (id: string, rb: any) => void;
|
|
30
|
-
loadedModels:
|
|
31
|
-
loadedTextures:
|
|
31
|
+
loadedModels: LoadedModels;
|
|
32
|
+
loadedTextures: LoadedTextures;
|
|
32
33
|
editMode?: boolean;
|
|
33
34
|
parentMatrix?: Matrix4;
|
|
34
35
|
}
|
|
@@ -57,13 +57,22 @@ export const PrefabRoot = forwardRef(({ editMode, data, selectedId, onSelect, on
|
|
|
57
57
|
const modelsToLoad = new Set();
|
|
58
58
|
const texturesToLoad = new Set();
|
|
59
59
|
walk(data.root, node => {
|
|
60
|
-
var _a
|
|
61
|
-
((
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
var _a;
|
|
61
|
+
Object.values((_a = node.components) !== null && _a !== void 0 ? _a : {}).forEach(component => {
|
|
62
|
+
var _a, _b, _c, _d;
|
|
63
|
+
if (!(component === null || component === void 0 ? void 0 : component.type))
|
|
64
|
+
return;
|
|
65
|
+
if (component.type === 'Model' && ((_a = component.properties) === null || _a === void 0 ? void 0 : _a.filename)) {
|
|
66
|
+
modelsToLoad.add(component.properties.filename);
|
|
67
|
+
}
|
|
68
|
+
if (component.type === 'Material') {
|
|
69
|
+
((_b = component.properties) === null || _b === void 0 ? void 0 : _b.texture) && texturesToLoad.add(component.properties.texture);
|
|
70
|
+
((_c = component.properties) === null || _c === void 0 ? void 0 : _c.normalMapTexture) && texturesToLoad.add(component.properties.normalMapTexture);
|
|
71
|
+
}
|
|
72
|
+
if (component.type === 'SpotLight' && ((_d = component.properties) === null || _d === void 0 ? void 0 : _d.map)) {
|
|
73
|
+
texturesToLoad.add(component.properties.map);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
67
76
|
});
|
|
68
77
|
modelsToLoad.forEach((file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
69
78
|
if (availableModels[file] || loading.current.has(file))
|
|
@@ -10,81 +10,16 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { SingleTextureViewer, TextureListViewer } from '../../assetviewer/page';
|
|
14
13
|
import { extend } from '@react-three/fiber';
|
|
15
|
-
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
16
|
-
import { createPortal } from 'react-dom';
|
|
17
14
|
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
18
|
-
import { colors } from '../styles';
|
|
19
15
|
import { useMemo } from 'react';
|
|
20
16
|
import { MeshBasicNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu';
|
|
17
|
+
import { TexturePicker } from '../../assetviewer/page';
|
|
21
18
|
import { RepeatWrapping, ClampToEdgeWrapping, SRGBColorSpace, LinearSRGBColorSpace, Vector2, NearestFilter, LinearFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapNearestFilter, LinearMipmapLinearFilter, FrontSide, BackSide, DoubleSide, } from 'three';
|
|
22
|
-
const PICKER_POPUP_WIDTH = 260;
|
|
23
|
-
const PICKER_POPUP_HEIGHT = 360;
|
|
24
19
|
extend({
|
|
25
20
|
MeshBasicNodeMaterial,
|
|
26
21
|
MeshStandardNodeMaterial,
|
|
27
22
|
});
|
|
28
|
-
function TexturePicker({ value, onChange, basePath }) {
|
|
29
|
-
const [textureFiles, setTextureFiles] = useState([]);
|
|
30
|
-
const [showPicker, setShowPicker] = useState(false);
|
|
31
|
-
const [popupStyle, setPopupStyle] = useState(null);
|
|
32
|
-
const triggerRef = useRef(null);
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
fetch(`${basePath}/textures/manifest.json`)
|
|
35
|
-
.then(r => r.json())
|
|
36
|
-
.then(data => setTextureFiles(Array.isArray(data) ? data : data.files || []))
|
|
37
|
-
.catch(console.error);
|
|
38
|
-
}, [basePath]);
|
|
39
|
-
useLayoutEffect(() => {
|
|
40
|
-
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
41
|
-
return;
|
|
42
|
-
const updatePosition = () => {
|
|
43
|
-
var _a;
|
|
44
|
-
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
45
|
-
if (!rect)
|
|
46
|
-
return;
|
|
47
|
-
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
48
|
-
const fallbackLeft = rect.right + 8;
|
|
49
|
-
const fitsLeft = preferredLeft >= 8;
|
|
50
|
-
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
51
|
-
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
52
|
-
setPopupStyle({
|
|
53
|
-
position: 'fixed',
|
|
54
|
-
left,
|
|
55
|
-
top,
|
|
56
|
-
background: colors.bg,
|
|
57
|
-
padding: 12,
|
|
58
|
-
border: `1px solid ${colors.border}`,
|
|
59
|
-
borderRadius: 6,
|
|
60
|
-
width: PICKER_POPUP_WIDTH,
|
|
61
|
-
height: PICKER_POPUP_HEIGHT,
|
|
62
|
-
overflow: 'hidden',
|
|
63
|
-
zIndex: 1000,
|
|
64
|
-
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
updatePosition();
|
|
68
|
-
window.addEventListener('resize', updatePosition);
|
|
69
|
-
window.addEventListener('scroll', updatePosition, true);
|
|
70
|
-
return () => {
|
|
71
|
-
window.removeEventListener('resize', updatePosition);
|
|
72
|
-
window.removeEventListener('scroll', updatePosition, true);
|
|
73
|
-
};
|
|
74
|
-
}, [showPicker]);
|
|
75
|
-
// Only show 3D preview for server-hosted textures (starting with / or http)
|
|
76
|
-
const canPreview = value && (value.startsWith('/') || value.startsWith('http'));
|
|
77
|
-
return (_jsxs("div", { style: { maxHeight: 128, overflow: 'visible', position: 'relative', display: 'flex', alignItems: 'center' }, children: [canPreview
|
|
78
|
-
? _jsx(SingleTextureViewer, { file: value, basePath: basePath })
|
|
79
|
-
: value
|
|
80
|
-
? _jsx("span", { style: { fontSize: 10, opacity: 0.6, maxWidth: 100, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: value })
|
|
81
|
-
: null, _jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4 }, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
82
|
-
onChange(undefined);
|
|
83
|
-
}, style: { padding: '4px 8px', backgroundColor: colors.bgLight, color: 'inherit', fontSize: 10, cursor: 'pointer', border: `1px solid ${colors.border}`, borderRadius: 3, marginTop: 4, marginLeft: 4 }, children: "Clear" }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(TextureListViewer, { files: textureFiles, selected: value || undefined, onSelect: (file) => {
|
|
84
|
-
onChange(file);
|
|
85
|
-
setShowPicker(false);
|
|
86
|
-
}, basePath: basePath }) }), document.body)] }));
|
|
87
|
-
}
|
|
88
23
|
function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
89
24
|
var _a;
|
|
90
25
|
const materialType = (_a = component.properties.materialType) !== null && _a !== void 0 ? _a : 'standard';
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { useContext,
|
|
4
|
-
import { createPortal } from 'react-dom';
|
|
2
|
+
import { ModelPicker } from '../../assetviewer/page';
|
|
3
|
+
import { useContext, useMemo } from 'react';
|
|
5
4
|
import { BooleanField, FieldGroup, Label, NumberInput, SelectInput } from './Input';
|
|
6
5
|
import { EditorContext } from '../EditorContext';
|
|
7
6
|
import { DEFAULT_REPEAT_AXES, getRepeatAxesFromModelProperties, normalizeRepeatAxes } from '../InstanceProvider';
|
|
8
7
|
import { colors } from '../styles';
|
|
9
|
-
const PICKER_POPUP_WIDTH = 260;
|
|
10
|
-
const PICKER_POPUP_HEIGHT = 360;
|
|
11
8
|
const AXIS_OPTIONS = [
|
|
12
9
|
{ value: 'x', label: 'X' },
|
|
13
10
|
{ value: 'y', label: 'Y' },
|
|
@@ -71,84 +68,12 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
|
|
|
71
68
|
}, title: "Remove repeat axis", children: "\u00D7" })) : null] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("div", { children: [_jsx(Label, { children: "Count" }), _jsx(NumberInput, { value: axisConfig.count, onChange: (count) => updateAxis(index, { count: Math.max(1, Math.floor(count)) }), step: 1, min: 1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { children: [_jsx(Label, { children: "Offset" }), _jsx(NumberInput, { value: axisConfig.offset, onChange: (offset) => updateAxis(index, { offset: quantize(offset, positionSnap) }), step: positionSnap > 0 ? positionSnap : 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] })] }, `${axisConfig.axis}-${index}`));
|
|
72
69
|
})] }));
|
|
73
70
|
}
|
|
74
|
-
function ModelPicker({ value, onChange, basePath, nodeId }) {
|
|
75
|
-
const [modelFiles, setModelFiles] = useState([]);
|
|
76
|
-
const [showPicker, setShowPicker] = useState(false);
|
|
77
|
-
const [popupStyle, setPopupStyle] = useState(null);
|
|
78
|
-
const triggerRef = useRef(null);
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
fetch(`${basePath}/models/manifest.json`)
|
|
81
|
-
.then(r => r.json())
|
|
82
|
-
.then(data => setModelFiles(Array.isArray(data) ? data : data.files || []))
|
|
83
|
-
.catch(console.error);
|
|
84
|
-
}, [basePath]);
|
|
85
|
-
useLayoutEffect(() => {
|
|
86
|
-
if (!showPicker || !triggerRef.current || typeof window === 'undefined')
|
|
87
|
-
return;
|
|
88
|
-
const updatePosition = () => {
|
|
89
|
-
var _a;
|
|
90
|
-
const rect = (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
91
|
-
if (!rect)
|
|
92
|
-
return;
|
|
93
|
-
const preferredLeft = rect.left - PICKER_POPUP_WIDTH - 8;
|
|
94
|
-
const fallbackLeft = rect.right + 8;
|
|
95
|
-
const fitsLeft = preferredLeft >= 8;
|
|
96
|
-
const left = fitsLeft ? preferredLeft : Math.min(fallbackLeft, window.innerWidth - PICKER_POPUP_WIDTH - 8);
|
|
97
|
-
const top = Math.min(Math.max(8, rect.top), window.innerHeight - PICKER_POPUP_HEIGHT - 8);
|
|
98
|
-
setPopupStyle({
|
|
99
|
-
position: 'fixed',
|
|
100
|
-
left,
|
|
101
|
-
top,
|
|
102
|
-
background: 'rgba(0,0,0,0.9)',
|
|
103
|
-
padding: 12,
|
|
104
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
105
|
-
borderRadius: 6,
|
|
106
|
-
width: PICKER_POPUP_WIDTH,
|
|
107
|
-
height: PICKER_POPUP_HEIGHT,
|
|
108
|
-
overflow: 'hidden',
|
|
109
|
-
zIndex: 1000,
|
|
110
|
-
boxShadow: '0 4px 16px rgba(0,0,0,0.6)',
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
updatePosition();
|
|
114
|
-
window.addEventListener('resize', updatePosition);
|
|
115
|
-
window.addEventListener('scroll', updatePosition, true);
|
|
116
|
-
return () => {
|
|
117
|
-
window.removeEventListener('resize', updatePosition);
|
|
118
|
-
window.removeEventListener('scroll', updatePosition, true);
|
|
119
|
-
};
|
|
120
|
-
}, [showPicker]);
|
|
121
|
-
const handleModelSelect = (file) => {
|
|
122
|
-
const filename = file.startsWith('/') ? file.slice(1) : file;
|
|
123
|
-
onChange(filename);
|
|
124
|
-
setShowPicker(false);
|
|
125
|
-
};
|
|
126
|
-
return (_jsxs("div", { style: { maxHeight: 160, overflow: 'visible', position: 'relative', display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center' }, children: [_jsx("div", { style: { flex: '0 0 auto' }, children: _jsx(SingleModelViewer, { file: value ? `/${value}` : undefined, basePath: basePath }) }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6, flex: '0 0 84px', minWidth: 84, justifyContent: 'flex-end' }, children: [_jsx("button", { ref: triggerRef, onClick: () => setShowPicker(!showPicker), style: {
|
|
127
|
-
width: '100%',
|
|
128
|
-
padding: '6px 8px',
|
|
129
|
-
backgroundColor: '#1f2937',
|
|
130
|
-
color: 'inherit',
|
|
131
|
-
fontSize: 10,
|
|
132
|
-
cursor: 'pointer',
|
|
133
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
134
|
-
}, children: showPicker ? 'Cancel' : 'Change' }), _jsx("button", { onClick: () => {
|
|
135
|
-
onChange(undefined);
|
|
136
|
-
}, style: {
|
|
137
|
-
width: '100%',
|
|
138
|
-
padding: '6px 8px',
|
|
139
|
-
backgroundColor: '#1f2937',
|
|
140
|
-
color: 'inherit',
|
|
141
|
-
fontSize: 10,
|
|
142
|
-
cursor: 'pointer',
|
|
143
|
-
border: '1px solid rgba(34, 211, 238, 0.3)',
|
|
144
|
-
}, children: "Clear" })] }), showPicker && popupStyle && typeof document !== 'undefined' && createPortal(_jsx("div", { style: popupStyle, onMouseLeave: () => setShowPicker(false), children: _jsx(ModelListViewer, { files: modelFiles, selected: value ? `/${value}` : undefined, onSelect: handleModelSelect, basePath: basePath }, nodeId) }), document.body)] }));
|
|
145
|
-
}
|
|
146
71
|
function ModelComponentEditor({ component, node, onUpdate, basePath = "" }) {
|
|
147
72
|
var _a;
|
|
148
73
|
const editorContext = useContext(EditorContext);
|
|
149
74
|
const positionSnap = (_a = editorContext === null || editorContext === void 0 ? void 0 : editorContext.positionSnap) !== null && _a !== void 0 ? _a : 0.5;
|
|
150
75
|
const repeatAxes = getRepeatAxesFromModelProperties(component.properties);
|
|
151
|
-
return (_jsxs(FieldGroup, { children: [_jsx(ModelPicker, { value: component.properties.filename, onChange: (filename) => onUpdate({ filename }), basePath: basePath,
|
|
76
|
+
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: "repeat", label: "Repeat", values: component.properties, onChange: onUpdate, fallback: false }), component.properties.repeat && (_jsx(RepeatAxisEditor, { axes: repeatAxes, onChange: (nextAxes) => onUpdate({ repeatAxes: nextAxes }), positionSnap: positionSnap }))] }))] }));
|
|
152
77
|
}
|
|
153
78
|
// View for Model component
|
|
154
79
|
function ModelComponentView({ properties, loadedModels, children }) {
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { BooleanField, ColorField, FieldGroup, NumberField } from "./Input";
|
|
3
|
+
import { BooleanField, ColorField, FieldGroup, Label, NumberField } from "./Input";
|
|
4
4
|
import { SpotLightHelper } from "three";
|
|
5
5
|
import { useFrame } from "@react-three/fiber";
|
|
6
|
+
import { TexturePicker } from "../../assetviewer/page";
|
|
6
7
|
const spotLightDefaults = {
|
|
7
8
|
color: '#ffffff',
|
|
8
|
-
intensity:
|
|
9
|
-
angle:
|
|
9
|
+
intensity: 200,
|
|
10
|
+
angle: 0.5,
|
|
10
11
|
penumbra: 0.5,
|
|
11
12
|
distance: 100,
|
|
12
13
|
castShadow: true,
|
|
13
14
|
};
|
|
14
|
-
function SpotLightComponentEditor({ component, onUpdate }) {
|
|
15
|
+
function SpotLightComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
15
16
|
const values = Object.assign(Object.assign({}, spotLightDefaults), component.properties);
|
|
16
|
-
return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step:
|
|
17
|
+
return (_jsxs(FieldGroup, { children: [_jsx(ColorField, { name: "color", label: "Color", values: values, onChange: onUpdate }), _jsx(NumberField, { name: "intensity", label: "Intensity", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 200 }), _jsx(NumberField, { name: "angle", label: "Angle", values: values, onChange: onUpdate, min: 0, max: Math.PI, step: 0.05, fallback: 0.5 }), _jsx(NumberField, { name: "penumbra", label: "Penumbra", values: values, onChange: onUpdate, min: 0, max: 1, step: 0.05, fallback: 0.5 }), _jsx(NumberField, { name: "distance", label: "Distance", values: values, onChange: onUpdate, min: 0, step: 1, fallback: 100 }), _jsx(BooleanField, { name: "castShadow", label: "Cast Shadow", values: values, onChange: onUpdate, fallback: true }), _jsxs("div", { children: [_jsx(Label, { children: "Texture Map" }), _jsx(TexturePicker, { value: values.map, onChange: (map) => onUpdate({ map }), basePath: basePath })] })] }));
|
|
17
18
|
}
|
|
18
|
-
function SpotLightView({ properties, editMode, isSelected }) {
|
|
19
|
+
function SpotLightView({ properties, editMode, isSelected, loadedTextures }) {
|
|
19
20
|
const merged = Object.assign(Object.assign({}, spotLightDefaults), properties);
|
|
20
21
|
const color = merged.color;
|
|
21
22
|
const intensity = merged.intensity;
|
|
@@ -23,6 +24,7 @@ function SpotLightView({ properties, editMode, isSelected }) {
|
|
|
23
24
|
const penumbra = merged.penumbra;
|
|
24
25
|
const distance = merged.distance;
|
|
25
26
|
const castShadow = merged.castShadow;
|
|
27
|
+
const textureMap = merged.map && loadedTextures ? loadedTextures[merged.map] : undefined;
|
|
26
28
|
const spotLightRef = useRef(null);
|
|
27
29
|
const targetRef = useRef(null);
|
|
28
30
|
const [spotLight, setSpotLight] = useState(null);
|
|
@@ -43,7 +45,7 @@ function SpotLightView({ properties, editMode, isSelected }) {
|
|
|
43
45
|
spotLightHelper.update();
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
|
-
return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, castShadow: castShadow, "shadow-mapSize-width": 1024, "shadow-mapSize-height": 1024, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), editMode && isSelected && spotLightHelper && (_jsx("primitive", { object: spotLightHelper })), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
|
|
48
|
+
return (_jsxs(_Fragment, { children: [_jsx("spotLight", { ref: spotLightRef, color: color, intensity: intensity, angle: angle, penumbra: penumbra, distance: distance, map: textureMap, castShadow: castShadow, "shadow-mapSize-width": 1024, "shadow-mapSize-height": 1024, "shadow-bias": -0.0001, "shadow-normalBias": 0.02 }), editMode && isSelected && spotLightHelper && (_jsx("primitive", { object: spotLightHelper })), _jsx("object3D", { ref: targetRef, position: [0, -5, 0] }), editMode && (_jsxs(_Fragment, { children: [_jsxs("mesh", { children: [_jsx("sphereGeometry", { args: [0.2, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true })] }), _jsxs("mesh", { position: [0, -5, 0], children: [_jsx("sphereGeometry", { args: [0.15, 8, 6] }), _jsx("meshBasicMaterial", { color: color, wireframe: true, opacity: 0.5, transparent: true })] })] }))] }));
|
|
47
49
|
}
|
|
48
50
|
const SpotLightComponent = {
|
|
49
51
|
name: 'SpotLight',
|