object selection

This commit is contained in:
azykov@mail.ru
2026-06-22 14:22:15 +03:00
parent 0b7cdd9f05
commit 21c48e44ee
10 changed files with 267 additions and 62 deletions
+36 -42
View File
@@ -1,37 +1,28 @@
import * as THREE from 'three';
import { MobxReconcilerMap, type ReconcilerMap } from '../utils/reconciler';
import type { ObjectGeometryViewItem, ObjectTypeGeometryViewItem } from '../state/viewState';
import type { ObjectTypeGeometryViewItem } from '../state/viewLayers';
export class ObjectGeometryList {
private items: ReconcilerMap<ObjectGeometryViewItem>;
private readonly typeRefCounts = new Map<string, number>();
private items: ReconcilerMap<ObjectTypeGeometryViewItem>;
private readonly matrixObj = new THREE.Object3D();
private readonly _p = new THREE.Vector3();
private readonly _e = new THREE.Euler();
private readonly _q = new THREE.Quaternion();
constructor(scene: THREE.Scene, geometiries: Iterable<ObjectGeometryViewItem>) {
this.items = new MobxReconcilerMap<ObjectGeometryViewItem>({
reaction: () => geometiries,
keyOf: (item) => item.object.id,
private readonly _meshToObjectIds = new Map<THREE.InstancedMesh, string[]>();
constructor(scene: THREE.Scene, typeGeometries: Iterable<ObjectTypeGeometryViewItem>) {
this.items = new MobxReconcilerMap<ObjectTypeGeometryViewItem>({
reaction: () => typeGeometries,
keyOf: (item) => item.objectType.id,
initializeItem: (item) => {
const typeId = item.objectTypeGeometry.objectType.id;
const count = (this.typeRefCounts.get(typeId) ?? 0) + 1;
this.typeRefCounts.set(typeId, count);
if (count === 1) {
for (const mesh of item.objectTypeGeometry.meshes ?? [])
scene.add(mesh);
}
for (const mesh of item.meshes ?? [])
scene.add(mesh);
},
disposeItem: (item) => {
const typeId = item.objectTypeGeometry.objectType.id;
const count = (this.typeRefCounts.get(typeId) ?? 1) - 1;
this.typeRefCounts.set(typeId, count);
if (count === 0) {
for (const mesh of item.objectTypeGeometry.meshes ?? [])
scene.remove(mesh);
}
for (const mesh of item.meshes ?? [])
scene.remove(mesh);
},
});
}
@@ -40,37 +31,40 @@ export class ObjectGeometryList {
this.items?.dispose();
}
// Called once per frame — reads MobX observables directly outside any reactive
// context, so no subscriptions are created and no re-renders are triggered.
getObjectIdByMesh(mesh: THREE.InstancedMesh, instanceId: number): string | undefined {
return this._meshToObjectIds.get(mesh)?.[instanceId];
}
tick(): void {
const touchedObjectTypes: Record<string, { typeGeometry: ObjectTypeGeometryViewItem, instanceCount: number }> = {};
this._meshToObjectIds.clear();
this.items.values.forEach((item) => {
const obj = item.object;
this.items.values.forEach((typeGeometry) => {
const instances = typeGeometry.instances;
const meshes = typeGeometry.meshes ?? [];
const objectIds: string[] = [];
this._p.set(...obj.position);
this._e.set(...obj.rotation);
this._q.setFromEuler(this._e);
instances.forEach((item, instanceIdx) => {
const obj = item.object;
const typeId = item.objectTypeGeometry.objectType.id;
if (!touchedObjectTypes[typeId])
touchedObjectTypes[typeId] = { typeGeometry: item.objectTypeGeometry, instanceCount: 0 };
const instanceIdx = touchedObjectTypes[typeId].instanceCount++;
for (const mesh of item.objectTypeGeometry.meshes ?? []) {
this._p.set(...obj.position);
this._e.set(...obj.rotation);
this._q.setFromEuler(this._e);
this.matrixObj.position.copy(this._p);
this.matrixObj.quaternion.copy(this._q);
this.matrixObj.scale.set(...obj.scale);
this.matrixObj.updateMatrix();
mesh.setMatrixAt(instanceIdx, this.matrixObj.matrix);
}
});
for (const objectType of Object.values(touchedObjectTypes)) {
for (const mesh of objectType.typeGeometry.meshes ?? []) {
mesh.count = objectType.instanceCount;
objectIds[instanceIdx] = obj.id;
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;
}
}
});
}
}