Compare commits
No commits in common. "bc9e8e8e4e439d0b4878496b3a7e31e62ca430ef" and "7ebccba19927c5a770145b3c769eebccbda9312d" have entirely different histories.
bc9e8e8e4e
...
7ebccba199
|
|
@ -4,7 +4,6 @@ import { useEffect, useRef, type RefObject } from "react";
|
||||||
import type { Group, Mesh } from "three";
|
import type { Group, Mesh } from "three";
|
||||||
import { Edges, TransformControls } from "@react-three/drei";
|
import { Edges, TransformControls } from "@react-three/drei";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { nextSelectionEditMode } from "../state/worldEditor";
|
|
||||||
|
|
||||||
type ObjectViewProps = {
|
type ObjectViewProps = {
|
||||||
object: ObjectInstance;
|
object: ObjectInstance;
|
||||||
|
|
@ -19,9 +18,6 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
|
||||||
return null;
|
return null;
|
||||||
const isSelected = state.worldEditor.isEnabled &&
|
const isSelected = state.worldEditor.isEnabled &&
|
||||||
state.worldEditor.selectedObjectId === object.id;
|
state.worldEditor.selectedObjectId === object.id;
|
||||||
const selectionMode = isSelected
|
|
||||||
? state.worldEditor.selectedObjectMode
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSelected)
|
if (!isSelected)
|
||||||
|
|
@ -42,7 +38,7 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
|
||||||
return;
|
return;
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(selectionMode));
|
state.worldEditor.setSelectedObjectId(object.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleObjectChange = () => {
|
const handleObjectChange = () => {
|
||||||
|
|
@ -50,43 +46,20 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
|
||||||
if (!group)
|
if (!group)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
state.worldEditor.setObjectTransform(
|
const { x, y, z } = group.position;
|
||||||
object.id,
|
state.worldEditor.setObjectPosition(object.id, [x, y, z]);
|
||||||
group.position.toArray(),
|
|
||||||
group.rotation.toArray().slice(0, 3), // chop EulerOrder off array
|
|
||||||
group.scale.toArray(),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
{
|
{(isSelected && groupRef.current) && <TransformControls object={groupRef as RefObject<Group>} onChange={handleObjectChange} mode="translate" />}
|
||||||
(isSelected && (selectionMode !== undefined) && groupRef.current) &&
|
<group ref={groupRef} position={object.position} rotation={object.rotation} onClick={handleClick}>
|
||||||
<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) => {
|
objectType.voxels.map((v, idx) => {
|
||||||
const vt = state.world.getVoxelTypeById(v.typeId);
|
const vt = state.world.getVoxelTypeById(v.typeId);
|
||||||
const color = (v.color ?? vt?.color) ?? 'white';
|
|
||||||
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
|
|
||||||
return (
|
return (
|
||||||
<mesh key={idx} position={v.position}>
|
<mesh key={idx} position={v.position}>
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial color={(v.color ?? vt?.color) ?? 'white'} opacity={(v.opacity ?? vt?.opacity) ?? 1} />
|
||||||
color={color}
|
|
||||||
opacity={opacity}
|
|
||||||
transparent={opacity < 1}
|
|
||||||
/>
|
|
||||||
{isSelected && <Edges color="white" lineWidth={3} stencilWrite={false} />}
|
{isSelected && <Edges color="white" lineWidth={3} stencilWrite={false} />}
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export const ThreeView = observer(function () {
|
||||||
<div style={{ width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
<div style={{ width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
|
||||||
<Canvas
|
<Canvas
|
||||||
camera={state.world.character.camera}
|
camera={state.world.character.camera}
|
||||||
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
|
onPointerMissed={() => state.worldEditor.setSelectedObjectId(undefined)}
|
||||||
>
|
>
|
||||||
<WorldView />
|
<WorldView />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const dirt: VoxelType = {
|
||||||
name: 'Dirt',
|
name: 'Dirt',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
collidable: true,
|
collidable: true,
|
||||||
color: '#302520',
|
color: '302520',
|
||||||
};
|
};
|
||||||
|
|
||||||
const water: VoxelType = {
|
const water: VoxelType = {
|
||||||
|
|
@ -21,15 +21,15 @@ const water: VoxelType = {
|
||||||
name: 'Stone',
|
name: 'Stone',
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
collidable: false,
|
collidable: false,
|
||||||
color: '#80a0f0',
|
color: 'c0d0ff',
|
||||||
};
|
};
|
||||||
|
|
||||||
const glass: VoxelType = {
|
const glass: VoxelType = {
|
||||||
id: 'glass',
|
id: 'glass',
|
||||||
name: 'Glass',
|
name: 'Glass',
|
||||||
opacity: 0.3,
|
opacity: 0.4,
|
||||||
collidable: true,
|
collidable: true,
|
||||||
color: '#b0d8e8',
|
color: 'f0f8ff',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VOXEL_TYPES = {
|
export const DEFAULT_VOXEL_TYPES = {
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,3 @@ export class RootState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const state = new RootState();
|
export const state = new RootState();
|
||||||
|
|
||||||
state.world.load();
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { makeAutoObservable } from "mobx";
|
||||||
import { WorldFactory } from "../model/worldFactory";
|
import { WorldFactory } from "../model/worldFactory";
|
||||||
import type { ObjectType, RunningGameState, Scene, World } from "../types";
|
import type { ObjectType, RunningGameState, Scene, World } from "../types";
|
||||||
import { CharacterState } from "./character";
|
import { CharacterState } from "./character";
|
||||||
import type { Pos3 } from "../types/3d";
|
import type { Pos3, V3 } from "../types/3d";
|
||||||
import { clone } from "../utils";
|
import { clone, randomId } from "../utils";
|
||||||
import type { VoxelType } from "../types/voxel";
|
import type { VoxelType } from "../types/voxel";
|
||||||
import { state } from "./root";
|
import { state } from "./root";
|
||||||
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
||||||
|
|
@ -14,19 +14,19 @@ export class WorldState {
|
||||||
public character = new CharacterState(this);
|
public character = new CharacterState(this);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.load();
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset() {
|
public reset() {
|
||||||
state.worldEditor.resetSelectedObject();
|
|
||||||
this.data = WorldFactory.create();
|
this.data = WorldFactory.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadMock() {
|
public loadMock() {
|
||||||
|
|
||||||
const objTypeId = 'test1';
|
const objTypeId = 'test1';
|
||||||
const voxelTypeId = 'glass';
|
const voxelTypeId = 'water';
|
||||||
const objectId1 = 'object1';
|
const objectId = 'object1';
|
||||||
const objectId2 = 'object2';
|
|
||||||
|
|
||||||
this.data = {
|
this.data = {
|
||||||
objectTypes: {
|
objectTypes: {
|
||||||
|
|
@ -52,19 +52,11 @@ export class WorldState {
|
||||||
look: [0, 0, 0],
|
look: [0, 0, 0],
|
||||||
},
|
},
|
||||||
objects: {
|
objects: {
|
||||||
[objectId1]: {
|
[objectId]: {
|
||||||
id: objectId1,
|
id: objectId,
|
||||||
typeId: objTypeId,
|
typeId: objTypeId,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [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],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -75,13 +67,10 @@ export class WorldState {
|
||||||
playing: false,
|
playing: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
state.worldEditor.resetSelectedObject();
|
|
||||||
this.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public load() {
|
public load() {
|
||||||
this.data = WorldFactory.load() ?? WorldFactory.create();
|
this.data = WorldFactory.load() ?? WorldFactory.create();
|
||||||
state.worldEditor.resetSelectedObject();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public save(): void {
|
public save(): void {
|
||||||
|
|
@ -125,7 +114,8 @@ export class WorldState {
|
||||||
scene: clone(this.data.initialScene),
|
scene: clone(this.data.initialScene),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.worldEditor.resetSelectedObject();
|
|
||||||
|
state.worldEditor.setSelectedObjectId(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public pause(): void {
|
public pause(): void {
|
||||||
|
|
|
||||||
|
|
@ -3,29 +3,12 @@ import type { WorldState } from "./world";
|
||||||
import type { ObjectInstance, Scene, World } from "../types";
|
import type { ObjectInstance, Scene, World } from "../types";
|
||||||
import { createObjectInstance } from "../utils/object";
|
import { createObjectInstance } from "../utils/object";
|
||||||
import { randomId } from "../utils";
|
import { randomId } from "../utils";
|
||||||
import type { Pos3, R3, V3 } from "../types/3d";
|
import type { Pos3, 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 {
|
export class WorldEditorState {
|
||||||
private readonly world: WorldState;
|
private readonly world: WorldState;
|
||||||
|
|
||||||
public selectedObjectId: string | undefined;
|
public selectedObjectId: string | undefined;
|
||||||
public selectedObjectMode: SelectionEditMode | undefined;
|
|
||||||
public isDragging: boolean = false;
|
public isDragging: boolean = false;
|
||||||
|
|
||||||
constructor(world: WorldState) {
|
constructor(world: WorldState) {
|
||||||
|
|
@ -42,14 +25,8 @@ export class WorldEditorState {
|
||||||
this.world.save();
|
this.world.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSelectedObject(id: string, mode: SelectionEditMode): void {
|
public setSelectedObjectId(value: string | undefined): void {
|
||||||
this.selectedObjectId = id;
|
this.selectedObjectId = value;
|
||||||
this.selectedObjectMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public resetSelectedObject(): void {
|
|
||||||
this.selectedObjectId = undefined;
|
|
||||||
this.selectedObjectMode = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setIsDragging(value: boolean): void {
|
public setIsDragging(value: boolean): void {
|
||||||
|
|
@ -90,22 +67,13 @@ export class WorldEditorState {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setObjectTransform(
|
public setObjectPosition(id: string, pos: V3): void {
|
||||||
id: string,
|
|
||||||
position: V3,
|
|
||||||
rotation: R3,
|
|
||||||
scale: V3,
|
|
||||||
): void {
|
|
||||||
const obj = this.scene.objects[id];
|
const obj = this.scene.objects[id];
|
||||||
if (!obj)
|
if (!obj)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.mutateObject({
|
obj.position = pos;
|
||||||
...obj,
|
this.mutateObject(obj);
|
||||||
position,
|
|
||||||
rotation,
|
|
||||||
scale,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public addObjectCloneAtRandomPosition(typeId: string): ObjectInstance {
|
public addObjectCloneAtRandomPosition(typeId: string): ObjectInstance {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type { R3, V3 } from "./3d";
|
|
||||||
import type { Voxel } from "./voxel";
|
import type { Voxel } from "./voxel";
|
||||||
|
|
||||||
export type ObjectType = {
|
export type ObjectType = {
|
||||||
|
|
@ -10,7 +9,6 @@ export type ObjectType = {
|
||||||
export type ObjectInstance = {
|
export type ObjectInstance = {
|
||||||
id: string;
|
id: string;
|
||||||
typeId: string;
|
typeId: string;
|
||||||
position: V3;
|
position: [number, number, number];
|
||||||
rotation: R3;
|
rotation: [number, number, number];
|
||||||
scale: V3;
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue