UNPKG

@geodanresearch/mapbox-3dtiles

Version:
232 lines (191 loc) 7.81 kB
import * as THREE from 'three'; import { GetIntersectingObjects } from './Utils.mjs' import { internalGLTFCache } from './TileLoaders.mjs'; import TilesetLayer from './TilesetLayer.mjs'; export default class FeatureInfo { constructor(world, map, camera, loader, selectMaterial) { this.world = world; this.map = map; this.camera = camera; this.loader = loader; this.selectedObjects = []; this.selectMaterial = selectMaterial ? selectMaterial : this._createSelectMaterial(); } getAt(result, x, y) { this.unselect(); const intersects = GetIntersectingObjects(this.camera, this.world.children, this.map.transform.width, this.map.transform.height, x, y); if (!intersects || intersects.length == 0 || !intersects[0].object || !intersects[0].object.modelType) { return result; } const intersect = intersects[0]; const type = intersect.object.modelType; const feature = this._createFeature(type, intersect); this._selectObject(type, intersect); result.unshift(feature); this.map.triggerRepaint(); return result; } unselect() { for (let i = 0; i < this.selectedObjects.length; i++) { const selected = this.selectedObjects[i]; selected.parent.remove(selected.object); } this.selectedObjects = []; this._updateMap(); } _createSelectMaterial() { const material = new THREE.MeshBasicMaterial({ color: 0xb077f8, side: THREE.FrontSide, transparent: true, depthTest: true, depthWrite: false, opacity: 0.75 }); material.polygonOffset = true; material.polygonOffsetUnit = 1; material.polygonOffsetFactor = -1; return material; } _updateMap() { this.map.triggerRepaint(); } _getTilesetID(o) { if (o instanceof TilesetLayer) { return o.tilesetId; } else if (o.parent) { return this._getTilesetID(o.parent); } else { return undefined; } } async _selectObject(type, intersect) { let selectedObject = {}; switch (type) { case "i3dm": selectedObject = await this._createSelectI3dm(intersect); break; case "b3dm": selectedObject = this._createSelectB3dm(intersect); break; } intersect.object.parent.add(selectedObject); this.selectedObjects.push({ parent: intersect.object.parent, object: selectedObject }) this._updateMap(); } _createFeature(type, intersect) { let feature = { type: 'Feature', properties: {}, geometry: {}, layer: { id: "", type: 'custom 3d' }, source: this.url, 'source-layer': null, state: {} }; const tilesetId = this._getTilesetID(intersect.object); feature.layer.id = tilesetId; switch (type) { case "i3dm": return this._createFeatureI3DM(feature, intersect); case "b3dm": return this._createFeatureB3DM(feature, intersect); } return feature; } _createFeatureI3DM(feature, intersect) { let keys = Object.keys(intersect.object.userData); if (keys.length) { for (let propertyName of keys) { feature.properties[propertyName] = intersect.object.userData[propertyName][intersect.instanceId]; } } else { feature.properties.batchId = intersect.instanceId; } return feature; } _createFeatureB3DM(feature, intersect) { if (intersect.object.userData.b3dm) { feature.properties['b3dm'] = intersect.object.userData.b3dm; } let keys = Object.keys(intersect.object.userData); if (keys.length) { for (let propertyName of keys) { if(intersect.object.userData[propertyName]) { feature.properties[propertyName] = intersect.object.userData[propertyName][intersect.instanceId]; } } } else { feature.properties.batchId = intersect.instanceId; } const vertexIdx = intersect.face.a; const propertyIndex = intersect.object.geometry.attributes._batchid.getX(vertexIdx); feature.properties.batchId = propertyIndex; //FIXME: userData is not always in the parent, it can also be on the object itself depending on how many separate meshes have been created let parentKeys = Object.keys(intersect.object.parent.userData); if (parentKeys.length) { for (let propertyName of parentKeys) { feature.properties[propertyName] = intersect.object.parent.userData[propertyName][propertyIndex]; } } return feature; } _createSelectB3dm(intersect) { const vertexIdx = intersect.face.a; const normals = []; const positions = []; const object = intersect.object; const count = object.geometry.attributes.position.count; const batchId = object.geometry.attributes._batchid.getX(vertexIdx); const indexArray = {}; for (let i = 0; i < count; i++) { if (object.geometry.attributes._batchid.getX(i) === batchId) { indexArray[i] = true; } } for (let i = 0; i < object.geometry.index.count; i++) { const val = object.geometry.index.array[i]; if (indexArray[val]) { normals.push(object.geometry.attributes.normal.getX(val)); normals.push(object.geometry.attributes.normal.getY(val)); normals.push(object.geometry.attributes.normal.getZ(val)); positions.push(object.geometry.attributes.position.getX(val)); positions.push(object.geometry.attributes.position.getY(val)); positions.push(object.geometry.attributes.position.getZ(val)); } } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3, false)); geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(normals), 3, false)); return new THREE.Mesh(geometry, this.selectMaterial); } _createSelectI3dm(intersect) { return new Promise((resolve, reject) => { const instanceId = intersect.instanceId; const objectMatrix = new THREE.Matrix4(); intersect.object.getMatrixAt(instanceId, objectMatrix); const cache = internalGLTFCache; var glbData = cache.get(intersect.object.model); let selectScene = undefined; const resource = intersect.object.model; this.loader.parse(glbData, resource, (gltf) => { selectScene = gltf.scene || gltf.scenes[0]; selectScene.rotateX(Math.PI / 2); // convert from GLTF Y-up to Mapbox Z-up selectScene.matrixWorldNeedsUpdate = false; selectScene.applyMatrix4(objectMatrix); selectScene.updateMatrixWorld(); selectScene.traverse(child => { if (child instanceof THREE.Mesh) { child.material = this.selectMaterial; } }); selectScene.needsUpdate = true; resolve(selectScene); }); }); } }