voxel groups for faster rendering

GL stats viewer
This commit is contained in:
azykov@mail.ru 2026-06-02 20:56:34 +03:00
parent 6fc89cd8c9
commit b3c7979f87
No known key found for this signature in database
3 changed files with 75 additions and 35 deletions

View File

@ -1,14 +1,37 @@
import { observer } from "mobx-react-lite";
import type { ObjectInstance } from "../types";
import type { ObjectInstance, ObjectType } from "../types";
import { useRef, type RefObject } from "react";
import type { Group } from "three";
import { TransformControls, useHelper } from "@react-three/drei";
import { Instance, Instances, TransformControls, useHelper } from "@react-three/drei";
import { BoxHelper } from "three";
import type { ThreeEvent } from "@react-three/fiber";
import { state } from "../state";
import { nextSelectionEditMode } from "../state/worldEditorState";
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 = {
object: ObjectInstance;
}
@ -66,26 +89,16 @@ export const ObjectView = observer(function ({ object }: ObjectViewProps) {
onClick={handleClick}
>
{
objectType.voxels.map((v, idx) => {
const vt = state.world.getVoxelTypeById(v.typeId);
const color = (v.color ?? vt?.color) ?? 'white';
const opacity = (v.opacity ?? vt?.opacity) ?? 1;
const pos = v.position;
return (
<mesh
key={idx}
name={`voxel ${idx} [${v.position}]`}
position={[pos[0] + 0.5, pos[1] + 0.5, pos[2] + 0.5]}
>
voxelGroups(objectType).map((vg) => (
<Instances key={vg.id}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial
color={color}
opacity={opacity}
transparent={opacity < 1}
/>
</mesh>
);
})
<meshStandardMaterial color={vg.color} opacity={vg.opacity} transparent={vg.opacity < 1} />
{
vg.positions
.map((pos, i) => <Instance key={i} position={pos} />)
}
</Instances>
))
}
</group>
</>);

View File

@ -1,5 +1,22 @@
import { Canvas } from '@react-three/fiber';
import { Canvas, useFrame, useThree } from '@react-three/fiber';
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 { state } from '../state';
import { GameView } from './GameView';
@ -11,6 +28,7 @@ const IconPlay = () => <svg viewBox="0 0 14 14"><polygon points="3,1 13,7 3,13"
export const ThreeView = observer(function () {
const isGame = state.isGamePlaying;
const infoRef = useRef<HTMLDivElement>(null);
return (
<div style={{ position: 'relative', width: '800px', height: '600px', border: '1px solid #333', borderRadius: '8px', overflow: 'hidden' }}>
<Canvas
@ -18,8 +36,15 @@ export const ThreeView = observer(function () {
onPointerMissed={() => state.worldEditor.resetSelectedObject()}
>
<Stats />
<RenderInfoUpdater domRef={infoRef} />
{isGame ? <GameView /> : <SceneEditorView />}
</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 }}>
{
state.game

View File

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