object selection
This commit is contained in:
@@ -2,6 +2,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { ObjectGeometriesLayer } from "./ObjectGeometriesLayer";
|
import { ObjectGeometriesLayer } from "./ObjectGeometriesLayer";
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import { SceneEditorCameraLayer } from "./SceneEditor/SceneEditorGameraLayer";
|
import { SceneEditorCameraLayer } from "./SceneEditor/SceneEditorGameraLayer";
|
||||||
|
import { SceneEditorObjectSelectionLayer } from "./SceneEditor/SceneEditorObjectSelectionLayer";
|
||||||
import { LightsLayer } from "./LighsLayer";
|
import { LightsLayer } from "./LighsLayer";
|
||||||
import { StatsLayer } from './StatsLayer';
|
import { StatsLayer } from './StatsLayer';
|
||||||
|
|
||||||
@@ -12,7 +13,9 @@ export const LayeredThreeView = observer(function () {
|
|||||||
<LightsLayer />
|
<LightsLayer />
|
||||||
{/* <Perf position="bottom-right" /> */}
|
{/* <Perf position="bottom-right" /> */}
|
||||||
<SceneEditorCameraLayer />
|
<SceneEditorCameraLayer />
|
||||||
<ObjectGeometriesLayer />
|
<ObjectGeometriesLayer>
|
||||||
|
<SceneEditorObjectSelectionLayer />
|
||||||
|
</ObjectGeometriesLayer>
|
||||||
<StatsLayer />
|
<StatsLayer />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</>;
|
</>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { createContext, useContext, useEffect, useRef } from "react";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import { ObjectGeometryList } from "../../rendering/ObjectGeometryList";
|
import { ObjectGeometryList } from "../../rendering/ObjectGeometryList";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@@ -6,22 +6,32 @@ import { state } from "../../state";
|
|||||||
import { useLayerFrame } from "../../utils/hooks/useLayerFrame";
|
import { useLayerFrame } from "../../utils/hooks/useLayerFrame";
|
||||||
import { measuredRenderTime } from "../../utils/layerTimings";
|
import { measuredRenderTime } from "../../utils/layerTimings";
|
||||||
|
|
||||||
export const ObjectGeometriesLayer = observer(measuredRenderTime("ObjectGeometries", function () {
|
export const ObjectGeometryListContext = createContext<React.RefObject<ObjectGeometryList | null>>(
|
||||||
const geometries = state.view.objectGeometries;
|
{ current: null },
|
||||||
|
);
|
||||||
|
|
||||||
|
export function useObjectGeometryList() {
|
||||||
|
return useContext(ObjectGeometryListContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ObjectGeometriesLayer = observer(measuredRenderTime("ObjectGeometries", function ({ children }: { children?: React.ReactNode }) {
|
||||||
|
const typeGeometries = state.view.objectTypeGeometries;
|
||||||
|
|
||||||
const threeScene = useThree(s => s.scene);
|
const threeScene = useThree(s => s.scene);
|
||||||
const list = useRef<ObjectGeometryList | null>(null);
|
const list = useRef<ObjectGeometryList | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const l = new ObjectGeometryList(threeScene, geometries);
|
const l = new ObjectGeometryList(threeScene, typeGeometries);
|
||||||
list.current = l;
|
list.current = l;
|
||||||
return () => {
|
return () => {
|
||||||
l.dispose();
|
l.dispose();
|
||||||
list.current = null;
|
list.current = null;
|
||||||
};
|
};
|
||||||
}, [threeScene, geometries]);
|
}, [threeScene, typeGeometries]);
|
||||||
|
|
||||||
useLayerFrame("ObjectGeometries", () => list.current?.tick());
|
useLayerFrame("ObjectGeometries", () => list.current?.tick());
|
||||||
|
|
||||||
return null;
|
return <ObjectGeometryListContext.Provider value={list}>
|
||||||
|
{children}
|
||||||
|
</ObjectGeometryListContext.Provider>;
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import { state } from "../../../state";
|
||||||
|
import type { ThreeEvent } from "@react-three/fiber";
|
||||||
|
import type { ObjectGeometryViewItem } from "../../../state/viewLayers";
|
||||||
|
|
||||||
|
export function ObjectClickTarget({ item }: { item: ObjectGeometryViewItem }) {
|
||||||
|
const obj = item.object;
|
||||||
|
const bb = item.objectTypeGeometry.boundingBox;
|
||||||
|
|
||||||
|
const size = useMemo(() => {
|
||||||
|
if (!bb)
|
||||||
|
return [1, 1, 1] as const;
|
||||||
|
|
||||||
|
return [bb.max[0] - bb.min[0], bb.max[1] - bb.min[1], bb.max[2] - bb.min[2]] as const;
|
||||||
|
}, [bb]);
|
||||||
|
|
||||||
|
const center = useMemo(() => {
|
||||||
|
if (!bb)
|
||||||
|
return [0.5, 0.5, 0.5] as const;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(bb.min[0] + bb.max[0]) / 2,
|
||||||
|
(bb.min[1] + bb.max[1]) / 2,
|
||||||
|
(bb.min[2] + bb.max[2]) / 2,
|
||||||
|
] as const;
|
||||||
|
}, [bb]);
|
||||||
|
|
||||||
|
const handleClick = (e: ThreeEvent<MouseEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
state.worldEditor.setSelectedObject({ id: obj.id, editMode: "translate" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return <mesh
|
||||||
|
position={obj.position}
|
||||||
|
rotation={obj.rotation}
|
||||||
|
scale={obj.scale}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<mesh position={center}>
|
||||||
|
<boxGeometry args={size} />
|
||||||
|
<meshBasicMaterial visible={false} />
|
||||||
|
</mesh>
|
||||||
|
</mesh>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { useEffect, useCallback } from "react";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { state } from "../../../state";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { SelectionBoundingBox } from "./SelectionBoundingBox";
|
||||||
|
import { measuredRenderTime } from "../../../utils/layerTimings";
|
||||||
|
import { useObjectGeometryList } from "../ObjectGeometriesLayer";
|
||||||
|
|
||||||
|
const raycaster = new THREE.Raycaster();
|
||||||
|
const pointer = new THREE.Vector2();
|
||||||
|
|
||||||
|
export const SceneEditorObjectSelectionLayer = observer(measuredRenderTime(
|
||||||
|
"ObjectSelection",
|
||||||
|
function () {
|
||||||
|
const selection = state.view.selectedObject;
|
||||||
|
const camera = useThree(s => s.camera);
|
||||||
|
const scene = useThree(s => s.scene);
|
||||||
|
const gl = useThree(s => s.gl);
|
||||||
|
const size = useThree(s => s.size);
|
||||||
|
const geometryListRef = useObjectGeometryList();
|
||||||
|
|
||||||
|
const handlePointerDown = useCallback((e: PointerEvent) => {
|
||||||
|
if (e.button !== 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pointer.x = (e.offsetX / size.width) * 2 - 1;
|
||||||
|
pointer.y = -(e.offsetY / size.height) * 2 + 1;
|
||||||
|
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
|
||||||
|
const instancedMeshes = scene.children.filter(
|
||||||
|
(child): child is THREE.InstancedMesh => child instanceof THREE.InstancedMesh,
|
||||||
|
);
|
||||||
|
|
||||||
|
const intersections = raycaster.intersectObjects(instancedMeshes, false);
|
||||||
|
|
||||||
|
if (intersections.length === 0) {
|
||||||
|
state.worldEditor.resetSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hit = intersections[0];
|
||||||
|
const mesh = hit.object as THREE.InstancedMesh;
|
||||||
|
const instanceId = hit.instanceId;
|
||||||
|
|
||||||
|
if (instanceId === undefined) {
|
||||||
|
state.worldEditor.resetSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const geometryList = geometryListRef.current;
|
||||||
|
if (!geometryList)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const objectId = geometryList.getObjectIdByMesh(mesh, instanceId);
|
||||||
|
if (objectId) {
|
||||||
|
state.worldEditor.setSelectedObject({ id: objectId, editMode: "translate" });
|
||||||
|
} else {
|
||||||
|
state.worldEditor.resetSelection();
|
||||||
|
}
|
||||||
|
}, [camera, scene, size, geometryListRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const domElement = gl.domElement;
|
||||||
|
domElement.addEventListener("pointerdown", handlePointerDown);
|
||||||
|
return () => {
|
||||||
|
domElement.removeEventListener("pointerdown", handlePointerDown);
|
||||||
|
};
|
||||||
|
}, [gl, handlePointerDown]);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{selection &&
|
||||||
|
<SelectionBoundingBox item={selection.geometry} />
|
||||||
|
}
|
||||||
|
</>;
|
||||||
|
},
|
||||||
|
));
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useMemo } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import type { ObjectGeometryViewItem } from "../../../state/viewLayers";
|
||||||
|
|
||||||
|
export const SelectionBoundingBox = function({ item }: { item: ObjectGeometryViewItem }) {
|
||||||
|
const obj = item.object;
|
||||||
|
const bb = item.objectTypeGeometry.boundingBox;
|
||||||
|
|
||||||
|
const geometry = useMemo(() => {
|
||||||
|
if (!bb)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
const box = new THREE.BoxGeometry(
|
||||||
|
bb.max[0] - bb.min[0],
|
||||||
|
bb.max[1] - bb.min[1],
|
||||||
|
bb.max[2] - bb.min[2],
|
||||||
|
);
|
||||||
|
return new THREE.EdgesGeometry(box);
|
||||||
|
}, [bb]);
|
||||||
|
|
||||||
|
const center = useMemo(() => {
|
||||||
|
if (!bb)
|
||||||
|
return [0.5, 0.5, 0.5] as const;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(bb.min[0] + bb.max[0]) / 2,
|
||||||
|
(bb.min[1] + bb.max[1]) / 2,
|
||||||
|
(bb.min[2] + bb.max[2]) / 2,
|
||||||
|
] as const;
|
||||||
|
}, [bb]);
|
||||||
|
|
||||||
|
if (!geometry)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return <mesh
|
||||||
|
position={obj.position}
|
||||||
|
rotation={obj.rotation}
|
||||||
|
scale={obj.scale}
|
||||||
|
>
|
||||||
|
<lineSegments position={center} geometry={geometry}>
|
||||||
|
<lineBasicMaterial color="#ffff00" />
|
||||||
|
</lineSegments>
|
||||||
|
</mesh>;
|
||||||
|
};
|
||||||
@@ -17,7 +17,7 @@ export const StatsLayer = function () {
|
|||||||
bottom: "8px",
|
bottom: "8px",
|
||||||
right: "8px",
|
right: "8px",
|
||||||
color: "#0f0",
|
color: "#0f0",
|
||||||
fontSize: "14px",
|
fontSize: "10px",
|
||||||
fontFamily: "monospace",
|
fontFamily: "monospace",
|
||||||
zIndex: "9999",
|
zIndex: "9999",
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
@@ -50,15 +50,10 @@ export const StatsLayer = function () {
|
|||||||
|
|
||||||
for (const [name, entry] of Object.entries(layerTimings)) {
|
for (const [name, entry] of Object.entries(layerTimings)) {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (entry.frame.frames > 0) {
|
parts.push(`frame ${(entry.frame.frames > 0 ? (entry.frame.acc / entry.frame.frames) : 0).toFixed(3)}`);
|
||||||
parts.push(`frame ${(entry.frame.acc / entry.frame.frames).toFixed(3)}`);
|
parts.push(`render ${(entry.render.frames > 0 ? (entry.render.acc / entry.render.frames) : 0).toFixed(3)}`);
|
||||||
}
|
lines.push(`${name} ${parts.join(" ")} ms`);
|
||||||
if (entry.render.frames > 0) {
|
|
||||||
parts.push(`render ${(entry.render.acc / entry.render.frames).toFixed(3)}`);
|
|
||||||
}
|
|
||||||
if (parts.length > 0) {
|
|
||||||
lines.push(`${name} ${parts.join(" ")} ms`);
|
|
||||||
}
|
|
||||||
entry.frame.acc = 0;
|
entry.frame.acc = 0;
|
||||||
entry.frame.frames = 0;
|
entry.frame.frames = 0;
|
||||||
entry.render.acc = 0;
|
entry.render.acc = 0;
|
||||||
|
|||||||
@@ -1,37 +1,28 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { MobxReconcilerMap, type ReconcilerMap } from '../utils/reconciler';
|
import { MobxReconcilerMap, type ReconcilerMap } from '../utils/reconciler';
|
||||||
import type { ObjectGeometryViewItem, ObjectTypeGeometryViewItem } from '../state/viewState';
|
import type { ObjectTypeGeometryViewItem } from '../state/viewLayers';
|
||||||
|
|
||||||
export class ObjectGeometryList {
|
export class ObjectGeometryList {
|
||||||
private items: ReconcilerMap<ObjectGeometryViewItem>;
|
private items: ReconcilerMap<ObjectTypeGeometryViewItem>;
|
||||||
private readonly typeRefCounts = new Map<string, number>();
|
|
||||||
|
|
||||||
private readonly matrixObj = new THREE.Object3D();
|
private readonly matrixObj = new THREE.Object3D();
|
||||||
private readonly _p = new THREE.Vector3();
|
private readonly _p = new THREE.Vector3();
|
||||||
private readonly _e = new THREE.Euler();
|
private readonly _e = new THREE.Euler();
|
||||||
private readonly _q = new THREE.Quaternion();
|
private readonly _q = new THREE.Quaternion();
|
||||||
|
|
||||||
constructor(scene: THREE.Scene, geometiries: Iterable<ObjectGeometryViewItem>) {
|
private readonly _meshToObjectIds = new Map<THREE.InstancedMesh, string[]>();
|
||||||
this.items = new MobxReconcilerMap<ObjectGeometryViewItem>({
|
|
||||||
reaction: () => geometiries,
|
constructor(scene: THREE.Scene, typeGeometries: Iterable<ObjectTypeGeometryViewItem>) {
|
||||||
keyOf: (item) => item.object.id,
|
this.items = new MobxReconcilerMap<ObjectTypeGeometryViewItem>({
|
||||||
|
reaction: () => typeGeometries,
|
||||||
|
keyOf: (item) => item.objectType.id,
|
||||||
initializeItem: (item) => {
|
initializeItem: (item) => {
|
||||||
const typeId = item.objectTypeGeometry.objectType.id;
|
for (const mesh of item.meshes ?? [])
|
||||||
const count = (this.typeRefCounts.get(typeId) ?? 0) + 1;
|
scene.add(mesh);
|
||||||
this.typeRefCounts.set(typeId, count);
|
|
||||||
if (count === 1) {
|
|
||||||
for (const mesh of item.objectTypeGeometry.meshes ?? [])
|
|
||||||
scene.add(mesh);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
disposeItem: (item) => {
|
disposeItem: (item) => {
|
||||||
const typeId = item.objectTypeGeometry.objectType.id;
|
for (const mesh of item.meshes ?? [])
|
||||||
const count = (this.typeRefCounts.get(typeId) ?? 1) - 1;
|
scene.remove(mesh);
|
||||||
this.typeRefCounts.set(typeId, count);
|
|
||||||
if (count === 0) {
|
|
||||||
for (const mesh of item.objectTypeGeometry.meshes ?? [])
|
|
||||||
scene.remove(mesh);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -40,37 +31,40 @@ export class ObjectGeometryList {
|
|||||||
this.items?.dispose();
|
this.items?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called once per frame — reads MobX observables directly outside any reactive
|
getObjectIdByMesh(mesh: THREE.InstancedMesh, instanceId: number): string | undefined {
|
||||||
// context, so no subscriptions are created and no re-renders are triggered.
|
return this._meshToObjectIds.get(mesh)?.[instanceId];
|
||||||
|
}
|
||||||
|
|
||||||
tick(): void {
|
tick(): void {
|
||||||
const touchedObjectTypes: Record<string, { typeGeometry: ObjectTypeGeometryViewItem, instanceCount: number }> = {};
|
this._meshToObjectIds.clear();
|
||||||
|
|
||||||
this.items.values.forEach((item) => {
|
this.items.values.forEach((typeGeometry) => {
|
||||||
const obj = item.object;
|
const instances = typeGeometry.instances;
|
||||||
|
const meshes = typeGeometry.meshes ?? [];
|
||||||
|
const objectIds: string[] = [];
|
||||||
|
|
||||||
this._p.set(...obj.position);
|
instances.forEach((item, instanceIdx) => {
|
||||||
this._e.set(...obj.rotation);
|
const obj = item.object;
|
||||||
this._q.setFromEuler(this._e);
|
|
||||||
|
|
||||||
const typeId = item.objectTypeGeometry.objectType.id;
|
this._p.set(...obj.position);
|
||||||
if (!touchedObjectTypes[typeId])
|
this._e.set(...obj.rotation);
|
||||||
touchedObjectTypes[typeId] = { typeGeometry: item.objectTypeGeometry, instanceCount: 0 };
|
this._q.setFromEuler(this._e);
|
||||||
const instanceIdx = touchedObjectTypes[typeId].instanceCount++;
|
|
||||||
|
|
||||||
for (const mesh of item.objectTypeGeometry.meshes ?? []) {
|
|
||||||
this.matrixObj.position.copy(this._p);
|
this.matrixObj.position.copy(this._p);
|
||||||
this.matrixObj.quaternion.copy(this._q);
|
this.matrixObj.quaternion.copy(this._q);
|
||||||
this.matrixObj.scale.set(...obj.scale);
|
this.matrixObj.scale.set(...obj.scale);
|
||||||
this.matrixObj.updateMatrix();
|
this.matrixObj.updateMatrix();
|
||||||
mesh.setMatrixAt(instanceIdx, this.matrixObj.matrix);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const objectType of Object.values(touchedObjectTypes)) {
|
objectIds[instanceIdx] = obj.id;
|
||||||
for (const mesh of objectType.typeGeometry.meshes ?? []) {
|
|
||||||
mesh.count = objectType.instanceCount;
|
for (const mesh of meshes)
|
||||||
|
mesh.setMatrixAt(instanceIdx, this.matrixObj.matrix);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const mesh of meshes) {
|
||||||
|
this._meshToObjectIds.set(mesh, objectIds);
|
||||||
|
mesh.count = instances.length;
|
||||||
mesh.instanceMatrix.needsUpdate = true;
|
mesh.instanceMatrix.needsUpdate = true;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { action, makeObservable, observable } from "mobx";
|
||||||
|
import { nextObjectEditMode, type ObjectEditMode } from "../worldEditorState";
|
||||||
|
import { ViewItem } from "./base";
|
||||||
|
import type { ObjectGeometryViewItem } from "./objectGeometry";
|
||||||
|
|
||||||
|
export class ObjectSelectionViewItem extends ViewItem {
|
||||||
|
public readonly geometry: ObjectGeometryViewItem;
|
||||||
|
public mode: ObjectEditMode;
|
||||||
|
|
||||||
|
constructor(geometry: ObjectGeometryViewItem, mode: ObjectEditMode) {
|
||||||
|
super();
|
||||||
|
this.geometry = geometry;
|
||||||
|
this.mode = mode;
|
||||||
|
|
||||||
|
makeObservable(
|
||||||
|
this,
|
||||||
|
{
|
||||||
|
geometry: observable.ref,
|
||||||
|
mode: true,
|
||||||
|
nextMode: action,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public nextMode(): void {
|
||||||
|
this.mode = nextObjectEditMode(this.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import type { ObjectType, V3, Voxel, VoxelGroup } from "../../types";
|
import type { ObjectType, V3, Voxel, VoxelGroup } from "../../types";
|
||||||
import { ViewItem } from "./base";
|
import { ViewItem } from "./base";
|
||||||
import { action, makeObservable, observable, reaction, type IReactionDisposer } from 'mobx';
|
import { action, computed, makeObservable, observable, reaction, type IReactionDisposer } from 'mobx';
|
||||||
import { getObjectVoxelGroups } from '../../utils/graphics/voxelGroup';
|
import { getObjectVoxelGroups } from '../../utils/graphics/voxelGroup';
|
||||||
import { buildVoxelGroupMesh } from '../../utils/graphics/voxelMesh';
|
import { buildVoxelGroupMesh } from '../../utils/graphics/voxelMesh';
|
||||||
import { state } from '../rootState';
|
import { state } from '../rootState';
|
||||||
import { buildObjectTrimesh } from '../../utils/graphics/mesh';
|
import { buildObjectTrimesh } from '../../utils/graphics/mesh';
|
||||||
import { computeBoundingBox } from '../../utils/runtime';
|
import { computeBoundingBox } from '../../utils/runtime';
|
||||||
|
import type { ObjectGeometryViewItem } from './objectGeometry';
|
||||||
|
import type { ViewState } from '../viewState';
|
||||||
|
|
||||||
export class ObjectTypeGeometryViewItem extends ViewItem {
|
export class ObjectTypeGeometryViewItem extends ViewItem {
|
||||||
|
public readonly owner: ViewState;
|
||||||
public readonly objectType: ObjectType;
|
public readonly objectType: ObjectType;
|
||||||
|
|
||||||
public voxelGroups: VoxelGroup[] | undefined;
|
public voxelGroups: VoxelGroup[] | undefined;
|
||||||
@@ -18,8 +21,9 @@ export class ObjectTypeGeometryViewItem extends ViewItem {
|
|||||||
|
|
||||||
private readonly _disposeVoxelReaction: IReactionDisposer;
|
private readonly _disposeVoxelReaction: IReactionDisposer;
|
||||||
|
|
||||||
constructor(objectType: ObjectType) {
|
constructor(owner: ViewState, objectType: ObjectType) {
|
||||||
super();
|
super();
|
||||||
|
this.owner = owner;
|
||||||
this.objectType = objectType;
|
this.objectType = objectType;
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
voxelGroups: observable.ref,
|
voxelGroups: observable.ref,
|
||||||
@@ -27,6 +31,7 @@ export class ObjectTypeGeometryViewItem extends ViewItem {
|
|||||||
boundingBox: observable.ref,
|
boundingBox: observable.ref,
|
||||||
meshes: observable.ref,
|
meshes: observable.ref,
|
||||||
updateRuntimeData: action,
|
updateRuntimeData: action,
|
||||||
|
instances: computed,
|
||||||
});
|
});
|
||||||
this._disposeVoxelReaction = reaction(
|
this._disposeVoxelReaction = reaction(
|
||||||
() => objectType.voxels.map(v => `${v.position}-${v.typeId}`).join('|'),
|
() => objectType.voxels.map(v => `${v.position}-${v.typeId}`).join('|'),
|
||||||
@@ -78,4 +83,8 @@ export class ObjectTypeGeometryViewItem extends ViewItem {
|
|||||||
this.colliderMesh = buildObjectTrimesh(this.objectType, state.world.data.voxelTypes);
|
this.colliderMesh = buildObjectTrimesh(this.objectType, state.world.data.voxelTypes);
|
||||||
this.boundingBox = computeBoundingBox(this.objectType);
|
this.boundingBox = computeBoundingBox(this.objectType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get instances(): ObjectGeometryViewItem[] {
|
||||||
|
return this.owner.objectGeometries.values.filter((o) => o.objectTypeGeometry === this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export class ViewState {
|
|||||||
this.objectTypeGeometries = new MobxReconcilerMap<ObjectType, ObjectTypeGeometryViewItem>({
|
this.objectTypeGeometries = new MobxReconcilerMap<ObjectType, ObjectTypeGeometryViewItem>({
|
||||||
reaction: () => Object.values(world.data.objectTypes),
|
reaction: () => Object.values(world.data.objectTypes),
|
||||||
keyOf: (ot) => ot.id,
|
keyOf: (ot) => ot.id,
|
||||||
mapItem: (ot) => new ObjectTypeGeometryViewItem(ot),
|
mapItem: (ot) => new ObjectTypeGeometryViewItem(this, ot),
|
||||||
updateItem: () => true,
|
updateItem: () => true,
|
||||||
disposeItem: (item) => { item.dispose(); },
|
disposeItem: (item) => { item.dispose(); },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user