diff --git a/src/components/ObjectView.tsx b/src/components/ObjectView.tsx index 96641c3..ba02f33 100644 --- a/src/components/ObjectView.tsx +++ b/src/components/ObjectView.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, type RefObject } from "react"; import type { Group, Mesh } from "three"; import { Edges, TransformControls } from "@react-three/drei"; import { state } from "../state"; +import { nextSelectionEditMode } from "../state/worldEditor"; type ObjectViewProps = { object: ObjectInstance; @@ -18,6 +19,9 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) { return null; const isSelected = state.worldEditor.isEnabled && state.worldEditor.selectedObjectId === object.id; + const selectionMode = isSelected + ? state.worldEditor.selectedObjectMode + : undefined; useEffect(() => { if (!isSelected) @@ -38,7 +42,7 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) { return; e.stopPropagation(); - state.worldEditor.setSelectedObjectId(object.id); + state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(selectionMode)); }; const handleObjectChange = () => { @@ -46,13 +50,30 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) { if (!group) return; - const { x, y, z } = group.position; - state.worldEditor.setObjectPosition(object.id, [x, y, z]); + state.worldEditor.setObjectTransform( + object.id, + group.position.toArray(), + group.rotation.toArray().slice(0, 3), // chop EulerOrder off array + group.scale.toArray(), + ); }; return (<> - {(isSelected && groupRef.current) && } onChange={handleObjectChange} mode="translate" />} - + { + (isSelected && (selectionMode !== undefined) && groupRef.current) && + } + onChange={handleObjectChange} + mode={selectionMode} + /> + } + { objectType.voxels.map((v, idx) => { const vt = state.world.getVoxelTypeById(v.typeId); diff --git a/src/components/ThreeView.tsx b/src/components/ThreeView.tsx index 66d4ffd..bc631d6 100644 --- a/src/components/ThreeView.tsx +++ b/src/components/ThreeView.tsx @@ -8,7 +8,7 @@ export const ThreeView = observer(function () {
state.worldEditor.setSelectedObjectId(undefined)} + onPointerMissed={() => state.worldEditor.resetSelectedObject()} > diff --git a/src/state/root.ts b/src/state/root.ts index fe04f6d..7441629 100644 --- a/src/state/root.ts +++ b/src/state/root.ts @@ -19,3 +19,5 @@ export class RootState { } export const state = new RootState(); + +state.world.load(); diff --git a/src/state/world.ts b/src/state/world.ts index 266fddb..c10a886 100644 --- a/src/state/world.ts +++ b/src/state/world.ts @@ -2,8 +2,8 @@ import { makeAutoObservable } from "mobx"; import { WorldFactory } from "../model/worldFactory"; import type { ObjectType, RunningGameState, Scene, World } from "../types"; import { CharacterState } from "./character"; -import type { Pos3, V3 } from "../types/3d"; -import { clone, randomId } from "../utils"; +import type { Pos3 } from "../types/3d"; +import { clone } from "../utils"; import type { VoxelType } from "../types/voxel"; import { state } from "./root"; import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes"; @@ -14,16 +14,15 @@ export class WorldState { public character = new CharacterState(this); constructor() { - this.load(); makeAutoObservable(this); } public reset() { + state.worldEditor.resetSelectedObject(); this.data = WorldFactory.create(); } public loadMock() { - const objTypeId = 'test1'; const voxelTypeId = 'glass'; const objectId1 = 'object1'; @@ -58,12 +57,14 @@ export class WorldState { typeId: objTypeId, position: [0, 0, 0], rotation: [0, 0, 0], + scale: [1, 1, 1], }, [objectId2]: { id: objectId2, typeId: objTypeId, position: [1, 0, 0], rotation: [0, 0, 0], + scale: [1, 1, 1], }, }, }, @@ -74,11 +75,13 @@ export class WorldState { playing: false, }, }; + state.worldEditor.resetSelectedObject(); this.save(); } public load() { this.data = WorldFactory.load() ?? WorldFactory.create(); + state.worldEditor.resetSelectedObject(); } public save(): void { @@ -122,8 +125,7 @@ export class WorldState { scene: clone(this.data.initialScene), } } - - state.worldEditor.setSelectedObjectId(undefined); + state.worldEditor.resetSelectedObject(); } public pause(): void { diff --git a/src/state/worldEditor.ts b/src/state/worldEditor.ts index 06a946b..5ef95bb 100644 --- a/src/state/worldEditor.ts +++ b/src/state/worldEditor.ts @@ -3,12 +3,29 @@ import type { WorldState } from "./world"; import type { ObjectInstance, Scene, World } from "../types"; import { createObjectInstance } from "../utils/object"; import { randomId } from "../utils"; -import type { Pos3, V3 } from "../types/3d"; +import type { Pos3, R3, V3 } from "../types/3d"; + +export const SelectionEditModeEnum = [ + 'translate', + 'rotate', + 'scale', +] as const; +type SelectionEditModeTuple = typeof SelectionEditModeEnum; +export type SelectionEditMode = SelectionEditModeTuple[number]; + +export function nextSelectionEditMode(mode: SelectionEditMode | undefined): SelectionEditMode { + if (mode === undefined) + return 'translate'; + + const idx = SelectionEditModeEnum.indexOf(mode); + return SelectionEditModeEnum[(idx + 1) % SelectionEditModeEnum.length]; +} export class WorldEditorState { private readonly world: WorldState; public selectedObjectId: string | undefined; + public selectedObjectMode: SelectionEditMode | undefined; public isDragging: boolean = false; constructor(world: WorldState) { @@ -25,8 +42,14 @@ export class WorldEditorState { this.world.save(); } - public setSelectedObjectId(value: string | undefined): void { - this.selectedObjectId = value; + public setSelectedObject(id: string, mode: SelectionEditMode): void { + this.selectedObjectId = id; + this.selectedObjectMode = mode; + } + + public resetSelectedObject(): void { + this.selectedObjectId = undefined; + this.selectedObjectMode = undefined; } public setIsDragging(value: boolean): void { @@ -67,13 +90,22 @@ export class WorldEditorState { }); } - public setObjectPosition(id: string, pos: V3): void { + public setObjectTransform( + id: string, + position: V3, + rotation: R3, + scale: V3, + ): void { const obj = this.scene.objects[id]; if (!obj) return; - obj.position = pos; - this.mutateObject(obj); + this.mutateObject({ + ...obj, + position, + rotation, + scale, + }); } public addObjectCloneAtRandomPosition(typeId: string): ObjectInstance { diff --git a/src/types/object.ts b/src/types/object.ts index 6cce804..6434ec5 100644 --- a/src/types/object.ts +++ b/src/types/object.ts @@ -1,3 +1,4 @@ +import type { R3, V3 } from "./3d"; import type { Voxel } from "./voxel"; export type ObjectType = { @@ -9,6 +10,7 @@ export type ObjectType = { export type ObjectInstance = { id: string; typeId: string; - position: [number, number, number]; - rotation: [number, number, number]; + position: V3; + rotation: R3; + scale: V3; } \ No newline at end of file