@tomorrowevening/hermes 0.0.35 → 0.0.37
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/hermes.cjs.js +144 -0
- package/dist/hermes.esm.js +3849 -0
- package/dist/hermes.umd.js +144 -0
- package/dist/style.css +1 -1
- package/package.json +20 -16
- package/src/core/Application.ts +111 -0
- package/src/core/RemoteController.ts +60 -0
- package/src/core/remote/BaseRemote.ts +16 -0
- package/src/core/remote/RemoteComponents.ts +45 -0
- package/src/core/remote/RemoteTheatre.ts +300 -0
- package/src/core/remote/RemoteThree.ts +143 -0
- package/src/core/remote/RemoteTweakpane.ts +194 -0
- package/src/core/types.ts +56 -0
- package/src/editor/Editor.tsx +20 -0
- package/src/editor/components/Draggable.tsx +40 -0
- package/src/editor/components/DraggableItem.tsx +22 -0
- package/src/editor/components/Dropdown.tsx +38 -0
- package/src/editor/components/DropdownItem.tsx +64 -0
- package/src/editor/components/NavButton.tsx +11 -0
- package/src/editor/components/content.ts +2 -0
- package/src/editor/components/icons/CloseIcon.tsx +7 -0
- package/src/editor/components/icons/DragIcon.tsx +9 -0
- package/src/editor/components/types.ts +41 -0
- package/src/editor/global.ts +20 -0
- package/src/editor/multiView/CameraWindow.tsx +74 -0
- package/src/editor/multiView/InfiniteGridHelper.ts +24 -0
- package/src/editor/multiView/InfiniteGridMaterial.ts +127 -0
- package/src/editor/multiView/MultiView.scss +101 -0
- package/src/editor/multiView/MultiView.tsx +636 -0
- package/src/editor/multiView/MultiViewData.ts +59 -0
- package/src/editor/multiView/UVMaterial.ts +55 -0
- package/src/editor/scss/_debug.scss +58 -0
- package/src/editor/scss/_draggable.scss +43 -0
- package/src/editor/scss/_dropdown.scss +84 -0
- package/src/editor/scss/_sidePanel.scss +278 -0
- package/src/editor/scss/_theme.scss +9 -0
- package/src/editor/scss/index.scss +67 -0
- package/src/editor/sidePanel/Accordion.tsx +41 -0
- package/src/editor/sidePanel/ChildObject.tsx +57 -0
- package/src/editor/sidePanel/ContainerObject.tsx +11 -0
- package/src/editor/sidePanel/SidePanel.tsx +64 -0
- package/src/editor/sidePanel/ToggleBtn.tsx +27 -0
- package/src/editor/sidePanel/inspector/Inspector.tsx +119 -0
- package/src/editor/sidePanel/inspector/InspectorField.tsx +198 -0
- package/src/editor/sidePanel/inspector/InspectorGroup.tsx +50 -0
- package/src/editor/sidePanel/inspector/SceneInspector.tsx +84 -0
- package/src/editor/sidePanel/inspector/inspector.scss +161 -0
- package/src/editor/sidePanel/inspector/utils/InspectAnimation.tsx +102 -0
- package/src/editor/sidePanel/inspector/utils/InspectCamera.tsx +75 -0
- package/src/editor/sidePanel/inspector/utils/InspectLight.tsx +62 -0
- package/src/editor/sidePanel/inspector/utils/InspectMaterial.tsx +710 -0
- package/src/editor/sidePanel/inspector/utils/InspectTransform.tsx +113 -0
- package/src/editor/sidePanel/types.ts +130 -0
- package/src/editor/sidePanel/utils.ts +278 -0
- package/src/editor/utils.ts +117 -0
- package/src/example/CustomEditor.tsx +78 -0
- package/src/example/components/App.css +6 -0
- package/src/example/components/App.tsx +246 -0
- package/src/example/constants.ts +52 -0
- package/src/example/index.scss +45 -0
- package/src/example/main.tsx +37 -0
- package/src/example/three/BaseScene.ts +42 -0
- package/src/example/three/CustomMaterial.ts +72 -0
- package/src/example/three/FBXAnimation.ts +26 -0
- package/src/example/three/Scene1.ts +225 -0
- package/src/example/three/Scene2.ts +138 -0
- package/src/example/three/loader.ts +110 -0
- package/src/index.ts +27 -0
- package/src/vite-env.d.ts +1 -0
- package/dist/hermes.js +0 -3901
- package/dist/hermes.umd.cjs +0 -144
@@ -0,0 +1,113 @@
|
|
1
|
+
import { Euler, Matrix4, Vector3 } from 'three';
|
2
|
+
import { degToRad, radToDeg } from 'three/src/math/MathUtils';
|
3
|
+
import InspectorGroup from '../InspectorGroup';
|
4
|
+
import { RemoteObject } from '../../types';
|
5
|
+
import RemoteThree from '@/core/remote/RemoteThree';
|
6
|
+
import { setItemProps } from '../../utils';
|
7
|
+
import { round } from '@/editor/utils';
|
8
|
+
|
9
|
+
export function InspectTransform(obj: RemoteObject, three: RemoteThree) {
|
10
|
+
const matrix = new Matrix4();
|
11
|
+
matrix.elements = obj.matrix;
|
12
|
+
const position = new Vector3();
|
13
|
+
const rotation = new Euler();
|
14
|
+
const scale = new Vector3();
|
15
|
+
if (obj.uuid.length > 0) {
|
16
|
+
position.setFromMatrixPosition(matrix);
|
17
|
+
rotation.setFromRotationMatrix(matrix);
|
18
|
+
scale.setFromMatrixScale(matrix);
|
19
|
+
}
|
20
|
+
|
21
|
+
const updateTransform = (prop: string, value: any) => {
|
22
|
+
three.updateObject(obj.uuid, prop, value);
|
23
|
+
const child = three.scene?.getObjectByProperty('uuid', obj.uuid);
|
24
|
+
if (child !== undefined) setItemProps(child, prop, value);
|
25
|
+
};
|
26
|
+
|
27
|
+
const updateRotation = (prop: string, value: any) => {
|
28
|
+
updateTransform(prop, degToRad(value));
|
29
|
+
};
|
30
|
+
|
31
|
+
return (
|
32
|
+
<InspectorGroup
|
33
|
+
title='Transform'
|
34
|
+
items={[
|
35
|
+
{
|
36
|
+
title: 'Position X',
|
37
|
+
prop: 'position.x',
|
38
|
+
type: 'number',
|
39
|
+
value: position.x,
|
40
|
+
onChange: updateTransform,
|
41
|
+
},
|
42
|
+
{
|
43
|
+
title: 'Position Y',
|
44
|
+
prop: 'position.y',
|
45
|
+
type: 'number',
|
46
|
+
value: position.y,
|
47
|
+
onChange: updateTransform,
|
48
|
+
},
|
49
|
+
{
|
50
|
+
title: 'Position Z',
|
51
|
+
prop: 'position.z',
|
52
|
+
type: 'number',
|
53
|
+
value: position.z,
|
54
|
+
onChange: updateTransform,
|
55
|
+
},
|
56
|
+
{
|
57
|
+
title: 'Rotation X',
|
58
|
+
prop: 'rotation.x',
|
59
|
+
type: 'number',
|
60
|
+
value: round(radToDeg(rotation.x)),
|
61
|
+
min: -360,
|
62
|
+
max: 360,
|
63
|
+
step: 0.1,
|
64
|
+
onChange: updateRotation,
|
65
|
+
},
|
66
|
+
{
|
67
|
+
title: 'Rotation Y',
|
68
|
+
prop: 'rotation.y',
|
69
|
+
type: 'number',
|
70
|
+
value: round(radToDeg(rotation.y)),
|
71
|
+
min: -360,
|
72
|
+
max: 360,
|
73
|
+
step: 0.1,
|
74
|
+
onChange: updateRotation,
|
75
|
+
},
|
76
|
+
{
|
77
|
+
title: 'Rotation Z',
|
78
|
+
prop: 'rotation.z',
|
79
|
+
type: 'number',
|
80
|
+
value: round(radToDeg(rotation.z)),
|
81
|
+
min: -360,
|
82
|
+
max: 360,
|
83
|
+
step: 0.1,
|
84
|
+
onChange: updateRotation,
|
85
|
+
},
|
86
|
+
{
|
87
|
+
title: 'Scale X',
|
88
|
+
prop: 'scale.x',
|
89
|
+
type: 'number',
|
90
|
+
value: scale.x,
|
91
|
+
step: 0.01,
|
92
|
+
onChange: updateTransform,
|
93
|
+
},
|
94
|
+
{
|
95
|
+
title: 'Scale Y',
|
96
|
+
prop: 'scale.y',
|
97
|
+
type: 'number',
|
98
|
+
value: scale.y,
|
99
|
+
step: 0.01,
|
100
|
+
onChange: updateTransform,
|
101
|
+
},
|
102
|
+
{
|
103
|
+
title: 'Scale Z',
|
104
|
+
prop: 'scale.z',
|
105
|
+
type: 'number',
|
106
|
+
value: scale.z,
|
107
|
+
step: 0.01,
|
108
|
+
onChange: updateTransform,
|
109
|
+
},
|
110
|
+
]}
|
111
|
+
/>
|
112
|
+
);
|
113
|
+
}
|
@@ -0,0 +1,130 @@
|
|
1
|
+
import RemoteThree from '@/core/remote/RemoteThree';
|
2
|
+
import { Color, Object3D } from 'three';
|
3
|
+
|
4
|
+
export interface CoreComponentProps {
|
5
|
+
class?: string
|
6
|
+
three: RemoteThree
|
7
|
+
}
|
8
|
+
|
9
|
+
export interface ChildObjectProps extends CoreComponentProps {
|
10
|
+
child: Object3D
|
11
|
+
three: RemoteThree
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface SidePanelState {
|
15
|
+
scene?: Object3D
|
16
|
+
three: RemoteThree
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface MinimumObject {
|
20
|
+
name: string
|
21
|
+
uuid: string
|
22
|
+
type: string
|
23
|
+
children: MinimumObject[]
|
24
|
+
}
|
25
|
+
|
26
|
+
export interface RemoteMaterial {
|
27
|
+
// Blending
|
28
|
+
blending: number
|
29
|
+
blendSrc: number
|
30
|
+
blendDst: number
|
31
|
+
blendEquation: number
|
32
|
+
blendColor: Color
|
33
|
+
blendAlpha: number
|
34
|
+
// Depth
|
35
|
+
depthFunc: number
|
36
|
+
depthTest: boolean
|
37
|
+
depthWrite: boolean
|
38
|
+
// Stencil
|
39
|
+
stencilWriteMask: number
|
40
|
+
stencilFunc: number
|
41
|
+
stencilRef: number
|
42
|
+
stencilFuncMask: number
|
43
|
+
stencilFail: number
|
44
|
+
stencilZFail: number
|
45
|
+
stencilZPass: number
|
46
|
+
stencilWrite: boolean
|
47
|
+
// Clipping
|
48
|
+
clipIntersection: boolean
|
49
|
+
// Polygon
|
50
|
+
polygonOffset: boolean
|
51
|
+
polygonOffsetFactor: number
|
52
|
+
polygonOffsetUnits: number
|
53
|
+
// ETC
|
54
|
+
dithering: boolean
|
55
|
+
name: string
|
56
|
+
opacity: number
|
57
|
+
premultipliedAlpha: boolean
|
58
|
+
side: number
|
59
|
+
toneMapped: boolean
|
60
|
+
transparent: boolean
|
61
|
+
type: string
|
62
|
+
uuid: string
|
63
|
+
vertexColors: boolean
|
64
|
+
defines: any
|
65
|
+
extensions: any
|
66
|
+
uniforms: any
|
67
|
+
// Colors
|
68
|
+
color?: Color
|
69
|
+
attenuationColor?: Color
|
70
|
+
sheenColor?: Color
|
71
|
+
specularColor?: Color
|
72
|
+
}
|
73
|
+
|
74
|
+
// Animation Info
|
75
|
+
|
76
|
+
export interface AnimationClipInfo {
|
77
|
+
name: string;
|
78
|
+
duration: number;
|
79
|
+
blendMode: number;
|
80
|
+
}
|
81
|
+
|
82
|
+
// Camera Info
|
83
|
+
|
84
|
+
export interface PerspectiveCameraInfo {
|
85
|
+
fov: number
|
86
|
+
zoom: number
|
87
|
+
near: number
|
88
|
+
far: number
|
89
|
+
focus: number
|
90
|
+
aspect: number
|
91
|
+
filmGauge: number
|
92
|
+
filmOffset: number
|
93
|
+
}
|
94
|
+
|
95
|
+
export interface OrthographicCameraInfo {
|
96
|
+
zoom: number
|
97
|
+
near: number
|
98
|
+
far: number
|
99
|
+
left: number
|
100
|
+
right: number
|
101
|
+
top: number
|
102
|
+
bottom: number
|
103
|
+
}
|
104
|
+
|
105
|
+
// Light Info
|
106
|
+
export interface LightInfo {
|
107
|
+
color: Color
|
108
|
+
intensity: number
|
109
|
+
// Point
|
110
|
+
decay?: number
|
111
|
+
distance?: number
|
112
|
+
// Spot
|
113
|
+
angle?: number
|
114
|
+
penumbra?: number
|
115
|
+
// Hemisphere
|
116
|
+
groundColor?: Color
|
117
|
+
}
|
118
|
+
|
119
|
+
export interface RemoteObject {
|
120
|
+
name: string
|
121
|
+
uuid: string
|
122
|
+
type: string
|
123
|
+
visible: boolean
|
124
|
+
matrix: number[] // based on Matrix4.elements
|
125
|
+
animations: AnimationClipInfo[]
|
126
|
+
material?: RemoteMaterial | RemoteMaterial[]
|
127
|
+
perspectiveCameraInfo?: PerspectiveCameraInfo
|
128
|
+
orthographicCameraInfo?: OrthographicCameraInfo
|
129
|
+
lightInfo?: LightInfo
|
130
|
+
}
|
@@ -0,0 +1,278 @@
|
|
1
|
+
import { AnimationClip, CubeTexture, Line, Material, Mesh, Object3D, Points, RepeatWrapping, Texture } from 'three';
|
2
|
+
import { MinimumObject, RemoteMaterial, RemoteObject } from './types';
|
3
|
+
|
4
|
+
export function determineIcon(obj: Object3D): string {
|
5
|
+
if (obj.name === 'cameras') {
|
6
|
+
return 'camera';
|
7
|
+
} else if (obj.name === 'interactive') {
|
8
|
+
return 'interactive';
|
9
|
+
} else if (obj.name === 'lights') {
|
10
|
+
return 'light';
|
11
|
+
} else if (obj.name === 'ui') {
|
12
|
+
return 'ui';
|
13
|
+
} else if (obj.name === 'utils') {
|
14
|
+
return 'utils';
|
15
|
+
}
|
16
|
+
|
17
|
+
const type = obj.type;
|
18
|
+
if (type.search('Helper') > -1) {
|
19
|
+
return 'icon_utils';
|
20
|
+
} else if (type.search('Camera') > -1) {
|
21
|
+
return 'camera';
|
22
|
+
} else if (type.search('Light') > -1) {
|
23
|
+
return 'light';
|
24
|
+
}
|
25
|
+
return 'obj3D';
|
26
|
+
}
|
27
|
+
|
28
|
+
export function stripScene(obj: Object3D): MinimumObject {
|
29
|
+
const min: MinimumObject = {
|
30
|
+
name: obj.name,
|
31
|
+
type: obj.type,
|
32
|
+
uuid: obj.uuid,
|
33
|
+
children: [],
|
34
|
+
};
|
35
|
+
obj.children.forEach((child: Object3D) => {
|
36
|
+
min.children.push(stripScene(child));
|
37
|
+
});
|
38
|
+
return min;
|
39
|
+
}
|
40
|
+
|
41
|
+
function cleanUniforms(obj: any) {
|
42
|
+
const newObj = {};
|
43
|
+
for (const i in obj) {
|
44
|
+
const value = obj[i].value;
|
45
|
+
newObj[i] = { value: value };
|
46
|
+
if (value === null) {
|
47
|
+
newObj[i].value = { src: '' };
|
48
|
+
} else if (value.isTexture) {
|
49
|
+
newObj[i].value = { src: value.image.src };
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return newObj;
|
53
|
+
}
|
54
|
+
|
55
|
+
function skipPropertyName(value: string): boolean {
|
56
|
+
switch (value) {
|
57
|
+
case 'blendSrcAlpha':
|
58
|
+
case 'blendDstAlpha':
|
59
|
+
case 'blendEquationAlpha':
|
60
|
+
case 'clippingPlanes':
|
61
|
+
case 'shadowSide':
|
62
|
+
case 'precision':
|
63
|
+
return true;
|
64
|
+
}
|
65
|
+
return false;
|
66
|
+
}
|
67
|
+
|
68
|
+
function stripMaterialData(material: Material): RemoteMaterial {
|
69
|
+
const materialData = {};
|
70
|
+
for (const i in material) {
|
71
|
+
if (i.substring(0, 1) === '_' || i.substring(0, 2) === 'is') continue;
|
72
|
+
if (skipPropertyName(i)) continue;
|
73
|
+
|
74
|
+
const type = typeof material[i];
|
75
|
+
const value = material[i];
|
76
|
+
switch (type) {
|
77
|
+
case 'boolean':
|
78
|
+
case 'number':
|
79
|
+
case 'string':
|
80
|
+
materialData[i] = value;
|
81
|
+
break;
|
82
|
+
case 'object':
|
83
|
+
if (value !== null) {
|
84
|
+
materialData[i] = value;
|
85
|
+
if (value.isTexture) {
|
86
|
+
if (value instanceof Texture) {
|
87
|
+
// materialData[i] = { src: convertImageToBase64(value.image) };
|
88
|
+
const textureSource = value.source.toJSON();
|
89
|
+
// @ts-ignore
|
90
|
+
materialData[i] = { src: textureSource.url };
|
91
|
+
} else if (value instanceof CubeTexture) {
|
92
|
+
console.log('env map');
|
93
|
+
console.log(value.source.data);
|
94
|
+
console.log(value.source.toJSON());
|
95
|
+
materialData[i] = { src: '' };
|
96
|
+
}
|
97
|
+
} else if (i === 'uniforms') {
|
98
|
+
materialData[i] = cleanUniforms(materialData[i]);
|
99
|
+
}
|
100
|
+
} else {
|
101
|
+
materialData[i] = { src: '' };
|
102
|
+
}
|
103
|
+
break;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
return materialData as RemoteMaterial;
|
108
|
+
}
|
109
|
+
|
110
|
+
export function convertImageToBase64(imgElement: HTMLImageElement): string {
|
111
|
+
// Create a canvas element
|
112
|
+
const canvas = document.createElement('canvas');
|
113
|
+
const ctx = canvas.getContext('2d');
|
114
|
+
|
115
|
+
// Set the canvas dimensions to match the image
|
116
|
+
canvas.width = imgElement.width;
|
117
|
+
canvas.height = imgElement.height;
|
118
|
+
|
119
|
+
// Draw the image onto the canvas
|
120
|
+
ctx!.drawImage(imgElement, 0, 0);
|
121
|
+
|
122
|
+
// Get the Base64 representation of the image from the canvas
|
123
|
+
return canvas.toDataURL('image/png');
|
124
|
+
}
|
125
|
+
|
126
|
+
export function stripObject(obj: Object3D): RemoteObject {
|
127
|
+
obj.updateMatrix();
|
128
|
+
|
129
|
+
const stripped: RemoteObject = {
|
130
|
+
name: obj.name,
|
131
|
+
type: obj.type,
|
132
|
+
uuid: obj.uuid,
|
133
|
+
visible: obj.visible,
|
134
|
+
matrix: obj.matrix.elements,
|
135
|
+
animations: [],
|
136
|
+
material: undefined,
|
137
|
+
perspectiveCameraInfo: undefined,
|
138
|
+
orthographicCameraInfo: undefined,
|
139
|
+
lightInfo: undefined,
|
140
|
+
};
|
141
|
+
|
142
|
+
// Animations
|
143
|
+
obj.animations.forEach((clip: AnimationClip) => {
|
144
|
+
stripped.animations.push({
|
145
|
+
name: clip.name,
|
146
|
+
duration: clip.duration,
|
147
|
+
blendMode: clip.blendMode,
|
148
|
+
});
|
149
|
+
});
|
150
|
+
|
151
|
+
const type = obj.type.toLowerCase();
|
152
|
+
if (type.search('mesh') > -1) {
|
153
|
+
const mesh = obj as Mesh;
|
154
|
+
if (Array.isArray(mesh.material)) {
|
155
|
+
const data: RemoteMaterial[] = [];
|
156
|
+
mesh.material.forEach((material: Material) => {
|
157
|
+
data.push(stripMaterialData(material));
|
158
|
+
});
|
159
|
+
stripped.material = data;
|
160
|
+
} else {
|
161
|
+
stripped.material = stripMaterialData(mesh.material);
|
162
|
+
}
|
163
|
+
} else if (type.search('points') > -1) {
|
164
|
+
const mesh = obj as Points;
|
165
|
+
if (Array.isArray(mesh.material)) {
|
166
|
+
const data: RemoteMaterial[] = [];
|
167
|
+
mesh.material.forEach((material: Material) => {
|
168
|
+
data.push(stripMaterialData(material));
|
169
|
+
});
|
170
|
+
stripped.material = data;
|
171
|
+
} else {
|
172
|
+
stripped.material = stripMaterialData(mesh.material);
|
173
|
+
}
|
174
|
+
} else if (type.search('line') > -1) {
|
175
|
+
const mesh = obj as Line;
|
176
|
+
if (Array.isArray(mesh.material)) {
|
177
|
+
const data: RemoteMaterial[] = [];
|
178
|
+
mesh.material.forEach((material: Material) => {
|
179
|
+
data.push(stripMaterialData(material));
|
180
|
+
});
|
181
|
+
stripped.material = data;
|
182
|
+
} else {
|
183
|
+
stripped.material = stripMaterialData(mesh.material);
|
184
|
+
}
|
185
|
+
} else if (type.search('camera') > -1) {
|
186
|
+
if (obj.type === 'PerspectiveCamera') {
|
187
|
+
stripped.perspectiveCameraInfo = {
|
188
|
+
fov: obj['fov'],
|
189
|
+
zoom: obj['zoom'],
|
190
|
+
near: obj['near'],
|
191
|
+
far: obj['far'],
|
192
|
+
focus: obj['focus'],
|
193
|
+
aspect: obj['aspect'],
|
194
|
+
filmGauge: obj['filmGauge'],
|
195
|
+
filmOffset: obj['filmOffset'],
|
196
|
+
};
|
197
|
+
} else if (obj.type === 'OrthographicCamera') {
|
198
|
+
stripped.orthographicCameraInfo = {
|
199
|
+
zoom: obj['zoom'],
|
200
|
+
near: obj['near'],
|
201
|
+
far: obj['far'],
|
202
|
+
left: obj['left'],
|
203
|
+
right: obj['right'],
|
204
|
+
top: obj['top'],
|
205
|
+
bottom: obj['bottom'],
|
206
|
+
};
|
207
|
+
}
|
208
|
+
} else if (type.search('light') > -1) {
|
209
|
+
stripped.lightInfo = {
|
210
|
+
color: obj['color'],
|
211
|
+
intensity: obj['intensity'],
|
212
|
+
decay: obj['decay'],
|
213
|
+
distance: obj['distance'],
|
214
|
+
angle: obj['angle'],
|
215
|
+
penumbra: obj['penumbra'],
|
216
|
+
groundColor: obj['groundColor'],
|
217
|
+
};
|
218
|
+
}
|
219
|
+
|
220
|
+
return stripped;
|
221
|
+
}
|
222
|
+
|
223
|
+
export function getSubItem(child: any, key: string): any {
|
224
|
+
const keys = key.split('.');
|
225
|
+
const total = keys.length;
|
226
|
+
switch (total) {
|
227
|
+
case 1:
|
228
|
+
return child[keys[0]];
|
229
|
+
case 2:
|
230
|
+
return child[keys[0]][keys[1]];
|
231
|
+
case 3:
|
232
|
+
return child[keys[0]][keys[1]][keys[2]];
|
233
|
+
case 4:
|
234
|
+
return child[keys[0]][keys[1]][keys[2]][keys[3]];
|
235
|
+
case 5:
|
236
|
+
return child[keys[0]][keys[1]][keys[2]][keys[3]][keys[4]];
|
237
|
+
case 6:
|
238
|
+
return child[keys[0]][keys[1]][keys[2]][keys[3]][keys[4]][keys[5]];
|
239
|
+
}
|
240
|
+
return undefined;
|
241
|
+
}
|
242
|
+
|
243
|
+
export function setItemProps(child: any, key: string, value: any) {
|
244
|
+
const keys = key.split('.');
|
245
|
+
const total = keys.length;
|
246
|
+
switch (total) {
|
247
|
+
case 1:
|
248
|
+
child[keys[0]] = value;
|
249
|
+
break;
|
250
|
+
case 2:
|
251
|
+
child[keys[0]][keys[1]] = value;
|
252
|
+
break;
|
253
|
+
case 3:
|
254
|
+
child[keys[0]][keys[1]][keys[2]] = value;
|
255
|
+
break;
|
256
|
+
case 4:
|
257
|
+
child[keys[0]][keys[1]][keys[2]][keys[3]] = value;
|
258
|
+
break;
|
259
|
+
case 5:
|
260
|
+
child[keys[0]][keys[1]][keys[2]][keys[3]][keys[4]] = value;
|
261
|
+
break;
|
262
|
+
}
|
263
|
+
}
|
264
|
+
|
265
|
+
export function textureFromSrc(imgSource: string): Promise<Texture> {
|
266
|
+
return new Promise((resolve, reject) => {
|
267
|
+
const img = new Image();
|
268
|
+
img.onload = () => {
|
269
|
+
const texture = new Texture(img);
|
270
|
+
texture.wrapS = RepeatWrapping;
|
271
|
+
texture.wrapT = RepeatWrapping;
|
272
|
+
texture.needsUpdate = true;
|
273
|
+
resolve(texture);
|
274
|
+
};
|
275
|
+
img.onerror = reject;
|
276
|
+
img.src = imgSource;
|
277
|
+
});
|
278
|
+
}
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import { Material, Mesh, Object3D, PositionalAudio, Texture } from 'three';
|
2
|
+
|
3
|
+
export function capitalize(value: string): string {
|
4
|
+
return value.substring(0, 1).toUpperCase() + value.substring(1);
|
5
|
+
}
|
6
|
+
|
7
|
+
export function clamp(min: number, max: number, value: number) {
|
8
|
+
return Math.min(max, Math.max(min, value));
|
9
|
+
}
|
10
|
+
|
11
|
+
export function distance(x: number, y: number): number {
|
12
|
+
const d = x - y;
|
13
|
+
return Math.sqrt(d * d);
|
14
|
+
}
|
15
|
+
|
16
|
+
export function randomID(): string {
|
17
|
+
return Math.round(Math.random() * 1000000).toString();
|
18
|
+
}
|
19
|
+
|
20
|
+
export function isColor(obj: any) {
|
21
|
+
return (
|
22
|
+
obj.r !== undefined &&
|
23
|
+
obj.g !== undefined &&
|
24
|
+
obj.b !== undefined
|
25
|
+
);
|
26
|
+
}
|
27
|
+
|
28
|
+
export function colorToHex(obj: any) {
|
29
|
+
const r = Math.round(obj.r * 255);
|
30
|
+
const g = Math.round(obj.g * 255);
|
31
|
+
const b = Math.round(obj.b * 255);
|
32
|
+
|
33
|
+
const toHex = (value: number) => {
|
34
|
+
const hex = value.toString(16);
|
35
|
+
return hex.length === 1 ? '0' + hex : hex;
|
36
|
+
};
|
37
|
+
|
38
|
+
const red = toHex(r);
|
39
|
+
const green = toHex(g);
|
40
|
+
const blue = toHex(b);
|
41
|
+
|
42
|
+
return '#' + red + green + blue;
|
43
|
+
}
|
44
|
+
|
45
|
+
export function round(value: number, precision: number = 1): number {
|
46
|
+
return Number(value.toFixed(precision));
|
47
|
+
}
|
48
|
+
|
49
|
+
export let totalThreeObjects = 0;
|
50
|
+
export const resetThreeObjects = () => {
|
51
|
+
totalThreeObjects = 0;
|
52
|
+
};
|
53
|
+
export const hierarchyUUID = (object: Object3D): void => {
|
54
|
+
if (!object) return;
|
55
|
+
|
56
|
+
let uuid = object.name.replace(' ', '');
|
57
|
+
// fallback in case there's no name
|
58
|
+
if (uuid.length === 0) {
|
59
|
+
uuid = `obj_${totalThreeObjects}`;
|
60
|
+
totalThreeObjects++;
|
61
|
+
}
|
62
|
+
// inherit parent's UUID for hierarchy
|
63
|
+
if (object.parent !== null) uuid = `${object.parent.uuid}.${uuid}`;
|
64
|
+
object.uuid = uuid;
|
65
|
+
|
66
|
+
// Iterate children
|
67
|
+
object.children.forEach((child: Object3D) => {
|
68
|
+
hierarchyUUID(child);
|
69
|
+
});
|
70
|
+
};
|
71
|
+
|
72
|
+
// Dispose
|
73
|
+
|
74
|
+
export const disposeTexture = (texture?: Texture): void => {
|
75
|
+
texture?.dispose();
|
76
|
+
};
|
77
|
+
|
78
|
+
// Dispose material
|
79
|
+
export const disposeMaterial = (material?: Material | Material[]): void => {
|
80
|
+
if (!material) return;
|
81
|
+
|
82
|
+
if (Array.isArray(material)) {
|
83
|
+
material.forEach((mat: Material) => mat.dispose());
|
84
|
+
} else {
|
85
|
+
material.dispose();
|
86
|
+
}
|
87
|
+
};
|
88
|
+
|
89
|
+
// Dispose object
|
90
|
+
export const dispose = (object: Object3D): void => {
|
91
|
+
if (!object) return;
|
92
|
+
|
93
|
+
// Dispose children
|
94
|
+
while (object.children.length > 0) {
|
95
|
+
const child = object.children[0];
|
96
|
+
if (child instanceof PositionalAudio) {
|
97
|
+
child.pause();
|
98
|
+
if (child.parent) {
|
99
|
+
child.parent.remove(child);
|
100
|
+
}
|
101
|
+
} else {
|
102
|
+
dispose(child);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
// Dispose object
|
107
|
+
if (object.parent) object.parent.remove(object);
|
108
|
+
// @ts-ignore
|
109
|
+
if (object.isMesh) {
|
110
|
+
const mesh = object as Mesh;
|
111
|
+
mesh.geometry?.dispose();
|
112
|
+
disposeMaterial(mesh.material);
|
113
|
+
}
|
114
|
+
|
115
|
+
// @ts-ignore
|
116
|
+
if (object.dispose !== undefined) object.dispose();
|
117
|
+
};
|
@@ -0,0 +1,78 @@
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import { Events, app, threeDispatcher } from './constants';
|
3
|
+
import Editor from '../editor/Editor';
|
4
|
+
import Dropdown from '../editor/components/Dropdown';
|
5
|
+
import SidePanel from '../editor/sidePanel/SidePanel';
|
6
|
+
import MultiView from '../editor/multiView/MultiView';
|
7
|
+
// Scenes
|
8
|
+
import BaseScene from './three/BaseScene';
|
9
|
+
import Scene1 from './three/Scene1';
|
10
|
+
import Scene2 from './three/Scene2';
|
11
|
+
|
12
|
+
// Referenced Scenes
|
13
|
+
const scenes: Map<string, any> = new Map();
|
14
|
+
scenes.set('Scene1', Scene1);
|
15
|
+
scenes.set('Scene2', Scene2);
|
16
|
+
|
17
|
+
export default function CustomEditor() {
|
18
|
+
const [loaded, setLoaded] = useState(false);
|
19
|
+
useEffect(() => {
|
20
|
+
const onLoad = () => {
|
21
|
+
threeDispatcher.removeEventListener(Events.LOAD_COMPLETE, onLoad);
|
22
|
+
setLoaded(true);
|
23
|
+
};
|
24
|
+
threeDispatcher.addEventListener(Events.LOAD_COMPLETE, onLoad);
|
25
|
+
return () => {
|
26
|
+
threeDispatcher.removeEventListener(Events.LOAD_COMPLETE, onLoad);
|
27
|
+
};
|
28
|
+
}, []);
|
29
|
+
|
30
|
+
return (
|
31
|
+
<Editor
|
32
|
+
header={[
|
33
|
+
<Dropdown
|
34
|
+
title='Scenes'
|
35
|
+
key='Scenes'
|
36
|
+
options={[
|
37
|
+
{
|
38
|
+
type: 'option',
|
39
|
+
title: 'Scene 1',
|
40
|
+
value: 'scene1',
|
41
|
+
},
|
42
|
+
{
|
43
|
+
type: 'option',
|
44
|
+
title: 'Scene 2',
|
45
|
+
value: 'scene2',
|
46
|
+
},
|
47
|
+
]}
|
48
|
+
onSelect={(value: string) => {
|
49
|
+
app.send({
|
50
|
+
target: 'app',
|
51
|
+
event: 'selectComponent',
|
52
|
+
data: {
|
53
|
+
dropdown: 'Scenes',
|
54
|
+
value,
|
55
|
+
},
|
56
|
+
});
|
57
|
+
}}
|
58
|
+
/>
|
59
|
+
]}>
|
60
|
+
<>
|
61
|
+
{loaded && (
|
62
|
+
<>
|
63
|
+
<MultiView
|
64
|
+
three={app.three}
|
65
|
+
scenes={scenes}
|
66
|
+
onSceneUpdate={(scene: any) => {
|
67
|
+
// Custom callback for animation updates
|
68
|
+
const baseScene = scene as BaseScene;
|
69
|
+
baseScene.update();
|
70
|
+
}}
|
71
|
+
/>
|
72
|
+
<SidePanel three={app.three} />
|
73
|
+
</>
|
74
|
+
)}
|
75
|
+
</>
|
76
|
+
</Editor>
|
77
|
+
);
|
78
|
+
}
|