UNPKG

io3fix

Version:

toolkit for interior apps

401 lines (330 loc) 13 kB
import checkDependencies from '../check-dependencies.js' import setMaterial from './data3d-view/set-material.js' import Io3dMaterial from './data3d-view/io3d-material.js' import Wireframe from './data3d-view/wireframe.js' import Promise from 'bluebird' // constants export default checkDependencies({ three: true, aframe: false }, function makeData3dView () { var WEBGL_SIDE = { front: 0, back: 1, both: 2 } var DEG_TO_RAD = Math.PI / 180 var RAD_TO_DEG = 180 / Math.PI // shared variables var geometry3dCache = {} /** * @name three.Data3dView * @memberof three * @param options * @constructor */ function Data3dView (options) { // API this.threeParent = options.parent // internals this.meshKeys = [] this.meshes = {} this.materialKeys = [] this.materials = {} this._meshes3d = {} // three meshes indexed by meshId this._wireframes3d = {} // wireframe three meshes indexed by meshId this._materials3d = {} // three materials indexed by meshId } Data3dView.prototype = { set: function (data3d, options) { // API options = options || {} var meshes = data3d.meshes || this.meshes, meshKeys = data3d.meshKeys, materials = data3d.materials || this.materials, materialKeys = data3d.materialKeys, loadingQueuePrefix = data3d.loadingQueuePrefix || options.loadingQueuePrefix || 'architecture', onFirstTextureSetLoaded = options.onFirstTextureSetLoaded, lightMapIntensity = options.lightMapIntensity, lightMapExposure = options.lightMapExposure // internals var self = this, meshId, mesh, materialId, wireframe3d, positions, uvs, uvs2, scale, normals, mesh3d, geometry3d, material3d, position, rotRad, rotDeg, i, l // output var promise ///////////////// meshes if (meshes) { // generate IDs if not provided if (!meshKeys) { meshKeys = Object.keys(meshes) } for (i = 0, l = meshKeys.length; i < l; i++) { meshId = meshKeys[ i ] mesh = meshes[ meshId ] // internals materialId = mesh.material positions = mesh.positions uvs = mesh.uvs uvs2 = mesh.uvsLightmap normals = mesh.normals position = mesh.position rotRad = mesh.rotRad rotDeg = mesh.rotDeg scale = mesh.scale // three.js materials if (!self._materials3d[ meshId ]) { // (one material pro mesh, because some of our mesh properties are material properties and it does not matter performance wise) material3d = new Io3dMaterial() material3d.name = materialId if (!materials) { // there is no material properties. using default properties setMaterial({ material3d: material3d }) } self._materials3d[ meshId ] = material3d } // set face side (a mesh property in our data structure, but a material property in three.js data structure) self._materials3d[ meshId ].side = WEBGL_SIDE[ meshes[ meshId ].side ] || WEBGL_SIDE['front'] // create three.js meshes if (!self._meshes3d[ meshId ]) { // create geometry geometry3d = createOrReuseGeometry3d( mesh.cacheKey ) // create mesh mesh3d = new THREE.Mesh(geometry3d, material3d) // add to parent self.threeParent.add(mesh3d) // remembers self._meshes3d[ meshId ] = mesh3d // create a separate geometry object for wireframes wireframe3d = new Wireframe() // add to parent self._meshes3d[ meshId ].add(wireframe3d) // remember self._wireframes3d[ meshId ] = wireframe3d } else { mesh3d = self._meshes3d[ meshId ] geometry3d = mesh3d.geometry } // apply scale if (scale) { mesh3d.scale.set( scale[0] , scale[1], scale[2] ) } // apply position if (position) { mesh3d.position.set( position[0] , position[1], position[2] ) } // apply rotation if (rotRad) { mesh3d.rotation.set( rotRad[0] , rotRad[1], rotRad[2] ) } else if (rotDeg) { mesh3d.rotation.set( rotDeg[0] * DEG_TO_RAD, rotDeg[1] * DEG_TO_RAD, rotDeg[2] * DEG_TO_RAD ) } // apply buffers if they are different than current buffers if (geometry3d.attributes.position === undefined) { geometry3d.addAttribute( 'position', new THREE.BufferAttribute(positions, 3) ) // The bounding box of the scene may need to be updated // self.vm.viewport.webglView.modelBoundingBoxNeedsUpdate = true } else if (geometry3d.attributes.position.array !== positions ) { geometry3d.attributes.position.array = positions geometry3d.attributes.position.needsUpdate = true // Three.js needs this to update geometry3d.computeBoundingSphere() // The bounding box of the scene may need to be updated // self.vm.viewport.webglView.modelBoundingBoxNeedsUpdate = true } if (geometry3d.attributes.normal === undefined) { geometry3d.addAttribute( 'normal', new THREE.BufferAttribute(normals, 3) ) } else if (geometry3d.attributes.normal.array !== normals ) { geometry3d.attributes.normal.array = normals geometry3d.attributes.normal.needsUpdate = true } // geometry3d.attributesKeys = ['position', 'normal'] // set uvs channel 1 (material) if (uvs) { if (geometry3d.attributes.uv === undefined) { geometry3d.attributes.uv = new THREE.BufferAttribute(uvs, 2) } else if (geometry3d.attributes.uv.array !== uvs ) { geometry3d.attributes.uv.array = uvs geometry3d.attributes.uv.needsUpdate = true // remove previous scale settings delete geometry3d.attributes.uv._scaleU delete geometry3d.attributes.uv._scaleV delete geometry3d.attributes.uv._source } // geometry3d.attributesKeys[ 2 ] = 'uv' } else if (geometry3d.attributes.uv) { delete geometry3d.attributes.uv } if (uvs2) { if (geometry3d.attributes.uv2 === undefined) { geometry3d.attributes.uv2 = new THREE.BufferAttribute(uvs2, 2) } else if (geometry3d.attributes.uv2.array !== uvs2 ) { geometry3d.attributes.uv2.array = uvs2 geometry3d.attributes.uv2.needsUpdate = true } // geometry3d.attributesKeys[ geometry3d.attributesKeys.length++ ] = 'uv2' } else if (geometry3d.attributes.uv2) { delete geometry3d.attributes.uv2 } // (2017/01/09) The WebGL buffer of the pickingColor attribute is erroneously deleted // by ThreeJS (r69) in deallocateGeometry(). ThreeJS doesn't seem to account for the fact // that the attribute is shared by multiple geometries. It then does not get recreated, because // this function was attempting to manually set BufferGeometry.attributesKeys, missing any // extra attributes such as pickingColor. geometry3d.attributesKeys = Object.keys(geometry3d.attributes); // update wireframe if (materials[ materialId ]) { self._wireframes3d[ meshId ].update({ positions: positions, thickness: materials[ materialId ].wireframeThickness === undefined ? 0 : materials[ materialId ].wireframeThickness, thresholdAngle: materials[ materialId ].wireframeThresholdAngle, color: materials[ materialId ].wireframeColor, opacity: materials[ materialId ].wireframeOpacity }) } } // remove obsolete three.js meshes var mesh, meshIds = Object.keys(self._meshes3d) meshIds.forEach(function(meshId, i){ mesh = self._meshes3d[meshId] if (!meshes[ meshId ]) { // destroy wireframe geometry var wireframe3d = self._wireframes3d[ meshId ] if (wireframe3d.parent) { wireframe3d.parent.remove( wireframe3d ) wireframe3d.geometry.dispose() } // destroy geometry var geometry3d = self._meshes3d[ meshId ].geometry disposeGeometry3dIfNotUsedElsewhere(self.meshes[ meshId ].cacheKey, geometry3d) // destroy threejs mesh var mesh3d = self._meshes3d[ meshId ] if (mesh3d.parent) { mesh3d.parent.remove( mesh3d ) } // destroy material var material3d = self._materials3d[ meshId ] if (material3d.map) material3d.map.disposeIfPossible() if (material3d.specularMap) material3d.specularMap.disposeIfPossible() if (material3d.normalMap) material3d.normalMap.disposeIfPossible() if (material3d.alphaMap) material3d.alphaMap.disposeIfPossible() if (material3d.lightMap) material3d.lightMap.disposeIfPossible() material3d.dispose() // remove reference to destroyed 3d objects delete self._meshes3d[ meshId ] delete self._wireframes3d[ meshId ] delete self._materials3d[ meshId ] } }) // update properties self.meshKeys = meshKeys self.meshes = meshes } ///////////////// materials if (materials) { var materialPromises = [], material for (i = 0, l = self.meshKeys.length; i < l; i++) { meshId = self.meshKeys[ i ] materialId = self.meshes[ meshId ].material // material attributes material = materials[ materialId ] if (material && Object.keys(material).length) { // set material materialPromises[ i ] = setMaterial({ vm: self.vm, loadingQueuePrefix: loadingQueuePrefix, mesh3d: self._meshes3d[ meshId ], material3d: self._materials3d[ meshId ], attributes: materials[ materialId ], onFirstTextureSetLoaded: onFirstTextureSetLoaded, lightMapIntensity: lightMapIntensity, lightMapExposure: lightMapExposure }) } } // output promise = Promise.all(materialPromises) // update properties self.materialKeys = materialKeys self.materials = materials } ///////////////// return return promise ? promise : Promise.resolve() }, hasMeshes: function hasMeshes() { return Object.keys(this._meshes3d).length > 0 }, setMeshes: function(meshes){ this.set({ meshes: meshes }) }, setMaterials: function(materials, options){ this.set({ materials: materials }, options) }, reset: function(){ this.set({ meshes: {}, materials: {} }) }, destroy: function(){ this.isDestroyed = true this.reset() this.threeParent = null // internals this.meshKeys = null this.meshes = null this.materialKeys = null this.materials = null this._meshes3d = null this._materials3d = null } } // helpers function createOrReuseGeometry3d( key ) { if (key) { // use cache if (geometry3dCache[ key ]) { geometry3dCache[ key ].refCount++ } else { geometry3dCache[ key ] = { geometry3d: new THREE.BufferGeometry(), refCount: 1 } } return geometry3dCache[ key ].geometry3d } else { // no key no cache return new THREE.BufferGeometry() } } function disposeGeometry3dIfNotUsedElsewhere( key, geometry3d ) { if (key) { // involve cache if (geometry3dCache[ key ]) { geometry3dCache[ key ].refCount-- if (geometry3dCache[ key ].refCount < 1) { geometry3dCache[ key ].geometry3d.dispose() delete geometry3dCache[ key ] } } else { // (2017/01/09) See comment in ThreeView.set() // if (geometry3d.attributes.pickingColor) // delete geometry3d.attributes['pickingColor']; geometry3d.dispose() } } else { // no key bo cache // (2017/01/09) See comment in ThreeView.set() // if (geometry3d.attributes.pickingColor) // delete geometry3d.attributes['pickingColor']; geometry3d.dispose() } } return Data3dView })