From b3c7979f87027e457c31304543eea8b6929a61d9 Mon Sep 17 00:00:00 2001 From: "azykov@mail.ru" Date: Tue, 2 Jun 2026 20:56:34 +0300 Subject: [PATCH] voxel groups for faster rendering GL stats viewer --- src/components/ObjectView.tsx | 57 +++++++++++++++++++++-------------- src/components/ThreeView.tsx | 27 ++++++++++++++++- src/state/worldState.ts | 26 ++++++++-------- 3 files changed, 75 insertions(+), 35 deletions(-) diff --git a/src/components/ObjectView.tsx b/src/components/ObjectView.tsx index d601526..845d713 100644 --- a/src/components/ObjectView.tsx +++ b/src/components/ObjectView.tsx @@ -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(); + 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 ( - - - - - ); - }) + voxelGroups(objectType).map((vg) => ( + + + + { + vg.positions + .map((pos, i) => ) + } + + )) } ); diff --git a/src/components/ThreeView.tsx b/src/components/ThreeView.tsx index e196090..de0293e 100644 --- a/src/components/ThreeView.tsx +++ b/src/components/ThreeView.tsx @@ -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 }) { + 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 = () => (null); return (
state.worldEditor.resetSelectedObject()} > + {isGame ? : } +
{ state.game diff --git a/src/state/worldState.ts b/src/state/worldState.ts index 16c3230..7995dc8 100644 --- a/src/state/worldState.ts +++ b/src/state/worldState.ts @@ -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 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(); }