world structure: arrays to records

object move with mouse
This commit is contained in:
azykov@mail.ru
2026-06-02 13:14:23 +03:00
parent f3033d1d0d
commit e37cb08698
9 changed files with 134 additions and 54 deletions
+39 -17
View File
@@ -1,8 +1,8 @@
import { observer } from "mobx-react-lite";
import type { ObjectInstance } from "../types";
import { useRef } from "react";
import type { Mesh } from "three";
import { Edges } from "@react-three/drei";
import { useEffect, useRef, type RefObject } from "react";
import type { Group, Mesh } from "three";
import { Edges, TransformControls } from "@react-three/drei";
import { state } from "../state";
type ObjectViewProps = {
@@ -10,39 +10,61 @@ type ObjectViewProps = {
}
export const ObjectView = observer(function ({ object }: ObjectViewProps) {
const meshRef = useRef<Mesh>(null);
const groupRef = useRef<Group>(null);
const controlsRef = useRef<any>(null);
const objectType = state.world.getObjectTypeById(object.typeId);
if (!objectType)
return null;
const isSelected = state.worldEditor.isEnabled &&
const isSelected = state.worldEditor.isEnabled &&
state.worldEditor.selectedObjectId === object.id;
useEffect(() => {
if (!isSelected)
return;
const controls = controlsRef.current;
if (!controls)
return;
const onDragChanged = (e: { value: boolean }) => state.worldEditor.setIsDragging(e.value);
controls.addEventListener('dragging-changed', onDragChanged);
return () => {
controls.removeEventListener('dragging-changed', onDragChanged);
state.worldEditor.setIsDragging(false);
};
}, [isSelected]);
const handleClick = (e: { stopPropagation: () => void }) => {
if (!state.worldEditor.isEnabled)
return;
e.stopPropagation();
state.worldEditor.setSelectedObjectId(object.id);
};
const handleObjectChange = () => {
const group = groupRef.current;
if (!group)
return;
const { x, y, z } = group.position;
state.worldEditor.setObjectPosition(object.id, [x, y, z]);
};
return (<>
<group
position={object.position}
rotation={object.rotation}
onClick={handleClick}
>
{(isSelected && groupRef.current) && <TransformControls object={groupRef as RefObject<Group>} onChange={handleObjectChange} mode="translate" />}
<group ref={groupRef} position={object.position} rotation={object.rotation} onClick={handleClick}>
{
objectType.voxels.map((v, idx) => {
const vt = state.world.getVoxelTypeById(v.typeId);
return <mesh key={idx} ref={meshRef} position={v.position}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={(v.color ?? vt?.color) ?? 'white'} opacity={(v.opacity ?? vt?.opacity) ?? 1} />
{isSelected && <Edges color="white" lineWidth={3} stencilWrite={false} />}
</mesh>
return (
<mesh key={idx} position={v.position}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={(v.color ?? vt?.color) ?? 'white'} opacity={(v.opacity ?? vt?.opacity) ?? 1} />
{isSelected && <Edges color="white" lineWidth={3} stencilWrite={false} />}
</mesh>
);
})
}
</group>
</>);
});
+4 -1
View File
@@ -6,7 +6,10 @@ import { WorldView } from './WorldView';
export const ThreeView = observer(function () {
return (
<div style={{ width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
<Canvas camera={state.world.character.camera}>
<Canvas
camera={state.world.character.camera}
onPointerMissed={() => state.worldEditor.setSelectedObjectId(undefined)}
>
<WorldView />
</Canvas>
</div>
+8 -2
View File
@@ -5,11 +5,16 @@ import type { RunningGameState } from "../types";
export const Toolbar = observer(function () {
function handleCloneTest1ObjectClick(): void {
function handleCloneTest1Object(): void {
state.worldEditor.addObjectCloneAtRandomPosition('test1');
}
function handleLoadMockWorld(): void {
state.world.loadMock();
}
return <div className="toolbar">
{state.worldEditor.isEnabled && <div>EDITOR MODE</div>}
{state.world.isPlaying
? <>
<button onClick={() => state.world.stop()}>Stop</button>
@@ -21,6 +26,7 @@ export const Toolbar = observer(function () {
</>
: <button onClick={() => state.world.play()}>Play</button>
}
<button onClick={handleCloneTest1ObjectClick}>Clone test1</button>
<button onClick={handleLoadMockWorld}>Load mock world</button>
<button onClick={handleCloneTest1Object}>Clone test1</button>
</div>
});
+3 -4
View File
@@ -11,8 +11,6 @@ export const WorldView = observer(function () {
const world = state.world;
const controlsRef = useRef<React.ComponentRef<typeof OrbitControls>>(null);
console.log(world.currentScene.objects);
useFrame((_, delta) => {
world.tick(delta);
});
@@ -31,13 +29,14 @@ export const WorldView = observer(function () {
return (<>
<OrbitControls
ref={controlsRef}
enabled={!world.isPlaying}
enabled={!world.isPlaying && !state.worldEditor.isDragging}
onEnd={handleEnd}
makeDefault
enableDamping={false}
/>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} intensity={1} />
{world.currentScene.objects.map((obj) => <ObjectView key={obj.id} object={obj} />)}
{Object.values(world.currentScene.objects).map((obj) => <ObjectView key={obj.id} object={obj} />)}
<CharacterView />
</>)
});