react-three-game 0.0.65 → 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/LICENSE +2 -660
- package/README.md +164 -91
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -2
- package/dist/shared/GameCanvas.js +1 -1
- package/dist/tools/assetviewer/page.d.ts +13 -2
- package/dist/tools/assetviewer/page.js +61 -7
- package/dist/tools/dragdrop/index.d.ts +1 -1
- package/dist/tools/dragdrop/modelLoader.d.ts +2 -0
- package/dist/tools/prefabeditor/EditorContext.d.ts +2 -2
- package/dist/tools/prefabeditor/EditorTree.js +17 -3
- package/dist/tools/prefabeditor/EditorTreeMenus.d.ts +3 -1
- package/dist/tools/prefabeditor/EditorTreeMenus.js +7 -8
- package/dist/tools/prefabeditor/EditorUI.js +3 -7
- package/dist/tools/prefabeditor/GameEvents.d.ts +14 -1
- package/dist/tools/prefabeditor/GameEvents.js +2 -1
- package/dist/tools/prefabeditor/InstanceProvider.d.ts +4 -0
- package/dist/tools/prefabeditor/InstanceProvider.js +44 -12
- package/dist/tools/prefabeditor/PrefabEditor.js +77 -16
- package/dist/tools/prefabeditor/PrefabRoot.d.ts +9 -6
- package/dist/tools/prefabeditor/PrefabRoot.js +52 -126
- package/dist/tools/prefabeditor/components/CameraComponent.js +1 -1
- package/dist/tools/prefabeditor/components/ClickComponent.d.ts +3 -0
- package/dist/tools/prefabeditor/components/ClickComponent.js +45 -0
- package/dist/tools/prefabeditor/components/ComponentRegistry.js +0 -3
- package/dist/tools/prefabeditor/components/DirectionalLightComponent.js +3 -3
- package/dist/tools/prefabeditor/components/Input.d.ts +5 -2
- package/dist/tools/prefabeditor/components/Input.js +71 -38
- package/dist/tools/prefabeditor/components/MaterialComponent.js +4 -69
- package/dist/tools/prefabeditor/components/ModelComponent.js +5 -80
- package/dist/tools/prefabeditor/components/PhysicsComponent.d.ts +2 -0
- package/dist/tools/prefabeditor/components/PhysicsComponent.js +77 -10
- package/dist/tools/prefabeditor/components/SpotLightComponent.js +9 -7
- package/dist/tools/prefabeditor/components/index.js +2 -0
- package/dist/tools/prefabeditor/types.d.ts +1 -0
- package/dist/tools/prefabeditor/utils.d.ts +7 -1
- package/dist/tools/prefabeditor/utils.js +34 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
16
|
-
import { createPortal } from 'react-dom';
|
|
17
|
-
import { FieldRenderer, Input } from './Input';
|
|
18
|
-
import { colors } from '../styles';
|
|
14
|
+
import { FieldRenderer, Label, NumberInput } from './Input';
|
|
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';
|
|
@@ -138,7 +73,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
138
73
|
label: 'Repeat (X, Y)',
|
|
139
74
|
render: ({ value, onChange }) => {
|
|
140
75
|
var _a, _b;
|
|
141
|
-
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(
|
|
76
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "X" }), _jsx(NumberInput, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0.01, max: 100, step: 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "Y" }), _jsx(NumberInput, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0.01, max: 100, step: 0.1, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] }));
|
|
142
77
|
},
|
|
143
78
|
}] : []),
|
|
144
79
|
{
|
|
@@ -153,7 +88,7 @@ function MaterialComponentEditor({ component, onUpdate, basePath = "" }) {
|
|
|
153
88
|
label: 'Normal Scale (X, Y)',
|
|
154
89
|
render: ({ value, onChange }) => {
|
|
155
90
|
var _a, _b;
|
|
156
|
-
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsx(
|
|
91
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 2 }, children: [_jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "X" }), _jsx(NumberInput, { value: (_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, onChange: v => { var _a; return onChange([v, (_a = value === null || value === void 0 ? void 0 : value[1]) !== null && _a !== void 0 ? _a : 1]); }, min: 0, max: 5, step: 0.01, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] }), _jsxs("div", { style: { flex: 1 }, children: [_jsx(Label, { children: "Y" }), _jsx(NumberInput, { value: (_b = value === null || value === void 0 ? void 0 : value[1]) !== null && _b !== void 0 ? _b : 1, onChange: v => { var _a; return onChange([(_a = value === null || value === void 0 ? void 0 : value[0]) !== null && _a !== void 0 ? _a : 1, v]); }, min: 0, max: 5, step: 0.01, style: { width: '100%', minWidth: 0, boxSizing: 'border-box' } })] })] }));
|
|
157
92
|
},
|
|
158
93
|
}] : []),
|
|
159
94
|
{ name: 'generateMipmaps', type: 'boolean', label: 'Generate Mipmaps' },
|
|
@@ -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 {
|
|
5
|
-
import { BooleanField, FieldGroup, Input, Label, SelectInput } from './Input';
|
|
2
|
+
import { ModelPicker } from '../../assetviewer/page';
|
|
3
|
+
import { useContext, useMemo } from 'react';
|
|
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' },
|
|
@@ -68,87 +65,15 @@ function RepeatAxisEditor({ axes, onChange, positionSnap, }) {
|
|
|
68
65
|
cursor: 'pointer',
|
|
69
66
|
padding: 0,
|
|
70
67
|
flexShrink: 0,
|
|
71
|
-
}, title: "Remove repeat axis", children: "\u00D7" })) : null] }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsx(
|
|
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 }) {
|
|
@@ -2,6 +2,8 @@ import type { RigidBodyOptions } from "@react-three/rapier";
|
|
|
2
2
|
import { Component } from "./ComponentRegistry";
|
|
3
3
|
export type PhysicsProps = RigidBodyOptions & {
|
|
4
4
|
activeCollisionTypes?: 'all' | undefined;
|
|
5
|
+
linearVelocity?: [number, number, number];
|
|
6
|
+
angularVelocity?: [number, number, number];
|
|
5
7
|
};
|
|
6
8
|
declare const PhysicsComponent: Component;
|
|
7
9
|
export default PhysicsComponent;
|
|
@@ -12,8 +12,50 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { RigidBody, useRapier } from "@react-three/rapier";
|
|
14
14
|
import { useRef, useEffect, useCallback } from 'react';
|
|
15
|
-
import { BooleanField, FieldGroup, NumberField, SelectField } from "./Input";
|
|
15
|
+
import { BooleanField, FieldGroup, NumberField, SelectField, Vector3Field } from "./Input";
|
|
16
16
|
import { gameEvents, getEntityIdFromRigidBody } from "../GameEvents";
|
|
17
|
+
import { colors } from "../styles";
|
|
18
|
+
const enabledAxesFallback = [true, true, true];
|
|
19
|
+
function LockedAxisField({ label, values, onChange, }) {
|
|
20
|
+
const enabledTranslations = Array.isArray(values.enabledTranslations)
|
|
21
|
+
? values.enabledTranslations
|
|
22
|
+
: enabledAxesFallback;
|
|
23
|
+
const axisLabels = ['X', 'Y', 'Z'];
|
|
24
|
+
const toggleAxisLock = (index) => {
|
|
25
|
+
const nextEnabledTranslations = [...enabledTranslations];
|
|
26
|
+
nextEnabledTranslations[index] = !nextEnabledTranslations[index];
|
|
27
|
+
onChange({ enabledTranslations: nextEnabledTranslations });
|
|
28
|
+
};
|
|
29
|
+
return (_jsxs("div", { children: [_jsxs("div", { style: {
|
|
30
|
+
display: 'flex',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
justifyContent: 'space-between',
|
|
33
|
+
marginBottom: 4,
|
|
34
|
+
}, children: [_jsx("span", { style: {
|
|
35
|
+
display: 'block',
|
|
36
|
+
fontSize: '10px',
|
|
37
|
+
color: colors.textMuted,
|
|
38
|
+
textTransform: 'uppercase',
|
|
39
|
+
letterSpacing: '0.05em',
|
|
40
|
+
fontWeight: 500,
|
|
41
|
+
}, children: label }), _jsx("span", { style: {
|
|
42
|
+
fontSize: '10px',
|
|
43
|
+
color: colors.textDim,
|
|
44
|
+
}, children: "Active means locked" })] }), _jsx("div", { style: { display: 'flex', gap: 4 }, children: axisLabels.map((axisLabel, index) => {
|
|
45
|
+
const isLocked = !enabledTranslations[index];
|
|
46
|
+
return (_jsx("button", { type: "button", onClick: () => toggleAxisLock(index), style: {
|
|
47
|
+
flex: 1,
|
|
48
|
+
backgroundColor: isLocked ? colors.dangerBg : colors.bgInput,
|
|
49
|
+
border: `1px solid ${isLocked ? colors.dangerBorder : colors.border}`,
|
|
50
|
+
borderRadius: 3,
|
|
51
|
+
padding: '6px 8px',
|
|
52
|
+
color: isLocked ? colors.danger : colors.textMuted,
|
|
53
|
+
fontSize: '11px',
|
|
54
|
+
fontFamily: 'monospace',
|
|
55
|
+
cursor: 'pointer',
|
|
56
|
+
}, children: axisLabel }, axisLabel));
|
|
57
|
+
}) })] }));
|
|
58
|
+
}
|
|
17
59
|
function PhysicsComponentEditor({ component, onUpdate }) {
|
|
18
60
|
return (_jsxs(FieldGroup, { children: [_jsx(SelectField, { name: "type", label: "Type", values: component.properties, onChange: onUpdate, options: [
|
|
19
61
|
{ value: 'dynamic', label: 'Dynamic' },
|
|
@@ -25,15 +67,20 @@ function PhysicsComponentEditor({ component, onUpdate }) {
|
|
|
25
67
|
{ value: 'trimesh', label: 'Trimesh (exact)' },
|
|
26
68
|
{ value: 'cuboid', label: 'Cuboid (box)' },
|
|
27
69
|
{ value: 'ball', label: 'Ball (sphere)' },
|
|
28
|
-
] }), _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
|
|
70
|
+
] }), _jsx(NumberField, { name: "mass", label: "Mass", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1, min: 0 }), _jsx(NumberField, { name: "restitution", label: "Restitution (Bounciness)", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, max: 1, step: 0.1 }), _jsx(NumberField, { name: "friction", label: "Friction", values: component.properties, onChange: onUpdate, fallback: 0.5, min: 0, step: 0.1 }), _jsx(NumberField, { name: "linearDamping", label: "Linear Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "angularDamping", label: "Angular Damping", values: component.properties, onChange: onUpdate, fallback: 0, min: 0, step: 0.1 }), _jsx(NumberField, { name: "gravityScale", label: "Gravity Scale", values: component.properties, onChange: onUpdate, fallback: 1, step: 0.1 }), _jsx(Vector3Field, { name: "linearVelocity", label: "Linear Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(Vector3Field, { name: "angularVelocity", label: "Angular Velocity", values: component.properties, onChange: onUpdate, fallback: [0, 0, 0] }), _jsx(LockedAxisField, { label: "Lock Movement", values: component.properties, onChange: onUpdate }), _jsx(BooleanField, { name: "sensor", label: "Sensor (Trigger Only)", values: component.properties, onChange: onUpdate, fallback: false }), _jsx(SelectField, { name: "activeCollisionTypes", label: "Collision Detection", values: component.properties, onChange: onUpdate, options: [
|
|
29
71
|
{ value: '', label: 'Default (Dynamic only)' },
|
|
30
72
|
{ value: 'all', label: 'All (includes kinematic & fixed)' },
|
|
31
73
|
] })] }));
|
|
32
74
|
}
|
|
33
75
|
function PhysicsComponentView({ properties, children, position, rotation, scale, editMode, nodeId, registerRigidBodyRef }) {
|
|
34
|
-
const { type, colliders, sensor, activeCollisionTypes } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes"]);
|
|
76
|
+
const { type, colliders, sensor, activeCollisionTypes, linearVelocity = [0, 0, 0], angularVelocity = [0, 0, 0], enabledTranslations = enabledAxesFallback } = properties, otherProps = __rest(properties, ["type", "colliders", "sensor", "activeCollisionTypes", "linearVelocity", "angularVelocity", "enabledTranslations"]);
|
|
35
77
|
const colliderType = colliders || (type === 'fixed' ? 'trimesh' : 'hull');
|
|
36
78
|
const rigidBodyRef = useRef(null);
|
|
79
|
+
const linearVelocityKey = linearVelocity.join(',');
|
|
80
|
+
const angularVelocityKey = angularVelocity.join(',');
|
|
81
|
+
const rbKey = editMode
|
|
82
|
+
? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
83
|
+
: `${type || 'dynamic'}_${colliderType}`;
|
|
37
84
|
// Try to get rapier context - will be null if not inside <Physics>
|
|
38
85
|
let rapier = null;
|
|
39
86
|
try {
|
|
@@ -67,6 +114,25 @@ function PhysicsComponentView({ properties, children, position, rotation, scale,
|
|
|
67
114
|
}
|
|
68
115
|
}
|
|
69
116
|
}, [activeCollisionTypes, rapier, type, colliders]);
|
|
117
|
+
// Seed authored velocities when the body instance changes or the authored values change.
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (!rigidBodyRef.current)
|
|
120
|
+
return;
|
|
121
|
+
rigidBodyRef.current.setLinvel({
|
|
122
|
+
x: linearVelocity[0],
|
|
123
|
+
y: linearVelocity[1],
|
|
124
|
+
z: linearVelocity[2],
|
|
125
|
+
}, true);
|
|
126
|
+
}, [rbKey, linearVelocityKey]);
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!rigidBodyRef.current)
|
|
129
|
+
return;
|
|
130
|
+
rigidBodyRef.current.setAngvel({
|
|
131
|
+
x: angularVelocity[0],
|
|
132
|
+
y: angularVelocity[1],
|
|
133
|
+
z: angularVelocity[2],
|
|
134
|
+
}, true);
|
|
135
|
+
}, [rbKey, angularVelocityKey]);
|
|
70
136
|
// Event handlers for physics interactions
|
|
71
137
|
const handleIntersectionEnter = useCallback((payload) => {
|
|
72
138
|
if (!nodeId)
|
|
@@ -104,18 +170,19 @@ function PhysicsComponentView({ properties, children, position, rotation, scale,
|
|
|
104
170
|
targetRigidBody: payload.other.rigidBody,
|
|
105
171
|
});
|
|
106
172
|
}, [nodeId]);
|
|
107
|
-
|
|
108
|
-
// This ensures the RigidBody debug visualization updates even when physics is paused
|
|
109
|
-
const rbKey = editMode
|
|
110
|
-
? `${type || 'dynamic'}_${colliderType}_${position === null || position === void 0 ? void 0 : position.join(',')}_${rotation === null || rotation === void 0 ? void 0 : rotation.join(',')}`
|
|
111
|
-
: `${type || 'dynamic'}_${colliderType}`;
|
|
112
|
-
return (_jsx(RigidBody, Object.assign({ ref: rigidBodyRef, type: type, colliders: colliderType, position: position, rotation: rotation, scale: scale, sensor: sensor, userData: { entityId: nodeId }, onIntersectionEnter: handleIntersectionEnter, onIntersectionExit: handleIntersectionExit, onCollisionEnter: handleCollisionEnter, onCollisionExit: handleCollisionExit }, otherProps, { children: children }), rbKey));
|
|
173
|
+
return (_jsx(RigidBody, Object.assign({ ref: rigidBodyRef, type: type, colliders: colliderType, position: position, rotation: rotation, scale: scale, sensor: sensor, enabledTranslations: enabledTranslations, userData: { entityId: nodeId }, onIntersectionEnter: handleIntersectionEnter, onIntersectionExit: handleIntersectionExit, onCollisionEnter: handleCollisionEnter, onCollisionExit: handleCollisionExit }, otherProps, { children: children }), rbKey));
|
|
113
174
|
}
|
|
114
175
|
const PhysicsComponent = {
|
|
115
176
|
name: 'Physics',
|
|
116
177
|
Editor: PhysicsComponentEditor,
|
|
117
178
|
View: PhysicsComponentView,
|
|
118
179
|
nonComposable: true,
|
|
119
|
-
defaultProperties: {
|
|
180
|
+
defaultProperties: {
|
|
181
|
+
type: 'dynamic',
|
|
182
|
+
colliders: 'hull',
|
|
183
|
+
linearVelocity: [0, 0, 0],
|
|
184
|
+
angularVelocity: [0, 0, 0],
|
|
185
|
+
enabledTranslations: [true, true, true],
|
|
186
|
+
}
|
|
120
187
|
};
|
|
121
188
|
export default PhysicsComponent;
|
|
@@ -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',
|
|
@@ -9,6 +9,7 @@ import ModelComponent from './ModelComponent';
|
|
|
9
9
|
import TextComponent from './TextComponent';
|
|
10
10
|
import EnvironmentComponent from './EnvironmentComponent';
|
|
11
11
|
import CameraComponent from './CameraComponent';
|
|
12
|
+
import ClickComponent from './ClickComponent';
|
|
12
13
|
export default [
|
|
13
14
|
GeometryComponent,
|
|
14
15
|
TransformComponent,
|
|
@@ -21,4 +22,5 @@ export default [
|
|
|
21
22
|
TextComponent,
|
|
22
23
|
EnvironmentComponent,
|
|
23
24
|
CameraComponent,
|
|
25
|
+
ClickComponent,
|
|
24
26
|
];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GameObject, Prefab } from "./types";
|
|
2
|
-
import { Object3D, Vector3 } from 'three';
|
|
2
|
+
import { Matrix4, Object3D, Vector3 } from 'three';
|
|
3
3
|
export interface ExportGLBOptions {
|
|
4
4
|
filename?: string;
|
|
5
5
|
binary?: boolean;
|
|
@@ -24,6 +24,12 @@ export declare function exportGLB(sceneRoot: Object3D, options?: ExportGLBOption
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function exportGLBData(sceneRoot: Object3D): Promise<ArrayBuffer>;
|
|
26
26
|
export declare function focusCameraOnObject(object: Object3D, camera: Object3D, target: Vector3, update?: () => void): void;
|
|
27
|
+
export declare function decompose(m: Matrix4): {
|
|
28
|
+
position: [number, number, number];
|
|
29
|
+
rotation: [number, number, number];
|
|
30
|
+
scale: [number, number, number];
|
|
31
|
+
};
|
|
32
|
+
export declare function computeParentWorldMatrix(root: GameObject, targetId: string): Matrix4;
|
|
27
33
|
/** Find a node by ID in the tree */
|
|
28
34
|
export declare function findNode(root: GameObject, id: string): GameObject | null;
|
|
29
35
|
/** Find the parent of a node by ID */
|
|
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js';
|
|
11
|
-
import { Box3, PerspectiveCamera, Quaternion, Vector3 } from 'three';
|
|
11
|
+
import { Box3, Euler, Matrix4, PerspectiveCamera, Quaternion, Vector3 } from 'three';
|
|
12
12
|
/** Save a prefab as JSON file, showing a Save As dialog when supported */
|
|
13
13
|
export function saveJson(data, filename) {
|
|
14
14
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -133,6 +133,39 @@ export function focusCameraOnObject(object, camera, target, update) {
|
|
|
133
133
|
target.copy(center);
|
|
134
134
|
update === null || update === void 0 ? void 0 : update();
|
|
135
135
|
}
|
|
136
|
+
export function decompose(m) {
|
|
137
|
+
const p = new Vector3(), q = new Quaternion(), s = new Vector3();
|
|
138
|
+
m.decompose(p, q, s);
|
|
139
|
+
const e = new Euler().setFromQuaternion(q);
|
|
140
|
+
return {
|
|
141
|
+
position: [p.x, p.y, p.z],
|
|
142
|
+
rotation: [e.x, e.y, e.z],
|
|
143
|
+
scale: [s.x, s.y, s.z],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function compose(node) {
|
|
147
|
+
var _a, _b, _c, _d, _e;
|
|
148
|
+
const t = (_b = (_a = node === null || node === void 0 ? void 0 : node.components) === null || _a === void 0 ? void 0 : _a.transform) === null || _b === void 0 ? void 0 : _b.properties;
|
|
149
|
+
const position = (_c = t === null || t === void 0 ? void 0 : t.position) !== null && _c !== void 0 ? _c : [0, 0, 0];
|
|
150
|
+
const rotation = (_d = t === null || t === void 0 ? void 0 : t.rotation) !== null && _d !== void 0 ? _d : [0, 0, 0];
|
|
151
|
+
const scale = (_e = t === null || t === void 0 ? void 0 : t.scale) !== null && _e !== void 0 ? _e : [1, 1, 1];
|
|
152
|
+
return new Matrix4().compose(new Vector3(...position), new Quaternion().setFromEuler(new Euler(...rotation)), new Vector3(...scale));
|
|
153
|
+
}
|
|
154
|
+
export function computeParentWorldMatrix(root, targetId) {
|
|
155
|
+
const identity = new Matrix4();
|
|
156
|
+
let result = null;
|
|
157
|
+
const visit = (node, parent) => {
|
|
158
|
+
var _a;
|
|
159
|
+
if (node.id === targetId) {
|
|
160
|
+
result = parent.clone();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const world = parent.clone().multiply(compose(node));
|
|
164
|
+
(_a = node.children) === null || _a === void 0 ? void 0 : _a.forEach(child => !result && visit(child, world));
|
|
165
|
+
};
|
|
166
|
+
visit(root, identity);
|
|
167
|
+
return result !== null && result !== void 0 ? result : identity;
|
|
168
|
+
}
|
|
136
169
|
/** Find a node by ID in the tree */
|
|
137
170
|
export function findNode(root, id) {
|
|
138
171
|
var _a;
|