Buckets:
ktongue/docker_container / simsite /frontend /node_modules /three /examples /jsm /loaders /3DMLoader.js
| import { | |
| BufferGeometryLoader, | |
| FileLoader, | |
| Loader, | |
| Object3D, | |
| MeshStandardMaterial, | |
| MeshPhysicalMaterial, | |
| Mesh, | |
| Color, | |
| Points, | |
| PointsMaterial, | |
| Line, | |
| LineBasicMaterial, | |
| Matrix4, | |
| DirectionalLight, | |
| PointLight, | |
| SpotLight, | |
| RectAreaLight, | |
| Sprite, | |
| SpriteMaterial, | |
| CanvasTexture, | |
| LinearFilter, | |
| ClampToEdgeWrapping, | |
| RepeatWrapping, | |
| TextureLoader, | |
| DoubleSide | |
| } from 'three'; | |
| import { EXRLoader } from '../loaders/EXRLoader.js'; | |
| const _taskCache = new WeakMap(); | |
| class Rhino3dmLoader extends Loader { | |
| constructor( manager ) { | |
| super( manager ); | |
| this.libraryPath = ''; | |
| this.libraryPending = null; | |
| this.libraryBinary = null; | |
| this.libraryConfig = {}; | |
| this.url = ''; | |
| this.workerLimit = 4; | |
| this.workerPool = []; | |
| this.workerNextTaskID = 1; | |
| this.workerSourceURL = ''; | |
| this.workerConfig = {}; | |
| this.materials = []; | |
| this.warnings = []; | |
| } | |
| setLibraryPath( path ) { | |
| this.libraryPath = path; | |
| return this; | |
| } | |
| setWorkerLimit( workerLimit ) { | |
| this.workerLimit = workerLimit; | |
| return this; | |
| } | |
| load( url, onLoad, onProgress, onError ) { | |
| const loader = new FileLoader( this.manager ); | |
| loader.setPath( this.path ); | |
| loader.setResponseType( 'arraybuffer' ); | |
| loader.setRequestHeader( this.requestHeader ); | |
| this.url = url; | |
| loader.load( url, ( buffer ) => { | |
| // Check for an existing task using this buffer. A transferred buffer cannot be transferred | |
| // again from this thread. | |
| if ( _taskCache.has( buffer ) ) { | |
| const cachedTask = _taskCache.get( buffer ); | |
| return cachedTask.promise.then( onLoad ).catch( onError ); | |
| } | |
| this.decodeObjects( buffer, url ) | |
| .then( result => { | |
| result.userData.warnings = this.warnings; | |
| onLoad( result ); | |
| } ) | |
| .catch( e => onError( e ) ); | |
| }, onProgress, onError ); | |
| } | |
| debug() { | |
| console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); | |
| } | |
| decodeObjects( buffer, url ) { | |
| let worker; | |
| let taskID; | |
| const taskCost = buffer.byteLength; | |
| const objectPending = this._getWorker( taskCost ) | |
| .then( ( _worker ) => { | |
| worker = _worker; | |
| taskID = this.workerNextTaskID ++; | |
| return new Promise( ( resolve, reject ) => { | |
| worker._callbacks[ taskID ] = { resolve, reject }; | |
| worker.postMessage( { type: 'decode', id: taskID, buffer }, [ buffer ] ); | |
| // this.debug(); | |
| } ); | |
| } ) | |
| .then( ( message ) => this._createGeometry( message.data ) ) | |
| .catch( e => { | |
| throw e; | |
| } ); | |
| // Remove task from the task list. | |
| // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) | |
| objectPending | |
| .catch( () => true ) | |
| .then( () => { | |
| if ( worker && taskID ) { | |
| this._releaseTask( worker, taskID ); | |
| //this.debug(); | |
| } | |
| } ); | |
| // Cache the task result. | |
| _taskCache.set( buffer, { | |
| url: url, | |
| promise: objectPending | |
| } ); | |
| return objectPending; | |
| } | |
| parse( data, onLoad, onError ) { | |
| this.decodeObjects( data, '' ) | |
| .then( result => { | |
| result.userData.warnings = this.warnings; | |
| onLoad( result ); | |
| } ) | |
| .catch( e => onError( e ) ); | |
| } | |
| _compareMaterials( material ) { | |
| const mat = {}; | |
| mat.name = material.name; | |
| mat.color = {}; | |
| mat.color.r = material.color.r; | |
| mat.color.g = material.color.g; | |
| mat.color.b = material.color.b; | |
| mat.type = material.type; | |
| const json = JSON.stringify( mat ); | |
| for ( let i = 0; i < this.materials.length; i ++ ) { | |
| const m = this.materials[ i ]; | |
| const _mat = {}; | |
| _mat.name = m.name; | |
| _mat.color = {}; | |
| _mat.color.r = m.color.r; | |
| _mat.color.g = m.color.g; | |
| _mat.color.b = m.color.b; | |
| _mat.type = m.type; | |
| if ( JSON.stringify( _mat ) === json ) { | |
| return m; | |
| } | |
| } | |
| this.materials.push( material ); | |
| return material; | |
| } | |
| _createMaterial( material, renderEnvironment ) { | |
| if ( material === undefined ) { | |
| return new MeshStandardMaterial( { | |
| color: new Color( 1, 1, 1 ), | |
| metalness: 0.8, | |
| name: Loader.DEFAULT_MATERIAL_NAME, | |
| side: DoubleSide | |
| } ); | |
| } | |
| //console.log(material) | |
| let mat = new MeshPhysicalMaterial( { | |
| color: new Color( material.diffuseColor.r / 255.0, material.diffuseColor.g / 255.0, material.diffuseColor.b / 255.0 ), | |
| emissive: new Color( material.emissionColor.r, material.emissionColor.g, material.emissionColor.b ), | |
| flatShading: material.disableLighting, | |
| ior: material.indexOfRefraction, | |
| name: material.name, | |
| reflectivity: material.reflectivity, | |
| opacity: 1.0 - material.transparency, | |
| side: DoubleSide, | |
| specularColor: material.specularColor, | |
| transparent: material.transparency > 0 ? true : false | |
| } ); | |
| mat.userData.id = material.id; | |
| if ( material.pbrSupported ) { | |
| const pbr = material.pbr; | |
| mat.anisotropy = pbr.anisotropy; | |
| mat.anisotropyRotation = pbr.anisotropicRotation; | |
| mat.color = new Color( pbr.baseColor.r, pbr.baseColor.g, pbr.baseColor.b ); | |
| mat.clearCoat = pbr.clearCoat; | |
| mat.clearCoatRoughness = pbr.clearCoatRoughness; | |
| mat.metalness = pbr.metallic; | |
| mat.transmission = 1 - pbr.opacity; | |
| mat.roughness = pbr.roughness; | |
| mat.sheen = pbr.sheen; | |
| mat.specularIntensity = pbr.specular; | |
| mat.thickness = pbr.subsurface; | |
| } | |
| if ( material.pbrSupported && material.pbr.opacity === 0 && material.transparency === 1 ) { | |
| //some compromises | |
| mat.opacity = 0.2; | |
| mat.transmission = 1.00; | |
| } | |
| const textureLoader = new TextureLoader(); | |
| for ( let i = 0; i < material.textures.length; i ++ ) { | |
| const texture = material.textures[ i ]; | |
| if ( texture.image !== null ) { | |
| const map = textureLoader.load( texture.image ); | |
| //console.log(texture.type ) | |
| switch ( texture.type ) { | |
| case 'Bump': | |
| mat.bumpMap = map; | |
| break; | |
| case 'Diffuse': | |
| mat.map = map; | |
| break; | |
| case 'Emap': | |
| mat.envMap = map; | |
| break; | |
| case 'Opacity': | |
| mat.transmissionMap = map; | |
| break; | |
| case 'Transparency': | |
| mat.alphaMap = map; | |
| mat.transparent = true; | |
| break; | |
| case 'PBR_Alpha': | |
| mat.alphaMap = map; | |
| mat.transparent = true; | |
| break; | |
| case 'PBR_AmbientOcclusion': | |
| mat.aoMap = map; | |
| break; | |
| case 'PBR_Anisotropic': | |
| mat.anisotropyMap = map; | |
| break; | |
| case 'PBR_BaseColor': | |
| mat.map = map; | |
| break; | |
| case 'PBR_Clearcoat': | |
| mat.clearcoatMap = map; | |
| break; | |
| case 'PBR_ClearcoatBump': | |
| mat.clearcoatNormalMap = map; | |
| break; | |
| case 'PBR_ClearcoatRoughness': | |
| mat.clearcoatRoughnessMap = map; | |
| break; | |
| case 'PBR_Displacement': | |
| mat.displacementMap = map; | |
| break; | |
| case 'PBR_Emission': | |
| mat.emissiveMap = map; | |
| break; | |
| case 'PBR_Metallic': | |
| mat.metalnessMap = map; | |
| break; | |
| case 'PBR_Roughness': | |
| mat.roughnessMap = map; | |
| break; | |
| case 'PBR_Sheen': | |
| mat.sheenColorMap = map; | |
| break; | |
| case 'PBR_Specular': | |
| mat.specularColorMap = map; | |
| break; | |
| case 'PBR_Subsurface': | |
| mat.thicknessMap = map; | |
| break; | |
| default: | |
| this.warnings.push( { | |
| message: `THREE.3DMLoader: No conversion exists for 3dm ${texture.type}.`, | |
| type: 'no conversion' | |
| } ); | |
| break; | |
| } | |
| map.wrapS = texture.wrapU === 0 ? RepeatWrapping : ClampToEdgeWrapping; | |
| map.wrapT = texture.wrapV === 0 ? RepeatWrapping : ClampToEdgeWrapping; | |
| if ( texture.repeat ) { | |
| map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] ); | |
| } | |
| } | |
| } | |
| if ( renderEnvironment ) { | |
| new EXRLoader().load( renderEnvironment.image, function ( texture ) { | |
| texture.mapping = THREE.EquirectangularReflectionMapping; | |
| mat.envMap = texture; | |
| } ); | |
| } | |
| return mat; | |
| } | |
| _createGeometry( data ) { | |
| const object = new Object3D(); | |
| const instanceDefinitionObjects = []; | |
| const instanceDefinitions = []; | |
| const instanceReferences = []; | |
| object.userData[ 'layers' ] = data.layers; | |
| object.userData[ 'groups' ] = data.groups; | |
| object.userData[ 'settings' ] = data.settings; | |
| object.userData.settings[ 'renderSettings' ] = data.renderSettings; | |
| object.userData[ 'objectType' ] = 'File3dm'; | |
| object.userData[ 'materials' ] = null; | |
| object.name = this.url; | |
| let objects = data.objects; | |
| const materials = data.materials; | |
| for ( let i = 0; i < objects.length; i ++ ) { | |
| const obj = objects[ i ]; | |
| const attributes = obj.attributes; | |
| switch ( obj.objectType ) { | |
| case 'InstanceDefinition': | |
| instanceDefinitions.push( obj ); | |
| break; | |
| case 'InstanceReference': | |
| instanceReferences.push( obj ); | |
| break; | |
| default: | |
| let matId; | |
| switch( attributes.materialSource.name ) { | |
| case 'ObjectMaterialSource_MaterialFromLayer': | |
| //check layer index | |
| if ( attributes.layerIndex >= 0 ) { | |
| matId = data.layers[ attributes.layerIndex ].renderMaterialIndex; | |
| } else { | |
| matId = null; | |
| } | |
| break; | |
| case 'ObjectMaterialSource_MaterialFromObject': | |
| if ( attributes.materialIndex >= 0 ) { | |
| matId = attributes.materialIndex; | |
| } else { | |
| matId = null; | |
| } | |
| break; | |
| } | |
| let material; | |
| if ( matId >= 0 ) { | |
| const rMaterial = materials[ matId ]; | |
| material = this._createMaterial( rMaterial, data.renderEnvironment ); | |
| } else { | |
| material = this._createMaterial(); | |
| } | |
| material = this._compareMaterials( material ); | |
| const _object = this._createObject( obj, material ); | |
| if ( _object === undefined ) { | |
| continue; | |
| } | |
| const layer = data.layers[ attributes.layerIndex ]; | |
| _object.visible = layer ? data.layers[ attributes.layerIndex ].visible : true; | |
| if ( attributes.isInstanceDefinitionObject ) { | |
| instanceDefinitionObjects.push( _object ); | |
| } else { | |
| object.add( _object ); | |
| } | |
| break; | |
| } | |
| } | |
| for ( let i = 0; i < instanceDefinitions.length; i ++ ) { | |
| const iDef = instanceDefinitions[ i ]; | |
| objects = []; | |
| for ( let j = 0; j < iDef.attributes.objectIds.length; j ++ ) { | |
| const objId = iDef.attributes.objectIds[ j ]; | |
| for ( let p = 0; p < instanceDefinitionObjects.length; p ++ ) { | |
| const idoId = instanceDefinitionObjects[ p ].userData.attributes.id; | |
| if ( objId === idoId ) { | |
| objects.push( instanceDefinitionObjects[ p ] ); | |
| } | |
| } | |
| } | |
| // Currently clones geometry and does not take advantage of instancing | |
| for ( let j = 0; j < instanceReferences.length; j ++ ) { | |
| const iRef = instanceReferences[ j ]; | |
| if ( iRef.geometry.parentIdefId === iDef.attributes.id ) { | |
| const iRefObject = new Object3D(); | |
| const xf = iRef.geometry.xform.array; | |
| const matrix = new Matrix4(); | |
| matrix.set( ...xf ); | |
| iRefObject.applyMatrix4( matrix ); | |
| for ( let p = 0; p < objects.length; p ++ ) { | |
| iRefObject.add( objects[ p ].clone( true ) ); | |
| } | |
| object.add( iRefObject ); | |
| } | |
| } | |
| } | |
| object.userData[ 'materials' ] = this.materials; | |
| object.name = ''; | |
| return object; | |
| } | |
| _createObject( obj, mat ) { | |
| const loader = new BufferGeometryLoader(); | |
| const attributes = obj.attributes; | |
| let geometry, material, _color, color; | |
| switch ( obj.objectType ) { | |
| case 'Point': | |
| case 'PointSet': | |
| geometry = loader.parse( obj.geometry ); | |
| if ( geometry.attributes.hasOwnProperty( 'color' ) ) { | |
| material = new PointsMaterial( { vertexColors: true, sizeAttenuation: false, size: 2 } ); | |
| } else { | |
| _color = attributes.drawColor; | |
| color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); | |
| material = new PointsMaterial( { color: color, sizeAttenuation: false, size: 2 } ); | |
| } | |
| material = this._compareMaterials( material ); | |
| const points = new Points( geometry, material ); | |
| points.userData[ 'attributes' ] = attributes; | |
| points.userData[ 'objectType' ] = obj.objectType; | |
| if ( attributes.name ) { | |
| points.name = attributes.name; | |
| } | |
| return points; | |
| case 'Mesh': | |
| case 'Extrusion': | |
| case 'SubD': | |
| case 'Brep': | |
| if ( obj.geometry === null ) return; | |
| geometry = loader.parse( obj.geometry ); | |
| if ( geometry.attributes.hasOwnProperty( 'color' ) ) { | |
| mat.vertexColors = true; | |
| } | |
| if ( mat === null ) { | |
| mat = this._createMaterial(); | |
| mat = this._compareMaterials( mat ); | |
| } | |
| const mesh = new Mesh( geometry, mat ); | |
| mesh.castShadow = attributes.castsShadows; | |
| mesh.receiveShadow = attributes.receivesShadows; | |
| mesh.userData[ 'attributes' ] = attributes; | |
| mesh.userData[ 'objectType' ] = obj.objectType; | |
| if ( attributes.name ) { | |
| mesh.name = attributes.name; | |
| } | |
| return mesh; | |
| case 'Curve': | |
| geometry = loader.parse( obj.geometry ); | |
| _color = attributes.drawColor; | |
| color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); | |
| material = new LineBasicMaterial( { color: color } ); | |
| material = this._compareMaterials( material ); | |
| const lines = new Line( geometry, material ); | |
| lines.userData[ 'attributes' ] = attributes; | |
| lines.userData[ 'objectType' ] = obj.objectType; | |
| if ( attributes.name ) { | |
| lines.name = attributes.name; | |
| } | |
| return lines; | |
| case 'TextDot': | |
| geometry = obj.geometry; | |
| const ctx = document.createElement( 'canvas' ).getContext( '2d' ); | |
| const font = `${geometry.fontHeight}px ${geometry.fontFace}`; | |
| ctx.font = font; | |
| const width = ctx.measureText( geometry.text ).width + 10; | |
| const height = geometry.fontHeight + 10; | |
| const r = window.devicePixelRatio; | |
| ctx.canvas.width = width * r; | |
| ctx.canvas.height = height * r; | |
| ctx.canvas.style.width = width + 'px'; | |
| ctx.canvas.style.height = height + 'px'; | |
| ctx.setTransform( r, 0, 0, r, 0, 0 ); | |
| ctx.font = font; | |
| ctx.textBaseline = 'middle'; | |
| ctx.textAlign = 'center'; | |
| color = attributes.drawColor; | |
| ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`; | |
| ctx.fillRect( 0, 0, width, height ); | |
| ctx.fillStyle = 'white'; | |
| ctx.fillText( geometry.text, width / 2, height / 2 ); | |
| const texture = new CanvasTexture( ctx.canvas ); | |
| texture.minFilter = LinearFilter; | |
| texture.wrapS = ClampToEdgeWrapping; | |
| texture.wrapT = ClampToEdgeWrapping; | |
| material = new SpriteMaterial( { map: texture, depthTest: false } ); | |
| const sprite = new Sprite( material ); | |
| sprite.position.set( geometry.point[ 0 ], geometry.point[ 1 ], geometry.point[ 2 ] ); | |
| sprite.scale.set( width / 10, height / 10, 1.0 ); | |
| sprite.userData[ 'attributes' ] = attributes; | |
| sprite.userData[ 'objectType' ] = obj.objectType; | |
| if ( attributes.name ) { | |
| sprite.name = attributes.name; | |
| } | |
| return sprite; | |
| case 'Light': | |
| geometry = obj.geometry; | |
| let light; | |
| switch ( geometry.lightStyle.name ) { | |
| case 'LightStyle_WorldPoint': | |
| light = new PointLight(); | |
| light.castShadow = attributes.castsShadows; | |
| light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); | |
| light.shadow.normalBias = 0.1; | |
| break; | |
| case 'LightStyle_WorldSpot': | |
| light = new SpotLight(); | |
| light.castShadow = attributes.castsShadows; | |
| light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); | |
| light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); | |
| light.angle = geometry.spotAngleRadians; | |
| light.shadow.normalBias = 0.1; | |
| break; | |
| case 'LightStyle_WorldRectangular': | |
| light = new RectAreaLight(); | |
| const width = Math.abs( geometry.width[ 2 ] ); | |
| const height = Math.abs( geometry.length[ 0 ] ); | |
| light.position.set( geometry.location[ 0 ] - ( height / 2 ), geometry.location[ 1 ], geometry.location[ 2 ] - ( width / 2 ) ); | |
| light.height = height; | |
| light.width = width; | |
| light.lookAt( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); | |
| break; | |
| case 'LightStyle_WorldDirectional': | |
| light = new DirectionalLight(); | |
| light.castShadow = attributes.castsShadows; | |
| light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); | |
| light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); | |
| light.shadow.normalBias = 0.1; | |
| break; | |
| case 'LightStyle_WorldLinear': | |
| // no conversion exists, warning has already been printed to the console | |
| break; | |
| default: | |
| break; | |
| } | |
| if ( light ) { | |
| light.intensity = geometry.intensity; | |
| _color = geometry.diffuse; | |
| color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); | |
| light.color = color; | |
| light.userData[ 'attributes' ] = attributes; | |
| light.userData[ 'objectType' ] = obj.objectType; | |
| } | |
| return light; | |
| } | |
| } | |
| _initLibrary() { | |
| if ( ! this.libraryPending ) { | |
| // Load rhino3dm wrapper. | |
| const jsLoader = new FileLoader( this.manager ); | |
| jsLoader.setPath( this.libraryPath ); | |
| const jsContent = new Promise( ( resolve, reject ) => { | |
| jsLoader.load( 'rhino3dm.js', resolve, undefined, reject ); | |
| } ); | |
| // Load rhino3dm WASM binary. | |
| const binaryLoader = new FileLoader( this.manager ); | |
| binaryLoader.setPath( this.libraryPath ); | |
| binaryLoader.setResponseType( 'arraybuffer' ); | |
| const binaryContent = new Promise( ( resolve, reject ) => { | |
| binaryLoader.load( 'rhino3dm.wasm', resolve, undefined, reject ); | |
| } ); | |
| this.libraryPending = Promise.all( [ jsContent, binaryContent ] ) | |
| .then( ( [ jsContent, binaryContent ] ) => { | |
| //this.libraryBinary = binaryContent; | |
| this.libraryConfig.wasmBinary = binaryContent; | |
| const fn = Rhino3dmWorker.toString(); | |
| const body = [ | |
| '/* rhino3dm.js */', | |
| jsContent, | |
| '/* worker */', | |
| fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) | |
| ].join( '\n' ); | |
| this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); | |
| } ); | |
| } | |
| return this.libraryPending; | |
| } | |
| _getWorker( taskCost ) { | |
| return this._initLibrary().then( () => { | |
| if ( this.workerPool.length < this.workerLimit ) { | |
| const worker = new Worker( this.workerSourceURL ); | |
| worker._callbacks = {}; | |
| worker._taskCosts = {}; | |
| worker._taskLoad = 0; | |
| worker.postMessage( { | |
| type: 'init', | |
| libraryConfig: this.libraryConfig | |
| } ); | |
| worker.onmessage = e => { | |
| const message = e.data; | |
| switch ( message.type ) { | |
| case 'warning': | |
| this.warnings.push( message.data ); | |
| console.warn( message.data ); | |
| break; | |
| case 'decode': | |
| worker._callbacks[ message.id ].resolve( message ); | |
| break; | |
| case 'error': | |
| worker._callbacks[ message.id ].reject( message ); | |
| break; | |
| default: | |
| console.error( 'THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"' ); | |
| } | |
| }; | |
| this.workerPool.push( worker ); | |
| } else { | |
| this.workerPool.sort( function ( a, b ) { | |
| return a._taskLoad > b._taskLoad ? - 1 : 1; | |
| } ); | |
| } | |
| const worker = this.workerPool[ this.workerPool.length - 1 ]; | |
| worker._taskLoad += taskCost; | |
| return worker; | |
| } ); | |
| } | |
| _releaseTask( worker, taskID ) { | |
| worker._taskLoad -= worker._taskCosts[ taskID ]; | |
| delete worker._callbacks[ taskID ]; | |
| delete worker._taskCosts[ taskID ]; | |
| } | |
| dispose() { | |
| for ( let i = 0; i < this.workerPool.length; ++ i ) { | |
| this.workerPool[ i ].terminate(); | |
| } | |
| this.workerPool.length = 0; | |
| return this; | |
| } | |
| } | |
| /* WEB WORKER */ | |
| function Rhino3dmWorker() { | |
| let libraryPending; | |
| let libraryConfig; | |
| let rhino; | |
| let taskID; | |
| onmessage = function ( e ) { | |
| const message = e.data; | |
| switch ( message.type ) { | |
| case 'init': | |
| libraryConfig = message.libraryConfig; | |
| const wasmBinary = libraryConfig.wasmBinary; | |
| let RhinoModule; | |
| libraryPending = new Promise( function ( resolve ) { | |
| /* Like Basis Loader */ | |
| RhinoModule = { wasmBinary, onRuntimeInitialized: resolve }; | |
| rhino3dm( RhinoModule ); // eslint-disable-line no-undef | |
| } ).then( () => { | |
| rhino = RhinoModule; | |
| } ); | |
| break; | |
| case 'decode': | |
| taskID = message.id; | |
| const buffer = message.buffer; | |
| libraryPending.then( () => { | |
| try { | |
| const data = decodeObjects( rhino, buffer ); | |
| self.postMessage( { type: 'decode', id: message.id, data } ); | |
| } catch ( error ) { | |
| self.postMessage( { type: 'error', id: message.id, error } ); | |
| } | |
| } ); | |
| break; | |
| } | |
| }; | |
| function decodeObjects( rhino, buffer ) { | |
| const arr = new Uint8Array( buffer ); | |
| const doc = rhino.File3dm.fromByteArray( arr ); | |
| const objects = []; | |
| const materials = []; | |
| const layers = []; | |
| const views = []; | |
| const namedViews = []; | |
| const groups = []; | |
| const strings = []; | |
| //Handle objects | |
| const objs = doc.objects(); | |
| const cnt = objs.count; | |
| for ( let i = 0; i < cnt; i ++ ) { | |
| const _object = objs.get( i ); | |
| const object = extractObjectData( _object, doc ); | |
| _object.delete(); | |
| if ( object ) { | |
| objects.push( object ); | |
| } | |
| } | |
| // Handle instance definitions | |
| // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` ); | |
| for ( let i = 0; i < doc.instanceDefinitions().count; i ++ ) { | |
| const idef = doc.instanceDefinitions().get( i ); | |
| const idefAttributes = extractProperties( idef ); | |
| idefAttributes.objectIds = idef.getObjectIds(); | |
| objects.push( { geometry: null, attributes: idefAttributes, objectType: 'InstanceDefinition' } ); | |
| } | |
| // Handle materials | |
| const textureTypes = [ | |
| // rhino.TextureType.Bitmap, | |
| rhino.TextureType.Diffuse, | |
| rhino.TextureType.Bump, | |
| rhino.TextureType.Transparency, | |
| rhino.TextureType.Opacity, | |
| rhino.TextureType.Emap | |
| ]; | |
| const pbrTextureTypes = [ | |
| rhino.TextureType.PBR_BaseColor, | |
| rhino.TextureType.PBR_Subsurface, | |
| rhino.TextureType.PBR_SubsurfaceScattering, | |
| rhino.TextureType.PBR_SubsurfaceScatteringRadius, | |
| rhino.TextureType.PBR_Metallic, | |
| rhino.TextureType.PBR_Specular, | |
| rhino.TextureType.PBR_SpecularTint, | |
| rhino.TextureType.PBR_Roughness, | |
| rhino.TextureType.PBR_Anisotropic, | |
| rhino.TextureType.PBR_Anisotropic_Rotation, | |
| rhino.TextureType.PBR_Sheen, | |
| rhino.TextureType.PBR_SheenTint, | |
| rhino.TextureType.PBR_Clearcoat, | |
| rhino.TextureType.PBR_ClearcoatBump, | |
| rhino.TextureType.PBR_ClearcoatRoughness, | |
| rhino.TextureType.PBR_OpacityIor, | |
| rhino.TextureType.PBR_OpacityRoughness, | |
| rhino.TextureType.PBR_Emission, | |
| rhino.TextureType.PBR_AmbientOcclusion, | |
| rhino.TextureType.PBR_Displacement | |
| ]; | |
| for ( let i = 0; i < doc.materials().count; i ++ ) { | |
| const _material = doc.materials().get( i ); | |
| let material = extractProperties( _material ); | |
| const textures = []; | |
| textures.push( ...extractTextures( _material, textureTypes, doc ) ); | |
| material.pbrSupported = _material.physicallyBased().supported; | |
| if ( material.pbrSupported ) { | |
| textures.push( ...extractTextures( _material, pbrTextureTypes, doc ) ); | |
| material.pbr = extractProperties( _material.physicallyBased() ); | |
| } | |
| material.textures = textures; | |
| materials.push( material ); | |
| _material.delete(); | |
| } | |
| // Handle layers | |
| for ( let i = 0; i < doc.layers().count; i ++ ) { | |
| const _layer = doc.layers().get( i ); | |
| const layer = extractProperties( _layer ); | |
| layers.push( layer ); | |
| _layer.delete(); | |
| } | |
| // Handle views | |
| for ( let i = 0; i < doc.views().count; i ++ ) { | |
| const _view = doc.views().get( i ); | |
| const view = extractProperties( _view ); | |
| views.push( view ); | |
| _view.delete(); | |
| } | |
| // Handle named views | |
| for ( let i = 0; i < doc.namedViews().count; i ++ ) { | |
| const _namedView = doc.namedViews().get( i ); | |
| const namedView = extractProperties( _namedView ); | |
| namedViews.push( namedView ); | |
| _namedView.delete(); | |
| } | |
| // Handle groups | |
| for ( let i = 0; i < doc.groups().count; i ++ ) { | |
| const _group = doc.groups().get( i ); | |
| const group = extractProperties( _group ); | |
| groups.push( group ); | |
| _group.delete(); | |
| } | |
| // Handle settings | |
| const settings = extractProperties( doc.settings() ); | |
| //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc. | |
| // Handle dimstyles | |
| // console.log( `Dimstyle Count: ${doc.dimstyles().count()}` ); | |
| // Handle bitmaps | |
| // console.log( `Bitmap Count: ${doc.bitmaps().count()}` ); | |
| // Handle strings | |
| // console.log( `Document Strings Count: ${doc.strings().count()}` ); | |
| // Note: doc.strings().documentUserTextCount() counts any doc.strings defined in a section | |
| // console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` ); | |
| const strings_count = doc.strings().count; | |
| for ( let i = 0; i < strings_count; i ++ ) { | |
| strings.push( doc.strings().get( i ) ); | |
| } | |
| // Handle Render Environments for Material Environment | |
| // get the id of the active render environment skylight, which we'll use for environment texture | |
| const reflectionId = doc.settings().renderSettings().renderEnvironments.reflectionId | |
| const rc = doc.renderContent() | |
| let renderEnvironment = null | |
| for( let i = 0; i < rc.count; i++ ) { | |
| const content = rc.get(i) | |
| switch( content.kind ) { | |
| case 'environment': | |
| const id = content.id | |
| // there could be multiple render environments in a 3dm file | |
| if ( id !== reflectionId ) break; | |
| const renderTexture = content.findChild( 'texture' ) | |
| const fileName = renderTexture.fileName | |
| for ( let j = 0; j < doc.embeddedFiles().count; j ++ ) { | |
| const _fileName = doc.embeddedFiles().get( j ).fileName | |
| if ( fileName === _fileName ) { | |
| const background = doc.getEmbeddedFileAsBase64( fileName ) | |
| const backgroundImage = 'data:image/png;base64,' + background | |
| renderEnvironment = { type: 'renderEnvironment', image: backgroundImage, name: fileName }; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| // Handle Render Settings | |
| const renderSettings = { | |
| ambientLight: doc.settings().renderSettings().ambientLight, | |
| backgroundColorTop: doc.settings().renderSettings().backgroundColorTop, | |
| backgroundColorBottom: doc.settings().renderSettings().backgroundColorBottom, | |
| useHiddenLights: doc.settings().renderSettings().useHiddenLights, | |
| depthCue: doc.settings().renderSettings().depthCue, | |
| flatShade: doc.settings().renderSettings().flatShade, | |
| renderBackFaces: doc.settings().renderSettings().renderBackFaces, | |
| renderPoints: doc.settings().renderSettings().renderPoints, | |
| renderCurves: doc.settings().renderSettings().renderCurves, | |
| renderIsoParams: doc.settings().renderSettings().renderIsoParams, | |
| renderMeshEdges: doc.settings().renderSettings().renderMeshEdges, | |
| renderAnnotations: doc.settings().renderSettings().renderAnnotations, | |
| useViewportSize: doc.settings().renderSettings().useViewportSize, | |
| scaleBackgroundToFit: doc.settings().renderSettings().scaleBackgroundToFit, | |
| transparentBackground: doc.settings().renderSettings().transparentBackground, | |
| imageDpi: doc.settings().renderSettings().imageDpi, | |
| shadowMapLevel: doc.settings().renderSettings().shadowMapLevel, | |
| namedView: doc.settings().renderSettings().namedView, | |
| snapShot: doc.settings().renderSettings().snapShot, | |
| specificViewport: doc.settings().renderSettings().specificViewport, | |
| groundPlane: extractProperties( doc.settings().renderSettings().groundPlane ), | |
| safeFrame: extractProperties( doc.settings().renderSettings().safeFrame ), | |
| dithering: extractProperties( doc.settings().renderSettings().dithering ), | |
| skylight: extractProperties( doc.settings().renderSettings().skylight ), | |
| linearWorkflow: extractProperties( doc.settings().renderSettings().linearWorkflow ), | |
| renderChannels: extractProperties( doc.settings().renderSettings().renderChannels ), | |
| sun: extractProperties( doc.settings().renderSettings().sun ), | |
| renderEnvironments: extractProperties( doc.settings().renderSettings().renderEnvironments ), | |
| postEffects: extractProperties( doc.settings().renderSettings().postEffects ), | |
| } | |
| doc.delete(); | |
| return { objects, materials, layers, views, namedViews, groups, strings, settings, renderSettings, renderEnvironment }; | |
| } | |
| function extractTextures( m, tTypes, d ) { | |
| const textures = [] | |
| for ( let i = 0; i < tTypes.length; i ++ ) { | |
| const _texture = m.getTexture( tTypes[ i ] ); | |
| if ( _texture ) { | |
| let textureType = tTypes[ i ].constructor.name; | |
| textureType = textureType.substring( 12, textureType.length ); | |
| const texture = extractTextureData( _texture, textureType, d ); | |
| textures.push( texture ); | |
| _texture.delete(); | |
| } | |
| } | |
| return textures; | |
| } | |
| function extractTextureData( t, tType, d ) { | |
| const texture = { type: tType }; | |
| const image = d.getEmbeddedFileAsBase64( t.fileName ); | |
| texture.wrapU = t.wrapU; | |
| texture.wrapV = t.wrapV; | |
| texture.wrapW = t.wrapW; | |
| const uvw = t.uvwTransform.toFloatArray( true ); | |
| texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ]; | |
| if ( image ) { | |
| texture.image = 'data:image/png;base64,' + image; | |
| } else { | |
| self.postMessage( { type: 'warning', id: taskID, data: { | |
| message: `THREE.3DMLoader: Image for ${tType} texture not embedded in file.`, | |
| type: 'missing resource' | |
| } | |
| } ); | |
| texture.image = null; | |
| } | |
| return texture; | |
| } | |
| function extractObjectData( object, doc ) { | |
| const _geometry = object.geometry(); | |
| const _attributes = object.attributes(); | |
| let objectType = _geometry.objectType; | |
| let geometry, attributes, position, data, mesh; | |
| // skip instance definition objects | |
| //if( _attributes.isInstanceDefinitionObject ) { continue; } | |
| // TODO: handle other geometry types | |
| switch ( objectType ) { | |
| case rhino.ObjectType.Curve: | |
| const pts = curveToPoints( _geometry, 100 ); | |
| position = {}; | |
| attributes = {}; | |
| data = {}; | |
| position.itemSize = 3; | |
| position.type = 'Float32Array'; | |
| position.array = []; | |
| for ( let j = 0; j < pts.length; j ++ ) { | |
| position.array.push( pts[ j ][ 0 ] ); | |
| position.array.push( pts[ j ][ 1 ] ); | |
| position.array.push( pts[ j ][ 2 ] ); | |
| } | |
| attributes.position = position; | |
| data.attributes = attributes; | |
| geometry = { data }; | |
| break; | |
| case rhino.ObjectType.Point: | |
| const pt = _geometry.location; | |
| position = {}; | |
| const color = {}; | |
| attributes = {}; | |
| data = {}; | |
| position.itemSize = 3; | |
| position.type = 'Float32Array'; | |
| position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ]; | |
| const _color = _attributes.drawColor( doc ); | |
| color.itemSize = 3; | |
| color.type = 'Float32Array'; | |
| color.array = [ _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ]; | |
| attributes.position = position; | |
| attributes.color = color; | |
| data.attributes = attributes; | |
| geometry = { data }; | |
| break; | |
| case rhino.ObjectType.PointSet: | |
| case rhino.ObjectType.Mesh: | |
| geometry = _geometry.toThreejsJSON(); | |
| break; | |
| case rhino.ObjectType.Brep: | |
| const faces = _geometry.faces(); | |
| mesh = new rhino.Mesh(); | |
| for ( let faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) { | |
| const face = faces.get( faceIndex ); | |
| const _mesh = face.getMesh( rhino.MeshType.Any ); | |
| if ( _mesh ) { | |
| mesh.append( _mesh ); | |
| _mesh.delete(); | |
| } | |
| face.delete(); | |
| } | |
| if ( mesh.faces().count > 0 ) { | |
| mesh.compact(); | |
| geometry = mesh.toThreejsJSON(); | |
| faces.delete(); | |
| } | |
| mesh.delete(); | |
| break; | |
| case rhino.ObjectType.Extrusion: | |
| mesh = _geometry.getMesh( rhino.MeshType.Any ); | |
| if ( mesh ) { | |
| geometry = mesh.toThreejsJSON(); | |
| mesh.delete(); | |
| } | |
| break; | |
| case rhino.ObjectType.TextDot: | |
| geometry = extractProperties( _geometry ); | |
| break; | |
| case rhino.ObjectType.Light: | |
| geometry = extractProperties( _geometry ); | |
| if ( geometry.lightStyle.name === 'LightStyle_WorldLinear' ) { | |
| self.postMessage( { type: 'warning', id: taskID, data: { | |
| message: `THREE.3DMLoader: No conversion exists for ${objectType.constructor.name} ${geometry.lightStyle.name}`, | |
| type: 'no conversion', | |
| guid: _attributes.id | |
| } | |
| } ); | |
| } | |
| break; | |
| case rhino.ObjectType.InstanceReference: | |
| geometry = extractProperties( _geometry ); | |
| geometry.xform = extractProperties( _geometry.xform ); | |
| geometry.xform.array = _geometry.xform.toFloatArray( true ); | |
| break; | |
| case rhino.ObjectType.SubD: | |
| // TODO: precalculate resulting vertices and faces and warn on excessive results | |
| _geometry.subdivide( 3 ); | |
| mesh = rhino.Mesh.createFromSubDControlNet( _geometry ); | |
| if ( mesh ) { | |
| geometry = mesh.toThreejsJSON(); | |
| mesh.delete(); | |
| } | |
| break; | |
| /* | |
| case rhino.ObjectType.Annotation: | |
| case rhino.ObjectType.Hatch: | |
| case rhino.ObjectType.ClipPlane: | |
| */ | |
| default: | |
| self.postMessage( { type: 'warning', id: taskID, data: { | |
| message: `THREE.3DMLoader: Conversion not implemented for ${objectType.constructor.name}`, | |
| type: 'not implemented', | |
| guid: _attributes.id | |
| } | |
| } ); | |
| break; | |
| } | |
| if ( geometry ) { | |
| attributes = extractProperties( _attributes ); | |
| attributes.geometry = extractProperties( _geometry ); | |
| if ( _attributes.groupCount > 0 ) { | |
| attributes.groupIds = _attributes.getGroupList(); | |
| } | |
| if ( _attributes.userStringCount > 0 ) { | |
| attributes.userStrings = _attributes.getUserStrings(); | |
| } | |
| if ( _geometry.userStringCount > 0 ) { | |
| attributes.geometry.userStrings = _geometry.getUserStrings(); | |
| } | |
| if ( _attributes.decals().count > 0 ) { | |
| self.postMessage( { type: 'warning', id: taskID, data: { | |
| message: `THREE.3DMLoader: No conversion exists for the decals associated with this object.`, | |
| type: 'no conversion', | |
| guid: _attributes.id | |
| } | |
| } ); | |
| } | |
| attributes.drawColor = _attributes.drawColor( doc ); | |
| objectType = objectType.constructor.name; | |
| objectType = objectType.substring( 11, objectType.length ); | |
| return { geometry, attributes, objectType }; | |
| } else { | |
| self.postMessage( { type: 'warning', id: taskID, data: { | |
| message: `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.`, | |
| type: 'missing mesh', | |
| guid: _attributes.id | |
| } | |
| } ); | |
| } | |
| } | |
| function extractProperties( object ) { | |
| const result = {}; | |
| for ( const property in object ) { | |
| const value = object[ property ]; | |
| if ( typeof value !== 'function' ) { | |
| if ( typeof value === 'object' && value !== null && value.hasOwnProperty( 'constructor' ) ) { | |
| result[ property ] = { name: value.constructor.name, value: value.value }; | |
| } else if ( typeof value === 'object' && value !== null ) { | |
| result[ property ] = extractProperties( value ); | |
| } else { | |
| result[ property ] = value; | |
| } | |
| } else { | |
| // these are functions that could be called to extract more data. | |
| //console.log( `${property}: ${object[ property ].constructor.name}` ); | |
| } | |
| } | |
| return result; | |
| } | |
| function curveToPoints( curve, pointLimit ) { | |
| let pointCount = pointLimit; | |
| let rc = []; | |
| const ts = []; | |
| if ( curve instanceof rhino.LineCurve ) { | |
| return [ curve.pointAtStart, curve.pointAtEnd ]; | |
| } | |
| if ( curve instanceof rhino.PolylineCurve ) { | |
| pointCount = curve.pointCount; | |
| for ( let i = 0; i < pointCount; i ++ ) { | |
| rc.push( curve.point( i ) ); | |
| } | |
| return rc; | |
| } | |
| if ( curve instanceof rhino.PolyCurve ) { | |
| const segmentCount = curve.segmentCount; | |
| for ( let i = 0; i < segmentCount; i ++ ) { | |
| const segment = curve.segmentCurve( i ); | |
| const segmentArray = curveToPoints( segment, pointCount ); | |
| rc = rc.concat( segmentArray ); | |
| segment.delete(); | |
| } | |
| return rc; | |
| } | |
| if ( curve instanceof rhino.ArcCurve ) { | |
| pointCount = Math.floor( curve.angleDegrees / 5 ); | |
| pointCount = pointCount < 2 ? 2 : pointCount; | |
| // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399 | |
| } | |
| if ( curve instanceof rhino.NurbsCurve && curve.degree === 1 ) { | |
| const pLine = curve.tryGetPolyline(); | |
| for ( let i = 0; i < pLine.count; i ++ ) { | |
| rc.push( pLine.get( i ) ); | |
| } | |
| pLine.delete(); | |
| return rc; | |
| } | |
| const domain = curve.domain; | |
| const divisions = pointCount - 1.0; | |
| for ( let j = 0; j < pointCount; j ++ ) { | |
| const t = domain[ 0 ] + ( j / divisions ) * ( domain[ 1 ] - domain[ 0 ] ); | |
| if ( t === domain[ 0 ] || t === domain[ 1 ] ) { | |
| ts.push( t ); | |
| continue; | |
| } | |
| const tan = curve.tangentAt( t ); | |
| const prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] ); | |
| // Duplicated from THREE.Vector3 | |
| // How to pass imports to worker? | |
| const tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ]; | |
| const ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ]; | |
| const denominator = Math.sqrt( tS * ptS ); | |
| let angle; | |
| if ( denominator === 0 ) { | |
| angle = Math.PI / 2; | |
| } else { | |
| const theta = ( tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z ) / denominator; | |
| angle = Math.acos( Math.max( - 1, Math.min( 1, theta ) ) ); | |
| } | |
| if ( angle < 0.1 ) continue; | |
| ts.push( t ); | |
| } | |
| rc = ts.map( t => curve.pointAt( t ) ); | |
| return rc; | |
| } | |
| } | |
| export { Rhino3dmLoader }; | |
Xet Storage Details
- Size:
- 37.6 kB
- Xet hash:
- 89b4a62ad212750e2e6c8436807b1b5b47714374348c7521c3dd13c676a6e223
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.