Compare commits

..

No commits in common. "b3c7979f87027e457c31304543eea8b6929a61d9" and "8bc56a433db6915727247c3b7a44c4968eee1e04" have entirely different histories.

5 changed files with 38 additions and 90 deletions

View File

@ -3,14 +3,9 @@ import type { Character } from "../types";
export const CharacterView = observer(function ({ character }: { character: Character }) { export const CharacterView = observer(function ({ character }: { character: Character }) {
const pos = character.position; return <mesh position={character.position} rotation={character.look}>
return <mesh
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
rotation={character.look}
>
<boxGeometry args={[0.8, 0.8, 0.8]} /> <boxGeometry args={[0.8, 0.8, 0.8]} />
<meshStandardMaterial color="yellow" /> <meshStandardMaterial color="yellow" />
</mesh > </mesh>
}); });

View File

@ -1,37 +1,14 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import type { ObjectInstance, ObjectType } from "../types"; import type { ObjectInstance } from "../types";
import { useRef, type RefObject } from "react"; import { useRef, type RefObject } from "react";
import type { Group } from "three"; import type { Group } from "three";
import { Instance, Instances, TransformControls, useHelper } from "@react-three/drei"; import { TransformControls, useHelper } from "@react-three/drei";
import { BoxHelper } from "three"; import { BoxHelper } from "three";
import type { ThreeEvent } from "@react-three/fiber"; import type { ThreeEvent } from "@react-three/fiber";
import { state } from "../state"; import { state } from "../state";
import { nextSelectionEditMode } from "../state/worldEditorState"; import { nextSelectionEditMode } from "../state/worldEditorState";
import type { R3 } from "../types/3d"; import type { R3 } from "../types/3d";
type VoxelGroup = {
id: string;
color: string;
opacity: number;
positions: [number, number, number][];
};
function voxelGroups(objectType: ObjectType): VoxelGroup[] {
const map = new Map<string, VoxelGroup>();
for (const idx in objectType.voxels) {
const v = objectType.voxels[idx];
const vt = state.world.getVoxelTypeById(v.typeId);
const color = (v.color ?? vt?.color) ?? 'white';
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
const key = `${color}-${opacity}`;
if (!map.has(key))
map.set(key, { id: key, color, opacity, positions: [] });
const p = v.position;
map.get(key)!.positions.push([p[0] + 0.5, p[1] + 0.5, p[2] + 0.5]);
}
return [...map.values()];
}
type ObjectViewProps = { type ObjectViewProps = {
object: ObjectInstance; object: ObjectInstance;
} }
@ -89,16 +66,25 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
onClick={handleClick} onClick={handleClick}
> >
{ {
voxelGroups(objectType).map((vg) => ( objectType.voxels.map((v, idx) => {
<Instances key={vg.id}> const vt = state.world.getVoxelTypeById(v.typeId);
const color = (v.color ?? vt?.color) ?? 'white';
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
return (
<mesh
key={idx}
name={`voxel ${idx} [${v.position}]`}
position={v.position}
>
<boxGeometry args={[1, 1, 1]} /> <boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={vg.color} opacity={vg.opacity} transparent={vg.opacity < 1} /> <meshStandardMaterial
{ color={color}
vg.positions opacity={opacity}
.map((pos, i) => <Instance key={i} position={pos} />) transparent={opacity < 1}
} />
</Instances> </mesh>
)) );
})
} }
</group> </group>
</>); </>);

View File

