Compare commits
3 Commits
42e6d17167
...
e520eb07c5
| Author | SHA1 | Date |
|---|---|---|
|
|
e520eb07c5 | |
|
|
765412f9a9 | |
|
|
4d8de50d58 |
|
|
@ -1,8 +1,9 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import type { ObjectInstance } from "../types";
|
import type { ObjectInstance } from "../types";
|
||||||
import { useEffect, useRef, type RefObject } from "react";
|
import { useEffect, useRef, type RefObject } from "react";
|
||||||
import type { Group, Mesh } from "three";
|
import type { Group } from "three";
|
||||||
import { Edges, TransformControls } from "@react-three/drei";
|
import { Edges, TransformControls } from "@react-three/drei";
|
||||||
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
import { state } from "../state";
|
import { state } from "../state";
|
||||||
import { nextSelectionEditMode } from "../state/worldEditor";
|
import { nextSelectionEditMode } from "../state/worldEditor";
|
||||||
import type { R3 } from "../types/3d";
|
import type { R3 } from "../types/3d";
|
||||||
|
|
@ -38,9 +39,11 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
|
||||||
};
|
};
|
||||||
}, [isSelected]);
|
}, [isSelected]);
|
||||||
|
|
||||||
const handleClick = (e: { stopPropagation: () => void }) => {
|
const handleClick = (e: ThreeEvent<MouseEvent>) => {
|
||||||
if (!state.worldEditor.isEnabled)
|
if (!state.worldEditor.isEnabled)
|
||||||
return;
|
return;
|
||||||
|
if (e.delta > 5)
|
||||||
|
return;
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(selectionMode));
|
state.worldEditor.setSelectedObject(object.id, nextSelectionEditMode(selectionMode));
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import type { Scene } from "../types";
|
||||||
|
import { CharacterView } from "./CharacterView";
|
||||||
|
import { ObjectView } from "./ObjectView";
|
||||||
|
|
||||||
|
type SceneViewProps = {
|
||||||
|
scene: Scene,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneView = observer(function ({ scene }: SceneViewProps) {
|
||||||
|
return (<>
|
||||||
|
<ambientLight intensity={0.5} />
|
||||||
|
<directionalLight position={[5, 5, 5]} intensity={1} />
|
||||||
|
{Object.values(scene.objects).map((obj) =>
|
||||||
|
<ObjectView key={obj.id} object={obj} />)}
|
||||||
|
<CharacterView />
|
||||||
|
</>);
|
||||||
|
});
|
||||||
|
|
@ -1,11 +1,23 @@
|
||||||
import { useRef } from 'react';
|
import { useLayoutEffect, useRef } from 'react';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { OrbitControls } from '@react-three/drei';
|
import { OrbitControls } from '@react-three/drei';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { state } from '../state';
|
import { state } from '../state';
|
||||||
import { ObjectView } from './ObjectView';
|
import { SceneView } from './SceneView';
|
||||||
import { CharacterView } from './CharacterView';
|
|
||||||
|
const CameraSync = observer(function () {
|
||||||
|
const { camera } = useThree();
|
||||||
|
const cam = state.world.currentCamera; // MobX tracks this; re-renders on change
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
camera.position.set(cam.position[0], cam.position[1], cam.position[2]);
|
||||||
|
camera.rotation.set(cam.look[0], cam.look[1], cam.look[2]);
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
export const WorldView = observer(function () {
|
export const WorldView = observer(function () {
|
||||||
const world = state.world;
|
const world = state.world;
|
||||||
|
|
@ -34,9 +46,7 @@ export const WorldView = observer(function () {
|
||||||
makeDefault
|
makeDefault
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
/>
|
/>
|
||||||
<ambientLight intensity={0.5} />
|
<CameraSync />
|
||||||
<directionalLight position={[5, 5, 5]} intensity={1} />
|
<SceneView scene={state.world.currentScene} />
|
||||||
{Object.values(world.currentScene.objects).map((obj) => <ObjectView key={obj.id} object={obj} />)}
|
|
||||||
<CharacterView />
|
|
||||||
</>)
|
</>)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { Voxel } from "../../types/voxel";
|
||||||
|
|
||||||
|
const G = '#808080'; // gray fur
|
||||||
|
const L = '#d0c8b8'; // light beige-gray (snout / face)
|
||||||
|
const D = '#484040'; // dark gray (outer ear)
|
||||||
|
const E = '#e8b820'; // amber (eyes)
|
||||||
|
const N = '#181414'; // near-black (nose, pupils)
|
||||||
|
const P = '#c06858'; // pink (inner ear)
|
||||||
|
|
||||||
|
function v(x: number, y: number, z: number, color: string): Voxel {
|
||||||
|
return { typeId: 'stone', position: [x, y, z], color };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wolf faces +Z (nose at Z=6). Head spans 7×7×7.
|
||||||
|
export const wolf: Voxel[] = [
|
||||||
|
// ── Back of skull Z=0 ───────────────────────────────────────────
|
||||||
|
v(2,3,0,G), v(3,3,0,G), v(4,3,0,G),
|
||||||
|
v(2,4,0,G), v(3,4,0,G), v(4,4,0,G),
|
||||||
|
v(2,5,0,G), v(3,5,0,G), v(4,5,0,G),
|
||||||
|
|
||||||
|
// ── Skull Z=1 ───────────────────────────────────────────────────
|
||||||
|
v(1,3,1,G), v(2,3,1,G), v(3,3,1,G), v(4,3,1,G), v(5,3,1,G),
|
||||||
|
v(1,4,1,G), v(2,4,1,G), v(3,4,1,G), v(4,4,1,G), v(5,4,1,G),
|
||||||
|
v(1,5,1,G), v(2,5,1,G), v(3,5,1,G), v(4,5,1,G), v(5,5,1,G),
|
||||||
|
v(2,6,1,G), v(3,6,1,G), v(4,6,1,G),
|
||||||
|
|
||||||
|
// ── Skull + ear bases Z=2 ───────────────────────────────────────
|
||||||
|
v(1,3,2,G), v(2,3,2,G), v(3,3,2,G), v(4,3,2,G), v(5,3,2,G),
|
||||||
|
v(1,4,2,G), v(2,4,2,G), v(3,4,2,G), v(4,4,2,G), v(5,4,2,G),
|
||||||
|
v(0,5,2,G), v(1,5,2,G), v(2,5,2,G), v(3,5,2,G), v(4,5,2,G), v(5,5,2,G), v(6,5,2,G),
|
||||||
|
v(1,6,2,G), v(2,6,2,G), v(3,6,2,G), v(4,6,2,G), v(5,6,2,G),
|
||||||
|
v(0,6,2,D), v(6,6,2,D),
|
||||||
|
|
||||||
|
// ── Face + ears Z=3 ─────────────────────────────────────────────
|
||||||
|
v(1,3,3,L), v(2,3,3,L), v(3,3,3,L), v(4,3,3,L), v(5,3,3,L),
|
||||||
|
v(1,4,3,G), v(2,4,3,G), v(3,4,3,G), v(4,4,3,G), v(5,4,3,G),
|
||||||
|
// ears: dark outer, pink inner
|
||||||
|
v(0,5,3,D), v(1,5,3,P), v(2,5,3,G), v(3,5,3,G), v(4,5,3,G), v(5,5,3,P), v(6,5,3,D),
|
||||||
|
v(0,6,3,D), v(1,6,3,P), v(2,6,3,G), v(3,6,3,G), v(4,6,3,G), v(5,6,3,P), v(6,6,3,D),
|
||||||
|
// snout start
|
||||||
|
v(2,1,3,L), v(3,1,3,L), v(4,1,3,L),
|
||||||
|
v(2,2,3,L), v(3,2,3,L), v(4,2,3,L),
|
||||||
|
|
||||||
|
// ── Face + amber eyes + snout Z=4 ───────────────────────────────
|
||||||
|
v(1,3,4,L), v(2,3,4,L), v(3,3,4,L), v(4,3,4,L), v(5,3,4,L),
|
||||||
|
v(1,4,4,G), v(2,4,4,E), v(3,4,4,G), v(4,4,4,E), v(5,4,4,G),
|
||||||
|
v(1,5,4,G), v(2,5,4,G), v(3,5,4,G), v(4,5,4,G), v(5,5,4,G),
|
||||||
|
v(2,1,4,L), v(3,1,4,L), v(4,1,4,L),
|
||||||
|
v(2,2,4,L), v(3,2,4,L), v(4,2,4,L),
|
||||||
|
|
||||||
|
// ── Front face + pupils + snout Z=5 ─────────────────────────────
|
||||||
|
v(1,3,5,L), v(2,3,5,L), v(3,3,5,L), v(4,3,5,L), v(5,3,5,L),
|
||||||
|
v(1,4,5,G), v(2,4,5,N), v(3,4,5,G), v(4,4,5,N), v(5,4,5,G),
|
||||||
|
v(1,5,5,G), v(2,5,5,G), v(3,5,5,G), v(4,5,5,G), v(5,5,5,G),
|
||||||
|
v(2,1,5,L), v(3,1,5,L), v(4,1,5,L),
|
||||||
|
v(2,2,5,L), v(3,2,5,L), v(4,2,5,L),
|
||||||
|
|
||||||
|
// ── Snout tip + nose Z=6 ────────────────────────────────────────
|
||||||
|
v(2,2,6,L), v(3,2,6,L), v(4,2,6,L),
|
||||||
|
v(2,1,6,L), v(3,1,6,L), v(4,1,6,L),
|
||||||
|
v(2,3,6,N), v(3,3,6,N), v(4,3,6,N), // nose
|
||||||
|
];
|
||||||
|
|
@ -7,6 +7,7 @@ import { clone } 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";
|
||||||
|
import { wolf } from "../model/objectPrefabs/wolf";
|
||||||
|
|
||||||
export class WorldState {
|
export class WorldState {
|
||||||
public data: World = WorldFactory.create();
|
public data: World = WorldFactory.create();
|
||||||
|
|
@ -23,22 +24,14 @@ export class WorldState {
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadMock() {
|
public loadMock() {
|
||||||
const objTypeId = 'test1';
|
|
||||||
const voxelTypeId = 'glass';
|
|
||||||
const objectId1 = 'object1';
|
const objectId1 = 'object1';
|
||||||
const objectId2 = 'object2';
|
|
||||||
|
|
||||||
this.data = {
|
this.data = {
|
||||||
objectTypes: {
|
objectTypes: {
|
||||||
[objTypeId]: {
|
wolf: {
|
||||||
id: objTypeId,
|
id: 'wolf',
|
||||||
name: 'Test Object',
|
name: 'Wolf',
|
||||||
voxels: [
|
voxels: wolf,
|
||||||
{
|
|
||||||
typeId: voxelTypeId,
|
|
||||||
position: [0, 0, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
voxelTypes: DEFAULT_VOXEL_TYPES,
|
voxelTypes: DEFAULT_VOXEL_TYPES,
|
||||||
|
|
@ -54,18 +47,12 @@ export class WorldState {
|
||||||
objects: {
|
objects: {
|
||||||
[objectId1]: {
|
[objectId1]: {
|
||||||
id: objectId1,
|
id: objectId1,
|
||||||
typeId: objTypeId,
|
typeId: 'wolf',
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
scale: [1, 1, 1],
|
scale: [1, 1, 1],
|
||||||
},
|
},
|
||||||
[objectId2]: {
|
|
||||||
id: objectId2,
|
|
||||||
typeId: objTypeId,
|
|
||||||
position: [1, 0, 0],
|
|
||||||
rotation: [0, 0, 0],
|
|
||||||
scale: [1, 1, 1],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
gameRules: {
|
gameRules: {
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ export class WorldEditorState {
|
||||||
return this.world.data.initialScene;
|
return this.world.data.initialScene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get camera(): Pos3 {
|
||||||
|
return this.world.data.editorCamera;
|
||||||
|
}
|
||||||
|
|
||||||
public mutateWorld(mutation: Partial<World>): void {
|
public mutateWorld(mutation: Partial<World>): void {
|
||||||
this.world.data = {
|
this.world.data = {
|
||||||
...this.world.data,
|
...this.world.data,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue