layered geometries
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
export abstract class ViewItem {
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { Pos3 } from "../../types";
|
||||
import { state } from "../rootState";
|
||||
import { ViewItem } from "./base";
|
||||
|
||||
export abstract class CameraViewItem extends ViewItem {
|
||||
public abstract get camera(): Pos3;
|
||||
public abstract set camera(value: Pos3);
|
||||
}
|
||||
|
||||
export class ControlledCameraViewItem extends CameraViewItem {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public get camera(): Pos3 {
|
||||
return state.worldEditor.camera;
|
||||
}
|
||||
|
||||
public set camera(value: Pos3) {
|
||||
state.worldEditor.setCamera(value);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class ThirdPersonCameraViewItem extends CameraViewItem {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './base';
|
||||
export * from './objectTypeGeometry';
|
||||
export * from './objectGeometry';
|
||||
export * from './camera';
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ObjectInstance, ObjectInstanceRigidBody } from "../../types";
|
||||
import { ViewItem } from "./base";
|
||||
import type { ObjectTypeGeometryViewItem } from './objectTypeGeometry';
|
||||
|
||||
export class ObjectGeometryViewItem extends ViewItem {
|
||||
public readonly object: ObjectInstance;
|
||||
public readonly objectTypeGeometry: ObjectTypeGeometryViewItem;
|
||||
|
||||
public rb: (() => ObjectInstanceRigidBody | null) | undefined;
|
||||
|
||||
constructor(object: ObjectInstance, objectTypeGeometry: ObjectTypeGeometryViewItem) {
|
||||
super();
|
||||
this.object = object;
|
||||
this.objectTypeGeometry = objectTypeGeometry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import * as THREE from 'three';
|
||||
import type { ObjectType, V3, Voxel, VoxelGroup } from "../../types";
|
||||
import { ViewItem } from "./base";
|
||||
import { action, makeObservable, observable, reaction, type IReactionDisposer } from 'mobx';
|
||||
import { getObjectVoxelGroups } from '../../utils/graphics/voxelGroup';
|
||||
import { buildVoxelGroupMesh } from '../../utils/graphics/voxelMesh';
|
||||
import { state } from '../rootState';
|
||||
import { buildObjectTrimesh } from '../../utils/graphics/mesh';
|
||||
import { computeBoundingBox } from '../../utils/runtime';
|
||||
|
||||
export class ObjectTypeGeometryViewItem extends ViewItem {
|
||||
public readonly objectType: ObjectType;
|
||||
|
||||
public voxelGroups: VoxelGroup[] | undefined;
|
||||
public colliderMesh: [Float32Array, Uint32Array] | undefined;
|
||||
public boundingBox: { min: V3; max: V3 } | undefined;
|
||||
public meshes: THREE.InstancedMesh[] | undefined;
|
||||
|
||||
private readonly _disposeVoxelReaction: IReactionDisposer;
|
||||
|
||||
constructor(objectType: ObjectType) {
|
||||
super();
|
||||
this.objectType = objectType;
|
||||
makeObservable(this, {
|
||||
voxelGroups: observable.ref,
|
||||
colliderMesh: observable.ref,
|
||||
boundingBox: observable.ref,
|
||||
meshes: observable.ref,
|
||||
updateRuntimeData: action,
|
||||
});
|
||||
this._disposeVoxelReaction = reaction(
|
||||
() => objectType.voxels.map(v => `${v.position}-${v.typeId}`).join('|'),
|
||||
() => this.updateRuntimeData(),
|
||||
);
|
||||
this.updateRuntimeData();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeVoxelReaction();
|
||||
}
|
||||
|
||||
public addVoxel(voxel: Voxel): boolean {
|
||||
const occupied = this.objectType.voxels.some(v =>
|
||||
v.position[0] === voxel.position[0] &&
|
||||
v.position[1] === voxel.position[1] &&
|
||||
v.position[2] === voxel.position[2]
|
||||
);
|
||||
if (!occupied) {
|
||||
this.objectType.voxels.push(voxel);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public removeVoxel(position: V3): Voxel | undefined {
|
||||
const idx = this.objectType.voxels.findIndex(v =>
|
||||
v.position[0] === position[0] &&
|
||||
v.position[1] === position[1] &&
|
||||
v.position[2] === position[2]
|
||||
);
|
||||
if (idx !== -1)
|
||||
return this.objectType.voxels.splice(idx, 1)[0];
|
||||
}
|
||||
|
||||
public updateRuntimeData(): void {
|
||||
this.voxelGroups = getObjectVoxelGroups(this.objectType, state.world.data.voxelTypes);
|
||||
this.meshes = [];
|
||||
for (const vg of this.voxelGroups) {
|
||||
const geo = buildVoxelGroupMesh(vg.positions);
|
||||
const mat = new THREE.MeshStandardMaterial({
|
||||
color: vg.color,
|
||||
opacity: vg.opacity,
|
||||
transparent: vg.opacity < 1,
|
||||
});
|
||||
const mesh = new THREE.InstancedMesh(geo, mat, 1000);
|
||||
this.meshes.push(mesh);
|
||||
}
|
||||
this.colliderMesh = buildObjectTrimesh(this.objectType, state.world.data.voxelTypes);
|
||||
this.boundingBox = computeBoundingBox(this.objectType);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +1,21 @@
|
||||
import * as THREE from 'three';
|
||||
import { action, makeAutoObservable, makeObservable, observable, reaction, type IReactionDisposer } from "mobx";
|
||||
import type { ObjectInstance, ObjectInstanceRigidBody, ObjectType, Scene, V3, Voxel, VoxelGroup } from "../types";
|
||||
import { state } from "./rootState";
|
||||
import { getObjectVoxelGroups } from "../utils/graphics/voxelGroup";
|
||||
import { buildVoxelGroupMesh } from "../utils/graphics/voxelMesh";
|
||||
import { buildObjectTrimesh } from '../utils/graphics/mesh';
|
||||
import { computeBoundingBox } from '../utils/runtime';
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import type { ObjectInstance, ObjectType } from "../types";
|
||||
import { MobxReconcilerMap } from '../utils/reconciler';
|
||||
|
||||
export abstract class ViewItem {
|
||||
}
|
||||
|
||||
export class ObjectTypeGeometryViewItem extends ViewItem {
|
||||
public readonly objectType: ObjectType;
|
||||
|
||||
public voxelGroups: VoxelGroup[] | undefined;
|
||||
public colliderMesh: [Float32Array, Uint32Array] | undefined;
|
||||
public boundingBox: { min: V3; max: V3 } | undefined;
|
||||
public meshes: THREE.InstancedMesh[] | undefined;
|
||||
|
||||
private readonly _disposeVoxelReaction: IReactionDisposer;
|
||||
|
||||
constructor(objectType: ObjectType) {
|
||||
super();
|
||||
this.objectType = objectType;
|
||||
makeObservable(this, {
|
||||
voxelGroups: observable.ref,
|
||||
colliderMesh: observable.ref,
|
||||
boundingBox: observable.ref,
|
||||
meshes: observable.ref,
|
||||
updateRuntimeData: action,
|
||||
});
|
||||
this._disposeVoxelReaction = reaction(
|
||||
() => objectType.voxels.map(v => `${v.position}-${v.typeId}`).join('|'),
|
||||
() => this.updateRuntimeData(),
|
||||
);
|
||||
this.updateRuntimeData();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeVoxelReaction();
|
||||
}
|
||||
|
||||
public addVoxel(voxel: Voxel): boolean {
|
||||
const occupied = this.objectType.voxels.some(v =>
|
||||
v.position[0] === voxel.position[0] &&
|
||||
v.position[1] === voxel.position[1] &&
|
||||
v.position[2] === voxel.position[2]
|
||||
);
|
||||
if (!occupied) {
|
||||
this.objectType.voxels.push(voxel);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public removeVoxel(position: V3): Voxel | undefined {
|
||||
const idx = this.objectType.voxels.findIndex(v =>
|
||||
v.position[0] === position[0] &&
|
||||
v.position[1] === position[1] &&
|
||||
v.position[2] === position[2]
|
||||
);
|
||||
if (idx !== -1)
|
||||
return this.objectType.voxels.splice(idx, 1)[0];
|
||||
}
|
||||
|
||||
public updateRuntimeData(): void {
|
||||
this.voxelGroups = getObjectVoxelGroups(this.objectType, state.world.data.voxelTypes);
|
||||
this.meshes = [];
|
||||
for (const vg of this.voxelGroups) {
|
||||
const geo = buildVoxelGroupMesh(vg.positions);
|
||||
const mat = new THREE.MeshStandardMaterial({
|
||||
color: vg.color,
|
||||
opacity: vg.opacity,
|
||||
transparent: vg.opacity < 1,
|
||||
});
|
||||
const mesh = new THREE.InstancedMesh(geo, mat, 1000);
|
||||
this.meshes.push(mesh);
|
||||
}
|
||||
this.colliderMesh = buildObjectTrimesh(this.objectType, state.world.data.voxelTypes);
|
||||
this.boundingBox = computeBoundingBox(this.objectType);
|
||||
}
|
||||
}
|
||||
|
||||
export class ObjectGeometryViewItem extends ViewItem {
|
||||
public readonly object: ObjectInstance;
|
||||
public readonly objectTypeGeometry: ObjectTypeGeometryViewItem;
|
||||
|
||||
public rb: (() => ObjectInstanceRigidBody | null) | undefined;
|
||||
|
||||
constructor(object: ObjectInstance, objectTypeGeometry: ObjectTypeGeometryViewItem) {
|
||||
super();
|
||||
this.object = object;
|
||||
this.objectTypeGeometry = objectTypeGeometry;
|
||||
}
|
||||
}
|
||||
import { ObjectGeometryViewItem, ObjectTypeGeometryViewItem, ControlledCameraViewItem, ViewItem } from './viewLayers';
|
||||
import type { WorldState } from "./worldState";
|
||||
|
||||
export class ViewState {
|
||||
public objectTypeGeometries: MobxReconcilerMap<ObjectType, ObjectTypeGeometryViewItem>;
|
||||
public objectGeometries: MobxReconcilerMap<ObjectInstance, ObjectGeometryViewItem>;
|
||||
public camera?: ControlledCameraViewItem;
|
||||
|
||||
constructor(world: { data: { objectTypes: Record<string, ObjectType>; initialScene: Scene } }) {
|
||||
constructor(world: WorldState) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
);
|
||||
|
||||
this.camera = new ControlledCameraViewItem();
|
||||
|
||||
this.objectTypeGeometries = new MobxReconcilerMap<ObjectType, ObjectTypeGeometryViewItem>({
|
||||
reaction: () => Object.values(world.data.objectTypes),
|
||||
keyOf: (ot) => ot.id,
|
||||
@@ -130,6 +42,7 @@ export class ViewState {
|
||||
return [
|
||||
...this.objectTypeGeometries,
|
||||
...this.objectGeometries,
|
||||
...[this.camera ? [this.camera] : []],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ export class WorldEditorState {
|
||||
}
|
||||
|
||||
public setCamera(value: Pos3): void {
|
||||
if (JSON.stringify(value) === JSON.stringify(this.world.data.editorCamera))
|
||||
return;
|
||||
this.world.data.editorCamera = value;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { autorun, makeAutoObservable, toJS } from "mobx";
|
||||
import { WorldFactory } from "../model/worldFactory";
|
||||
import { type ObjectInstance, type ObjectType, type World, type VoxelType, type RuntimeWorld } from "../types";
|
||||
import { type ObjectInstance, type ObjectType, type World, type VoxelType } from "../types";
|
||||
import { state } from "./rootState";
|
||||
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
||||
import { wolf } from "../model/objectPrefabs/wolf";
|
||||
@@ -8,7 +8,7 @@ import { terrainXZ } from "../model/objectPrefabs/terrain";
|
||||
import { populateRuntimeWorld } from "../utils/runtime";
|
||||
|
||||
export class WorldState {
|
||||
public data: RuntimeWorld = WorldFactory.create();
|
||||
public data: World = WorldFactory.create();
|
||||
|
||||
private _buildSave(): World {
|
||||
const d = this.data;
|
||||
|
||||
Reference in New Issue
Block a user