custom voxel mesh builder

object visibility
This commit is contained in:
azykov@mail.ru
2026-06-17 10:54:13 +03:00
parent 7ac154dd58
commit 31c77b0a72
27 changed files with 416 additions and 266 deletions
+16 -12
View File
@@ -1,6 +1,6 @@
import { autorun, makeAutoObservable, toJS } from "mobx";
import type { WorldState } from "./worldState";
import type { Game, GameObjectInstance, Pos3, RuntimeGameScene, ScriptContext, ScriptCoroutineContext } from "../types";
import type { Game, GameObjectInstance, GameScene, Pos3, RuntimeGameScene, ScriptContext, ScriptCoroutineContext } from "../types";
import type { CameraProps } from "@react-three/fiber";
import type { ScriptGameEventBus } from "../scripts/eventBus";
import { GameFactory } from "../model/gameFactory";
@@ -18,7 +18,7 @@ export class GameState {
private coroutines = new ScriptCoroutines();
private eventBus: ScriptGameEventBus;
private _buildGameObjects() {
private sceneObjectsAsSaveables(): GameScene['objects'] {
return Object.fromEntries(
Object.entries(this.scene.objects).map(([id, obj]) => [id, {
id: obj.id,
@@ -31,17 +31,25 @@ export class GameState {
version: obj.version,
linearVelocity: toJS(obj.linearVelocity),
angularVelocity: toJS(obj.angularVelocity),
visible: toJS(obj.visible),
} satisfies GameObjectInstance])
) as unknown as RuntimeGameScene['objects'];
}
public get asSaveableGame(): Game<GameScene> {
return {
paused: this.isPaused,
time: this.time,
scene: {
playerObjectId: this.scene.playerObjectId,
objects: this.sceneObjectsAsSaveables(),
},
};
}
private startAutoSave() {
return autorun(() => {
GameFactory.save({
paused: this.isPaused,
time: this.time,
scene: { playerObjectId: this.scene.playerObjectId, objects: this._buildGameObjects() },
});
this.save();
}, { delay: 5000 });
}
@@ -118,11 +126,7 @@ export class GameState {
public save(): void {
console.log('Saving game...');
GameFactory.save({
paused: this.isPaused,
time: this.time,
scene: { playerObjectId: this.scene.playerObjectId, objects: this._buildGameObjects() },
});
GameFactory.save(this.asSaveableGame);
}
public resume(): void {
+8 -1
View File
@@ -1,7 +1,7 @@
import { makeAutoObservable } from "mobx";
import { state } from "./rootState";
import type { ReactNode } from "react";
import { IconRun, IconTrash, IconPlus, IconCubePlus, IconCubeOff, IconEdit } from '@tabler/icons-react';
import { IconRun, IconTrash, IconPlus, IconCubePlus, IconCubeOff, IconEdit, IconEye, IconEyeClosed } from '@tabler/icons-react';
export type MenuNodeAction = {
id: string;
@@ -53,6 +53,13 @@ export class MenuState {
});
}
actions.push({
id: 'toggle-object-instance-visibility',
content: o.visible ? <IconEye size="1em" /> : <IconEyeClosed size="1em" />,
tooltip: 'Toggle visibility',
onClick: () => { editor.setObjectVisible(o.id, !o.visible); },
});
return {
id: `o-${o.id}`,
title: <>
+17 -12
View File
@@ -147,6 +147,11 @@ export class WorldEditorState {
delete (this.scene.objects[objectId]);
}
public setObjectVisible(objectId: string, value: boolean) {
if (this.scene.objects[objectId])
this.scene.objects[objectId].visible = value;
}
public addObjectType(): ObjectType {
const objectType: ObjectType = {
id: randomId(),
@@ -165,39 +170,39 @@ export class WorldEditorState {
public addVoxelToObjectType(typeId: string, voxel: Voxel): void {
const objectType = this.world.getObjectTypeById(typeId);
if (!objectType)
return;
if (!objectType)
return;
const occupied = objectType.voxels.some(v =>
v.position[0] === voxel.position[0] &&
v.position[1] === voxel.position[1] &&
v.position[2] === voxel.position[2]
);
if (!occupied)
objectType.voxels.push(voxel);
if (!occupied)
objectType.voxels.push(voxel);
}
public removeVoxelFromObjectType(typeId: string, position: V3): void {
const objectType = this.world.getObjectTypeById(typeId);
if (!objectType)
return;
if (!objectType)
return;
const idx = objectType.voxels.findIndex(v =>
v.position[0] === position[0] &&
v.position[1] === position[1] &&
v.position[2] === position[2]
);
if (idx !== -1)
objectType.voxels.splice(idx, 1);
if (idx !== -1)
objectType.voxels.splice(idx, 1);
}
public refreshObjectTypeCaches(typeId: string): void {
const objectType = this.world.getObjectTypeById(typeId);
if (!objectType)
return;
if (!objectType)
return;
const world = this.world.data;
const now = Date.now();
for (const obj of Object.values(this.scene.objects)) {
if (obj.typeId !== typeId)
continue;
if (obj.typeId !== typeId)
continue;
obj.cache.voxelGroups = getObjectVoxelGroups(objectType, world.voxelTypes);
obj.cache.colliderMesh = buildObjectTrimesh(objectType, world.voxelTypes);
obj.cache.boundingBox = computeBoundingBox(objectType);