@ -1,12 +1,12 @@
import { useLayoutEffect, useRef } from 'react'; import { useLayoutEffect, useRef } from 'react';
import type React from 'react'; import type React from 'react';
import { useThree } from '@react-three/fiber'; import { useThree } from '@react-three/fiber';
import { Grid, 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 { SceneView } from './SceneView'; import { SceneView } from './SceneView';
import type { Pos3 } from '../types/3d'; import type { Pos3 } from '../types/3d';
import { type OrthographicCamera, type PerspectiveCamera } from 'three'; import type { OrthographicCamera, PerspectiveCamera } from 'three';
const CameraSync = observer(function ({ camera }: { camera: Pos3 }) { const CameraSync = observer(function ({ camera }: { camera: Pos3 }) {
const { camera: threeCamera } = useThree(); const { camera: threeCamera } = useThree();
@ -62,12 +62,6 @@ export const SceneEditorView = observer(function () {
enableDamping={false} enableDamping={false}
/> />
<CameraSync camera={state.worldEditor.camera} /> <CameraSync camera={state.worldEditor.camera} />
<Grid
sectionThickness={0.5}
cellThickness={0}
sectionColor="white"
infiniteGrid
/>
<SceneView scene={state.worldEditor.scene} /> <SceneView scene={state.worldEditor.scene} />
</>); </>);
}); });

View File

@ -1,22 +1,5 @@
import { Canvas, useFrame, useThree } from '@react-three/fiber'; import { Canvas } from '@react-three/fiber';
import { Stats } from '@react-three/drei'; import { Stats } from '@react-three/drei';
import { useRef } from 'react';
function RenderInfoUpdater({ domRef }: { domRef: React.RefObject<HTMLDivElement | null> }) {
const { gl } = useThree();
useFrame(() => {
if (!domRef.current)
return;
const { calls, triangles } = gl.info.render;
const shaders = gl.info.programs?.length ?? 0;
const { geometries, textures } = gl.info.memory;
domRef.current.textContent = `draw: ${calls} | tri: ${triangles} | shaders: ${shaders} | geometries: ${geometries} | textures: ${textures}`;
});
return null;
}
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { state } from '../state'; import { state } from '../state';
import { GameView } from './GameView'; import { GameView } from './GameView';
@ -28,7 +11,6 @@ const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13"
export const ThreeView = observer(function () { export const ThreeView = observer(function () {
const isGame = state.isGamePlaying; const isGame = state.isGamePlaying;
const infoRef = useRef<HTMLDivElement>(null);
return ( return (
<div style={{ position: 'relative', width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}> <div style={{ position: 'relative', width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
<Canvas <Canvas
@ -36,15 +18,8 @@ export const ThreeView = observer(function () {
onPointerMissed={() => state.worldEditor.resetSelectedObject()} onPointerMissed={() => state.worldEditor.resetSelectedObject()}
> >
<Stats /> <Stats />
<RenderInfoUpdater domRef={infoRef} />
{isGame ? <GameView /> : <SceneEditorView />} {isGame ? <GameView /> : <SceneEditorView />}
</Canvas> </Canvas>
<div ref={infoRef} style={{
position: 'absolute', bottom: 8, left: 8,
color: 'white', fontSize: 11, fontFamily: 'monospace',
background: 'rgba(0,0,0,0.5)', padding: '2px 6px', borderRadius: 3,
pointerEvents: 'none',
}} />
<div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}> <div style={{ position: 'absolute', top: 8, right: 8, display: 'flex', gap: 4 }}>
{ {
state.game state.game

View File

@ -1,6 +1,6 @@
import { makeAutoObservable, reaction, toJS } from "mobx"; import { makeAutoObservable, reaction, toJS } from "mobx";
import { WorldFactory } from "../model/worldFactory"; import { WorldFactory } from "../model/worldFactory";
import type { ObjectInstance, ObjectType, World } from "../types"; import type { ObjectType, World } from "../types";
import type { VoxelType } from "../types/voxel"; import type { VoxelType } from "../types/voxel";
import { state } from "./rootState"; import { state } from "./rootState";
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes"; import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
@ -39,17 +39,7 @@ export class WorldState {
public loadMock() { public loadMock() {
console.log('Mocking world...'); console.log('Mocking world...');
const objects = Array(3).fill(0) const objectId1 = 'object1';
.map((_, idx) => ({
id: `obj${idx}`,
typeId: 'wolf',
position: [idx * 10 - 10, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
}));
const objectMap = Object.fromEntries(
objects.map((obj) => [obj.id, obj]),
) as Record<string, ObjectInstance>
this.data = { this.data = {
objectTypes: { objectTypes: {
@ -69,7 +59,16 @@ export class WorldState {
position: [0, 0, 0], position: [0, 0, 0],
look: [0, 0, 0], look: [0, 0, 0],
}, },
objects: objectMap, objects: {
[objectId1]: {
id: objectId1,
typeId: 'wolf',
position: [0, 0, 0],
rotation: [0, 0, 0],
scale: [1, 1, 1],
},
},
}, },
gameRules: { gameRules: {
gravity: true, gravity: true,
@ -78,7 +77,6 @@ export class WorldState {
playing: false, playing: false,
}, },
}; };
console.log(objects);
state.worldEditor.resetSelectedObject(); state.worldEditor.resetSelectedObject();
} }