object rotation and scaling in editor
This commit is contained in:
parent
742e841ce0
commit
bc9e8e8e4e
|
|
@ -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) && <TransformControls object={groupRef as RefObject<Group>} onChange={handleObjectChange} mode="translate" />}
|
||||
<group ref={groupRef} position={object.position} rotation={object.rotation} onClick={handleClick}>
|
||||
{
|
||||
(isSelected && (selectionMode !== undefined) && groupRef.current) &&
|
||||
<TransformControls
|
||||
object={groupRef as RefObject<Group>}
|
||||
onChange={handleObjectChange}
|
||||
mode={selectionMode}
|
||||
/>
|
||||
}
|
||||
<group
|
||||
ref={groupRef}
|
||||
position={object.position}
|
||||
rotation={object.rotation}
|
||||
scale={object.scale}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{
|
||||
objectType.voxels.map((v, idx) => {
|
||||
const vt = state.world.getVoxelTypeById(v.typeId);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export const ThreeView = observer(function () {
|
|||
<div style={{ width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
||||
<Canvas
|
||||
camera={state.world.character.camera}
|
||||
onPointerMissed={() => state.worldEditor.setSelectedObjectId(undefined)}
|
||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
||||
>
|
||||
<WorldView />
|
||||
</Canvas>
|
||||
|
|
|
|||
|
|
@ -19,3 +19,5 @@ export class RootState {
|
|||
}
|
||||
|
||||
export const state = new RootState();
|
||||
|
||||
state.world.load();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue