2021-06-18 13:05:06 +02:00
|
|
|
import type {StateCreator} from 'zustand'
|
|
|
|
import create from 'zustand'
|
2021-06-29 15:08:02 +02:00
|
|
|
import type {Object3D, Scene, WebGLRenderer} from 'three'
|
2021-06-18 13:05:06 +02:00
|
|
|
import {DefaultLoadingManager, Group} from 'three'
|
|
|
|
import type {MutableRefObject} from 'react'
|
|
|
|
import type {OrbitControls} from '@react-three/drei'
|
|
|
|
// @ts-ignore TODO
|
|
|
|
import type {ContainerProps} from '@react-three/fiber'
|
|
|
|
import type {ISheet, ISheetObject} from '@theatre/core'
|
2021-06-29 16:00:27 +02:00
|
|
|
import {types, getProject} from '@theatre/core'
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export type EditableType =
|
|
|
|
| 'group'
|
|
|
|
| 'mesh'
|
|
|
|
| 'spotLight'
|
|
|
|
| 'directionalLight'
|
|
|
|
| 'pointLight'
|
|
|
|
| 'perspectiveCamera'
|
|
|
|
| 'orthographicCamera'
|
|
|
|
|
|
|
|
export type TransformControlsMode = 'translate' | 'rotate' | 'scale'
|
|
|
|
export type TransformControlsSpace = 'world' | 'local'
|
|
|
|
export type ViewportShading = 'wireframe' | 'flat' | 'solid' | 'rendered'
|
|
|
|
|
2021-06-29 15:47:27 +02:00
|
|
|
export const baseSheetObjectType = {
|
|
|
|
props: types.compound({
|
|
|
|
position: types.compound({
|
|
|
|
x: types.number(0),
|
|
|
|
y: types.number(0),
|
|
|
|
z: types.number(0),
|
2021-06-18 13:05:06 +02:00
|
|
|
}),
|
2021-06-29 15:47:27 +02:00
|
|
|
rotation: types.compound({
|
|
|
|
x: types.number(0),
|
|
|
|
y: types.number(0),
|
|
|
|
z: types.number(0),
|
|
|
|
}),
|
|
|
|
scale: types.compound({
|
|
|
|
x: types.number(1),
|
|
|
|
y: types.number(1),
|
|
|
|
z: types.number(1),
|
|
|
|
}),
|
|
|
|
}),
|
2021-06-18 13:05:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export type BaseSheetObjectType = ISheetObject<
|
2021-06-29 15:47:27 +02:00
|
|
|
typeof baseSheetObjectType['props']
|
2021-06-18 13:05:06 +02:00
|
|
|
>
|
|
|
|
|
|
|
|
export interface AbstractEditable<T extends EditableType> {
|
|
|
|
type: T
|
|
|
|
role: 'active' | 'removed'
|
|
|
|
sheetObject?: ISheetObject<any>
|
|
|
|
}
|
|
|
|
|
|
|
|
// all these identical types are to prepare for a future in which different object types have different properties
|
|
|
|
export interface EditableGroup extends AbstractEditable<'group'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditableMesh extends AbstractEditable<'mesh'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditableSpotLight extends AbstractEditable<'spotLight'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditableDirectionalLight
|
|
|
|
extends AbstractEditable<'directionalLight'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditablePointLight extends AbstractEditable<'pointLight'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditablePerspectiveCamera
|
|
|
|
extends AbstractEditable<'perspectiveCamera'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface EditableOrthographicCamera
|
|
|
|
extends AbstractEditable<'orthographicCamera'> {
|
|
|
|
sheetObject?: BaseSheetObjectType
|
|
|
|
}
|
|
|
|
|
|
|
|
export type Editable =
|
|
|
|
| EditableGroup
|
|
|
|
| EditableMesh
|
|
|
|
| EditableSpotLight
|
|
|
|
| EditableDirectionalLight
|
|
|
|
| EditablePointLight
|
|
|
|
| EditablePerspectiveCamera
|
|
|
|
| EditableOrthographicCamera
|
|
|
|
|
|
|
|
export type EditableSnapshot<T extends Editable = Editable> = {
|
|
|
|
proxyObject?: Object3D | null
|
|
|
|
} & T
|
|
|
|
|
|
|
|
export interface AbstractSerializedEditable<T extends EditableType> {
|
|
|
|
type: T
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface SerializedEditableGroup
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'group'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditableMesh
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'mesh'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditableSpotLight
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'spotLight'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditableDirectionalLight
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'directionalLight'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditablePointLight
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'pointLight'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditablePerspectiveCamera
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'perspectiveCamera'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export interface SerializedEditableOrthographicCamera
|
2021-06-29 15:08:02 +02:00
|
|
|
extends AbstractSerializedEditable<'orthographicCamera'> {}
|
2021-06-18 13:05:06 +02:00
|
|
|
|
|
|
|
export type SerializedEditable =
|
|
|
|
| SerializedEditableGroup
|
|
|
|
| SerializedEditableMesh
|
|
|
|
| SerializedEditableSpotLight
|
|
|
|
| SerializedEditableDirectionalLight
|
|
|
|
| SerializedEditablePointLight
|
|
|
|
| SerializedEditablePerspectiveCamera
|
|
|
|
| SerializedEditableOrthographicCamera
|
|
|
|
|
|
|
|
export interface EditableState {
|
|
|
|
editables: Record<string, SerializedEditable>
|
|
|
|
}
|
|
|
|
|
|
|
|
export type EditorStore = {
|
|
|
|
sheet: ISheet | null
|
2021-07-02 20:43:25 +02:00
|
|
|
editorObject: ISheetObject<typeof editorSheetObjectConfig['props']> | null
|
2021-06-18 13:05:06 +02:00
|
|
|
sheetObjects: {[uniqueName in string]?: BaseSheetObjectType}
|
|
|
|
scene: Scene | null
|
|
|
|
gl: WebGLRenderer | null
|
|
|
|
allowImplicitInstancing: boolean
|
|
|
|
orbitControlsRef: MutableRefObject<typeof OrbitControls | undefined> | null
|
|
|
|
helpersRoot: Group
|
|
|
|
editables: Record<string, Editable>
|
|
|
|
// this will come in handy when we start supporting multiple canvases
|
|
|
|
canvasName: string
|
|
|
|
transformControlsMode: TransformControlsMode
|
|
|
|
transformControlsSpace: TransformControlsSpace
|
|
|
|
viewportShading: ViewportShading
|
|
|
|
sceneSnapshot: Scene | null
|
|
|
|
editablesSnapshot: Record<string, EditableSnapshot> | null
|
|
|
|
hdrPaths: string[]
|
|
|
|
selectedHdr: string | null
|
|
|
|
useHdrAsBackground: boolean
|
|
|
|
referenceWindowSize: number
|
|
|
|
initialEditorCamera: ContainerProps['camera']
|
|
|
|
|
|
|
|
init: (
|
|
|
|
scene: Scene,
|
|
|
|
gl: WebGLRenderer,
|
|
|
|
allowImplicitInstancing: boolean,
|
|
|
|
editorCamera: ContainerProps['camera'],
|
|
|
|
sheet: ISheet,
|
2021-07-02 20:43:25 +02:00
|
|
|
editorObject: null | ISheetObject<typeof editorSheetObjectConfig['props']>,
|
2021-06-18 13:05:06 +02:00
|
|
|
) => void
|
|
|
|
|
|
|
|
setOrbitControlsRef: (
|
|
|
|
orbitControlsRef: MutableRefObject<typeof OrbitControls | undefined>,
|
|
|
|
) => void
|
|
|
|
addEditable: <T extends EditableType>(type: T, uniqueName: string) => void
|
|
|
|
removeEditable: (uniqueName: string) => void
|
|
|
|
setSelectedHdr: (hdr: string | null) => void
|
|
|
|
setTransformControlsMode: (mode: TransformControlsMode) => void
|
|
|
|
setTransformControlsSpace: (mode: TransformControlsSpace) => void
|
|
|
|
setViewportShading: (mode: ViewportShading) => void
|
|
|
|
setUseHdrAsBackground: (use: boolean) => void
|
|
|
|
setReferenceWindowSize: (size: number) => void
|
|
|
|
createSnapshot: () => void
|
|
|
|
setSheetObject: (uniqueName: string, sheetObject: BaseSheetObjectType) => void
|
|
|
|
setSnapshotProxyObject: (
|
|
|
|
proxyObject: Object3D | null,
|
|
|
|
uniqueName: string,
|
|
|
|
) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
const config: StateCreator<EditorStore> = (set, get) => {
|
|
|
|
setTimeout(() => {
|
|
|
|
const existingHandler = DefaultLoadingManager.onProgress
|
|
|
|
DefaultLoadingManager.onProgress = (url, loaded, total) => {
|
|
|
|
existingHandler(url, loaded, total)
|
|
|
|
if (url.match(/\.hdr$/)) {
|
|
|
|
set((state) => {
|
|
|
|
const newPaths = new Set(state.hdrPaths)
|
|
|
|
newPaths.add(url)
|
|
|
|
const selectedHdr = newPaths.size === 1 ? url : state.selectedHdr
|
|
|
|
return {hdrPaths: Array.from(newPaths), selectedHdr}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return {
|
|
|
|
sheet: null,
|
2021-07-02 20:43:25 +02:00
|
|
|
editorObject: null,
|
2021-06-18 13:05:06 +02:00
|
|
|
sheetObjects: {},
|
|
|
|
scene: null,
|
|
|
|
gl: null,
|
|
|
|
allowImplicitInstancing: false,
|
|
|
|
orbitControlsRef: null,
|
|
|
|
helpersRoot: new Group(),
|
|
|
|
editables: {},
|
|
|
|
canvasName: 'default',
|
|
|
|
transformControlsMode: 'translate',
|
|
|
|
transformControlsSpace: 'world',
|
|
|
|
viewportShading: 'rendered',
|
|
|
|
sceneSnapshot: null,
|
|
|
|
editablesSnapshot: null,
|
|
|
|
hdrPaths: [],
|
|
|
|
selectedHdr: null,
|
|
|
|
useHdrAsBackground: false,
|
|
|
|
referenceWindowSize: 120,
|
|
|
|
initialEditorCamera: {},
|
|
|
|
|
|
|
|
init: (
|
|
|
|
scene,
|
|
|
|
gl,
|
|
|
|
allowImplicitInstancing,
|
|
|
|
editorCamera,
|
|
|
|
sheet,
|
2021-07-02 20:43:25 +02:00
|
|
|
editorObject,
|
2021-06-18 13:05:06 +02:00
|
|
|
) => {
|
|
|
|
set({
|
|
|
|
scene,
|
|
|
|
gl,
|
|
|
|
allowImplicitInstancing,
|
|
|
|
initialEditorCamera: editorCamera,
|
|
|
|
sheet,
|
2021-07-02 20:43:25 +02:00
|
|
|
editorObject,
|
2021-06-18 13:05:06 +02:00
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
addEditable: (type, uniqueName) =>
|
|
|
|
set((state) => {
|
|
|
|
if (state.editables[uniqueName]) {
|
|
|
|
if (
|
|
|
|
state.editables[uniqueName].type !== type &&
|
|
|
|
process.env.NODE_ENV === 'development'
|
|
|
|
) {
|
|
|
|
console.error(`Warning: There is a mismatch between the serialized type of ${uniqueName} and the one set when adding it to the scene.
|
|
|
|
Serialized: ${state.editables[uniqueName].type}.
|
|
|
|
Current: ${type}.
|
|
|
|
|
|
|
|
This might have happened either because you changed the type of an object, in which case a re-export will solve the issue, or because you re-used the uniqueName for an object of a different type, which is an error.`)
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
state.editables[uniqueName].role === 'active' &&
|
|
|
|
!state.allowImplicitInstancing
|
|
|
|
) {
|
|
|
|
throw Error(
|
|
|
|
`Scene already has an editable object named ${uniqueName}.
|
|
|
|
If this is intentional, please set the allowImplicitInstancing prop of EditableManager to true.`,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
editables: {
|
|
|
|
...state.editables,
|
|
|
|
[uniqueName]: {
|
|
|
|
type: type as EditableType,
|
|
|
|
role: 'active',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
setOrbitControlsRef: (camera) => {
|
|
|
|
set({orbitControlsRef: camera})
|
|
|
|
},
|
|
|
|
removeEditable: (name) =>
|
|
|
|
set((state) => {
|
|
|
|
const {[name]: removed, ...rest} = state.editables
|
|
|
|
return {
|
|
|
|
editables: {
|
|
|
|
...rest,
|
|
|
|
[name]: {...removed, role: 'removed'},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
setSheetObject: (uniqueName, sheetObject) => {
|
|
|
|
set((state) => ({
|
|
|
|
sheetObjects: {
|
|
|
|
...state.sheetObjects,
|
|
|
|
[uniqueName]: sheetObject,
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
setSelectedHdr: (hdr) => {
|
|
|
|
set({selectedHdr: hdr})
|
|
|
|
},
|
|
|
|
setTransformControlsMode: (mode) => {
|
|
|
|
set({transformControlsMode: mode})
|
|
|
|
},
|
|
|
|
setTransformControlsSpace: (mode) => {
|
|
|
|
set({transformControlsSpace: mode})
|
|
|
|
},
|
|
|
|
setViewportShading: (mode) => {
|
|
|
|
set({viewportShading: mode})
|
|
|
|
},
|
2021-07-03 13:28:35 +02:00
|
|
|
|
2021-06-18 13:05:06 +02:00
|
|
|
setUseHdrAsBackground: (use) => {
|
|
|
|
set({useHdrAsBackground: use})
|
|
|
|
},
|
2021-07-03 13:20:40 +02:00
|
|
|
|
2021-06-18 13:05:06 +02:00
|
|
|
setReferenceWindowSize: (size) => set({referenceWindowSize: size}),
|
|
|
|
createSnapshot: () => {
|
|
|
|
set((state) => ({
|
|
|
|
sceneSnapshot: state.scene?.clone() ?? null,
|
|
|
|
editablesSnapshot: state.editables,
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
setSnapshotProxyObject: (proxyObject, uniqueName) => {
|
|
|
|
set((state) => ({
|
|
|
|
editablesSnapshot: {
|
|
|
|
...state.editablesSnapshot,
|
|
|
|
[uniqueName]: {
|
|
|
|
...state.editablesSnapshot![uniqueName],
|
|
|
|
proxyObject,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const useEditorStore = create<EditorStore>(config)
|
|
|
|
|
|
|
|
export type BindFunction = (options: {
|
|
|
|
allowImplicitInstancing?: boolean
|
|
|
|
editorCamera?: ContainerProps['camera']
|
|
|
|
sheet: ISheet
|
|
|
|
}) => (options: {gl: WebGLRenderer; scene: Scene}) => void
|
|
|
|
|
2021-07-02 20:43:25 +02:00
|
|
|
const editorSheetObjectConfig = {
|
|
|
|
props: types.compound({
|
2021-07-02 20:47:54 +02:00
|
|
|
isOpen: types.boolean(false),
|
2021-07-03 13:20:40 +02:00
|
|
|
showAxes: types.boolean(true),
|
2021-07-03 13:24:39 +02:00
|
|
|
showGrid: types.boolean(true),
|
2021-07-03 13:28:35 +02:00
|
|
|
showOverlayIcons: types.boolean(false),
|
2021-07-02 20:43:25 +02:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
2021-06-29 15:39:47 +02:00
|
|
|
export const bindToCanvas: BindFunction = ({
|
|
|
|
allowImplicitInstancing = false,
|
|
|
|
editorCamera = {},
|
|
|
|
sheet,
|
|
|
|
}) => {
|
2021-06-29 16:00:27 +02:00
|
|
|
const uiSheet: null | ISheet =
|
|
|
|
process.env.NODE_ENV === 'development'
|
|
|
|
? getProject('R3F Plugin').sheet('UI')
|
|
|
|
: null
|
|
|
|
|
2021-07-02 20:43:25 +02:00
|
|
|
const editorSheetObject =
|
|
|
|
uiSheet?.object('Editor', null, editorSheetObjectConfig) || null
|
|
|
|
|
2021-06-29 15:39:47 +02:00
|
|
|
return ({gl, scene}) => {
|
|
|
|
const init = useEditorStore.getState().init
|
|
|
|
init(
|
|
|
|
scene,
|
|
|
|
gl,
|
|
|
|
allowImplicitInstancing,
|
|
|
|
{...{position: [20, 20, 20]}, ...editorCamera},
|
|
|
|
sheet,
|
2021-07-02 20:43:25 +02:00
|
|
|
editorSheetObject,
|
2021-06-29 15:39:47 +02:00
|
|
|
)
|
2021-06-18 13:05:06 +02:00
|
|
|
}
|
|
|
|
}
|