From 88cd07b3a8151250fc8eb6da4d773f07345fabcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Gonz=C3=A1lez=20Viegas?= Date: Wed, 31 Jul 2024 13:07:12 +0200 Subject: [PATCH] feat: implement fragment cloning --- packages/fragments/package.json | 2 +- packages/fragments/src/fragment-mesh.ts | 6 ++ packages/fragments/src/fragment.ts | 45 +++++++++++++ packages/fragments/src/fragments-group.ts | 80 +++++++++++++++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/packages/fragments/package.json b/packages/fragments/package.json index 7cba7dd..81a3184 100644 --- a/packages/fragments/package.json +++ b/packages/fragments/package.json @@ -1,7 +1,7 @@ { "name": "@thatopen/fragments", "description": "Simple geometric system built on top of Three.js to display 3D BIM data efficiently.", - "version": "2.1.5", + "version": "2.1.6", "author": "That Open Company", "contributors": [ "Antonio Gonzalez Viegas (https://github.com/agviegas)", diff --git a/packages/fragments/src/fragment-mesh.ts b/packages/fragments/src/fragment-mesh.ts index fe6bb71..ddeef27 100644 --- a/packages/fragments/src/fragment-mesh.ts +++ b/packages/fragments/src/fragment-mesh.ts @@ -118,4 +118,10 @@ export class FragmentMesh extends THREE.InstancedMesh { colors, }; } + + clone(_recursive?: boolean): any { + throw new Error( + "Fragment meshes can't be cloned directly. Use mesh.fragment.clone instead!", + ); + } } diff --git a/packages/fragments/src/fragment.ts b/packages/fragments/src/fragment.ts index 6c4374f..088c2aa 100644 --- a/packages/fragments/src/fragment.ts +++ b/packages/fragments/src/fragment.ts @@ -546,6 +546,51 @@ export class Fragment { return { ...geometry, ids, id }; } + /** + * Creates a copy of the whole fragment or a part of it. It shares the geometry with the original fragment, but has its own InstancedMesh data, so it also needs to be disposed. + * + * @param itemIDs - An iterable of item IDs to be included in the clone. + * + */ + clone(itemIDs: Iterable = this.ids) { + const newFragment = new Fragment( + this.mesh.geometry, + this.mesh.material, + this.capacity, + ); + + const items: Item[] = []; + + for (const id of itemIDs) { + const instancesIDs = this.getInstancesIDs(id); + if (instancesIDs === null) { + continue; + } + + const transforms: THREE.Matrix4[] = []; + const colors: THREE.Color[] = []; + + for (const instanceID of instancesIDs) { + const newMatrix = new THREE.Matrix4(); + const newColor = new THREE.Color(); + this.mesh.getMatrixAt(instanceID, newMatrix); + this.mesh.getColorAt(instanceID, newColor); + transforms.push(newMatrix); + colors.push(newColor); + } + + items.push({ + id, + transforms, + colors, + }); + } + + newFragment.add(items); + + return newFragment; + } + private putLast(instanceID1: number) { if (this.mesh.count === 0) return; diff --git a/packages/fragments/src/fragments-group.ts b/packages/fragments/src/fragments-group.ts index bd2d8cb..ec847a2 100644 --- a/packages/fragments/src/fragments-group.ts +++ b/packages/fragments/src/fragments-group.ts @@ -404,6 +404,86 @@ export class FragmentsGroup extends THREE.Group { return result; } + clone(_recursive?: boolean): any { + throw new Error("Use FragmentsGroup.cloneGroup instead!"); + } + + /** + * Creates a copy of the whole group or a part of it. Each fragment clone shares the geometry of with its respective original fragment, but has its own InstancedMesh data, so it also needs to be disposed. + * + * @param items - Optional - The part of the group to be cloned. If not given, the whole group is cloned. + * + */ + cloneGroup(items?: FragmentIdMap) { + const newGroup = new FragmentsGroup(); + + newGroup.coordinationMatrix = this.coordinationMatrix; + newGroup.position.copy(this.position); + newGroup.rotation.copy(this.rotation); + newGroup.scale.copy(this.scale); + newGroup.updateMatrix(); + + newGroup.ifcMetadata = { ...this.ifcMetadata }; + + if (!items) { + items = this.getFragmentMap(this.data.keys()); + } + + const allIDs = new Set(); + + const fragmentIDConversion = new Map(); + + for (const fragment of this.items) { + if (!items[fragment.id]) { + continue; + } + + const ids = items[fragment.id]; + const newFragment = fragment.clone(ids); + + fragmentIDConversion.set(fragment.id, newFragment.id); + + newGroup.items.push(newFragment); + newGroup.add(newFragment.mesh); + + for (const expressID of ids) { + allIDs.add(expressID); + } + } + + for (const id of allIDs) { + const data = this.data.get(id); + if (data) { + newGroup.data.set(id, data); + } + } + + for (const [fragKey, fragID] of this.keyFragments) { + if (fragmentIDConversion.has(fragID)) { + const newID = fragmentIDConversion.get(fragID); + if (newID === undefined) { + throw new Error("Malformed fragment ID map during clone!"); + } + newGroup.keyFragments.set(fragKey, newID); + } + } + + for (const [globalID, expressID] of this.globalToExpressIDs) { + if (allIDs.has(expressID)) { + newGroup.globalToExpressIDs.set(globalID, expressID); + } + } + + if (this.civilData) { + newGroup.civilData = { + coordinationMatrix: this.coordinationMatrix, + alignments: new Map(), + }; + } + + return newGroup as this; + } + private getPropsURL(id: number) { const { ids } = this.streamSettings; const fileID = ids.get(id);