import { C3DTilesLayer } from 'itowns';
import {
Box3,
BufferAttribute,
BufferGeometry,
Matrix3,
Mesh,
MeshStandardMaterial,
Vector3,
} from 'three';
/**
* Creates a mock-up object.
*
* @param {Array<C3DTilesLayer>} layers Array of layers, contains information about objects in a 3D scene.
* @param {Box3} area Box3 the selected area for the mock-up.
* @returns {Mesh} mock-up object
*/
export function createMockUpObject(layers, area) {
if (!area.min || !area.max)
throw new Error("area is not a Box3 or hasn't min and max field");
if (!layers.length) {
console.warn("Can't create mockUp without layers");
return null;
}
// Parse geometry intersected
const geometryMockUp = new BufferGeometry();
const positionsMockUp = [];
const normalsMockUp = [];
const materialsMockup = [];
const addToFinalMockUp = (positions, normals, color) => {
let materialIndex = -1;
const material = materialsMockup.filter((mat, index) => {
if (mat.color.getHex() == color) {
materialIndex = index;
return true;
}
return false;
});
if (material.length == 0) {
materialsMockup.push(new MeshStandardMaterial({ color: color }));
materialIndex = materialsMockup.length - 1;
}
geometryMockUp.addGroup(
positionsMockUp.length / 3,
positions.length / 3,
materialIndex
);
positionsMockUp.push(...positions);
normalsMockUp.push(...normals);
};
// compute potential object intersecting
const potentialObjects = new Map();
layers.forEach((l) => {
l.object3d.traverse((child) => {
if (child.geometry && child.geometry.attributes._BATCHID) {
const bbChild = child.geometry.boundingBox
.clone()
.applyMatrix4(child.matrixWorld);
if (area.intersectsBox(bbChild))
potentialObjects.set(child.uuid, child);
}
});
});
// compute gml_id intersecting
const gmlIDs = [];
const bbBuffer = new Box3();
layers.forEach((l) => {
/* First pass to find gmlids to add to mock up */
for (const [, c3dTfeatures] of l.tilesC3DTileFeatures) {
for (const [, feature] of c3dTfeatures) {
const gmlId = feature.getInfo().batchTable['gml_id'];
if (gmlIDs.includes(gmlId) || !gmlId) continue; // gml id already added
if (!potentialObjects.has(feature.object3d.uuid)) continue; // object3d not intersecting with area
feature.computeWorldBox3(bbBuffer);
if (area.intersectsBox(bbBuffer)) gmlIDs.push(gmlId);
}
}
});
// add to mockup gmlids recorded
const bufferPos = new Vector3();
const bufferNormal = new Vector3();
layers.forEach((l) => {
for (const [, c3dTfeatures] of l.tilesC3DTileFeatures) {
for (const [, feature] of c3dTfeatures) {
const gmlId = feature.getInfo().batchTable['gml_id'];
if (gmlIDs.includes(gmlId)) {
// add to the mockup
const positions = [];
const normals = [];
const normalMatrixWorld = new Matrix3().getNormalMatrix(
feature.object3d.matrixWorld
);
feature.groups.forEach((group) => {
const positionIndexStart = group.start * 3;
const positionIndexCount = (group.start + group.count) * 3;
for (
let index = positionIndexStart;
index < positionIndexCount;
index += 3
) {
bufferPos.x =
feature.object3d.geometry.attributes.position.array[index];
bufferPos.y =
feature.object3d.geometry.attributes.position.array[index + 1];
bufferPos.z =
feature.object3d.geometry.attributes.position.array[index + 2];
positions.push(
...bufferPos
.applyMatrix4(feature.object3d.matrixWorld)
.toArray()
);
bufferNormal.x =
feature.object3d.geometry.attributes.normal.array[index];
bufferNormal.y =
feature.object3d.geometry.attributes.normal.array[index + 1];
bufferNormal.z =
feature.object3d.geometry.attributes.normal.array[index + 2];
normals.push(
...bufferNormal.applyMatrix3(normalMatrixWorld).toArray()
);
}
});
addToFinalMockUp(positions, normals, feature.object3d.material.color);
}
}
}
});
// create mock up from geometry
geometryMockUp.setAttribute(
'position',
new BufferAttribute(new Float32Array(positionsMockUp), 3)
);
geometryMockUp.setAttribute(
'normal',
new BufferAttribute(new Float32Array(normalsMockUp), 3)
);
// center geometryockUp on xy and put it at zero on z
geometryMockUp.computeBoundingBox();
const bbMockUp = geometryMockUp.boundingBox;
const centerMockUp = bbMockUp.min.clone().lerp(bbMockUp.max, 0.5);
const geoPositionsMockUp = geometryMockUp.attributes.position.array;
for (let index = 0; index < geoPositionsMockUp.length; index += 3) {
geoPositionsMockUp[index] -= centerMockUp.x;
geoPositionsMockUp[index + 1] -= centerMockUp.y;
geoPositionsMockUp[index + 2] -= bbMockUp.min.z; // so it's on the table
}
// create mesh
const mockUpObject = new Mesh(geometryMockUp, materialsMockup);
mockUpObject.name = 'MockUp Object';
return mockUpObject;
}