diff --git a/webclient/src/App.tsx b/webclient/src/App.tsx index 8630c58..46bfece 100644 --- a/webclient/src/App.tsx +++ b/webclient/src/App.tsx @@ -2,10 +2,10 @@ import { BrowserRouter, Link, Outlet, Route, Routes, useLocation, useNavigate, u import { VoxelEditorPage } from './components/voxelEditor/VoxelEditorPage'; import { useEffect, useLayoutEffect, useRef } from 'react'; import { reaction } from 'mobx'; -import { ThreeView } from './components/ThreeView'; import { state } from './state'; import './App.scss'; import { Panels } from './components/Panels'; +import { LayeredThreeView } from './components/layers/LayeredThreeView'; function StateToUrlSync() { const navigate = useNavigate(); @@ -94,7 +94,7 @@ function EditorLayout() { return ( <> - + ); diff --git a/webclient/src/components/DebugLabel.tsx b/webclient/src/components/DebugLabel.tsx index 391acb8..6178c74 100644 --- a/webclient/src/components/DebugLabel.tsx +++ b/webclient/src/components/DebugLabel.tsx @@ -2,13 +2,13 @@ import { Html } from "@react-three/drei"; import { useFrame } from "@react-three/fiber"; import { useRef } from "react"; import * as THREE from "three"; -import type { RuntimeObjectInstance } from "../types"; import { debugLabelsStore } from "../state/debugLabelsState"; import { state } from "../state"; import { observer } from "mobx-react-lite"; import { formatV3 } from "../utils"; +import type { ObjectInstance } from "../types"; -export const DebugLabel = observer(function ({ obj }: { obj: RuntimeObjectInstance; }) { +export const DebugLabel = observer(function ({ obj }: { obj: ObjectInstance; }) { const groupRef = useRef(null); const rb = obj.rb(); diff --git a/webclient/src/components/DebugLabelsView.tsx b/webclient/src/components/DebugLabelsView.tsx index bd8f6d0..1d84431 100644 --- a/webclient/src/components/DebugLabelsView.tsx +++ b/webclient/src/components/DebugLabelsView.tsx @@ -2,9 +2,8 @@ import { useEffect } from "react"; import { observer } from "mobx-react-lite"; import { debugLabelsStore } from "../state/debugLabelsState"; import { DebugLabel } from "./DebugLabel"; -import type { RuntimeScene } from "../types"; -export const DebugLabelsView = observer(function ({ scene, editMode }: { scene: RuntimeScene, editMode?: boolean }) { +export const DebugLabelsView = observer(function ({ scene, editMode }: { scene: Scene, editMode?: boolean }) { useEffect( () => { return () => { debugLabelsStore.clear(); }; diff --git a/webclient/src/components/GameObjectView.tsx b/webclient/src/components/GameObjectView.tsx index f385819..2f7e392 100644 --- a/webclient/src/components/GameObjectView.tsx +++ b/webclient/src/components/GameObjectView.tsx @@ -1,11 +1,10 @@ import { observer } from "mobx-react-lite"; -import type { RuntimeGameObjectInstance } from "../types"; import { state } from "../state"; import { ObjectViewInternal } from "./ObjectViewInternal"; import { PlayerObjectView } from "./CharacterView"; export type GameObjectViewProps = { - object: RuntimeGameObjectInstance; + object: ObjectInstance; isPlayer: boolean; isSceneEditor?: boolean; } @@ -16,7 +15,7 @@ export const GameObjectView = observer(function (props: GameObjectViewProps) { return null; if (props.isPlayer) - return ; + return ; function handleClick(): void { state.game?.emitEvent('objectClicked', { object: props.object }); diff --git a/webclient/src/components/ObjectEditorView.tsx b/webclient/src/components/ObjectEditorView.tsx index 8528020..afb299f 100644 --- a/webclient/src/components/ObjectEditorView.tsx +++ b/webclient/src/components/ObjectEditorView.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react-lite"; import { runInAction } from "mobx"; -import type { ObjectInstance, R3, Runtime } from "../types"; +import type { ObjectInstance, R3 } from "../types"; import { useMemo, useRef, useEffect, useCallback, type RefObject } from "react"; import type { Group } from "three"; import { Euler, Quaternion, Vector3, BoxHelper, type LineBasicMaterial } from "three"; @@ -11,7 +11,7 @@ import { nextObjectEditMode } from "../state/worldEditorState"; import { ObjectViewInternal, type ObjectViewInternalHandle } from "./ObjectViewInternal"; type ObjectEditorViewProps = { - object: Runtime; + object: ObjectInstance; isPlayer: boolean; } diff --git a/webclient/src/components/ObjectViewInternal.tsx b/webclient/src/components/ObjectViewInternal.tsx index 3b982c6..afd0eac 100644 --- a/webclient/src/components/ObjectViewInternal.tsx +++ b/webclient/src/components/ObjectViewInternal.tsx @@ -1,4 +1,4 @@ -import type { ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types"; +import type { ObjectInstance, ObjectType, RuntimeGameObjectInstance, RuntimeObjectInstance } from "../types"; import type { VoxelGroup } from "../types/runtime/voxelGroup"; import { useEffect, useImperativeHandle, useRef, type Ref, memo, useMemo } from "react"; import { Euler, Quaternion, Mesh, MeshStandardMaterial, type Group } from "three"; @@ -8,10 +8,10 @@ import { SyncRigidBody } from "./SyncRigidBody"; import { reaction, runInAction } from "mobx"; import { v3toRapier } from "../utils"; import { state } from "../state"; -import { buildVoxelMesh } from "../utils/graphics/voxelMesh"; +import { buildVoxelGroupMesh } from "../utils/graphics/voxelMesh"; const VoxelGroupMesh = memo(function VoxelGroupMesh({ vg, opacity }: { vg: VoxelGroup, opacity: number }) { - const geo = useMemo(() => buildVoxelMesh(vg.positions), [vg]); + const geo = useMemo(() => buildVoxelGroupMesh(vg.positions), [vg]); useEffect(() => () => { geo.dispose(); }, [geo]); const op = vg.opacity * opacity; @@ -29,7 +29,7 @@ export type ObjectViewInternalHandle = { } type ObjectViewInternalProps = { - object: Omit; + object: Omit; objectType: ObjectType; editMode?: boolean; isPlayer?: boolean; @@ -54,19 +54,17 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props if (!rbRef.current) return; - const gameObj = object as RuntimeGameObjectInstance; + rbRef.current.setTranslation(v3toRapier(object.position), true); - rbRef.current.setTranslation(v3toRapier(gameObj.position), true); - - const euler = new Euler(gameObj.rotation[0], gameObj.rotation[1], gameObj.rotation[2]); + const euler = new Euler(...object.rotation); const q = new Quaternion().setFromEuler(euler); rbRef.current.setRotation({ x: q.x, y: q.y, z: q.z, w: q.w }, true); - groupRef.current?.scale.set(...gameObj.scale); + groupRef.current?.scale.set(...object.scale); - rbRef.current.setLinvel(v3toRapier(gameObj.linearVelocity), true); + rbRef.current.setLinvel(v3toRapier(object.linearVelocity), true); - rbRef.current.setAngvel(v3toRapier(gameObj.angularVelocity), true); + rbRef.current.setAngvel(v3toRapier(object.angularVelocity), true); }, ); }, @@ -141,8 +139,8 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props object.position = data.position; object.rotation = data.rotation; if (!props.editMode) { - (object as RuntimeGameObjectInstance).linearVelocity = data.linearVelocity; - (object as RuntimeGameObjectInstance).angularVelocity = data.radialVelocity; + (object as ObjectInstance).linearVelocity = data.linearVelocity; + (object as ObjectInstance).angularVelocity = data.radialVelocity; } // do not change object.version to not trigger rerender }); @@ -154,7 +152,7 @@ export const ObjectViewInternal = function ({ object, objectType, ref, ...props scale={object.scale} onClick={handleClick} > - { + {/* { object.cache.voxelGroups.map((vg) => ) } - {object.cache.colliderMesh && } + {object.cache.colliderMesh && } */} ); diff --git a/webclient/src/components/SceneEditorView.tsx b/webclient/src/components/SceneEditorView.tsx index 5e04b94..aee90a1 100644 --- a/webclient/src/components/SceneEditorView.tsx +++ b/webclient/src/components/SceneEditorView.tsx @@ -1,6 +1,6 @@ import { Suspense, useRef } from 'react'; import type React from 'react'; -import { Grid, OrbitControls } from '@react-three/drei'; +import { OrbitControls } from '@react-three/drei'; import { observer } from 'mobx-react-lite'; import { state } from '../state'; import { SceneView } from './SceneView'; diff --git a/webclient/src/components/SceneView.tsx b/webclient/src/components/SceneView.tsx index 348d204..4bc85fa 100644 --- a/webclient/src/components/SceneView.tsx +++ b/webclient/src/components/SceneView.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react-lite"; -import type { RuntimeGameObjectInstance, RuntimeScene } from "../types"; +import type { RuntimeScene } from "../types"; import { GameObjectView } from "./GameObjectView"; import { ObjectEditorView } from "./ObjectEditorView"; import { DebugLabelsView } from "./DebugLabelsView"; diff --git a/webclient/src/components/layers/DebugLabelsLayer.tsx b/webclient/src/components/layers/DebugLabelsLayer.tsx new file mode 100644 index 0000000..091d4c2 --- /dev/null +++ b/webclient/src/components/layers/DebugLabelsLayer.tsx @@ -0,0 +1,5 @@ +import { observer } from "mobx-react-lite"; + +export const DebugLabelsLayer = observer(function () { + return null; //TODO +}); diff --git a/webclient/src/components/layers/GameraLayer.tsx b/webclient/src/components/layers/GameraLayer.tsx new file mode 100644 index 0000000..7d4e488 --- /dev/null +++ b/webclient/src/components/layers/GameraLayer.tsx @@ -0,0 +1,27 @@ +import { useFrame } from "@react-three/fiber"; +import type { Pos3 } from "../../types"; +import { Euler, Quaternion, Vector3 } from "three"; + +const _q = new Quaternion(); +const _e = new Euler(0, 0, 0, 'YXZ'); +const _offset = new Vector3(); +const _charPos = new Vector3(); +const _lookAt = new Vector3(); + +type Props = { + camera: Pos3; +}; + +export function CameraLayer({ camera: cameraPos }: Props) { + + useFrame(({ camera }) => { + // const t = rb.translation(); + // _charPos.set(t.x, t.y, t.z); + // _offset.copy(shoulderOffset).applyEuler(_e.set(pitchRef.current, yawRef.current, 0, 'YXZ')); + // camera.position.copy(_charPos).add(_offset); + // _lookAt.set(t.x, t.y + lookAtY, t.z); + camera.lookAt(_lookAt); + }); + + return null; +} diff --git a/webclient/src/components/layers/LayeredThreeView.tsx b/webclient/src/components/layers/LayeredThreeView.tsx new file mode 100644 index 0000000..d22447d --- /dev/null +++ b/webclient/src/components/layers/LayeredThreeView.tsx @@ -0,0 +1,23 @@ +import { observer } from "mobx-react-lite"; +import { ObjectGeomertiesLayer } from "./ObjectGeomertiesLayer"; +import { Canvas } from "@react-three/fiber"; +import { Perf } from "r3f-perf"; +import { state } from "../../state"; +import { CameraLayer } from "./GameraLayer"; + +export const LayeredThreeView = observer(function () { + + return <> + + + + + + + + + + ; +}); diff --git a/webclient/src/components/layers/ObjectGeomertiesLayer.tsx b/webclient/src/components/layers/ObjectGeomertiesLayer.tsx new file mode 100644 index 0000000..52a97dc --- /dev/null +++ b/webclient/src/components/layers/ObjectGeomertiesLayer.tsx @@ -0,0 +1,26 @@ +import { useEffect, useRef } from "react"; +import { useThree, useFrame } from "@react-three/fiber"; +import { ObjectGeometryList } from "../../rendering/ObjectGeometryList"; +import { observer } from "mobx-react-lite"; +import { state } from "../../state"; + +export const ObjectGeomertiesLayer = observer(function () { + + const geometries = state.view.objectGeometries; + + const threeScene = useThree(s => s.scene); + const list = useRef(null); + + useEffect(() => { + const l = new ObjectGeometryList(threeScene, geometries); + list.current = l; + return () => { + l.dispose(); + list.current = null; + }; + }, [threeScene, geometries]); + + useFrame(() => list.current?.tick()); + + return null; +}); diff --git a/webclient/src/components/voxelEditor/VoxelEditorPage.tsx b/webclient/src/components/voxelEditor/VoxelEditorPage.tsx index bab5708..906fb92 100644 --- a/webclient/src/components/voxelEditor/VoxelEditorPage.tsx +++ b/webclient/src/components/voxelEditor/VoxelEditorPage.tsx @@ -1,11 +1,11 @@ import { Canvas } from '@react-three/fiber'; -import { useParams, useNavigate, Link } from 'react-router-dom'; +import { useNavigate, Link, useParams } from 'react-router-dom'; import { observer } from 'mobx-react-lite'; import { useState, useCallback, useEffect, useRef } from 'react'; -import { state } from '../../state'; import type { Voxel, V3 } from '../../types'; import { VoxelEditorScene } from './VoxelEditorScene'; import { IconArrowLeft, IconArrowBackUp, IconArrowForwardUp, IconPlus, IconTrash } from '@tabler/icons-react'; +import { state } from '../../state'; const PALETTE = [ '#ffffff', '#cccccc', '#999999', '#555555', '#222222', @@ -19,7 +19,10 @@ type HistoryEntry = | { type: 'remove'; voxel: Voxel }; export const VoxelEditorPage = observer(function () { + const { id } = useParams<{ id: string }>(); + const objectType = id ? state.view.objectTypeGeometries.get(id) : undefined; + const navigate = useNavigate(); const [mode, setMode] = useState<'add' | 'remove'>('add'); const [color, setColor] = useState('#808080'); @@ -28,14 +31,11 @@ export const VoxelEditorPage = observer(function () { const history = useRef([]); const future = useRef([]); - const objectType = id ? state.world.getObjectTypeById(id) : undefined; - useEffect(() => { return () => { - if (id) -state.worldEditor.refreshObjectTypeCaches(id); + objectType?.updateRuntimeData(); }; - }, [id]); + }, [objectType]); function pushHistory(entry: HistoryEntry) { history.current.push(entry); @@ -46,31 +46,31 @@ state.worldEditor.refreshObjectTypeCaches(id); const undo = useCallback(() => { const entry = history.current.pop(); - if (!entry || !id) + if (!entry) return; if (entry.type === 'add') { - state.worldEditor.removeVoxelFromObjectType(id, entry.voxel.position); + objectType?.removeVoxel(entry.voxel.position); } else { - state.worldEditor.addVoxelToObjectType(id, entry.voxel); + objectType?.addVoxel(entry.voxel); } future.current.push(entry); setCanUndo(history.current.length > 0); setCanRedo(true); - }, [id]); + }, [objectType]); const redo = useCallback(() => { const entry = future.current.pop(); - if (!entry || !id) + if (!entry) return; if (entry.type === 'add') { - state.worldEditor.addVoxelToObjectType(id, entry.voxel); + objectType?.addVoxel(entry.voxel); } else { - state.worldEditor.removeVoxelFromObjectType(id, entry.voxel.position); + objectType?.removeVoxel(entry.voxel.position); } history.current.push(entry); setCanUndo(true); setCanRedo(future.current.length > 0); - }, [id]); + }, [objectType]); useEffect(() => { function onKeyDown(e: KeyboardEvent) { @@ -84,24 +84,14 @@ state.worldEditor.refreshObjectTypeCaches(id); }, [undo, redo]); function handleAdd(voxel: Voxel) { - if (!id) - return; - state.worldEditor.addVoxelToObjectType(id, voxel); - pushHistory({ type: 'add', voxel }); + if (objectType?.addVoxel(voxel)) + pushHistory({ type: 'add', voxel }); } function handleRemove(position: V3) { - if (!id || !objectType) - return; - const voxel = objectType.voxels.find(v => - v.position[0] === position[0] && - v.position[1] === position[1] && - v.position[2] === position[2] - ); - if (!voxel) - return; - state.worldEditor.removeVoxelFromObjectType(id, position); - pushHistory({ type: 'remove', voxel }); + const voxel = objectType?.removeVoxel(position); + if (voxel) + pushHistory({ type: 'remove', voxel }); } if (!objectType) { @@ -116,11 +106,11 @@ state.worldEditor.refreshObjectTypeCaches(id);
{/* Top bar */}
- - {objectType.name} - {objectType.voxels.length} voxels + {objectType.objectType.name} + {objectType.objectType.voxels.length} voxels